From 5de819826e53ef6fb4743d59644e1ed33bf23f10 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Sat, 29 Oct 2022 15:18:31 +0200 Subject: [PATCH] Media Viewer: Various fixes (#2075) --- .../mediaViewer/MediaViewerContent.tsx | 1 + .../mediaViewer/MediaViewerSlides.tsx | 9 ++-- src/components/mediaViewer/VideoPlayer.tsx | 32 ++++++++++++--- .../mediaViewer/VideoPlayerControls.scss | 18 ++++++-- .../mediaViewer/VideoPlayerControls.tsx | 6 ++- src/hooks/useFullscreen.ts | 41 ++++++++++++++++--- src/util/environment.ts | 2 + 7 files changed, 91 insertions(+), 18 deletions(-) diff --git a/src/components/mediaViewer/MediaViewerContent.tsx b/src/components/mediaViewer/MediaViewerContent.tsx index 03753d6c1..fb9da83c5 100644 --- a/src/components/mediaViewer/MediaViewerContent.tsx +++ b/src/components/mediaViewer/MediaViewerContent.tsx @@ -130,6 +130,7 @@ const MediaViewerContent: FC = (props) => { noPlay={!isActive} onClose={onClose} isMuted + shouldCloseOnClick volume={0} playbackRate={1} /> diff --git a/src/components/mediaViewer/MediaViewerSlides.tsx b/src/components/mediaViewer/MediaViewerSlides.tsx index d06779722..590729f39 100644 --- a/src/components/mediaViewer/MediaViewerSlides.tsx +++ b/src/components/mediaViewer/MediaViewerSlides.tsx @@ -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 = ({ 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 = ({ }, [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 = ({ 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 = ({ containerRef.current.dispatchEvent(wheelEvent); }, }); - }, [zoomLevelChange, hasZoomChanged, isHidden]); + }, [zoomLevelChange, hasZoomChanged, isHidden, isFullscreen]); if (activeMediaId === undefined) return undefined; diff --git a/src/components/mediaViewer/VideoPlayer.tsx b/src/components/mediaViewer/VideoPlayer.tsx index d68d2487c..8885784b5 100644 --- a/src/components/mediaViewer/VideoPlayer.tsx +++ b/src/components/mediaViewer/VideoPlayer.tsx @@ -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) => void; }; @@ -58,6 +61,7 @@ const VideoPlayer: FC = ({ onClose, toggleControls, areControlsVisible, + shouldCloseOnClick, isProtected, }) => { const { @@ -70,10 +74,18 @@ const VideoPlayer: FC = ({ const videoRef = useRef(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 = ({ } }, [isPlaying]); + const handleClick = useCallback((e: React.MouseEvent) => { + if (shouldCloseOnClick) { + onClose(e); + } else { + togglePlayState(e); + } + }, [onClose, shouldCloseOnClick, togglePlayState]); + useVideoCleanup(videoRef, []); const handleTimeUpdate = useCallback((e: React.SyntheticEvent) => { @@ -231,7 +251,7 @@ const VideoPlayer: FC = ({ 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} diff --git a/src/components/mediaViewer/VideoPlayerControls.scss b/src/components/mediaViewer/VideoPlayerControls.scss index dd30325b8..473d57e64 100644 --- a/src/components/mediaViewer/VideoPlayerControls.scss +++ b/src/components/mediaViewer/VideoPlayerControls.scss @@ -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; } diff --git a/src/components/mediaViewer/VideoPlayerControls.tsx b/src/components/mediaViewer/VideoPlayerControls.tsx index 1afe6756a..92af742ab 100644 --- a/src/components/mediaViewer/VideoPlayerControls.tsx +++ b/src/components/mediaViewer/VideoPlayerControls.tsx @@ -235,7 +235,11 @@ const VideoPlayerControls: FC = ({ 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(); diff --git a/src/util/environment.ts b/src/util/environment.ts index dcc898814..2e2450997 100644 --- a/src/util/environment.ts +++ b/src/util/environment.ts @@ -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