Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

[Video] Manage foreground/background playback on the native side (#5104)

authored by

Hailey and committed by
GitHub
dde72b48 05e61346

+104 -29
+90 -3
patches/expo-video+1.2.4.patch
··· 80 80 + } 81 81 +} 82 82 \ No newline at end of file 83 + diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoManager.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoManager.kt 84 + index 4b6c6d8..e20f51a 100644 85 + --- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoManager.kt 86 + +++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoManager.kt 87 + @@ -1,5 +1,6 @@ 88 + package expo.modules.video 89 + 90 + +import android.provider.MediaStore.Video 91 + import androidx.annotation.OptIn 92 + import androidx.media3.common.util.UnstableApi 93 + import expo.modules.kotlin.AppContext 94 + @@ -15,6 +16,8 @@ object VideoManager { 95 + // Keeps track of all existing VideoPlayers, and whether they are attached to a VideoView 96 + private var videoPlayersToVideoViews = mutableMapOf<VideoPlayer, MutableList<VideoView>>() 97 + 98 + + private var previouslyPlayingViews: MutableList<VideoView>? = null 99 + + 100 + private lateinit var audioFocusManager: AudioFocusManager 101 + 102 + fun onModuleCreated(appContext: AppContext) { 103 + @@ -69,16 +72,24 @@ object VideoManager { 104 + return videoPlayersToVideoViews[videoPlayer]?.isNotEmpty() ?: false 105 + } 106 + 107 + - fun onAppForegrounded() = Unit 108 + + fun onAppForegrounded() { 109 + + val previouslyPlayingViews = this.previouslyPlayingViews ?: return 110 + + for (videoView in previouslyPlayingViews) { 111 + + val player = videoView.videoPlayer?.player ?: continue 112 + + player.play() 113 + + } 114 + + this.previouslyPlayingViews = null 115 + + } 116 + 117 + fun onAppBackgrounded() { 118 + + val previouslyPlayingViews = mutableListOf<VideoView>() 119 + for (videoView in videoViews.values) { 120 + - if (videoView.videoPlayer?.staysActiveInBackground == false && 121 + - !videoView.willEnterPiP && 122 + - !videoView.isInFullscreen 123 + - ) { 124 + - videoView.videoPlayer?.player?.pause() 125 + + val player = videoView.videoPlayer?.player ?: continue 126 + + if (player.isPlaying) { 127 + + player.pause() 128 + + previouslyPlayingViews.add(videoView) 129 + } 130 + } 131 + + this.previouslyPlayingViews = previouslyPlayingViews 132 + } 133 + } 83 134 diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt 84 135 index ec3da2a..5a1397a 100644 85 136 --- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt ··· 220 271 //# sourceMappingURL=VideoView.types.d.ts.map 221 272 \ No newline at end of file 222 273 diff --git a/node_modules/expo-video/ios/VideoManager.swift b/node_modules/expo-video/ios/VideoManager.swift 223 - index 094a8b0..412fd0c 100644 274 + index 094a8b0..3f00525 100644 224 275 --- a/node_modules/expo-video/ios/VideoManager.swift 225 276 +++ b/node_modules/expo-video/ios/VideoManager.swift 226 - @@ -51,45 +51,45 @@ class VideoManager { 277 + @@ -12,6 +12,7 @@ class VideoManager { 278 + 279 + private var videoViews = NSHashTable<VideoView>.weakObjects() 280 + private var videoPlayers = NSHashTable<VideoPlayer>.weakObjects() 281 + + private var previouslyPlayingPlayers: [VideoPlayer]? 282 + 283 + func register(videoPlayer: VideoPlayer) { 284 + videoPlayers.add(videoPlayer) 285 + @@ -33,63 +34,70 @@ class VideoManager { 286 + for videoPlayer in videoPlayers.allObjects { 287 + videoPlayer.setTracksEnabled(true) 288 + } 289 + + 290 + + if let previouslyPlayingPlayers = self.previouslyPlayingPlayers { 291 + + previouslyPlayingPlayers.forEach { player in 292 + + player.pointer.play() 293 + + } 294 + + } 295 + } 296 + 297 + func onAppBackgrounded() { 298 + + var previouslyPlayingPlayers: [VideoPlayer] = [] 299 + for videoView in videoViews.allObjects { 300 + guard let player = videoView.player else { 301 + continue 302 + } 303 + - if player.staysActiveInBackground == true { 304 + - player.setTracksEnabled(videoView.isInPictureInPicture) 305 + - } else if !videoView.isInPictureInPicture { 306 + + if player.isPlaying { 307 + player.pointer.pause() 308 + + previouslyPlayingPlayers.append(player) 309 + } 310 + } 311 + + self.previouslyPlayingPlayers = previouslyPlayingPlayers 312 + } 313 + 227 314 // MARK: - Audio Session Management 228 - 315 + 229 316 internal func setAppropriateAudioSessionOrWarn() { 230 317 - let audioSession = AVAudioSession.sharedInstance() 231 318 - var audioSessionCategoryOptions: AVAudioSession.CategoryOptions = []
+10 -1
patches/expo-video+1.2.4.patch.md
··· 2 2 3 3 ## `expo-video` Patch 4 4 5 - This patch adds two props to `VideoView`: `onEnterFullscreen` and `onExitFullscreen` which do exactly what they say on 5 + ### `onEnterFullScreen`/`onExitFullScreen` 6 + Adds two props to `VideoView`: `onEnterFullscreen` and `onExitFullscreen` which do exactly what they say on 6 7 the tin. 7 8 9 + ### Removing audio session management 10 + 8 11 This patch also removes the audio session management that Expo does on its own, as we handle audio session management 9 12 ourselves. 13 + 14 + ### Pausing/playing on background/foreground 15 + 16 + Instead of handling the pausing/playing of videos in React, we'll handle them here. There's some logic that we do not 17 + need (around PIP mode) that we can remove, and just pause any playing players on background and then resume them on 18 + foreground.
+4 -25
src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx
··· 5 5 import {AppBskyEmbedVideo} from '@atproto/api' 6 6 import {msg} from '@lingui/macro' 7 7 import {useLingui} from '@lingui/react' 8 - import {useIsFocused} from '@react-navigation/native' 9 8 10 9 import {HITSLOP_30} from '#/lib/constants' 11 - import {useAppState} from '#/lib/hooks/useAppState' 12 10 import {clamp} from '#/lib/numbers' 13 - import {logger} from '#/logger' 14 11 import {useActiveVideoNative} from 'view/com/util/post-embeds/ActiveVideoNativeContext' 15 12 import {atoms as a, useTheme} from '#/alf' 16 13 import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' ··· 29 26 const {_} = useLingui() 30 27 const {player} = useActiveVideoNative() 31 28 const ref = useRef<VideoView>(null) 32 - const isScreenFocused = useIsFocused() 33 - const isAppFocused = useAppState() 34 - 35 - useEffect(() => { 36 - try { 37 - if (isAppFocused === 'active' && isScreenFocused && !player.playing) { 38 - PlatformInfo.setAudioCategory(AudioCategory.Ambient) 39 - PlatformInfo.setAudioActive(false) 40 - player.muted = true 41 - player.play() 42 - } else if (player.playing) { 43 - player.pause() 44 - } 45 - } catch (err) { 46 - logger.error( 47 - 'Failed to play/pause while backgrounding/switching screens', 48 - {safeMessage: err}, 49 - ) 50 - } 51 - }, [isAppFocused, player, isScreenFocused]) 52 29 53 30 const enterFullscreen = useCallback(() => { 54 31 ref.current?.enterFullscreen() ··· 69 46 player={player} 70 47 style={[a.flex_1, a.rounded_sm]} 71 48 contentFit="contain" 72 - nativeControls={true} 49 + nativeControls={false} 73 50 accessibilityIgnoresInvertColors 74 51 onEnterFullscreen={() => { 75 52 PlatformInfo.setAudioCategory(AudioCategory.Playback) ··· 80 57 PlatformInfo.setAudioCategory(AudioCategory.Ambient) 81 58 PlatformInfo.setAudioActive(false) 82 59 player.muted = true 83 - if (!player.playing) player.play() 60 + if (!player.playing) { 61 + player.play() 62 + } 84 63 }} 85 64 accessibilityLabel={ 86 65 embed.alt ? _(msg`Video: ${embed.alt}`) : _(msg`Video`)