expo-video 1.2.2 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/AndroidManifest.xml +1 -1
  4. package/android/src/main/java/expo/modules/video/AudioFocusManager.kt +187 -0
  5. package/android/src/main/java/expo/modules/video/PlayerEvent.kt +54 -0
  6. package/android/src/main/java/expo/modules/video/VideoExceptions.kt +3 -0
  7. package/android/src/main/java/expo/modules/video/VideoManager.kt +11 -2
  8. package/android/src/main/java/expo/modules/video/VideoModule.kt +13 -6
  9. package/android/src/main/java/expo/modules/video/VideoPlayer.kt +59 -109
  10. package/android/src/main/java/expo/modules/video/VideoPlayerListener.kt +18 -0
  11. package/android/src/main/java/expo/modules/video/VideoView.kt +27 -5
  12. package/android/src/main/java/expo/modules/video/delegates/IgnoreSameSet.kt +24 -0
  13. package/android/src/main/java/expo/modules/video/drawing/OutlineProvider.kt +1 -1
  14. package/android/src/main/java/expo/modules/video/{ContentFit.kt → enums/ContentFit.kt} +1 -1
  15. package/android/src/main/java/expo/modules/video/{ExpoVideoPlaybackService.kt → playbackService/ExpoVideoPlaybackService.kt} +20 -1
  16. package/android/src/main/java/expo/modules/video/playbackService/PlaybackServiceConnection.kt +36 -0
  17. package/android/src/main/java/expo/modules/video/{VideoMediaSessionCallback.kt → playbackService/VideoMediaSessionCallback.kt} +1 -1
  18. package/android/src/main/java/expo/modules/video/records/VideoSource.kt +3 -2
  19. package/android/src/main/java/expo/modules/video/records/VolumeEvent.kt +1 -1
  20. package/android/src/main/java/expo/modules/video/{DataSourceUtils.kt → utils/DataSourceUtils.kt} +13 -2
  21. package/android/src/main/java/expo/modules/video/{YogaUtils.kt → utils/YogaUtils.kt} +1 -1
  22. package/build/VideoPlayer.types.d.ts +1 -1
  23. package/build/VideoPlayer.types.d.ts.map +1 -1
  24. package/build/VideoPlayer.types.js.map +1 -1
  25. package/build/VideoPlayer.web.d.ts +9 -1
  26. package/build/VideoPlayer.web.d.ts.map +1 -1
  27. package/build/VideoPlayer.web.js +61 -13
  28. package/build/VideoPlayer.web.js.map +1 -1
  29. package/build/VideoView.d.ts +3 -0
  30. package/build/VideoView.d.ts.map +1 -1
  31. package/build/VideoView.js +3 -0
  32. package/build/VideoView.js.map +1 -1
  33. package/build/VideoView.types.d.ts +13 -0
  34. package/build/VideoView.types.d.ts.map +1 -1
  35. package/build/VideoView.types.js.map +1 -1
  36. package/build/VideoView.web.d.ts.map +1 -1
  37. package/build/VideoView.web.js +42 -13
  38. package/build/VideoView.web.js.map +1 -1
  39. package/ios/NowPlayingManager.swift +6 -10
  40. package/ios/VideoModule.swift +19 -0
  41. package/ios/VideoPlayer.swift +7 -1
  42. package/package.json +2 -2
  43. package/plugin/build/withExpoVideo.d.ts +5 -1
  44. package/plugin/build/withExpoVideo.js +21 -3
  45. package/plugin/src/withExpoVideo.ts +35 -3
  46. package/src/VideoPlayer.types.ts +1 -1
  47. package/src/VideoPlayer.web.tsx +72 -13
  48. package/src/VideoView.tsx +3 -0
  49. package/src/VideoView.types.ts +14 -0
  50. package/src/VideoView.web.tsx +49 -14
  51. package/android/src/main/java/expo/modules/video/VideoPlayerAudioFocusManager.kt +0 -105
@@ -1,28 +1,22 @@
1
1
  package expo.modules.video
2
2
 
3
- import android.content.ComponentName
4
3
  import android.content.Context
5
- import android.content.Context.BIND_AUTO_CREATE
6
- import android.content.Intent
7
- import android.content.ServiceConnection
8
- import android.os.Build
9
- import android.os.IBinder
10
- import android.util.Log
11
4
  import android.view.SurfaceView
12
5
  import androidx.media3.common.MediaItem
13
6
  import androidx.media3.common.PlaybackException
14
7
  import androidx.media3.common.PlaybackParameters
15
8
  import androidx.media3.common.Player
16
- import androidx.media3.common.Timeline
17
9
  import androidx.media3.common.util.UnstableApi
18
10
  import androidx.media3.exoplayer.DefaultRenderersFactory
19
11
  import androidx.media3.exoplayer.ExoPlayer
20
- import androidx.media3.session.MediaSessionService
21
12
  import androidx.media3.ui.PlayerView
22
13
  import expo.modules.kotlin.AppContext
23
14
  import expo.modules.kotlin.sharedobjects.SharedObject
15
+ import expo.modules.video.delegates.IgnoreSameSet
24
16
  import expo.modules.video.enums.PlayerStatus
25
17
  import expo.modules.video.enums.PlayerStatus.*
18
+ import expo.modules.video.playbackService.ExpoVideoPlaybackService
19
+ import expo.modules.video.playbackService.PlaybackServiceConnection
26
20
  import expo.modules.video.records.PlaybackError
27
21
  import expo.modules.video.records.VideoSource
28
22
  import expo.modules.video.records.VolumeEvent
@@ -35,29 +29,23 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
35
29
  // This improves the performance of playing DRM-protected content
36
30
  private var renderersFactory = DefaultRenderersFactory(context)
37
31
  .forceEnableMediaCodecAsynchronousQueueing()
38
- val audioFocusManager = VideoPlayerAudioFocusManager(context, WeakReference(this))
32
+ private var listeners: MutableList<WeakReference<VideoPlayerListener>> = mutableListOf()
33
+
39
34
  val player = ExoPlayer
40
35
  .Builder(context, renderersFactory)
41
36
  .setLooper(context.mainLooper)
42
37
  .build()
43
38
 
44
- // We duplicate some properties of the player, because we don't want to always use the mainQueue to access them.
45
- var playing = false
46
- set(value) {
47
- if (field != value) {
48
- sendEventOnJSThread("playingChange", value, field)
49
- }
50
- field = value
51
- }
39
+ val serviceConnection = PlaybackServiceConnection(WeakReference(player))
40
+
41
+ var playing by IgnoreSameSet(false) { new, old ->
42
+ sendEvent(PlayerEvent.IsPlayingChanged(new, old))
43
+ }
52
44
 
53
45
  var uncommittedSource: VideoSource? = source
54
- private var lastLoadedSource: VideoSource? = null
55
- set(value) {
56
- if (field != value && value != null) {
57
- sendEventOnJSThread("sourceChange", value, field)
58
- }
59
- field = value
60
- }
46
+ private var lastLoadedSource by IgnoreSameSet<VideoSource?>(null) { new, old ->
47
+ sendEvent(PlayerEvent.SourceChanged(new, old))
48
+ }
61
49
 
62
50
  // Volume of the player if there was no mute applied.
63
51
  var userVolume = 1f
@@ -66,66 +54,48 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
66
54
  var staysActiveInBackground = false
67
55
  var preservesPitch = false
68
56
  set(preservesPitch) {
69
- playbackParameters = applyPitchCorrection(playbackParameters)
70
57
  field = preservesPitch
58
+ playbackParameters = applyPitchCorrection(playbackParameters)
71
59
  }
72
60
  var showNowPlayingNotification = true
73
61
  set(value) {
74
62
  field = value
75
- playbackServiceBinder?.service?.setShowNotification(value, this.player)
63
+ serviceConnection.playbackServiceBinder?.service?.setShowNotification(value, this.player)
76
64
  }
77
65
  var duration = 0f
78
66
  var isLive = false
79
67
 
80
- private var serviceConnection: ServiceConnection
81
- internal var playbackServiceBinder: PlaybackServiceBinder? = null
82
- lateinit var timeline: Timeline
83
-
84
- var volume = 1f
85
- set(volume) {
86
- if (player.volume == volume) return
87
- player.volume = if (muted) 0f else volume
88
- sendEventOnJSThread("volumeChange", VolumeEvent(volume, muted), VolumeEvent(field, muted))
89
- field = volume
90
- }
68
+ var volume: Float by IgnoreSameSet(1f) { new: Float, old: Float ->
69
+ player.volume = if (muted) 0f else new
70
+ sendEvent(PlayerEvent.VolumeChanged(VolumeEvent(new, muted), VolumeEvent(old, muted)))
71
+ }
91
72
 
92
- var muted = false
93
- set(muted) {
94
- if (field == muted) return
95
- sendEventOnJSThread("volumeChange", VolumeEvent(volume, muted), VolumeEvent(volume, field))
96
- player.volume = if (muted) 0f else userVolume
97
- field = muted
98
- audioFocusManager.onPlayerChangedAudioFocusProperty(this@VideoPlayer)
99
- }
73
+ var muted: Boolean by IgnoreSameSet(false) { new: Boolean, old: Boolean ->
74
+ volume = if (new) 0f else userVolume
75
+ sendEvent(PlayerEvent.VolumeChanged(VolumeEvent(volume, new), VolumeEvent(volume, old)))
76
+ }
100
77
 
101
- var playbackParameters: PlaybackParameters = PlaybackParameters.DEFAULT
102
- set(newPlaybackParameters) {
103
- if (playbackParameters.speed != newPlaybackParameters.speed) {
104
- sendEventOnJSThread("playbackRateChange", newPlaybackParameters.speed, playbackParameters.speed)
105
- }
106
- val pitchCorrectedPlaybackParameters = applyPitchCorrection(newPlaybackParameters)
107
- field = pitchCorrectedPlaybackParameters
78
+ var playbackParameters by IgnoreSameSet(
79
+ PlaybackParameters.DEFAULT,
80
+ propertyMapper = { applyPitchCorrection(it) }
81
+ ) { new: PlaybackParameters, old: PlaybackParameters ->
82
+ player.playbackParameters = new
108
83
 
109
- if (player.playbackParameters != pitchCorrectedPlaybackParameters) {
110
- player.playbackParameters = pitchCorrectedPlaybackParameters
111
- }
84
+ if (old.speed != new.speed) {
85
+ sendEvent(PlayerEvent.PlaybackRateChanged(new.speed, old.speed))
112
86
  }
87
+ }
113
88
 
114
89
  private val playerListener = object : Player.Listener {
115
90
  override fun onIsPlayingChanged(isPlaying: Boolean) {
116
91
  this@VideoPlayer.playing = isPlaying
117
- audioFocusManager.onPlayerChangedAudioFocusProperty(this@VideoPlayer)
118
- }
119
-
120
- override fun onTimelineChanged(timeline: Timeline, reason: Int) {
121
- this@VideoPlayer.timeline = timeline
122
92
  }
123
93
 
124
94
  override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
125
95
  this@VideoPlayer.duration = 0f
126
96
  this@VideoPlayer.isLive = false
127
97
  if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT) {
128
- sendEventOnJSThread("playToEnd")
98
+ sendEvent(PlayerEvent.PlayedToEnd())
129
99
  }
130
100
  super.onMediaItemTransition(mediaItem, reason)
131
101
  }
@@ -144,7 +114,6 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
144
114
 
145
115
  override fun onVolumeChanged(volume: Float) {
146
116
  this@VideoPlayer.volume = volume
147
- audioFocusManager.onPlayerChangedAudioFocusProperty(this@VideoPlayer)
148
117
  }
149
118
 
150
119
  override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) {
@@ -154,9 +123,9 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
154
123
 
155
124
  override fun onPlayerErrorChanged(error: PlaybackException?) {
156
125
  error?.let {
157
- setStatus(ERROR, error)
158
126
  this@VideoPlayer.duration = 0f
159
127
  this@VideoPlayer.isLive = false
128
+ setStatus(ERROR, error)
160
129
  } ?: run {
161
130
  setStatus(playerStateToPlayerStatus(player.playbackState), null)
162
131
  }
@@ -166,53 +135,14 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
166
135
  }
167
136
 
168
137
  init {
169
- serviceConnection = object : ServiceConnection {
170
- override fun onServiceConnected(componentName: ComponentName, binder: IBinder) {
171
- playbackServiceBinder = binder as? PlaybackServiceBinder
172
- playbackServiceBinder?.service?.registerPlayer(player) ?: run {
173
- Log.w(
174
- "ExpoVideo",
175
- "Expo Video could not bind to the playback service. " +
176
- "This will cause issues with playback notifications and sustaining background playback."
177
- )
178
- }
179
- }
180
-
181
- override fun onServiceDisconnected(componentName: ComponentName) {
182
- playbackServiceBinder = null
183
- }
184
-
185
- override fun onNullBinding(componentName: ComponentName) {
186
- Log.w(
187
- "ExpoVideo",
188
- "Expo Video could not bind to the playback service. " +
189
- "This will cause issues with playback notifications and sustaining background playback."
190
- )
191
- }
192
- }
193
-
194
- appContext.reactContext?.apply {
195
- val intent = Intent(context, ExpoVideoPlaybackService::class.java)
196
- intent.action = MediaSessionService.SERVICE_INTERFACE
197
-
198
- startService(intent)
199
-
200
- val flags = if (Build.VERSION.SDK_INT >= 29) {
201
- BIND_AUTO_CREATE or Context.BIND_INCLUDE_CAPABILITIES
202
- } else {
203
- BIND_AUTO_CREATE
204
- }
205
-
206
- bindService(intent, serviceConnection, flags)
207
- }
138
+ ExpoVideoPlaybackService.startService(appContext, context, serviceConnection)
208
139
  player.addListener(playerListener)
209
140
  VideoManager.registerVideoPlayer(this)
210
141
  }
211
142
 
212
143
  override fun close() {
213
- audioFocusManager.onPlayerDestroyed()
214
144
  appContext?.reactContext?.unbindService(serviceConnection)
215
- playbackServiceBinder?.service?.unregisterPlayer(player)
145
+ serviceConnection.playbackServiceBinder?.service?.unregisterPlayer(player)
216
146
  VideoManager.unregisterVideoPlayer(this@VideoPlayer)
217
147
 
218
148
  appContext?.mainQueue?.launch {
@@ -270,19 +200,39 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
270
200
  else -> IDLE
271
201
  }
272
202
  }
203
+
273
204
  private fun setStatus(status: PlayerStatus, error: PlaybackException?) {
205
+ val oldStatus = this.status
206
+ this.status = status
207
+
274
208
  val playbackError = error?.let {
275
209
  PlaybackError(it)
276
210
  }
277
211
 
278
212
  if (playbackError == null && player.playbackState == Player.STATE_ENDED) {
279
- sendEventOnJSThread("playToEnd")
213
+ sendEvent(PlayerEvent.PlayedToEnd())
280
214
  }
281
215
 
282
- if (this.status != status) {
283
- sendEventOnJSThread("statusChange", status.value, this.status.value, playbackError)
216
+ if (this.status != oldStatus) {
217
+ sendEvent(PlayerEvent.StatusChanged(status, oldStatus, playbackError))
284
218
  }
285
- this.status = status
219
+ }
220
+
221
+ fun addListener(videoPlayerListener: VideoPlayerListener) {
222
+ if (listeners.all { it.get() != videoPlayerListener }) {
223
+ listeners.add(WeakReference(videoPlayerListener))
224
+ }
225
+ }
226
+
227
+ fun removeListener(videoPlayerListener: VideoPlayerListener) {
228
+ listeners.removeAll { it.get() == videoPlayerListener }
229
+ }
230
+
231
+ private fun sendEvent(event: PlayerEvent) {
232
+ // Emits to the native listeners
233
+ event.emit(this, listeners.mapNotNull { it.get() })
234
+ // Emits to the JS side
235
+ sendEventOnJSThread(event.name, *event.arguments)
286
236
  }
287
237
 
288
238
  private fun sendEventOnJSThread(eventName: String, vararg args: Any?) {
@@ -0,0 +1,18 @@
1
+ package expo.modules.video
2
+
3
+ import androidx.annotation.OptIn
4
+ import androidx.media3.common.util.UnstableApi
5
+ import expo.modules.video.enums.PlayerStatus
6
+ import expo.modules.video.records.PlaybackError
7
+ import expo.modules.video.records.VideoSource
8
+ import expo.modules.video.records.VolumeEvent
9
+
10
+ @OptIn(UnstableApi::class)
11
+ interface VideoPlayerListener {
12
+ fun onStatusChanged(player: VideoPlayer, status: PlayerStatus, oldStatus: PlayerStatus?, error: PlaybackError?) {}
13
+ fun onIsPlayingChanged(player: VideoPlayer, isPlaying: Boolean, oldIsPlaying: Boolean?) {}
14
+ fun onVolumeChanged(player: VideoPlayer, newValue: VolumeEvent, oldVolume: VolumeEvent?) {}
15
+ fun onSourceChanged(player: VideoPlayer, source: VideoSource?, oldSource: VideoSource?) {}
16
+ fun onPlaybackRateChanged(player: VideoPlayer, rate: Float, oldRate: Float?) {}
17
+ fun onPlayedToEnd(player: VideoPlayer) {}
18
+ }
@@ -7,6 +7,7 @@ import android.content.Intent
7
7
  import android.graphics.Canvas
8
8
  import android.graphics.Rect
9
9
  import android.os.Build
10
+ import android.util.Log
10
11
  import android.util.Rational
11
12
  import android.view.View
12
13
  import android.view.ViewGroup
@@ -24,6 +25,8 @@ import expo.modules.kotlin.exception.Exceptions
24
25
  import expo.modules.kotlin.viewevent.EventDispatcher
25
26
  import expo.modules.kotlin.views.ExpoView
26
27
  import expo.modules.video.drawing.OutlineProvider
28
+ import expo.modules.video.enums.ContentFit
29
+ import expo.modules.video.utils.ifYogaDefinedUse
27
30
  import java.util.UUID
28
31
 
29
32
  // https://developer.android.com/guide/topics/media/media3/getting-started/migration-guide#improvements_in_media3
@@ -74,10 +77,12 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap
74
77
 
75
78
  var autoEnterPiP: Boolean = false
76
79
  set(value) {
77
- field = value
78
- if (Build.VERSION.SDK_INT >= 31) {
79
- currentActivity.setPictureInPictureParams(PictureInPictureParams.Builder().setAutoEnterEnabled(value).build())
80
+ if (Build.VERSION.SDK_INT >= 31 && isPictureInPictureSupported(currentActivity) && field != value) {
81
+ runWithPiPMisconfigurationSoftHandling {
82
+ currentActivity.setPictureInPictureParams(PictureInPictureParams.Builder().setAutoEnterEnabled(value).build())
83
+ }
80
84
  }
85
+ field = value
81
86
  }
82
87
 
83
88
  var contentFit: ContentFit = ContentFit.CONTAIN
@@ -251,8 +256,10 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap
251
256
  }
252
257
 
253
258
  private fun applyRectHint() {
254
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
255
- currentActivity.setPictureInPictureParams(PictureInPictureParams.Builder().setSourceRectHint(rectHint).build())
259
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isPictureInPictureSupported(currentActivity)) {
260
+ runWithPiPMisconfigurationSoftHandling(ignore = true) {
261
+ currentActivity.setPictureInPictureParams(PictureInPictureParams.Builder().setSourceRectHint(rectHint).build())
262
+ }
256
263
  }
257
264
  }
258
265
 
@@ -388,6 +395,21 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap
388
395
  }
389
396
  }
390
397
 
398
+ // We can't check if AndroidManifest.xml is configured properly, so we have to handle the exceptions ourselves to prevent crashes
399
+ internal fun runWithPiPMisconfigurationSoftHandling(shouldThrow: Boolean = false, ignore: Boolean = false, block: () -> Any?) {
400
+ try {
401
+ block()
402
+ } catch (e: IllegalStateException) {
403
+ if (ignore) {
404
+ return
405
+ }
406
+ Log.e("ExpoVideo", "Current activity does not support picture-in-picture. Make sure you have configured the `expo-video` config plugin correctly.")
407
+ if (shouldThrow) {
408
+ throw PictureInPictureConfigurationException()
409
+ }
410
+ }
411
+ }
412
+
391
413
  companion object {
392
414
  fun isPictureInPictureSupported(currentActivity: Activity): Boolean {
393
415
  return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && currentActivity.packageManager.hasSystemFeature(
@@ -0,0 +1,24 @@
1
+ package expo.modules.video.delegates
2
+
3
+ import kotlin.reflect.KProperty
4
+
5
+ /**
6
+ * Property delegate, where the set is ignored unless the value has changed.
7
+ * @param T The type of the property.
8
+ * @param value The initial value of the property.
9
+ * @param propertyMapper A function that maps the new value to the property value.
10
+ * @param didSet A function that is called when the property value has changed.
11
+ */
12
+
13
+ class IgnoreSameSet<T : Any?>(private var value: T, val propertyMapper: ((T) -> T) = { v -> v }, val didSet: ((new: T, old: T) -> Unit)? = null) {
14
+ operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
15
+ return value
16
+ }
17
+
18
+ operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
19
+ if (this.value == propertyMapper(value)) return
20
+ val oldValue = this.value
21
+ this.value = propertyMapper(value)
22
+ didSet?.invoke(this.value, oldValue)
23
+ }
24
+ }
@@ -12,7 +12,7 @@ import com.facebook.react.modules.i18nmanager.I18nUtil
12
12
  import com.facebook.react.uimanager.FloatUtil
13
13
  import com.facebook.react.uimanager.PixelUtil
14
14
  import com.facebook.yoga.YogaConstants
15
- import expo.modules.video.ifYogaUndefinedUse
15
+ import expo.modules.video.utils.ifYogaUndefinedUse
16
16
 
17
17
  class OutlineProvider(private val mContext: Context) : ViewOutlineProvider() {
18
18
  enum class BorderRadiusConfig {
@@ -1,4 +1,4 @@
1
- package expo.modules.video
1
+ package expo.modules.video.enums
2
2
 
3
3
  import androidx.media3.ui.AspectRatioFrameLayout
4
4
  import expo.modules.kotlin.types.Enumerable
@@ -1,4 +1,4 @@
1
- package expo.modules.video
1
+ package expo.modules.video.playbackService
2
2
 
3
3
  import android.app.NotificationChannel
4
4
  import android.app.NotificationManager
@@ -18,6 +18,8 @@ import androidx.media3.session.MediaSessionService
18
18
  import androidx.media3.session.MediaStyleNotificationHelper
19
19
  import androidx.media3.session.SessionCommand
20
20
  import com.google.common.collect.ImmutableList
21
+ import expo.modules.kotlin.AppContext
22
+ import expo.modules.video.R
21
23
 
22
24
  class PlaybackServiceBinder(val service: ExpoVideoPlaybackService) : Binder()
23
25
 
@@ -152,5 +154,22 @@ class ExpoVideoPlaybackService : MediaSessionService() {
152
154
  const val CHANNEL_ID = "PlaybackService"
153
155
  const val SESSION_SHOW_NOTIFICATION = "showNotification"
154
156
  const val SEEK_INTERVAL_MS = 10000L
157
+
158
+ fun startService(appContext: AppContext, context: Context, serviceConnection: PlaybackServiceConnection) {
159
+ appContext.reactContext?.apply {
160
+ val intent = Intent(context, ExpoVideoPlaybackService::class.java)
161
+ intent.action = SERVICE_INTERFACE
162
+
163
+ startService(intent)
164
+
165
+ val flags = if (Build.VERSION.SDK_INT >= 29) {
166
+ BIND_AUTO_CREATE or Context.BIND_INCLUDE_CAPABILITIES
167
+ } else {
168
+ BIND_AUTO_CREATE
169
+ }
170
+
171
+ bindService(intent, serviceConnection, flags)
172
+ }
173
+ }
155
174
  }
156
175
  }
@@ -0,0 +1,36 @@
1
+ package expo.modules.video.playbackService
2
+
3
+ import android.content.ComponentName
4
+ import android.content.ServiceConnection
5
+ import android.os.IBinder
6
+ import android.util.Log
7
+ import androidx.media3.exoplayer.ExoPlayer
8
+ import java.lang.ref.WeakReference
9
+
10
+ class PlaybackServiceConnection(val player: WeakReference<ExoPlayer>) : ServiceConnection {
11
+ var playbackServiceBinder: PlaybackServiceBinder? = null
12
+
13
+ override fun onServiceConnected(componentName: ComponentName, binder: IBinder) {
14
+ val player: ExoPlayer = player.get() ?: return
15
+ playbackServiceBinder = binder as? PlaybackServiceBinder
16
+ playbackServiceBinder?.service?.registerPlayer(player) ?: run {
17
+ Log.w(
18
+ "ExpoVideo",
19
+ "Expo Video could not bind to the playback service. " +
20
+ "This will cause issues with playback notifications and sustaining background playback."
21
+ )
22
+ }
23
+ }
24
+
25
+ override fun onServiceDisconnected(componentName: ComponentName) {
26
+ playbackServiceBinder = null
27
+ }
28
+
29
+ override fun onNullBinding(componentName: ComponentName) {
30
+ Log.w(
31
+ "ExpoVideo",
32
+ "Expo Video could not bind to the playback service. " +
33
+ "This will cause issues with playback notifications and sustaining background playback."
34
+ )
35
+ }
36
+ }
@@ -1,4 +1,4 @@
1
- package expo.modules.video
1
+ package expo.modules.video.playbackService
2
2
 
3
3
  import android.os.Bundle
4
4
  import androidx.media3.common.Player
@@ -1,5 +1,6 @@
1
1
  package expo.modules.video.records
2
2
  import android.content.Context
3
+ import android.net.Uri
3
4
  import androidx.annotation.OptIn
4
5
  import androidx.media3.common.MediaItem
5
6
  import androidx.media3.common.MediaMetadata
@@ -13,7 +14,7 @@ import java.io.Serializable
13
14
 
14
15
  @OptIn(UnstableApi::class)
15
16
  class VideoSource(
16
- @Field var uri: String? = null,
17
+ @Field var uri: Uri? = null,
17
18
  @Field var drm: DRMOptions? = null,
18
19
  @Field var metadata: VideoMetadata? = null,
19
20
  @Field var headers: Map<String, String>? = null
@@ -37,7 +38,7 @@ class VideoSource(
37
38
  fun toMediaItem() = MediaItem
38
39
  .Builder()
39
40
  .apply {
40
- setUri(uri ?: "")
41
+ setUri(uri)
41
42
  setMediaId(toMediaId())
42
43
  drm?.let {
43
44
  if (it.type.isSupported()) {
@@ -4,7 +4,7 @@ import expo.modules.kotlin.records.Field
4
4
  import expo.modules.kotlin.records.Record
5
5
  import java.io.Serializable
6
6
 
7
- internal class VolumeEvent(
7
+ class VolumeEvent(
8
8
  @Field var volume: Float? = null,
9
9
  @Field var isMuted: Boolean? = null
10
10
  ) : Record, Serializable
@@ -5,6 +5,8 @@ import android.content.pm.ApplicationInfo
5
5
  import androidx.annotation.OptIn
6
6
  import androidx.media3.common.util.UnstableApi
7
7
  import androidx.media3.common.util.Util
8
+ import androidx.media3.datasource.DataSource
9
+ import androidx.media3.datasource.DefaultDataSource
8
10
  import androidx.media3.datasource.okhttp.OkHttpDataSource
9
11
  import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
10
12
  import androidx.media3.exoplayer.source.MediaSource
@@ -12,7 +14,16 @@ import expo.modules.video.records.VideoSource
12
14
  import okhttp3.OkHttpClient
13
15
 
14
16
  @OptIn(UnstableApi::class)
15
- fun buildDataSourceFactory(context: Context, videoSource: VideoSource): OkHttpDataSource.Factory {
17
+ fun buildDataSourceFactory(context: Context, videoSource: VideoSource): DataSource.Factory {
18
+ return if (videoSource.uri?.scheme?.startsWith("http") == true) {
19
+ buildOkHttpDataSourceFactory(context, videoSource)
20
+ } else {
21
+ DefaultDataSource.Factory(context)
22
+ }
23
+ }
24
+
25
+ @OptIn(UnstableApi::class)
26
+ fun buildOkHttpDataSourceFactory(context: Context, videoSource: VideoSource): OkHttpDataSource.Factory {
16
27
  val client = OkHttpClient.Builder().build()
17
28
  val userAgent = Util.getUserAgent(context, getApplicationName(context))
18
29
  return OkHttpDataSource.Factory(client).apply {
@@ -25,7 +36,7 @@ fun buildDataSourceFactory(context: Context, videoSource: VideoSource): OkHttpDa
25
36
  }
26
37
  }
27
38
 
28
- fun buildMediaSourceFactory(context: Context, dataSourceFactory: OkHttpDataSource.Factory): MediaSource.Factory {
39
+ fun buildMediaSourceFactory(context: Context, dataSourceFactory: DataSource.Factory): MediaSource.Factory {
29
40
  return DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)
30
41
  }
31
42
 
@@ -1,4 +1,4 @@
1
- package expo.modules.video
1
+ package expo.modules.video.utils
2
2
 
3
3
  import com.facebook.yoga.YogaConstants
4
4
 
@@ -107,7 +107,7 @@ export type VideoPlayerEvents = {
107
107
  /**
108
108
  * Handler for an event emitted when the status of the player changes.
109
109
  */
110
- statusChange(newStatus: VideoPlayerStatus, oldStatus: VideoPlayerStatus, error: PlayerError): void;
110
+ statusChange(newStatus: VideoPlayerStatus, oldStatus: VideoPlayerStatus, error?: PlayerError): void;
111
111
  /**
112
112
  * Handler for an event emitted when the player starts or stops playback.
113
113
  */
@@ -1 +1 @@
1
- {"version":3,"file":"VideoPlayer.types.d.ts","sourceRoot":"","sources":["../src/VideoPlayer.types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"VideoPlayer.types.d.ts","sourceRoot":"","sources":["../src/VideoPlayer.types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"VideoPlayer.types.js","sourceRoot":"","sources":["../src/VideoPlayer.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { SharedObject } from 'expo-modules-core';\n\n/**\n * A class that represents an instance of the video player.\n */\nexport declare class VideoPlayer extends SharedObject<VideoPlayerEvents> {\n /**\n * Boolean value whether the player is currently playing.\n * > This property is get-only, use `play` and `pause` methods to control the playback.\n */\n playing: boolean;\n\n /**\n * Determines whether the player should automatically replay after reaching the end of the video.\n * @default false\n */\n loop: boolean;\n\n /**\n * Boolean value whether the player is currently muted.\n * Setting this property to `true`/`false` will mute/unmute the player.\n * @default false\n */\n muted: boolean;\n\n /**\n * Float value indicating the current playback time in seconds.\n *\n * If the player is not yet playing, this value indicates the time position\n * at which playback will begin once the `play()` method is called.\n *\n * Setting `currentTime` to a new value seeks the player to the given time.\n */\n currentTime: number;\n\n /**\n * Float value indicating the duration of the current video in seconds.\n * > This property is get-only\n */\n duration: number;\n\n /**\n * Float value between 0 and 1 representing the current volume.\n * Muting the player doesn't affect the volume. In other words, when the player is muted, the volume is the same as\n * when unmuted. Similarly, setting the volume doesn't unmute the player.\n * @default 1.0\n */\n volume: number;\n\n /**\n * Boolean value indicating if the player should correct audio pitch when the playback speed changes.\n * > On web, changing this property is not supported, the player will always correct the pitch.\n * @default true\n * @platform android\n * @platform ios\n */\n preservesPitch: boolean;\n\n /**\n * Float value between 0 and 16 indicating the current playback speed of the player.\n * @default 1.0\n */\n playbackRate: number;\n\n /**\n * Boolean value indicating whether the player is currently playing a live stream.\n * > This property is get-only\n */\n isLive: boolean;\n\n /**\n * Indicates the current status of the player.\n * > This property is get-only\n */\n status: VideoPlayerStatus;\n\n /**\n * Boolean value determining whether the player should show the now playing notification.\n */\n showNowPlayingNotification: boolean;\n\n /**\n * Determines whether the player should continue playing after the app enters the background.\n * @default false\n * @platform ios\n * @platform android\n */\n staysActiveInBackground: boolean;\n\n /**\n * Initializes a new video player instance with the given source.\n * @hidden\n */\n constructor(source: VideoSource);\n\n /**\n * Resumes the player.\n */\n play(): void;\n\n /**\n * Pauses the player.\n */\n pause(): void;\n\n /**\n * Replaces the current source with a new one.\n */\n replace(source: VideoSource): void;\n\n /**\n * Seeks the playback by the given number of seconds.\n */\n seekBy(seconds: number): void;\n\n /**\n * Seeks the playback to the beginning.\n */\n replay(): void;\n}\n\n/**\n * Handlers for events which can be emitted by the player.\n */\nexport type VideoPlayerEvents = {\n /**\n * Handler for an event emitted when the status of the player changes.\n */\n statusChange(\n newStatus: VideoPlayerStatus,\n oldStatus: VideoPlayerStatus,\n error: PlayerError\n ): void;\n /**\n * Handler for an event emitted when the player starts or stops playback.\n */\n playingChange(newIsPlaying: boolean, oldIsPlaying: boolean): void;\n /**\n * Handler for an event emitted when the `playbackRate` property of the player changes.\n */\n playbackRateChange(newPlaybackRate: number, oldPlaybackRate: number): void;\n /**\n * Handler for an event emitted when the `volume` property of the player changes.\n */\n volumeChange(newVolume: VolumeEvent, oldVolume: VolumeEvent): void;\n /**\n * Handler for an event emitted when the player plays to the end of the current source.\n */\n playToEnd(): void;\n /**\n * Handler for an event emitted when the current media source of the player changes.\n */\n sourceChange(newSource: VideoSource, previousSource: VideoSource): void;\n};\n\n/**\n * Describes the current status of the player.\n * - `idle`: The player is not playing or loading any videos.\n * - `loading`: The player is loading video data from the provided source\n * - `readyToPlay`: The player has loaded enough data to start playing or to continue playback.\n * - `error`: The player has encountered an error while loading or playing the video.\n */\nexport type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error';\n\nexport type VideoSource =\n | string\n | {\n /**\n * The URI of the video.\n */\n uri: string;\n /**\n * Specifies the DRM options which will be used by the player while loading the video.\n */\n drm?: DRMOptions;\n /**\n * Specifies information which will be displayed in the now playing notification.\n * When undefined the player will display information contained in the video metadata.\n */\n metadata?: VideoMetadata;\n /**\n * Specifies headers sent with the video request.\n * > For DRM license headers use the `headers` field of [`DRMOptions`](#drmoptions).\n * @platform android\n * @platform ios\n */\n headers?: Record<string, string>;\n }\n | null;\n\n/**\n * Contains information about any errors that the player encountered during the playback\n */\nexport type PlayerError = {\n message: string;\n};\n\n/**\n * Contains information about the current volume and whether the player is muted.\n */\nexport type VolumeEvent = {\n volume: number;\n isMuted: boolean;\n};\n\n/**\n * Contains information that will be displayed in the now playing notification when the video is playing.\n */\nexport type VideoMetadata = {\n /**\n * The title of the video.\n */\n title?: string;\n /**\n * Secondary text that will be displayed under the title.\n */\n artist?: string;\n};\n\n/**\n * Specifies which type of DRM to use. Android supports Widevine, PlayReady and ClearKey, iOS supports FairPlay.\n */\nexport type DRMType = 'clearkey' | 'fairplay' | 'playready' | 'widevine';\n\n/**\n * Specifies DRM options which will be used by the player while loading the video.\n */\nexport type DRMOptions = {\n /**\n * Determines which type of DRM to use.\n */\n type: DRMType;\n\n /**\n * Determines the license server URL.\n */\n licenseServer: string;\n\n /**\n * Determines headers sent to the license server on license requests.\n */\n headers?: { [key: string]: string };\n\n /**\n * Specifies whether the DRM is a multi-key DRM.\n * @platform android\n */\n multiKey?: boolean;\n\n /**\n * Specifies the content ID of the stream.\n * @platform ios\n */\n contentId?: string;\n\n /**\n * Specifies the certificate URL for the FairPlay DRM.\n * @platform ios\n */\n certificateUrl?: string;\n\n /**\n * Specifies the base64 encoded certificate data for the FairPlay DRM.\n * When this property is set, the `certificateUrl` property is ignored.\n * @platform ios\n */\n base64CertificateData?: string;\n};\n"]}
1
+ {"version":3,"file":"VideoPlayer.types.js","sourceRoot":"","sources":["../src/VideoPlayer.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { SharedObject } from 'expo-modules-core';\n\n/**\n * A class that represents an instance of the video player.\n */\nexport declare class VideoPlayer extends SharedObject<VideoPlayerEvents> {\n /**\n * Boolean value whether the player is currently playing.\n * > This property is get-only, use `play` and `pause` methods to control the playback.\n */\n playing: boolean;\n\n /**\n * Determines whether the player should automatically replay after reaching the end of the video.\n * @default false\n */\n loop: boolean;\n\n /**\n * Boolean value whether the player is currently muted.\n * Setting this property to `true`/`false` will mute/unmute the player.\n * @default false\n */\n muted: boolean;\n\n /**\n * Float value indicating the current playback time in seconds.\n *\n * If the player is not yet playing, this value indicates the time position\n * at which playback will begin once the `play()` method is called.\n *\n * Setting `currentTime` to a new value seeks the player to the given time.\n */\n currentTime: number;\n\n /**\n * Float value indicating the duration of the current video in seconds.\n * > This property is get-only\n */\n duration: number;\n\n /**\n * Float value between 0 and 1 representing the current volume.\n * Muting the player doesn't affect the volume. In other words, when the player is muted, the volume is the same as\n * when unmuted. Similarly, setting the volume doesn't unmute the player.\n * @default 1.0\n */\n volume: number;\n\n /**\n * Boolean value indicating if the player should correct audio pitch when the playback speed changes.\n * > On web, changing this property is not supported, the player will always correct the pitch.\n * @default true\n * @platform android\n * @platform ios\n */\n preservesPitch: boolean;\n\n /**\n * Float value between 0 and 16 indicating the current playback speed of the player.\n * @default 1.0\n */\n playbackRate: number;\n\n /**\n * Boolean value indicating whether the player is currently playing a live stream.\n * > This property is get-only\n */\n isLive: boolean;\n\n /**\n * Indicates the current status of the player.\n * > This property is get-only\n */\n status: VideoPlayerStatus;\n\n /**\n * Boolean value determining whether the player should show the now playing notification.\n */\n showNowPlayingNotification: boolean;\n\n /**\n * Determines whether the player should continue playing after the app enters the background.\n * @default false\n * @platform ios\n * @platform android\n */\n staysActiveInBackground: boolean;\n\n /**\n * Initializes a new video player instance with the given source.\n * @hidden\n */\n constructor(source: VideoSource);\n\n /**\n * Resumes the player.\n */\n play(): void;\n\n /**\n * Pauses the player.\n */\n pause(): void;\n\n /**\n * Replaces the current source with a new one.\n */\n replace(source: VideoSource): void;\n\n /**\n * Seeks the playback by the given number of seconds.\n */\n seekBy(seconds: number): void;\n\n /**\n * Seeks the playback to the beginning.\n */\n replay(): void;\n}\n\n/**\n * Handlers for events which can be emitted by the player.\n */\nexport type VideoPlayerEvents = {\n /**\n * Handler for an event emitted when the status of the player changes.\n */\n statusChange(\n newStatus: VideoPlayerStatus,\n oldStatus: VideoPlayerStatus,\n error?: PlayerError\n ): void;\n /**\n * Handler for an event emitted when the player starts or stops playback.\n */\n playingChange(newIsPlaying: boolean, oldIsPlaying: boolean): void;\n /**\n * Handler for an event emitted when the `playbackRate` property of the player changes.\n */\n playbackRateChange(newPlaybackRate: number, oldPlaybackRate: number): void;\n /**\n * Handler for an event emitted when the `volume` property of the player changes.\n */\n volumeChange(newVolume: VolumeEvent, oldVolume: VolumeEvent): void;\n /**\n * Handler for an event emitted when the player plays to the end of the current source.\n */\n playToEnd(): void;\n /**\n * Handler for an event emitted when the current media source of the player changes.\n */\n sourceChange(newSource: VideoSource, previousSource: VideoSource): void;\n};\n\n/**\n * Describes the current status of the player.\n * - `idle`: The player is not playing or loading any videos.\n * - `loading`: The player is loading video data from the provided source\n * - `readyToPlay`: The player has loaded enough data to start playing or to continue playback.\n * - `error`: The player has encountered an error while loading or playing the video.\n */\nexport type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error';\n\nexport type VideoSource =\n | string\n | {\n /**\n * The URI of the video.\n */\n uri: string;\n /**\n * Specifies the DRM options which will be used by the player while loading the video.\n */\n drm?: DRMOptions;\n /**\n * Specifies information which will be displayed in the now playing notification.\n * When undefined the player will display information contained in the video metadata.\n */\n metadata?: VideoMetadata;\n /**\n * Specifies headers sent with the video request.\n * > For DRM license headers use the `headers` field of [`DRMOptions`](#drmoptions).\n * @platform android\n * @platform ios\n */\n headers?: Record<string, string>;\n }\n | null;\n\n/**\n * Contains information about any errors that the player encountered during the playback\n */\nexport type PlayerError = {\n message: string;\n};\n\n/**\n * Contains information about the current volume and whether the player is muted.\n */\nexport type VolumeEvent = {\n volume: number;\n isMuted: boolean;\n};\n\n/**\n * Contains information that will be displayed in the now playing notification when the video is playing.\n */\nexport type VideoMetadata = {\n /**\n * The title of the video.\n */\n title?: string;\n /**\n * Secondary text that will be displayed under the title.\n */\n artist?: string;\n};\n\n/**\n * Specifies which type of DRM to use. Android supports Widevine, PlayReady and ClearKey, iOS supports FairPlay.\n */\nexport type DRMType = 'clearkey' | 'fairplay' | 'playready' | 'widevine';\n\n/**\n * Specifies DRM options which will be used by the player while loading the video.\n */\nexport type DRMOptions = {\n /**\n * Determines which type of DRM to use.\n */\n type: DRMType;\n\n /**\n * Determines the license server URL.\n */\n licenseServer: string;\n\n /**\n * Determines headers sent to the license server on license requests.\n */\n headers?: { [key: string]: string };\n\n /**\n * Specifies whether the DRM is a multi-key DRM.\n * @platform android\n */\n multiKey?: boolean;\n\n /**\n * Specifies the content ID of the stream.\n * @platform ios\n */\n contentId?: string;\n\n /**\n * Specifies the certificate URL for the FairPlay DRM.\n * @platform ios\n */\n certificateUrl?: string;\n\n /**\n * Specifies the base64 encoded certificate data for the FairPlay DRM.\n * When this property is set, the `certificateUrl` property is ignored.\n * @platform ios\n */\n base64CertificateData?: string;\n};\n"]}