From a5437ebbeb9d2da84a1560d4c68d7c9ce42c5140 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 4 Aug 2025 20:24:53 +0300 Subject: Fix web video ViewportObserver component (#8776) * Revert "[APP-1083] bug fix: videos not accurately autoplaying on web (#8692)" This reverts commit 9aa35e9fbb6136a88a66388ff5e4644ad25c9e4b. * fix overflow hidden breaking the video viewport observer --- src/components/Post/Embed/VideoEmbed/index.web.tsx | 106 ++++++++++----------- 1 file changed, 51 insertions(+), 55 deletions(-) (limited to 'src/components/Post/Embed/VideoEmbed/index.web.tsx') 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()}> - + - + ) return ( - {cropDisabled ? ( - - {contents} - - ) : ( - - {contents} - - )} + + {cropDisabled ? ( + + {contents} + + ) : ( + + {contents} + + )} + ) } +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 ( - {nearScreen && children} + + {children} +
{ + const nearScreen = useContext(NearScreenContext) + + return nearScreen ? children : null +} + function VideoError({error, retry}: {error: unknown; retry: () => void}) { const {_} = useLingui() -- cgit 1.4.1