One-Time Video: Show and play one-time video (#4258)
Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com> Co-authored-by: Alexander Zinchuk <alx.zinchuk@gmail.com>
This commit is contained in:
parent
80e70d2899
commit
cb9330bcef
@ -78,10 +78,19 @@ export function buildMessageMediaContent(media: GramJs.TypeMessageMedia): MediaC
|
|||||||
if (isExpiredVoice) {
|
if (isExpiredVoice) {
|
||||||
return { isExpiredVoice };
|
return { isExpiredVoice };
|
||||||
}
|
}
|
||||||
|
const isExpiredRoundVideo = isExpiredRoundVideoMessage(media);
|
||||||
|
if (isExpiredRoundVideo) {
|
||||||
|
return { isExpiredRoundVideo };
|
||||||
|
}
|
||||||
|
|
||||||
const voice = buildVoice(media);
|
const voice = buildVoice(media);
|
||||||
if (voice) return { voice, ttlSeconds };
|
if (voice) return { voice, ttlSeconds };
|
||||||
|
|
||||||
|
if ('round' in media && media.round) {
|
||||||
|
const video = buildVideo(media);
|
||||||
|
if (video) return { video, ttlSeconds };
|
||||||
|
}
|
||||||
|
|
||||||
// Other disappearing media types are not supported
|
// Other disappearing media types are not supported
|
||||||
if (ttlSeconds !== undefined) {
|
if (ttlSeconds !== undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -270,6 +279,13 @@ function isExpiredVoiceMessage(media: GramJs.TypeMessageMedia): MediaContent['is
|
|||||||
return !media.document && media.voice;
|
return !media.document && media.voice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isExpiredRoundVideoMessage(media: GramJs.TypeMessageMedia): MediaContent['isExpiredRoundVideo'] {
|
||||||
|
if (!(media instanceof GramJs.MessageMediaDocument)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !media.document && media.round;
|
||||||
|
}
|
||||||
|
|
||||||
function buildVoice(media: GramJs.TypeMessageMedia): ApiVoice | undefined {
|
function buildVoice(media: GramJs.TypeMessageMedia): ApiVoice | undefined {
|
||||||
if (
|
if (
|
||||||
!(media instanceof GramJs.MessageMediaDocument)
|
!(media instanceof GramJs.MessageMediaDocument)
|
||||||
|
|||||||
@ -485,8 +485,9 @@ export type MediaContent = {
|
|||||||
storyData?: ApiMessageStoryData;
|
storyData?: ApiMessageStoryData;
|
||||||
giveaway?: ApiGiveaway;
|
giveaway?: ApiGiveaway;
|
||||||
giveawayResults?: ApiGiveawayResults;
|
giveawayResults?: ApiGiveawayResults;
|
||||||
ttlSeconds?: number;
|
|
||||||
isExpiredVoice?: boolean;
|
isExpiredVoice?: boolean;
|
||||||
|
isExpiredRoundVideo?: boolean;
|
||||||
|
ttlSeconds?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ApiMessage {
|
export interface ApiMessage {
|
||||||
|
|||||||
@ -128,7 +128,7 @@ const Audio: FC<OwnProps> = ({
|
|||||||
const coverHash = getMessageMediaHash(message, 'pictogram');
|
const coverHash = getMessageMediaHash(message, 'pictogram');
|
||||||
const coverBlobUrl = useMedia(coverHash, false, ApiMediaFormat.BlobUrl);
|
const coverBlobUrl = useMedia(coverHash, false, ApiMediaFormat.BlobUrl);
|
||||||
const hasTtl = hasMessageTtl(message);
|
const hasTtl = hasMessageTtl(message);
|
||||||
const isOneTimeModalOrigin = origin === AudioOrigin.OneTimeModal;
|
const isInOneTimeModal = origin === AudioOrigin.OneTimeModal;
|
||||||
const trackType = isVoice ? (hasTtl ? 'oneTimeVoice' : 'voice') : 'audio';
|
const trackType = isVoice ? (hasTtl ? 'oneTimeVoice' : 'voice') : 'audio';
|
||||||
|
|
||||||
const mediaData = useMedia(
|
const mediaData = useMedia(
|
||||||
@ -156,7 +156,7 @@ const Audio: FC<OwnProps> = ({
|
|||||||
isBuffered, bufferedRanges, bufferingHandlers, checkBuffering,
|
isBuffered, bufferedRanges, bufferingHandlers, checkBuffering,
|
||||||
} = useBuffering();
|
} = useBuffering();
|
||||||
|
|
||||||
const noReset = isOneTimeModalOrigin;
|
const noReset = isInOneTimeModal;
|
||||||
const {
|
const {
|
||||||
isPlaying, playProgress, playPause, setCurrentTime, duration,
|
isPlaying, playProgress, playPause, setCurrentTime, duration,
|
||||||
} = useAudioPlayer(
|
} = useAudioPlayer(
|
||||||
@ -179,7 +179,7 @@ const Audio: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const reversePlayProgress = 1 - playProgress;
|
const reversePlayProgress = 1 - playProgress;
|
||||||
const isOwn = isOwnMessage(message);
|
const isOwn = isOwnMessage(message);
|
||||||
const isReverse = hasTtl && isOneTimeModalOrigin;
|
const isReverse = hasTtl && isInOneTimeModal;
|
||||||
|
|
||||||
const waveformCanvasRef = useWaveformCanvas(
|
const waveformCanvasRef = useWaveformCanvas(
|
||||||
theme,
|
theme,
|
||||||
@ -279,14 +279,14 @@ const Audio: FC<OwnProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!seekerRef.current || !withSeekline || isOneTimeModalOrigin) return undefined;
|
if (!seekerRef.current || !withSeekline || isInOneTimeModal) return undefined;
|
||||||
return captureEvents(seekerRef.current, {
|
return captureEvents(seekerRef.current, {
|
||||||
onCapture: handleStartSeek,
|
onCapture: handleStartSeek,
|
||||||
onRelease: handleStopSeek,
|
onRelease: handleStopSeek,
|
||||||
onClick: handleStopSeek,
|
onClick: handleStopSeek,
|
||||||
onDrag: handleSeek,
|
onDrag: handleSeek,
|
||||||
});
|
});
|
||||||
}, [withSeekline, handleStartSeek, handleSeek, handleStopSeek, isOneTimeModalOrigin]);
|
}, [withSeekline, handleStartSeek, handleSeek, handleStopSeek, isInOneTimeModal]);
|
||||||
|
|
||||||
function renderFirstLine() {
|
function renderFirstLine() {
|
||||||
if (isVoice) {
|
if (isVoice) {
|
||||||
@ -323,7 +323,7 @@ const Audio: FC<OwnProps> = ({
|
|||||||
const fullClassName = buildClassName(
|
const fullClassName = buildClassName(
|
||||||
'Audio',
|
'Audio',
|
||||||
className,
|
className,
|
||||||
isOneTimeModalOrigin && 'non-interactive',
|
isInOneTimeModal && 'non-interactive',
|
||||||
origin === AudioOrigin.Inline && 'inline',
|
origin === AudioOrigin.Inline && 'inline',
|
||||||
isOwn && origin === AudioOrigin.Inline && 'own',
|
isOwn && origin === AudioOrigin.Inline && 'own',
|
||||||
(origin === AudioOrigin.Search || origin === AudioOrigin.SharedMedia) && 'bigger',
|
(origin === AudioOrigin.Search || origin === AudioOrigin.SharedMedia) && 'bigger',
|
||||||
@ -383,11 +383,11 @@ const Audio: FC<OwnProps> = ({
|
|||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
isRtl={lang.isRtl}
|
isRtl={lang.isRtl}
|
||||||
backgroundImage={coverBlobUrl}
|
backgroundImage={coverBlobUrl}
|
||||||
nonInteractive={isOneTimeModalOrigin}
|
nonInteractive={isInOneTimeModal}
|
||||||
>
|
>
|
||||||
{!isOneTimeModalOrigin && <Icon name="play" />}
|
{!isInOneTimeModal && <Icon name="play" />}
|
||||||
{!isOneTimeModalOrigin && <Icon name="pause" />}
|
{!isInOneTimeModal && <Icon name="pause" />}
|
||||||
{isOneTimeModalOrigin && (
|
{isInOneTimeModal && (
|
||||||
<AnimatedIcon
|
<AnimatedIcon
|
||||||
className="flame"
|
className="flame"
|
||||||
tgsUrl={LOCAL_TGS_URLS.Flame}
|
tgsUrl={LOCAL_TGS_URLS.Flame}
|
||||||
@ -397,7 +397,7 @@ const Audio: FC<OwnProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
{hasTtl && !isOneTimeModalOrigin && (
|
{hasTtl && !isInOneTimeModal && (
|
||||||
<Icon name="view-once" />
|
<Icon name="view-once" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -424,7 +424,7 @@ const Audio: FC<OwnProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isOneTimeModalOrigin && !shouldRenderSpinner && (
|
{isInOneTimeModal && !shouldRenderSpinner && (
|
||||||
<div className={buildClassName('media-loading')}>
|
<div className={buildClassName('media-loading')}>
|
||||||
<ProgressSpinner
|
<ProgressSpinner
|
||||||
progress={playProgress}
|
progress={playProgress}
|
||||||
@ -461,7 +461,7 @@ const Audio: FC<OwnProps> = ({
|
|||||||
onDateClick ? handleDateClick : undefined,
|
onDateClick ? handleDateClick : undefined,
|
||||||
)}
|
)}
|
||||||
{origin === AudioOrigin.SharedMedia && (voice || video) && renderWithTitle()}
|
{origin === AudioOrigin.SharedMedia && (voice || video) && renderWithTitle()}
|
||||||
{(origin === AudioOrigin.Inline || isOneTimeModalOrigin) && voice && (
|
{(origin === AudioOrigin.Inline || isInOneTimeModal) && voice && (
|
||||||
renderVoice(
|
renderVoice(
|
||||||
voice,
|
voice,
|
||||||
seekerRef,
|
seekerRef,
|
||||||
|
|||||||
@ -964,6 +964,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
metaPosition === 'in-text' && 'with-meta',
|
metaPosition === 'in-text' && 'with-meta',
|
||||||
outgoingStatus && 'with-outgoing-icon',
|
outgoingStatus && 'with-outgoing-icon',
|
||||||
);
|
);
|
||||||
|
const shouldReadMedia = !hasTtl || !isOwn || isChatWithSelf;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className} onDoubleClick={handleContentDoubleClick} dir="auto">
|
<div className={className} onDoubleClick={handleContentDoubleClick} dir="auto">
|
||||||
@ -1086,6 +1087,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
observeIntersection={observeIntersectionForLoading}
|
observeIntersection={observeIntersectionForLoading}
|
||||||
canAutoLoad={canAutoLoadMedia}
|
canAutoLoad={canAutoLoadMedia}
|
||||||
isDownloading={isDownloading}
|
isDownloading={isDownloading}
|
||||||
|
onReadMedia={shouldReadMedia ? handleReadMedia : undefined}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!isAlbum && video && !video.isRound && (
|
{!isAlbum && video && !video.isRound && (
|
||||||
@ -1115,7 +1117,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
noAvatars={noAvatars}
|
noAvatars={noAvatars}
|
||||||
onPlay={handleAudioPlay}
|
onPlay={handleAudioPlay}
|
||||||
onReadMedia={voice && (!isOwn || isChatWithSelf || (isOwn && !hasTtl)) ? handleReadMedia : undefined}
|
onReadMedia={voice && shouldReadMedia ? handleReadMedia : undefined}
|
||||||
onCancelUpload={handleCancelUpload}
|
onCancelUpload={handleCancelUpload}
|
||||||
isDownloading={isDownloading}
|
isDownloading={isDownloading}
|
||||||
isTranscribing={isTranscribing}
|
isTranscribing={isTranscribing}
|
||||||
|
|||||||
@ -4,15 +4,24 @@
|
|||||||
height: 15rem;
|
height: 15rem;
|
||||||
cursor: var(--custom-cursor, pointer);
|
cursor: var(--custom-cursor, pointer);
|
||||||
|
|
||||||
|
&.non-interactive {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.video-wrapper {
|
.video-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas {
|
.thumbnail {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,4 +47,24 @@
|
|||||||
video::-webkit-media-controls-start-playback-button {
|
video::-webkit-media-controls-start-playback-button {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.play-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
.icon-play {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-view-once {
|
||||||
|
position: absolute;
|
||||||
|
background-color: rgba(0, 0, 0, 0.75);
|
||||||
|
padding: 0.125rem;
|
||||||
|
left: 1.625rem;
|
||||||
|
bottom: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: var(--color-white);
|
||||||
|
z-index: var(--z-badge);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,9 @@ import type { ApiMessage } from '../../../api/types';
|
|||||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
import { ApiMediaFormat } from '../../../api/types';
|
import { ApiMediaFormat } from '../../../api/types';
|
||||||
|
|
||||||
import { getMessageMediaFormat, getMessageMediaHash, getMessageMediaThumbDataUri } from '../../../global/helpers';
|
import {
|
||||||
|
getMessageMediaFormat, getMessageMediaHash, getMessageMediaThumbDataUri, hasMessageTtl,
|
||||||
|
} from '../../../global/helpers';
|
||||||
import { stopCurrentAudio } from '../../../util/audioPlayer';
|
import { stopCurrentAudio } from '../../../util/audioPlayer';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { formatMediaDuration } from '../../../util/dateFormat';
|
import { formatMediaDuration } from '../../../util/dateFormat';
|
||||||
@ -29,6 +31,9 @@ import useShowTransition from '../../../hooks/useShowTransition';
|
|||||||
import useSignal from '../../../hooks/useSignal';
|
import useSignal from '../../../hooks/useSignal';
|
||||||
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
|
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
|
||||||
|
|
||||||
|
import Icon from '../../common/Icon';
|
||||||
|
import MediaSpoiler from '../../common/MediaSpoiler';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
import OptimizedVideo from '../../ui/OptimizedVideo';
|
import OptimizedVideo from '../../ui/OptimizedVideo';
|
||||||
import ProgressSpinner from '../../ui/ProgressSpinner';
|
import ProgressSpinner from '../../ui/ProgressSpinner';
|
||||||
|
|
||||||
@ -36,9 +41,13 @@ import './RoundVideo.scss';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
observeIntersection: ObserveFn;
|
className?: string;
|
||||||
canAutoLoad?: boolean;
|
canAutoLoad?: boolean;
|
||||||
isDownloading?: boolean;
|
isDownloading?: boolean;
|
||||||
|
origin?: 'oneTimeModal';
|
||||||
|
observeIntersection?: ObserveFn;
|
||||||
|
onStop?: NoneToVoidFunction;
|
||||||
|
onReadMedia?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PROGRESS_CENTER = ROUND_VIDEO_DIMENSIONS_PX / 2;
|
const PROGRESS_CENTER = ROUND_VIDEO_DIMENSIONS_PX / 2;
|
||||||
@ -50,9 +59,13 @@ let stopPrevious: NoneToVoidFunction;
|
|||||||
|
|
||||||
const RoundVideo: FC<OwnProps> = ({
|
const RoundVideo: FC<OwnProps> = ({
|
||||||
message,
|
message,
|
||||||
observeIntersection,
|
className,
|
||||||
canAutoLoad,
|
canAutoLoad,
|
||||||
isDownloading,
|
isDownloading,
|
||||||
|
origin,
|
||||||
|
observeIntersection,
|
||||||
|
onStop,
|
||||||
|
onReadMedia,
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
@ -63,6 +76,8 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const video = message.content.video!;
|
const video = message.content.video!;
|
||||||
|
|
||||||
|
const { cancelMessageMediaDownload, openOneTimeMediaModal } = getActions();
|
||||||
|
|
||||||
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
||||||
|
|
||||||
const [isLoadAllowed, setIsLoadAllowed] = useState(canAutoLoad);
|
const [isLoadAllowed, setIsLoadAllowed] = useState(canAutoLoad);
|
||||||
@ -80,11 +95,14 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const [isPlayerReady, markPlayerReady] = useFlag();
|
const [isPlayerReady, markPlayerReady] = useFlag();
|
||||||
|
const hasTtl = hasMessageTtl(message);
|
||||||
|
const isInOneTimeModal = origin === 'oneTimeModal';
|
||||||
|
const shouldRenderSpoiler = hasTtl && !isInOneTimeModal;
|
||||||
const hasThumb = Boolean(getMessageMediaThumbDataUri(message));
|
const hasThumb = Boolean(getMessageMediaThumbDataUri(message));
|
||||||
const noThumb = !hasThumb || isPlayerReady;
|
const noThumb = !hasThumb || isPlayerReady || shouldRenderSpoiler;
|
||||||
const thumbRef = useBlurredMediaThumbRef(message, noThumb);
|
const thumbRef = useBlurredMediaThumbRef(message, noThumb);
|
||||||
const thumbClassNames = useMediaTransition(!noThumb);
|
const thumbClassNames = useMediaTransition(!noThumb);
|
||||||
|
const thumbDataUri = getMessageMediaThumbDataUri(message);
|
||||||
const isTransferring = (isLoadAllowed && !isPlayerReady) || isDownloading;
|
const isTransferring = (isLoadAllowed && !isPlayerReady) || isDownloading;
|
||||||
const wasLoadDisabled = usePrevious(isLoadAllowed) === false;
|
const wasLoadDisabled = usePrevious(isLoadAllowed) === false;
|
||||||
|
|
||||||
@ -133,18 +151,7 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
stopPrevious = stopPlaying;
|
stopPrevious = stopPlaying;
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleClick = useLastCallback(() => {
|
const tooglePlaying = useLastCallback(() => {
|
||||||
if (!mediaData) {
|
|
||||||
setIsLoadAllowed((isAllowed) => !isAllowed);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDownloading) {
|
|
||||||
getActions().cancelMessageMediaDownload({ message });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const playerEl = playerRef.current!;
|
const playerEl = playerRef.current!;
|
||||||
if (isActivated) {
|
if (isActivated) {
|
||||||
if (playerEl.paused) {
|
if (playerEl.paused) {
|
||||||
@ -160,25 +167,77 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
playerEl.currentTime = 0;
|
playerEl.currentTime = 0;
|
||||||
safePlay(playerEl);
|
safePlay(playerEl);
|
||||||
stopCurrentAudio();
|
stopCurrentAudio();
|
||||||
|
|
||||||
setIsActivated(true);
|
setIsActivated(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isInOneTimeModal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tooglePlaying();
|
||||||
|
}, [isInOneTimeModal]);
|
||||||
|
|
||||||
|
const handleClick = useLastCallback(() => {
|
||||||
|
if (!mediaData) {
|
||||||
|
setIsLoadAllowed((isAllowed) => !isAllowed);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDownloading) {
|
||||||
|
cancelMessageMediaDownload({ message });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasTtl && !isInOneTimeModal) {
|
||||||
|
openOneTimeMediaModal({ message });
|
||||||
|
onReadMedia?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tooglePlaying();
|
||||||
|
});
|
||||||
|
|
||||||
const handleTimeUpdate = useLastCallback((e: React.UIEvent<HTMLVideoElement>) => {
|
const handleTimeUpdate = useLastCallback((e: React.UIEvent<HTMLVideoElement>) => {
|
||||||
const playerEl = e.currentTarget;
|
const playerEl = e.currentTarget;
|
||||||
|
|
||||||
setProgress(playerEl.currentTime / playerEl.duration);
|
setProgress(playerEl.currentTime / playerEl.duration);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function renderPlayWrapper() {
|
||||||
|
return (
|
||||||
|
<div className="play-wrapper">
|
||||||
|
<Button
|
||||||
|
color="dark"
|
||||||
|
round
|
||||||
|
size="smaller"
|
||||||
|
className="play"
|
||||||
|
nonInteractive
|
||||||
|
>
|
||||||
|
<Icon name="play" />
|
||||||
|
</Button>
|
||||||
|
<Icon name="view-once" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="RoundVideo media-inner"
|
className={buildClassName('RoundVideo', 'media-inner', isInOneTimeModal && 'non-interactive', className)}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
{mediaData && (
|
{mediaData && (
|
||||||
<div className="video-wrapper">
|
<div className="video-wrapper">
|
||||||
|
{shouldRenderSpoiler && (
|
||||||
|
<MediaSpoiler
|
||||||
|
isVisible
|
||||||
|
thumbDataUri={thumbDataUri}
|
||||||
|
width={ROUND_VIDEO_DIMENSIONS_PX}
|
||||||
|
height={ROUND_VIDEO_DIMENSIONS_PX}
|
||||||
|
className="media-spoiler"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<OptimizedVideo
|
<OptimizedVideo
|
||||||
canPlay={shouldPlay}
|
canPlay={shouldPlay}
|
||||||
ref={playerRef}
|
ref={playerRef}
|
||||||
@ -186,22 +245,24 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
className="full-media"
|
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={!shouldRenderSpoiler}
|
||||||
disablePictureInPicture
|
disablePictureInPicture
|
||||||
muted={!isActivated}
|
muted={!isActivated}
|
||||||
loop={!isActivated}
|
loop={!isActivated}
|
||||||
playsInline
|
playsInline
|
||||||
onEnded={isActivated ? stopPlaying : undefined}
|
onEnded={isActivated ? onStop ?? stopPlaying : undefined}
|
||||||
onTimeUpdate={isActivated ? handleTimeUpdate : undefined}
|
onTimeUpdate={isActivated ? handleTimeUpdate : undefined}
|
||||||
onReady={markPlayerReady}
|
onReady={markPlayerReady}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<canvas
|
{!shouldRenderSpoiler && (
|
||||||
ref={thumbRef}
|
<canvas
|
||||||
className={buildClassName('thumbnail', thumbClassNames)}
|
ref={thumbRef}
|
||||||
style={`width: ${ROUND_VIDEO_DIMENSIONS_PX}px; height: ${ROUND_VIDEO_DIMENSIONS_PX}px`}
|
className={buildClassName('thumbnail', thumbClassNames)}
|
||||||
/>
|
style={`width: ${ROUND_VIDEO_DIMENSIONS_PX}px; height: ${ROUND_VIDEO_DIMENSIONS_PX}px`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="progress">
|
<div className="progress">
|
||||||
{isActivated && (
|
{isActivated && (
|
||||||
<svg width={ROUND_VIDEO_DIMENSIONS_PX} height={ROUND_VIDEO_DIMENSIONS_PX}>
|
<svg width={ROUND_VIDEO_DIMENSIONS_PX} height={ROUND_VIDEO_DIMENSIONS_PX}>
|
||||||
@ -223,13 +284,16 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
<ProgressSpinner progress={isDownloading ? downloadProgress : loadProgress} />
|
<ProgressSpinner progress={isDownloading ? downloadProgress : loadProgress} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{shouldRenderSpoiler && !shouldSpinnerRender && renderPlayWrapper()}
|
||||||
{!mediaData && !isLoadAllowed && (
|
{!mediaData && !isLoadAllowed && (
|
||||||
<i className="icon icon-download" />
|
<i className="icon icon-download" />
|
||||||
)}
|
)}
|
||||||
<div className="message-media-duration">
|
{!isInOneTimeModal && (
|
||||||
{isActivated ? formatMediaDuration(playerRef.current!.currentTime) : formatMediaDuration(video.duration)}
|
<div className="message-media-duration">
|
||||||
{(!isActivated || playerRef.current!.paused) && <i className="icon icon-muted" />}
|
{isActivated ? formatMediaDuration(playerRef.current!.currentTime) : formatMediaDuration(video.duration)}
|
||||||
</div>
|
{(!isActivated || playerRef.current!.paused) && <Icon name="muted" />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
backdrop-filter: blur(2rem);
|
backdrop-filter: blur(2rem);
|
||||||
animation: fade-in-opacity 0.3s ease;
|
animation: fade-in-opacity 0.3s ease;
|
||||||
background-color: rgba(0, 0, 0, 0.25);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
z-index: var(--z-modal-confirm);
|
z-index: var(--z-modal-confirm);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
@ -20,10 +20,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.voice {
|
||||||
background-color: var(--color-background);
|
|
||||||
padding: 0.6875rem;
|
padding: 0.6875rem;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video {
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
|
|||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
|
|
||||||
import Audio from '../../common/Audio';
|
import Audio from '../../common/Audio';
|
||||||
|
import RoundVideo from '../../middle/message/RoundVideo';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
|
|
||||||
import styles from './OneTimeMediaModal.module.scss';
|
import styles from './OneTimeMediaModal.module.scss';
|
||||||
@ -54,9 +55,14 @@ const OneTimeMediaModal = ({
|
|||||||
const closeBtnTitle = isOwn ? lang('Chat.Voice.Single.Close') : lang('Chat.Voice.Single.DeleteAndClose');
|
const closeBtnTitle = isOwn ? lang('Chat.Voice.Single.Close') : lang('Chat.Voice.Single.DeleteAndClose');
|
||||||
|
|
||||||
function renderMedia() {
|
function renderMedia() {
|
||||||
if (message?.content?.voice) {
|
if (!message?.content) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const { voice, video } = message.content;
|
||||||
|
if (voice) {
|
||||||
return (
|
return (
|
||||||
<Audio
|
<Audio
|
||||||
|
className={styles.voice}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
message={message}
|
message={message}
|
||||||
origin={AudioOrigin.OneTimeModal}
|
origin={AudioOrigin.OneTimeModal}
|
||||||
@ -65,13 +71,22 @@ const OneTimeMediaModal = ({
|
|||||||
onPause={handleClose}
|
onPause={handleClose}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
} else if (video?.isRound) {
|
||||||
|
return (
|
||||||
|
<RoundVideo
|
||||||
|
className={styles.video}
|
||||||
|
message={message}
|
||||||
|
origin="oneTimeModal"
|
||||||
|
onStop={handleClose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName(styles.root, transitionClassNames)}>
|
<div className={buildClassName(styles.root, transitionClassNames)}>
|
||||||
<div className={styles.main}>{renderMedia()}</div>
|
{renderMedia()}
|
||||||
<div className={styles.footer}>
|
<div className={styles.footer}>
|
||||||
<Button
|
<Button
|
||||||
faded
|
faded
|
||||||
|
|||||||
@ -324,15 +324,18 @@ export function extractMessageText(message: ApiMessage | ApiStory, inChatList =
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getExpiredMessageDescription(langFn: LangFn, message: ApiMessage): string | undefined {
|
export function getExpiredMessageDescription(langFn: LangFn, message: ApiMessage): string | undefined {
|
||||||
const { isExpiredVoice } = message.content;
|
const { isExpiredVoice, isExpiredRoundVideo } = message.content;
|
||||||
if (isExpiredVoice) {
|
if (isExpiredVoice) {
|
||||||
return langFn('Message.VoiceMessageExpired');
|
return langFn('Message.VoiceMessageExpired');
|
||||||
|
} else if (isExpiredRoundVideo) {
|
||||||
|
return langFn('Message.VideoMessageExpired');
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isExpiredMessage(message: ApiMessage) {
|
export function isExpiredMessage(message: ApiMessage) {
|
||||||
return Boolean(message.content?.isExpiredVoice);
|
const { isExpiredVoice, isExpiredRoundVideo } = message.content ?? {};
|
||||||
|
return Boolean(isExpiredVoice || isExpiredRoundVideo);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasMessageTtl(message: ApiMessage) {
|
export function hasMessageTtl(message: ApiMessage) {
|
||||||
|
|||||||
@ -206,6 +206,12 @@ export function updateChatMessage<T extends GlobalState>(
|
|||||||
voice: undefined,
|
voice: undefined,
|
||||||
isExpiredVoice: true,
|
isExpiredVoice: true,
|
||||||
};
|
};
|
||||||
|
} else if (message.content.video?.isRound) {
|
||||||
|
messageUpdate.content = {
|
||||||
|
...messageUpdate.content,
|
||||||
|
video: undefined,
|
||||||
|
isExpiredRoundVideo: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const updatedMessage = {
|
const updatedMessage = {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user