···11-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
22-index 473f964..f37aff9 100644
33---- a/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerEvent.kt
44-+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerEvent.kt
55-@@ -41,6 +41,11 @@ sealed class PlayerEvent {
66- override val name = "playToEnd"
77- }
88-99-+ data class PlayerTimeRemainingChanged(val timeRemaining: Double): PlayerEvent() {
1010-+ override val name = "timeRemainingChange"
1111-+ override val arguments = arrayOf(timeRemaining)
1212-+ }
1313-+
1414- fun emit(player: VideoPlayer, listeners: List<VideoPlayerListener>) {
1515- when (this) {
1616- is StatusChanged -> listeners.forEach { it.onStatusChanged(player, status, oldStatus, error) }
1717-@@ -49,6 +54,7 @@ sealed class PlayerEvent {
1818- is SourceChanged -> listeners.forEach { it.onSourceChanged(player, source, oldSource) }
1919- is PlaybackRateChanged -> listeners.forEach { it.onPlaybackRateChanged(player, rate, oldRate) }
2020- is PlayedToEnd -> listeners.forEach { it.onPlayedToEnd(player) }
2121-+ is PlayerTimeRemainingChanged -> listeners.forEach { it.onPlayerTimeRemainingChanged(player, timeRemaining) }
2222- }
2323- }
2424- }
2525-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
2626-index 9905e13..47342ff 100644
2727---- a/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerViewExtension.kt
2828-+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerViewExtension.kt
2929-@@ -11,6 +11,7 @@ internal fun PlayerView.applyRequiresLinearPlayback(requireLinearPlayback: Boole
3030- setShowPreviousButton(!requireLinearPlayback)
3131- setShowNextButton(!requireLinearPlayback)
3232- setTimeBarInteractive(requireLinearPlayback)
3333-+ setShowSubtitleButton(true)
3434- }
3535-3636- @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
3737-@@ -27,7 +28,8 @@ internal fun PlayerView.setTimeBarInteractive(interactive: Boolean) {
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)
4242-+ val fullscreenButton =
4343-+ findViewById<android.widget.ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
4444- fullscreenButton?.visibility = if (visible) {
4545- android.view.View.VISIBLE
4646- } else {
4747-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
4848-new file mode 100644
4949-index 0000000..0249e23
5050---- /dev/null
5151-+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/ProgressTracker.kt
5252-@@ -0,0 +1,29 @@
5353-+import android.os.Handler
5454-+import android.os.Looper
5555-+import androidx.annotation.OptIn
5656-+import androidx.media3.common.util.UnstableApi
5757-+import expo.modules.video.PlayerEvent
5858-+import expo.modules.video.VideoPlayer
5959-+import kotlin.math.floor
6060-+
6161-+@OptIn(UnstableApi::class)
6262-+class ProgressTracker(private val videoPlayer: VideoPlayer) : Runnable {
6363-+ private val handler: Handler = Handler(Looper.getMainLooper())
6464-+ private val player = videoPlayer.player
6565-+
6666-+ init {
6767-+ handler.post(this)
6868-+ }
6969-+
7070-+ override fun run() {
7171-+ val currentPosition = player.currentPosition
7272-+ val duration = player.duration
7373-+ val timeRemaining = floor(((duration - currentPosition) / 1000).toDouble())
7474-+ videoPlayer.sendEvent(PlayerEvent.PlayerTimeRemainingChanged(timeRemaining))
7575-+ handler.postDelayed(this, 1000 /* ms */)
7676-+ }
7777-+
7878-+ fun remove() {
7979-+ handler.removeCallbacks(this)
8080-+ }
8181-+}
8282-\ No newline at end of file
8383-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
8484-index 4b6c6d8..e20f51a 100644
8585---- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoManager.kt
8686-+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoManager.kt
8787-@@ -1,5 +1,6 @@
8888- package expo.modules.video
8989-9090-+import android.provider.MediaStore.Video
9191- import androidx.annotation.OptIn
9292- import androidx.media3.common.util.UnstableApi
9393- import expo.modules.kotlin.AppContext
9494-@@ -15,6 +16,8 @@ object VideoManager {
9595- // Keeps track of all existing VideoPlayers, and whether they are attached to a VideoView
9696- private var videoPlayersToVideoViews = mutableMapOf<VideoPlayer, MutableList<VideoView>>()
9797-9898-+ private var previouslyPlayingViews: MutableList<VideoView>? = null
9999-+
100100- private lateinit var audioFocusManager: AudioFocusManager
101101-102102- fun onModuleCreated(appContext: AppContext) {
103103-@@ -69,16 +72,24 @@ object VideoManager {
104104- return videoPlayersToVideoViews[videoPlayer]?.isNotEmpty() ?: false
105105- }
106106-107107-- fun onAppForegrounded() = Unit
108108-+ fun onAppForegrounded() {
109109-+ val previouslyPlayingViews = this.previouslyPlayingViews ?: return
110110-+ for (videoView in previouslyPlayingViews) {
111111-+ val player = videoView.videoPlayer?.player ?: continue
112112-+ player.play()
113113-+ }
114114-+ this.previouslyPlayingViews = null
115115-+ }
116116-117117- fun onAppBackgrounded() {
118118-+ val previouslyPlayingViews = mutableListOf<VideoView>()
119119- for (videoView in videoViews.values) {
120120-- if (videoView.videoPlayer?.staysActiveInBackground == false &&
121121-- !videoView.willEnterPiP &&
122122-- !videoView.isInFullscreen
123123-- ) {
124124-- videoView.videoPlayer?.player?.pause()
125125-+ val player = videoView.videoPlayer?.player ?: continue
126126-+ if (player.isPlaying) {
127127-+ player.pause()
128128-+ previouslyPlayingViews.add(videoView)
129129- }
130130- }
131131-+ this.previouslyPlayingViews = previouslyPlayingViews
132132- }
133133- }
134134-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
135135-index ec3da2a..5a1397a 100644
136136---- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt
137137-+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt
138138-@@ -43,7 +43,9 @@ class VideoModule : Module() {
139139- View(VideoView::class) {
140140- Events(
141141- "onPictureInPictureStart",
142142-- "onPictureInPictureStop"
143143-+ "onPictureInPictureStop",
144144-+ "onEnterFullscreen",
145145-+ "onExitFullscreen"
146146- )
147147-148148- Prop("player") { view: VideoView, player: VideoPlayer ->
149149-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
150150-index 58f00af..5ad8237 100644
151151---- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt
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-156156-+import ProgressTracker
157157- import android.content.Context
158158- import android.view.SurfaceView
159159- import androidx.media3.common.MediaItem
160160-@@ -35,11 +36,13 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
161161- .Builder(context, renderersFactory)
162162- .setLooper(context.mainLooper)
163163- .build()
164164-+ var progressTracker: ProgressTracker? = null
165165-166166- val serviceConnection = PlaybackServiceConnection(WeakReference(player))
167167-168168- var playing by IgnoreSameSet(false) { new, old ->
169169- sendEvent(PlayerEvent.IsPlayingChanged(new, old))
170170-+ addOrRemoveProgressTracker()
171171- }
172172-173173- var uncommittedSource: VideoSource? = source
174174-@@ -141,6 +144,9 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
175175- }
176176-177177- override fun close() {
178178-+ this.progressTracker?.remove()
179179-+ this.progressTracker = null
180180-+
181181- appContext?.reactContext?.unbindService(serviceConnection)
182182- serviceConnection.playbackServiceBinder?.service?.unregisterPlayer(player)
183183- VideoManager.unregisterVideoPlayer(this@VideoPlayer)
184184-@@ -228,7 +234,7 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
185185- listeners.removeAll { it.get() == videoPlayerListener }
186186- }
187187-188188-- private fun sendEvent(event: PlayerEvent) {
189189-+ fun sendEvent(event: PlayerEvent) {
190190- // Emits to the native listeners
191191- event.emit(this, listeners.mapNotNull { it.get() })
192192- // Emits to the JS side
193193-@@ -240,4 +246,13 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
194194- sendEvent(eventName, *args)
195195- }
196196- }
197197-+
198198-+ private fun addOrRemoveProgressTracker() {
199199-+ this.progressTracker?.remove()
200200-+ if (this.playing) {
201201-+ this.progressTracker = ProgressTracker(this)
202202-+ } else {
203203-+ this.progressTracker = null
204204-+ }
205205-+ }
206206- }
207207-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
208208-index f654254..dcfe3f0 100644
209209---- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayerListener.kt
210210-+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayerListener.kt
211211-@@ -15,4 +15,5 @@ interface VideoPlayerListener {
212212- fun onSourceChanged(player: VideoPlayer, source: VideoSource?, oldSource: VideoSource?) {}
213213- fun onPlaybackRateChanged(player: VideoPlayer, rate: Float, oldRate: Float?) {}
214214- fun onPlayedToEnd(player: VideoPlayer) {}
215215-+ fun onPlayerTimeRemainingChanged(player: VideoPlayer, timeRemaining: Double) {}
216216- }
217217-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
218218-index a951d80..3932535 100644
219219---- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoView.kt
220220-+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoView.kt
221221-@@ -36,6 +36,8 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap
222222- val playerView: PlayerView = PlayerView(context.applicationContext)
223223- val onPictureInPictureStart by EventDispatcher<Unit>()
224224- val onPictureInPictureStop by EventDispatcher<Unit>()
225225-+ val onEnterFullscreen by EventDispatcher()
226226-+ val onExitFullscreen by EventDispatcher()
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
231231- @Suppress("DEPRECATION")
232232- currentActivity.overridePendingTransition(0, 0)
233233- }
234234-+ onEnterFullscreen(mapOf())
235235- isInFullscreen = true
236236- }
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)
241241- videoPlayer?.changePlayerView(playerView)
242242-+ this.onExitFullscreen(mapOf())
243243- isInFullscreen = false
244244- }
245245-246246-diff --git a/node_modules/expo-video/build/VideoPlayer.types.d.ts b/node_modules/expo-video/build/VideoPlayer.types.d.ts
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 = {
251251- * Handler for an event emitted when the current media source of the player changes.
252252- */
253253- sourceChange(newSource: VideoSource, previousSource: VideoSource): void;
254254-+
255255-+ timeRemainingChange(timeRemaining: number): void;
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.
268268-diff --git a/node_modules/expo-video/build/VideoView.types.d.ts b/node_modules/expo-video/build/VideoView.types.d.ts
269269-index cb9ca6d..ed8bb7e 100644
270270---- a/node_modules/expo-video/build/VideoView.types.d.ts
271271-+++ b/node_modules/expo-video/build/VideoView.types.d.ts
272272-@@ -89,5 +89,8 @@ export interface VideoViewProps extends ViewProps {
273273- * @platform ios 16.0+
274274- */
275275- allowsVideoFrameAnalysis?: boolean;
276276-+
277277-+ onEnterFullscreen?: () => void;
278278-+ onExitFullscreen?: () => void;
279279- }
280280- //# sourceMappingURL=VideoView.types.d.ts.map
281281-\ 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- }
295295-diff --git a/node_modules/expo-video/ios/VideoManager.swift b/node_modules/expo-video/ios/VideoManager.swift
296296-index 094a8b0..16e7081 100644
297297---- a/node_modules/expo-video/ios/VideoManager.swift
298298-+++ b/node_modules/expo-video/ios/VideoManager.swift
299299-@@ -12,6 +12,7 @@ class VideoManager {
300300-301301- private var videoViews = NSHashTable<VideoView>.weakObjects()
302302- private var videoPlayers = NSHashTable<VideoPlayer>.weakObjects()
303303-+ private var previouslyPlayingPlayers: [VideoPlayer]?
304304-305305- func register(videoPlayer: VideoPlayer) {
306306- videoPlayers.add(videoPlayer)
307307-@@ -33,63 +34,70 @@ class VideoManager {
308308- for videoPlayer in videoPlayers.allObjects {
309309- videoPlayer.setTracksEnabled(true)
310310- }
311311-+
312312-+ if let previouslyPlayingPlayers = self.previouslyPlayingPlayers {
313313-+ previouslyPlayingPlayers.forEach { player in
314314-+ player.pointer.play()
315315-+ }
316316-+ }
317317- }
318318-319319- func onAppBackgrounded() {
320320-+ var previouslyPlayingPlayers: [VideoPlayer] = []
321321- for videoView in videoViews.allObjects {
322322- guard let player = videoView.player else {
323323- continue
324324- }
325325-- if player.staysActiveInBackground == true {
326326-- player.setTracksEnabled(videoView.isInPictureInPicture)
327327-- } else if !videoView.isInPictureInPicture {
328328-+ if player.isPlaying {
329329- player.pointer.pause()
330330-+ previouslyPlayingPlayers.append(player)
331331- }
332332- }
333333-+ self.previouslyPlayingPlayers = previouslyPlayingPlayers
334334- }
335335-336336- // MARK: - Audio Session Management
337337-338338- internal func setAppropriateAudioSessionOrWarn() {
339339-- let audioSession = AVAudioSession.sharedInstance()
340340-- var audioSessionCategoryOptions: AVAudioSession.CategoryOptions = []
341341--
342342-- let isAnyPlayerPlaying = videoPlayers.allObjects.contains { player in
343343-- player.isPlaying
344344-- }
345345-- let areAllPlayersMuted = videoPlayers.allObjects.allSatisfy { player in
346346-- player.isMuted
347347-- }
348348-- let needsPiPSupport = videoViews.allObjects.contains { view in
349349-- view.allowPictureInPicture
350350-- }
351351-- let anyPlayerShowsNotification = videoPlayers.allObjects.contains { player in
352352-- player.showNowPlayingNotification
353353-- }
354354-- // The notification won't be shown if we allow the audio to mix with others
355355-- let shouldAllowMixing = (!isAnyPlayerPlaying || areAllPlayersMuted) && !anyPlayerShowsNotification
356356-- let isOutputtingAudio = !areAllPlayersMuted && isAnyPlayerPlaying
357357-- let shouldUpdateToAllowMixing = !audioSession.categoryOptions.contains(.mixWithOthers) && shouldAllowMixing
358358--
359359-- if shouldAllowMixing {
360360-- audioSessionCategoryOptions.insert(.mixWithOthers)
361361-- }
362362--
363363-- if isOutputtingAudio || needsPiPSupport || shouldUpdateToAllowMixing || anyPlayerShowsNotification {
364364-- do {
365365-- try audioSession.setCategory(.playback, mode: .moviePlayback)
366366-- } catch {
367367-- log.warn("Failed to set audio session category. This might cause issues with audio playback and Picture in Picture. \(error.localizedDescription)")
368368-- }
369369-- }
370370--
371371-- // Make sure audio session is active if any video is playing
372372-- if isAnyPlayerPlaying {
373373-- do {
374374-- try audioSession.setActive(true)
375375-- } catch {
376376-- log.warn("Failed to activate the audio session. This might cause issues with audio playback. \(error.localizedDescription)")
377377-- }
378378-- }
379379-+// let audioSession = AVAudioSession.sharedInstance()
380380-+// var audioSessionCategoryOptions: AVAudioSession.CategoryOptions = []
381381-+//
382382-+// let isAnyPlayerPlaying = videoPlayers.allObjects.contains { player in
383383-+// player.isPlaying
384384-+// }
385385-+// let areAllPlayersMuted = videoPlayers.allObjects.allSatisfy { player in
386386-+// player.isMuted
387387-+// }
388388-+// let needsPiPSupport = videoViews.allObjects.contains { view in
389389-+// view.allowPictureInPicture
390390-+// }
391391-+// let anyPlayerShowsNotification = videoPlayers.allObjects.contains { player in
392392-+// player.showNowPlayingNotification
393393-+// }
394394-+// // The notification won't be shown if we allow the audio to mix with others
395395-+// let shouldAllowMixing = (!isAnyPlayerPlaying || areAllPlayersMuted) && !anyPlayerShowsNotification
396396-+// let isOutputtingAudio = !areAllPlayersMuted && isAnyPlayerPlaying
397397-+// let shouldUpdateToAllowMixing = !audioSession.categoryOptions.contains(.mixWithOthers) && shouldAllowMixing
398398-+//
399399-+// if shouldAllowMixing {
400400-+// audioSessionCategoryOptions.insert(.mixWithOthers)
401401-+// }
402402-+//
403403-+// if isOutputtingAudio || needsPiPSupport || shouldUpdateToAllowMixing || anyPlayerShowsNotification {
404404-+// do {
405405-+// try audioSession.setCategory(.playback, mode: .moviePlayback)
406406-+// } catch {
407407-+// log.warn("Failed to set audio session category. This might cause issues with audio playback and Picture in Picture. \(error.localizedDescription)")
408408-+// }
409409-+// }
410410-+//
411411-+// // Make sure audio session is active if any video is playing
412412-+// if isAnyPlayerPlaying {
413413-+// do {
414414-+// try audioSession.setActive(true)
415415-+// } catch {
416416-+// log.warn("Failed to activate the audio session. This might cause issues with audio playback. \(error.localizedDescription)")
417417-+// }
418418-+// }
419419- }
420420- }
421421-diff --git a/node_modules/expo-video/ios/VideoModule.swift b/node_modules/expo-video/ios/VideoModule.swift
422422-index c537a12..e4a918f 100644
423423---- a/node_modules/expo-video/ios/VideoModule.swift
424424-+++ b/node_modules/expo-video/ios/VideoModule.swift
425425-@@ -16,7 +16,9 @@ public final class VideoModule: Module {
426426- View(VideoView.self) {
427427- Events(
428428- "onPictureInPictureStart",
429429-- "onPictureInPictureStop"
430430-+ "onPictureInPictureStop",
431431-+ "onEnterFullscreen",
432432-+ "onExitFullscreen"
433433- )
434434-435435- Prop("player") { (view, player: VideoPlayer?) in
436436-diff --git a/node_modules/expo-video/ios/VideoPlayer.swift b/node_modules/expo-video/ios/VideoPlayer.swift
437437-index 3315b88..733ab1f 100644
438438---- a/node_modules/expo-video/ios/VideoPlayer.swift
439439-+++ b/node_modules/expo-video/ios/VideoPlayer.swift
440440-@@ -185,6 +185,10 @@ internal final class VideoPlayer: SharedRef<AVPlayer>, Hashable, VideoPlayerObse
441441- safeEmit(event: "sourceChange", arguments: newVideoPlayerItem?.videoSource, oldVideoPlayerItem?.videoSource)
442442- }
443443-444444-+ func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double) {
445445-+ safeEmit(event: "timeRemainingChange", arguments: timeRemaining)
446446-+ }
447447-+
448448- func safeEmit<each A: AnyArgument>(event: String, arguments: repeat each A) {
449449- if self.appContext != nil {
450450- self.emit(event: event, arguments: repeat each arguments)
451451-diff --git a/node_modules/expo-video/ios/VideoPlayerObserver.swift b/node_modules/expo-video/ios/VideoPlayerObserver.swift
452452-index d289e26..7de8cbf 100644
453453---- a/node_modules/expo-video/ios/VideoPlayerObserver.swift
454454-+++ b/node_modules/expo-video/ios/VideoPlayerObserver.swift
455455-@@ -21,6 +21,7 @@ protocol VideoPlayerObserverDelegate: AnyObject {
456456- func onItemChanged(player: AVPlayer, oldVideoPlayerItem: VideoPlayerItem?, newVideoPlayerItem: VideoPlayerItem?)
457457- func onIsMutedChanged(player: AVPlayer, oldIsMuted: Bool?, newIsMuted: Bool)
458458- func onPlayerItemStatusChanged(player: AVPlayer, oldStatus: AVPlayerItem.Status?, newStatus: AVPlayerItem.Status)
459459-+ func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double)
460460- }
461461-462462- // Default implementations for the delegate
463463-@@ -33,6 +34,7 @@ extension VideoPlayerObserverDelegate {
464464- func onItemChanged(player: AVPlayer, oldVideoPlayerItem: VideoPlayerItem?, newVideoPlayerItem: VideoPlayerItem?) {}
465465- func onIsMutedChanged(player: AVPlayer, oldIsMuted: Bool?, newIsMuted: Bool) {}
466466- func onPlayerItemStatusChanged(player: AVPlayer, oldStatus: AVPlayerItem.Status?, newStatus: AVPlayerItem.Status) {}
467467-+ func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double) {}
468468- }
469469-470470- // Wrapper used to store WeakReferences to the observer delegate
471471-@@ -91,6 +93,7 @@ class VideoPlayerObserver {
472472- private var playerVolumeObserver: NSKeyValueObservation?
473473- private var playerCurrentItemObserver: NSKeyValueObservation?
474474- private var playerIsMutedObserver: NSKeyValueObservation?
475475-+ private var playerPeriodicTimeObserver: Any?
476476-477477- // Current player item observers
478478- private var playbackBufferEmptyObserver: NSKeyValueObservation?
479479-@@ -152,6 +155,9 @@ class VideoPlayerObserver {
480480- playerVolumeObserver?.invalidate()
481481- playerIsMutedObserver?.invalidate()
482482- playerCurrentItemObserver?.invalidate()
483483-+ if let playerPeriodicTimeObserver = self.playerPeriodicTimeObserver {
484484-+ player?.removeTimeObserver(playerPeriodicTimeObserver)
485485-+ }
486486- }
487487-488488- private func initializeCurrentPlayerItemObservers(player: AVPlayer, playerItem: AVPlayerItem) {
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-497497- if isPlaying != (player.timeControlStatus == .playing) {
498498- isPlaying = player.timeControlStatus == .playing
499499-+ addPeriodicTimeObserverIfNeeded()
500500- }
501501- }
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- }
517517-@@ -310,4 +317,28 @@ class VideoPlayerObserver {
518518- }
519519- }
520520- }
521521-+
522522-+ private func onPlayerTimeRemainingChanged(_ player: AVPlayer, _ timeRemaining: Double) {
523523-+ delegates.forEach { delegate in
524524-+ delegate.value?.onPlayerTimeRemainingChanged(player: player, timeRemaining: timeRemaining)
525525-+ }
526526-+ }
527527-+
528528-+ private func addPeriodicTimeObserverIfNeeded() {
529529-+ guard self.playerPeriodicTimeObserver == nil, let player = self.player else {
530530-+ return
531531-+ }
532532-+
533533-+ if isPlaying {
534534-+ // Add the time update listener
535535-+ playerPeriodicTimeObserver = player.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1.0, preferredTimescale: Int32(NSEC_PER_SEC)), queue: nil) { event in
536536-+ guard let duration = player.currentItem?.duration else {
537537-+ return
538538-+ }
539539-+
540540-+ let timeRemaining = (duration.seconds - event.seconds).rounded()
541541-+ self.onPlayerTimeRemainingChanged(player, timeRemaining)
542542-+ }
543543-+ }
544544-+ }
545545- }
546546-diff --git a/node_modules/expo-video/ios/VideoView.swift b/node_modules/expo-video/ios/VideoView.swift
547547-index f4579e4..10c5908 100644
548548---- a/node_modules/expo-video/ios/VideoView.swift
549549-+++ b/node_modules/expo-video/ios/VideoView.swift
550550-@@ -41,6 +41,8 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
551551-552552- let onPictureInPictureStart = EventDispatcher()
553553- let onPictureInPictureStop = EventDispatcher()
554554-+ let onEnterFullscreen = EventDispatcher()
555555-+ let onExitFullscreen = EventDispatcher()
556556-557557- public override var bounds: CGRect {
558558- didSet {
559559-@@ -163,6 +165,7 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
560560- _ playerViewController: AVPlayerViewController,
561561- willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator
562562- ) {
563563-+ onEnterFullscreen()
564564- isFullscreen = true
565565- }
566566-567567-@@ -179,6 +182,7 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
568568- if wasPlaying {
569569- self.player?.pointer.play()
570570- }
571571-+ self.onExitFullscreen()
572572- self.isFullscreen = false
573573- }
574574- }
575575-diff --git a/node_modules/expo-video/src/VideoPlayer.types.ts b/node_modules/expo-video/src/VideoPlayer.types.ts
576576-index aaf4b63..5ff6b7a 100644
577577---- a/node_modules/expo-video/src/VideoPlayer.types.ts
578578-+++ b/node_modules/expo-video/src/VideoPlayer.types.ts
579579-@@ -151,6 +151,8 @@ export type VideoPlayerEvents = {
580580- * Handler for an event emitted when the current media source of the player changes.
581581- */
582582- sourceChange(newSource: VideoSource, previousSource: VideoSource): void;
583583-+
584584-+ timeRemainingChange(timeRemaining: number): void;
585585- };
586586-587587- /**
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
597597-diff --git a/node_modules/expo-video/src/VideoView.types.ts b/node_modules/expo-video/src/VideoView.types.ts
598598-index 29fe5db..e1fbf59 100644
599599---- a/node_modules/expo-video/src/VideoView.types.ts
600600-+++ b/node_modules/expo-video/src/VideoView.types.ts
601601-@@ -100,4 +100,7 @@ export interface VideoViewProps extends ViewProps {
602602- * @platform ios 16.0+
603603- */
604604- allowsVideoFrameAnalysis?: boolean;
605605-+
606606-+ onEnterFullscreen?: () => void;
607607-+ onExitFullscreen?: () => void;
608608- }
-31
patches/expo-video+1.2.4.patch.md
···11-## uwu woad beawing, do not wemove
22-33-## `expo-video` Patch
44-55-### `onEnterFullScreen`/`onExitFullScreen`
66-77-Adds two props to `VideoView`: `onEnterFullscreen` and `onExitFullscreen` which do exactly what they say on
88-the tin.
99-1010-### Removing audio session management
1111-1212-This patch also removes the audio session management that Expo does on its own, as we handle audio session management
1313-ourselves.
1414-1515-### Pausing/playing on background/foreground
1616-1717-Instead of handling the pausing/playing of videos in React, we'll handle them here. There's some logic that we do not
1818-need (around PIP mode) that we can remove, and just pause any playing players on background and then resume them on
1919-foreground.
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.