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.
- package/CHANGELOG.md +28 -0
- package/android/build.gradle +2 -2
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/java/expo/modules/video/AudioFocusManager.kt +187 -0
- package/android/src/main/java/expo/modules/video/PlayerEvent.kt +54 -0
- package/android/src/main/java/expo/modules/video/VideoExceptions.kt +3 -0
- package/android/src/main/java/expo/modules/video/VideoManager.kt +11 -2
- package/android/src/main/java/expo/modules/video/VideoModule.kt +13 -6
- package/android/src/main/java/expo/modules/video/VideoPlayer.kt +59 -109
- package/android/src/main/java/expo/modules/video/VideoPlayerListener.kt +18 -0
- package/android/src/main/java/expo/modules/video/VideoView.kt +27 -5
- package/android/src/main/java/expo/modules/video/delegates/IgnoreSameSet.kt +24 -0
- package/android/src/main/java/expo/modules/video/drawing/OutlineProvider.kt +1 -1
- package/android/src/main/java/expo/modules/video/{ContentFit.kt → enums/ContentFit.kt} +1 -1
- package/android/src/main/java/expo/modules/video/{ExpoVideoPlaybackService.kt → playbackService/ExpoVideoPlaybackService.kt} +20 -1
- package/android/src/main/java/expo/modules/video/playbackService/PlaybackServiceConnection.kt +36 -0
- package/android/src/main/java/expo/modules/video/{VideoMediaSessionCallback.kt → playbackService/VideoMediaSessionCallback.kt} +1 -1
- package/android/src/main/java/expo/modules/video/records/VideoSource.kt +3 -2
- package/android/src/main/java/expo/modules/video/records/VolumeEvent.kt +1 -1
- package/android/src/main/java/expo/modules/video/{DataSourceUtils.kt → utils/DataSourceUtils.kt} +13 -2
- package/android/src/main/java/expo/modules/video/{YogaUtils.kt → utils/YogaUtils.kt} +1 -1
- package/build/VideoPlayer.types.d.ts +1 -1
- package/build/VideoPlayer.types.d.ts.map +1 -1
- package/build/VideoPlayer.types.js.map +1 -1
- package/build/VideoPlayer.web.d.ts +9 -1
- package/build/VideoPlayer.web.d.ts.map +1 -1
- package/build/VideoPlayer.web.js +61 -13
- package/build/VideoPlayer.web.js.map +1 -1
- package/build/VideoView.d.ts +3 -0
- package/build/VideoView.d.ts.map +1 -1
- package/build/VideoView.js +3 -0
- package/build/VideoView.js.map +1 -1
- package/build/VideoView.types.d.ts +13 -0
- package/build/VideoView.types.d.ts.map +1 -1
- package/build/VideoView.types.js.map +1 -1
- package/build/VideoView.web.d.ts.map +1 -1
- package/build/VideoView.web.js +42 -13
- package/build/VideoView.web.js.map +1 -1
- package/ios/NowPlayingManager.swift +6 -10
- package/ios/VideoModule.swift +19 -0
- package/ios/VideoPlayer.swift +7 -1
- package/package.json +2 -2
- package/plugin/build/withExpoVideo.d.ts +5 -1
- package/plugin/build/withExpoVideo.js +21 -3
- package/plugin/src/withExpoVideo.ts +35 -3
- package/src/VideoPlayer.types.ts +1 -1
- package/src/VideoPlayer.web.tsx +72 -13
- package/src/VideoView.tsx +3 -0
- package/src/VideoView.types.ts +14 -0
- package/src/VideoView.web.tsx +49 -14
- 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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
213
|
+
sendEvent(PlayerEvent.PlayedToEnd())
|
|
280
214
|
}
|
|
281
215
|
|
|
282
|
-
if (this.status !=
|
|
283
|
-
|
|
216
|
+
if (this.status != oldStatus) {
|
|
217
|
+
sendEvent(PlayerEvent.StatusChanged(status, oldStatus, playbackError))
|
|
284
218
|
}
|
|
285
|
-
|
|
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
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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.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,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:
|
|
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
|
-
|
|
7
|
+
class VolumeEvent(
|
|
8
8
|
@Field var volume: Float? = null,
|
|
9
9
|
@Field var isMuted: Boolean? = null
|
|
10
10
|
) : Record, Serializable
|
package/android/src/main/java/expo/modules/video/{DataSourceUtils.kt → utils/DataSourceUtils.kt}
RENAMED
|
@@ -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):
|
|
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:
|
|
39
|
+
fun buildMediaSourceFactory(context: Context, dataSourceFactory: DataSource.Factory): MediaSource.Factory {
|
|
29
40
|
return DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)
|
|
30
41
|
}
|
|
31
42
|
|
|
@@ -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
|
|
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":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,YAAY,CAAC,iBAAiB,CAAC;IACtE;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,IAAI,EAAE,OAAO,CAAC;IAEd;;;;OAIG;IACH,KAAK,EAAE,OAAO,CAAC;IAEf;;;;;;;OAOG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;;;;;OAMG;IACH,cAAc,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;;OAGG;IACH,MAAM,EAAE,iBAAiB,CAAC;IAE1B;;OAEG;IACH,0BAA0B,EAAE,OAAO,CAAC;IAEpC;;;;;OAKG;IACH,uBAAuB,EAAE,OAAO,CAAC;IAEjC;;;OAGG;gBACS,MAAM,EAAE,WAAW;IAE/B;;OAEG;IACH,IAAI,IAAI,IAAI;IAEZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAEb;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAElC;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAE7B;;OAEG;IACH,MAAM,IAAI,IAAI;CACf;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;OAEG;IACH,YAAY,CACV,SAAS,EAAE,iBAAiB,EAC5B,SAAS,EAAE,iBAAiB,EAC5B,KAAK,EAAE,WAAW,
|
|
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
|
|
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"]}
|