diff --git a/apps/mobile/src/components/MediaVideoPlayer.tsx b/apps/mobile/src/components/MediaVideoPlayer.tsx index 0e3d8416affa..d06b8bcca04c 100644 --- a/apps/mobile/src/components/MediaVideoPlayer.tsx +++ b/apps/mobile/src/components/MediaVideoPlayer.tsx @@ -19,6 +19,7 @@ function LoadedMediaVideo(props: { }) { const focused = useIsFocused(); const active = useRef(focused && AppState.currentState === "active"); + const fullscreen = useRef(false); const [attempt, setAttempt] = useState(0); // Expo's Android player also reports completed playback as idle. const [loadState, setLoadState] = useState<"pending" | "complete" | "error">("pending"); @@ -38,10 +39,12 @@ function LoadedMediaVideo(props: { useEffect(() => { active.current = focused && !props.paused && AppState.currentState === "active"; - if (!active.current) player.pause(); + if (!focused || props.paused || (!active.current && !fullscreen.current)) player.pause(); + // Native background handling distinguishes Android's fullscreen activity + // from leaving the app; React Native reports both as background. const subscription = AppState.addEventListener("change", (state) => { active.current = focused && !props.paused && state === "active"; - if (!active.current) player.pause(); + if (state === "inactive" || (state === "background" && !fullscreen.current)) player.pause(); }); return () => subscription.remove(); }, [focused, player, props.paused]); @@ -70,6 +73,12 @@ function LoadedMediaVideo(props: { nativeControls contentFit="contain" fullscreenOptions={{ enable: true }} + onFullscreenEnter={() => { + fullscreen.current = true; + }} + onFullscreenExit={() => { + fullscreen.current = false; + }} allowsPictureInPicture={false} /> {loadState === "error" || (loadState === "complete" && status === "error") ? ( diff --git a/apps/web/src/components/media/MediaVideoPlayer.tsx b/apps/web/src/components/media/MediaVideoPlayer.tsx index 90c18984e17b..cc3185a3a77e 100644 --- a/apps/web/src/components/media/MediaVideoPlayer.tsx +++ b/apps/web/src/components/media/MediaVideoPlayer.tsx @@ -93,11 +93,19 @@ export function MediaVideoPlayer({ const video = videoRef.current; if (!video) return; const pauseWhenHidden = () => { - if (document.hidden) video.pause(); + // Native fullscreen can hide the inline page while this video is still visible. + const fullscreen = + document.fullscreenElement?.contains(video) || + ("webkitDisplayingFullscreen" in video && video.webkitDisplayingFullscreen === true); + if (document.hidden && !fullscreen) video.pause(); }; document.addEventListener("visibilitychange", pauseWhenHidden); + document.addEventListener("fullscreenchange", pauseWhenHidden); + video.addEventListener("webkitendfullscreen", pauseWhenHidden); return () => { document.removeEventListener("visibilitychange", pauseWhenHidden); + document.removeEventListener("fullscreenchange", pauseWhenHidden); + video.removeEventListener("webkitendfullscreen", pauseWhenHidden); video.pause(); }; }, [src, failed, loadAttempt]);