From 7e4f8cabd3971bdf19647d122e3267ab6d1991e8 Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 6 Sep 2024 15:01:05 -0700 Subject: [PATCH] [Video] Handle push/pop on Android for autoplay (#5194) --- package.json | 2 +- .../post-embeds/ActiveVideoNativeContext.tsx | 15 +++++++++-- src/view/com/util/post-embeds/VideoEmbed.tsx | 2 +- .../VideoEmbedInner/VideoEmbedInnerNative.tsx | 4 +++ src/view/shell/index.tsx | 25 ++++++++++++++++++- yarn.lock | 4 +-- 6 files changed, 45 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 3f9f0bce..eff665a6 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,7 @@ "expo-system-ui": "~3.0.4", "expo-task-manager": "~11.8.1", "expo-updates": "~0.25.14", - "expo-video": "https://github.com/bluesky-social/expo/raw/expo-video-1.2.4-patch/packages/expo-video/expo-video-v1.2.4-1.tgz", + "expo-video": "https://github.com/bluesky-social/expo/raw/expo-video-1.2.4-patch/packages/expo-video/expo-video-v1.2.4-2.tgz", "expo-web-browser": "~13.0.3", "fast-text-encoding": "^1.0.6", "history": "^5.3.0", diff --git a/src/view/com/util/post-embeds/ActiveVideoNativeContext.tsx b/src/view/com/util/post-embeds/ActiveVideoNativeContext.tsx index da8c7a98..95fa0bb0 100644 --- a/src/view/com/util/post-embeds/ActiveVideoNativeContext.tsx +++ b/src/view/com/util/post-embeds/ActiveVideoNativeContext.tsx @@ -1,7 +1,7 @@ import React from 'react' import {useVideoPlayer, VideoPlayer} from 'expo-video' -import {isNative} from '#/platform/detection' +import {isAndroid, isNative} from '#/platform/detection' const Context = React.createContext<{ activeSource: string @@ -26,7 +26,18 @@ export function Provider({children}: {children: React.ReactNode}) { }) const setActiveSourceOuter = (src: string | null, viewId: string | null) => { - setActiveSource(src ? src : '') + // HACK + // expo-video doesn't like it when you try and move a `player` to another `VideoView`. Instead, we need to actually + // unregister that player to let the new screen register it. This is only a problem on Android, so we only need to + // apply it there. + if (src === activeSource && isAndroid) { + setActiveSource('') + setTimeout(() => { + setActiveSource(src ? src : '') + }, 100) + } else { + setActiveSource(src ? src : '') + } setActiveViewId(viewId ? viewId : '') } diff --git a/src/view/com/util/post-embeds/VideoEmbed.tsx b/src/view/com/util/post-embeds/VideoEmbed.tsx index e5457555..9c3a34dd 100644 --- a/src/view/com/util/post-embeds/VideoEmbed.tsx +++ b/src/view/com/util/post-embeds/VideoEmbed.tsx @@ -71,7 +71,7 @@ function InnerWrapper({embed}: Props) { const [playerStatus, setPlayerStatus] = useState< VideoPlayerStatus | 'paused' - >(player.playing ? 'readyToPlay' : 'paused') + >('paused') const [isMuted, setIsMuted] = useState(player.muted) const [isFullscreen, setIsFullscreen] = React.useState(false) const [timeRemaining, setTimeRemaining] = React.useState(0) diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx index 31e86303..de9a2c74 100644 --- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx @@ -8,6 +8,7 @@ import {useLingui} from '@lingui/react' import {HITSLOP_30} from '#/lib/constants' import {clamp} from '#/lib/numbers' +import {isAndroid} from 'platform/detection' import {useActiveVideoNative} from 'view/com/util/post-embeds/ActiveVideoNativeContext' import {atoms as a, useTheme} from '#/alf' import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' @@ -61,6 +62,9 @@ export function VideoEmbedInnerNative({ PlatformInfo.setAudioActive(true) player.muted = false setIsFullscreen(true) + if (isAndroid) { + player.play() + } }} onFullscreenExit={() => { PlatformInfo.setAudioCategory(AudioCategory.Ambient) diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index 7d080e57..aed92cbb 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -11,7 +11,7 @@ import Animated from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' import * as NavigationBar from 'expo-navigation-bar' import {StatusBar} from 'expo-status-bar' -import {useNavigationState} from '@react-navigation/native' +import {useNavigation, useNavigationState} from '@react-navigation/native' import {useSession} from '#/state/session' import { @@ -20,6 +20,7 @@ import { useSetDrawerOpen, } from '#/state/shell' import {useCloseAnyActiveElement} from '#/state/util' +import {useDedupe} from 'lib/hooks/useDedupe' import {useNotificationsHandler} from 'lib/hooks/useNotificationHandler' import {usePalette} from 'lib/hooks/usePalette' import {useNotificationsRegistration} from 'lib/notifications/notifications' @@ -33,6 +34,7 @@ import {ErrorBoundary} from 'view/com/util/ErrorBoundary' import {MutedWordsDialog} from '#/components/dialogs/MutedWords' import {SigninDialog} from '#/components/dialogs/Signin' import {Outlet as PortalOutlet} from '#/components/Portal' +import {updateActiveViewAsync} from '../../../modules/expo-bluesky-swiss-army/src/VisibilityView' import {RoutesContainer, TabsNavigator} from '../../Navigation' import {Composer} from './Composer' import {DrawerContent} from './Drawer' @@ -76,6 +78,27 @@ function ShellInner() { } }, [closeAnyActiveElement]) + // HACK + // expo-video doesn't like it when you try and move a `player` to another `VideoView`. Instead, we need to actually + // unregister that player to let the new screen register it. This is only a problem on Android, so we only need to + // apply it there. + // The `state` event should only fire whenever we push or pop to a screen, and should not fire consecutively quickly. + // To be certain though, we will also dedupe these calls. + const navigation = useNavigation() + const dedupe = useDedupe(1000) + React.useEffect(() => { + if (!isAndroid) return + const onFocusOrBlur = () => { + setTimeout(() => { + dedupe(updateActiveViewAsync) + }, 500) + } + navigation.addListener('state', onFocusOrBlur) + return () => { + navigation.removeListener('state', onFocusOrBlur) + } + }, [dedupe, navigation]) + return ( <>