[Video] Fix fullscreen on Chrome (#5246)
parent
0f6be244a6
commit
66239ba11d
|
@ -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<HTMLElement>) {
|
||||||
|
if (!isWeb) throw new Error("'useFullscreen' is a web-only hook")
|
||||||
|
const isFullscreen = useSyncExternalStore(fullscreenSubscribe, () =>
|
||||||
|
Boolean(document.fullscreenElement),
|
||||||
|
)
|
||||||
|
const scrollYRef = useRef<null | number>(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
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import {
|
||||||
VideoNotFoundError,
|
VideoNotFoundError,
|
||||||
} from '#/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb'
|
} from '#/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb'
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
|
import {useFullscreen} from '#/components/hooks/useFullscreen'
|
||||||
import {ErrorBoundary} from '../ErrorBoundary'
|
import {ErrorBoundary} from '../ErrorBoundary'
|
||||||
import {useActiveVideoWeb} from './ActiveVideoWebContext'
|
import {useActiveVideoWeb} from './ActiveVideoWebContext'
|
||||||
import * as VideoFallback from './VideoEmbedInner/VideoFallback'
|
import * as VideoFallback from './VideoEmbedInner/VideoFallback'
|
||||||
|
@ -106,6 +107,8 @@ function ViewportObserver({
|
||||||
}) {
|
}) {
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
const [nearScreen, setNearScreen] = useState(false)
|
const [nearScreen, setNearScreen] = useState(false)
|
||||||
|
const [isFullscreen] = useFullscreen()
|
||||||
|
const [nearScreenOrFullscreen, setNearScreenOrFullscreen] = useState(false)
|
||||||
|
|
||||||
// Send position when scrolling. This is done with an IntersectionObserver
|
// Send position when scrolling. This is done with an IntersectionObserver
|
||||||
// observing a div of 100vh height
|
// observing a div of 100vh height
|
||||||
|
@ -135,9 +138,17 @@ function ViewportObserver({
|
||||||
}
|
}
|
||||||
}, [isAnyViewActive, sendPosition])
|
}, [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 (
|
return (
|
||||||
<View style={[a.flex_1, a.flex_row]}>
|
<View style={[a.flex_1, a.flex_row]}>
|
||||||
{nearScreen && children}
|
{nearScreenOrFullscreen && children}
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
style={{
|
style={{
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
import React, {
|
import React, {useCallback, useEffect, useRef, useState} from 'react'
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
useSyncExternalStore,
|
|
||||||
} from 'react'
|
|
||||||
import {Pressable, View} from 'react-native'
|
import {Pressable, View} from 'react-native'
|
||||||
import {SvgProps} from 'react-native-svg'
|
import {SvgProps} from 'react-native-svg'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
@ -21,6 +15,7 @@ import {
|
||||||
} from '#/state/preferences'
|
} from '#/state/preferences'
|
||||||
import {atoms as a, useTheme, web} from '#/alf'
|
import {atoms as a, useTheme, web} from '#/alf'
|
||||||
import {Button} from '#/components/Button'
|
import {Button} from '#/components/Button'
|
||||||
|
import {useFullscreen} from '#/components/hooks/useFullscreen'
|
||||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||||
import {
|
import {
|
||||||
ArrowsDiagonalIn_Stroke2_Corner0_Rounded as ArrowsInIcon,
|
ArrowsDiagonalIn_Stroke2_Corner0_Rounded as ArrowsInIcon,
|
||||||
|
@ -850,25 +845,3 @@ function useVideoUtils(ref: React.RefObject<HTMLVideoElement>) {
|
||||||
canPlay,
|
canPlay,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fullscreenSubscribe(onChange: () => void) {
|
|
||||||
document.addEventListener('fullscreenchange', onChange)
|
|
||||||
return () => document.removeEventListener('fullscreenchange', onChange)
|
|
||||||
}
|
|
||||||
|
|
||||||
function useFullscreen(ref: React.RefObject<HTMLElement>) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue