@mux/mux-react-native-player 0.1.0
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/MuxReactNativePlayer.podspec +37 -0
- package/README.md +134 -0
- package/android/build.gradle +33 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/mux/reactnativeplayer/MuxReactNativePlayerModule.kt +135 -0
- package/android/src/main/java/com/mux/reactnativeplayer/MuxVideoRecords.kt +174 -0
- package/android/src/main/java/com/mux/reactnativeplayer/MuxVideoView.kt +452 -0
- package/android/src/main/res/layout/mux_video_player_view.xml +6 -0
- package/assets/MuxRobot_02.gif +0 -0
- package/assets/MuxRobot_02@2x.gif +0 -0
- package/assets/MuxRobot_03.gif +0 -0
- package/assets/MuxRobot_03@2x.gif +0 -0
- package/assets/MuxRobot_04.gif +0 -0
- package/assets/MuxRobot_04@2x.gif +0 -0
- package/assets/MuxRobot_05.gif +0 -0
- package/assets/MuxRobot_05@2x.gif +0 -0
- package/build/MuxVideoControls.d.ts +21 -0
- package/build/MuxVideoControls.d.ts.map +1 -0
- package/build/MuxVideoControls.js +1032 -0
- package/build/MuxVideoPlayer.d.ts +59 -0
- package/build/MuxVideoPlayer.d.ts.map +1 -0
- package/build/MuxVideoPlayer.js +265 -0
- package/build/MuxVideoView.d.ts +39 -0
- package/build/MuxVideoView.d.ts.map +1 -0
- package/build/MuxVideoView.js +254 -0
- package/build/NativeMuxVideoView.d.ts +5 -0
- package/build/NativeMuxVideoView.d.ts.map +1 -0
- package/build/NativeMuxVideoView.js +4 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +3 -0
- package/build/normalizeSource.d.ts +7 -0
- package/build/normalizeSource.d.ts.map +1 -0
- package/build/normalizeSource.js +76 -0
- package/build/screenOrientation.d.ts +3 -0
- package/build/screenOrientation.d.ts.map +1 -0
- package/build/screenOrientation.js +38 -0
- package/build/types.d.ts +170 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +1 -0
- package/expo-module.config.json +13 -0
- package/ios/MuxReactNativePlayerModule.swift +139 -0
- package/ios/MuxVideoRecords.swift +212 -0
- package/ios/MuxVideoView.swift +502 -0
- package/package.json +69 -0
- package/plugin/index.d.ts +11 -0
- package/plugin/index.js +1 -0
- package/plugin/withMuxReactNativePlayer.js +203 -0
- package/src/MuxVideoControls.tsx +1772 -0
- package/src/MuxVideoPlayer.ts +338 -0
- package/src/MuxVideoView.tsx +412 -0
- package/src/NativeMuxVideoView.ts +15 -0
- package/src/index.ts +32 -0
- package/src/normalizeSource.ts +101 -0
- package/src/screenOrientation.ts +46 -0
- package/src/types.ts +228 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
package com.mux.reactnativeplayer
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.os.Handler
|
|
5
|
+
import android.os.Looper
|
|
6
|
+
import android.view.LayoutInflater
|
|
7
|
+
import android.view.ViewGroup
|
|
8
|
+
import androidx.annotation.OptIn
|
|
9
|
+
import androidx.media3.common.C
|
|
10
|
+
import androidx.media3.common.PlaybackException
|
|
11
|
+
import androidx.media3.common.Player
|
|
12
|
+
import androidx.media3.common.TrackGroup
|
|
13
|
+
import androidx.media3.common.TrackSelectionOverride
|
|
14
|
+
import androidx.media3.common.Tracks
|
|
15
|
+
import androidx.media3.exoplayer.DefaultLoadControl
|
|
16
|
+
import androidx.media3.common.util.UnstableApi
|
|
17
|
+
import androidx.media3.ui.AspectRatioFrameLayout
|
|
18
|
+
import androidx.media3.ui.PlayerView
|
|
19
|
+
import com.mux.player.MuxPlayer
|
|
20
|
+
import expo.modules.kotlin.AppContext
|
|
21
|
+
import expo.modules.kotlin.viewevent.EventDispatcher
|
|
22
|
+
import expo.modules.kotlin.views.ExpoView
|
|
23
|
+
|
|
24
|
+
@OptIn(UnstableApi::class)
|
|
25
|
+
class MuxVideoView(
|
|
26
|
+
context: Context,
|
|
27
|
+
appContext: AppContext,
|
|
28
|
+
) : ExpoView(context, appContext) {
|
|
29
|
+
private val onStatusChange by EventDispatcher()
|
|
30
|
+
private val onPlayingChange by EventDispatcher()
|
|
31
|
+
private val onTimeUpdate by EventDispatcher()
|
|
32
|
+
private val onSourceLoad by EventDispatcher()
|
|
33
|
+
private val onSourceError by EventDispatcher()
|
|
34
|
+
|
|
35
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
36
|
+
private val playerView = (LayoutInflater.from(context)
|
|
37
|
+
.inflate(R.layout.mux_video_player_view, this, false) as PlayerView).also {
|
|
38
|
+
it.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
39
|
+
it.useController = true
|
|
40
|
+
it.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
|
|
41
|
+
it.setKeepContentOnPlayerReset(true)
|
|
42
|
+
addView(it)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private var player: MuxPlayer? = null
|
|
46
|
+
private var sourceFingerprint: String? = null
|
|
47
|
+
private var currentPlaybackId: String? = null
|
|
48
|
+
private var didEmitSourceLoad = false
|
|
49
|
+
private var muted = false
|
|
50
|
+
private var volume = 1f
|
|
51
|
+
private var loop = false
|
|
52
|
+
private var playbackRate = 1f
|
|
53
|
+
private var shouldPlay = false
|
|
54
|
+
private var timeUpdateEventIntervalMs = 500L
|
|
55
|
+
private var startupBufferDurationMs = 0L
|
|
56
|
+
|
|
57
|
+
private data class TextTrackSelection(
|
|
58
|
+
val id: String,
|
|
59
|
+
val trackGroup: TrackGroup,
|
|
60
|
+
val trackIndex: Int,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
private val timeUpdateRunnable = object : Runnable {
|
|
64
|
+
override fun run() {
|
|
65
|
+
sendTimeUpdate()
|
|
66
|
+
mainHandler.postDelayed(this, timeUpdateEventIntervalMs)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private val listener = object : Player.Listener {
|
|
71
|
+
override fun onPlaybackStateChanged(playbackState: Int) {
|
|
72
|
+
if (playbackState == Player.STATE_READY && !didEmitSourceLoad) {
|
|
73
|
+
didEmitSourceLoad = true
|
|
74
|
+
onSourceLoad(
|
|
75
|
+
mapOf(
|
|
76
|
+
"playbackId" to currentPlaybackId.orEmpty(),
|
|
77
|
+
"duration" to durationSeconds(),
|
|
78
|
+
"captionTracks" to captionTracksPayload(),
|
|
79
|
+
"selectedCaptionTrackId" to selectedCaptionTrackId().orEmpty(),
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
if (playbackState == Player.STATE_READY && shouldPlay && player?.isPlaying != true) {
|
|
84
|
+
player?.play()
|
|
85
|
+
}
|
|
86
|
+
sendStatusChange()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
|
90
|
+
onPlayingChange(mapOf("isPlaying" to isPlaying))
|
|
91
|
+
sendStatusChange()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
override fun onPlayerError(error: PlaybackException) {
|
|
95
|
+
val message = error.localizedMessage ?: "Mux playback failed."
|
|
96
|
+
onSourceError(
|
|
97
|
+
mutableMapOf<String, Any>(
|
|
98
|
+
"playbackId" to currentPlaybackId.orEmpty(),
|
|
99
|
+
"message" to message,
|
|
100
|
+
"code" to error.errorCodeName
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
sendStatusChange("error", message)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
override fun onTracksChanged(tracks: Tracks) {
|
|
107
|
+
sendStatusChange()
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
override fun onAttachedToWindow() {
|
|
112
|
+
super.onAttachedToWindow()
|
|
113
|
+
startTimeUpdates()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
override fun onDetachedFromWindow() {
|
|
117
|
+
stopTimeUpdates()
|
|
118
|
+
super.onDetachedFromWindow()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fun setSource(source: MuxVideoSourceRecord?) {
|
|
122
|
+
if (source == null) {
|
|
123
|
+
release()
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (source.fingerprint == sourceFingerprint) {
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
releasePlayer()
|
|
132
|
+
sourceFingerprint = source.fingerprint
|
|
133
|
+
currentPlaybackId = source.playbackId
|
|
134
|
+
didEmitSourceLoad = false
|
|
135
|
+
sendStatusChange("loading")
|
|
136
|
+
|
|
137
|
+
val bufferForPlaybackMs = startupBufferDurationMs.coerceAtLeast(500L).toInt()
|
|
138
|
+
val bufferForPlaybackAfterRebufferMs = startupBufferDurationMs.coerceAtLeast(1_500L).toInt()
|
|
139
|
+
val minBufferMs = startupBufferDurationMs.coerceAtLeast(3_000L).toInt()
|
|
140
|
+
val loadControl = DefaultLoadControl.Builder()
|
|
141
|
+
.setBufferDurationsMs(
|
|
142
|
+
minBufferMs,
|
|
143
|
+
12_000,
|
|
144
|
+
bufferForPlaybackMs,
|
|
145
|
+
bufferForPlaybackAfterRebufferMs,
|
|
146
|
+
)
|
|
147
|
+
.setPrioritizeTimeOverSizeThresholds(true)
|
|
148
|
+
.build()
|
|
149
|
+
|
|
150
|
+
val builder = MuxPlayer.Builder(context)
|
|
151
|
+
.enableSmartCache(true)
|
|
152
|
+
.applyExoConfig {
|
|
153
|
+
setLoadControl(loadControl)
|
|
154
|
+
}
|
|
155
|
+
.addMonitoringData(source.toCustomerData())
|
|
156
|
+
|
|
157
|
+
source.metadata?.envKey?.takeIf { it.isNotBlank() }?.let {
|
|
158
|
+
builder.setMuxDataEnv(it)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
val nextPlayer = builder.build()
|
|
162
|
+
nextPlayer.addListener(listener)
|
|
163
|
+
nextPlayer.setMediaItem(source.toMediaItem())
|
|
164
|
+
nextPlayer.playWhenReady = shouldPlay
|
|
165
|
+
nextPlayer.prepare()
|
|
166
|
+
|
|
167
|
+
player = nextPlayer
|
|
168
|
+
playerView.player = nextPlayer
|
|
169
|
+
applyPlayerConfiguration()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
fun setNativeControls(enabled: Boolean) {
|
|
173
|
+
playerView.useController = enabled
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
fun setContentFit(contentFit: String) {
|
|
177
|
+
playerView.resizeMode = when (contentFit) {
|
|
178
|
+
"cover" -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
|
179
|
+
"fill" -> AspectRatioFrameLayout.RESIZE_MODE_FILL
|
|
180
|
+
else -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
fun setTimeUpdateEventInterval(interval: Double) {
|
|
185
|
+
timeUpdateEventIntervalMs = (interval.coerceAtLeast(0.1) * 1000).toLong()
|
|
186
|
+
stopTimeUpdates()
|
|
187
|
+
startTimeUpdates()
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
fun setStartupBufferDuration(duration: Double) {
|
|
191
|
+
startupBufferDurationMs = (duration.coerceAtLeast(0.0) * 1000).toLong()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fun setPlayWhenReady(playWhenReady: Boolean) {
|
|
195
|
+
if (shouldPlay == playWhenReady) {
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (playWhenReady) {
|
|
200
|
+
play()
|
|
201
|
+
} else {
|
|
202
|
+
pause()
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
fun setMuted(muted: Boolean) {
|
|
207
|
+
this.muted = muted
|
|
208
|
+
player?.volume = if (muted) 0f else volume
|
|
209
|
+
sendStatusChange()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
fun setVolume(volume: Double) {
|
|
213
|
+
this.volume = volume.coerceIn(0.0, 1.0).toFloat()
|
|
214
|
+
if (!muted) {
|
|
215
|
+
player?.volume = this.volume
|
|
216
|
+
}
|
|
217
|
+
sendStatusChange()
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
fun setLoop(loop: Boolean) {
|
|
221
|
+
this.loop = loop
|
|
222
|
+
player?.repeatMode = if (loop) Player.REPEAT_MODE_ONE else Player.REPEAT_MODE_OFF
|
|
223
|
+
sendStatusChange()
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
fun setPlaybackRate(rate: Double) {
|
|
227
|
+
playbackRate = rate.coerceIn(0.25, 4.0).toFloat()
|
|
228
|
+
player?.setPlaybackSpeed(playbackRate)
|
|
229
|
+
sendStatusChange()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
fun setCaptionTrack(trackId: String?) {
|
|
233
|
+
val currentPlayer = player ?: return
|
|
234
|
+
val builder = currentPlayer.trackSelectionParameters.buildUpon()
|
|
235
|
+
|
|
236
|
+
if (trackId == null) {
|
|
237
|
+
currentPlayer.trackSelectionParameters = builder
|
|
238
|
+
.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, true)
|
|
239
|
+
.build()
|
|
240
|
+
sendStatusChange()
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
val selection = findTextTrack(trackId) ?: return
|
|
245
|
+
currentPlayer.trackSelectionParameters = builder
|
|
246
|
+
.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, false)
|
|
247
|
+
.setOverrideForType(
|
|
248
|
+
TrackSelectionOverride(selection.trackGroup, listOf(selection.trackIndex))
|
|
249
|
+
)
|
|
250
|
+
.build()
|
|
251
|
+
sendStatusChange()
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
fun play() {
|
|
255
|
+
shouldPlay = true
|
|
256
|
+
player?.play()
|
|
257
|
+
sendStatusChange()
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
fun pause() {
|
|
261
|
+
shouldPlay = false
|
|
262
|
+
player?.pause()
|
|
263
|
+
sendStatusChange()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
fun replay() {
|
|
267
|
+
shouldPlay = true
|
|
268
|
+
seekTo(0.0)
|
|
269
|
+
play()
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
fun seekBy(seconds: Double) {
|
|
273
|
+
val current = (player?.currentPosition ?: 0L) / 1000.0
|
|
274
|
+
seekTo(current + seconds)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
fun seekTo(seconds: Double) {
|
|
278
|
+
player?.seekTo((seconds.coerceAtLeast(0.0) * 1000).toLong())
|
|
279
|
+
sendStatusChange()
|
|
280
|
+
sendTimeUpdate()
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
fun release() {
|
|
284
|
+
releasePlayer()
|
|
285
|
+
sourceFingerprint = null
|
|
286
|
+
currentPlaybackId = null
|
|
287
|
+
didEmitSourceLoad = false
|
|
288
|
+
shouldPlay = false
|
|
289
|
+
sendStatusChange("idle")
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private fun applyPlayerConfiguration() {
|
|
293
|
+
player?.volume = if (muted) 0f else volume
|
|
294
|
+
player?.repeatMode = if (loop) Player.REPEAT_MODE_ONE else Player.REPEAT_MODE_OFF
|
|
295
|
+
player?.setPlaybackSpeed(playbackRate)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private fun releasePlayer() {
|
|
299
|
+
playerView.player = null
|
|
300
|
+
player?.removeListener(listener)
|
|
301
|
+
player?.release()
|
|
302
|
+
player = null
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private fun startTimeUpdates() {
|
|
306
|
+
mainHandler.removeCallbacks(timeUpdateRunnable)
|
|
307
|
+
mainHandler.postDelayed(timeUpdateRunnable, timeUpdateEventIntervalMs)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private fun stopTimeUpdates() {
|
|
311
|
+
mainHandler.removeCallbacks(timeUpdateRunnable)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private fun sendTimeUpdate() {
|
|
315
|
+
onTimeUpdate(
|
|
316
|
+
mapOf(
|
|
317
|
+
"currentTime" to currentTimeSeconds(),
|
|
318
|
+
"duration" to durationSeconds(),
|
|
319
|
+
"bufferedPosition" to bufferedPositionSeconds()
|
|
320
|
+
)
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private fun sendStatusChange(status: String? = null, error: String? = null) {
|
|
325
|
+
val payload = mutableMapOf<String, Any>(
|
|
326
|
+
"status" to (status ?: inferStatus()),
|
|
327
|
+
"currentTime" to currentTimeSeconds(),
|
|
328
|
+
"duration" to durationSeconds(),
|
|
329
|
+
"bufferedPosition" to bufferedPositionSeconds(),
|
|
330
|
+
"muted" to muted,
|
|
331
|
+
"volume" to volume.toDouble(),
|
|
332
|
+
"loop" to loop,
|
|
333
|
+
"playbackRate" to playbackRate.toDouble(),
|
|
334
|
+
"captionTracks" to captionTracksPayload(),
|
|
335
|
+
"selectedCaptionTrackId" to selectedCaptionTrackId().orEmpty(),
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
if (error != null) {
|
|
339
|
+
payload["error"] = error
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
onStatusChange(
|
|
343
|
+
payload
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private fun inferStatus(): String {
|
|
348
|
+
val player = player ?: return "idle"
|
|
349
|
+
return when (player.playbackState) {
|
|
350
|
+
Player.STATE_BUFFERING -> "buffering"
|
|
351
|
+
Player.STATE_READY -> if (player.isPlaying) "playing" else "paused"
|
|
352
|
+
Player.STATE_ENDED -> "ended"
|
|
353
|
+
Player.STATE_IDLE -> if (currentPlaybackId == null) "idle" else "loading"
|
|
354
|
+
else -> "idle"
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private fun currentTimeSeconds(): Double {
|
|
359
|
+
return ((player?.currentPosition ?: 0L).coerceAtLeast(0L)) / 1000.0
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private fun durationSeconds(): Double {
|
|
363
|
+
val duration = player?.duration ?: C.TIME_UNSET
|
|
364
|
+
return if (duration == C.TIME_UNSET || duration < 0) 0.0 else duration / 1000.0
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private fun bufferedPositionSeconds(): Double {
|
|
368
|
+
return ((player?.bufferedPosition ?: 0L).coerceAtLeast(0L)) / 1000.0
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private fun captionTracksPayload(): List<Map<String, Any>> {
|
|
372
|
+
val currentPlayer = player ?: return emptyList()
|
|
373
|
+
val tracks = mutableListOf<Map<String, Any>>()
|
|
374
|
+
var fallbackIndex = 1
|
|
375
|
+
|
|
376
|
+
currentPlayer.currentTracks.groups.forEachIndexed { groupIndex, group ->
|
|
377
|
+
if (group.type != C.TRACK_TYPE_TEXT) {
|
|
378
|
+
return@forEachIndexed
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
for (trackIndex in 0 until group.length) {
|
|
382
|
+
if (!group.isTrackSupported(trackIndex)) {
|
|
383
|
+
continue
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
val format = group.getTrackFormat(trackIndex)
|
|
387
|
+
val language = format.language
|
|
388
|
+
val label = format.label
|
|
389
|
+
?.takeIf { it.isNotBlank() }
|
|
390
|
+
?: language?.takeIf { it.isNotBlank() }
|
|
391
|
+
?: "Captions ${fallbackIndex++}"
|
|
392
|
+
val payload = mutableMapOf<String, Any>(
|
|
393
|
+
"id" to "$groupIndex:$trackIndex",
|
|
394
|
+
"label" to label,
|
|
395
|
+
"kind" to captionTrackKind(format.selectionFlags, format.roleFlags),
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
language?.takeIf { it.isNotBlank() }?.let {
|
|
399
|
+
payload["language"] = it
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
tracks.add(payload)
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return tracks
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
private fun selectedCaptionTrackId(): String? {
|
|
410
|
+
val currentPlayer = player ?: return null
|
|
411
|
+
|
|
412
|
+
currentPlayer.currentTracks.groups.forEachIndexed { groupIndex, group ->
|
|
413
|
+
if (group.type != C.TRACK_TYPE_TEXT) {
|
|
414
|
+
return@forEachIndexed
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
for (trackIndex in 0 until group.length) {
|
|
418
|
+
if (group.isTrackSelected(trackIndex)) {
|
|
419
|
+
return "$groupIndex:$trackIndex"
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return null
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
private fun findTextTrack(trackId: String): TextTrackSelection? {
|
|
428
|
+
val currentPlayer = player ?: return null
|
|
429
|
+
|
|
430
|
+
currentPlayer.currentTracks.groups.forEachIndexed { groupIndex, group ->
|
|
431
|
+
if (group.type != C.TRACK_TYPE_TEXT) {
|
|
432
|
+
return@forEachIndexed
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
for (trackIndex in 0 until group.length) {
|
|
436
|
+
if ("$groupIndex:$trackIndex" == trackId && group.isTrackSupported(trackIndex)) {
|
|
437
|
+
return TextTrackSelection(trackId, group.mediaTrackGroup, trackIndex)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return null
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private fun captionTrackKind(selectionFlags: Int, roleFlags: Int): String {
|
|
446
|
+
return when {
|
|
447
|
+
selectionFlags and C.SELECTION_FLAG_FORCED != 0 -> "forced"
|
|
448
|
+
roleFlags and C.ROLE_FLAG_CAPTION != 0 -> "captions"
|
|
449
|
+
else -> "subtitles"
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<androidx.media3.ui.PlayerView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
3
|
+
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
4
|
+
android:layout_width="match_parent"
|
|
5
|
+
android:layout_height="match_parent"
|
|
6
|
+
app:surface_type="texture_view" />
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { MuxPlayerStatus, MuxVideoChapter, MuxVideoControlsTheme, MuxVideoKeyMoment, MuxVideoRobotsConfig, MuxVideoSummary } from './types';
|
|
2
|
+
import { MuxVideoPlayer } from './MuxVideoPlayer';
|
|
3
|
+
type MuxVideoControlsProps = {
|
|
4
|
+
player: MuxVideoPlayer;
|
|
5
|
+
status: MuxPlayerStatus;
|
|
6
|
+
shouldPlay: boolean;
|
|
7
|
+
theme?: MuxVideoControlsTheme;
|
|
8
|
+
robots?: MuxVideoRobotsConfig;
|
|
9
|
+
allowsFullscreen?: boolean;
|
|
10
|
+
isFullscreen?: boolean;
|
|
11
|
+
onToggleFullscreen?: () => void;
|
|
12
|
+
generatedSummary?: MuxVideoSummary;
|
|
13
|
+
generatedChapters?: MuxVideoChapter[];
|
|
14
|
+
generatedKeyMoments?: MuxVideoKeyMoment[];
|
|
15
|
+
onGeneratedSummaryChange?: (summary: MuxVideoSummary | undefined) => void;
|
|
16
|
+
onGeneratedChaptersChange?: (chapters: MuxVideoChapter[] | undefined) => void;
|
|
17
|
+
onGeneratedKeyMomentsChange?: (keyMoments: MuxVideoKeyMoment[] | undefined) => void;
|
|
18
|
+
};
|
|
19
|
+
export declare function MuxVideoControls({ player, status, shouldPlay, theme, robots, allowsFullscreen, isFullscreen, onToggleFullscreen, generatedSummary, generatedChapters, generatedKeyMoments, onGeneratedSummaryChange, onGeneratedChaptersChange, onGeneratedKeyMomentsChange, }: MuxVideoControlsProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=MuxVideoControls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MuxVideoControls.d.ts","sourceRoot":"","sources":["../src/MuxVideoControls.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACV,eAAe,EAEf,eAAe,EACf,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EAChB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAKlD,KAAK,qBAAqB,GAAG;IAC3B,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,qBAAqB,CAAC;IAC9B,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,gBAAgB,CAAC,EAAE,eAAe,CAAC;IACnC,iBAAiB,CAAC,EAAE,eAAe,EAAE,CAAC;IACtC,mBAAmB,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC1C,wBAAwB,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,GAAG,SAAS,KAAK,IAAI,CAAC;IAC1E,yBAAyB,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,SAAS,KAAK,IAAI,CAAC;IAC9E,2BAA2B,CAAC,EAAE,CAAC,UAAU,EAAE,iBAAiB,EAAE,GAAG,SAAS,KAAK,IAAI,CAAC;CACrF,CAAC;AA8BF,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,MAAM,EACN,UAAU,EACV,KAAK,EACL,MAAM,EACN,gBAAwB,EACxB,YAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,2BAA2B,GAC5B,EAAE,qBAAqB,2CAk4BvB"}
|