Media Viewer: Various fixes (#2075)

This commit is contained in:
Alexander Zinchuk 2022-10-29 15:18:31 +02:00
parent eb69f55f29
commit 5de819826e
7 changed files with 91 additions and 18 deletions

View File

@ -130,6 +130,7 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
noPlay={!isActive}
onClose={onClose}
isMuted
shouldCloseOnClick
volume={0}
playbackRate={1}
/>

View File

@ -20,6 +20,7 @@ import usePrevious from '../../hooks/usePrevious';
import useTimeout from '../../hooks/useTimeout';
import useWindowSize from '../../hooks/useWindowSize';
import useHistoryBack from '../../hooks/useHistoryBack';
import { useFullscreenStatus } from '../../hooks/useFullscreen';
import MediaViewerContent from './MediaViewerContent';
@ -101,6 +102,7 @@ const MediaViewerSlides: FC<OwnProps> = ({
const hasZoomChanged = prevZoomLevelChange !== undefined && prevZoomLevelChange !== zoomLevelChange;
const forceUpdate = useForceUpdate();
const [areControlsVisible, setControlsVisible] = useState(false);
const isFullscreen = useFullscreenStatus();
const [isMouseDown, setIsMouseDown] = useState(false);
const { height: windowHeight, width: windowWidth, isResizing } = useWindowSize();
const { onClose } = rest;
@ -145,7 +147,7 @@ const MediaViewerSlides: FC<OwnProps> = ({
}, [mediaId]);
useEffect(() => {
if (!containerRef.current || activeMediaId === undefined || isHidden) {
if (!containerRef.current || activeMediaId === undefined || isHidden || isFullscreen) {
return undefined;
}
let lastTransform = lastTransformRef.current;
@ -631,10 +633,11 @@ const MediaViewerSlides: FC<OwnProps> = ({
animationLevel,
setIsMouseDown,
isHidden,
isFullscreen,
]);
useEffect(() => {
if (!containerRef.current || !hasZoomChanged || isHidden) return;
if (!containerRef.current || !hasZoomChanged || isHidden || isFullscreen) return;
const { scale } = transformRef.current;
const dir = zoomLevelChange > 0 ? -1 : +1;
const minZoom = MIN_ZOOM * 0.6;
@ -662,7 +665,7 @@ const MediaViewerSlides: FC<OwnProps> = ({
containerRef.current.dispatchEvent(wheelEvent);
},
});
}, [zoomLevelChange, hasZoomChanged, isHidden]);
}, [zoomLevelChange, hasZoomChanged, isHidden, isFullscreen]);
if (activeMediaId === undefined) return undefined;

View File

@ -7,11 +7,13 @@ import { getActions } from '../../global';
import type { ApiDimensions } from '../../api/types';
import useBuffering from '../../hooks/useBuffering';
import useFullscreenStatus from '../../hooks/useFullscreen';
import useFullscreen from '../../hooks/useFullscreen';
import usePictureInPicture from '../../hooks/usePictureInPicture';
import useShowTransition from '../../hooks/useShowTransition';
import useVideoCleanup from '../../hooks/useVideoCleanup';
import { IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../util/environment';
import {
IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV, IS_YA_BROWSER,
} from '../../util/environment';
import safePlay from '../../util/safePlay';
import stopEvent from '../../util/stopEvent';
@ -36,6 +38,7 @@ type OwnProps = {
playbackRate: number;
isProtected?: boolean;
areControlsVisible: boolean;
shouldCloseOnClick?: boolean;
toggleControls: (isVisible: boolean) => void;
onClose: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void;
};
@ -58,6 +61,7 @@ const VideoPlayer: FC<OwnProps> = ({
onClose,
toggleControls,
areControlsVisible,
shouldCloseOnClick,
isProtected,
}) => {
const {
@ -70,10 +74,18 @@ const VideoPlayer: FC<OwnProps> = ({
const videoRef = useRef<HTMLVideoElement>(null);
const [isPlaying, setIsPlaying] = useState(!IS_TOUCH_ENV || !IS_IOS);
const [currentTime, setCurrentTime] = useState(0);
const [isFullscreen, setFullscreen, exitFullscreen] = useFullscreenStatus(videoRef, setIsPlaying);
const [isFullscreen, setFullscreen, exitFullscreen] = useFullscreen(videoRef, setIsPlaying);
const handleEnterFullscreen = useCallback(() => setMediaViewerHidden(true), [setMediaViewerHidden]);
const handleLeaveFullscreen = useCallback(() => setMediaViewerHidden(false), [setMediaViewerHidden]);
const handleEnterFullscreen = useCallback(() => {
// Yandex browser doesn't support PIP when video is hidden
if (IS_YA_BROWSER) return;
setMediaViewerHidden(true);
}, [setMediaViewerHidden]);
const handleLeaveFullscreen = useCallback(() => {
if (IS_YA_BROWSER) return;
setMediaViewerHidden(false);
}, [setMediaViewerHidden]);
const [
isPictureInPictureSupported,
@ -143,6 +155,14 @@ const VideoPlayer: FC<OwnProps> = ({
}
}, [isPlaying]);
const handleClick = useCallback((e: React.MouseEvent<HTMLVideoElement, MouseEvent>) => {
if (shouldCloseOnClick) {
onClose(e);
} else {
togglePlayState(e);
}
}, [onClose, shouldCloseOnClick, togglePlayState]);
useVideoCleanup(videoRef, []);
const handleTimeUpdate = useCallback((e: React.SyntheticEvent<HTMLVideoElement>) => {
@ -231,7 +251,7 @@ const VideoPlayer: FC<OwnProps> = ({
style={videoStyle}
onPlay={() => setIsPlaying(true)}
onEnded={handleEnded}
onClick={!IS_SINGLE_COLUMN_LAYOUT ? togglePlayState : undefined}
onClick={!IS_SINGLE_COLUMN_LAYOUT ? handleClick : undefined}
onDoubleClick={!IS_TOUCH_ENV ? handleFullscreenChange : undefined}
// eslint-disable-next-line react/jsx-props-no-spreading
{...bufferingHandlers}

View File

@ -82,6 +82,7 @@
}
.player-time {
color: rgba(255, 255, 255, 0.5);
white-space: nowrap;
}
@ -149,12 +150,23 @@
}
.playback-rate-menu {
color: var(--color-text);
.bubble {
min-width: 3.5rem;
margin-right: 3.5rem;
bottom: 4.1875rem
margin-right: 5.8125rem;
bottom: 4.1875rem;
@media (max-width: 600px) {
bottom: 4.6875rem;
}
}
&.no-fullscreen {
&.no-fullscreen, &.no-pip {
.bubble {
margin-right: 3.3125rem;
}
}
&.no-fullscreen.no-pip {
.bubble {
margin-right: 0.8125rem;
}

View File

@ -235,7 +235,11 @@ const VideoPlayerControls: FC<OwnProps> = ({
</div>
<Menu
isOpen={isPlaybackMenuOpen}
className={buildClassName('playback-rate-menu', !isFullscreenSupported && 'no-fullscreen')}
className={buildClassName(
'playback-rate-menu',
!isFullscreenSupported && 'no-fullscreen',
!isPictureInPictureSupported && 'no-pip',
)}
positionX="right"
positionY="bottom"
autoClose

View File

@ -1,4 +1,4 @@
import { useLayoutEffect, useState } from '../lib/teact/teact';
import { useLayoutEffect, useState, useEffect } from '../lib/teact/teact';
import { IS_IOS } from '../util/environment';
type RefType = {
@ -10,7 +10,7 @@ type CallbackType = (isPlayed: boolean) => void;
const prop = getBrowserFullscreenElementProp();
export default function useFullscreenStatus(elRef: RefType, setIsPlayed: CallbackType): ReturnType {
export default function useFullscreen(elRef: RefType, setIsPlayed: CallbackType): ReturnType {
const [isFullscreen, setIsFullscreen] = useState(Boolean(prop && document[prop]));
const setFullscreen = () => {
@ -30,13 +30,18 @@ export default function useFullscreenStatus(elRef: RefType, setIsPlayed: Callbac
};
useLayoutEffect(() => {
const listener = () => { setIsFullscreen(Boolean(prop && document[prop])); };
const video = elRef.current;
const listener = () => {
const isEnabled = Boolean(prop && document[prop]);
setIsFullscreen(isEnabled);
// In Firefox fullscreen video controls are not visible by default, so we force them manually
video!.controls = isEnabled;
};
const listenerEnter = () => { setIsFullscreen(true); };
const listenerExit = () => {
setIsFullscreen(false);
setIsPlayed(false);
};
const video = elRef.current;
document.addEventListener('fullscreenchange', listener, false);
document.addEventListener('webkitfullscreenchange', listener, false);
@ -66,6 +71,28 @@ export default function useFullscreenStatus(elRef: RefType, setIsPlayed: Callbac
return [isFullscreen, setFullscreen, exitFullscreen];
}
export const useFullscreenStatus = () => {
const [isFullscreen, setIsFullscreen] = useState(false);
useEffect(() => {
const listener = () => {
setIsFullscreen(checkIfFullscreen());
};
document.addEventListener('fullscreenchange', listener, false);
document.addEventListener('webkitfullscreenchange', listener, false);
document.addEventListener('mozfullscreenchange', listener, false);
return () => {
document.removeEventListener('fullscreenchange', listener, false);
document.removeEventListener('webkitfullscreenchange', listener, false);
document.removeEventListener('mozfullscreenchange', listener, false);
};
}, []);
return isFullscreen;
};
function getBrowserFullscreenElementProp() {
if (typeof document.fullscreenElement !== 'undefined') {
return 'fullscreenElement';
@ -74,10 +101,14 @@ function getBrowserFullscreenElementProp() {
} else if (typeof document.webkitFullscreenElement !== 'undefined') {
return 'webkitFullscreenElement';
}
return '';
}
export function checkIfFullscreen() {
const fullscreenProp = getBrowserFullscreenElementProp();
return Boolean(fullscreenProp && document[fullscreenProp]);
}
export function safeRequestFullscreen(video: HTMLVideoElement) {
if (video.requestFullscreen) {
video.requestFullscreen();

View File

@ -44,6 +44,8 @@ export const IS_MAC_OS = PLATFORM_ENV === 'macOS';
export const IS_IOS = PLATFORM_ENV === 'iOS';
export const IS_ANDROID = PLATFORM_ENV === 'Android';
export const IS_SAFARI = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
export const IS_YA_BROWSER = navigator.userAgent.includes('YaBrowser');
export const IS_PWA = (
window.matchMedia('(display-mode: standalone)').matches
|| (window.navigator as any).standalone