@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.
Files changed (180) hide show
  1. package/README.md +45 -0
  2. package/android/build.gradle +32 -0
  3. package/android/src/main/AndroidManifest.xml +20 -0
  4. package/android/src/main/java/expo/modules/video/AudioFocusManager.kt +241 -0
  5. package/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt +145 -0
  6. package/android/src/main/java/expo/modules/video/IntervalUpdateClock.kt +54 -0
  7. package/android/src/main/java/expo/modules/video/MediaMetadataRetriever.kt +89 -0
  8. package/android/src/main/java/expo/modules/video/PictureInPictureHelperFragment.kt +26 -0
  9. package/android/src/main/java/expo/modules/video/PlayerViewExtension.kt +36 -0
  10. package/android/src/main/java/expo/modules/video/VideoCache.kt +104 -0
  11. package/android/src/main/java/expo/modules/video/VideoExceptions.kt +34 -0
  12. package/android/src/main/java/expo/modules/video/VideoManager.kt +133 -0
  13. package/android/src/main/java/expo/modules/video/VideoModule.kt +414 -0
  14. package/android/src/main/java/expo/modules/video/VideoThumbnail.kt +20 -0
  15. package/android/src/main/java/expo/modules/video/VideoView.kt +367 -0
  16. package/android/src/main/java/expo/modules/video/delegates/IgnoreSameSet.kt +24 -0
  17. package/android/src/main/java/expo/modules/video/drawing/OutlineProvider.kt +217 -0
  18. package/android/src/main/java/expo/modules/video/enums/AudioMixingMode.kt +20 -0
  19. package/android/src/main/java/expo/modules/video/enums/ContentFit.kt +19 -0
  20. package/android/src/main/java/expo/modules/video/enums/ContentType.kt +22 -0
  21. package/android/src/main/java/expo/modules/video/enums/DRMType.kt +26 -0
  22. package/android/src/main/java/expo/modules/video/enums/PlayerStatus.kt +10 -0
  23. package/android/src/main/java/expo/modules/video/playbackService/ExpoVideoPlaybackService.kt +184 -0
  24. package/android/src/main/java/expo/modules/video/playbackService/PlaybackServiceConnection.kt +39 -0
  25. package/android/src/main/java/expo/modules/video/playbackService/VideoMediaSessionCallback.kt +47 -0
  26. package/android/src/main/java/expo/modules/video/player/FirstFrameEventGenerator.kt +93 -0
  27. package/android/src/main/java/expo/modules/video/player/PlayerEvent.kt +164 -0
  28. package/android/src/main/java/expo/modules/video/player/VideoPlayer.kt +460 -0
  29. package/android/src/main/java/expo/modules/video/player/VideoPlayerAudioTracks.kt +125 -0
  30. package/android/src/main/java/expo/modules/video/player/VideoPlayerListener.kt +32 -0
  31. package/android/src/main/java/expo/modules/video/player/VideoPlayerLoadControl.kt +525 -0
  32. package/android/src/main/java/expo/modules/video/player/VideoPlayerSubtitles.kt +125 -0
  33. package/android/src/main/java/expo/modules/video/records/BufferOptions.kt +15 -0
  34. package/android/src/main/java/expo/modules/video/records/DRMOptions.kt +25 -0
  35. package/android/src/main/java/expo/modules/video/records/PlaybackError.kt +19 -0
  36. package/android/src/main/java/expo/modules/video/records/Tracks.kt +81 -0
  37. package/android/src/main/java/expo/modules/video/records/VideoEventPayloads.kt +79 -0
  38. package/android/src/main/java/expo/modules/video/records/VideoMetadata.kt +12 -0
  39. package/android/src/main/java/expo/modules/video/records/VideoSize.kt +14 -0
  40. package/android/src/main/java/expo/modules/video/records/VideoSource.kt +104 -0
  41. package/android/src/main/java/expo/modules/video/records/VideoThumbnailOptions.kt +24 -0
  42. package/android/src/main/java/expo/modules/video/utils/DataSourceUtils.kt +75 -0
  43. package/android/src/main/java/expo/modules/video/utils/EventDispatcherUtils.kt +43 -0
  44. package/android/src/main/java/expo/modules/video/utils/MutableWeakReference.kt +15 -0
  45. package/android/src/main/java/expo/modules/video/utils/PictureInPictureUtils.kt +96 -0
  46. package/android/src/main/java/expo/modules/video/utils/YogaUtils.kt +20 -0
  47. package/android/src/main/res/drawable/seek_backwards_10s.xml +25 -0
  48. package/android/src/main/res/drawable/seek_backwards_15s.xml +25 -0
  49. package/android/src/main/res/drawable/seek_backwards_5s.xml +25 -0
  50. package/android/src/main/res/drawable/seek_forwards_10s.xml +30 -0
  51. package/android/src/main/res/drawable/seek_forwards_15s.xml +31 -0
  52. package/android/src/main/res/drawable/seek_forwards_5s.xml +30 -0
  53. package/android/src/main/res/layout/fullscreen_player_activity.xml +16 -0
  54. package/android/src/main/res/layout/surface_player_view.xml +7 -0
  55. package/android/src/main/res/layout/texture_player_view.xml +7 -0
  56. package/android/src/main/res/values/styles.xml +9 -0
  57. package/app.plugin.js +1 -0
  58. package/build/NativeVideoModule.d.ts +16 -0
  59. package/build/NativeVideoModule.d.ts.map +1 -0
  60. package/build/NativeVideoModule.js +3 -0
  61. package/build/NativeVideoModule.js.map +1 -0
  62. package/build/NativeVideoModule.web.d.ts +3 -0
  63. package/build/NativeVideoModule.web.d.ts.map +1 -0
  64. package/build/NativeVideoModule.web.js +2 -0
  65. package/build/NativeVideoModule.web.js.map +1 -0
  66. package/build/NativeVideoView.d.ts +4 -0
  67. package/build/NativeVideoView.d.ts.map +1 -0
  68. package/build/NativeVideoView.js +6 -0
  69. package/build/NativeVideoView.js.map +1 -0
  70. package/build/VideoModule.d.ts +38 -0
  71. package/build/VideoModule.d.ts.map +1 -0
  72. package/build/VideoModule.js +53 -0
  73. package/build/VideoModule.js.map +1 -0
  74. package/build/VideoPlayer.d.ts +15 -0
  75. package/build/VideoPlayer.d.ts.map +1 -0
  76. package/build/VideoPlayer.js +52 -0
  77. package/build/VideoPlayer.js.map +1 -0
  78. package/build/VideoPlayer.types.d.ts +532 -0
  79. package/build/VideoPlayer.types.d.ts.map +1 -0
  80. package/build/VideoPlayer.types.js +2 -0
  81. package/build/VideoPlayer.types.js.map +1 -0
  82. package/build/VideoPlayer.web.d.ts +75 -0
  83. package/build/VideoPlayer.web.d.ts.map +1 -0
  84. package/build/VideoPlayer.web.js +376 -0
  85. package/build/VideoPlayer.web.js.map +1 -0
  86. package/build/VideoPlayerEvents.types.d.ts +262 -0
  87. package/build/VideoPlayerEvents.types.d.ts.map +1 -0
  88. package/build/VideoPlayerEvents.types.js +2 -0
  89. package/build/VideoPlayerEvents.types.js.map +1 -0
  90. package/build/VideoThumbnail.d.ts +29 -0
  91. package/build/VideoThumbnail.d.ts.map +1 -0
  92. package/build/VideoThumbnail.js +3 -0
  93. package/build/VideoThumbnail.js.map +1 -0
  94. package/build/VideoView.d.ts +44 -0
  95. package/build/VideoView.d.ts.map +1 -0
  96. package/build/VideoView.js +76 -0
  97. package/build/VideoView.js.map +1 -0
  98. package/build/VideoView.types.d.ts +147 -0
  99. package/build/VideoView.types.d.ts.map +1 -0
  100. package/build/VideoView.types.js +2 -0
  101. package/build/VideoView.types.js.map +1 -0
  102. package/build/VideoView.web.d.ts +9 -0
  103. package/build/VideoView.web.d.ts.map +1 -0
  104. package/build/VideoView.web.js +180 -0
  105. package/build/VideoView.web.js.map +1 -0
  106. package/build/index.d.ts +9 -0
  107. package/build/index.d.ts.map +1 -0
  108. package/build/index.js +7 -0
  109. package/build/index.js.map +1 -0
  110. package/build/resolveAssetSource.d.ts +3 -0
  111. package/build/resolveAssetSource.d.ts.map +1 -0
  112. package/build/resolveAssetSource.js +3 -0
  113. package/build/resolveAssetSource.js.map +1 -0
  114. package/build/resolveAssetSource.web.d.ts +4 -0
  115. package/build/resolveAssetSource.web.d.ts.map +1 -0
  116. package/build/resolveAssetSource.web.js +16 -0
  117. package/build/resolveAssetSource.web.js.map +1 -0
  118. package/expo-module.config.json +9 -0
  119. package/ios/Cache/CachableRequest.swift +44 -0
  120. package/ios/Cache/CachedResource.swift +97 -0
  121. package/ios/Cache/CachingHelpers.swift +92 -0
  122. package/ios/Cache/MediaFileHandle.swift +94 -0
  123. package/ios/Cache/MediaInfo.swift +147 -0
  124. package/ios/Cache/ResourceLoaderDelegate.swift +274 -0
  125. package/ios/Cache/SynchronizedHashTable.swift +23 -0
  126. package/ios/Cache/VideoCacheManager.swift +338 -0
  127. package/ios/ContentKeyDelegate.swift +214 -0
  128. package/ios/ContentKeyManager.swift +21 -0
  129. package/ios/Enums/AudioMixingMode.swift +37 -0
  130. package/ios/Enums/ContentType.swift +12 -0
  131. package/ios/Enums/DRMType.swift +20 -0
  132. package/ios/Enums/PlayerStatus.swift +10 -0
  133. package/ios/Enums/VideoContentFit.swift +39 -0
  134. package/ios/ExpoVideo.podspec +29 -0
  135. package/ios/NowPlayingManager.swift +296 -0
  136. package/ios/Records/BufferOptions.swift +12 -0
  137. package/ios/Records/DRMOptions.swift +24 -0
  138. package/ios/Records/PlaybackError.swift +10 -0
  139. package/ios/Records/Tracks.swift +176 -0
  140. package/ios/Records/VideoEventPayloads.swift +76 -0
  141. package/ios/Records/VideoMetadata.swift +16 -0
  142. package/ios/Records/VideoSize.swift +15 -0
  143. package/ios/Records/VideoSource.swift +25 -0
  144. package/ios/Thumbnails/VideoThumbnail.swift +27 -0
  145. package/ios/Thumbnails/VideoThumbnailGenerator.swift +68 -0
  146. package/ios/Thumbnails/VideoThumbnailOptions.swift +15 -0
  147. package/ios/VideoAsset.swift +123 -0
  148. package/ios/VideoExceptions.swift +53 -0
  149. package/ios/VideoItem.swift +11 -0
  150. package/ios/VideoManager.swift +140 -0
  151. package/ios/VideoModule.swift +383 -0
  152. package/ios/VideoPlayer/DangerousPropertiesStore.swift +19 -0
  153. package/ios/VideoPlayer.swift +435 -0
  154. package/ios/VideoPlayerAudioTracks.swift +72 -0
  155. package/ios/VideoPlayerItem.swift +97 -0
  156. package/ios/VideoPlayerObserver.swift +523 -0
  157. package/ios/VideoPlayerSubtitles.swift +71 -0
  158. package/ios/VideoSourceLoader.swift +89 -0
  159. package/ios/VideoSourceLoaderListener.swift +34 -0
  160. package/ios/VideoView.swift +224 -0
  161. package/package.json +59 -0
  162. package/plugin/build/tsconfig.tsbuildinfo +1 -0
  163. package/plugin/build/withExpoVideo.d.ts +7 -0
  164. package/plugin/build/withExpoVideo.js +38 -0
  165. package/src/NativeVideoModule.ts +20 -0
  166. package/src/NativeVideoModule.web.ts +1 -0
  167. package/src/NativeVideoView.ts +8 -0
  168. package/src/VideoModule.ts +59 -0
  169. package/src/VideoPlayer.tsx +67 -0
  170. package/src/VideoPlayer.types.ts +613 -0
  171. package/src/VideoPlayer.web.tsx +451 -0
  172. package/src/VideoPlayerEvents.types.ts +313 -0
  173. package/src/VideoThumbnail.ts +31 -0
  174. package/src/VideoView.tsx +86 -0
  175. package/src/VideoView.types.ts +165 -0
  176. package/src/VideoView.web.tsx +214 -0
  177. package/src/index.ts +46 -0
  178. package/src/resolveAssetSource.ts +2 -0
  179. package/src/resolveAssetSource.web.ts +17 -0
  180. 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
+ }