2021-05-24 00:28:09 +03:00

229 lines
7.8 KiB
TypeScript

import React, {
FC, useCallback, useRef, useState,
} from '../../../lib/teact/teact';
import { ApiMessage } from '../../../api/types';
import { IMediaDimensions } from './helpers/calculateAlbumLayout';
import { formatMediaDuration } from '../../../util/dateFormat';
import buildClassName from '../../../util/buildClassName';
import { calculateVideoDimensions } from '../../common/helpers/mediaDimensions';
import {
canMessagePlayVideoInline,
getMediaTransferState,
getMessageMediaFormat,
getMessageMediaHash,
isForwardedMessage,
isOwnMessage,
} from '../../../modules/helpers';
import { ObserveFn, useIsIntersecting } from '../../../hooks/useIntersectionObserver';
import useMediaWithDownloadProgress from '../../../hooks/useMediaWithDownloadProgress';
import useMedia from '../../../hooks/useMedia';
import useShowTransition from '../../../hooks/useShowTransition';
import useTransitionForMedia from '../../../hooks/useTransitionForMedia';
import usePrevious from '../../../hooks/usePrevious';
import useBuffering from '../../../hooks/useBuffering';
import useHeavyAnimationCheckForVideo from '../../../hooks/useHeavyAnimationCheckForVideo';
import useVideoCleanup from '../../../hooks/useVideoCleanup';
import usePauseOnInactive from './hooks/usePauseOnInactive';
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
import ProgressSpinner from '../../ui/ProgressSpinner';
export type OwnProps = {
id?: string;
message: ApiMessage;
observeIntersection: ObserveFn;
shouldAutoLoad?: boolean;
shouldAutoPlay?: boolean;
uploadProgress?: number;
dimensions?: IMediaDimensions;
lastSyncTime?: number;
onClick?: (id: number) => void;
onCancelUpload?: (message: ApiMessage) => void;
};
const Video: FC<OwnProps> = ({
id,
message,
observeIntersection,
shouldAutoLoad,
shouldAutoPlay,
uploadProgress,
lastSyncTime,
dimensions,
onClick,
onCancelUpload,
}) => {
// 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 video = message.content.video!;
const localBlobUrl = video.blobUrl;
const canPlayInline = Boolean(localBlobUrl) || canMessagePlayVideoInline(video);
const isIntersecting = useIsIntersecting(ref, observeIntersection);
const [isDownloadAllowed, setIsDownloadAllowed] = useState(shouldAutoLoad);
const shouldDownload = Boolean(isDownloadAllowed && isIntersecting && lastSyncTime);
const [isPlayAllowed, setIsPlayAllowed] = useState(shouldAutoPlay);
const previewBlobUrl = useMedia(
getMessageMediaHash(message, 'pictogram'),
!(isIntersecting && lastSyncTime),
getMessageMediaFormat(message, 'pictogram'),
lastSyncTime,
);
const thumbRef = useBlurredMediaThumbRef(message);
const { mediaData, downloadProgress } = useMediaWithDownloadProgress(
getMessageMediaHash(message, 'inline'),
!shouldDownload,
getMessageMediaFormat(message, 'inline'),
lastSyncTime,
);
const fullMediaData = localBlobUrl || mediaData;
const isInline = Boolean(canPlayInline && isIntersecting && fullMediaData);
const { isBuffered, bufferingHandlers } = useBuffering(!shouldAutoLoad);
const { isUploading, isTransferring, transferProgress } = getMediaTransferState(
message,
uploadProgress || downloadProgress,
shouldDownload && (canPlayInline && !isBuffered),
);
const wasDownloadDisabled = usePrevious(isDownloadAllowed) === false;
const {
shouldRender: shouldRenderSpinner,
transitionClassNames: spinnerClassNames,
} = useShowTransition(isTransferring, undefined, wasDownloadDisabled);
const { shouldRenderThumb, transitionClassNames } = useTransitionForMedia(fullMediaData, 'slow');
const [playProgress, setPlayProgress] = useState<number>(0);
const handleTimeUpdate = useCallback((e: React.SyntheticEvent<HTMLVideoElement>) => {
setPlayProgress(Math.max(0, e.currentTarget.currentTime - 1));
}, []);
const isOwn = isOwnMessage(message);
const isForwarded = isForwardedMessage(message);
const { width, height } = dimensions || calculateVideoDimensions(video, isOwn, isForwarded);
useHeavyAnimationCheckForVideo(videoRef, Boolean(isInline && shouldAutoPlay));
usePauseOnInactive(videoRef, isPlayAllowed);
useVideoCleanup(videoRef, [isInline]);
const handleClick = useCallback(() => {
if (isUploading) {
if (onCancelUpload) {
onCancelUpload(message);
}
} else if (canPlayInline && !fullMediaData) {
setIsDownloadAllowed((isAllowed) => !isAllowed);
} else if (canPlayInline && fullMediaData && !isPlayAllowed) {
setIsPlayAllowed(true);
videoRef.current!.play();
} else if (onClick) {
onClick(message.id);
}
}, [isUploading, canPlayInline, fullMediaData, isPlayAllowed, onClick, onCancelUpload, message]);
const className = buildClassName('media-inner dark', !isUploading && 'interactive');
const videoClassName = buildClassName('full-media', transitionClassNames);
const videoStyle = previewBlobUrl
? `background-image: url(${previewBlobUrl}); background-size: cover`
: 'background: transparent';
const style = dimensions
? `width: ${width}px; height: ${height}px; left: ${dimensions.x}px; top: ${dimensions.y}px;`
: '';
const shouldRenderInlineVideo = isInline;
const shouldRenderHqPreview = !canPlayInline && mediaData;
const shouldRenderPlayButton = !canPlayInline || (isDownloadAllowed && !isPlayAllowed && !shouldRenderSpinner);
const shouldRenderDownloadButton = canPlayInline && !isDownloadAllowed;
return (
<div
ref={ref}
id={id}
className={className}
// @ts-ignore teact feature
style={style}
onClick={isUploading ? undefined : handleClick}
>
{((!isInline || shouldRenderThumb) && !previewBlobUrl)
&& (
<canvas
ref={thumbRef}
className="thumbnail"
// @ts-ignore teact feature
style={`width: ${width}px; height: ${height}px;`}
/>
)}
{previewBlobUrl && fullMediaData && (
<img
src={previewBlobUrl}
className="thumbnail"
// @ts-ignore teact feature
style={`width: ${width}px; height: ${height}px;`}
alt=""
/>
)}
{shouldRenderInlineVideo && (
<video
ref={videoRef}
className={videoClassName}
width={width}
height={height}
autoPlay={isPlayAllowed}
muted
loop
playsInline
// @ts-ignore teact feature
style={videoStyle}
// eslint-disable-next-line react/jsx-props-no-spreading
{...bufferingHandlers}
onTimeUpdate={handleTimeUpdate}
>
<source src={fullMediaData} />
</video>
)}
{shouldRenderHqPreview && (
<img
src={mediaData}
className={`full-media ${transitionClassNames}`}
width={width}
height={height}
alt=""
/>
)}
{shouldRenderPlayButton && (
<i className="icon-large-play" />
)}
{shouldRenderSpinner && (
<div className={`media-loading ${spinnerClassNames}`}>
<ProgressSpinner progress={transferProgress} onClick={isUploading ? handleClick : undefined} />
</div>
)}
{shouldRenderDownloadButton && (
<i className="icon-download" />
)}
{isTransferring && !canPlayInline ? (
<span className="message-upload-progress">{Math.round(transferProgress * 100)}%</span>
) : isTransferring && canPlayInline ? (
<span className="message-upload-progress">...</span>
) : (
<div className="message-media-duration">
{video.isGif ? 'GIF' : formatMediaDuration(video.duration - playProgress)}
</div>
)}
</div>
);
};
export default Video;