[Video] Use expo-video
from fork (#5159)
This commit is contained in:
parent
6d8ed5c3c8
commit
93c171b403
6 changed files with 7 additions and 652 deletions
|
@ -191,7 +191,7 @@ module.exports = function (config) {
|
||||||
'expo-build-properties',
|
'expo-build-properties',
|
||||||
{
|
{
|
||||||
ios: {
|
ios: {
|
||||||
deploymentTarget: '14.0',
|
deploymentTarget: '15.1',
|
||||||
newArchEnabled: false,
|
newArchEnabled: false,
|
||||||
},
|
},
|
||||||
android: {
|
android: {
|
||||||
|
|
|
@ -139,7 +139,7 @@
|
||||||
"expo-system-ui": "~3.0.4",
|
"expo-system-ui": "~3.0.4",
|
||||||
"expo-task-manager": "~11.8.1",
|
"expo-task-manager": "~11.8.1",
|
||||||
"expo-updates": "~0.25.14",
|
"expo-updates": "~0.25.14",
|
||||||
"expo-video": "^1.2.4",
|
"expo-video": "https://github.com/bluesky-social/expo/raw/main/packages/expo-video/expo-video-v1.2.4.tgz",
|
||||||
"expo-web-browser": "~13.0.3",
|
"expo-web-browser": "~13.0.3",
|
||||||
"fast-text-encoding": "^1.0.6",
|
"fast-text-encoding": "^1.0.6",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
|
|
|
@ -1,608 +0,0 @@
|
||||||
diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerEvent.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerEvent.kt
|
|
||||||
index 473f964..f37aff9 100644
|
|
||||||
--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerEvent.kt
|
|
||||||
+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerEvent.kt
|
|
||||||
@@ -41,6 +41,11 @@ sealed class PlayerEvent {
|
|
||||||
override val name = "playToEnd"
|
|
||||||
}
|
|
||||||
|
|
||||||
+ data class PlayerTimeRemainingChanged(val timeRemaining: Double): PlayerEvent() {
|
|
||||||
+ override val name = "timeRemainingChange"
|
|
||||||
+ override val arguments = arrayOf(timeRemaining)
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
fun emit(player: VideoPlayer, listeners: List<VideoPlayerListener>) {
|
|
||||||
when (this) {
|
|
||||||
is StatusChanged -> listeners.forEach { it.onStatusChanged(player, status, oldStatus, error) }
|
|
||||||
@@ -49,6 +54,7 @@ sealed class PlayerEvent {
|
|
||||||
is SourceChanged -> listeners.forEach { it.onSourceChanged(player, source, oldSource) }
|
|
||||||
is PlaybackRateChanged -> listeners.forEach { it.onPlaybackRateChanged(player, rate, oldRate) }
|
|
||||||
is PlayedToEnd -> listeners.forEach { it.onPlayedToEnd(player) }
|
|
||||||
+ is PlayerTimeRemainingChanged -> listeners.forEach { it.onPlayerTimeRemainingChanged(player, timeRemaining) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerViewExtension.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerViewExtension.kt
|
|
||||||
index 9905e13..47342ff 100644
|
|
||||||
--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerViewExtension.kt
|
|
||||||
+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerViewExtension.kt
|
|
||||||
@@ -11,6 +11,7 @@ internal fun PlayerView.applyRequiresLinearPlayback(requireLinearPlayback: Boole
|
|
||||||
setShowPreviousButton(!requireLinearPlayback)
|
|
||||||
setShowNextButton(!requireLinearPlayback)
|
|
||||||
setTimeBarInteractive(requireLinearPlayback)
|
|
||||||
+ setShowSubtitleButton(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
|
||||||
@@ -27,7 +28,8 @@ internal fun PlayerView.setTimeBarInteractive(interactive: Boolean) {
|
|
||||||
|
|
||||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
|
||||||
internal fun PlayerView.setFullscreenButtonVisibility(visible: Boolean) {
|
|
||||||
- val fullscreenButton = findViewById<android.widget.ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
|
|
||||||
+ val fullscreenButton =
|
|
||||||
+ findViewById<android.widget.ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
|
|
||||||
fullscreenButton?.visibility = if (visible) {
|
|
||||||
android.view.View.VISIBLE
|
|
||||||
} else {
|
|
||||||
diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/ProgressTracker.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/ProgressTracker.kt
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000..0249e23
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/ProgressTracker.kt
|
|
||||||
@@ -0,0 +1,29 @@
|
|
||||||
+import android.os.Handler
|
|
||||||
+import android.os.Looper
|
|
||||||
+import androidx.annotation.OptIn
|
|
||||||
+import androidx.media3.common.util.UnstableApi
|
|
||||||
+import expo.modules.video.PlayerEvent
|
|
||||||
+import expo.modules.video.VideoPlayer
|
|
||||||
+import kotlin.math.floor
|
|
||||||
+
|
|
||||||
+@OptIn(UnstableApi::class)
|
|
||||||
+class ProgressTracker(private val videoPlayer: VideoPlayer) : Runnable {
|
|
||||||
+ private val handler: Handler = Handler(Looper.getMainLooper())
|
|
||||||
+ private val player = videoPlayer.player
|
|
||||||
+
|
|
||||||
+ init {
|
|
||||||
+ handler.post(this)
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ override fun run() {
|
|
||||||
+ val currentPosition = player.currentPosition
|
|
||||||
+ val duration = player.duration
|
|
||||||
+ val timeRemaining = floor(((duration - currentPosition) / 1000).toDouble())
|
|
||||||
+ videoPlayer.sendEvent(PlayerEvent.PlayerTimeRemainingChanged(timeRemaining))
|
|
||||||
+ handler.postDelayed(this, 1000 /* ms */)
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ fun remove() {
|
|
||||||
+ handler.removeCallbacks(this)
|
|
||||||
+ }
|
|
||||||
+}
|
|
||||||
\ 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
|
|
||||||
index ec3da2a..5a1397a 100644
|
|
||||||
--- 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
|
|
||||||
@@ -43,7 +43,9 @@ class VideoModule : Module() {
|
|
||||||
View(VideoView::class) {
|
|
||||||
Events(
|
|
||||||
"onPictureInPictureStart",
|
|
||||||
- "onPictureInPictureStop"
|
|
||||||
+ "onPictureInPictureStop",
|
|
||||||
+ "onEnterFullscreen",
|
|
||||||
+ "onExitFullscreen"
|
|
||||||
)
|
|
||||||
|
|
||||||
Prop("player") { view: VideoView, player: VideoPlayer ->
|
|
||||||
diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt
|
|
||||||
index 58f00af..5ad8237 100644
|
|
||||||
--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt
|
|
||||||
+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt
|
|
||||||
@@ -1,5 +1,6 @@
|
|
||||||
package expo.modules.video
|
|
||||||
|
|
||||||
+import ProgressTracker
|
|
||||||
import android.content.Context
|
|
||||||
import android.view.SurfaceView
|
|
||||||
import androidx.media3.common.MediaItem
|
|
||||||
@@ -35,11 +36,13 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
|
|
||||||
.Builder(context, renderersFactory)
|
|
||||||
.setLooper(context.mainLooper)
|
|
||||||
.build()
|
|
||||||
+ var progressTracker: ProgressTracker? = null
|
|
||||||
|
|
||||||
val serviceConnection = PlaybackServiceConnection(WeakReference(player))
|
|
||||||
|
|
||||||
var playing by IgnoreSameSet(false) { new, old ->
|
|
||||||
sendEvent(PlayerEvent.IsPlayingChanged(new, old))
|
|
||||||
+ addOrRemoveProgressTracker()
|
|
||||||
}
|
|
||||||
|
|
||||||
var uncommittedSource: VideoSource? = source
|
|
||||||
@@ -141,6 +144,9 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
+ this.progressTracker?.remove()
|
|
||||||
+ this.progressTracker = null
|
|
||||||
+
|
|
||||||
appContext?.reactContext?.unbindService(serviceConnection)
|
|
||||||
serviceConnection.playbackServiceBinder?.service?.unregisterPlayer(player)
|
|
||||||
VideoManager.unregisterVideoPlayer(this@VideoPlayer)
|
|
||||||
@@ -228,7 +234,7 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
|
|
||||||
listeners.removeAll { it.get() == videoPlayerListener }
|
|
||||||
}
|
|
||||||
|
|
||||||
- private fun sendEvent(event: PlayerEvent) {
|
|
||||||
+ fun sendEvent(event: PlayerEvent) {
|
|
||||||
// Emits to the native listeners
|
|
||||||
event.emit(this, listeners.mapNotNull { it.get() })
|
|
||||||
// Emits to the JS side
|
|
||||||
@@ -240,4 +246,13 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
|
|
||||||
sendEvent(eventName, *args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+
|
|
||||||
+ private fun addOrRemoveProgressTracker() {
|
|
||||||
+ this.progressTracker?.remove()
|
|
||||||
+ if (this.playing) {
|
|
||||||
+ this.progressTracker = ProgressTracker(this)
|
|
||||||
+ } else {
|
|
||||||
+ this.progressTracker = null
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayerListener.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayerListener.kt
|
|
||||||
index f654254..dcfe3f0 100644
|
|
||||||
--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayerListener.kt
|
|
||||||
+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayerListener.kt
|
|
||||||
@@ -15,4 +15,5 @@ interface VideoPlayerListener {
|
|
||||||
fun onSourceChanged(player: VideoPlayer, source: VideoSource?, oldSource: VideoSource?) {}
|
|
||||||
fun onPlaybackRateChanged(player: VideoPlayer, rate: Float, oldRate: Float?) {}
|
|
||||||
fun onPlayedToEnd(player: VideoPlayer) {}
|
|
||||||
+ fun onPlayerTimeRemainingChanged(player: VideoPlayer, timeRemaining: Double) {}
|
|
||||||
}
|
|
||||||
diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoView.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoView.kt
|
|
||||||
index a951d80..3932535 100644
|
|
||||||
--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoView.kt
|
|
||||||
+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoView.kt
|
|
||||||
@@ -36,6 +36,8 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap
|
|
||||||
val playerView: PlayerView = PlayerView(context.applicationContext)
|
|
||||||
val onPictureInPictureStart by EventDispatcher<Unit>()
|
|
||||||
val onPictureInPictureStop by EventDispatcher<Unit>()
|
|
||||||
+ val onEnterFullscreen by EventDispatcher()
|
|
||||||
+ val onExitFullscreen by EventDispatcher()
|
|
||||||
|
|
||||||
var willEnterPiP: Boolean = false
|
|
||||||
var isInFullscreen: Boolean = false
|
|
||||||
@@ -154,6 +156,7 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
currentActivity.overridePendingTransition(0, 0)
|
|
||||||
}
|
|
||||||
+ onEnterFullscreen(mapOf())
|
|
||||||
isInFullscreen = true
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -162,6 +165,7 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap
|
|
||||||
val fullScreenButton: ImageButton = playerView.findViewById(androidx.media3.ui.R.id.exo_fullscreen)
|
|
||||||
fullScreenButton.setImageResource(androidx.media3.ui.R.drawable.exo_icon_fullscreen_enter)
|
|
||||||
videoPlayer?.changePlayerView(playerView)
|
|
||||||
+ this.onExitFullscreen(mapOf())
|
|
||||||
isInFullscreen = false
|
|
||||||
}
|
|
||||||
|
|
||||||
diff --git a/node_modules/expo-video/build/VideoPlayer.types.d.ts b/node_modules/expo-video/build/VideoPlayer.types.d.ts
|
|
||||||
index a09fcfe..46cbae7 100644
|
|
||||||
--- a/node_modules/expo-video/build/VideoPlayer.types.d.ts
|
|
||||||
+++ b/node_modules/expo-video/build/VideoPlayer.types.d.ts
|
|
||||||
@@ -128,6 +128,8 @@ export type VideoPlayerEvents = {
|
|
||||||
* Handler for an event emitted when the current media source of the player changes.
|
|
||||||
*/
|
|
||||||
sourceChange(newSource: VideoSource, previousSource: VideoSource): void;
|
|
||||||
+
|
|
||||||
+ timeRemainingChange(timeRemaining: number): void;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Describes the current status of the player.
|
|
||||||
@@ -136,7 +138,7 @@ export type VideoPlayerEvents = {
|
|
||||||
* - `readyToPlay`: The player has loaded enough data to start playing or to continue playback.
|
|
||||||
* - `error`: The player has encountered an error while loading or playing the video.
|
|
||||||
*/
|
|
||||||
-export type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error';
|
|
||||||
+export type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error' | 'waitingToPlayAtSpecifiedRate';
|
|
||||||
export type VideoSource = string | {
|
|
||||||
/**
|
|
||||||
* The URI of the video.
|
|
||||||
diff --git a/node_modules/expo-video/build/VideoView.types.d.ts b/node_modules/expo-video/build/VideoView.types.d.ts
|
|
||||||
index cb9ca6d..ed8bb7e 100644
|
|
||||||
--- a/node_modules/expo-video/build/VideoView.types.d.ts
|
|
||||||
+++ b/node_modules/expo-video/build/VideoView.types.d.ts
|
|
||||||
@@ -89,5 +89,8 @@ export interface VideoViewProps extends ViewProps {
|
|
||||||
* @platform ios 16.0+
|
|
||||||
*/
|
|
||||||
allowsVideoFrameAnalysis?: boolean;
|
|
||||||
+
|
|
||||||
+ onEnterFullscreen?: () => void;
|
|
||||||
+ onExitFullscreen?: () => void;
|
|
||||||
}
|
|
||||||
//# sourceMappingURL=VideoView.types.d.ts.map
|
|
||||||
\ No newline at end of file
|
|
||||||
diff --git a/node_modules/expo-video/ios/Enums/PlayerStatus.swift b/node_modules/expo-video/ios/Enums/PlayerStatus.swift
|
|
||||||
index 6af69ca..189fbbe 100644
|
|
||||||
--- a/node_modules/expo-video/ios/Enums/PlayerStatus.swift
|
|
||||||
+++ b/node_modules/expo-video/ios/Enums/PlayerStatus.swift
|
|
||||||
@@ -6,5 +6,8 @@ internal enum PlayerStatus: String, Enumerable {
|
|
||||||
case idle
|
|
||||||
case loading
|
|
||||||
case readyToPlay
|
|
||||||
+ case waitingToPlayAtSpecifiedRate
|
|
||||||
+ case unlikeToKeepUp
|
|
||||||
+ case playbackBufferEmpty
|
|
||||||
case error
|
|
||||||
}
|
|
||||||
diff --git a/node_modules/expo-video/ios/VideoManager.swift b/node_modules/expo-video/ios/VideoManager.swift
|
|
||||||
index 094a8b0..16e7081 100644
|
|
||||||
--- a/node_modules/expo-video/ios/VideoManager.swift
|
|
||||||
+++ b/node_modules/expo-video/ios/VideoManager.swift
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||
internal func setAppropriateAudioSessionOrWarn() {
|
|
||||||
- let audioSession = AVAudioSession.sharedInstance()
|
|
||||||
- var audioSessionCategoryOptions: AVAudioSession.CategoryOptions = []
|
|
||||||
-
|
|
||||||
- let isAnyPlayerPlaying = videoPlayers.allObjects.contains { player in
|
|
||||||
- player.isPlaying
|
|
||||||
- }
|
|
||||||
- let areAllPlayersMuted = videoPlayers.allObjects.allSatisfy { player in
|
|
||||||
- player.isMuted
|
|
||||||
- }
|
|
||||||
- let needsPiPSupport = videoViews.allObjects.contains { view in
|
|
||||||
- view.allowPictureInPicture
|
|
||||||
- }
|
|
||||||
- let anyPlayerShowsNotification = videoPlayers.allObjects.contains { player in
|
|
||||||
- player.showNowPlayingNotification
|
|
||||||
- }
|
|
||||||
- // The notification won't be shown if we allow the audio to mix with others
|
|
||||||
- let shouldAllowMixing = (!isAnyPlayerPlaying || areAllPlayersMuted) && !anyPlayerShowsNotification
|
|
||||||
- let isOutputtingAudio = !areAllPlayersMuted && isAnyPlayerPlaying
|
|
||||||
- let shouldUpdateToAllowMixing = !audioSession.categoryOptions.contains(.mixWithOthers) && shouldAllowMixing
|
|
||||||
-
|
|
||||||
- if shouldAllowMixing {
|
|
||||||
- audioSessionCategoryOptions.insert(.mixWithOthers)
|
|
||||||
- }
|
|
||||||
-
|
|
||||||
- if isOutputtingAudio || needsPiPSupport || shouldUpdateToAllowMixing || anyPlayerShowsNotification {
|
|
||||||
- do {
|
|
||||||
- try audioSession.setCategory(.playback, mode: .moviePlayback)
|
|
||||||
- } catch {
|
|
||||||
- log.warn("Failed to set audio session category. This might cause issues with audio playback and Picture in Picture. \(error.localizedDescription)")
|
|
||||||
- }
|
|
||||||
- }
|
|
||||||
-
|
|
||||||
- // Make sure audio session is active if any video is playing
|
|
||||||
- if isAnyPlayerPlaying {
|
|
||||||
- do {
|
|
||||||
- try audioSession.setActive(true)
|
|
||||||
- } catch {
|
|
||||||
- log.warn("Failed to activate the audio session. This might cause issues with audio playback. \(error.localizedDescription)")
|
|
||||||
- }
|
|
||||||
- }
|
|
||||||
+// let audioSession = AVAudioSession.sharedInstance()
|
|
||||||
+// var audioSessionCategoryOptions: AVAudioSession.CategoryOptions = []
|
|
||||||
+//
|
|
||||||
+// let isAnyPlayerPlaying = videoPlayers.allObjects.contains { player in
|
|
||||||
+// player.isPlaying
|
|
||||||
+// }
|
|
||||||
+// let areAllPlayersMuted = videoPlayers.allObjects.allSatisfy { player in
|
|
||||||
+// player.isMuted
|
|
||||||
+// }
|
|
||||||
+// let needsPiPSupport = videoViews.allObjects.contains { view in
|
|
||||||
+// view.allowPictureInPicture
|
|
||||||
+// }
|
|
||||||
+// let anyPlayerShowsNotification = videoPlayers.allObjects.contains { player in
|
|
||||||
+// player.showNowPlayingNotification
|
|
||||||
+// }
|
|
||||||
+// // The notification won't be shown if we allow the audio to mix with others
|
|
||||||
+// let shouldAllowMixing = (!isAnyPlayerPlaying || areAllPlayersMuted) && !anyPlayerShowsNotification
|
|
||||||
+// let isOutputtingAudio = !areAllPlayersMuted && isAnyPlayerPlaying
|
|
||||||
+// let shouldUpdateToAllowMixing = !audioSession.categoryOptions.contains(.mixWithOthers) && shouldAllowMixing
|
|
||||||
+//
|
|
||||||
+// if shouldAllowMixing {
|
|
||||||
+// audioSessionCategoryOptions.insert(.mixWithOthers)
|
|
||||||
+// }
|
|
||||||
+//
|
|
||||||
+// if isOutputtingAudio || needsPiPSupport || shouldUpdateToAllowMixing || anyPlayerShowsNotification {
|
|
||||||
+// do {
|
|
||||||
+// try audioSession.setCategory(.playback, mode: .moviePlayback)
|
|
||||||
+// } catch {
|
|
||||||
+// log.warn("Failed to set audio session category. This might cause issues with audio playback and Picture in Picture. \(error.localizedDescription)")
|
|
||||||
+// }
|
|
||||||
+// }
|
|
||||||
+//
|
|
||||||
+// // Make sure audio session is active if any video is playing
|
|
||||||
+// if isAnyPlayerPlaying {
|
|
||||||
+// do {
|
|
||||||
+// try audioSession.setActive(true)
|
|
||||||
+// } catch {
|
|
||||||
+// log.warn("Failed to activate the audio session. This might cause issues with audio playback. \(error.localizedDescription)")
|
|
||||||
+// }
|
|
||||||
+// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
diff --git a/node_modules/expo-video/ios/VideoModule.swift b/node_modules/expo-video/ios/VideoModule.swift
|
|
||||||
index c537a12..e4a918f 100644
|
|
||||||
--- a/node_modules/expo-video/ios/VideoModule.swift
|
|
||||||
+++ b/node_modules/expo-video/ios/VideoModule.swift
|
|
||||||
@@ -16,7 +16,9 @@ public final class VideoModule: Module {
|
|
||||||
View(VideoView.self) {
|
|
||||||
Events(
|
|
||||||
"onPictureInPictureStart",
|
|
||||||
- "onPictureInPictureStop"
|
|
||||||
+ "onPictureInPictureStop",
|
|
||||||
+ "onEnterFullscreen",
|
|
||||||
+ "onExitFullscreen"
|
|
||||||
)
|
|
||||||
|
|
||||||
Prop("player") { (view, player: VideoPlayer?) in
|
|
||||||
diff --git a/node_modules/expo-video/ios/VideoPlayer.swift b/node_modules/expo-video/ios/VideoPlayer.swift
|
|
||||||
index 3315b88..733ab1f 100644
|
|
||||||
--- a/node_modules/expo-video/ios/VideoPlayer.swift
|
|
||||||
+++ b/node_modules/expo-video/ios/VideoPlayer.swift
|
|
||||||
@@ -185,6 +185,10 @@ internal final class VideoPlayer: SharedRef<AVPlayer>, Hashable, VideoPlayerObse
|
|
||||||
safeEmit(event: "sourceChange", arguments: newVideoPlayerItem?.videoSource, oldVideoPlayerItem?.videoSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
+ func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double) {
|
|
||||||
+ safeEmit(event: "timeRemainingChange", arguments: timeRemaining)
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
func safeEmit<each A: AnyArgument>(event: String, arguments: repeat each A) {
|
|
||||||
if self.appContext != nil {
|
|
||||||
self.emit(event: event, arguments: repeat each arguments)
|
|
||||||
diff --git a/node_modules/expo-video/ios/VideoPlayerObserver.swift b/node_modules/expo-video/ios/VideoPlayerObserver.swift
|
|
||||||
index d289e26..7de8cbf 100644
|
|
||||||
--- a/node_modules/expo-video/ios/VideoPlayerObserver.swift
|
|
||||||
+++ b/node_modules/expo-video/ios/VideoPlayerObserver.swift
|
|
||||||
@@ -21,6 +21,7 @@ protocol VideoPlayerObserverDelegate: AnyObject {
|
|
||||||
func onItemChanged(player: AVPlayer, oldVideoPlayerItem: VideoPlayerItem?, newVideoPlayerItem: VideoPlayerItem?)
|
|
||||||
func onIsMutedChanged(player: AVPlayer, oldIsMuted: Bool?, newIsMuted: Bool)
|
|
||||||
func onPlayerItemStatusChanged(player: AVPlayer, oldStatus: AVPlayerItem.Status?, newStatus: AVPlayerItem.Status)
|
|
||||||
+ func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default implementations for the delegate
|
|
||||||
@@ -33,6 +34,7 @@ extension VideoPlayerObserverDelegate {
|
|
||||||
func onItemChanged(player: AVPlayer, oldVideoPlayerItem: VideoPlayerItem?, newVideoPlayerItem: VideoPlayerItem?) {}
|
|
||||||
func onIsMutedChanged(player: AVPlayer, oldIsMuted: Bool?, newIsMuted: Bool) {}
|
|
||||||
func onPlayerItemStatusChanged(player: AVPlayer, oldStatus: AVPlayerItem.Status?, newStatus: AVPlayerItem.Status) {}
|
|
||||||
+ func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrapper used to store WeakReferences to the observer delegate
|
|
||||||
@@ -91,6 +93,7 @@ class VideoPlayerObserver {
|
|
||||||
private var playerVolumeObserver: NSKeyValueObservation?
|
|
||||||
private var playerCurrentItemObserver: NSKeyValueObservation?
|
|
||||||
private var playerIsMutedObserver: NSKeyValueObservation?
|
|
||||||
+ private var playerPeriodicTimeObserver: Any?
|
|
||||||
|
|
||||||
// Current player item observers
|
|
||||||
private var playbackBufferEmptyObserver: NSKeyValueObservation?
|
|
||||||
@@ -152,6 +155,9 @@ class VideoPlayerObserver {
|
|
||||||
playerVolumeObserver?.invalidate()
|
|
||||||
playerIsMutedObserver?.invalidate()
|
|
||||||
playerCurrentItemObserver?.invalidate()
|
|
||||||
+ if let playerPeriodicTimeObserver = self.playerPeriodicTimeObserver {
|
|
||||||
+ player?.removeTimeObserver(playerPeriodicTimeObserver)
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
|
|
||||||
private func initializeCurrentPlayerItemObservers(player: AVPlayer, playerItem: AVPlayerItem) {
|
|
||||||
@@ -265,23 +271,24 @@ class VideoPlayerObserver {
|
|
||||||
if player.timeControlStatus != .waitingToPlayAtSpecifiedRate && player.status == .readyToPlay && currentItem?.isPlaybackBufferEmpty != true {
|
|
||||||
status = .readyToPlay
|
|
||||||
} else if player.timeControlStatus == .waitingToPlayAtSpecifiedRate {
|
|
||||||
- status = .loading
|
|
||||||
+ status = .waitingToPlayAtSpecifiedRate
|
|
||||||
}
|
|
||||||
|
|
||||||
if isPlaying != (player.timeControlStatus == .playing) {
|
|
||||||
isPlaying = player.timeControlStatus == .playing
|
|
||||||
+ addPeriodicTimeObserverIfNeeded()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func onIsBufferEmptyChanged(_ playerItem: AVPlayerItem, _ change: NSKeyValueObservedChange<Bool>) {
|
|
||||||
if playerItem.isPlaybackBufferEmpty {
|
|
||||||
- status = .loading
|
|
||||||
+ status = .playbackBufferEmpty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func onPlayerLikelyToKeepUpChanged(_ playerItem: AVPlayerItem, _ change: NSKeyValueObservedChange<Bool>) {
|
|
||||||
if !playerItem.isPlaybackLikelyToKeepUp && playerItem.isPlaybackBufferEmpty {
|
|
||||||
- status = .loading
|
|
||||||
+ status = .unlikeToKeepUp
|
|
||||||
} else if playerItem.isPlaybackLikelyToKeepUp {
|
|
||||||
status = .readyToPlay
|
|
||||||
}
|
|
||||||
@@ -310,4 +317,28 @@ class VideoPlayerObserver {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+
|
|
||||||
+ private func onPlayerTimeRemainingChanged(_ player: AVPlayer, _ timeRemaining: Double) {
|
|
||||||
+ delegates.forEach { delegate in
|
|
||||||
+ delegate.value?.onPlayerTimeRemainingChanged(player: player, timeRemaining: timeRemaining)
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ private func addPeriodicTimeObserverIfNeeded() {
|
|
||||||
+ guard self.playerPeriodicTimeObserver == nil, let player = self.player else {
|
|
||||||
+ return
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ if isPlaying {
|
|
||||||
+ // Add the time update listener
|
|
||||||
+ playerPeriodicTimeObserver = player.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1.0, preferredTimescale: Int32(NSEC_PER_SEC)), queue: nil) { event in
|
|
||||||
+ guard let duration = player.currentItem?.duration else {
|
|
||||||
+ return
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ let timeRemaining = (duration.seconds - event.seconds).rounded()
|
|
||||||
+ self.onPlayerTimeRemainingChanged(player, timeRemaining)
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
diff --git a/node_modules/expo-video/ios/VideoView.swift b/node_modules/expo-video/ios/VideoView.swift
|
|
||||||
index f4579e4..10c5908 100644
|
|
||||||
--- a/node_modules/expo-video/ios/VideoView.swift
|
|
||||||
+++ b/node_modules/expo-video/ios/VideoView.swift
|
|
||||||
@@ -41,6 +41,8 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
|
|
||||||
|
|
||||||
let onPictureInPictureStart = EventDispatcher()
|
|
||||||
let onPictureInPictureStop = EventDispatcher()
|
|
||||||
+ let onEnterFullscreen = EventDispatcher()
|
|
||||||
+ let onExitFullscreen = EventDispatcher()
|
|
||||||
|
|
||||||
public override var bounds: CGRect {
|
|
||||||
didSet {
|
|
||||||
@@ -163,6 +165,7 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
|
|
||||||
_ playerViewController: AVPlayerViewController,
|
|
||||||
willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator
|
|
||||||
) {
|
|
||||||
+ onEnterFullscreen()
|
|
||||||
isFullscreen = true
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -179,6 +182,7 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
|
|
||||||
if wasPlaying {
|
|
||||||
self.player?.pointer.play()
|
|
||||||
}
|
|
||||||
+ self.onExitFullscreen()
|
|
||||||
self.isFullscreen = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
diff --git a/node_modules/expo-video/src/VideoPlayer.types.ts b/node_modules/expo-video/src/VideoPlayer.types.ts
|
|
||||||
index aaf4b63..5ff6b7a 100644
|
|
||||||
--- a/node_modules/expo-video/src/VideoPlayer.types.ts
|
|
||||||
+++ b/node_modules/expo-video/src/VideoPlayer.types.ts
|
|
||||||
@@ -151,6 +151,8 @@ export type VideoPlayerEvents = {
|
|
||||||
* Handler for an event emitted when the current media source of the player changes.
|
|
||||||
*/
|
|
||||||
sourceChange(newSource: VideoSource, previousSource: VideoSource): void;
|
|
||||||
+
|
|
||||||
+ timeRemainingChange(timeRemaining: number): void;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
@@ -160,7 +162,7 @@ export type VideoPlayerEvents = {
|
|
||||||
* - `readyToPlay`: The player has loaded enough data to start playing or to continue playback.
|
|
||||||
* - `error`: The player has encountered an error while loading or playing the video.
|
|
||||||
*/
|
|
||||||
-export type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error';
|
|
||||||
+export type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error' | 'waitingToPlayAtSpecifiedRate';
|
|
||||||
|
|
||||||
export type VideoSource =
|
|
||||||
| string
|
|
||||||
diff --git a/node_modules/expo-video/src/VideoView.types.ts b/node_modules/expo-video/src/VideoView.types.ts
|
|
||||||
index 29fe5db..e1fbf59 100644
|
|
||||||
--- a/node_modules/expo-video/src/VideoView.types.ts
|
|
||||||
+++ b/node_modules/expo-video/src/VideoView.types.ts
|
|
||||||
@@ -100,4 +100,7 @@ export interface VideoViewProps extends ViewProps {
|
|
||||||
* @platform ios 16.0+
|
|
||||||
*/
|
|
||||||
allowsVideoFrameAnalysis?: boolean;
|
|
||||||
+
|
|
||||||
+ onEnterFullscreen?: () => void;
|
|
||||||
+ onExitFullscreen?: () => void;
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
## uwu woad beawing, do not wemove
|
|
||||||
|
|
||||||
## `expo-video` Patch
|
|
||||||
|
|
||||||
### `onEnterFullScreen`/`onExitFullScreen`
|
|
||||||
|
|
||||||
Adds two props to `VideoView`: `onEnterFullscreen` and `onExitFullscreen` which do exactly what they say on
|
|
||||||
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
|
|
||||||
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.
|
|
||||||
|
|
||||||
### Additional `statusChange` Events
|
|
||||||
|
|
||||||
`expo-video` uses the `loading` status for a variety of cases where the video is not actually "loading". We're making
|
|
||||||
those status events more specific here, so that we can determine if a video is truly loading or not. These statuses are:
|
|
||||||
|
|
||||||
- `waitingToPlayAtSpecifiedRate`
|
|
||||||
- `unlikelyToKeepUp`
|
|
||||||
- `playbackBufferEmpty`
|
|
||||||
|
|
||||||
It's unlikely we will ever need to pay attention to these statuses, so they are not being include in the TypeScript
|
|
||||||
types.
|
|
|
@ -56,13 +56,13 @@ export function VideoEmbedInnerNative({
|
||||||
contentFit="cover"
|
contentFit="cover"
|
||||||
nativeControls={isFullscreen}
|
nativeControls={isFullscreen}
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
onEnterFullscreen={() => {
|
onFullscreenEnter={() => {
|
||||||
PlatformInfo.setAudioCategory(AudioCategory.Playback)
|
PlatformInfo.setAudioCategory(AudioCategory.Playback)
|
||||||
PlatformInfo.setAudioActive(true)
|
PlatformInfo.setAudioActive(true)
|
||||||
player.muted = false
|
player.muted = false
|
||||||
setIsFullscreen(true)
|
setIsFullscreen(true)
|
||||||
}}
|
}}
|
||||||
onExitFullscreen={() => {
|
onFullscreenExit={() => {
|
||||||
PlatformInfo.setAudioCategory(AudioCategory.Ambient)
|
PlatformInfo.setAudioCategory(AudioCategory.Ambient)
|
||||||
PlatformInfo.setAudioActive(false)
|
PlatformInfo.setAudioActive(false)
|
||||||
player.muted = true
|
player.muted = true
|
||||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -9917,12 +9917,7 @@ caniuse-api@^3.0.0:
|
||||||
lodash.memoize "^4.1.2"
|
lodash.memoize "^4.1.2"
|
||||||
lodash.uniq "^4.5.0"
|
lodash.uniq "^4.5.0"
|
||||||
|
|
||||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001520:
|
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001520, caniuse-lite@^1.0.30001587:
|
||||||
version "1.0.30001655"
|
|
||||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz"
|
|
||||||
integrity sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==
|
|
||||||
|
|
||||||
caniuse-lite@^1.0.30001587:
|
|
||||||
version "1.0.30001655"
|
version "1.0.30001655"
|
||||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz"
|
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz"
|
||||||
integrity sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==
|
integrity sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==
|
||||||
|
@ -12419,10 +12414,9 @@ expo-updates@~0.25.14:
|
||||||
ignore "^5.3.1"
|
ignore "^5.3.1"
|
||||||
resolve-from "^5.0.0"
|
resolve-from "^5.0.0"
|
||||||
|
|
||||||
expo-video@^1.2.4:
|
"expo-video@https://github.com/bluesky-social/expo/raw/main/packages/expo-video/expo-video-v1.2.4.tgz":
|
||||||
version "1.2.4"
|
version "1.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/expo-video/-/expo-video-1.2.4.tgz#787342aded4295a1b6864f59227d178b93e1bb53"
|
resolved "https://github.com/bluesky-social/expo/raw/main/packages/expo-video/expo-video-v1.2.4.tgz#ebb6a672a385f9a059ca614fd148f7803d4267fc"
|
||||||
integrity sha512-pBK9mt7vYAbuPQjCSQxHQ7xrNjbmRheJep7JIStEg57O183/JRfP2blKuXniiSt1HBdZYPdoQnGRa3jGMXB9pg==
|
|
||||||
|
|
||||||
expo-web-browser@~13.0.3:
|
expo-web-browser@~13.0.3:
|
||||||
version "13.0.3"
|
version "13.0.3"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue