···55@@ -41,6 +41,11 @@ sealed class PlayerEvent {
66 override val name = "playToEnd"
77 }
88-88+99+ data class PlayerTimeRemainingChanged(val timeRemaining: Double): PlayerEvent() {
1010+ override val name = "timeRemainingChange"
1111+ override val arguments = arrayOf(timeRemaining)
···3232 setTimeBarInteractive(requireLinearPlayback)
3333+ setShowSubtitleButton(true)
3434 }
3535-3535+3636 @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
3737@@ -27,7 +28,8 @@ internal fun PlayerView.setTimeBarInteractive(interactive: Boolean) {
3838-3838+3939 @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
4040 internal fun PlayerView.setFullscreenButtonVisibility(visible: Boolean) {
4141- val fullscreenButton = findViewById<android.widget.ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
···144144+ "onEnterFullscreen",
145145+ "onExitFullscreen"
146146 )
147147-147147+148148 Prop("player") { view: VideoView, player: VideoPlayer ->
149149diff --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
150150index 58f00af..5ad8237 100644
···152152+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt
153153@@ -1,5 +1,6 @@
154154 package expo.modules.video
155155-155155+156156+import ProgressTracker
157157 import android.content.Context
158158 import android.view.SurfaceView
···162162 .setLooper(context.mainLooper)
163163 .build()
164164+ var progressTracker: ProgressTracker? = null
165165-165165+166166 val serviceConnection = PlaybackServiceConnection(WeakReference(player))
167167-167167+168168 var playing by IgnoreSameSet(false) { new, old ->
169169 sendEvent(PlayerEvent.IsPlayingChanged(new, old))
170170+ addOrRemoveProgressTracker()
171171 }
172172-172172+173173 var uncommittedSource: VideoSource? = source
174174@@ -141,6 +144,9 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
175175 }
176176-176176+177177 override fun close() {
178178+ this.progressTracker?.remove()
179179+ this.progressTracker = null
···184184@@ -228,7 +234,7 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
185185 listeners.removeAll { it.get() == videoPlayerListener }
186186 }
187187-187187+188188- private fun sendEvent(event: PlayerEvent) {
189189+ fun sendEvent(event: PlayerEvent) {
190190 // Emits to the native listeners
···224224 val onPictureInPictureStop by EventDispatcher<Unit>()
225225+ val onEnterFullscreen by EventDispatcher()
226226+ val onExitFullscreen by EventDispatcher()
227227-227227+228228 var willEnterPiP: Boolean = false
229229 var isInFullscreen: Boolean = false
230230@@ -154,6 +156,7 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap
···234234+ onEnterFullscreen(mapOf())
235235 isInFullscreen = true
236236 }
237237-237237+238238@@ -162,6 +165,7 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap
239239 val fullScreenButton: ImageButton = playerView.findViewById(androidx.media3.ui.R.id.exo_fullscreen)
240240 fullScreenButton.setImageResource(androidx.media3.ui.R.drawable.exo_icon_fullscreen_enter)
···242242+ this.onExitFullscreen(mapOf())
243243 isInFullscreen = false
244244 }
245245-245245+246246diff --git a/node_modules/expo-video/build/VideoPlayer.types.d.ts b/node_modules/expo-video/build/VideoPlayer.types.d.ts
247247-index a09fcfe..5eac9e5 100644
247247+index a09fcfe..46cbae7 100644
248248--- a/node_modules/expo-video/build/VideoPlayer.types.d.ts
249249+++ b/node_modules/expo-video/build/VideoPlayer.types.d.ts
250250@@ -128,6 +128,8 @@ export type VideoPlayerEvents = {
···256256 };
257257 /**
258258 * Describes the current status of the player.
259259+@@ -136,7 +138,7 @@ export type VideoPlayerEvents = {
260260+ * - `readyToPlay`: The player has loaded enough data to start playing or to continue playback.
261261+ * - `error`: The player has encountered an error while loading or playing the video.
262262+ */
263263+-export type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error';
264264++export type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error' | 'waitingToPlayAtSpecifiedRate';
265265+ export type VideoSource = string | {
266266+ /**
267267+ * The URI of the video.
259268diff --git a/node_modules/expo-video/build/VideoView.types.d.ts b/node_modules/expo-video/build/VideoView.types.d.ts
260269index cb9ca6d..ed8bb7e 100644
261270--- a/node_modules/expo-video/build/VideoView.types.d.ts
···270279 }
271280 //# sourceMappingURL=VideoView.types.d.ts.map
272281\ No newline at end of file
282282+diff --git a/node_modules/expo-video/ios/Enums/PlayerStatus.swift b/node_modules/expo-video/ios/Enums/PlayerStatus.swift
283283+index 6af69ca..189fbbe 100644
284284+--- a/node_modules/expo-video/ios/Enums/PlayerStatus.swift
285285++++ b/node_modules/expo-video/ios/Enums/PlayerStatus.swift
286286+@@ -6,5 +6,8 @@ internal enum PlayerStatus: String, Enumerable {
287287+ case idle
288288+ case loading
289289+ case readyToPlay
290290++ case waitingToPlayAtSpecifiedRate
291291++ case unlikeToKeepUp
292292++ case playbackBufferEmpty
293293+ case error
294294+ }
273295diff --git a/node_modules/expo-video/ios/VideoManager.swift b/node_modules/expo-video/ios/VideoManager.swift
274274-index 094a8b0..3f00525 100644
296296+index 094a8b0..16e7081 100644
275297--- a/node_modules/expo-video/ios/VideoManager.swift
276298+++ b/node_modules/expo-video/ios/VideoManager.swift
277299@@ -12,6 +12,7 @@ class VideoManager {
···409431+ "onEnterFullscreen",
410432+ "onExitFullscreen"
411433 )
412412-434434+413435 Prop("player") { (view, player: VideoPlayer?) in
414436diff --git a/node_modules/expo-video/ios/VideoPlayer.swift b/node_modules/expo-video/ios/VideoPlayer.swift
415437index 3315b88..733ab1f 100644
···418440@@ -185,6 +185,10 @@ internal final class VideoPlayer: SharedRef<AVPlayer>, Hashable, VideoPlayerObse
419441 safeEmit(event: "sourceChange", arguments: newVideoPlayerItem?.videoSource, oldVideoPlayerItem?.videoSource)
420442 }
421421-443443+422444+ func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double) {
423445+ safeEmit(event: "timeRemainingChange", arguments: timeRemaining)
424446+ }
···427449 if self.appContext != nil {
428450 self.emit(event: event, arguments: repeat each arguments)
429451diff --git a/node_modules/expo-video/ios/VideoPlayerObserver.swift b/node_modules/expo-video/ios/VideoPlayerObserver.swift
430430-index d289e26..ea4d96f 100644
452452+index d289e26..7de8cbf 100644
431453--- a/node_modules/expo-video/ios/VideoPlayerObserver.swift
432454+++ b/node_modules/expo-video/ios/VideoPlayerObserver.swift
433455@@ -21,6 +21,7 @@ protocol VideoPlayerObserverDelegate: AnyObject {
···436458 func onPlayerItemStatusChanged(player: AVPlayer, oldStatus: AVPlayerItem.Status?, newStatus: AVPlayerItem.Status)
437459+ func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double)
438460 }
439439-461461+440462 // Default implementations for the delegate
441463@@ -33,6 +34,7 @@ extension VideoPlayerObserverDelegate {
442464 func onItemChanged(player: AVPlayer, oldVideoPlayerItem: VideoPlayerItem?, newVideoPlayerItem: VideoPlayerItem?) {}
···444466 func onPlayerItemStatusChanged(player: AVPlayer, oldStatus: AVPlayerItem.Status?, newStatus: AVPlayerItem.Status) {}
445467+ func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double) {}
446468 }
447447-469469+448470 // Wrapper used to store WeakReferences to the observer delegate
449471@@ -91,6 +93,7 @@ class VideoPlayerObserver {
450472 private var playerVolumeObserver: NSKeyValueObservation?
451473 private var playerCurrentItemObserver: NSKeyValueObservation?
452474 private var playerIsMutedObserver: NSKeyValueObservation?
453475+ private var playerPeriodicTimeObserver: Any?
454454-476476+455477 // Current player item observers
456478 private var playbackBufferEmptyObserver: NSKeyValueObservation?
457479@@ -152,6 +155,9 @@ class VideoPlayerObserver {
···462484+ player?.removeTimeObserver(playerPeriodicTimeObserver)
463485+ }
464486 }
465465-487487+466488 private func initializeCurrentPlayerItemObservers(player: AVPlayer, playerItem: AVPlayerItem) {
467467-@@ -270,6 +276,7 @@ class VideoPlayerObserver {
468468-489489+@@ -265,23 +271,24 @@ class VideoPlayerObserver {
490490+ if player.timeControlStatus != .waitingToPlayAtSpecifiedRate && player.status == .readyToPlay && currentItem?.isPlaybackBufferEmpty != true {
491491+ status = .readyToPlay
492492+ } else if player.timeControlStatus == .waitingToPlayAtSpecifiedRate {
493493+- status = .loading
494494++ status = .waitingToPlayAtSpecifiedRate
495495+ }
496496+469497 if isPlaying != (player.timeControlStatus == .playing) {
470498 isPlaying = player.timeControlStatus == .playing
471499+ addPeriodicTimeObserverIfNeeded()
472500 }
473501 }
474474-502502+503503+ private func onIsBufferEmptyChanged(_ playerItem: AVPlayerItem, _ change: NSKeyValueObservedChange<Bool>) {
504504+ if playerItem.isPlaybackBufferEmpty {
505505+- status = .loading
506506++ status = .playbackBufferEmpty
507507+ }
508508+ }
509509+510510+ private func onPlayerLikelyToKeepUpChanged(_ playerItem: AVPlayerItem, _ change: NSKeyValueObservedChange<Bool>) {
511511+ if !playerItem.isPlaybackLikelyToKeepUp && playerItem.isPlaybackBufferEmpty {
512512+- status = .loading
513513++ status = .unlikeToKeepUp
514514+ } else if playerItem.isPlaybackLikelyToKeepUp {
515515+ status = .readyToPlay
516516+ }
475517@@ -310,4 +317,28 @@ class VideoPlayerObserver {
476518 }
477519 }
···506548--- a/node_modules/expo-video/ios/VideoView.swift
507549+++ b/node_modules/expo-video/ios/VideoView.swift
508550@@ -41,6 +41,8 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
509509-551551+510552 let onPictureInPictureStart = EventDispatcher()
511553 let onPictureInPictureStop = EventDispatcher()
512554+ let onEnterFullscreen = EventDispatcher()
513555+ let onExitFullscreen = EventDispatcher()
514514-556556+515557 public override var bounds: CGRect {
516558 didSet {
517559@@ -163,6 +165,7 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
···521563+ onEnterFullscreen()
522564 isFullscreen = true
523565 }
524524-566566+525567@@ -179,6 +182,7 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
526568 if wasPlaying {
527569 self.player?.pointer.play()
···531573 }
532574 }
533575diff --git a/node_modules/expo-video/src/VideoPlayer.types.ts b/node_modules/expo-video/src/VideoPlayer.types.ts
534534-index aaf4b63..f438196 100644
576576+index aaf4b63..5ff6b7a 100644
535577--- a/node_modules/expo-video/src/VideoPlayer.types.ts
536578+++ b/node_modules/expo-video/src/VideoPlayer.types.ts
537579@@ -151,6 +151,8 @@ export type VideoPlayerEvents = {
···541583+
542584+ timeRemainingChange(timeRemaining: number): void;
543585 };
544544-586586+545587 /**
588588+@@ -160,7 +162,7 @@ export type VideoPlayerEvents = {
589589+ * - `readyToPlay`: The player has loaded enough data to start playing or to continue playback.
590590+ * - `error`: The player has encountered an error while loading or playing the video.
591591+ */
592592+-export type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error';
593593++export type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error' | 'waitingToPlayAtSpecifiedRate';
594594+595595+ export type VideoSource =
596596+ | string
546597diff --git a/node_modules/expo-video/src/VideoView.types.ts b/node_modules/expo-video/src/VideoView.types.ts
547598index 29fe5db..e1fbf59 100644
548599--- a/node_modules/expo-video/src/VideoView.types.ts
+13
patches/expo-video+1.2.4.patch.md
···33## `expo-video` Patch
4455### `onEnterFullScreen`/`onExitFullScreen`
66+67Adds two props to `VideoView`: `onEnterFullscreen` and `onExitFullscreen` which do exactly what they say on
78the tin.
89···1617Instead of handling the pausing/playing of videos in React, we'll handle them here. There's some logic that we do not
1718need (around PIP mode) that we can remove, and just pause any playing players on background and then resume them on
1819foreground.
2020+2121+### Additional `statusChange` Events
2222+2323+`expo-video` uses the `loading` status for a variety of cases where the video is not actually "loading". We're making
2424+those status events more specific here, so that we can determine if a video is truly loading or not. These statuses are:
2525+2626+- `waitingToPlayAtSpecifiedRate`
2727+- `unlikelyToKeepUp`
2828+- `playbackBufferEmpty`
2929+3030+It's unlikely we will ever need to pay attention to these statuses, so they are not being include in the TypeScript
3131+types.