Message / Video: Better progressive loading
This commit is contained in:
parent
128d25047f
commit
a050b10aa8
@ -110,7 +110,7 @@ const ProfilePhoto: FC<OwnProps> = ({
|
|||||||
disablePictureInPicture
|
disablePictureInPicture
|
||||||
loop
|
loop
|
||||||
playsInline
|
playsInline
|
||||||
onPlay={markVideoReady}
|
onReady={markVideoReady}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<img
|
<img
|
||||||
|
|||||||
@ -170,7 +170,7 @@ const StickerView: FC<OwnProps> = ({
|
|||||||
muted
|
muted
|
||||||
loop={!loopLimit}
|
loop={!loopLimit}
|
||||||
disablePictureInPicture
|
disablePictureInPicture
|
||||||
onPlay={markPlayerReady}
|
onReady={markPlayerReady}
|
||||||
onEnded={onVideoEnded}
|
onEnded={onVideoEnded}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -338,7 +338,7 @@ function getNodes(origin: MediaViewerOrigin, message?: ApiMessage) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
container,
|
container,
|
||||||
mediaEl: mediaEls?.[mediaEls.length - 1],
|
mediaEl: mediaEls?.[0],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -73,7 +73,7 @@ export const useMediaProps = ({
|
|||||||
return getChatAvatarHash(avatarOwner, isFull ? 'big' : 'normal');
|
return getChatAvatarHash(avatarOwner, isFull ? 'big' : 'normal');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return message && getMessageMediaHash(message, isFull ? 'viewerFull' : 'viewerPreview');
|
return message && getMessageMediaHash(message, isFull ? 'full' : 'preview');
|
||||||
}, [avatarOwner, message, avatarMedia, mediaId]);
|
}, [avatarOwner, message, avatarMedia, mediaId]);
|
||||||
|
|
||||||
const pictogramBlobUrl = useMedia(
|
const pictogramBlobUrl = useMedia(
|
||||||
@ -97,7 +97,7 @@ export const useMediaProps = ({
|
|||||||
} = useMediaWithLoadProgress(
|
} = useMediaWithLoadProgress(
|
||||||
getMediaHash(true),
|
getMediaHash(true),
|
||||||
undefined,
|
undefined,
|
||||||
message && getMessageMediaFormat(message, 'viewerFull'),
|
message && getMessageMediaFormat(message, 'full'),
|
||||||
undefined,
|
undefined,
|
||||||
delay,
|
delay,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -81,9 +81,9 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
const { openHistoryCalendar } = getActions();
|
const { openHistoryCalendar } = getActions();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
observeIntersectionForMedia,
|
|
||||||
observeIntersectionForReading,
|
observeIntersectionForReading,
|
||||||
observeIntersectionForAnimatedStickers,
|
observeIntersectionForLoading,
|
||||||
|
observeIntersectionForPlaying,
|
||||||
} = useMessageObservers(type, containerRef, memoFirstUnreadIdRef);
|
} = useMessageObservers(type, containerRef, memoFirstUnreadIdRef);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -144,8 +144,8 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
key={message.id}
|
key={message.id}
|
||||||
message={message}
|
message={message}
|
||||||
observeIntersectionForReading={observeIntersectionForReading}
|
observeIntersectionForReading={observeIntersectionForReading}
|
||||||
observeIntersectionForLoading={observeIntersectionForMedia}
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||||
observeIntersectionForPlaying={observeIntersectionForAnimatedStickers}
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||||
memoFirstUnreadIdRef={memoFirstUnreadIdRef}
|
memoFirstUnreadIdRef={memoFirstUnreadIdRef}
|
||||||
appearanceOrder={messageCountToAnimate - ++appearanceIndex}
|
appearanceOrder={messageCountToAnimate - ++appearanceIndex}
|
||||||
isLastInList={isLastInList}
|
isLastInList={isLastInList}
|
||||||
@ -196,8 +196,8 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
key={key}
|
key={key}
|
||||||
message={message}
|
message={message}
|
||||||
observeIntersectionForBottom={observeIntersectionForReading}
|
observeIntersectionForBottom={observeIntersectionForReading}
|
||||||
observeIntersectionForMedia={observeIntersectionForMedia}
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||||
observeIntersectionForAnimatedStickers={observeIntersectionForAnimatedStickers}
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||||
album={album}
|
album={album}
|
||||||
noAvatars={noAvatars}
|
noAvatars={noAvatars}
|
||||||
withAvatar={position.isLastInGroup && withUsers && !isOwn && !(message.id === threadTopMessageId)}
|
withAvatar={position.isLastInGroup && withUsers && !isOwn && !(message.id === threadTopMessageId)}
|
||||||
|
|||||||
@ -7,9 +7,9 @@ import { IS_ANDROID, IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
|||||||
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||||
import useBackgroundMode from '../../../hooks/useBackgroundMode';
|
import useBackgroundMode from '../../../hooks/useBackgroundMode';
|
||||||
|
|
||||||
const INTERSECTION_THROTTLE_FOR_MEDIA = IS_ANDROID ? 1000 : 350;
|
|
||||||
const INTERSECTION_MARGIN_FOR_MEDIA = IS_SINGLE_COLUMN_LAYOUT ? 300 : 500;
|
|
||||||
const INTERSECTION_THROTTLE_FOR_READING = 150;
|
const INTERSECTION_THROTTLE_FOR_READING = 150;
|
||||||
|
const INTERSECTION_THROTTLE_FOR_MEDIA = IS_ANDROID ? 1000 : 350;
|
||||||
|
const INTERSECTION_MARGIN_FOR_LOADING = IS_SINGLE_COLUMN_LAYOUT ? 300 : 500;
|
||||||
|
|
||||||
export default function useMessageObservers(
|
export default function useMessageObservers(
|
||||||
type: MessageListType,
|
type: MessageListType,
|
||||||
@ -18,14 +18,6 @@ export default function useMessageObservers(
|
|||||||
) {
|
) {
|
||||||
const { markMessageListRead, markMentionsRead, animateUnreadReaction } = getActions();
|
const { markMessageListRead, markMentionsRead, animateUnreadReaction } = getActions();
|
||||||
|
|
||||||
const {
|
|
||||||
observe: observeIntersectionForMedia,
|
|
||||||
} = useIntersectionObserver({
|
|
||||||
rootRef: containerRef,
|
|
||||||
throttleMs: INTERSECTION_THROTTLE_FOR_MEDIA,
|
|
||||||
margin: INTERSECTION_MARGIN_FOR_MEDIA,
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
observe: observeIntersectionForReading, freeze: freezeForReading, unfreeze: unfreezeForReading,
|
observe: observeIntersectionForReading, freeze: freezeForReading, unfreeze: unfreezeForReading,
|
||||||
} = useIntersectionObserver({
|
} = useIntersectionObserver({
|
||||||
@ -78,14 +70,22 @@ export default function useMessageObservers(
|
|||||||
|
|
||||||
useBackgroundMode(freezeForReading, unfreezeForReading);
|
useBackgroundMode(freezeForReading, unfreezeForReading);
|
||||||
|
|
||||||
const { observe: observeIntersectionForAnimatedStickers } = useIntersectionObserver({
|
const {
|
||||||
|
observe: observeIntersectionForLoading,
|
||||||
|
} = useIntersectionObserver({
|
||||||
|
rootRef: containerRef,
|
||||||
|
throttleMs: INTERSECTION_THROTTLE_FOR_MEDIA,
|
||||||
|
margin: INTERSECTION_MARGIN_FOR_LOADING,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { observe: observeIntersectionForPlaying } = useIntersectionObserver({
|
||||||
rootRef: containerRef,
|
rootRef: containerRef,
|
||||||
throttleMs: INTERSECTION_THROTTLE_FOR_MEDIA,
|
throttleMs: INTERSECTION_THROTTLE_FOR_MEDIA,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
observeIntersectionForMedia,
|
|
||||||
observeIntersectionForReading,
|
observeIntersectionForReading,
|
||||||
observeIntersectionForAnimatedStickers,
|
observeIntersectionForLoading,
|
||||||
|
observeIntersectionForPlaying,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,7 +84,7 @@ const Album: FC<OwnProps & StateProps> = ({
|
|||||||
<PhotoWithSelect
|
<PhotoWithSelect
|
||||||
id={`album-media-${getMessageHtmlId(message.id)}`}
|
id={`album-media-${getMessageHtmlId(message.id)}`}
|
||||||
message={message}
|
message={message}
|
||||||
observeIntersection={observeIntersection}
|
observeIntersectionForLoading={observeIntersection}
|
||||||
canAutoLoad={canAutoLoad}
|
canAutoLoad={canAutoLoad}
|
||||||
shouldAffectAppendix={shouldAffectAppendix}
|
shouldAffectAppendix={shouldAffectAppendix}
|
||||||
uploadProgress={uploadProgress}
|
uploadProgress={uploadProgress}
|
||||||
@ -101,7 +101,7 @@ const Album: FC<OwnProps & StateProps> = ({
|
|||||||
<VideoWithSelect
|
<VideoWithSelect
|
||||||
id={`album-media-${getMessageHtmlId(message.id)}`}
|
id={`album-media-${getMessageHtmlId(message.id)}`}
|
||||||
message={message}
|
message={message}
|
||||||
observeIntersection={observeIntersection}
|
observeIntersectionForLoading={observeIntersection}
|
||||||
canAutoLoad={canAutoLoad}
|
canAutoLoad={canAutoLoad}
|
||||||
canAutoPlay={canAutoPlay}
|
canAutoPlay={canAutoPlay}
|
||||||
uploadProgress={uploadProgress}
|
uploadProgress={uploadProgress}
|
||||||
|
|||||||
@ -143,8 +143,8 @@ type OwnProps =
|
|||||||
{
|
{
|
||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
observeIntersectionForBottom: ObserveFn;
|
observeIntersectionForBottom: ObserveFn;
|
||||||
observeIntersectionForMedia: ObserveFn;
|
observeIntersectionForLoading: ObserveFn;
|
||||||
observeIntersectionForAnimatedStickers: ObserveFn;
|
observeIntersectionForPlaying: ObserveFn;
|
||||||
album?: IAlbum;
|
album?: IAlbum;
|
||||||
noAvatars?: boolean;
|
noAvatars?: boolean;
|
||||||
withAvatar?: boolean;
|
withAvatar?: boolean;
|
||||||
@ -235,8 +235,8 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
message,
|
message,
|
||||||
chatUsername,
|
chatUsername,
|
||||||
observeIntersectionForBottom,
|
observeIntersectionForBottom,
|
||||||
observeIntersectionForMedia,
|
observeIntersectionForLoading,
|
||||||
observeIntersectionForAnimatedStickers,
|
observeIntersectionForPlaying,
|
||||||
album,
|
album,
|
||||||
noAvatars,
|
noAvatars,
|
||||||
withAvatar,
|
withAvatar,
|
||||||
@ -634,7 +634,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
text={hiddenName}
|
text={hiddenName}
|
||||||
lastSyncTime={lastSyncTime}
|
lastSyncTime={lastSyncTime}
|
||||||
onClick={(avatarUser || avatarChat) ? handleAvatarClick : undefined}
|
onClick={(avatarUser || avatarChat) ? handleAvatarClick : undefined}
|
||||||
observeIntersection={observeIntersectionForMedia}
|
observeIntersection={observeIntersectionForLoading}
|
||||||
animationLevel={animationLevel}
|
animationLevel={animationLevel}
|
||||||
withVideo
|
withVideo
|
||||||
/>
|
/>
|
||||||
@ -693,16 +693,16 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
noUserColors={isOwn}
|
noUserColors={isOwn}
|
||||||
isProtected={isProtected}
|
isProtected={isProtected}
|
||||||
sender={replyMessageSender}
|
sender={replyMessageSender}
|
||||||
observeIntersectionForLoading={observeIntersectionForMedia}
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||||
observeIntersectionForPlaying={observeIntersectionForAnimatedStickers}
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||||
onClick={handleReplyClick}
|
onClick={handleReplyClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{sticker && (
|
{sticker && (
|
||||||
<Sticker
|
<Sticker
|
||||||
message={message}
|
message={message}
|
||||||
observeIntersection={observeIntersectionForMedia}
|
observeIntersection={observeIntersectionForLoading}
|
||||||
observeIntersectionForPlaying={observeIntersectionForAnimatedStickers}
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||||
shouldLoop={shouldLoopStickers}
|
shouldLoop={shouldLoopStickers}
|
||||||
lastSyncTime={lastSyncTime}
|
lastSyncTime={lastSyncTime}
|
||||||
shouldPlayEffect={(
|
shouldPlayEffect={(
|
||||||
@ -719,7 +719,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
customEmojiId={animatedCustomEmoji}
|
customEmojiId={animatedCustomEmoji}
|
||||||
withEffects={isUserId(chatId)}
|
withEffects={isUserId(chatId)}
|
||||||
isOwn={isOwn}
|
isOwn={isOwn}
|
||||||
observeIntersection={observeIntersectionForMedia}
|
observeIntersection={observeIntersectionForLoading}
|
||||||
lastSyncTime={lastSyncTime}
|
lastSyncTime={lastSyncTime}
|
||||||
forceLoadPreview={isLocal}
|
forceLoadPreview={isLocal}
|
||||||
messageId={messageId}
|
messageId={messageId}
|
||||||
@ -732,7 +732,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
emoji={animatedEmoji}
|
emoji={animatedEmoji}
|
||||||
withEffects={isUserId(chatId)}
|
withEffects={isUserId(chatId)}
|
||||||
isOwn={isOwn}
|
isOwn={isOwn}
|
||||||
observeIntersection={observeIntersectionForMedia}
|
observeIntersection={observeIntersectionForLoading}
|
||||||
lastSyncTime={lastSyncTime}
|
lastSyncTime={lastSyncTime}
|
||||||
forceLoadPreview={isLocal}
|
forceLoadPreview={isLocal}
|
||||||
messageId={messageId}
|
messageId={messageId}
|
||||||
@ -744,7 +744,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
<Album
|
<Album
|
||||||
album={album!}
|
album={album!}
|
||||||
albumLayout={albumLayout!}
|
albumLayout={albumLayout!}
|
||||||
observeIntersection={observeIntersectionForMedia}
|
observeIntersection={observeIntersectionForLoading}
|
||||||
isOwn={isOwn}
|
isOwn={isOwn}
|
||||||
isProtected={isProtected}
|
isProtected={isProtected}
|
||||||
hasCustomAppendix={hasCustomAppendix}
|
hasCustomAppendix={hasCustomAppendix}
|
||||||
@ -762,7 +762,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
{!isAlbum && photo && (
|
{!isAlbum && photo && (
|
||||||
<Photo
|
<Photo
|
||||||
message={message}
|
message={message}
|
||||||
observeIntersection={observeIntersectionForMedia}
|
observeIntersection={observeIntersectionForLoading}
|
||||||
noAvatars={noAvatars}
|
noAvatars={noAvatars}
|
||||||
canAutoLoad={canAutoLoadMedia}
|
canAutoLoad={canAutoLoadMedia}
|
||||||
uploadProgress={uploadProgress}
|
uploadProgress={uploadProgress}
|
||||||
@ -777,7 +777,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
{!isAlbum && video && video.isRound && (
|
{!isAlbum && video && video.isRound && (
|
||||||
<RoundVideo
|
<RoundVideo
|
||||||
message={message}
|
message={message}
|
||||||
observeIntersection={observeIntersectionForMedia}
|
observeIntersection={observeIntersectionForLoading}
|
||||||
canAutoLoad={canAutoLoadMedia}
|
canAutoLoad={canAutoLoadMedia}
|
||||||
lastSyncTime={lastSyncTime}
|
lastSyncTime={lastSyncTime}
|
||||||
isDownloading={isDownloading}
|
isDownloading={isDownloading}
|
||||||
@ -786,7 +786,8 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
{!isAlbum && video && !video.isRound && (
|
{!isAlbum && video && !video.isRound && (
|
||||||
<Video
|
<Video
|
||||||
message={message}
|
message={message}
|
||||||
observeIntersection={observeIntersectionForMedia}
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||||
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||||
noAvatars={noAvatars}
|
noAvatars={noAvatars}
|
||||||
canAutoLoad={canAutoLoadMedia}
|
canAutoLoad={canAutoLoadMedia}
|
||||||
canAutoPlay={canAutoPlayMedia}
|
canAutoPlay={canAutoPlayMedia}
|
||||||
@ -824,7 +825,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
{document && (
|
{document && (
|
||||||
<Document
|
<Document
|
||||||
message={message}
|
message={message}
|
||||||
observeIntersection={observeIntersectionForMedia}
|
observeIntersection={observeIntersectionForLoading}
|
||||||
canAutoLoad={canAutoLoadMedia}
|
canAutoLoad={canAutoLoadMedia}
|
||||||
autoLoadFileMaxSizeMb={autoLoadFileMaxSizeMb}
|
autoLoadFileMaxSizeMb={autoLoadFileMaxSizeMb}
|
||||||
uploadProgress={uploadProgress}
|
uploadProgress={uploadProgress}
|
||||||
@ -876,8 +877,8 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
emojiSize={emojiSize}
|
emojiSize={emojiSize}
|
||||||
highlight={highlight}
|
highlight={highlight}
|
||||||
isProtected={isProtected}
|
isProtected={isProtected}
|
||||||
observeIntersectionForLoading={observeIntersectionForMedia}
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||||
observeIntersectionForPlaying={observeIntersectionForAnimatedStickers}
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||||
withTranslucentThumbs={isCustomShape}
|
withTranslucentThumbs={isCustomShape}
|
||||||
/>
|
/>
|
||||||
{metaPosition === 'in-text' && renderReactionsAndMeta()}
|
{metaPosition === 'in-text' && renderReactionsAndMeta()}
|
||||||
@ -887,7 +888,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
{webPage && (
|
{webPage && (
|
||||||
<WebPage
|
<WebPage
|
||||||
message={message}
|
message={message}
|
||||||
observeIntersection={observeIntersectionForMedia}
|
observeIntersection={observeIntersectionForLoading}
|
||||||
noAvatars={noAvatars}
|
noAvatars={noAvatars}
|
||||||
canAutoLoad={canAutoLoadMedia}
|
canAutoLoad={canAutoLoadMedia}
|
||||||
canAutoPlay={canAutoPlayMedia}
|
canAutoPlay={canAutoPlayMedia}
|
||||||
@ -960,8 +961,8 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
<CustomEmoji
|
<CustomEmoji
|
||||||
documentId={senderEmojiStatus.documentId}
|
documentId={senderEmojiStatus.documentId}
|
||||||
loopLimit={EMOJI_STATUS_LOOP_LIMIT}
|
loopLimit={EMOJI_STATUS_LOOP_LIMIT}
|
||||||
observeIntersectionForLoading={observeIntersectionForMedia}
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||||
observeIntersectionForPlaying={observeIntersectionForAnimatedStickers}
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!asForwarded && !senderEmojiStatus && senderIsPremium && <PremiumIcon />}
|
{!asForwarded && !senderEmojiStatus && senderIsPremium && <PremiumIcon />}
|
||||||
|
|||||||
@ -88,7 +88,9 @@ const Photo: FC<OwnProps> = ({
|
|||||||
const fullMediaData = localBlobUrl || mediaData;
|
const fullMediaData = localBlobUrl || mediaData;
|
||||||
|
|
||||||
const [withThumb] = useState(!fullMediaData);
|
const [withThumb] = useState(!fullMediaData);
|
||||||
const thumbRef = useBlurredMediaThumbRef(message, fullMediaData);
|
const noThumb = Boolean(fullMediaData);
|
||||||
|
const thumbRef = useBlurredMediaThumbRef(message, noThumb);
|
||||||
|
const thumbClassNames = useMediaTransition(!noThumb);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
loadProgress: downloadProgress,
|
loadProgress: downloadProgress,
|
||||||
@ -105,7 +107,6 @@ const Photo: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
const wasLoadDisabled = usePrevious(isLoadAllowed) === false;
|
const wasLoadDisabled = usePrevious(isLoadAllowed) === false;
|
||||||
|
|
||||||
const transitionClassNames = useMediaTransition(fullMediaData);
|
|
||||||
const {
|
const {
|
||||||
shouldRender: shouldRenderSpinner,
|
shouldRender: shouldRenderSpinner,
|
||||||
transitionClassNames: spinnerClassNames,
|
transitionClassNames: spinnerClassNames,
|
||||||
@ -169,21 +170,21 @@ const Photo: FC<OwnProps> = ({
|
|||||||
style={style}
|
style={style}
|
||||||
onClick={isUploading ? undefined : handleClick}
|
onClick={isUploading ? undefined : handleClick}
|
||||||
>
|
>
|
||||||
{withThumb && (
|
|
||||||
<canvas
|
|
||||||
ref={thumbRef}
|
|
||||||
className="thumbnail"
|
|
||||||
style={`width: ${width}px; height: ${height}px;${aspectRatio}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<img
|
<img
|
||||||
src={fullMediaData}
|
src={fullMediaData}
|
||||||
className={`full-media ${transitionClassNames}`}
|
className="full-media"
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
alt=""
|
alt=""
|
||||||
draggable={!isProtected}
|
draggable={!isProtected}
|
||||||
/>
|
/>
|
||||||
|
{withThumb && (
|
||||||
|
<canvas
|
||||||
|
ref={thumbRef}
|
||||||
|
className={buildClassName('thumbnail', thumbClassNames)}
|
||||||
|
style={`width: ${width}px; height: ${height}px;${aspectRatio}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{isProtected && <span className="protector" />}
|
{isProtected && <span className="protector" />}
|
||||||
{shouldRenderSpinner && !shouldRenderDownloadButton && (
|
{shouldRenderSpinner && !shouldRenderDownloadButton && (
|
||||||
<div className={`media-loading ${spinnerClassNames}`}>
|
<div className={`media-loading ${spinnerClassNames}`}>
|
||||||
|
|||||||
@ -4,13 +4,6 @@
|
|||||||
height: 15rem;
|
height: 15rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.thumbnail-wrapper {
|
|
||||||
width: 15rem;
|
|
||||||
height: 15rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-wrapper {
|
.video-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -19,6 +12,10 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|||||||
@ -82,7 +82,7 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
const isTransferring = (isLoadAllowed && !isBuffered) || isDownloading;
|
const isTransferring = (isLoadAllowed && !isBuffered) || isDownloading;
|
||||||
const wasLoadDisabled = usePrevious(isLoadAllowed) === false;
|
const wasLoadDisabled = usePrevious(isLoadAllowed) === false;
|
||||||
|
|
||||||
const transitionClassNames = useMediaTransition(mediaData);
|
const thumbClassNames = useMediaTransition(!mediaData);
|
||||||
const {
|
const {
|
||||||
shouldRender: shouldSpinnerRender,
|
shouldRender: shouldSpinnerRender,
|
||||||
transitionClassNames: spinnerClassNames,
|
transitionClassNames: spinnerClassNames,
|
||||||
@ -180,30 +180,19 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
setProgress(playerEl.currentTime / playerEl.duration);
|
setProgress(playerEl.currentTime / playerEl.duration);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const videoClassName = buildClassName('full-media', transitionClassNames);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="RoundVideo media-inner"
|
className="RoundVideo media-inner"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
{withThumb && (
|
|
||||||
<div className="thumbnail-wrapper">
|
|
||||||
<canvas
|
|
||||||
ref={thumbRef}
|
|
||||||
className="thumbnail"
|
|
||||||
style={`width: ${ROUND_VIDEO_DIMENSIONS_PX}px; height: ${ROUND_VIDEO_DIMENSIONS_PX}px`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{mediaData && (
|
{mediaData && (
|
||||||
<div className="video-wrapper">
|
<div className="video-wrapper">
|
||||||
<OptimizedVideo
|
<OptimizedVideo
|
||||||
canPlay={shouldPlay}
|
canPlay={shouldPlay}
|
||||||
ref={playerRef}
|
ref={playerRef}
|
||||||
src={mediaData}
|
src={mediaData}
|
||||||
className={videoClassName}
|
className="full-media"
|
||||||
width={ROUND_VIDEO_DIMENSIONS_PX}
|
width={ROUND_VIDEO_DIMENSIONS_PX}
|
||||||
height={ROUND_VIDEO_DIMENSIONS_PX}
|
height={ROUND_VIDEO_DIMENSIONS_PX}
|
||||||
autoPlay
|
autoPlay
|
||||||
@ -218,6 +207,13 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{withThumb && (
|
||||||
|
<canvas
|
||||||
|
ref={thumbRef}
|
||||||
|
className={buildClassName('thumbnail', thumbClassNames)}
|
||||||
|
style={`width: ${ROUND_VIDEO_DIMENSIONS_PX}px; height: ${ROUND_VIDEO_DIMENSIONS_PX}px`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="progress" ref={playingProgressRef} />
|
<div className="progress" ref={playingProgressRef} />
|
||||||
{shouldSpinnerRender && (
|
{shouldSpinnerRender && (
|
||||||
<div className={`media-loading ${spinnerClassNames}`}>
|
<div className={`media-loading ${spinnerClassNames}`}>
|
||||||
|
|||||||
@ -11,21 +11,22 @@ import { calculateVideoDimensions } from '../../common/helpers/mediaDimensions';
|
|||||||
import {
|
import {
|
||||||
getMediaTransferState,
|
getMediaTransferState,
|
||||||
getMessageMediaFormat,
|
getMessageMediaFormat,
|
||||||
getMessageMediaHash,
|
getMessageMediaHash, getMessageMediaThumbDataUri,
|
||||||
getMessageVideo,
|
getMessageVideo,
|
||||||
getMessageWebPageVideo,
|
getMessageWebPageVideo,
|
||||||
isForwardedMessage,
|
isForwardedMessage,
|
||||||
isOwnMessage,
|
isOwnMessage,
|
||||||
} from '../../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
|
import * as mediaLoader from '../../../util/mediaLoader';
|
||||||
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
|
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
|
||||||
import useMediaWithLoadProgress from '../../../hooks/useMediaWithLoadProgress';
|
import useMediaWithLoadProgress from '../../../hooks/useMediaWithLoadProgress';
|
||||||
import useMedia from '../../../hooks/useMedia';
|
import useMedia from '../../../hooks/useMedia';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
import useBuffering from '../../../hooks/useBuffering';
|
|
||||||
import useMediaTransition from '../../../hooks/useMediaTransition';
|
import useMediaTransition from '../../../hooks/useMediaTransition';
|
||||||
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
|
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
|
||||||
|
import useFlag from '../../../hooks/useFlag';
|
||||||
|
|
||||||
import ProgressSpinner from '../../ui/ProgressSpinner';
|
import ProgressSpinner from '../../ui/ProgressSpinner';
|
||||||
import OptimizedVideo from '../../ui/OptimizedVideo';
|
import OptimizedVideo from '../../ui/OptimizedVideo';
|
||||||
@ -33,7 +34,8 @@ import OptimizedVideo from '../../ui/OptimizedVideo';
|
|||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
id?: string;
|
id?: string;
|
||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
observeIntersection: ObserveFn;
|
observeIntersectionForLoading: ObserveFn;
|
||||||
|
observeIntersectionForPlaying?: ObserveFn;
|
||||||
noAvatars?: boolean;
|
noAvatars?: boolean;
|
||||||
canAutoLoad?: boolean;
|
canAutoLoad?: boolean;
|
||||||
canAutoPlay?: boolean;
|
canAutoPlay?: boolean;
|
||||||
@ -50,7 +52,8 @@ export type OwnProps = {
|
|||||||
const Video: FC<OwnProps> = ({
|
const Video: FC<OwnProps> = ({
|
||||||
id,
|
id,
|
||||||
message,
|
message,
|
||||||
observeIntersection,
|
observeIntersectionForLoading,
|
||||||
|
observeIntersectionForPlaying,
|
||||||
noAvatars,
|
noAvatars,
|
||||||
canAutoLoad,
|
canAutoLoad,
|
||||||
canAutoPlay,
|
canAutoPlay,
|
||||||
@ -71,32 +74,41 @@ const Video: FC<OwnProps> = ({
|
|||||||
const video = (getMessageVideo(message) || getMessageWebPageVideo(message))!;
|
const video = (getMessageVideo(message) || getMessageWebPageVideo(message))!;
|
||||||
const localBlobUrl = video.blobUrl;
|
const localBlobUrl = video.blobUrl;
|
||||||
|
|
||||||
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
const isIntersectingForLoading = useIsIntersecting(ref, observeIntersectionForLoading);
|
||||||
|
const isIntersectingForPlaying = (
|
||||||
|
useIsIntersecting(ref, observeIntersectionForPlaying)
|
||||||
|
&& isIntersectingForLoading
|
||||||
|
);
|
||||||
|
const wasIntersectedRef = useRef(isIntersectingForLoading);
|
||||||
|
if (isIntersectingForPlaying && !wasIntersectedRef.current) {
|
||||||
|
wasIntersectedRef.current = true;
|
||||||
|
}
|
||||||
|
|
||||||
const [isLoadAllowed, setIsLoadAllowed] = useState(canAutoLoad);
|
const [isLoadAllowed, setIsLoadAllowed] = useState(canAutoLoad);
|
||||||
const shouldLoad = Boolean(isLoadAllowed && isIntersecting && lastSyncTime);
|
const shouldLoad = Boolean(isLoadAllowed && isIntersectingForLoading && lastSyncTime);
|
||||||
const [isPlayAllowed, setIsPlayAllowed] = useState(canAutoPlay);
|
const [isPlayAllowed, setIsPlayAllowed] = useState(canAutoPlay);
|
||||||
|
|
||||||
const previewBlobUrl = useMedia(
|
const fullMediaHash = getMessageMediaHash(message, 'inline');
|
||||||
getMessageMediaHash(message, 'pictogram'),
|
const [isFullMediaPreloaded] = useState(Boolean(fullMediaHash && mediaLoader.getFromMemory(fullMediaHash)));
|
||||||
!(isIntersecting && lastSyncTime),
|
|
||||||
getMessageMediaFormat(message, 'pictogram'),
|
|
||||||
lastSyncTime,
|
|
||||||
);
|
|
||||||
const previewClassNames = useMediaTransition(previewBlobUrl);
|
|
||||||
|
|
||||||
const { mediaData, loadProgress } = useMediaWithLoadProgress(
|
const { mediaData, loadProgress } = useMediaWithLoadProgress(
|
||||||
getMessageMediaHash(message, 'inline'),
|
fullMediaHash, !shouldLoad, getMessageMediaFormat(message, 'inline'), lastSyncTime,
|
||||||
!shouldLoad,
|
|
||||||
getMessageMediaFormat(message, 'inline'),
|
|
||||||
lastSyncTime,
|
|
||||||
);
|
);
|
||||||
const fullMediaData = localBlobUrl || mediaData;
|
const fullMediaData = localBlobUrl || mediaData;
|
||||||
const isInline = Boolean(isIntersecting && fullMediaData);
|
const [isPlayerReady, markPlayerReady] = useFlag();
|
||||||
|
|
||||||
// Thumbnail is always rendered, so we can only disable blur if we have a preview
|
const hasThumb = Boolean(getMessageMediaThumbDataUri(message));
|
||||||
const [withThumb] = useState(!previewBlobUrl);
|
|
||||||
const thumbRef = useBlurredMediaThumbRef(message, previewBlobUrl);
|
const previewMediaHash = getMessageMediaHash(message, 'preview');
|
||||||
|
const [isPreviewPreloaded] = useState(Boolean(previewMediaHash && mediaLoader.getFromMemory(previewMediaHash)));
|
||||||
|
const canLoadPreview = isIntersectingForLoading && lastSyncTime;
|
||||||
|
const previewBlobUrl = useMedia(previewMediaHash, !canLoadPreview, undefined, lastSyncTime);
|
||||||
|
const previewClassNames = useMediaTransition((hasThumb || previewBlobUrl) && !isPlayerReady);
|
||||||
|
|
||||||
|
const noThumb = !hasThumb || previewBlobUrl || isPlayerReady;
|
||||||
|
const thumbRef = useBlurredMediaThumbRef(message, noThumb);
|
||||||
|
const thumbClassNames = useMediaTransition(!noThumb);
|
||||||
|
|
||||||
|
const isInline = fullMediaData && wasIntersectedRef.current;
|
||||||
|
|
||||||
const { loadProgress: downloadProgress } = useMediaWithLoadProgress(
|
const { loadProgress: downloadProgress } = useMediaWithLoadProgress(
|
||||||
getMessageMediaHash(message, 'download'),
|
getMessageMediaHash(message, 'download'),
|
||||||
@ -105,21 +117,20 @@ const Video: FC<OwnProps> = ({
|
|||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { isBuffered, bufferingHandlers } = useBuffering(!canAutoLoad);
|
|
||||||
const { isUploading, isTransferring, transferProgress } = getMediaTransferState(
|
const { isUploading, isTransferring, transferProgress } = getMediaTransferState(
|
||||||
message,
|
message,
|
||||||
uploadProgress || (isDownloading ? downloadProgress : loadProgress),
|
uploadProgress || (isDownloading ? downloadProgress : loadProgress),
|
||||||
(shouldLoad && !isBuffered) || isDownloading,
|
(shouldLoad && !isPlayerReady && !isFullMediaPreloaded) || isDownloading,
|
||||||
);
|
);
|
||||||
|
|
||||||
const wasLoadDisabled = usePrevious(isLoadAllowed) === false;
|
const wasLoadDisabled = usePrevious(isLoadAllowed) === false;
|
||||||
const {
|
const {
|
||||||
shouldRender: shouldRenderSpinner,
|
shouldRender: shouldRenderSpinner,
|
||||||
transitionClassNames: spinnerClassNames,
|
transitionClassNames: spinnerClassNames,
|
||||||
} = useShowTransition(isTransferring, undefined, wasLoadDisabled);
|
} = useShowTransition(isTransferring, undefined, wasLoadDisabled);
|
||||||
const {
|
const {
|
||||||
shouldRender: shouldRenderPlayButton,
|
|
||||||
transitionClassNames: playButtonClassNames,
|
transitionClassNames: playButtonClassNames,
|
||||||
} = useShowTransition(isLoadAllowed && !isPlayAllowed && !shouldRenderSpinner);
|
} = useShowTransition(Boolean((isLoadAllowed || fullMediaData) && !isPlayAllowed && !shouldRenderSpinner));
|
||||||
|
|
||||||
const [playProgress, setPlayProgress] = useState<number>(0);
|
const [playProgress, setPlayProgress] = useState<number>(0);
|
||||||
const handleTimeUpdate = useCallback((e: React.SyntheticEvent<HTMLVideoElement>) => {
|
const handleTimeUpdate = useCallback((e: React.SyntheticEvent<HTMLVideoElement>) => {
|
||||||
@ -149,10 +160,11 @@ const Video: FC<OwnProps> = ({
|
|||||||
}, [isUploading, isDownloading, fullMediaData, isPlayAllowed, onClick, onCancelUpload, message]);
|
}, [isUploading, isDownloading, fullMediaData, isPlayAllowed, onClick, onCancelUpload, message]);
|
||||||
|
|
||||||
const className = buildClassName('media-inner dark', !isUploading && 'interactive');
|
const className = buildClassName('media-inner dark', !isUploading && 'interactive');
|
||||||
const aspectRatio = withAspectRatio ? `aspect-ratio: ${(width / height).toFixed(3)}/ 1` : '';
|
|
||||||
const style = dimensions
|
const dimensionsStyle = dimensions ? ` left: ${dimensions.x}px; top: ${dimensions.y}px;` : '';
|
||||||
? `width: ${width}px; height: ${height}px; left: ${dimensions.x}px; top: ${dimensions.y}px;${aspectRatio}`
|
const aspectRatioStyle = withAspectRatio ? ` aspect-ratio: ${(width / height).toFixed(3)}/ 1;` : '';
|
||||||
: '';
|
const style = `width: ${width}px; height: ${height}px;${dimensionsStyle}${aspectRatioStyle}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@ -161,47 +173,42 @@ const Video: FC<OwnProps> = ({
|
|||||||
style={style}
|
style={style}
|
||||||
onClick={isUploading ? undefined : handleClick}
|
onClick={isUploading ? undefined : handleClick}
|
||||||
>
|
>
|
||||||
{withThumb ? (
|
|
||||||
<canvas
|
|
||||||
ref={thumbRef}
|
|
||||||
className="thumbnail"
|
|
||||||
style={`width: ${width}px; height: ${height}px;${aspectRatio}`}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<img
|
|
||||||
src={previewBlobUrl}
|
|
||||||
className={buildClassName('thumbnail', previewClassNames)}
|
|
||||||
style={`width: ${width}px; height: ${height}px;${aspectRatio}`}
|
|
||||||
alt=""
|
|
||||||
draggable={!isProtected}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isInline && (
|
{isInline && (
|
||||||
<OptimizedVideo
|
<OptimizedVideo
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
canPlay={isPlayAllowed}
|
|
||||||
src={fullMediaData}
|
src={fullMediaData}
|
||||||
className="full-media"
|
className="full-media"
|
||||||
|
canPlay={isPlayAllowed && isIntersectingForPlaying}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
muted
|
muted
|
||||||
loop
|
loop
|
||||||
playsInline
|
playsInline
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
||||||
{...bufferingHandlers}
|
|
||||||
draggable={!isProtected}
|
draggable={!isProtected}
|
||||||
onTimeUpdate={handleTimeUpdate}
|
onTimeUpdate={handleTimeUpdate}
|
||||||
style={aspectRatio}
|
onReady={markPlayerReady}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<img
|
||||||
|
src={previewBlobUrl}
|
||||||
|
className={buildClassName('thumbnail', previewClassNames)}
|
||||||
|
alt=""
|
||||||
|
draggable={!isProtected}
|
||||||
|
/>
|
||||||
|
{hasThumb && !isPreviewPreloaded && (
|
||||||
|
<canvas
|
||||||
|
ref={thumbRef}
|
||||||
|
className={buildClassName('thumbnail', thumbClassNames)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isProtected && <span className="protector" />}
|
{isProtected && <span className="protector" />}
|
||||||
{shouldRenderPlayButton && <i className={buildClassName('icon-large-play', playButtonClassNames)} />}
|
<i className={buildClassName('icon-large-play', playButtonClassNames)} />
|
||||||
{shouldRenderSpinner && (
|
{shouldRenderSpinner && (
|
||||||
<div className={buildClassName('media-loading', spinnerClassNames)}>
|
<div className={buildClassName('media-loading', spinnerClassNames)}>
|
||||||
<ProgressSpinner progress={transferProgress} onClick={handleClick} />
|
<ProgressSpinner progress={transferProgress} onClick={handleClick} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isLoadAllowed && (
|
{!isLoadAllowed && !fullMediaData && (
|
||||||
<i className="icon-download" />
|
<i className="icon-download" />
|
||||||
)}
|
)}
|
||||||
{isTransferring ? (
|
{isTransferring ? (
|
||||||
|
|||||||
@ -121,7 +121,7 @@ const WebPage: FC<OwnProps> = ({
|
|||||||
{!inPreview && video && (
|
{!inPreview && video && (
|
||||||
<Video
|
<Video
|
||||||
message={message}
|
message={message}
|
||||||
observeIntersection={observeIntersection!}
|
observeIntersectionForLoading={observeIntersection!}
|
||||||
noAvatars={noAvatars}
|
noAvatars={noAvatars}
|
||||||
canAutoLoad={canAutoLoad}
|
canAutoLoad={canAutoLoad}
|
||||||
canAutoPlay={canAutoPlay}
|
canAutoPlay={canAutoPlay}
|
||||||
|
|||||||
@ -500,7 +500,7 @@
|
|||||||
&.interactive {
|
&.interactive {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&.dark video {
|
&.dark video, &.dark canvas {
|
||||||
background-color: #232323;
|
background-color: #232323;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,10 +4,10 @@ import { IS_CANVAS_FILTER_SUPPORTED, IS_SINGLE_COLUMN_LAYOUT } from '../../../..
|
|||||||
import { getMessageMediaThumbDataUri } from '../../../../global/helpers';
|
import { getMessageMediaThumbDataUri } from '../../../../global/helpers';
|
||||||
import useCanvasBlur from '../../../../hooks/useCanvasBlur';
|
import useCanvasBlur from '../../../../hooks/useCanvasBlur';
|
||||||
|
|
||||||
export default function useBlurredMediaThumbRef(message: ApiMessage, fullMediaData?: string) {
|
export default function useBlurredMediaThumbRef(message: ApiMessage, isDisabled?: boolean | string) {
|
||||||
return useCanvasBlur(
|
return useCanvasBlur(
|
||||||
getMessageMediaThumbDataUri(message),
|
getMessageMediaThumbDataUri(message),
|
||||||
Boolean(fullMediaData),
|
Boolean(isDisabled),
|
||||||
IS_SINGLE_COLUMN_LAYOUT && !IS_CANVAS_FILTER_SUPPORTED,
|
IS_SINGLE_COLUMN_LAYOUT && !IS_CANVAS_FILTER_SUPPORTED,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,33 +1,62 @@
|
|||||||
import React, { memo, useRef } from '../../lib/teact/teact';
|
import React, { memo, useCallback, useRef } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import useVideoAutoPause from '../middle/message/hooks/useVideoAutoPause';
|
import useVideoAutoPause from '../middle/message/hooks/useVideoAutoPause';
|
||||||
import useVideoCleanup from '../../hooks/useVideoCleanup';
|
import useVideoCleanup from '../../hooks/useVideoCleanup';
|
||||||
|
import useBuffering from '../../hooks/useBuffering';
|
||||||
|
import useOnChange from '../../hooks/useOnChange';
|
||||||
|
|
||||||
type OwnProps =
|
type OwnProps =
|
||||||
{
|
{
|
||||||
canPlay: boolean;
|
|
||||||
ref?: React.RefObject<HTMLVideoElement>;
|
ref?: React.RefObject<HTMLVideoElement>;
|
||||||
|
canPlay: boolean;
|
||||||
|
onReady?: NoneToVoidFunction;
|
||||||
}
|
}
|
||||||
& React.DetailedHTMLProps<React.VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>;
|
& React.DetailedHTMLProps<React.VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>;
|
||||||
|
|
||||||
function OptimizedVideo({
|
function OptimizedVideo({
|
||||||
ref,
|
ref,
|
||||||
canPlay,
|
canPlay,
|
||||||
|
onReady,
|
||||||
|
onTimeUpdate,
|
||||||
...restProps
|
...restProps
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const localRef = useRef<HTMLVideoElement>(null);
|
const localRef = useRef<HTMLVideoElement>(null);
|
||||||
|
|
||||||
if (!ref) {
|
if (!ref) {
|
||||||
ref = localRef;
|
ref = localRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { handlePlaying } = useVideoAutoPause(ref, canPlay);
|
const { handlePlaying: handlePlayingForAutoPause } = useVideoAutoPause(ref, canPlay);
|
||||||
useVideoCleanup(ref, []);
|
useVideoCleanup(ref, []);
|
||||||
|
|
||||||
|
const isReadyRef = useRef(false);
|
||||||
|
const handleReady = useCallback(() => {
|
||||||
|
if (!isReadyRef.current) {
|
||||||
|
onReady?.();
|
||||||
|
isReadyRef.current = true;
|
||||||
|
}
|
||||||
|
}, [onReady]);
|
||||||
|
|
||||||
|
// This is only needed for browsers not allowing autoplay
|
||||||
|
const { isBuffered, bufferingHandlers } = useBuffering(true, onTimeUpdate);
|
||||||
|
const { onPlaying: handlePlayingForBuffering, ...otherBufferingHandlers } = bufferingHandlers;
|
||||||
|
useOnChange(([prevIsBuffered]) => {
|
||||||
|
if (prevIsBuffered === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReady();
|
||||||
|
}, [isBuffered]);
|
||||||
|
|
||||||
|
const handlePlaying = useCallback((e) => {
|
||||||
|
handlePlayingForAutoPause();
|
||||||
|
handlePlayingForBuffering(e);
|
||||||
|
handleReady();
|
||||||
|
}, [handlePlayingForAutoPause, handlePlayingForBuffering, handleReady]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
<video ref={ref} autoPlay {...restProps} onPlaying={handlePlaying} />
|
<video ref={ref} autoPlay {...restProps} {...otherBufferingHandlers} onPlaying={handlePlaying} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,8 +26,8 @@ type Target =
|
|||||||
'micro'
|
'micro'
|
||||||
| 'pictogram'
|
| 'pictogram'
|
||||||
| 'inline'
|
| 'inline'
|
||||||
| 'viewerPreview'
|
| 'preview'
|
||||||
| 'viewerFull'
|
| 'full'
|
||||||
| 'download';
|
| 'download';
|
||||||
|
|
||||||
export function getMessageContent(message: ApiMessage) {
|
export function getMessageContent(message: ApiMessage) {
|
||||||
@ -191,9 +191,9 @@ export function getMessageMediaHash(
|
|||||||
return `${base}?size=m`;
|
return `${base}?size=m`;
|
||||||
case 'inline':
|
case 'inline':
|
||||||
return !hasMessageLocalBlobUrl(message) ? getVideoOrAudioBaseHash(messageVideo, base) : undefined;
|
return !hasMessageLocalBlobUrl(message) ? getVideoOrAudioBaseHash(messageVideo, base) : undefined;
|
||||||
case 'viewerPreview':
|
case 'preview':
|
||||||
return `${base}?size=m`;
|
return `${base}?size=x`;
|
||||||
case 'viewerFull':
|
case 'full':
|
||||||
return getVideoOrAudioBaseHash(messageVideo, base);
|
return getVideoOrAudioBaseHash(messageVideo, base);
|
||||||
case 'download':
|
case 'download':
|
||||||
return `${base}?download`;
|
return `${base}?download`;
|
||||||
@ -207,9 +207,9 @@ export function getMessageMediaHash(
|
|||||||
return `${base}?size=m`;
|
return `${base}?size=m`;
|
||||||
case 'inline':
|
case 'inline':
|
||||||
return !hasMessageLocalBlobUrl(message) ? `${base}?size=x` : undefined;
|
return !hasMessageLocalBlobUrl(message) ? `${base}?size=x` : undefined;
|
||||||
case 'viewerPreview':
|
case 'preview':
|
||||||
return `${base}?size=x`;
|
return `${base}?size=x`;
|
||||||
case 'viewerFull':
|
case 'full':
|
||||||
case 'download':
|
case 'download':
|
||||||
return `${base}?size=z`;
|
return `${base}?size=z`;
|
||||||
}
|
}
|
||||||
@ -220,13 +220,13 @@ export function getMessageMediaHash(
|
|||||||
case 'micro':
|
case 'micro':
|
||||||
case 'pictogram':
|
case 'pictogram':
|
||||||
case 'inline':
|
case 'inline':
|
||||||
case 'viewerPreview':
|
case 'preview':
|
||||||
if (!getDocumentHasPreview(document) || hasMessageLocalBlobUrl(message)) {
|
if (!getDocumentHasPreview(document) || hasMessageLocalBlobUrl(message)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${base}?size=m`;
|
return `${base}?size=m`;
|
||||||
case 'viewerFull':
|
case 'full':
|
||||||
case 'download':
|
case 'download':
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
@ -325,7 +325,7 @@ export function getMessageMediaFormat(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fullVideo && IS_PROGRESSIVE_SUPPORTED && (
|
if (fullVideo && IS_PROGRESSIVE_SUPPORTED && (
|
||||||
target === 'viewerFull' || target === 'inline'
|
target === 'full' || target === 'inline'
|
||||||
)) {
|
)) {
|
||||||
return ApiMediaFormat.Progressive;
|
return ApiMediaFormat.Progressive;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ const DEBOUNCE = 200;
|
|||||||
*/
|
*/
|
||||||
export type BufferedRange = { start: number; end: number };
|
export type BufferedRange = { start: number; end: number };
|
||||||
|
|
||||||
const useBuffering = (noInitiallyBuffered = false) => {
|
const useBuffering = (noInitiallyBuffered = false, onTimeUpdate?: AnyToVoidFunction) => {
|
||||||
const [isBuffered, setIsBuffered] = useState(!noInitiallyBuffered);
|
const [isBuffered, setIsBuffered] = useState(!noInitiallyBuffered);
|
||||||
const [bufferedProgress, setBufferedProgress] = useState(0);
|
const [bufferedProgress, setBufferedProgress] = useState(0);
|
||||||
const [bufferedRanges, setBufferedRanges] = useState<BufferedRange[]>([]);
|
const [bufferedRanges, setBufferedRanges] = useState<BufferedRange[]>([]);
|
||||||
@ -24,6 +24,10 @@ const useBuffering = (noInitiallyBuffered = false) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleBuffering = useCallback<BufferingEvent>((e) => {
|
const handleBuffering = useCallback<BufferingEvent>((e) => {
|
||||||
|
if (e.type === 'timeupdate') {
|
||||||
|
onTimeUpdate?.(e);
|
||||||
|
}
|
||||||
|
|
||||||
const media = e.currentTarget as HTMLMediaElement;
|
const media = e.currentTarget as HTMLMediaElement;
|
||||||
|
|
||||||
if (!isSafariPatchInProgress(media)) {
|
if (!isSafariPatchInProgress(media)) {
|
||||||
@ -36,7 +40,7 @@ const useBuffering = (noInitiallyBuffered = false) => {
|
|||||||
|
|
||||||
setIsBufferedDebounced(media.readyState >= MIN_READY_STATE || media.currentTime > 0);
|
setIsBufferedDebounced(media.readyState >= MIN_READY_STATE || media.currentTime > 0);
|
||||||
}
|
}
|
||||||
}, [setIsBufferedDebounced]);
|
}, [onTimeUpdate, setIsBufferedDebounced]);
|
||||||
|
|
||||||
const bufferingHandlers = {
|
const bufferingHandlers = {
|
||||||
onLoadedData: handleBuffering,
|
onLoadedData: handleBuffering,
|
||||||
|
|||||||
@ -15,8 +15,14 @@
|
|||||||
background-size: contain;
|
background-size: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thumbnail ~ .thumbnail,
|
.thumbnail {
|
||||||
.thumbnail ~ .full-media,
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.media-loading {
|
.media-loading {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user