diff options
Diffstat (limited to 'src/components/Post/Embed/VideoEmbed/index.web.tsx')
-rw-r--r-- | src/components/Post/Embed/VideoEmbed/index.web.tsx | 106 |
1 files changed, 51 insertions, 55 deletions
diff --git a/src/components/Post/Embed/VideoEmbed/index.web.tsx b/src/components/Post/Embed/VideoEmbed/index.web.tsx index 900bc2188..5bb54eef8 100644 --- a/src/components/Post/Embed/VideoEmbed/index.web.tsx +++ b/src/components/Post/Embed/VideoEmbed/index.web.tsx @@ -1,4 +1,11 @@ -import {useCallback, useEffect, useRef, useState} from 'react' +import { + createContext, + useCallback, + useContext, + useEffect, + useRef, + useState, +} from 'react' import {View} from 'react-native' import {type AppBskyEmbedVideo} from '@atproto/api' import {msg} from '@lingui/macro' @@ -83,9 +90,7 @@ export function VideoEmbed({ style={{display: 'flex', flex: 1, cursor: 'default'}} onClick={evt => evt.stopPropagation()}> <ErrorBoundary renderError={renderError} key={key}> - <ViewportObserver - sendPosition={sendPosition} - isAnyViewActive={currentActiveView !== null}> + <OnlyNearScreen> <VideoEmbedInnerWeb embed={embed} active={active} @@ -93,31 +98,39 @@ export function VideoEmbed({ onScreen={onScreen} lastKnownTime={lastKnownTime} /> - </ViewportObserver> + </OnlyNearScreen> </ErrorBoundary> </div> ) return ( <View style={[a.pt_xs]}> - {cropDisabled ? ( - <View style={[a.w_full, a.overflow_hidden, {aspectRatio: max ?? 1}]}> - {contents} - </View> - ) : ( - <ConstrainedImage - fullBleed={crop === 'square'} - aspectRatio={constrained || 1}> - {contents} - </ConstrainedImage> - )} + <ViewportObserver + sendPosition={sendPosition} + isAnyViewActive={currentActiveView !== null}> + {cropDisabled ? ( + <View style={[a.w_full, a.overflow_hidden, {aspectRatio: max ?? 1}]}> + {contents} + </View> + ) : ( + <ConstrainedImage + fullBleed={crop === 'square'} + aspectRatio={constrained || 1}> + {contents} + </ConstrainedImage> + )} + </ViewportObserver> </View> ) } +const NearScreenContext = createContext(false) + /** * Renders a 100vh tall div and watches it with an IntersectionObserver to * send the position of the div when it's near the screen. + * + * IMPORTANT: ViewportObserver _must_ not be within a `overflow: hidden` container. */ function ViewportObserver({ children, @@ -138,53 +151,22 @@ function ViewportObserver({ useEffect(() => { if (!ref.current) return if (isFullscreen && !isFirefox) return - - let scrollTimeout: NodeJS.Timeout | null = null - let lastObserverEntry: IntersectionObserverEntry | null = null - - const updatePositionFromEntry = () => { - if (!lastObserverEntry) return - const rect = lastObserverEntry.boundingClientRect - const position = rect.y + rect.height / 2 - sendPosition(position) - } - - const handleScroll = () => { - if (scrollTimeout) { - clearTimeout(scrollTimeout) - } - scrollTimeout = setTimeout(updatePositionFromEntry, 4) // ~240fps - } - const observer = new IntersectionObserver( entries => { const entry = entries[0] if (!entry) return - lastObserverEntry = entry - setNearScreen(entry.isIntersecting) - const rect = entry.boundingClientRect - const position = rect.y + rect.height / 2 + const position = + entry.boundingClientRect.y + entry.boundingClientRect.height / 2 sendPosition(position) + setNearScreen(entry.isIntersecting) }, - {threshold: [0, 0.1, 0.25, 0.5, 0.75, 1.0]}, + {threshold: Array.from({length: 101}, (_, i) => i / 100)}, ) - observer.observe(ref.current) + return () => observer.disconnect() + }, [sendPosition, isFullscreen]) - if (nearScreen) { - window.addEventListener('scroll', handleScroll, {passive: true}) - } - - return () => { - observer.disconnect() - if (scrollTimeout) { - clearTimeout(scrollTimeout) - } - window.removeEventListener('scroll', handleScroll) - } - }, [sendPosition, isFullscreen, nearScreen]) - - // In case scrolling hasn't started yet, send the original position + // In case scrolling hasn't started yet, send up the position useEffect(() => { if (ref.current && !isAnyViewActive) { const rect = ref.current.getBoundingClientRect() @@ -195,7 +177,9 @@ function ViewportObserver({ return ( <View style={[a.flex_1, a.flex_row]}> - {nearScreen && children} + <NearScreenContext.Provider value={nearScreen}> + {children} + </NearScreenContext.Provider> <div ref={ref} style={{ @@ -213,6 +197,18 @@ function ViewportObserver({ ) } +/** + * Awkward data flow here, but we need to hide the video when it's not near the screen. + * But also, ViewportObserver _must_ not be within a `overflow: hidden` container. + * So we put it at the top level of the component tree here, then hide the children of + * the auto-resizing container. + */ +export const OnlyNearScreen = ({children}: {children: React.ReactNode}) => { + const nearScreen = useContext(NearScreenContext) + + return nearScreen ? children : null +} + function VideoError({error, retry}: {error: unknown; retry: () => void}) { const {_} = useLingui() |