| ... | @@ -19,6 +19,7 @@ function LoadedMediaVideo(props: { | ... | @@ -19,6 +19,7 @@ function LoadedMediaVideo(props: { |
| 19 | }) { | 19 | }) { |
| 20 | const focused = useIsFocused(); | 20 | const focused = useIsFocused(); |
| 21 | const active = useRef(focused && AppState.currentState === "active"); | 21 | const active = useRef(focused && AppState.currentState === "active"); |
| | 22 | const fullscreen = useRef(false); |
| 22 | const [attempt, setAttempt] = useState(0); | 23 | const [attempt, setAttempt] = useState(0); |
| 23 | // Expo's Android player also reports completed playback as idle. | 24 | // Expo's Android player also reports completed playback as idle. |
| 24 | const [loadState, setLoadState] = useState<"pending" | "complete" | "error">("pending"); | 25 | const [loadState, setLoadState] = useState<"pending" | "complete" | "error">("pending"); |
| ... | @@ -38,10 +39,12 @@ function LoadedMediaVideo(props: { | ... | @@ -38,10 +39,12 @@ function LoadedMediaVideo(props: { |
| 38 | | 39 | |
| 39 | useEffect(() => { | 40 | useEffect(() => { |
| 40 | active.current = focused && !props.paused && AppState.currentState === "active"; | 41 | active.current = focused && !props.paused && AppState.currentState === "active"; |
| 41 | if (!active.current) player.pause(); | 42 | if (!focused || props.paused || (!active.current && !fullscreen.current)) player.pause(); |
| | 43 | // Native background handling distinguishes Android's fullscreen activity |
| | 44 | // from leaving the app; React Native reports both as background. |
| 42 | const subscription = AppState.addEventListener("change", (state) => { | 45 | const subscription = AppState.addEventListener("change", (state) => { |
| 43 | active.current = focused && !props.paused && state === "active"; | 46 | active.current = focused && !props.paused && state === "active"; |
| 44 | if (!active.current) player.pause(); | 47 | if (state === "inactive" || (state === "background" && !fullscreen.current)) player.pause(); |
| 45 | }); | 48 | }); |
| 46 | return () => subscription.remove(); | 49 | return () => subscription.remove(); |
| 47 | }, [focused, player, props.paused]); | 50 | }, [focused, player, props.paused]); |
| ... | @@ -70,6 +73,12 @@ function LoadedMediaVideo(props: { | ... | @@ -70,6 +73,12 @@ function LoadedMediaVideo(props: { |
| 70 | nativeControls | 73 | nativeControls |
| 71 | contentFit="contain" | 74 | contentFit="contain" |
| 72 | fullscreenOptions={{ enable: true }} | 75 | fullscreenOptions={{ enable: true }} |
| | 76 | onFullscreenEnter={() => { |
| | 77 | fullscreen.current = true; |
| | 78 | }} |
| | 79 | onFullscreenExit={() => { |
| | 80 | fullscreen.current = false; |
| | 81 | }} |
| 73 | allowsPictureInPicture={false} | 82 | allowsPictureInPicture={false} |
| 74 | /> | 83 | /> |
| 75 | {loadState === "error" || (loadState === "complete" && status === "error") ? ( | 84 | {loadState === "error" || (loadState === "complete" && status === "error") ? ( |