1
+ 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
2
+ index 473f964..f37aff9 100644
3
+ --- a/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerEvent.kt
4
+ +++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerEvent.kt
5
+ @@ -41,6 +41,11 @@ sealed class PlayerEvent {
6
+ override val name = "playToEnd"
7
+ }
8
+
9
+ + data class PlayerTimeRemainingChanged(val timeRemaining: Double): PlayerEvent() {
10
+ + override val name = "timeRemainingChange"
11
+ + override val arguments = arrayOf(timeRemaining)
12
+ + }
13
+ +
14
+ fun emit(player: VideoPlayer, listeners: List<VideoPlayerListener>) {
15
+ when (this) {
16
+ is StatusChanged -> listeners.forEach { it.onStatusChanged(player, status, oldStatus, error) }
17
+ @@ -49,6 +54,7 @@ sealed class PlayerEvent {
18
+ is SourceChanged -> listeners.forEach { it.onSourceChanged(player, source, oldSource) }
19
+ is PlaybackRateChanged -> listeners.forEach { it.onPlaybackRateChanged(player, rate, oldRate) }
20
+ is PlayedToEnd -> listeners.forEach { it.onPlayedToEnd(player) }
21
+ + is PlayerTimeRemainingChanged -> listeners.forEach { it.onPlayerTimeRemainingChanged(player, timeRemaining) }
22
+ }
23
+ }
24
+ }
1
25
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
2
26
index 9905e13..47342ff 100644
3
27
--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerViewExtension.kt
@@ -8,10 +32,10 @@ index 9905e13..47342ff 100644
8
32
setTimeBarInteractive(requireLinearPlayback)
9
33
+ setShowSubtitleButton(true)
10
34
}
11
-
35
+
12
36
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
13
37
@@ -27,7 +28,8 @@ internal fun PlayerView.setTimeBarInteractive(interactive: Boolean) {
14
-
38
+
15
39
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
16
40
internal fun PlayerView.setFullscreenButtonVisibility(visible: Boolean) {
17
41
- val fullscreenButton = findViewById<android.widget.ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
@@ -20,6 +44,42 @@ index 9905e13..47342ff 100644
20
44
fullscreenButton?.visibility = if (visible) {
21
45
android.view.View.VISIBLE
22
46
} else {
47
+ 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
48
+ new file mode 100644
49
+ index 0000000..0249e23
50
+ --- /dev/null
51
+ +++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/ProgressTracker.kt
52
+ @@ -0,0 +1,29 @@
53
+ + import android.os.Handler
54
+ + import android.os.Looper
55
+ + import androidx.annotation.OptIn
56
+ + import androidx.media3.common.util.UnstableApi
57
+ + import expo.modules.video.PlayerEvent
58
+ + import expo.modules.video.VideoPlayer
59
+ + import kotlin.math.floor
60
+ +
61
+ + @OptIn(UnstableApi::class)
62
+ + class ProgressTracker(private val videoPlayer: VideoPlayer) : Runnable {
63
+ + private val handler: Handler = Handler(Looper.getMainLooper())
64
+ + private val player = videoPlayer.player
65
+ +
66
+ + init {
67
+ + handler.post(this)
68
+ + }
69
+ +
70
+ + override fun run() {
71
+ + val currentPosition = player.currentPosition
72
+ + val duration = player.duration
73
+ + val timeRemaining = floor(((duration - currentPosition) / 1000).toDouble())
74
+ + videoPlayer.sendEvent(PlayerEvent.PlayerTimeRemainingChanged(timeRemaining))
75
+ + handler.postDelayed(this, 1000 /* ms */)
76
+ + }
77
+ +
78
+ + fun remove() {
79
+ + handler.removeCallbacks(this)
80
+ + }
81
+ + }
82
+ \ No newline at end of file
23
83
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
24
84
index ec3da2a..5a1397a 100644
25
85
--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt
@@ -33,8 +93,76 @@ index ec3da2a..5a1397a 100644
33
93
+ "onEnterFullscreen",
34
94
+ "onExitFullscreen"
35
95
)
36
-
96
+
37
97
Prop("player") { view: VideoView, player: VideoPlayer ->
98
+ 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
99
+ index 58f00af..5ad8237 100644
100
+ --- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt
101
+ +++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt
102
+ @@ -1,5 +1,6 @@
103
+ package expo.modules.video
104
+
105
+ + import ProgressTracker
106
+ import android.content.Context
107
+ import android.view.SurfaceView
108
+ import androidx.media3.common.MediaItem
109
+ @@ -35,11 +36,13 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
110
+ .Builder(context, renderersFactory)
111
+ .setLooper(context.mainLooper)
112
+ .build()
113
+ + var progressTracker: ProgressTracker? = null
114
+
115
+ val serviceConnection = PlaybackServiceConnection(WeakReference(player))
116
+
117
+ var playing by IgnoreSameSet(false) { new, old ->
118
+ sendEvent(PlayerEvent.IsPlayingChanged(new, old))
119
+ + addOrRemoveProgressTracker()
120
+ }
121
+
122
+ var uncommittedSource: VideoSource? = source
123
+ @@ -141,6 +144,9 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
124
+ }
125
+
126
+ override fun close() {
127
+ + this.progressTracker?.remove()
128
+ + this.progressTracker = null
129
+ +
130
+ appContext?.reactContext?.unbindService(serviceConnection)
131
+ serviceConnection.playbackServiceBinder?.service?.unregisterPlayer(player)
132
+ VideoManager.unregisterVideoPlayer(this@VideoPlayer)
133
+ @@ -228,7 +234,7 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
134
+ listeners.removeAll { it.get() == videoPlayerListener }
135
+ }
136
+
137
+ - private fun sendEvent(event: PlayerEvent) {
138
+ + fun sendEvent(event: PlayerEvent) {
139
+ // Emits to the native listeners
140
+ event.emit(this, listeners.mapNotNull { it.get() })
141
+ // Emits to the JS side
142
+ @@ -240,4 +246,13 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
143
+ sendEvent(eventName, *args)
144
+ }
145
+ }
146
+ +
147
+ + private fun addOrRemoveProgressTracker() {
148
+ + this.progressTracker?.remove()
149
+ + if (this.playing) {
150
+ + this.progressTracker = ProgressTracker(this)
151
+ + } else {
152
+ + this.progressTracker = null
153
+ + }
154
+ + }
155
+ }
156
+ 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
157
+ index f654254..dcfe3f0 100644
158
+ --- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayerListener.kt
159
+ +++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayerListener.kt
160
+ @@ -15,4 +15,5 @@ interface VideoPlayerListener {
161
+ fun onSourceChanged(player: VideoPlayer, source: VideoSource?, oldSource: VideoSource?) {}
162
+ fun onPlaybackRateChanged(player: VideoPlayer, rate: Float, oldRate: Float?) {}
163
+ fun onPlayedToEnd(player: VideoPlayer) {}
164
+ + fun onPlayerTimeRemainingChanged(player: VideoPlayer, timeRemaining: Double) {}
165
+ }
38
166
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
39
167
index a951d80..3932535 100644
40
168
--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoView.kt
@@ -45,7 +173,7 @@ index a951d80..3932535 100644
45
173
val onPictureInPictureStop by EventDispatcher<Unit>()
46
174
+ val onEnterFullscreen by EventDispatcher()
47
175
+ val onExitFullscreen by EventDispatcher()
48
-
176
+
49
177
var willEnterPiP: Boolean = false
50
178
var isInFullscreen: Boolean = false
51
179
@@ -154,6 +156,7 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap
@@ -55,17 +183,30 @@ index a951d80..3932535 100644
55
183
+ onEnterFullscreen(mapOf())
56
184
isInFullscreen = true
57
185
}
58
-
186
+
59
187
@@ -162,6 +165,7 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap
60
188
val fullScreenButton: ImageButton = playerView.findViewById(androidx.media3.ui.R.id.exo_fullscreen)
61
189
fullScreenButton.setImageResource(androidx.media3.ui.R.drawable.exo_icon_fullscreen_enter)
62
190
videoPlayer?.changePlayerView(playerView)
63
191
+ this.onExitFullscreen(mapOf())
64
192
isInFullscreen = false
65
193
}
66
-
194
+
195
+ diff --git a/node_modules/expo-video/build/VideoPlayer.types.d.ts b/node_modules/expo-video/build/VideoPlayer.types.d.ts
196
+ index a09fcfe..65fe29a 100644
197
+ --- a/node_modules/expo-video/build/VideoPlayer.types.d.ts
198
+ +++ b/node_modules/expo-video/build/VideoPlayer.types.d.ts
199
+ @@ -128,6 +128,8 @@ export type VideoPlayerEvents = {
200
+ * Handler for an event emitted when the current media source of the player changes.
201
+ */
202
+ sourceChange(newSource: VideoSource, previousSource: VideoSource): void;
203
+ +
204
+ + timeRemainingChange(timeRemaining: number): void;
205
+ };
206
+ /**
207
+ * Describes the current status of the player.
67
208
diff --git a/node_modules/expo-video/build/VideoView.types.d.ts b/node_modules/expo-video/build/VideoView.types.d.ts
68
- index cb9ca6d..60e9f4e 100644
209
+ index cb9ca6d..ed8bb7e 100644
69
210
--- a/node_modules/expo-video/build/VideoView.types.d.ts
70
211
+++ b/node_modules/expo-video/build/VideoView.types.d.ts
71
212
@@ -89,5 +89,8 @@ export interface VideoViewProps extends ViewProps {
@@ -77,6 +218,7 @@ index cb9ca6d..60e9f4e 100644
77
218
+ onExitFullscreen?: () => void;
78
219
}
79
220
//# sourceMappingURL=VideoView.types.d.ts.map
221
+ \ No newline at end of file
80
222
diff --git a/node_modules/expo-video/ios/VideoModule.swift b/node_modules/expo-video/ios/VideoModule.swift
81
223
index c537a12..e4a918f 100644
82
224
--- a/node_modules/expo-video/ios/VideoModule.swift
@@ -90,19 +232,111 @@ index c537a12..e4a918f 100644
90
232
+ "onEnterFullscreen",
91
233
+ "onExitFullscreen"
92
234
)
93
-
235
+
94
236
Prop("player") { (view, player: VideoPlayer?) in
237
+ diff --git a/node_modules/expo-video/ios/VideoPlayer.swift b/node_modules/expo-video/ios/VideoPlayer.swift
238
+ index 3315b88..f482390 100644
239
+ --- a/node_modules/expo-video/ios/VideoPlayer.swift
240
+ +++ b/node_modules/expo-video/ios/VideoPlayer.swift
241
+ @@ -185,6 +185,10 @@ internal final class VideoPlayer: SharedRef<AVPlayer>, Hashable, VideoPlayerObse
242
+ safeEmit(event: "sourceChange", arguments: newVideoPlayerItem?.videoSource, oldVideoPlayerItem?.videoSource)
243
+ }
244
+
245
+ + func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double) {
246
+ + safeEmit(event: "timeRemainingChange", arguments: timeRemaining)
247
+ + }
248
+ +
249
+ func safeEmit<each A: AnyArgument>(event: String, arguments: repeat each A) {
250
+ if self.appContext != nil {
251
+ self.emit(event: event, arguments: repeat each arguments)
252
+ diff --git a/node_modules/expo-video/ios/VideoPlayerObserver.swift b/node_modules/expo-video/ios/VideoPlayerObserver.swift
253
+ index d289e26..d0fdd30 100644
254
+ --- a/node_modules/expo-video/ios/VideoPlayerObserver.swift
255
+ +++ b/node_modules/expo-video/ios/VideoPlayerObserver.swift
256
+ @@ -21,6 +21,7 @@ protocol VideoPlayerObserverDelegate: AnyObject {
257
+ func onItemChanged(player: AVPlayer, oldVideoPlayerItem: VideoPlayerItem?, newVideoPlayerItem: VideoPlayerItem?)
258
+ func onIsMutedChanged(player: AVPlayer, oldIsMuted: Bool?, newIsMuted: Bool)
259
+ func onPlayerItemStatusChanged(player: AVPlayer, oldStatus: AVPlayerItem.Status?, newStatus: AVPlayerItem.Status)
260
+ + func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double)
261
+ }
262
+
263
+ // Default implementations for the delegate
264
+ @@ -33,6 +34,7 @@ extension VideoPlayerObserverDelegate {
265
+ func onItemChanged(player: AVPlayer, oldVideoPlayerItem: VideoPlayerItem?, newVideoPlayerItem: VideoPlayerItem?) {}
266
+ func onIsMutedChanged(player: AVPlayer, oldIsMuted: Bool?, newIsMuted: Bool) {}
267
+ func onPlayerItemStatusChanged(player: AVPlayer, oldStatus: AVPlayerItem.Status?, newStatus: AVPlayerItem.Status) {}
268
+ + func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double) {}
269
+ }
270
+
271
+ // Wrapper used to store WeakReferences to the observer delegate
272
+ @@ -91,6 +93,7 @@ class VideoPlayerObserver {
273
+ private var playerVolumeObserver: NSKeyValueObservation?
274
+ private var playerCurrentItemObserver: NSKeyValueObservation?
275
+ private var playerIsMutedObserver: NSKeyValueObservation?
276
+ + private var playerPeriodicTimeObserver: Any?
277
+
278
+ // Current player item observers
279
+ private var playbackBufferEmptyObserver: NSKeyValueObservation?
280
+ @@ -152,6 +155,9 @@ class VideoPlayerObserver {
281
+ playerVolumeObserver?.invalidate()
282
+ playerIsMutedObserver?.invalidate()
283
+ playerCurrentItemObserver?.invalidate()
284
+ + if let playerPeriodicTimeObserver = self.playerPeriodicTimeObserver {
285
+ + player?.removeTimeObserver(playerPeriodicTimeObserver)
286
+ + }
287
+ }
288
+
289
+ private func initializeCurrentPlayerItemObservers(player: AVPlayer, playerItem: AVPlayerItem) {
290
+ @@ -270,6 +276,7 @@ class VideoPlayerObserver {
291
+
292
+ if isPlaying != (player.timeControlStatus == .playing) {
293
+ isPlaying = player.timeControlStatus == .playing
294
+ + addOrRemovePeriodicTimeObserver()
295
+ }
296
+ }
297
+
298
+ @@ -310,4 +317,30 @@ class VideoPlayerObserver {
299
+ }
300
+ }
301
+ }
302
+ +
303
+ + private func onPlayerTimeRemainingChanged(_ player: AVPlayer, _ timeRemaining: Double) {
304
+ + delegates.forEach { delegate in
305
+ + delegate.value?.onPlayerTimeRemainingChanged(player: player, timeRemaining: timeRemaining)
306
+ + }
307
+ + }
308
+ +
309
+ + private func addOrRemovePeriodicTimeObserver() {
310
+ + guard let player = self.player else {
311
+ + return
312
+ + }
313
+ +
314
+ + if isPlaying {
315
+ + // Add the time update listener
316
+ + playerPeriodicTimeObserver = player.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1.0, preferredTimescale: Int32(NSEC_PER_SEC)), queue: nil) { event in
317
+ + guard let duration = player.currentItem?.duration else {
318
+ + return
319
+ + }
320
+ +
321
+ + let timeRemaining = (duration.seconds - event.seconds).rounded()
322
+ + self.onPlayerTimeRemainingChanged(player, timeRemaining)
323
+ + }
324
+ + } else if let playerPeriodicTimeObserver = self.playerPeriodicTimeObserver {
325
+ + player.removeTimeObserver(playerPeriodicTimeObserver)
326
+ + }
327
+ + }
328
+ }
95
329
diff --git a/node_modules/expo-video/ios/VideoView.swift b/node_modules/expo-video/ios/VideoView.swift
96
330
index f4579e4..10c5908 100644
97
331
--- a/node_modules/expo-video/ios/VideoView.swift
98
332
+++ b/node_modules/expo-video/ios/VideoView.swift
99
333
@@ -41,6 +41,8 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
100
-
334
+
101
335
let onPictureInPictureStart = EventDispatcher()
102
336
let onPictureInPictureStop = EventDispatcher()
103
337
+ let onEnterFullscreen = EventDispatcher()
104
338
+ let onExitFullscreen = EventDispatcher()
105
-
339
+
106
340
public override var bounds: CGRect {
107
341
didSet {
108
342
@@ -163,6 +165,7 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
@@ -112,7 +346,7 @@ index f4579e4..10c5908 100644
112
346
+ onEnterFullscreen()
113
347
isFullscreen = true
114
348
}
115
-
349
+
116
350
@@ -179,6 +182,7 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
117
351
if wasPlaying {
118
352
self.player?.pointer.play()
@@ -121,6 +355,19 @@ index f4579e4..10c5908 100644
121
355
self.isFullscreen = false
122
356
}
123
357
}
358
+ diff --git a/node_modules/expo-video/src/VideoPlayer.types.ts b/node_modules/expo-video/src/VideoPlayer.types.ts
359
+ index aaf4b63..f438196 100644
360
+ --- a/node_modules/expo-video/src/VideoPlayer.types.ts
361
+ +++ b/node_modules/expo-video/src/VideoPlayer.types.ts
362
+ @@ -151,6 +151,8 @@ export type VideoPlayerEvents = {
363
+ * Handler for an event emitted when the current media source of the player changes.
364
+ */
365
+ sourceChange(newSource: VideoSource, previousSource: VideoSource): void;
366
+ +
367
+ + timeRemainingChange(timeRemaining: number): void;
368
+ };
369
+
370
+ /**
124
371
diff --git a/node_modules/expo-video/src/VideoView.types.ts b/node_modules/expo-video/src/VideoView.types.ts
125
372
index 29fe5db..e1fbf59 100644
126
373
--- a/node_modules/expo-video/src/VideoView.types.ts
0 commit comments