Paid Media: Follow-up (#4805)

This commit is contained in:
zubiden 2024-08-06 20:06:23 +02:00 committed by Alexander Zinchuk
parent 9ad622e416
commit 3bd0f7130c
11 changed files with 34 additions and 19 deletions

View File

@ -180,7 +180,7 @@ export function buildVideoFromDocument(document: GramJs.Document, isSpoiler?: bo
} }
const { const {
id, mimeType, thumbs, size, attributes, id, mimeType, thumbs, size, videoThumbs, attributes,
} = document; } = document;
// eslint-disable-next-line no-restricted-globals // eslint-disable-next-line no-restricted-globals
@ -189,14 +189,16 @@ export function buildVideoFromDocument(document: GramJs.Document, isSpoiler?: bo
} }
const videoAttr = attributes const videoAttr = attributes
.find((a: any): a is GramJs.DocumentAttributeVideo => a instanceof GramJs.DocumentAttributeVideo); .find((a): a is GramJs.DocumentAttributeVideo => a instanceof GramJs.DocumentAttributeVideo);
if (!videoAttr) { if (!videoAttr) {
return undefined; return undefined;
} }
const gifAttr = attributes const gifAttr = attributes
.find((a: any): a is GramJs.DocumentAttributeAnimated => a instanceof GramJs.DocumentAttributeAnimated); .find((a): a is GramJs.DocumentAttributeAnimated => a instanceof GramJs.DocumentAttributeAnimated);
const hasVideoPreview = videoThumbs?.some((thumb) => thumb instanceof GramJs.VideoSize && thumb.type === 'v');
const { const {
duration, duration,
@ -221,6 +223,7 @@ export function buildVideoFromDocument(document: GramJs.Document, isSpoiler?: bo
thumbnail: buildApiThumbnailFromStripped(thumbs), thumbnail: buildApiThumbnailFromStripped(thumbs),
size: size.toJSNumber(), size: size.toJSNumber(),
isSpoiler, isSpoiler,
hasVideoPreview,
...(nosound && { noSound: true }), ...(nosound && { noSound: true }),
}; };
} }

View File

@ -98,6 +98,7 @@ export interface ApiVideo {
supportsStreaming?: boolean; supportsStreaming?: boolean;
isRound?: boolean; isRound?: boolean;
isGif?: boolean; isGif?: boolean;
hasVideoPreview?: boolean;
isSpoiler?: boolean; isSpoiler?: boolean;
thumbnail?: ApiThumbnail; thumbnail?: ApiThumbnail;
blobUrl?: string; blobUrl?: string;

View File

@ -5,9 +5,8 @@ import React, {
import type { ApiVideo } from '../../api/types'; import type { ApiVideo } from '../../api/types';
import type { ObserveFn } from '../../hooks/useIntersectionObserver'; import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import { ApiMediaFormat } from '../../api/types';
import { getVideoMediaHash } from '../../global/helpers'; import { getVideoMediaHash, getVideoPreviewMediaHash } from '../../global/helpers';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment'; import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur'; import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur';
@ -55,10 +54,15 @@ const GifButton: FC<OwnProps> = ({
const isIntersecting = useIsIntersecting(ref, observeIntersection); const isIntersecting = useIsIntersecting(ref, observeIntersection);
const loadAndPlay = isIntersecting && !isDisabled; const loadAndPlay = isIntersecting && !isDisabled;
const previewBlobUrl = useMedia(getVideoMediaHash(gif, 'preview'), !loadAndPlay, ApiMediaFormat.BlobUrl); const previewHash = !gif.hasVideoPreview && gif.thumbnail && getVideoMediaHash(gif, 'pictogram');
const previewBlobUrl = useMedia(previewHash, !loadAndPlay);
const [withThumb] = useState(gif.thumbnail?.dataUri && !previewBlobUrl); const [withThumb] = useState(gif.thumbnail?.dataUri && !previewBlobUrl);
const thumbRef = useCanvasBlur(gif.thumbnail?.dataUri, !withThumb); const thumbRef = useCanvasBlur(gif.thumbnail?.dataUri, !withThumb);
const videoData = useMedia(getVideoMediaHash(gif, 'full'), !loadAndPlay, ApiMediaFormat.BlobUrl);
const videoHash = getVideoPreviewMediaHash(gif) || getVideoMediaHash(gif, 'full');
const videoData = useMedia(videoHash, !loadAndPlay);
const shouldRenderVideo = Boolean(loadAndPlay && videoData); const shouldRenderVideo = Boolean(loadAndPlay && videoData);
const { isBuffered, bufferingHandlers } = useBuffering(true); const { isBuffered, bufferingHandlers } = useBuffering(true);
const shouldRenderSpinner = loadAndPlay && !isBuffered; const shouldRenderSpinner = loadAndPlay && !isBuffered;
@ -154,8 +158,6 @@ const GifButton: FC<OwnProps> = ({
<canvas <canvas
ref={thumbRef} ref={thumbRef}
className="thumbnail canvas-blur-setup" className="thumbnail canvas-blur-setup"
// We need to always render to avoid blur re-calculation
style={isVideoReady ? 'display: none;' : undefined}
/> />
)} )}
{previewBlobUrl && !isVideoReady && ( {previewBlobUrl && !isVideoReady && (

View File

@ -124,6 +124,7 @@ const Album: FC<OwnProps & StateProps> = ({
onCancelUpload={handleCancelUpload} onCancelUpload={handleCancelUpload}
isDownloading={photo.mediaType !== 'extendedMediaPreview' && getIsDownloading(activeDownloads, photo)} isDownloading={photo.mediaType !== 'extendedMediaPreview' && getIsDownloading(activeDownloads, photo)}
theme={theme} theme={theme}
noSelectControls={album.isPaidMedia}
/> />
); );
} else if (video) { } else if (video) {
@ -142,6 +143,7 @@ const Album: FC<OwnProps & StateProps> = ({
onCancelUpload={handleCancelUpload} onCancelUpload={handleCancelUpload}
isDownloading={video.mediaType !== 'extendedMediaPreview' && getIsDownloading(activeDownloads, video)} isDownloading={video.mediaType !== 'extendedMediaPreview' && getIsDownloading(activeDownloads, video)}
theme={theme} theme={theme}
noSelectControls={album.isPaidMedia}
/> />
); );
} }

View File

@ -919,7 +919,7 @@
--border-bottom-left-radius: var(--border-radius-messages-small); --border-bottom-left-radius: var(--border-radius-messages-small);
--border-bottom-right-radius: var(--border-radius-messages-small); --border-bottom-right-radius: var(--border-radius-messages-small);
> .media-inner { .media-inner {
margin: 0 !important; margin: 0 !important;
margin-bottom: 0.25rem !important; margin-bottom: 0.25rem !important;
} }

View File

@ -17,6 +17,7 @@ import useLastCallback from '../../../../hooks/useLastCallback';
type OwnProps<T> = type OwnProps<T> =
(PhotoProps<T> | VideoProps<T>) & { (PhotoProps<T> | VideoProps<T>) & {
clickArg: number; clickArg: number;
noSelectControls?: boolean;
}; };
type StateProps = { type StateProps = {
@ -76,10 +77,10 @@ export default function withSelectControl(WrappedComponent: FC) {
return memo(withGlobal<OwnProps<unknown>>( return memo(withGlobal<OwnProps<unknown>>(
(global, ownProps) => { (global, ownProps) => {
const { clickArg } = ownProps; const { clickArg, noSelectControls } = ownProps;
return { return {
isInSelectMode: selectIsInSelectMode(global), isInSelectMode: !noSelectControls && selectIsInSelectMode(global),
isSelected: selectIsMessageSelected(global, clickArg), isSelected: !noSelectControls && selectIsMessageSelected(global, clickArg),
}; };
}, },
)(ComponentWithSelectControl)) as typeof ComponentWithSelectControl; )(ComponentWithSelectControl)) as typeof ComponentWithSelectControl;

View File

@ -104,7 +104,6 @@ export default function useInnerHandlers(
}); });
const openMediaViewerWithPhotoOrVideo = useLastCallback((withDynamicLoading: boolean): void => { const openMediaViewerWithPhotoOrVideo = useLastCallback((withDynamicLoading: boolean): void => {
if (paidMedia && !paidMedia.isBought) return; if (paidMedia && !paidMedia.isBought) return;
if (paidMedia) return; // TODO: Implement MV and remove this line
if (withDynamicLoading) { if (withDynamicLoading) {
searchChatMediaMessages({ chatId, threadId, currentMediaMessageId: messageId }); searchChatMediaMessages({ chatId, threadId, currentMediaMessageId: messageId });
} }
@ -117,12 +116,12 @@ export default function useInnerHandlers(
}); });
}); });
const handlePhotoMediaClick = useLastCallback((): void => { const handlePhotoMediaClick = useLastCallback((): void => {
const withDynamicLoading = !isScheduled; const withDynamicLoading = !isScheduled && !paidMedia;
openMediaViewerWithPhotoOrVideo(withDynamicLoading); openMediaViewerWithPhotoOrVideo(withDynamicLoading);
}); });
const handleVideoMediaClick = useLastCallback(() => { const handleVideoMediaClick = useLastCallback(() => {
const isGif = message.content?.video?.isGif; const isGif = message.content?.video?.isGif;
const withDynamicLoading = !isGif && !isScheduled; const withDynamicLoading = !isGif && !isScheduled && !paidMedia;
openMediaViewerWithPhotoOrVideo(withDynamicLoading); openMediaViewerWithPhotoOrVideo(withDynamicLoading);
}); });

View File

@ -10,6 +10,7 @@ import { getUserFullName } from '../../../global/helpers';
import { selectUser } from '../../../global/selectors'; import { selectUser } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { formatCurrency } from '../../../util/formatCurrency'; import { formatCurrency } from '../../../util/formatCurrency';
import { formatInteger } from '../../../util/textFormat';
import renderText from '../../common/helpers/renderText'; import renderText from '../../common/helpers/renderText';
import useFlag from '../../../hooks/useFlag'; import useFlag from '../../../hooks/useFlag';
@ -233,7 +234,7 @@ function StarTopupOption({
return ( return (
<div className={styles.option} key={option.stars} onClick={() => onClick?.(option)}> <div className={styles.option} key={option.stars} onClick={() => onClick?.(option)}>
<div className={styles.optionTop}> <div className={styles.optionTop}>
+{option.stars} +{formatInteger(option.stars)}
{/* Switch directionality for correct order. Can't use flex because https://issues.chromium.org/issues/40249030 */} {/* Switch directionality for correct order. Can't use flex because https://issues.chromium.org/issues/40249030 */}
<div className={styles.stackedStars} dir={lang.isRtl ? 'ltr' : 'rtl'}> <div className={styles.stackedStars} dir={lang.isRtl ? 'ltr' : 'rtl'}>
{Array.from({ length: starsCount }).map(() => ( {Array.from({ length: starsCount }).map(() => (

View File

@ -699,7 +699,9 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
} else { } else {
const content = media as MediaContent; const content = media as MediaContent;
global = updateChatMessage(global, chatId, id, { global = updateChatMessage(global, chatId, id, {
content, content: {
...content,
},
}); });
setGlobal(global); setGlobal(global);
} }

View File

@ -319,6 +319,10 @@ export function getVideoMediaHash(video: ApiVideo | ApiDocument, target: Target)
} }
} }
export function getVideoPreviewMediaHash(video: ApiVideo) {
return video.hasVideoPreview ? `document${video.id}?size=v` : undefined;
}
export function getDocumentMediaHash(document: ApiDocument, target: Target) { export function getDocumentMediaHash(document: ApiDocument, target: Target) {
const base = `document${document.id}`; const base = `document${document.id}`;

View File

@ -1245,7 +1245,7 @@ export function selectCanForwardMessages<T extends GlobalState>(global: T, chatI
return messageIds return messageIds
.map((id) => messages[id]) .map((id) => messages[id])
.every((message) => !hasMessageTtl(message) .every((message) => message && !hasMessageTtl(message)
&& (message.isForwardingAllowed || isServiceNotificationMessage(message))); && (message.isForwardingAllowed || isServiceNotificationMessage(message)));
} }