@stepincto/expo-video 1.0.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/README.md +45 -0
- package/android/build.gradle +32 -0
- package/android/src/main/AndroidManifest.xml +20 -0
- package/android/src/main/java/expo/modules/video/AudioFocusManager.kt +241 -0
- package/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt +145 -0
- package/android/src/main/java/expo/modules/video/IntervalUpdateClock.kt +54 -0
- package/android/src/main/java/expo/modules/video/MediaMetadataRetriever.kt +89 -0
- package/android/src/main/java/expo/modules/video/PictureInPictureHelperFragment.kt +26 -0
- package/android/src/main/java/expo/modules/video/PlayerViewExtension.kt +36 -0
- package/android/src/main/java/expo/modules/video/VideoCache.kt +104 -0
- package/android/src/main/java/expo/modules/video/VideoExceptions.kt +34 -0
- package/android/src/main/java/expo/modules/video/VideoManager.kt +133 -0
- package/android/src/main/java/expo/modules/video/VideoModule.kt +414 -0
- package/android/src/main/java/expo/modules/video/VideoThumbnail.kt +20 -0
- package/android/src/main/java/expo/modules/video/VideoView.kt +367 -0
- package/android/src/main/java/expo/modules/video/delegates/IgnoreSameSet.kt +24 -0
- package/android/src/main/java/expo/modules/video/drawing/OutlineProvider.kt +217 -0
- package/android/src/main/java/expo/modules/video/enums/AudioMixingMode.kt +20 -0
- package/android/src/main/java/expo/modules/video/enums/ContentFit.kt +19 -0
- package/android/src/main/java/expo/modules/video/enums/ContentType.kt +22 -0
- package/android/src/main/java/expo/modules/video/enums/DRMType.kt +26 -0
- package/android/src/main/java/expo/modules/video/enums/PlayerStatus.kt +10 -0
- package/android/src/main/java/expo/modules/video/playbackService/ExpoVideoPlaybackService.kt +184 -0
- package/android/src/main/java/expo/modules/video/playbackService/PlaybackServiceConnection.kt +39 -0
- package/android/src/main/java/expo/modules/video/playbackService/VideoMediaSessionCallback.kt +47 -0
- package/android/src/main/java/expo/modules/video/player/FirstFrameEventGenerator.kt +93 -0
- package/android/src/main/java/expo/modules/video/player/PlayerEvent.kt +164 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayer.kt +460 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayerAudioTracks.kt +125 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayerListener.kt +32 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayerLoadControl.kt +525 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayerSubtitles.kt +125 -0
- package/android/src/main/java/expo/modules/video/records/BufferOptions.kt +15 -0
- package/android/src/main/java/expo/modules/video/records/DRMOptions.kt +25 -0
- package/android/src/main/java/expo/modules/video/records/PlaybackError.kt +19 -0
- package/android/src/main/java/expo/modules/video/records/Tracks.kt +81 -0
- package/android/src/main/java/expo/modules/video/records/VideoEventPayloads.kt +79 -0
- package/android/src/main/java/expo/modules/video/records/VideoMetadata.kt +12 -0
- package/android/src/main/java/expo/modules/video/records/VideoSize.kt +14 -0
- package/android/src/main/java/expo/modules/video/records/VideoSource.kt +104 -0
- package/android/src/main/java/expo/modules/video/records/VideoThumbnailOptions.kt +24 -0
- package/android/src/main/java/expo/modules/video/utils/DataSourceUtils.kt +75 -0
- package/android/src/main/java/expo/modules/video/utils/EventDispatcherUtils.kt +43 -0
- package/android/src/main/java/expo/modules/video/utils/MutableWeakReference.kt +15 -0
- package/android/src/main/java/expo/modules/video/utils/PictureInPictureUtils.kt +96 -0
- package/android/src/main/java/expo/modules/video/utils/YogaUtils.kt +20 -0
- package/android/src/main/res/drawable/seek_backwards_10s.xml +25 -0
- package/android/src/main/res/drawable/seek_backwards_15s.xml +25 -0
- package/android/src/main/res/drawable/seek_backwards_5s.xml +25 -0
- package/android/src/main/res/drawable/seek_forwards_10s.xml +30 -0
- package/android/src/main/res/drawable/seek_forwards_15s.xml +31 -0
- package/android/src/main/res/drawable/seek_forwards_5s.xml +30 -0
- package/android/src/main/res/layout/fullscreen_player_activity.xml +16 -0
- package/android/src/main/res/layout/surface_player_view.xml +7 -0
- package/android/src/main/res/layout/texture_player_view.xml +7 -0
- package/android/src/main/res/values/styles.xml +9 -0
- package/app.plugin.js +1 -0
- package/build/NativeVideoModule.d.ts +16 -0
- package/build/NativeVideoModule.d.ts.map +1 -0
- package/build/NativeVideoModule.js +3 -0
- package/build/NativeVideoModule.js.map +1 -0
- package/build/NativeVideoModule.web.d.ts +3 -0
- package/build/NativeVideoModule.web.d.ts.map +1 -0
- package/build/NativeVideoModule.web.js +2 -0
- package/build/NativeVideoModule.web.js.map +1 -0
- package/build/NativeVideoView.d.ts +4 -0
- package/build/NativeVideoView.d.ts.map +1 -0
- package/build/NativeVideoView.js +6 -0
- package/build/NativeVideoView.js.map +1 -0
- package/build/VideoModule.d.ts +38 -0
- package/build/VideoModule.d.ts.map +1 -0
- package/build/VideoModule.js +53 -0
- package/build/VideoModule.js.map +1 -0
- package/build/VideoPlayer.d.ts +15 -0
- package/build/VideoPlayer.d.ts.map +1 -0
- package/build/VideoPlayer.js +52 -0
- package/build/VideoPlayer.js.map +1 -0
- package/build/VideoPlayer.types.d.ts +532 -0
- package/build/VideoPlayer.types.d.ts.map +1 -0
- package/build/VideoPlayer.types.js +2 -0
- package/build/VideoPlayer.types.js.map +1 -0
- package/build/VideoPlayer.web.d.ts +75 -0
- package/build/VideoPlayer.web.d.ts.map +1 -0
- package/build/VideoPlayer.web.js +376 -0
- package/build/VideoPlayer.web.js.map +1 -0
- package/build/VideoPlayerEvents.types.d.ts +262 -0
- package/build/VideoPlayerEvents.types.d.ts.map +1 -0
- package/build/VideoPlayerEvents.types.js +2 -0
- package/build/VideoPlayerEvents.types.js.map +1 -0
- package/build/VideoThumbnail.d.ts +29 -0
- package/build/VideoThumbnail.d.ts.map +1 -0
- package/build/VideoThumbnail.js +3 -0
- package/build/VideoThumbnail.js.map +1 -0
- package/build/VideoView.d.ts +44 -0
- package/build/VideoView.d.ts.map +1 -0
- package/build/VideoView.js +76 -0
- package/build/VideoView.js.map +1 -0
- package/build/VideoView.types.d.ts +147 -0
- package/build/VideoView.types.d.ts.map +1 -0
- package/build/VideoView.types.js +2 -0
- package/build/VideoView.types.js.map +1 -0
- package/build/VideoView.web.d.ts +9 -0
- package/build/VideoView.web.d.ts.map +1 -0
- package/build/VideoView.web.js +180 -0
- package/build/VideoView.web.js.map +1 -0
- package/build/index.d.ts +9 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +7 -0
- package/build/index.js.map +1 -0
- package/build/resolveAssetSource.d.ts +3 -0
- package/build/resolveAssetSource.d.ts.map +1 -0
- package/build/resolveAssetSource.js +3 -0
- package/build/resolveAssetSource.js.map +1 -0
- package/build/resolveAssetSource.web.d.ts +4 -0
- package/build/resolveAssetSource.web.d.ts.map +1 -0
- package/build/resolveAssetSource.web.js +16 -0
- package/build/resolveAssetSource.web.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/Cache/CachableRequest.swift +44 -0
- package/ios/Cache/CachedResource.swift +97 -0
- package/ios/Cache/CachingHelpers.swift +92 -0
- package/ios/Cache/MediaFileHandle.swift +94 -0
- package/ios/Cache/MediaInfo.swift +147 -0
- package/ios/Cache/ResourceLoaderDelegate.swift +274 -0
- package/ios/Cache/SynchronizedHashTable.swift +23 -0
- package/ios/Cache/VideoCacheManager.swift +338 -0
- package/ios/ContentKeyDelegate.swift +214 -0
- package/ios/ContentKeyManager.swift +21 -0
- package/ios/Enums/AudioMixingMode.swift +37 -0
- package/ios/Enums/ContentType.swift +12 -0
- package/ios/Enums/DRMType.swift +20 -0
- package/ios/Enums/PlayerStatus.swift +10 -0
- package/ios/Enums/VideoContentFit.swift +39 -0
- package/ios/ExpoVideo.podspec +29 -0
- package/ios/NowPlayingManager.swift +296 -0
- package/ios/Records/BufferOptions.swift +12 -0
- package/ios/Records/DRMOptions.swift +24 -0
- package/ios/Records/PlaybackError.swift +10 -0
- package/ios/Records/Tracks.swift +176 -0
- package/ios/Records/VideoEventPayloads.swift +76 -0
- package/ios/Records/VideoMetadata.swift +16 -0
- package/ios/Records/VideoSize.swift +15 -0
- package/ios/Records/VideoSource.swift +25 -0
- package/ios/Thumbnails/VideoThumbnail.swift +27 -0
- package/ios/Thumbnails/VideoThumbnailGenerator.swift +68 -0
- package/ios/Thumbnails/VideoThumbnailOptions.swift +15 -0
- package/ios/VideoAsset.swift +123 -0
- package/ios/VideoExceptions.swift +53 -0
- package/ios/VideoItem.swift +11 -0
- package/ios/VideoManager.swift +140 -0
- package/ios/VideoModule.swift +383 -0
- package/ios/VideoPlayer/DangerousPropertiesStore.swift +19 -0
- package/ios/VideoPlayer.swift +435 -0
- package/ios/VideoPlayerAudioTracks.swift +72 -0
- package/ios/VideoPlayerItem.swift +97 -0
- package/ios/VideoPlayerObserver.swift +523 -0
- package/ios/VideoPlayerSubtitles.swift +71 -0
- package/ios/VideoSourceLoader.swift +89 -0
- package/ios/VideoSourceLoaderListener.swift +34 -0
- package/ios/VideoView.swift +224 -0
- package/package.json +59 -0
- package/plugin/build/tsconfig.tsbuildinfo +1 -0
- package/plugin/build/withExpoVideo.d.ts +7 -0
- package/plugin/build/withExpoVideo.js +38 -0
- package/src/NativeVideoModule.ts +20 -0
- package/src/NativeVideoModule.web.ts +1 -0
- package/src/NativeVideoView.ts +8 -0
- package/src/VideoModule.ts +59 -0
- package/src/VideoPlayer.tsx +67 -0
- package/src/VideoPlayer.types.ts +613 -0
- package/src/VideoPlayer.web.tsx +451 -0
- package/src/VideoPlayerEvents.types.ts +313 -0
- package/src/VideoThumbnail.ts +31 -0
- package/src/VideoView.tsx +86 -0
- package/src/VideoView.types.ts +165 -0
- package/src/VideoView.web.tsx +214 -0
- package/src/index.ts +46 -0
- package/src/resolveAssetSource.ts +2 -0
- package/src/resolveAssetSource.web.ts +17 -0
- package/src/ts-declarations/react-native-assets.d.ts +1 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
package expo.modules.video.player
|
|
2
|
+
|
|
3
|
+
import androidx.media3.common.C
|
|
4
|
+
import androidx.media3.common.Timeline
|
|
5
|
+
import androidx.media3.common.util.Assertions
|
|
6
|
+
import androidx.media3.common.util.Log
|
|
7
|
+
import androidx.media3.common.util.UnstableApi
|
|
8
|
+
import androidx.media3.common.util.Util
|
|
9
|
+
import androidx.media3.exoplayer.DefaultLoadControl
|
|
10
|
+
import androidx.media3.exoplayer.LoadControl
|
|
11
|
+
import androidx.media3.exoplayer.Renderer
|
|
12
|
+
import androidx.media3.exoplayer.source.MediaSource
|
|
13
|
+
import androidx.media3.exoplayer.source.TrackGroupArray
|
|
14
|
+
import androidx.media3.exoplayer.trackselection.ExoTrackSelection
|
|
15
|
+
import androidx.media3.exoplayer.upstream.Allocator
|
|
16
|
+
import androidx.media3.exoplayer.upstream.DefaultAllocator
|
|
17
|
+
import expo.modules.video.records.BufferOptions
|
|
18
|
+
import kotlin.math.max
|
|
19
|
+
import kotlin.math.min
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Default LoadControl implementation copied from ExoPlayer source (auto-converted to Kotlin)
|
|
23
|
+
* Modified to allow changing buffer parameters during playback.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** The default [LoadControl] implementation. */
|
|
27
|
+
@UnstableApi
|
|
28
|
+
class VideoPlayerLoadControl
|
|
29
|
+
private constructor(
|
|
30
|
+
allocator: DefaultAllocator,
|
|
31
|
+
minBufferMs: Int,
|
|
32
|
+
maxBufferMs: Int,
|
|
33
|
+
bufferForPlaybackMs: Int,
|
|
34
|
+
bufferForPlaybackAfterRebufferMs: Int,
|
|
35
|
+
targetBufferBytes: Int,
|
|
36
|
+
prioritizeTimeOverSizeThresholds: Boolean,
|
|
37
|
+
backBufferDurationMs: Int,
|
|
38
|
+
retainBackBufferFromKeyframe: Boolean
|
|
39
|
+
) : LoadControl {
|
|
40
|
+
/** Builder for [DefaultLoadControl]. */
|
|
41
|
+
class Builder {
|
|
42
|
+
private var allocator: DefaultAllocator? = null
|
|
43
|
+
private var minBufferMs: Int
|
|
44
|
+
private var maxBufferMs: Int
|
|
45
|
+
private var bufferForPlaybackMs: Int
|
|
46
|
+
private var bufferForPlaybackAfterRebufferMs: Int
|
|
47
|
+
private var targetBufferBytes: Int
|
|
48
|
+
private var prioritizeTimeOverSizeThresholds: Boolean
|
|
49
|
+
private var backBufferDurationMs: Int
|
|
50
|
+
private var retainBackBufferFromKeyframe: Boolean
|
|
51
|
+
private var buildCalled = false
|
|
52
|
+
|
|
53
|
+
/** Constructs a new instance. */
|
|
54
|
+
init {
|
|
55
|
+
minBufferMs = DEFAULT_MIN_BUFFER_MS
|
|
56
|
+
maxBufferMs = DEFAULT_MAX_BUFFER_MS
|
|
57
|
+
bufferForPlaybackMs = DEFAULT_BUFFER_FOR_PLAYBACK_MS
|
|
58
|
+
bufferForPlaybackAfterRebufferMs = DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
|
|
59
|
+
targetBufferBytes = DEFAULT_TARGET_BUFFER_BYTES
|
|
60
|
+
prioritizeTimeOverSizeThresholds = DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS
|
|
61
|
+
backBufferDurationMs = DEFAULT_BACK_BUFFER_DURATION_MS
|
|
62
|
+
retainBackBufferFromKeyframe = DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Sets the [DefaultAllocator] used by the loader.
|
|
67
|
+
*
|
|
68
|
+
* @param allocator The [DefaultAllocator].
|
|
69
|
+
* @return This builder, for convenience.
|
|
70
|
+
* @throws IllegalStateException If [.build] has already been called.
|
|
71
|
+
*/
|
|
72
|
+
fun setAllocator(allocator: DefaultAllocator?): Builder {
|
|
73
|
+
Assertions.checkState(!buildCalled)
|
|
74
|
+
this.allocator = allocator
|
|
75
|
+
return this
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Sets the buffer duration parameters.
|
|
80
|
+
*
|
|
81
|
+
* @param minBufferMs The minimum duration of media that the player will attempt to ensure is
|
|
82
|
+
* buffered at all times, in milliseconds.
|
|
83
|
+
* @param maxBufferMs The maximum duration of media that the player will attempt to buffer, in
|
|
84
|
+
* milliseconds.
|
|
85
|
+
* @param bufferForPlaybackMs The duration of media that must be buffered for playback to start
|
|
86
|
+
* or resume following a user action such as a seek, in milliseconds.
|
|
87
|
+
* @param bufferForPlaybackAfterRebufferMs The default duration of media that must be buffered
|
|
88
|
+
* for playback to resume after a rebuffer, in milliseconds. A rebuffer is defined to be
|
|
89
|
+
* caused by buffer depletion rather than a user action.
|
|
90
|
+
* @return This builder, for convenience.
|
|
91
|
+
* @throws IllegalStateException If [.build] has already been called.
|
|
92
|
+
*/
|
|
93
|
+
fun setBufferDurationsMs(
|
|
94
|
+
minBufferMs: Int,
|
|
95
|
+
maxBufferMs: Int,
|
|
96
|
+
bufferForPlaybackMs: Int,
|
|
97
|
+
bufferForPlaybackAfterRebufferMs: Int
|
|
98
|
+
): Builder {
|
|
99
|
+
Assertions.checkState(!buildCalled)
|
|
100
|
+
assertGreaterOrEqual(bufferForPlaybackMs, 0, "bufferForPlaybackMs", "0")
|
|
101
|
+
assertGreaterOrEqual(
|
|
102
|
+
bufferForPlaybackAfterRebufferMs,
|
|
103
|
+
0,
|
|
104
|
+
"bufferForPlaybackAfterRebufferMs",
|
|
105
|
+
"0"
|
|
106
|
+
)
|
|
107
|
+
assertGreaterOrEqual(minBufferMs, bufferForPlaybackMs, "minBufferMs", "bufferForPlaybackMs")
|
|
108
|
+
assertGreaterOrEqual(
|
|
109
|
+
minBufferMs,
|
|
110
|
+
bufferForPlaybackAfterRebufferMs,
|
|
111
|
+
"minBufferMs",
|
|
112
|
+
"bufferForPlaybackAfterRebufferMs"
|
|
113
|
+
)
|
|
114
|
+
assertGreaterOrEqual(maxBufferMs, minBufferMs, "maxBufferMs", "minBufferMs")
|
|
115
|
+
this.minBufferMs = minBufferMs
|
|
116
|
+
this.maxBufferMs = maxBufferMs
|
|
117
|
+
this.bufferForPlaybackMs = bufferForPlaybackMs
|
|
118
|
+
this.bufferForPlaybackAfterRebufferMs = bufferForPlaybackAfterRebufferMs
|
|
119
|
+
return this
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Sets the target buffer size in bytes. If set to [C.LENGTH_UNSET], the target buffer
|
|
124
|
+
* size will be calculated based on the selected tracks.
|
|
125
|
+
*
|
|
126
|
+
* @param targetBufferBytes The target buffer size in bytes.
|
|
127
|
+
* @return This builder, for convenience.
|
|
128
|
+
* @throws IllegalStateException If [.build] has already been called.
|
|
129
|
+
*/
|
|
130
|
+
fun setTargetBufferBytes(targetBufferBytes: Int): Builder {
|
|
131
|
+
Assertions.checkState(!buildCalled)
|
|
132
|
+
this.targetBufferBytes = targetBufferBytes
|
|
133
|
+
return this
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Sets whether the load control prioritizes buffer time constraints over buffer size
|
|
138
|
+
* constraints.
|
|
139
|
+
*
|
|
140
|
+
* @param prioritizeTimeOverSizeThresholds Whether the load control prioritizes buffer time
|
|
141
|
+
* constraints over buffer size constraints.
|
|
142
|
+
* @return This builder, for convenience.
|
|
143
|
+
* @throws IllegalStateException If [.build] has already been called.
|
|
144
|
+
*/
|
|
145
|
+
fun setPrioritizeTimeOverSizeThresholds(prioritizeTimeOverSizeThresholds: Boolean): Builder {
|
|
146
|
+
Assertions.checkState(!buildCalled)
|
|
147
|
+
this.prioritizeTimeOverSizeThresholds = prioritizeTimeOverSizeThresholds
|
|
148
|
+
return this
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Sets the back buffer duration, and whether the back buffer is retained from the previous
|
|
153
|
+
* keyframe.
|
|
154
|
+
*
|
|
155
|
+
* @param backBufferDurationMs The back buffer duration in milliseconds.
|
|
156
|
+
* @param retainBackBufferFromKeyframe Whether the back buffer is retained from the previous
|
|
157
|
+
* keyframe.
|
|
158
|
+
* @return This builder, for convenience.
|
|
159
|
+
* @throws IllegalStateException If [.build] has already been called.
|
|
160
|
+
*/
|
|
161
|
+
fun setBackBuffer(backBufferDurationMs: Int, retainBackBufferFromKeyframe: Boolean): Builder {
|
|
162
|
+
Assertions.checkState(!buildCalled)
|
|
163
|
+
assertGreaterOrEqual(backBufferDurationMs, 0, "backBufferDurationMs", "0")
|
|
164
|
+
this.backBufferDurationMs = backBufferDurationMs
|
|
165
|
+
this.retainBackBufferFromKeyframe = retainBackBufferFromKeyframe
|
|
166
|
+
return this
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Creates a [DefaultLoadControl]. */
|
|
170
|
+
fun build(): VideoPlayerLoadControl {
|
|
171
|
+
Assertions.checkState(!buildCalled)
|
|
172
|
+
buildCalled = true
|
|
173
|
+
if (allocator == null) {
|
|
174
|
+
allocator = DefaultAllocator( /* trimOnReset= */true, C.DEFAULT_BUFFER_SEGMENT_SIZE)
|
|
175
|
+
}
|
|
176
|
+
return VideoPlayerLoadControl(
|
|
177
|
+
allocator!!,
|
|
178
|
+
minBufferMs,
|
|
179
|
+
maxBufferMs,
|
|
180
|
+
bufferForPlaybackMs,
|
|
181
|
+
bufferForPlaybackAfterRebufferMs,
|
|
182
|
+
targetBufferBytes,
|
|
183
|
+
prioritizeTimeOverSizeThresholds,
|
|
184
|
+
backBufferDurationMs,
|
|
185
|
+
retainBackBufferFromKeyframe
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private var minBufferUs: Long
|
|
191
|
+
private var maxBufferUs: Long
|
|
192
|
+
private var bufferForPlaybackUs: Long
|
|
193
|
+
private var bufferForPlaybackAfterRebufferUs: Long
|
|
194
|
+
private var targetBufferBytesOverwrite: Int
|
|
195
|
+
private var prioritizeTimeOverSizeThresholds: Boolean
|
|
196
|
+
private val backBufferDurationUs: Long
|
|
197
|
+
private val retainBackBufferFromKeyframe: Boolean
|
|
198
|
+
|
|
199
|
+
private var targetBufferBytes: Int
|
|
200
|
+
private var isLoading = false
|
|
201
|
+
|
|
202
|
+
private var renderers: Array<Renderer>? = null
|
|
203
|
+
private var trackSelections: Array<ExoTrackSelection>? = null
|
|
204
|
+
|
|
205
|
+
private val allocator: DefaultAllocator
|
|
206
|
+
|
|
207
|
+
var targetBufferMs: Long = DEFAULT_MAX_BUFFER_MS.toLong()
|
|
208
|
+
set(value) {
|
|
209
|
+
minBufferUs = Util.msToUs(value)
|
|
210
|
+
maxBufferUs = Util.msToUs(value)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
var bufferForPlaybackMs: Long = DEFAULT_BUFFER_FOR_PLAYBACK_MS.toLong()
|
|
214
|
+
set(value) {
|
|
215
|
+
bufferForPlaybackUs = Util.msToUs(value)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
var bufferForPlaybackAfterRebufferMs: Long = DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS.toLong()
|
|
219
|
+
set(value) {
|
|
220
|
+
bufferForPlaybackAfterRebufferUs = Util.msToUs(value)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Constructs a new instance, using the `DEFAULT_*` constants defined in this class. */
|
|
224
|
+
constructor() : this(
|
|
225
|
+
DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
|
|
226
|
+
DEFAULT_MIN_BUFFER_MS,
|
|
227
|
+
DEFAULT_MAX_BUFFER_MS,
|
|
228
|
+
DEFAULT_BUFFER_FOR_PLAYBACK_MS,
|
|
229
|
+
DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS,
|
|
230
|
+
DEFAULT_TARGET_BUFFER_BYTES,
|
|
231
|
+
DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS,
|
|
232
|
+
DEFAULT_BACK_BUFFER_DURATION_MS,
|
|
233
|
+
DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
init {
|
|
237
|
+
assertGreaterOrEqual(bufferForPlaybackMs, 0, "bufferForPlaybackMs", "0")
|
|
238
|
+
assertGreaterOrEqual(
|
|
239
|
+
bufferForPlaybackAfterRebufferMs,
|
|
240
|
+
0,
|
|
241
|
+
"bufferForPlaybackAfterRebufferMs",
|
|
242
|
+
"0"
|
|
243
|
+
)
|
|
244
|
+
assertGreaterOrEqual(minBufferMs, bufferForPlaybackMs, "minBufferMs", "bufferForPlaybackMs")
|
|
245
|
+
assertGreaterOrEqual(
|
|
246
|
+
minBufferMs,
|
|
247
|
+
bufferForPlaybackAfterRebufferMs,
|
|
248
|
+
"minBufferMs",
|
|
249
|
+
"bufferForPlaybackAfterRebufferMs"
|
|
250
|
+
)
|
|
251
|
+
assertGreaterOrEqual(maxBufferMs, minBufferMs, "maxBufferMs", "minBufferMs")
|
|
252
|
+
assertGreaterOrEqual(backBufferDurationMs, 0, "backBufferDurationMs", "0")
|
|
253
|
+
|
|
254
|
+
this.allocator = allocator
|
|
255
|
+
this.minBufferUs = Util.msToUs(minBufferMs.toLong())
|
|
256
|
+
this.maxBufferUs = Util.msToUs(maxBufferMs.toLong())
|
|
257
|
+
this.bufferForPlaybackUs = Util.msToUs(bufferForPlaybackMs.toLong())
|
|
258
|
+
this.bufferForPlaybackAfterRebufferUs = Util.msToUs(bufferForPlaybackAfterRebufferMs.toLong())
|
|
259
|
+
this.targetBufferBytesOverwrite = targetBufferBytes
|
|
260
|
+
this.targetBufferBytes =
|
|
261
|
+
if (targetBufferBytesOverwrite != C.LENGTH_UNSET
|
|
262
|
+
) {
|
|
263
|
+
targetBufferBytesOverwrite
|
|
264
|
+
} else {
|
|
265
|
+
DEFAULT_MIN_BUFFER_SIZE
|
|
266
|
+
}
|
|
267
|
+
this.prioritizeTimeOverSizeThresholds = prioritizeTimeOverSizeThresholds
|
|
268
|
+
this.backBufferDurationUs = Util.msToUs(backBufferDurationMs.toLong())
|
|
269
|
+
this.retainBackBufferFromKeyframe = retainBackBufferFromKeyframe
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
fun applyBufferOptions(bufferOptions: BufferOptions) {
|
|
273
|
+
bufferOptions.preferredForwardBufferDuration?.let {
|
|
274
|
+
targetBufferMs = (it * 1000).toLong()
|
|
275
|
+
} ?: run {
|
|
276
|
+
targetBufferMs = DEFAULT_MAX_BUFFER_MS.toLong()
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
targetBufferBytesOverwrite = if (bufferOptions.maxBufferBytes == 0L) {
|
|
280
|
+
C.LENGTH_UNSET
|
|
281
|
+
} else {
|
|
282
|
+
bufferOptions.maxBufferBytes.toInt()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (targetBufferBytesOverwrite != C.LENGTH_UNSET) {
|
|
286
|
+
targetBufferBytes = targetBufferBytesOverwrite
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
applyBufferBytes()
|
|
290
|
+
|
|
291
|
+
prioritizeTimeOverSizeThresholds = bufferOptions.prioritizeTimeOverSizeThreshold
|
|
292
|
+
|
|
293
|
+
val optionsBufferForPlaybackMs = bufferOptions.minBufferForPlayback * 1000
|
|
294
|
+
val safeBufferForPlayback = if (optionsBufferForPlaybackMs > targetBufferMs) {
|
|
295
|
+
targetBufferMs
|
|
296
|
+
} else {
|
|
297
|
+
bufferOptions.minBufferForPlayback
|
|
298
|
+
}
|
|
299
|
+
bufferForPlaybackMs = safeBufferForPlayback.toLong()
|
|
300
|
+
bufferForPlaybackAfterRebufferMs = safeBufferForPlayback.toLong()
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private fun applyBufferBytes() {
|
|
304
|
+
val calculatedBufferBytes = this.renderers?.let { renderers ->
|
|
305
|
+
this.trackSelections?.let { trackSelections ->
|
|
306
|
+
calculateTargetBufferBytes(renderers, trackSelections)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (targetBufferBytesOverwrite == C.LENGTH_UNSET && calculatedBufferBytes != null) {
|
|
311
|
+
allocator.setTargetBufferSize(calculatedBufferBytes)
|
|
312
|
+
targetBufferBytes = calculatedBufferBytes
|
|
313
|
+
} else {
|
|
314
|
+
allocator.setTargetBufferSize(targetBufferBytesOverwrite)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
override fun onPrepared() {
|
|
319
|
+
reset(false)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
override fun onTracksSelected(
|
|
323
|
+
timeline: Timeline,
|
|
324
|
+
mediaPeriodId: MediaSource.MediaPeriodId,
|
|
325
|
+
renderers: Array<Renderer>,
|
|
326
|
+
trackGroups: TrackGroupArray,
|
|
327
|
+
trackSelections: Array<ExoTrackSelection>
|
|
328
|
+
) {
|
|
329
|
+
this.renderers = renderers
|
|
330
|
+
this.trackSelections = trackSelections
|
|
331
|
+
applyBufferBytes()
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
override fun onStopped() {
|
|
335
|
+
reset(true)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
override fun onReleased() {
|
|
339
|
+
reset(true)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
override fun getAllocator(): Allocator {
|
|
343
|
+
return allocator
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
override fun getBackBufferDurationUs(): Long {
|
|
347
|
+
return backBufferDurationUs
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
override fun retainBackBufferFromKeyframe(): Boolean {
|
|
351
|
+
return retainBackBufferFromKeyframe
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
override fun shouldContinueLoading(
|
|
355
|
+
playbackPositionUs: Long,
|
|
356
|
+
bufferedDurationUs: Long,
|
|
357
|
+
playbackSpeed: Float
|
|
358
|
+
): Boolean {
|
|
359
|
+
val targetBufferSizeReached = allocator.totalBytesAllocated >= targetBufferBytes
|
|
360
|
+
var minBufferUs = this.minBufferUs
|
|
361
|
+
if (playbackSpeed > 1) {
|
|
362
|
+
// The playback speed is faster than real time, so scale up the minimum required media
|
|
363
|
+
// duration to keep enough media buffered for a playout duration of minBufferUs.
|
|
364
|
+
val mediaDurationMinBufferUs =
|
|
365
|
+
Util.getMediaDurationForPlayoutDuration(minBufferUs, playbackSpeed)
|
|
366
|
+
minBufferUs = min(mediaDurationMinBufferUs.toDouble(), maxBufferUs.toDouble()).toLong()
|
|
367
|
+
}
|
|
368
|
+
// Prevent playback from getting stuck if minBufferUs is too small.
|
|
369
|
+
minBufferUs = max(minBufferUs.toDouble(), 500000.0).toLong()
|
|
370
|
+
if (bufferedDurationUs < minBufferUs) {
|
|
371
|
+
isLoading = prioritizeTimeOverSizeThresholds || !targetBufferSizeReached
|
|
372
|
+
if (!isLoading && bufferedDurationUs < 500000) {
|
|
373
|
+
Log.w(
|
|
374
|
+
"DefaultLoadControl",
|
|
375
|
+
"Target buffer size reached with less than 500ms of buffered media data."
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
} else if (bufferedDurationUs >= maxBufferUs || targetBufferSizeReached) {
|
|
379
|
+
isLoading = false
|
|
380
|
+
} // Else don't change the loading state.
|
|
381
|
+
|
|
382
|
+
return isLoading
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
override fun shouldStartPlayback(
|
|
386
|
+
timeline: Timeline,
|
|
387
|
+
mediaPeriodId: MediaSource.MediaPeriodId,
|
|
388
|
+
bufferedDurationUs: Long,
|
|
389
|
+
playbackSpeed: Float,
|
|
390
|
+
rebuffering: Boolean,
|
|
391
|
+
targetLiveOffsetUs: Long
|
|
392
|
+
): Boolean {
|
|
393
|
+
var bufferedDurationUs = bufferedDurationUs
|
|
394
|
+
bufferedDurationUs = Util.getPlayoutDurationForMediaDuration(bufferedDurationUs, playbackSpeed)
|
|
395
|
+
var minBufferDurationUs = if (rebuffering) bufferForPlaybackAfterRebufferUs else bufferForPlaybackUs
|
|
396
|
+
if (targetLiveOffsetUs != C.TIME_UNSET) {
|
|
397
|
+
minBufferDurationUs = min((targetLiveOffsetUs / 2).toDouble(), minBufferDurationUs.toDouble()).toLong()
|
|
398
|
+
}
|
|
399
|
+
return minBufferDurationUs <= 0 || bufferedDurationUs >= minBufferDurationUs || (
|
|
400
|
+
!prioritizeTimeOverSizeThresholds &&
|
|
401
|
+
allocator.totalBytesAllocated >= targetBufferBytes
|
|
402
|
+
)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Calculate target buffer size in bytes based on the selected tracks. The player will try not to
|
|
407
|
+
* exceed this target buffer. Only used when `targetBufferBytes` is [C.LENGTH_UNSET].
|
|
408
|
+
*
|
|
409
|
+
* @param renderers The renderers for which the track were selected.
|
|
410
|
+
* @param trackSelectionArray The selected tracks.
|
|
411
|
+
* @return The target buffer size in bytes.
|
|
412
|
+
*/
|
|
413
|
+
protected fun calculateTargetBufferBytes(
|
|
414
|
+
renderers: Array<Renderer>,
|
|
415
|
+
trackSelectionArray: Array<ExoTrackSelection>
|
|
416
|
+
): Int {
|
|
417
|
+
var targetBufferSize = 0
|
|
418
|
+
for (i in renderers.indices) {
|
|
419
|
+
if (trackSelectionArray[i] != null) {
|
|
420
|
+
targetBufferSize += getDefaultBufferSize(renderers[i].trackType)
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return max(DEFAULT_MIN_BUFFER_SIZE.toDouble(), targetBufferSize.toDouble()).toInt()
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
private fun reset(resetAllocator: Boolean) {
|
|
427
|
+
targetBufferBytes =
|
|
428
|
+
if (targetBufferBytesOverwrite == C.LENGTH_UNSET
|
|
429
|
+
) {
|
|
430
|
+
DEFAULT_MIN_BUFFER_SIZE
|
|
431
|
+
} else {
|
|
432
|
+
targetBufferBytesOverwrite
|
|
433
|
+
}
|
|
434
|
+
isLoading = false
|
|
435
|
+
if (resetAllocator) {
|
|
436
|
+
allocator.reset()
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
companion object {
|
|
441
|
+
/**
|
|
442
|
+
* The default minimum duration of media that the player will attempt to ensure is buffered at all
|
|
443
|
+
* times, in milliseconds.
|
|
444
|
+
*/
|
|
445
|
+
const val DEFAULT_MIN_BUFFER_MS: Int = 25000
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* The default maximum duration of media that the player will attempt to buffer, in milliseconds.
|
|
449
|
+
*/
|
|
450
|
+
const val DEFAULT_MAX_BUFFER_MS: Int = 25000
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* The default duration of media that must be buffered for playback to start or resume following a
|
|
454
|
+
* user action such as a seek, in milliseconds.
|
|
455
|
+
*/
|
|
456
|
+
const val DEFAULT_BUFFER_FOR_PLAYBACK_MS: Int = 2000
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* The default duration of media that must be buffered for playback to resume after a rebuffer, in
|
|
460
|
+
* milliseconds. A rebuffer is defined to be caused by buffer depletion rather than a user action.
|
|
461
|
+
*/
|
|
462
|
+
const val DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS: Int = 2000
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* The default target buffer size in bytes. The value ([C.LENGTH_UNSET]) means that the load
|
|
466
|
+
* control will calculate the target buffer size based on the selected tracks.
|
|
467
|
+
*/
|
|
468
|
+
const val DEFAULT_TARGET_BUFFER_BYTES: Int = C.LENGTH_UNSET
|
|
469
|
+
|
|
470
|
+
/** The default prioritization of buffer time constraints over size constraints. */
|
|
471
|
+
const val DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS: Boolean = false
|
|
472
|
+
|
|
473
|
+
/** The default back buffer duration in milliseconds. */
|
|
474
|
+
const val DEFAULT_BACK_BUFFER_DURATION_MS: Int = 0
|
|
475
|
+
|
|
476
|
+
/** The default for whether the back buffer is retained from the previous keyframe. */
|
|
477
|
+
const val DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME: Boolean = false
|
|
478
|
+
|
|
479
|
+
/** A default size in bytes for a video buffer. */
|
|
480
|
+
const val DEFAULT_VIDEO_BUFFER_SIZE: Int = 2000 * C.DEFAULT_BUFFER_SEGMENT_SIZE
|
|
481
|
+
|
|
482
|
+
/** A default size in bytes for an audio buffer. */
|
|
483
|
+
const val DEFAULT_AUDIO_BUFFER_SIZE: Int = 200 * C.DEFAULT_BUFFER_SEGMENT_SIZE
|
|
484
|
+
|
|
485
|
+
/** A default size in bytes for a text buffer. */
|
|
486
|
+
const val DEFAULT_TEXT_BUFFER_SIZE: Int = 2 * C.DEFAULT_BUFFER_SEGMENT_SIZE
|
|
487
|
+
|
|
488
|
+
/** A default size in bytes for a metadata buffer. */
|
|
489
|
+
const val DEFAULT_METADATA_BUFFER_SIZE: Int = 2 * C.DEFAULT_BUFFER_SEGMENT_SIZE
|
|
490
|
+
|
|
491
|
+
/** A default size in bytes for a camera motion buffer. */
|
|
492
|
+
const val DEFAULT_CAMERA_MOTION_BUFFER_SIZE: Int = 2 * C.DEFAULT_BUFFER_SEGMENT_SIZE
|
|
493
|
+
|
|
494
|
+
/** A default size in bytes for an image buffer. */
|
|
495
|
+
const val DEFAULT_IMAGE_BUFFER_SIZE: Int = 2 * C.DEFAULT_BUFFER_SEGMENT_SIZE
|
|
496
|
+
|
|
497
|
+
/** A default size in bytes for a muxed buffer (e.g. containing video, audio and text). */
|
|
498
|
+
val DEFAULT_MUXED_BUFFER_SIZE: Int = VideoPlayerLoadControl.DEFAULT_VIDEO_BUFFER_SIZE + VideoPlayerLoadControl.DEFAULT_AUDIO_BUFFER_SIZE + VideoPlayerLoadControl.DEFAULT_TEXT_BUFFER_SIZE
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* The buffer size in bytes that will be used as a minimum target buffer in all cases. This is
|
|
502
|
+
* also the default target buffer before tracks are selected.
|
|
503
|
+
*/
|
|
504
|
+
const val DEFAULT_MIN_BUFFER_SIZE: Int = 200 * C.DEFAULT_BUFFER_SEGMENT_SIZE
|
|
505
|
+
|
|
506
|
+
private fun getDefaultBufferSize(trackType: @C.TrackType Int): Int {
|
|
507
|
+
return when (trackType) {
|
|
508
|
+
C.TRACK_TYPE_DEFAULT -> DEFAULT_MUXED_BUFFER_SIZE
|
|
509
|
+
C.TRACK_TYPE_AUDIO -> DEFAULT_AUDIO_BUFFER_SIZE
|
|
510
|
+
C.TRACK_TYPE_VIDEO -> DEFAULT_VIDEO_BUFFER_SIZE
|
|
511
|
+
C.TRACK_TYPE_TEXT -> DEFAULT_TEXT_BUFFER_SIZE
|
|
512
|
+
C.TRACK_TYPE_METADATA -> DEFAULT_METADATA_BUFFER_SIZE
|
|
513
|
+
C.TRACK_TYPE_CAMERA_MOTION -> DEFAULT_CAMERA_MOTION_BUFFER_SIZE
|
|
514
|
+
C.TRACK_TYPE_IMAGE -> DEFAULT_IMAGE_BUFFER_SIZE
|
|
515
|
+
C.TRACK_TYPE_NONE -> 0
|
|
516
|
+
C.TRACK_TYPE_UNKNOWN -> throw IllegalArgumentException()
|
|
517
|
+
else -> throw IllegalArgumentException()
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
private fun assertGreaterOrEqual(value1: Int, value2: Int, name1: String, name2: String) {
|
|
522
|
+
Assertions.checkArgument(value1 >= value2, "$name1 cannot be less than $name2")
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
package expo.modules.video.player
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.OptIn
|
|
4
|
+
import androidx.media3.common.C
|
|
5
|
+
import androidx.media3.common.Format
|
|
6
|
+
import androidx.media3.common.MimeTypes
|
|
7
|
+
import androidx.media3.common.TrackGroup
|
|
8
|
+
import androidx.media3.common.TrackSelectionOverride
|
|
9
|
+
import androidx.media3.common.TrackSelectionParameters
|
|
10
|
+
import androidx.media3.common.Tracks
|
|
11
|
+
import androidx.media3.common.util.UnstableApi
|
|
12
|
+
import expo.modules.video.records.SubtitleTrack
|
|
13
|
+
import java.lang.ref.WeakReference
|
|
14
|
+
|
|
15
|
+
@OptIn(UnstableApi::class)
|
|
16
|
+
class VideoPlayerSubtitles(owner: VideoPlayer) : VideoPlayerListener {
|
|
17
|
+
private val owner = WeakReference(owner)
|
|
18
|
+
private val videoPlayer: VideoPlayer?
|
|
19
|
+
get() {
|
|
20
|
+
return owner.get()
|
|
21
|
+
}
|
|
22
|
+
private val formatsToGroups = mutableMapOf<Format, Pair<TrackGroup, Int>>()
|
|
23
|
+
private var currentSubtitleFormat: Format? = null
|
|
24
|
+
private var currentOverride: TrackSelectionOverride? = null
|
|
25
|
+
|
|
26
|
+
var currentSubtitleTrack: SubtitleTrack?
|
|
27
|
+
get() {
|
|
28
|
+
return SubtitleTrack.fromFormat(currentSubtitleFormat)
|
|
29
|
+
}
|
|
30
|
+
set(value) {
|
|
31
|
+
applySubtitleTrack(value)
|
|
32
|
+
}
|
|
33
|
+
val availableSubtitleTracks = arrayListOf<SubtitleTrack>()
|
|
34
|
+
|
|
35
|
+
init {
|
|
36
|
+
owner.addListener(this)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fun setSubtitlesEnabled(enabled: Boolean) {
|
|
40
|
+
val currentParams = videoPlayer?.player?.trackSelectionParameters ?: return
|
|
41
|
+
var params = currentParams.buildUpon().setTrackTypeDisabled(C.TRACK_TYPE_TEXT, !enabled).build()
|
|
42
|
+
if (!enabled) {
|
|
43
|
+
params = params.buildUpon().clearOverridesOfType(C.TRACK_TYPE_TEXT).build()
|
|
44
|
+
}
|
|
45
|
+
videoPlayer?.player?.trackSelectionParameters = params
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// VideoPlayerListener
|
|
49
|
+
override fun onTrackSelectionParametersChanged(player: VideoPlayer, trackSelectionParameters: TrackSelectionParameters) {
|
|
50
|
+
currentSubtitleFormat = findSelectedSubtitleFormat()
|
|
51
|
+
super.onTrackSelectionParametersChanged(player, trackSelectionParameters)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
override fun onTracksChanged(player: VideoPlayer, tracks: Tracks) {
|
|
55
|
+
formatsToGroups.clear()
|
|
56
|
+
availableSubtitleTracks.clear()
|
|
57
|
+
for (group in tracks.groups) {
|
|
58
|
+
for (i in 0..<group.length) {
|
|
59
|
+
val format: Format = group.getTrackFormat(i)
|
|
60
|
+
|
|
61
|
+
if (MimeTypes.isText(format.sampleMimeType)) {
|
|
62
|
+
formatsToGroups[format] = Pair(group.mediaTrackGroup, i)
|
|
63
|
+
val track = SubtitleTrack.fromFormat(format) ?: continue
|
|
64
|
+
availableSubtitleTracks.add(track)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
currentSubtitleFormat = findSelectedSubtitleFormat()
|
|
69
|
+
super.onTracksChanged(player, tracks)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Private methods
|
|
73
|
+
private fun applySubtitleTrack(subtitleTrack: SubtitleTrack?) {
|
|
74
|
+
val player = videoPlayer?.player ?: return
|
|
75
|
+
var newParameters: TrackSelectionParameters = player.trackSelectionParameters
|
|
76
|
+
currentOverride?.let { override ->
|
|
77
|
+
newParameters = newParameters.buildUpon().clearOverridesOfType(C.TRACK_TYPE_TEXT).build()
|
|
78
|
+
}
|
|
79
|
+
if (subtitleTrack == null) {
|
|
80
|
+
player.trackSelectionParameters = newParameters
|
|
81
|
+
setSubtitlesEnabled(false)
|
|
82
|
+
currentOverride = null
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
val format = formatsToGroups.keys.firstOrNull {
|
|
86
|
+
it.id == subtitleTrack.id
|
|
87
|
+
}
|
|
88
|
+
format?.let {
|
|
89
|
+
formatsToGroups[it]?.let { subtitlePair ->
|
|
90
|
+
val override = TrackSelectionOverride(subtitlePair.first, subtitlePair.second)
|
|
91
|
+
newParameters = newParameters.buildUpon().addOverride(override).build()
|
|
92
|
+
player.trackSelectionParameters = newParameters
|
|
93
|
+
setSubtitlesEnabled(true)
|
|
94
|
+
currentOverride = override
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private fun findSelectedSubtitleFormat(): Format? {
|
|
100
|
+
val trackSelectionParameters = videoPlayer?.player?.trackSelectionParameters
|
|
101
|
+
val preferredTextLanguages = trackSelectionParameters?.preferredTextLanguages
|
|
102
|
+
val overriddenFormat: Format? = trackSelectionParameters?.overrides?.let {
|
|
103
|
+
for ((group, override) in it) {
|
|
104
|
+
if (group.type == C.TRACK_TYPE_TEXT) {
|
|
105
|
+
// For subtitles only one index will be replaced
|
|
106
|
+
return@let override.trackIndices.firstOrNull()?.let { index ->
|
|
107
|
+
group.getFormat(index)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return@let null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
val preferredFormat: Format? = preferredTextLanguages?.let { preferredTextLanguages ->
|
|
115
|
+
for (preferredLanguage in preferredTextLanguages) {
|
|
116
|
+
return@let formatsToGroups.keys.firstOrNull {
|
|
117
|
+
it.language == preferredLanguage
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return@let null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return overriddenFormat ?: preferredFormat
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package expo.modules.video.records
|
|
2
|
+
|
|
3
|
+
import androidx.media3.common.util.UnstableApi
|
|
4
|
+
import expo.modules.kotlin.records.Field
|
|
5
|
+
import expo.modules.kotlin.records.Record
|
|
6
|
+
import expo.modules.video.player.VideoPlayerLoadControl
|
|
7
|
+
import java.io.Serializable
|
|
8
|
+
|
|
9
|
+
@UnstableApi
|
|
10
|
+
class BufferOptions(
|
|
11
|
+
@Field var preferredForwardBufferDuration: Double? = null,
|
|
12
|
+
@Field var maxBufferBytes: Long = 0,
|
|
13
|
+
@Field var prioritizeTimeOverSizeThreshold: Boolean = VideoPlayerLoadControl.DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS,
|
|
14
|
+
@Field var minBufferForPlayback: Double = VideoPlayerLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS.toDouble() / 1000
|
|
15
|
+
) : Record, Serializable
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package expo.modules.video.records
|
|
2
|
+
|
|
3
|
+
import androidx.media3.common.MediaItem
|
|
4
|
+
import expo.modules.kotlin.records.Field
|
|
5
|
+
import expo.modules.kotlin.records.Record
|
|
6
|
+
import expo.modules.video.enums.DRMType
|
|
7
|
+
import java.io.Serializable
|
|
8
|
+
|
|
9
|
+
class DRMOptions(
|
|
10
|
+
@Field var type: DRMType = DRMType.WIDEVINE,
|
|
11
|
+
@Field var licenseServer: String? = null,
|
|
12
|
+
@Field var headers: Map<String, String>? = null,
|
|
13
|
+
@Field var multiKey: Boolean = false
|
|
14
|
+
) : Record, Serializable {
|
|
15
|
+
|
|
16
|
+
fun toDRMConfiguration() = MediaItem
|
|
17
|
+
.DrmConfiguration
|
|
18
|
+
.Builder(type.toUUID())
|
|
19
|
+
.apply {
|
|
20
|
+
licenseServer?.let { setLicenseUri(it) }
|
|
21
|
+
headers?.let { setLicenseRequestHeaders(it) }
|
|
22
|
+
setMultiSession(multiKey)
|
|
23
|
+
}
|
|
24
|
+
.build()
|
|
25
|
+
}
|