[Video] Use `expo-video` from fork (#5159)

zio/stable
Hailey 2024-09-05 08:27:28 -07:00 committed by GitHub
parent 6d8ed5c3c8
commit 93c171b403
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 7 additions and 652 deletions

View File

@ -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: {

View File

@ -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",

View File

@ -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;
}

View File

@ -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.

View File

@ -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

View File

@ -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"