Media Viewer: Fix video controls (#1997)

This commit is contained in:
Alexander Zinchuk 2022-08-17 10:38:18 +02:00
parent b7be72f5d9
commit c54e01b8e0
4 changed files with 37 additions and 33 deletions

View File

@ -33,8 +33,8 @@ type OwnProps = {
animationLevel: 0 | 1 | 2; animationLevel: 0 | 1 | 2;
onClose: () => void; onClose: () => void;
onFooterClick: () => void; onFooterClick: () => void;
setIsFooterHidden?: (isHidden: boolean) => void; setControlsVisible?: (isVisible: boolean) => void;
isFooterHidden?: boolean; areControlsVisible: boolean;
}; };
type StateProps = { type StateProps = {
@ -62,14 +62,14 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
message, message,
origin, origin,
animationLevel, animationLevel,
isFooterHidden, areControlsVisible,
isProtected, isProtected,
volume, volume,
playbackRate, playbackRate,
isMuted, isMuted,
onClose, onClose,
onFooterClick, onFooterClick,
setIsFooterHidden, setControlsVisible,
} = props; } = props;
const isGhostAnimation = animationLevel === 2; const isGhostAnimation = animationLevel === 2;
@ -94,16 +94,8 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
const isOpen = Boolean(avatarOwner || mediaId); const isOpen = Boolean(avatarOwner || mediaId);
const toggleControls = useCallback((isVisible) => { const toggleControls = useCallback((isVisible) => {
setIsFooterHidden?.(!isVisible); setControlsVisible?.(isVisible);
}, [setIsFooterHidden]); }, [setControlsVisible]);
const handleMouseMove = useCallback(() => {
toggleControls(true);
}, [toggleControls]);
const handleMouseOut = useCallback(() => {
toggleControls(false);
}, [toggleControls]);
if (avatarOwner) { if (avatarOwner) {
if (!isVideoAvatar) { if (!isVideoAvatar) {
@ -129,7 +121,7 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
loadProgress={loadProgress} loadProgress={loadProgress}
fileSize={videoSize!} fileSize={videoSize!}
isMediaViewerOpen={isOpen && isActive} isMediaViewerOpen={isOpen && isActive}
areControlsVisible={!isFooterHidden} areControlsVisible={areControlsVisible}
toggleControls={toggleControls} toggleControls={toggleControls}
isProtected={isProtected} isProtected={isProtected}
noPlay={!isActive} noPlay={!isActive}
@ -150,8 +142,6 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
return ( return (
<div <div
className={buildClassName('MediaViewerContent', hasFooter && 'has-footer')} className={buildClassName('MediaViewerContent', hasFooter && 'has-footer')}
onMouseMove={!isGif && !IS_TOUCH_ENV ? handleMouseMove : undefined}
onMouseOut={!isGif && !IS_TOUCH_ENV ? handleMouseOut : undefined}
> >
{isPhoto && renderPhoto( {isPhoto && renderPhoto(
localBlobUrl || fullMediaBlobUrl || previewBlobUrl || pictogramBlobUrl, localBlobUrl || fullMediaBlobUrl || previewBlobUrl || pictogramBlobUrl,
@ -173,8 +163,8 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
posterSize={message && calculateMediaViewerDimensions(dimensions!, hasFooter, true)} posterSize={message && calculateMediaViewerDimensions(dimensions!, hasFooter, true)}
loadProgress={loadProgress} loadProgress={loadProgress}
fileSize={videoSize!} fileSize={videoSize!}
areControlsVisible={areControlsVisible}
isMediaViewerOpen={isOpen && isActive} isMediaViewerOpen={isOpen && isActive}
areControlsVisible={!isFooterHidden}
toggleControls={toggleControls} toggleControls={toggleControls}
noPlay={!isActive} noPlay={!isActive}
onClose={onClose} onClose={onClose}
@ -189,7 +179,7 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
text={textParts} text={textParts}
onClick={onFooterClick} onClick={onFooterClick}
isProtected={isProtected} isProtected={isProtected}
isHidden={isFooterHidden} isHidden={IS_TOUCH_ENV ? !areControlsVisible : false}
isForVideo={isVideo && !isGif} isForVideo={isVideo && !isGif}
/> />
)} )}

View File

@ -97,7 +97,7 @@ const MediaViewerSlides: FC<OwnProps> = ({
const prevZoomLevelChange = usePrevious(zoomLevelChange); const prevZoomLevelChange = usePrevious(zoomLevelChange);
const hasZoomChanged = prevZoomLevelChange !== undefined && prevZoomLevelChange !== zoomLevelChange; const hasZoomChanged = prevZoomLevelChange !== undefined && prevZoomLevelChange !== zoomLevelChange;
const forceUpdate = useForceUpdate(); const forceUpdate = useForceUpdate();
const [isFooterHidden, setIsFooterHidden] = useState(true); const [areControlsVisible, setControlsVisible] = useState(false);
const [isMouseDown, setIsMouseDown] = useState(false); const [isMouseDown, setIsMouseDown] = useState(false);
const { height: windowHeight, width: windowWidth, isResizing } = useWindowSize(); const { height: windowHeight, width: windowWidth, isResizing } = useWindowSize();
const { onClose } = rest; const { onClose } = rest;
@ -121,15 +121,15 @@ const MediaViewerSlides: FC<OwnProps> = ({
const shouldCloseOnVideo = isGif && !IS_IOS; const shouldCloseOnVideo = isGif && !IS_IOS;
const clickXThreshold = IS_TOUCH_ENV ? 40 : windowWidth / 10; const clickXThreshold = IS_TOUCH_ENV ? 40 : windowWidth / 10;
const handleToggleFooterVisibility = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => { const handleControlsVisibility = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (!IS_TOUCH_ENV) return; if (!IS_TOUCH_ENV) return;
const isFooter = windowHeight - e.pageY < CLICK_Y_THRESHOLD; const isFooter = windowHeight - e.pageY < CLICK_Y_THRESHOLD;
if (!isFooter && e.pageX < clickXThreshold) return; if (!isFooter && e.pageX < clickXThreshold) return;
if (!isFooter && e.pageX > windowWidth - clickXThreshold) return; if (!isFooter && e.pageX > windowWidth - clickXThreshold) return;
setIsFooterHidden(!isFooterHidden); setControlsVisible(!areControlsVisible);
}, [clickXThreshold, isFooterHidden, windowHeight, windowWidth]); }, [clickXThreshold, areControlsVisible, windowHeight, windowWidth]);
useTimeout(() => setIsFooterHidden(false), ANIMATION_DURATION - 150); useTimeout(() => setControlsVisible(true), ANIMATION_DURATION + 100);
useEffect(() => { useEffect(() => {
if (!containerRef.current || activeMediaId === undefined) { if (!containerRef.current || activeMediaId === undefined) {
@ -665,7 +665,7 @@ const MediaViewerSlides: FC<OwnProps> = ({
/* eslint-disable-next-line react/jsx-props-no-spreading */ /* eslint-disable-next-line react/jsx-props-no-spreading */
{...rest} {...rest}
animationLevel={animationLevel} animationLevel={animationLevel}
isFooterHidden={isFooterHidden} areControlsVisible={areControlsVisible}
mediaId={prevMediaId} mediaId={prevMediaId}
/> />
</div> </div>
@ -676,7 +676,7 @@ const MediaViewerSlides: FC<OwnProps> = ({
'MediaViewerSlide--active', 'MediaViewerSlide--active',
isMouseDown && scale > 1 && 'MediaViewerSlide--moving', isMouseDown && scale > 1 && 'MediaViewerSlide--moving',
)} )}
onClick={handleToggleFooterVisibility} onClick={handleControlsVisibility}
ref={activeSlideRef} ref={activeSlideRef}
style={getAnimationStyle(offsetX, offsetY, scale)} style={getAnimationStyle(offsetX, offsetY, scale)}
> >
@ -686,8 +686,8 @@ const MediaViewerSlides: FC<OwnProps> = ({
mediaId={activeMediaId} mediaId={activeMediaId}
animationLevel={animationLevel} animationLevel={animationLevel}
isActive={isActiveRef.current} isActive={isActiveRef.current}
setIsFooterHidden={setIsFooterHidden} setControlsVisible={setControlsVisible}
isFooterHidden={isFooterHidden || scale !== 1} areControlsVisible={areControlsVisible && scale === 1}
/> />
</div> </div>
{hasNext && scale === 1 && !isResizing && ( {hasNext && scale === 1 && !isResizing && (
@ -696,7 +696,7 @@ const MediaViewerSlides: FC<OwnProps> = ({
/* eslint-disable-next-line react/jsx-props-no-spreading */ /* eslint-disable-next-line react/jsx-props-no-spreading */
{...rest} {...rest}
animationLevel={animationLevel} animationLevel={animationLevel}
isFooterHidden={isFooterHidden} areControlsVisible={areControlsVisible}
mediaId={nextMediaId} mediaId={nextMediaId}
/> />
</div> </div>

View File

@ -29,11 +29,11 @@ type OwnProps = {
fileSize: number; fileSize: number;
isMediaViewerOpen?: boolean; isMediaViewerOpen?: boolean;
noPlay?: boolean; noPlay?: boolean;
areControlsVisible: boolean;
volume: number; volume: number;
isMuted: boolean; isMuted: boolean;
playbackRate: number; playbackRate: number;
isProtected?: boolean; isProtected?: boolean;
areControlsVisible: boolean;
toggleControls: (isVisible: boolean) => void; toggleControls: (isVisible: boolean) => void;
onClose: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void; onClose: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void;
}; };
@ -66,9 +66,20 @@ const VideoPlayer: FC<OwnProps> = ({
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 [isFullscreen, setFullscreen, exitFullscreen] = useFullscreenStatus(videoRef, setIsPlayed); const [isFullscreen, setFullscreen, exitFullscreen] = useFullscreenStatus(videoRef, setIsPlayed);
const handleVideoMove = useCallback(() => {
toggleControls(true);
}, [toggleControls]);
const handleVideoLeave = useCallback((e) => {
const bounds = videoRef.current?.getBoundingClientRect();
if (!bounds) return;
if (e.clientX < bounds.left || e.clientX > bounds.right || e.clientY < bounds.top || e.clientY > bounds.bottom) {
toggleControls(false);
}
}, [toggleControls]);
const { const {
isBuffered, bufferedRanges, bufferingHandlers, bufferedProgress, isBuffered, bufferedRanges, bufferingHandlers, bufferedProgress,
} = useBuffering(); } = useBuffering();
@ -178,6 +189,8 @@ const VideoPlayer: FC<OwnProps> = ({
return ( return (
<div <div
className="VideoPlayer" className="VideoPlayer"
onMouseMove={!IS_TOUCH_ENV ? handleVideoMove : undefined}
onMouseOut={!IS_TOUCH_ENV ? handleVideoLeave : undefined}
> >
<div <div
style={wrapperStyle} style={wrapperStyle}

View File

@ -5,7 +5,7 @@ import React, {
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import useFlag from '../../hooks/useFlag'; import useFlag from '../../hooks/useFlag';
import { IS_IOS, IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment'; import { IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../util/environment';
import { formatMediaDuration } from '../../util/dateFormat'; import { formatMediaDuration } from '../../util/dateFormat';
import { formatFileSize } from '../../util/textFormat'; import { formatFileSize } from '../../util/textFormat';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
@ -54,7 +54,7 @@ const PLAYBACK_RATES = [
2, 2,
]; ];
const HIDE_CONTROLS_TIMEOUT_MS = 1500; const HIDE_CONTROLS_TIMEOUT_MS = 3000;
const VideoPlayerControls: FC<OwnProps> = ({ const VideoPlayerControls: FC<OwnProps> = ({
bufferedRanges, bufferedRanges,
@ -86,6 +86,7 @@ const VideoPlayerControls: FC<OwnProps> = ({
const isSeeking = isSeekingRef.current; const isSeeking = isSeekingRef.current;
useEffect(() => { useEffect(() => {
if (!IS_TOUCH_ENV) return undefined;
let timeout: number | undefined; let timeout: number | undefined;
if (!isVisible || !isPlayed || isSeeking || isPlaybackMenuOpen) { if (!isVisible || !isPlayed || isSeeking || isPlaybackMenuOpen) {
if (timeout) window.clearTimeout(timeout); if (timeout) window.clearTimeout(timeout);