Skip to content
Source
Sign inSign up

useMediaVideoPlayback.ts

57 lines2.5 KB

1import { useIsFocused } from "@react-navigation/native";
2import { useEvent } from "expo";
3import type { VideoPlayer } from "expo-video";
4import { useEffect, useRef } from "react";
5import { AppState } from "react-native";
6​
7import { useFullscreenVideoOpen, useMediaAudioSessionHold } from "./videoAudioSession";
8​
9export function useMediaVideoPlayback(
10 player: VideoPlayer,
11 options: { readonly paused: boolean; readonly visible?: boolean },
12 playback: { play(): Promise<void>; pause(): void },
13) {
14 const focused = useIsFocused();
15 const fullscreenVideoOpen = useFullscreenVideoOpen();
16 const paused = options.paused || fullscreenVideoOpen || options.visible === false;
17 const active = useRef(focused && !paused && AppState.currentState === "active");
18 const fullscreen = useRef(false);
19 const interruption = useRef(0);
20 const { isPlaying } = useEvent(player, "playingChange", { isPlaying: player.playing });
21 useMediaAudioSessionHold(isPlaying);
22​
23 // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- subscribes to AppState for the player's lifetime and pauses the native player when unfocused or backgrounded
24 useEffect(() => {
25 active.current = focused && !paused && AppState.currentState === "active";
26 if (!active.current) interruption.current += 1;
27 if (!focused || paused || (!active.current && !fullscreen.current)) playback.pause();
28 // Native background handling distinguishes Android's fullscreen activity
29 // from leaving the app; React Native reports both as background.
30 const subscription = AppState.addEventListener("change", (state) => {
31 active.current = focused && !paused && state === "active";
32 if (!active.current) interruption.current += 1;
33 if (state === "inactive" || (state === "background" && !fullscreen.current)) playback.pause();
34 });
35 return () => subscription.remove();
36 }, [focused, playback, paused]);
37​
38 const createPlayRequest = () => {
39 const requestedAt = interruption.current;
40 const requestedWhileActive = active.current;
41 // Returning to the content must not revive playback interrupted during loading.
42 return () => {
43 if (requestedWhileActive && active.current && requestedAt === interruption.current)
44 return playback.play();
45 };
46 };
47​
48 const onFullscreenEnter = () => {
49 fullscreen.current = true;
50 };
51 const onFullscreenExit = () => {
52 fullscreen.current = false;
53 if (!active.current) playback.pause();
54 };
55​
56 return { active, fullscreen, createPlayRequest, onFullscreenEnter, onFullscreenExit };