Media Viewer: Various fixes and improvements (#1710)
This commit is contained in:
parent
831d1589b4
commit
611ec32ee8
@ -1,4 +1,4 @@
|
|||||||
import React, { FC, memo } from '../../lib/teact/teact';
|
import React, { FC, memo, useCallback } from '../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../lib/teact/teactn';
|
import { withGlobal } from '../../lib/teact/teactn';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -50,6 +50,7 @@ type OwnProps = {
|
|||||||
animationLevel: 0 | 1 | 2;
|
animationLevel: 0 | 1 | 2;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onFooterClick: () => void;
|
onFooterClick: () => void;
|
||||||
|
setIsFooterHidden?: (isHidden: boolean) => void;
|
||||||
isFooterHidden?: boolean;
|
isFooterHidden?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,6 +82,7 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
|
|||||||
onFooterClick,
|
onFooterClick,
|
||||||
isFooterHidden,
|
isFooterHidden,
|
||||||
isProtected,
|
isProtected,
|
||||||
|
setIsFooterHidden,
|
||||||
} = props;
|
} = props;
|
||||||
/* Content */
|
/* Content */
|
||||||
const photo = message ? getMessagePhoto(message) : undefined;
|
const photo = message ? getMessagePhoto(message) : undefined;
|
||||||
@ -139,6 +141,10 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
|
|||||||
isGhostAnimation && ANIMATION_DURATION,
|
isGhostAnimation && ANIMATION_DURATION,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const toggleControls = useCallback((isVisible) => {
|
||||||
|
setIsFooterHidden?.(!isVisible);
|
||||||
|
}, [setIsFooterHidden]);
|
||||||
|
|
||||||
const localBlobUrl = (photo || video) ? (photo || video)!.blobUrl : undefined;
|
const localBlobUrl = (photo || video) ? (photo || video)!.blobUrl : undefined;
|
||||||
let bestImageData = (!isVideo && (localBlobUrl || fullMediaBlobUrl)) || previewBlobUrl || pictogramBlobUrl;
|
let bestImageData = (!isVideo && (localBlobUrl || fullMediaBlobUrl)) || previewBlobUrl || pictogramBlobUrl;
|
||||||
const thumbDataUri = useBlurSync(!bestImageData && message && getMessageMediaThumbDataUri(message));
|
const thumbDataUri = useBlurSync(!bestImageData && message && getMessageMediaThumbDataUri(message));
|
||||||
@ -189,7 +195,7 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
|
|||||||
)}
|
)}
|
||||||
{isVideo && ((!isActive && IS_TOUCH_ENV) ? renderVideoPreview(
|
{isVideo && ((!isActive && IS_TOUCH_ENV) ? renderVideoPreview(
|
||||||
bestImageData,
|
bestImageData,
|
||||||
message && calculateMediaViewerDimensions(dimensions!, hasFooter, false),
|
message && calculateMediaViewerDimensions(dimensions!, hasFooter, true),
|
||||||
!IS_SINGLE_COLUMN_LAYOUT && !isProtected,
|
!IS_SINGLE_COLUMN_LAYOUT && !isProtected,
|
||||||
) : (
|
) : (
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
@ -197,10 +203,12 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
|
|||||||
url={localBlobUrl || fullMediaBlobUrl}
|
url={localBlobUrl || fullMediaBlobUrl}
|
||||||
isGif={isGif}
|
isGif={isGif}
|
||||||
posterData={bestImageData}
|
posterData={bestImageData}
|
||||||
posterSize={message && calculateMediaViewerDimensions(dimensions!, hasFooter, false)}
|
posterSize={message && calculateMediaViewerDimensions(dimensions!, hasFooter, true)}
|
||||||
loadProgress={loadProgress}
|
loadProgress={loadProgress}
|
||||||
fileSize={videoSize!}
|
fileSize={videoSize!}
|
||||||
isMediaViewerOpen={isOpen && isActive}
|
isMediaViewerOpen={isOpen && isActive}
|
||||||
|
areControlsVisible={!isFooterHidden}
|
||||||
|
toggleControls={toggleControls}
|
||||||
noPlay={!isActive}
|
noPlay={!isActive}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
/>
|
/>
|
||||||
@ -209,7 +217,7 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
|
|||||||
<MediaViewerFooter
|
<MediaViewerFooter
|
||||||
text={textParts}
|
text={textParts}
|
||||||
onClick={onFooterClick}
|
onClick={onFooterClick}
|
||||||
isHidden={isFooterHidden}
|
isHidden={isFooterHidden && IS_TOUCH_ENV}
|
||||||
isForVideo={isVideo && !isGif}
|
isForVideo={isVideo && !isGif}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -49,6 +49,7 @@ const MAX_ZOOM = 4;
|
|||||||
const MIN_ZOOM = 0.6;
|
const MIN_ZOOM = 0.6;
|
||||||
const DOUBLE_TAP_ZOOM = 3;
|
const DOUBLE_TAP_ZOOM = 3;
|
||||||
const CLICK_X_THRESHOLD = 40;
|
const CLICK_X_THRESHOLD = 40;
|
||||||
|
const CLICK_Y_THRESHOLD = 80;
|
||||||
let cancelAnimation: Function | undefined;
|
let cancelAnimation: Function | undefined;
|
||||||
|
|
||||||
type Transform = {
|
type Transform = {
|
||||||
@ -105,11 +106,12 @@ const MediaViewerSlides: FC<OwnProps> = ({
|
|||||||
const debounceActive = useDebounce(DEBOUNCE_ACTIVE, true);
|
const debounceActive = useDebounce(DEBOUNCE_ACTIVE, true);
|
||||||
|
|
||||||
const handleToggleFooterVisibility = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
const handleToggleFooterVisibility = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||||
if (!IS_TOUCH_ENV || !hasFooter || (!isPhoto && !isGif)) return;
|
if (!IS_TOUCH_ENV) return;
|
||||||
if (e.pageX < CLICK_X_THRESHOLD) return;
|
const isFooter = window.innerHeight - e.pageY < CLICK_Y_THRESHOLD;
|
||||||
if (e.pageX > window.innerWidth - CLICK_X_THRESHOLD) return;
|
if (!isFooter && e.pageX < CLICK_X_THRESHOLD) return;
|
||||||
|
if (!isFooter && e.pageX > window.innerWidth - CLICK_X_THRESHOLD) return;
|
||||||
setIsFooterHidden(!isFooterHidden);
|
setIsFooterHidden(!isFooterHidden);
|
||||||
}, [hasFooter, isFooterHidden, isGif, isPhoto]);
|
}, [isFooterHidden]);
|
||||||
|
|
||||||
useTimeout(() => setIsFooterHidden(false), ANIMATION_DURATION - 150);
|
useTimeout(() => setIsFooterHidden(false), ANIMATION_DURATION - 150);
|
||||||
|
|
||||||
@ -140,6 +142,9 @@ const MediaViewerSlides: FC<OwnProps> = ({
|
|||||||
const changeSlide = (e: MouseEvent) => {
|
const changeSlide = (e: MouseEvent) => {
|
||||||
if (transformRef.current.scale !== 1) return false;
|
if (transformRef.current.scale !== 1) return false;
|
||||||
let direction = 0;
|
let direction = 0;
|
||||||
|
if (window.innerHeight - e.pageY < CLICK_Y_THRESHOLD) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (e.pageX < CLICK_X_THRESHOLD) {
|
if (e.pageX < CLICK_X_THRESHOLD) {
|
||||||
direction = -1;
|
direction = -1;
|
||||||
} else if (e.pageX > window.innerWidth - CLICK_X_THRESHOLD) {
|
} else if (e.pageX > window.innerWidth - CLICK_X_THRESHOLD) {
|
||||||
@ -508,8 +513,10 @@ const MediaViewerSlides: FC<OwnProps> = ({
|
|||||||
<div className="MediaViewerSlides" ref={containerRef}>
|
<div className="MediaViewerSlides" ref={containerRef}>
|
||||||
{previousMessageId && scale === 1 && (
|
{previousMessageId && scale === 1 && (
|
||||||
<div className="MediaViewerSlide" style={getAnimationStyle(-window.innerWidth + offsetX - SLIDES_GAP)}>
|
<div className="MediaViewerSlide" style={getAnimationStyle(-window.innerWidth + offsetX - SLIDES_GAP)}>
|
||||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
<MediaViewerContent
|
||||||
<MediaViewerContent {...rest} messageId={previousMessageId} isFooterHidden={isFooterHidden} />
|
/* eslint-disable-next-line react/jsx-props-no-spreading */
|
||||||
|
{...rest}
|
||||||
|
messageId={previousMessageId} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{activeMessageId && (
|
{activeMessageId && (
|
||||||
@ -524,14 +531,17 @@ const MediaViewerSlides: FC<OwnProps> = ({
|
|||||||
{...rest}
|
{...rest}
|
||||||
messageId={activeMessageId}
|
messageId={activeMessageId}
|
||||||
isActive={isActive && isActiveRef.current}
|
isActive={isActive && isActiveRef.current}
|
||||||
|
setIsFooterHidden={setIsFooterHidden}
|
||||||
isFooterHidden={isFooterHidden || isZoomed || scale !== 1}
|
isFooterHidden={isFooterHidden || isZoomed || scale !== 1}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{nextMessageId && scale === 1 && (
|
{nextMessageId && scale === 1 && (
|
||||||
<div className="MediaViewerSlide" style={getAnimationStyle(window.innerWidth + offsetX + SLIDES_GAP)}>
|
<div className="MediaViewerSlide" style={getAnimationStyle(window.innerWidth + offsetX + SLIDES_GAP)}>
|
||||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
<MediaViewerContent
|
||||||
<MediaViewerContent {...rest} messageId={nextMessageId} isFooterHidden={isFooterHidden} />
|
/* eslint-disable-next-line react/jsx-props-no-spreading */
|
||||||
|
{...rest}
|
||||||
|
messageId={nextMessageId}/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -49,6 +49,11 @@
|
|||||||
@media (max-height: 640px) {
|
@media (max-height: 640px) {
|
||||||
max-height: calc(100vh - 10rem);
|
max-height: calc(100vh - 10rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable fullscreen on double tap on mobile devices
|
||||||
|
.is-touch-env & {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.play-button {
|
.play-button {
|
||||||
|
|||||||
@ -1,22 +1,19 @@
|
|||||||
import React, {
|
|
||||||
FC, memo, useCallback, useEffect, useRef, useState,
|
|
||||||
} from '../../lib/teact/teact';
|
|
||||||
|
|
||||||
import { ApiDimensions } from '../../api/types';
|
import { ApiDimensions } from '../../api/types';
|
||||||
|
|
||||||
import { IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../util/environment';
|
|
||||||
import useShowTransition from '../../hooks/useShowTransition';
|
|
||||||
import useBuffering from '../../hooks/useBuffering';
|
import useBuffering from '../../hooks/useBuffering';
|
||||||
import useFullscreenStatus from '../../hooks/useFullscreen';
|
import useFullscreenStatus from '../../hooks/useFullscreen';
|
||||||
|
import useShowTransition from '../../hooks/useShowTransition';
|
||||||
import useVideoCleanup from '../../hooks/useVideoCleanup';
|
import useVideoCleanup from '../../hooks/useVideoCleanup';
|
||||||
import safePlay from '../../util/safePlay';
|
import React, { FC, memo, useCallback, useEffect, useRef, useState } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import VideoPlayerControls from './VideoPlayerControls';
|
import { IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../util/environment';
|
||||||
import ProgressSpinner from '../ui/ProgressSpinner';
|
import safePlay from '../../util/safePlay';
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
|
import ProgressSpinner from '../ui/ProgressSpinner';
|
||||||
|
|
||||||
import './VideoPlayer.scss';
|
import './VideoPlayer.scss';
|
||||||
|
|
||||||
|
import VideoPlayerControls from './VideoPlayerControls';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
url?: string;
|
url?: string;
|
||||||
isGif?: boolean;
|
isGif?: boolean;
|
||||||
@ -26,6 +23,8 @@ type OwnProps = {
|
|||||||
fileSize: number;
|
fileSize: number;
|
||||||
isMediaViewerOpen?: boolean;
|
isMediaViewerOpen?: boolean;
|
||||||
noPlay?: boolean;
|
noPlay?: boolean;
|
||||||
|
areControlsVisible: boolean;
|
||||||
|
toggleControls: (isVisible: boolean) => void;
|
||||||
onClose: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void;
|
onClose: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,12 +40,13 @@ const VideoPlayer: FC<OwnProps> = ({
|
|||||||
isMediaViewerOpen,
|
isMediaViewerOpen,
|
||||||
noPlay,
|
noPlay,
|
||||||
onClose,
|
onClose,
|
||||||
|
toggleControls,
|
||||||
|
areControlsVisible,
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const videoRef = useRef<HTMLVideoElement>(null);
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
const [isPlayed, setIsPlayed] = useState(!IS_TOUCH_ENV || !IS_IOS);
|
const [isPlayed, setIsPlayed] = useState(!IS_TOUCH_ENV || !IS_IOS);
|
||||||
const [currentTime, setCurrentTime] = useState(0);
|
const [currentTime, setCurrentTime] = useState(0);
|
||||||
const [isControlsVisible, setIsControlsVisible] = useState(true);
|
|
||||||
|
|
||||||
const [isFullscreen, setFullscreen, exitFullscreen] = useFullscreenStatus(videoRef, setIsPlayed);
|
const [isFullscreen, setFullscreen, exitFullscreen] = useFullscreenStatus(videoRef, setIsPlayed);
|
||||||
|
|
||||||
@ -88,21 +88,20 @@ const VideoPlayer: FC<OwnProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
safePlay(videoRef.current!);
|
safePlay(videoRef.current!);
|
||||||
setIsPlayed(true);
|
setIsPlayed(true);
|
||||||
if (IS_SINGLE_COLUMN_LAYOUT) {
|
|
||||||
setIsControlsVisible(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [isPlayed]);
|
}, [isPlayed]);
|
||||||
|
|
||||||
useVideoCleanup(videoRef, []);
|
useVideoCleanup(videoRef, []);
|
||||||
|
|
||||||
const handleMouseOver = useCallback(() => {
|
const handleMouseMove = useCallback(() => {
|
||||||
setIsControlsVisible(true);
|
toggleControls(true);
|
||||||
}, []);
|
}, [toggleControls]);
|
||||||
|
|
||||||
const handleMouseOut = useCallback(() => {
|
const handleMouseOut = useCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||||
setIsControlsVisible(false);
|
if (e.target === videoRef.current) {
|
||||||
}, []);
|
toggleControls(false);
|
||||||
|
}
|
||||||
|
}, [toggleControls]);
|
||||||
|
|
||||||
const handleTimeUpdate = useCallback((e: React.SyntheticEvent<HTMLVideoElement>) => {
|
const handleTimeUpdate = useCallback((e: React.SyntheticEvent<HTMLVideoElement>) => {
|
||||||
setCurrentTime(e.currentTarget.currentTime);
|
setCurrentTime(e.currentTarget.currentTime);
|
||||||
@ -111,8 +110,8 @@ const VideoPlayer: FC<OwnProps> = ({
|
|||||||
const handleEnded = useCallback(() => {
|
const handleEnded = useCallback(() => {
|
||||||
setCurrentTime(0);
|
setCurrentTime(0);
|
||||||
setIsPlayed(false);
|
setIsPlayed(false);
|
||||||
setIsControlsVisible(true);
|
toggleControls(true);
|
||||||
}, []);
|
}, [toggleControls]);
|
||||||
|
|
||||||
const handleFullscreenChange = useCallback(() => {
|
const handleFullscreenChange = useCallback(() => {
|
||||||
if (isFullscreen && exitFullscreen) {
|
if (isFullscreen && exitFullscreen) {
|
||||||
@ -126,11 +125,6 @@ const VideoPlayer: FC<OwnProps> = ({
|
|||||||
videoRef.current!.currentTime = position;
|
videoRef.current!.currentTime = position;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggleControls = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsControlsVisible(!isControlsVisible);
|
|
||||||
}, [isControlsVisible]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const togglePayingStateBySpace = (e: KeyboardEvent) => {
|
const togglePayingStateBySpace = (e: KeyboardEvent) => {
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
@ -152,8 +146,7 @@ const VideoPlayer: FC<OwnProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="VideoPlayer"
|
className="VideoPlayer"
|
||||||
onClick={!isGif && IS_SINGLE_COLUMN_LAYOUT ? toggleControls : undefined}
|
onMouseMove={!isGif && !IS_TOUCH_ENV ? handleMouseMove : undefined}
|
||||||
onMouseOver={!isGif && !IS_TOUCH_ENV ? handleMouseOver : undefined}
|
|
||||||
onMouseOut={!isGif && !IS_TOUCH_ENV ? handleMouseOut : undefined}
|
onMouseOut={!isGif && !IS_TOUCH_ENV ? handleMouseOut : undefined}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -172,7 +165,7 @@ const VideoPlayer: FC<OwnProps> = ({
|
|||||||
onPlay={IS_IOS ? () => setIsPlayed(true) : undefined}
|
onPlay={IS_IOS ? () => setIsPlayed(true) : undefined}
|
||||||
onEnded={handleEnded}
|
onEnded={handleEnded}
|
||||||
onClick={!IS_SINGLE_COLUMN_LAYOUT ? togglePlayState : undefined}
|
onClick={!IS_SINGLE_COLUMN_LAYOUT ? togglePlayState : undefined}
|
||||||
onDoubleClick={handleFullscreenChange}
|
onDoubleClick={!IS_TOUCH_ENV ? handleFullscreenChange : undefined}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...bufferingHandlers}
|
{...bufferingHandlers}
|
||||||
onTimeUpdate={handleTimeUpdate}
|
onTimeUpdate={handleTimeUpdate}
|
||||||
@ -205,7 +198,8 @@ const VideoPlayer: FC<OwnProps> = ({
|
|||||||
isFullscreen={isFullscreen}
|
isFullscreen={isFullscreen}
|
||||||
fileSize={fileSize}
|
fileSize={fileSize}
|
||||||
duration={videoRef.current ? videoRef.current.duration || 0 : 0}
|
duration={videoRef.current ? videoRef.current.duration || 0 : 0}
|
||||||
isForceVisible={isControlsVisible}
|
isVisible={areControlsVisible}
|
||||||
|
setVisibility={toggleControls}
|
||||||
isForceMobileVersion={posterSize && posterSize.width < MOBILE_VERSION_CONTROL_WIDTH}
|
isForceMobileVersion={posterSize && posterSize.width < MOBILE_VERSION_CONTROL_WIDTH}
|
||||||
onSeek={handleSeek}
|
onSeek={handleSeek}
|
||||||
onChangeFullscreen={handleFullscreenChange}
|
onChangeFullscreen={handleFullscreenChange}
|
||||||
|
|||||||
@ -5,10 +5,10 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-top: 0.625rem;
|
padding: 1rem 0.5rem 0.5rem;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
background: linear-gradient(to top, #000 0%, rgba(0, 0, 0, 0) 100%);
|
background: linear-gradient(to top, #000 0%, rgba(0, 0, 0, 0) 100%);
|
||||||
transition: opacity 0.15s;
|
transition: opacity 0.3s;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, useState, useEffect, useRef, useCallback,
|
FC, useEffect, useRef, useCallback,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
@ -18,13 +18,14 @@ type IProps = {
|
|||||||
currentTime: number;
|
currentTime: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
fileSize: number;
|
fileSize: number;
|
||||||
isForceVisible: boolean;
|
|
||||||
isForceMobileVersion?: boolean;
|
isForceMobileVersion?: boolean;
|
||||||
isPlayed: boolean;
|
isPlayed: boolean;
|
||||||
isFullscreenSupported: boolean;
|
isFullscreenSupported: boolean;
|
||||||
isFullscreen: boolean;
|
isFullscreen: boolean;
|
||||||
onChangeFullscreen: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
onChangeFullscreen: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||||
onPlayPause: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
onPlayPause: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||||
|
isVisible: boolean;
|
||||||
|
setVisibility: (isVisible: boolean) => void;
|
||||||
onSeek: (position: number) => void;
|
onSeek: (position: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -32,68 +33,61 @@ const stopEvent = (e: React.MouseEvent<HTMLElement>) => {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
const HIDE_CONTROLS_TIMEOUT_MS = 800;
|
const HIDE_CONTROLS_TIMEOUT_MS = 1500;
|
||||||
|
|
||||||
const VideoPlayerControls: FC<IProps> = ({
|
const VideoPlayerControls: FC<IProps> = ({
|
||||||
bufferedProgress,
|
bufferedProgress,
|
||||||
currentTime,
|
currentTime,
|
||||||
duration,
|
duration,
|
||||||
fileSize,
|
fileSize,
|
||||||
isForceVisible,
|
|
||||||
isForceMobileVersion,
|
isForceMobileVersion,
|
||||||
isPlayed,
|
isPlayed,
|
||||||
isFullscreenSupported,
|
isFullscreenSupported,
|
||||||
isFullscreen,
|
isFullscreen,
|
||||||
onChangeFullscreen,
|
onChangeFullscreen,
|
||||||
onPlayPause,
|
onPlayPause,
|
||||||
|
isVisible,
|
||||||
|
setVisibility,
|
||||||
onSeek,
|
onSeek,
|
||||||
}) => {
|
}) => {
|
||||||
const [isVisible, setVisibility] = useState(true);
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const seekerRef = useRef<HTMLDivElement>(null);
|
const seekerRef = useRef<HTMLDivElement>(null);
|
||||||
const isSeeking = useRef<boolean>(false);
|
const isSeeking = useRef<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isForceVisible) {
|
|
||||||
setVisibility(isForceVisible);
|
|
||||||
}
|
|
||||||
}, [isForceVisible]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let timeout: number | undefined;
|
let timeout: number | undefined;
|
||||||
|
if (!isVisible || !isPlayed) {
|
||||||
if (!isForceVisible) {
|
if (timeout) window.clearTimeout(timeout);
|
||||||
if (IS_SINGLE_COLUMN_LAYOUT) {
|
return;
|
||||||
setVisibility(false);
|
|
||||||
} else {
|
|
||||||
timeout = window.setTimeout(() => {
|
|
||||||
setVisibility(false);
|
|
||||||
}, HIDE_CONTROLS_TIMEOUT_MS);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
timeout = window.setTimeout(() => {
|
||||||
|
setVisibility(false);
|
||||||
|
}, HIDE_CONTROLS_TIMEOUT_MS);
|
||||||
return () => {
|
return () => {
|
||||||
if (timeout) {
|
if (timeout) window.clearTimeout(timeout);
|
||||||
window.clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, [isForceVisible]);
|
}, [isPlayed, isVisible, setVisibility]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isVisible || isForceVisible) {
|
if (isVisible) {
|
||||||
document.body.classList.add('video-controls-visible');
|
document.body.classList.add('video-controls-visible');
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('video-controls-visible');
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.body.classList.remove('video-controls-visible');
|
document.body.classList.remove('video-controls-visible');
|
||||||
};
|
};
|
||||||
}, [isForceVisible, isVisible]);
|
}, [isVisible]);
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const handleSeek = useCallback((e: MouseEvent | TouchEvent) => {
|
const handleSeek = useCallback((e: MouseEvent | TouchEvent) => {
|
||||||
if (isSeeking.current && seekerRef.current) {
|
if (isSeeking.current && seekerRef.current) {
|
||||||
const { width, left } = seekerRef.current.getBoundingClientRect();
|
const {
|
||||||
|
width,
|
||||||
|
left,
|
||||||
|
} = seekerRef.current.getBoundingClientRect();
|
||||||
const clientX = e instanceof MouseEvent ? e.clientX : e.targetTouches[0].clientX;
|
const clientX = e instanceof MouseEvent ? e.clientX : e.targetTouches[0].clientX;
|
||||||
onSeek(Math.max(Math.min(duration * ((clientX - left) / width), duration), 0));
|
onSeek(Math.max(Math.min(duration * ((clientX - left) / width), duration), 0));
|
||||||
}
|
}
|
||||||
@ -118,11 +112,10 @@ const VideoPlayerControls: FC<IProps> = ({
|
|||||||
});
|
});
|
||||||
}, [isVisible, handleStartSeek, handleSeek, handleStopSeek]);
|
}, [isVisible, handleStartSeek, handleSeek, handleStopSeek]);
|
||||||
|
|
||||||
const isActive = isVisible || isForceVisible;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={buildClassName('VideoPlayerControls', isForceMobileVersion && 'mobile', isActive && 'active')}
|
className={buildClassName('VideoPlayerControls', isForceMobileVersion && 'mobile', isVisible && 'active')}
|
||||||
onClick={stopEvent}
|
onClick={stopEvent}
|
||||||
>
|
>
|
||||||
{renderSeekLine(currentTime, duration, bufferedProgress, seekerRef)}
|
{renderSeekLine(currentTime, duration, bufferedProgress, seekerRef)}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user