[Perf] Introduce Optimized Video component
This commit is contained in:
parent
7d714a9b26
commit
04dee2ce7b
@ -32,8 +32,8 @@ import useMedia from '../../hooks/useMedia';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
||||
import useVideoAutoPause from '../middle/message/hooks/useVideoAutoPause';
|
||||
import useVideoCleanup from '../../hooks/useVideoCleanup';
|
||||
|
||||
import OptimizedVideo from '../ui/OptimizedVideo';
|
||||
|
||||
import './Avatar.scss';
|
||||
|
||||
@ -79,8 +79,6 @@ const Avatar: FC<OwnProps> = ({
|
||||
const { loadFullUser } = getActions();
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const videoLoopCountRef = useRef(0);
|
||||
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
||||
const isDeleted = user && isDeletedUser(user);
|
||||
@ -117,24 +115,14 @@ const Avatar: FC<OwnProps> = ({
|
||||
|
||||
const { transitionClassNames } = useShowTransition(hasBlobUrl, undefined, hasBlobUrl, 'slow');
|
||||
|
||||
const { handlePlaying } = useVideoAutoPause(videoRef, shouldPlayVideo);
|
||||
useVideoCleanup(videoRef, [shouldPlayVideo]);
|
||||
const handleVideoEnded = useCallback((e) => {
|
||||
const video = e.currentTarget;
|
||||
if (!videoBlobUrl) return;
|
||||
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
if (!video || !videoBlobUrl) return undefined;
|
||||
|
||||
const returnToStart = () => {
|
||||
videoLoopCountRef.current += 1;
|
||||
if (videoLoopCountRef.current >= LOOP_COUNT || noLoop) {
|
||||
video.style.display = 'none';
|
||||
} else {
|
||||
video.play();
|
||||
}
|
||||
};
|
||||
|
||||
video.addEventListener('ended', returnToStart);
|
||||
return () => video.removeEventListener('ended', returnToStart);
|
||||
videoLoopCountRef.current += 1;
|
||||
if (videoLoopCountRef.current >= LOOP_COUNT || noLoop) {
|
||||
video.style.display = 'none';
|
||||
}
|
||||
}, [noLoop, videoBlobUrl]);
|
||||
|
||||
const userId = user?.id;
|
||||
@ -165,15 +153,15 @@ const Avatar: FC<OwnProps> = ({
|
||||
decoding="async"
|
||||
/>
|
||||
{shouldPlayVideo && (
|
||||
<video
|
||||
ref={videoRef}
|
||||
<OptimizedVideo
|
||||
canPlay
|
||||
src={videoBlobUrl}
|
||||
className={buildClassName(cn.media, 'avatar-media')}
|
||||
muted
|
||||
autoPlay
|
||||
disablePictureInPicture
|
||||
playsInline
|
||||
onPlaying={handlePlaying}
|
||||
onEnded={handleVideoEnded}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, {
|
||||
memo, useCallback, useEffect, useMemo, useRef, useState,
|
||||
memo, useCallback, useEffect, useRef, useState,
|
||||
} from '../../lib/teact/teact';
|
||||
import { getGlobal } from '../../global';
|
||||
|
||||
@ -8,7 +8,6 @@ import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
||||
|
||||
import { IS_WEBM_SUPPORTED } from '../../util/environment';
|
||||
import renderText from './helpers/renderText';
|
||||
import safePlay from '../../util/safePlay';
|
||||
import { getPropertyHexColor } from '../../util/themeStyle';
|
||||
import { hexToRgb } from '../../util/switchTheme';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
@ -21,6 +20,7 @@ import useThumbnail from '../../hooks/useThumbnail';
|
||||
import useCustomEmoji from './hooks/useCustomEmoji';
|
||||
|
||||
import AnimatedSticker from './AnimatedSticker';
|
||||
import OptimizedVideo from '../ui/OptimizedVideo';
|
||||
|
||||
import styles from './CustomEmoji.module.scss';
|
||||
|
||||
@ -77,36 +77,16 @@ const CustomEmoji: FC<OwnProps> = ({
|
||||
|
||||
useEnsureCustomEmoji(documentId);
|
||||
|
||||
useEffect(() => {
|
||||
if (!customEmoji?.isVideo) return;
|
||||
const video = ref.current?.querySelector('video');
|
||||
if (!video || isIntersecting === !video.paused) return;
|
||||
const handleVideoEnded = useCallback((e) => {
|
||||
if (!loopLimit) return;
|
||||
|
||||
if (isIntersecting) {
|
||||
safePlay(video);
|
||||
} else {
|
||||
video.pause();
|
||||
loopCountRef.current += 1;
|
||||
|
||||
if (loopCountRef.current >= loopLimit) {
|
||||
setShouldLoop(false);
|
||||
e.currentTarget.currentTime = 0;
|
||||
}
|
||||
}, [customEmoji, isIntersecting]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loopLimit) return undefined;
|
||||
const video = ref.current?.querySelector('video');
|
||||
if (!mediaData || !video) return undefined;
|
||||
|
||||
const returnToStart = () => {
|
||||
loopCountRef.current += 1;
|
||||
if (loopCountRef.current >= loopLimit) {
|
||||
setShouldLoop(false);
|
||||
video.currentTime = 0;
|
||||
} else {
|
||||
video.play();
|
||||
}
|
||||
};
|
||||
|
||||
video.addEventListener('ended', returnToStart);
|
||||
return () => video.removeEventListener('ended', returnToStart);
|
||||
}, [loopLimit, mediaData]);
|
||||
}, [loopLimit]);
|
||||
|
||||
const handleStickerLoop = useCallback(() => {
|
||||
if (!loopLimit) return;
|
||||
@ -117,7 +97,7 @@ const CustomEmoji: FC<OwnProps> = ({
|
||||
}
|
||||
}, [loopLimit]);
|
||||
|
||||
const content = useMemo(() => {
|
||||
function renderContent() {
|
||||
if (!customEmoji || (!thumbDataUri && !mediaData)) {
|
||||
return (children && renderText(children, ['emoji']));
|
||||
}
|
||||
@ -136,14 +116,15 @@ const CustomEmoji: FC<OwnProps> = ({
|
||||
|
||||
if (customEmoji.isVideo) {
|
||||
return (
|
||||
<video
|
||||
<OptimizedVideo
|
||||
canPlay={isIntersecting}
|
||||
className={styles.media}
|
||||
src={mediaData}
|
||||
playsInline
|
||||
muted
|
||||
loop={!loopLimit}
|
||||
autoPlay={isIntersecting}
|
||||
src={mediaData}
|
||||
disablePictureInPicture
|
||||
onEnded={handleVideoEnded}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -161,10 +142,7 @@ const CustomEmoji: FC<OwnProps> = ({
|
||||
onLoop={handleStickerLoop}
|
||||
/>
|
||||
);
|
||||
}, [
|
||||
children, customColor, customEmoji, handleStickerLoop, isIntersecting, loopLimit, mediaData, shouldLoop,
|
||||
thumbDataUri,
|
||||
]);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -179,7 +157,7 @@ const CustomEmoji: FC<OwnProps> = ({
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{content}
|
||||
{renderContent()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -13,7 +13,6 @@ import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
||||
import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur';
|
||||
|
||||
import useMedia from '../../hooks/useMedia';
|
||||
import useVideoCleanup from '../../hooks/useVideoCleanup';
|
||||
import useBuffering from '../../hooks/useBuffering';
|
||||
import useCanvasBlur from '../../hooks/useCanvasBlur';
|
||||
import useLang from '../../hooks/useLang';
|
||||
@ -24,6 +23,7 @@ import Spinner from '../ui/Spinner';
|
||||
import Button from '../ui/Button';
|
||||
import Menu from '../ui/Menu';
|
||||
import MenuItem from '../ui/MenuItem';
|
||||
import OptimizedVideo from '../ui/OptimizedVideo';
|
||||
|
||||
import './GifButton.scss';
|
||||
|
||||
@ -48,8 +48,6 @@ const GifButton: FC<OwnProps> = ({
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
@ -65,8 +63,6 @@ const GifButton: FC<OwnProps> = ({
|
||||
const shouldRenderSpinner = loadAndPlay && !isBuffered;
|
||||
const isVideoReady = loadAndPlay && isBuffered;
|
||||
|
||||
useVideoCleanup(videoRef, [shouldRenderVideo]);
|
||||
|
||||
const {
|
||||
isContextMenuOpen, contextMenuPosition,
|
||||
handleBeforeContextMenu, handleContextMenu,
|
||||
@ -177,8 +173,9 @@ const GifButton: FC<OwnProps> = ({
|
||||
/>
|
||||
)}
|
||||
{shouldRenderVideo && (
|
||||
<video
|
||||
ref={videoRef}
|
||||
<OptimizedVideo
|
||||
canPlay
|
||||
src={videoData}
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
@ -187,9 +184,7 @@ const GifButton: FC<OwnProps> = ({
|
||||
preload="none"
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...bufferingHandlers}
|
||||
>
|
||||
<source src={videoData} />
|
||||
</video>
|
||||
/>
|
||||
)}
|
||||
{shouldRenderSpinner && (
|
||||
<Spinner color={previewBlobUrl || hasThumbnail ? 'white' : 'black'} />
|
||||
|
||||
@ -18,11 +18,9 @@ import buildClassName from '../../util/buildClassName';
|
||||
import { getFirstLetters } from '../../util/textFormat';
|
||||
import useMedia from '../../hooks/useMedia';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useVideoAutoPause from '../middle/message/hooks/useVideoAutoPause';
|
||||
import useVideoCleanup from '../../hooks/useVideoCleanup';
|
||||
import safePlay from '../../util/safePlay';
|
||||
|
||||
import Spinner from '../ui/Spinner';
|
||||
import OptimizedVideo from '../ui/OptimizedVideo';
|
||||
|
||||
import './ProfilePhoto.scss';
|
||||
|
||||
@ -79,18 +77,11 @@ const ProfilePhoto: FC<OwnProps> = ({
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!videoRef.current) return;
|
||||
if (!canPlayVideo) {
|
||||
videoRef.current.pause();
|
||||
if (videoRef.current && !canPlayVideo) {
|
||||
videoRef.current.currentTime = 0;
|
||||
} else {
|
||||
safePlay(videoRef.current);
|
||||
}
|
||||
}, [canPlayVideo]);
|
||||
|
||||
const { handlePlaying } = useVideoAutoPause(videoRef, canPlayVideo);
|
||||
useVideoCleanup(videoRef, []);
|
||||
|
||||
const photoHash = getMediaHash('big', 'photo');
|
||||
const photoBlobUrl = useMedia(photoHash, false, ApiMediaFormat.BlobUrl, lastSyncTime);
|
||||
const videoHash = getMediaHash('normal', 'video');
|
||||
@ -108,16 +99,15 @@ const ProfilePhoto: FC<OwnProps> = ({
|
||||
} else if (imageSrc) {
|
||||
if (videoBlobUrl) {
|
||||
content = (
|
||||
<video
|
||||
<OptimizedVideo
|
||||
canPlay={canPlayVideo}
|
||||
ref={videoRef}
|
||||
src={imageSrc}
|
||||
className="avatar-media"
|
||||
muted
|
||||
disablePictureInPicture
|
||||
autoPlay={canPlayVideo}
|
||||
loop
|
||||
playsInline
|
||||
onPlaying={handlePlaying}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
|
||||
@ -26,6 +26,7 @@ import AnimatedSticker from './AnimatedSticker';
|
||||
import Button from '../ui/Button';
|
||||
import Menu from '../ui/Menu';
|
||||
import MenuItem from '../ui/MenuItem';
|
||||
import OptimizedVideo from '../ui/OptimizedVideo';
|
||||
|
||||
import './StickerButton.scss';
|
||||
|
||||
@ -269,10 +270,10 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
<img src={previewBlobUrl} className={previewTransitionClassNames} />
|
||||
)}
|
||||
{isVideo && (
|
||||
<video
|
||||
<OptimizedVideo
|
||||
canPlay={canVideoPlay}
|
||||
className={previewTransitionClassNames}
|
||||
src={videoBlobUrl}
|
||||
autoPlay={canVideoPlay}
|
||||
loop
|
||||
playsInline
|
||||
disablePictureInPicture
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { FC } from '../../../../lib/teact/teact';
|
||||
import React, { memo, useEffect, useRef } from '../../../../lib/teact/teact';
|
||||
import React, { memo } from '../../../../lib/teact/teact';
|
||||
|
||||
import type { ApiThumbnail } from '../../../../api/types';
|
||||
|
||||
@ -7,9 +7,9 @@ import useMedia from '../../../../hooks/useMedia';
|
||||
import buildClassName from '../../../../util/buildClassName';
|
||||
import useCanvasBlur from '../../../../hooks/useCanvasBlur';
|
||||
import useMediaTransition from '../../../../hooks/useMediaTransition';
|
||||
import safePlay from '../../../../util/safePlay';
|
||||
|
||||
import DeviceFrame from '../../../../assets/premium/DeviceFrame.svg';
|
||||
import OptimizedVideo from '../../../ui/OptimizedVideo';
|
||||
|
||||
import styles from './PremiumFeaturePreviewVideo.module.scss';
|
||||
|
||||
@ -33,19 +33,6 @@ const PremiumFeaturePreviewVideo: FC<OwnProps> = ({
|
||||
const mediaData = useMedia(`document${videoId}`);
|
||||
const thumbnailRef = useCanvasBlur(videoThumbnail.dataUri);
|
||||
const transitionClassNames = useMediaTransition(mediaData);
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
|
||||
if (isActive) {
|
||||
safePlay(video);
|
||||
} else {
|
||||
video.pause();
|
||||
}
|
||||
}, [isActive]);
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
@ -59,14 +46,10 @@ const PremiumFeaturePreviewVideo: FC<OwnProps> = ({
|
||||
>
|
||||
<img src={DeviceFrame} alt="" className={styles.frame} />
|
||||
<canvas ref={thumbnailRef} className={styles.video} />
|
||||
<video
|
||||
ref={videoRef}
|
||||
className={buildClassName(
|
||||
styles.video,
|
||||
transitionClassNames,
|
||||
)}
|
||||
<OptimizedVideo
|
||||
canPlay={isActive}
|
||||
className={buildClassName(styles.video, transitionClassNames)}
|
||||
src={mediaData}
|
||||
autoPlay={isActive}
|
||||
disablePictureInPicture
|
||||
playsInline
|
||||
muted
|
||||
|
||||
@ -64,6 +64,7 @@ import MessageListContent from './MessageListContent';
|
||||
import ContactGreeting from './ContactGreeting';
|
||||
import NoMessages from './NoMessages';
|
||||
import Skeleton from '../ui/Skeleton';
|
||||
import OptimizedVideo from '../ui/OptimizedVideo';
|
||||
|
||||
import './MessageList.scss';
|
||||
|
||||
@ -563,10 +564,10 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
/>
|
||||
)}
|
||||
{botInfoGifUrl && (
|
||||
<video
|
||||
<OptimizedVideo
|
||||
canPlay
|
||||
src={botInfoGifUrl}
|
||||
loop
|
||||
autoPlay
|
||||
disablePictureInPicture
|
||||
muted
|
||||
playsInline
|
||||
|
||||
@ -10,6 +10,8 @@ import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
|
||||
import useMedia from '../../../hooks/useMedia';
|
||||
import useMediaTransition from '../../../hooks/useMediaTransition';
|
||||
|
||||
import OptimizedVideo from '../../ui/OptimizedVideo';
|
||||
|
||||
type OwnProps = {
|
||||
stickerSet: ApiStickerSet;
|
||||
observeIntersection: ObserveFn;
|
||||
@ -34,7 +36,7 @@ const StickerSetCover: FC<OwnProps> = ({ stickerSet, observeIntersection }) => {
|
||||
<div ref={ref} className="sticker-set-cover">
|
||||
{firstLetters}
|
||||
{isVideo ? (
|
||||
<video src={mediaData} className={transitionClassNames} loop autoPlay disablePictureInPicture />
|
||||
<OptimizedVideo canPlay src={mediaData} className={transitionClassNames} loop disablePictureInPicture />
|
||||
) : (
|
||||
<img src={mediaData} className={transitionClassNames} alt="" />
|
||||
)}
|
||||
|
||||
@ -24,11 +24,10 @@ import useShowTransition from '../../../hooks/useShowTransition';
|
||||
import useMediaTransition from '../../../hooks/useMediaTransition';
|
||||
import usePrevious from '../../../hooks/usePrevious';
|
||||
import useBuffering from '../../../hooks/useBuffering';
|
||||
import useVideoCleanup from '../../../hooks/useVideoCleanup';
|
||||
import useVideoAutoPause from './hooks/useVideoAutoPause';
|
||||
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
|
||||
|
||||
import ProgressSpinner from '../../ui/ProgressSpinner';
|
||||
import OptimizedVideo from '../../ui/OptimizedVideo';
|
||||
|
||||
import './RoundVideo.scss';
|
||||
|
||||
@ -141,21 +140,6 @@ const RoundVideo: FC<OwnProps> = ({
|
||||
stopPrevious = stopPlaying;
|
||||
}, [stopPlaying]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!playerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldPlay) {
|
||||
safePlay(playerRef.current);
|
||||
} else {
|
||||
playerRef.current.pause();
|
||||
}
|
||||
}, [shouldPlay]);
|
||||
|
||||
useVideoAutoPause(playerRef, shouldPlay);
|
||||
useVideoCleanup(playerRef, [mediaData]);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
if (!mediaData) {
|
||||
setIsLoadAllowed((isAllowed) => !isAllowed);
|
||||
@ -212,8 +196,10 @@ const RoundVideo: FC<OwnProps> = ({
|
||||
{mediaData && (
|
||||
<div className="video-wrapper">
|
||||
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
|
||||
<video
|
||||
<OptimizedVideo
|
||||
canPlay={shouldPlay}
|
||||
ref={playerRef}
|
||||
src={mediaData}
|
||||
className={videoClassName}
|
||||
width={ROUND_VIDEO_DIMENSIONS_PX}
|
||||
height={ROUND_VIDEO_DIMENSIONS_PX}
|
||||
@ -226,9 +212,7 @@ const RoundVideo: FC<OwnProps> = ({
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...bufferingHandlers}
|
||||
onTimeUpdate={isActivated ? handleTimeUpdate : undefined}
|
||||
>
|
||||
<source src={mediaData} />
|
||||
</video>
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="progress" ref={playingProgressRef} />
|
||||
|
||||
@ -7,7 +7,6 @@ import { ApiMediaFormat } from '../../../api/types';
|
||||
import { getStickerDimensions } from '../../common/helpers/mediaDimensions';
|
||||
import { getMessageMediaFormat, getMessageMediaHash } from '../../../global/helpers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import safePlay from '../../../util/safePlay';
|
||||
import { IS_WEBM_SUPPORTED } from '../../../util/environment';
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
@ -20,6 +19,7 @@ import useThumbnail from '../../../hooks/useThumbnail';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
|
||||
import AnimatedSticker from '../../common/AnimatedSticker';
|
||||
import OptimizedVideo from '../../ui/OptimizedVideo';
|
||||
|
||||
import './Sticker.scss';
|
||||
|
||||
@ -101,17 +101,6 @@ const Sticker: FC<OwnProps> = ({
|
||||
onStopEffect?.();
|
||||
}, [onStopEffect, stopPlayingEffect]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isVideo || !ref.current) return;
|
||||
const video = ref.current.querySelector('video');
|
||||
if (!video) return;
|
||||
if (shouldPlay) {
|
||||
safePlay(video);
|
||||
} else {
|
||||
video.pause();
|
||||
}
|
||||
}, [isVideo, shouldPlay]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasEffect && shouldPlay && shouldPlayEffect) {
|
||||
startPlayingEffect();
|
||||
@ -164,11 +153,11 @@ const Sticker: FC<OwnProps> = ({
|
||||
/>
|
||||
)}
|
||||
{isVideo && canDisplayVideo && isMediaReady && (
|
||||
<video
|
||||
<OptimizedVideo
|
||||
canPlay={shouldPlay}
|
||||
src={mediaData as string}
|
||||
width={width}
|
||||
height={height}
|
||||
autoPlay={shouldPlay}
|
||||
playsInline
|
||||
disablePictureInPicture
|
||||
loop={shouldLoop}
|
||||
|
||||
@ -24,12 +24,11 @@ import useMedia from '../../../hooks/useMedia';
|
||||
import useShowTransition from '../../../hooks/useShowTransition';
|
||||
import usePrevious from '../../../hooks/usePrevious';
|
||||
import useBuffering from '../../../hooks/useBuffering';
|
||||
import useVideoCleanup from '../../../hooks/useVideoCleanup';
|
||||
import useMediaTransition from '../../../hooks/useMediaTransition';
|
||||
import useVideoAutoPause from './hooks/useVideoAutoPause';
|
||||
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
|
||||
|
||||
import ProgressSpinner from '../../ui/ProgressSpinner';
|
||||
import OptimizedVideo from '../../ui/OptimizedVideo';
|
||||
|
||||
export type OwnProps = {
|
||||
id?: string;
|
||||
@ -125,15 +124,12 @@ const Video: FC<OwnProps> = ({
|
||||
setPlayProgress(Math.max(0, e.currentTarget.currentTime - 1));
|
||||
}, []);
|
||||
|
||||
const duration = (videoRef.current?.duration) || video.duration || 0;
|
||||
const duration = videoRef.current?.duration || video.duration || 0;
|
||||
|
||||
const isOwn = isOwnMessage(message);
|
||||
const isForwarded = isForwardedMessage(message);
|
||||
const { width, height } = dimensions || calculateVideoDimensions(video, isOwn, isForwarded, noAvatars);
|
||||
|
||||
useVideoAutoPause(videoRef, isInline);
|
||||
useVideoCleanup(videoRef, [isInline]);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
if (isUploading) {
|
||||
if (onCancelUpload) {
|
||||
@ -145,7 +141,6 @@ const Video: FC<OwnProps> = ({
|
||||
setIsLoadAllowed((isAllowed) => !isAllowed);
|
||||
} else if (fullMediaData && !isPlayAllowed) {
|
||||
setIsPlayAllowed(true);
|
||||
videoRef.current!.play();
|
||||
} else if (onClick) {
|
||||
onClick(message.id);
|
||||
}
|
||||
@ -177,12 +172,13 @@ const Video: FC<OwnProps> = ({
|
||||
draggable={!isProtected}
|
||||
/>
|
||||
{isInline && (
|
||||
<video
|
||||
<OptimizedVideo
|
||||
ref={videoRef}
|
||||
canPlay={isPlayAllowed}
|
||||
src={fullMediaData}
|
||||
className="full-media"
|
||||
width={width}
|
||||
height={height}
|
||||
autoPlay={isPlayAllowed}
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
@ -191,9 +187,7 @@ const Video: FC<OwnProps> = ({
|
||||
draggable={!isProtected}
|
||||
onTimeUpdate={handleTimeUpdate}
|
||||
style={aspectRatio}
|
||||
>
|
||||
<source src={fullMediaData} />
|
||||
</video>
|
||||
/>
|
||||
)}
|
||||
{isProtected && <span className="protector" />}
|
||||
{shouldRenderPlayButton && <i className={buildClassName('icon-large-play', playButtonClassNames)} />}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useCallback, useRef } from '../../../../lib/teact/teact';
|
||||
import { useCallback, useEffect, useRef } from '../../../../lib/teact/teact';
|
||||
|
||||
import { fastRaf } from '../../../../util/schedulers';
|
||||
import safePlay from '../../../../util/safePlay';
|
||||
@ -6,7 +6,6 @@ import useBackgroundMode from '../../../../hooks/useBackgroundMode';
|
||||
import useHeavyAnimationCheck from '../../../../hooks/useHeavyAnimationCheck';
|
||||
|
||||
export default function useVideoAutoPause(playerRef: { current: HTMLVideoElement | null }, canPlay: boolean) {
|
||||
const wasPlaying = useRef(playerRef.current?.paused);
|
||||
const canPlayRef = useRef();
|
||||
canPlayRef.current = canPlay;
|
||||
|
||||
@ -15,23 +14,15 @@ export default function useVideoAutoPause(playerRef: { current: HTMLVideoElement
|
||||
const freezePlaying = useCallback(() => {
|
||||
isFrozenRef.current = true;
|
||||
|
||||
if (!playerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
wasPlaying.current = !playerRef.current.paused;
|
||||
|
||||
if (wasPlaying.current) {
|
||||
playerRef.current.pause();
|
||||
}
|
||||
playerRef.current?.pause();
|
||||
}, [playerRef]);
|
||||
|
||||
const unfreezePlaying = useCallback(() => {
|
||||
isFrozenRef.current = false;
|
||||
|
||||
if (
|
||||
playerRef.current && wasPlaying.current && canPlayRef.current
|
||||
// At this point HTMLVideoElement can be unmounted from the DOM
|
||||
playerRef.current && canPlayRef.current
|
||||
// At this point `HTMLVideoElement` can be unmounted from the DOM
|
||||
&& document.body.contains(playerRef.current)
|
||||
) {
|
||||
safePlay(playerRef.current);
|
||||
@ -46,11 +37,24 @@ export default function useVideoAutoPause(playerRef: { current: HTMLVideoElement
|
||||
useHeavyAnimationCheck(freezePlaying, unfreezePlaying);
|
||||
|
||||
const handlePlaying = useCallback(() => {
|
||||
if (isFrozenRef.current) {
|
||||
wasPlaying.current = true;
|
||||
if (!canPlayRef.current || isFrozenRef.current) {
|
||||
playerRef.current!.pause();
|
||||
}
|
||||
}, [playerRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!playerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (canPlay) {
|
||||
if (!isFrozenRef.current) {
|
||||
safePlay(playerRef.current);
|
||||
}
|
||||
} else {
|
||||
playerRef.current!.pause();
|
||||
}
|
||||
}, [canPlay, playerRef]);
|
||||
|
||||
return { handlePlaying };
|
||||
}
|
||||
|
||||
34
src/components/ui/OptimizedVideo.tsx
Normal file
34
src/components/ui/OptimizedVideo.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React, { memo, useRef } from '../../lib/teact/teact';
|
||||
|
||||
import useVideoAutoPause from '../middle/message/hooks/useVideoAutoPause';
|
||||
import useVideoCleanup from '../../hooks/useVideoCleanup';
|
||||
|
||||
type OwnProps =
|
||||
{
|
||||
canPlay: boolean;
|
||||
ref?: React.RefObject<HTMLVideoElement>;
|
||||
}
|
||||
& React.DetailedHTMLProps<React.VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>;
|
||||
|
||||
function OptimizedVideo({
|
||||
ref,
|
||||
canPlay,
|
||||
...restProps
|
||||
}: OwnProps) {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const localRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
if (!ref) {
|
||||
ref = localRef;
|
||||
}
|
||||
|
||||
const { handlePlaying } = useVideoAutoPause(ref, canPlay);
|
||||
useVideoCleanup(ref, []);
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<video ref={ref} autoPlay {...restProps} onPlaying={handlePlaying} />
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(OptimizedVideo);
|
||||
Loading…
x
Reference in New Issue
Block a user