[Video] Manage foreground/background playback on the native side (#5104)
parent
05e61346b8
commit
dde72b48e1
|
@ -80,6 +80,57 @@ index 0000000..0249e23
|
||||||
+ }
|
+ }
|
||||||
+}
|
+}
|
||||||
\ No newline at end of file
|
\ No newline at end of file
|
||||||
|
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
|
||||||
|
index 4b6c6d8..e20f51a 100644
|
||||||
|
--- 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
|
||||||
|
@@ -1,5 +1,6 @@
|
||||||
|
package expo.modules.video
|
||||||
|
|
||||||
|
+import android.provider.MediaStore.Video
|
||||||
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import expo.modules.kotlin.AppContext
|
||||||
|
@@ -15,6 +16,8 @@ object VideoManager {
|
||||||
|
// Keeps track of all existing VideoPlayers, and whether they are attached to a VideoView
|
||||||
|
private var videoPlayersToVideoViews = mutableMapOf<VideoPlayer, MutableList<VideoView>>()
|
||||||
|
|
||||||
|
+ private var previouslyPlayingViews: MutableList<VideoView>? = null
|
||||||
|
+
|
||||||
|
private lateinit var audioFocusManager: AudioFocusManager
|
||||||
|
|
||||||
|
fun onModuleCreated(appContext: AppContext) {
|
||||||
|
@@ -69,16 +72,24 @@ object VideoManager {
|
||||||
|
return videoPlayersToVideoViews[videoPlayer]?.isNotEmpty() ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
- fun onAppForegrounded() = Unit
|
||||||
|
+ fun onAppForegrounded() {
|
||||||
|
+ val previouslyPlayingViews = this.previouslyPlayingViews ?: return
|
||||||
|
+ for (videoView in previouslyPlayingViews) {
|
||||||
|
+ val player = videoView.videoPlayer?.player ?: continue
|
||||||
|
+ player.play()
|
||||||
|
+ }
|
||||||
|
+ this.previouslyPlayingViews = null
|
||||||
|
+ }
|
||||||
|
|
||||||
|
fun onAppBackgrounded() {
|
||||||
|
+ val previouslyPlayingViews = mutableListOf<VideoView>()
|
||||||
|
for (videoView in videoViews.values) {
|
||||||
|
- if (videoView.videoPlayer?.staysActiveInBackground == false &&
|
||||||
|
- !videoView.willEnterPiP &&
|
||||||
|
- !videoView.isInFullscreen
|
||||||
|
- ) {
|
||||||
|
- videoView.videoPlayer?.player?.pause()
|
||||||
|
+ val player = videoView.videoPlayer?.player ?: continue
|
||||||
|
+ if (player.isPlaying) {
|
||||||
|
+ player.pause()
|
||||||
|
+ previouslyPlayingViews.add(videoView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ this.previouslyPlayingViews = previouslyPlayingViews
|
||||||
|
}
|
||||||
|
}
|
||||||
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
|
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
|
||||||
index ec3da2a..5a1397a 100644
|
index ec3da2a..5a1397a 100644
|
||||||
--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt
|
--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt
|
||||||
|
@ -220,12 +271,48 @@ index cb9ca6d..ed8bb7e 100644
|
||||||
//# sourceMappingURL=VideoView.types.d.ts.map
|
//# sourceMappingURL=VideoView.types.d.ts.map
|
||||||
\ No newline at end of file
|
\ No newline at end of file
|
||||||
diff --git a/node_modules/expo-video/ios/VideoManager.swift b/node_modules/expo-video/ios/VideoManager.swift
|
diff --git a/node_modules/expo-video/ios/VideoManager.swift b/node_modules/expo-video/ios/VideoManager.swift
|
||||||
index 094a8b0..412fd0c 100644
|
index 094a8b0..3f00525 100644
|
||||||
--- a/node_modules/expo-video/ios/VideoManager.swift
|
--- a/node_modules/expo-video/ios/VideoManager.swift
|
||||||
+++ b/node_modules/expo-video/ios/VideoManager.swift
|
+++ b/node_modules/expo-video/ios/VideoManager.swift
|
||||||
@@ -51,45 +51,45 @@ class VideoManager {
|
@@ -12,6 +12,7 @@ class VideoManager {
|
||||||
|
|
||||||
|
private var videoViews = NSHashTable<VideoView>.weakObjects()
|
||||||
|
private var videoPlayers = NSHashTable<VideoPlayer>.weakObjects()
|
||||||
|
+ private var previouslyPlayingPlayers: [VideoPlayer]?
|
||||||
|
|
||||||
|
func register(videoPlayer: VideoPlayer) {
|
||||||
|
videoPlayers.add(videoPlayer)
|
||||||
|
@@ -33,63 +34,70 @@ class VideoManager {
|
||||||
|
for videoPlayer in videoPlayers.allObjects {
|
||||||
|
videoPlayer.setTracksEnabled(true)
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ if let previouslyPlayingPlayers = self.previouslyPlayingPlayers {
|
||||||
|
+ previouslyPlayingPlayers.forEach { player in
|
||||||
|
+ player.pointer.play()
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
func onAppBackgrounded() {
|
||||||
|
+ var previouslyPlayingPlayers: [VideoPlayer] = []
|
||||||
|
for videoView in videoViews.allObjects {
|
||||||
|
guard let player = videoView.player else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
- if player.staysActiveInBackground == true {
|
||||||
|
- player.setTracksEnabled(videoView.isInPictureInPicture)
|
||||||
|
- } else if !videoView.isInPictureInPicture {
|
||||||
|
+ if player.isPlaying {
|
||||||
|
player.pointer.pause()
|
||||||
|
+ previouslyPlayingPlayers.append(player)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ self.previouslyPlayingPlayers = previouslyPlayingPlayers
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Audio Session Management
|
// MARK: - Audio Session Management
|
||||||
|
|
||||||
internal func setAppropriateAudioSessionOrWarn() {
|
internal func setAppropriateAudioSessionOrWarn() {
|
||||||
- let audioSession = AVAudioSession.sharedInstance()
|
- let audioSession = AVAudioSession.sharedInstance()
|
||||||
- var audioSessionCategoryOptions: AVAudioSession.CategoryOptions = []
|
- var audioSessionCategoryOptions: AVAudioSession.CategoryOptions = []
|
||||||
|
|
|
@ -2,8 +2,17 @@
|
||||||
|
|
||||||
## `expo-video` Patch
|
## `expo-video` Patch
|
||||||
|
|
||||||
This patch adds two props to `VideoView`: `onEnterFullscreen` and `onExitFullscreen` which do exactly what they say on
|
### `onEnterFullScreen`/`onExitFullScreen`
|
||||||
|
Adds two props to `VideoView`: `onEnterFullscreen` and `onExitFullscreen` which do exactly what they say on
|
||||||
the tin.
|
the tin.
|
||||||
|
|
||||||
|
### Removing audio session management
|
||||||
|
|
||||||
This patch also removes the audio session management that Expo does on its own, as we handle audio session management
|
This patch also removes the audio session management that Expo does on its own, as we handle audio session management
|
||||||
ourselves.
|
ourselves.
|
||||||
|
|
||||||
|
### Pausing/playing on background/foreground
|
||||||
|
|
||||||
|
Instead of handling the pausing/playing of videos in React, we'll handle them here. There's some logic that we do not
|
||||||
|
need (around PIP mode) that we can remove, and just pause any playing players on background and then resume them on
|
||||||
|
foreground.
|
||||||
|
|
|
@ -5,12 +5,9 @@ import {VideoPlayer, VideoView} from 'expo-video'
|
||||||
import {AppBskyEmbedVideo} from '@atproto/api'
|
import {AppBskyEmbedVideo} from '@atproto/api'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useIsFocused} from '@react-navigation/native'
|
|
||||||
|
|
||||||
import {HITSLOP_30} from '#/lib/constants'
|
import {HITSLOP_30} from '#/lib/constants'
|
||||||
import {useAppState} from '#/lib/hooks/useAppState'
|
|
||||||
import {clamp} from '#/lib/numbers'
|
import {clamp} from '#/lib/numbers'
|
||||||
import {logger} from '#/logger'
|
|
||||||
import {useActiveVideoNative} from 'view/com/util/post-embeds/ActiveVideoNativeContext'
|
import {useActiveVideoNative} from 'view/com/util/post-embeds/ActiveVideoNativeContext'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
|
import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
|
||||||
|
@ -29,26 +26,6 @@ export function VideoEmbedInnerNative({
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {player} = useActiveVideoNative()
|
const {player} = useActiveVideoNative()
|
||||||
const ref = useRef<VideoView>(null)
|
const ref = useRef<VideoView>(null)
|
||||||
const isScreenFocused = useIsFocused()
|
|
||||||
const isAppFocused = useAppState()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
try {
|
|
||||||
if (isAppFocused === 'active' && isScreenFocused && !player.playing) {
|
|
||||||
PlatformInfo.setAudioCategory(AudioCategory.Ambient)
|
|
||||||
PlatformInfo.setAudioActive(false)
|
|
||||||
player.muted = true
|
|
||||||
player.play()
|
|
||||||
} else if (player.playing) {
|
|
||||||
player.pause()
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(
|
|
||||||
'Failed to play/pause while backgrounding/switching screens',
|
|
||||||
{safeMessage: err},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}, [isAppFocused, player, isScreenFocused])
|
|
||||||
|
|
||||||
const enterFullscreen = useCallback(() => {
|
const enterFullscreen = useCallback(() => {
|
||||||
ref.current?.enterFullscreen()
|
ref.current?.enterFullscreen()
|
||||||
|
@ -69,7 +46,7 @@ export function VideoEmbedInnerNative({
|
||||||
player={player}
|
player={player}
|
||||||
style={[a.flex_1, a.rounded_sm]}
|
style={[a.flex_1, a.rounded_sm]}
|
||||||
contentFit="contain"
|
contentFit="contain"
|
||||||
nativeControls={true}
|
nativeControls={false}
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
onEnterFullscreen={() => {
|
onEnterFullscreen={() => {
|
||||||
PlatformInfo.setAudioCategory(AudioCategory.Playback)
|
PlatformInfo.setAudioCategory(AudioCategory.Playback)
|
||||||
|
@ -80,7 +57,9 @@ export function VideoEmbedInnerNative({
|
||||||
PlatformInfo.setAudioCategory(AudioCategory.Ambient)
|
PlatformInfo.setAudioCategory(AudioCategory.Ambient)
|
||||||
PlatformInfo.setAudioActive(false)
|
PlatformInfo.setAudioActive(false)
|
||||||
player.muted = true
|
player.muted = true
|
||||||
if (!player.playing) player.play()
|
if (!player.playing) {
|
||||||
|
player.play()
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
accessibilityLabel={
|
accessibilityLabel={
|
||||||
embed.alt ? _(msg`Video: ${embed.alt}`) : _(msg`Video`)
|
embed.alt ? _(msg`Video: ${embed.alt}`) : _(msg`Video`)
|
||||||
|
|
Loading…
Reference in New Issue