From 66239ba11dd38056d1215327f160a0bb61d49320 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 10 Sep 2024 06:37:57 +0100 Subject: [PATCH] [Video] Fix fullscreen on Chrome (#5246) --- src/components/hooks/useFullscreen.ts | 53 +++++++++++++++++++ .../com/util/post-embeds/VideoEmbed.web.tsx | 13 ++++- .../VideoEmbedInner/VideoWebControls.tsx | 31 +---------- 3 files changed, 67 insertions(+), 30 deletions(-) create mode 100644 src/components/hooks/useFullscreen.ts diff --git a/src/components/hooks/useFullscreen.ts b/src/components/hooks/useFullscreen.ts new file mode 100644 index 00000000..498f2222 --- /dev/null +++ b/src/components/hooks/useFullscreen.ts @@ -0,0 +1,53 @@ +import { + useCallback, + useEffect, + useRef, + useState, + useSyncExternalStore, +} from 'react' + +import {isFirefox, isSafari} from '#/lib/browser' +import {isWeb} from '#/platform/detection' + +function fullscreenSubscribe(onChange: () => void) { + document.addEventListener('fullscreenchange', onChange) + return () => document.removeEventListener('fullscreenchange', onChange) +} + +export function useFullscreen(ref?: React.RefObject) { + if (!isWeb) throw new Error("'useFullscreen' is a web-only hook") + const isFullscreen = useSyncExternalStore(fullscreenSubscribe, () => + Boolean(document.fullscreenElement), + ) + const scrollYRef = useRef(null) + const [prevIsFullscreen, setPrevIsFullscreen] = useState(isFullscreen) + + const toggleFullscreen = useCallback(() => { + if (isFullscreen) { + document.exitFullscreen() + } else { + if (!ref) throw new Error('No ref provided') + if (!ref.current) return + scrollYRef.current = window.scrollY + ref.current.requestFullscreen() + } + }, [isFullscreen, ref]) + + useEffect(() => { + if (prevIsFullscreen === isFullscreen) return + setPrevIsFullscreen(isFullscreen) + + // Chrome has an issue where it doesn't scroll back to the top after exiting fullscreen + // Let's play it safe and do it if not FF or Safari, since anything else will probably be chromium + if (prevIsFullscreen && !isFirefox && !isSafari) { + setTimeout(() => { + if (scrollYRef.current !== null) { + window.scrollTo(0, scrollYRef.current) + scrollYRef.current = null + } + }, 100) + } + }, [isFullscreen, prevIsFullscreen]) + + return [isFullscreen, toggleFullscreen] as const +} diff --git a/src/view/com/util/post-embeds/VideoEmbed.web.tsx b/src/view/com/util/post-embeds/VideoEmbed.web.tsx index a25f9464..e96b7592 100644 --- a/src/view/com/util/post-embeds/VideoEmbed.web.tsx +++ b/src/view/com/util/post-embeds/VideoEmbed.web.tsx @@ -12,6 +12,7 @@ import { VideoNotFoundError, } from '#/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb' import {atoms as a} from '#/alf' +import {useFullscreen} from '#/components/hooks/useFullscreen' import {ErrorBoundary} from '../ErrorBoundary' import {useActiveVideoWeb} from './ActiveVideoWebContext' import * as VideoFallback from './VideoEmbedInner/VideoFallback' @@ -106,6 +107,8 @@ function ViewportObserver({ }) { const ref = useRef(null) const [nearScreen, setNearScreen] = useState(false) + const [isFullscreen] = useFullscreen() + const [nearScreenOrFullscreen, setNearScreenOrFullscreen] = useState(false) // Send position when scrolling. This is done with an IntersectionObserver // observing a div of 100vh height @@ -135,9 +138,17 @@ function ViewportObserver({ } }, [isAnyViewActive, sendPosition]) + // disguesting effect - it should be `nearScreen` except when fullscreen + // when it should be whatever it was before fullscreen changed + useEffect(() => { + if (!isFullscreen) { + setNearScreenOrFullscreen(nearScreen) + } + }, [isFullscreen, nearScreen]) + return ( - {nearScreen && children} + {nearScreenOrFullscreen && children}
) { canPlay, } } - -function fullscreenSubscribe(onChange: () => void) { - document.addEventListener('fullscreenchange', onChange) - return () => document.removeEventListener('fullscreenchange', onChange) -} - -function useFullscreen(ref: React.RefObject) { - const isFullscreen = useSyncExternalStore(fullscreenSubscribe, () => - Boolean(document.fullscreenElement), - ) - - const toggleFullscreen = useCallback(() => { - if (isFullscreen) { - document.exitFullscreen() - } else { - if (!ref.current) return - ref.current.requestFullscreen() - } - }, [isFullscreen, ref]) - - return [isFullscreen, toggleFullscreen] as const -}