@granite-js/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/CHANGELOG.md +7 -0
- package/GraniteVideo.podspec +72 -0
- package/android/README.md +232 -0
- package/android/build.gradle +117 -0
- package/android/gradle.properties +8 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/run/granite/video/GraniteVideoModule.kt +70 -0
- package/android/src/main/java/run/granite/video/GraniteVideoPackage.kt +43 -0
- package/android/src/main/java/run/granite/video/GraniteVideoView.kt +384 -0
- package/android/src/main/java/run/granite/video/GraniteVideoViewManager.kt +318 -0
- package/android/src/main/java/run/granite/video/event/GraniteVideoEvents.kt +273 -0
- package/android/src/main/java/run/granite/video/event/VideoEventDispatcher.kt +66 -0
- package/android/src/main/java/run/granite/video/event/VideoEventListenerAdapter.kt +157 -0
- package/android/src/main/java/run/granite/video/provider/GraniteVideoProvider.kt +346 -0
- package/android/src/media3/AndroidManifest.xml +9 -0
- package/android/src/media3/java/run/granite/video/provider/media3/ExoPlayerProvider.kt +386 -0
- package/android/src/media3/java/run/granite/video/provider/media3/Media3ContentProvider.kt +29 -0
- package/android/src/media3/java/run/granite/video/provider/media3/Media3Initializer.kt +25 -0
- package/android/src/media3/java/run/granite/video/provider/media3/factory/ExoPlayerFactory.kt +32 -0
- package/android/src/media3/java/run/granite/video/provider/media3/factory/MediaSourceFactory.kt +61 -0
- package/android/src/media3/java/run/granite/video/provider/media3/factory/TrackSelectorFactory.kt +26 -0
- package/android/src/media3/java/run/granite/video/provider/media3/factory/VideoSurfaceFactory.kt +62 -0
- package/android/src/media3/java/run/granite/video/provider/media3/listener/ExoPlayerEventListener.kt +104 -0
- package/android/src/media3/java/run/granite/video/provider/media3/scheduler/ProgressScheduler.kt +56 -0
- package/android/src/test/java/run/granite/video/GraniteVideoViewRobolectricTest.kt +598 -0
- package/android/src/test/java/run/granite/video/event/VideoEventListenerAdapterTest.kt +319 -0
- package/android/src/test/java/run/granite/video/helpers/FakeGraniteVideoProvider.kt +161 -0
- package/android/src/test/java/run/granite/video/helpers/TestProgressScheduler.kt +42 -0
- package/android/src/test/java/run/granite/video/provider/GraniteVideoRegistryTest.kt +232 -0
- package/android/src/test/java/run/granite/video/provider/ProviderContractTest.kt +174 -0
- package/android/src/test/java/run/granite/video/provider/media3/listener/ExoPlayerEventListenerTest.kt +243 -0
- package/android/src/test/resources/kotest.properties +2 -0
- package/dist/module/GraniteVideo.js +458 -0
- package/dist/module/GraniteVideo.js.map +1 -0
- package/dist/module/GraniteVideoNativeComponent.ts +265 -0
- package/dist/module/index.js +7 -0
- package/dist/module/index.js.map +1 -0
- package/dist/module/package.json +1 -0
- package/dist/module/types.js +4 -0
- package/dist/module/types.js.map +1 -0
- package/dist/typescript/GraniteVideo.d.ts +12 -0
- package/dist/typescript/GraniteVideoNativeComponent.d.ts +189 -0
- package/dist/typescript/index.d.ts +5 -0
- package/dist/typescript/types.d.ts +328 -0
- package/ios/GraniteVideoComponentsProvider.h +10 -0
- package/ios/GraniteVideoProvider.swift +280 -0
- package/ios/GraniteVideoView.h +15 -0
- package/ios/GraniteVideoView.mm +661 -0
- package/ios/Providers/AVPlayerProvider.swift +541 -0
- package/package.json +106 -0
- package/src/GraniteVideo.tsx +575 -0
- package/src/GraniteVideoNativeComponent.ts +265 -0
- package/src/index.ts +8 -0
- package/src/types.ts +464 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
package run.granite.video
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.widget.FrameLayout
|
|
5
|
+
import run.granite.video.provider.*
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Video view that wraps a GraniteVideoProvider.
|
|
9
|
+
*
|
|
10
|
+
* Provider selection:
|
|
11
|
+
* - Default: Uses the provider set via GraniteVideoRegistry.setDefaultProvider()
|
|
12
|
+
* - Custom: Can inject a custom providerFactory for unit tests
|
|
13
|
+
*
|
|
14
|
+
* If no provider is registered, an IllegalStateException will be thrown.
|
|
15
|
+
*
|
|
16
|
+
* To change the default provider at runtime, use GraniteVideoModule.setDefaultProvider()
|
|
17
|
+
* from JavaScript before creating new video views.
|
|
18
|
+
*/
|
|
19
|
+
class GraniteVideoView @JvmOverloads constructor(
|
|
20
|
+
context: Context,
|
|
21
|
+
private val providerFactory: (() -> GraniteVideoProvider)? = null
|
|
22
|
+
) : FrameLayout(context), GraniteVideoDelegate {
|
|
23
|
+
|
|
24
|
+
private var provider: GraniteVideoProvider? = null
|
|
25
|
+
private var playerView: android.view.View? = null
|
|
26
|
+
|
|
27
|
+
// State
|
|
28
|
+
private var paused: Boolean = true
|
|
29
|
+
private var muted: Boolean = false
|
|
30
|
+
private var volume: Float = 1.0f
|
|
31
|
+
private var rate: Float = 1.0f
|
|
32
|
+
private var repeat: Boolean = false
|
|
33
|
+
private var resizeMode: String = "contain"
|
|
34
|
+
|
|
35
|
+
// Event listener
|
|
36
|
+
var eventListener: GraniteVideoEventListener? = null
|
|
37
|
+
|
|
38
|
+
// Expose provider for testing
|
|
39
|
+
val currentProvider: GraniteVideoProvider?
|
|
40
|
+
get() = provider
|
|
41
|
+
|
|
42
|
+
init {
|
|
43
|
+
setupProvider(context)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private fun setupProvider(context: Context) {
|
|
47
|
+
// Create provider using:
|
|
48
|
+
// 1. Custom factory (for testing)
|
|
49
|
+
// 2. Default from registry (set via GraniteVideoRegistry.setDefaultProvider)
|
|
50
|
+
provider = providerFactory?.invoke()
|
|
51
|
+
?: GraniteVideoRegistry.createProvider()
|
|
52
|
+
?: throw IllegalStateException(
|
|
53
|
+
"No video provider registered. Either register a provider via " +
|
|
54
|
+
"GraniteVideoRegistry.registerFactory() or enable the default provider " +
|
|
55
|
+
"by setting GRANITE_VIDEO_DEFAULT_PROVIDER=true."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
provider?.delegate = this
|
|
59
|
+
|
|
60
|
+
// Create player view
|
|
61
|
+
playerView = provider?.createPlayerView(context)
|
|
62
|
+
playerView?.let {
|
|
63
|
+
it.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
64
|
+
addView(it)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Source
|
|
69
|
+
fun setSource(source: Map<String, Any>?) {
|
|
70
|
+
source ?: return
|
|
71
|
+
|
|
72
|
+
val videoSource = GraniteVideoSource(
|
|
73
|
+
uri = source["uri"] as? String,
|
|
74
|
+
type = source["type"] as? String,
|
|
75
|
+
startPosition = (source["startPosition"] as? Number)?.toDouble() ?: 0.0,
|
|
76
|
+
cropStart = (source["cropStart"] as? Number)?.toDouble() ?: 0.0,
|
|
77
|
+
cropEnd = (source["cropEnd"] as? Number)?.toDouble() ?: 0.0,
|
|
78
|
+
headers = @Suppress("UNCHECKED_CAST") (source["headers"] as? Map<String, String>)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
provider?.loadSource(videoSource)
|
|
82
|
+
|
|
83
|
+
if (!paused) {
|
|
84
|
+
provider?.play()
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Playback Control
|
|
89
|
+
fun setPaused(paused: Boolean) {
|
|
90
|
+
this.paused = paused
|
|
91
|
+
if (paused) {
|
|
92
|
+
provider?.pause()
|
|
93
|
+
} else {
|
|
94
|
+
provider?.play()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fun setMuted(muted: Boolean) {
|
|
99
|
+
this.muted = muted
|
|
100
|
+
provider?.setMuted(muted)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fun setVolume(volume: Float) {
|
|
104
|
+
this.volume = volume
|
|
105
|
+
provider?.setVolume(volume)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fun setRate(rate: Float) {
|
|
109
|
+
this.rate = rate
|
|
110
|
+
provider?.setRate(rate)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fun setRepeat(repeat: Boolean) {
|
|
114
|
+
this.repeat = repeat
|
|
115
|
+
provider?.setRepeat(repeat)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
fun setResizeMode(mode: String) {
|
|
119
|
+
this.resizeMode = mode
|
|
120
|
+
val resizeModeEnum = when (mode) {
|
|
121
|
+
"cover" -> GraniteVideoResizeMode.COVER
|
|
122
|
+
"stretch" -> GraniteVideoResizeMode.STRETCH
|
|
123
|
+
"none" -> GraniteVideoResizeMode.NONE
|
|
124
|
+
else -> GraniteVideoResizeMode.CONTAIN
|
|
125
|
+
}
|
|
126
|
+
provider?.setResizeMode(resizeModeEnum)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Controls
|
|
130
|
+
fun setControls(enabled: Boolean) {
|
|
131
|
+
provider?.setControlsEnabled(enabled)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fun setFullscreen(fullscreen: Boolean) {
|
|
135
|
+
provider?.setFullscreen(fullscreen)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fun setPictureInPicture(enabled: Boolean) {
|
|
139
|
+
provider?.setPictureInPictureEnabled(enabled)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
fun setPlayInBackground(enabled: Boolean) {
|
|
143
|
+
provider?.setPlayInBackground(enabled)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fun setPlayWhenInactive(enabled: Boolean) {
|
|
147
|
+
provider?.setPlayWhenInactive(enabled)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Buffer
|
|
151
|
+
fun setBufferConfig(config: Map<String, Any>?) {
|
|
152
|
+
config ?: return
|
|
153
|
+
|
|
154
|
+
val bufferConfig = GraniteVideoBufferConfig(
|
|
155
|
+
minBufferMs = (config["minBufferMs"] as? Number)?.toInt() ?: 15000,
|
|
156
|
+
maxBufferMs = (config["maxBufferMs"] as? Number)?.toInt() ?: 50000,
|
|
157
|
+
bufferForPlaybackMs = (config["bufferForPlaybackMs"] as? Number)?.toInt() ?: 2500,
|
|
158
|
+
bufferForPlaybackAfterRebufferMs = (config["bufferForPlaybackAfterRebufferMs"] as? Number)?.toInt() ?: 5000,
|
|
159
|
+
backBufferDurationMs = (config["backBufferDurationMs"] as? Number)?.toInt() ?: 0,
|
|
160
|
+
cacheSizeMB = (config["cacheSizeMB"] as? Number)?.toInt() ?: 0
|
|
161
|
+
)
|
|
162
|
+
provider?.setBufferConfig(bufferConfig)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
fun setMaxBitRate(bitRate: Int) {
|
|
166
|
+
provider?.setMaxBitRate(bitRate)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fun setMinLoadRetryCount(count: Int) {
|
|
170
|
+
provider?.setMinLoadRetryCount(count)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Track Selection
|
|
174
|
+
fun setSelectedAudioTrack(track: Map<String, Any>?) {
|
|
175
|
+
track ?: return
|
|
176
|
+
val selectedTrack = GraniteVideoSelectedTrack(
|
|
177
|
+
type = track["type"] as? String ?: "system",
|
|
178
|
+
value = track["value"] as? String
|
|
179
|
+
)
|
|
180
|
+
provider?.setSelectedAudioTrack(selectedTrack)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fun setSelectedTextTrack(track: Map<String, Any>?) {
|
|
184
|
+
track ?: return
|
|
185
|
+
val selectedTrack = GraniteVideoSelectedTrack(
|
|
186
|
+
type = track["type"] as? String ?: "system",
|
|
187
|
+
value = track["value"] as? String
|
|
188
|
+
)
|
|
189
|
+
provider?.setSelectedTextTrack(selectedTrack)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
fun setSelectedVideoTrack(type: String, value: Int) {
|
|
193
|
+
provider?.setSelectedVideoTrack(type, value)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// View Settings
|
|
197
|
+
fun setUseTextureView(useTexture: Boolean) {
|
|
198
|
+
provider?.setUseTextureView(useTexture)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
fun setUseSecureView(useSecure: Boolean) {
|
|
202
|
+
provider?.setUseSecureView(useSecure)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
fun setShutterColor(color: Int) {
|
|
206
|
+
provider?.setShutterColor(color)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fun setHideShutterView(hide: Boolean) {
|
|
210
|
+
provider?.setHideShutterView(hide)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Commands
|
|
214
|
+
fun seek(time: Double, tolerance: Double = 0.0) {
|
|
215
|
+
provider?.seek(time, tolerance)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
fun seekCommand(time: Double, tolerance: Double) {
|
|
219
|
+
seek(time, tolerance)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
fun pauseCommand() {
|
|
223
|
+
provider?.pause()
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
fun resumeCommand() {
|
|
227
|
+
provider?.play()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
fun setVolumeCommand(volume: Float) {
|
|
231
|
+
setVolume(volume)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
fun setFullScreenCommand(fullscreen: Boolean) {
|
|
235
|
+
setFullscreen(fullscreen)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
fun setSourceCommand(uri: String) {
|
|
239
|
+
setSource(mapOf("uri" to uri))
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fun enterPictureInPictureCommand() {
|
|
243
|
+
provider?.enterPictureInPicture()
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
fun exitPictureInPictureCommand() {
|
|
247
|
+
provider?.exitPictureInPicture()
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// GraniteVideoDelegate Implementation
|
|
251
|
+
override fun onLoadStart(isNetwork: Boolean, type: String, uri: String) {
|
|
252
|
+
eventListener?.onLoadStart(isNetwork, type, uri)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
override fun onLoad(data: GraniteVideoLoadData) {
|
|
256
|
+
eventListener?.onLoad(data)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
override fun onError(error: GraniteVideoErrorData) {
|
|
260
|
+
eventListener?.onError(error)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
override fun onProgress(data: GraniteVideoProgressData) {
|
|
264
|
+
eventListener?.onProgress(data)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
override fun onSeek(currentTime: Double, seekTime: Double) {
|
|
268
|
+
eventListener?.onSeek(currentTime, seekTime)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
override fun onEnd() {
|
|
272
|
+
eventListener?.onEnd()
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
override fun onBuffer(isBuffering: Boolean) {
|
|
276
|
+
eventListener?.onBuffer(isBuffering)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
override fun onBandwidthUpdate(bitrate: Double, width: Int, height: Int) {
|
|
280
|
+
eventListener?.onBandwidthUpdate(bitrate, width, height)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
override fun onPlaybackStateChanged(isPlaying: Boolean, isSeeking: Boolean, isLooping: Boolean) {
|
|
284
|
+
eventListener?.onPlaybackStateChanged(isPlaying, isSeeking, isLooping)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
override fun onPlaybackRateChange(rate: Float) {
|
|
288
|
+
eventListener?.onPlaybackRateChange(rate)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
override fun onVolumeChange(volume: Float) {
|
|
292
|
+
eventListener?.onVolumeChange(volume)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
override fun onIdle() {
|
|
296
|
+
eventListener?.onIdle()
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
override fun onReadyForDisplay() {
|
|
300
|
+
eventListener?.onReadyForDisplay()
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
override fun onAudioFocusChanged(hasAudioFocus: Boolean) {
|
|
304
|
+
eventListener?.onAudioFocusChanged(hasAudioFocus)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
override fun onAudioBecomingNoisy() {
|
|
308
|
+
eventListener?.onAudioBecomingNoisy()
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
override fun onFullscreenPlayerWillPresent() {
|
|
312
|
+
eventListener?.onFullscreenPlayerWillPresent()
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
override fun onFullscreenPlayerDidPresent() {
|
|
316
|
+
eventListener?.onFullscreenPlayerDidPresent()
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
override fun onFullscreenPlayerWillDismiss() {
|
|
320
|
+
eventListener?.onFullscreenPlayerWillDismiss()
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
override fun onFullscreenPlayerDidDismiss() {
|
|
324
|
+
eventListener?.onFullscreenPlayerDidDismiss()
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
override fun onPictureInPictureStatusChanged(isActive: Boolean) {
|
|
328
|
+
eventListener?.onPictureInPictureStatusChanged(isActive)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
override fun onControlsVisibilityChanged(isVisible: Boolean) {
|
|
332
|
+
eventListener?.onControlsVisibilityChanged(isVisible)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
override fun onAspectRatioChanged(width: Double, height: Double) {
|
|
336
|
+
eventListener?.onAspectRatioChanged(width, height)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
override fun onTransferEnd(uri: String, bytesTransferred: Long) {
|
|
340
|
+
eventListener?.onTransferEnd(uri, bytesTransferred)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Cleanup
|
|
344
|
+
private fun releaseProvider() {
|
|
345
|
+
provider?.release()
|
|
346
|
+
provider = null
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
fun release() {
|
|
350
|
+
releaseProvider()
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
override fun onDetachedFromWindow() {
|
|
354
|
+
super.onDetachedFromWindow()
|
|
355
|
+
release()
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Event Listener Interface
|
|
360
|
+
interface GraniteVideoEventListener {
|
|
361
|
+
fun onLoadStart(isNetwork: Boolean, type: String, uri: String)
|
|
362
|
+
fun onLoad(data: GraniteVideoLoadData)
|
|
363
|
+
fun onError(error: GraniteVideoErrorData)
|
|
364
|
+
fun onProgress(data: GraniteVideoProgressData)
|
|
365
|
+
fun onSeek(currentTime: Double, seekTime: Double)
|
|
366
|
+
fun onEnd()
|
|
367
|
+
fun onBuffer(isBuffering: Boolean)
|
|
368
|
+
fun onBandwidthUpdate(bitrate: Double, width: Int, height: Int)
|
|
369
|
+
fun onPlaybackStateChanged(isPlaying: Boolean, isSeeking: Boolean, isLooping: Boolean)
|
|
370
|
+
fun onPlaybackRateChange(rate: Float)
|
|
371
|
+
fun onVolumeChange(volume: Float)
|
|
372
|
+
fun onIdle()
|
|
373
|
+
fun onReadyForDisplay()
|
|
374
|
+
fun onAudioFocusChanged(hasAudioFocus: Boolean)
|
|
375
|
+
fun onAudioBecomingNoisy()
|
|
376
|
+
fun onFullscreenPlayerWillPresent()
|
|
377
|
+
fun onFullscreenPlayerDidPresent()
|
|
378
|
+
fun onFullscreenPlayerWillDismiss()
|
|
379
|
+
fun onFullscreenPlayerDidDismiss()
|
|
380
|
+
fun onPictureInPictureStatusChanged(isActive: Boolean)
|
|
381
|
+
fun onControlsVisibilityChanged(isVisible: Boolean)
|
|
382
|
+
fun onAspectRatioChanged(width: Double, height: Double)
|
|
383
|
+
fun onTransferEnd(uri: String, bytesTransferred: Long) {}
|
|
384
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
package run.granite.video
|
|
2
|
+
|
|
3
|
+
import android.graphics.Color
|
|
4
|
+
import com.facebook.react.bridge.ReadableMap
|
|
5
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
6
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
7
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
8
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
9
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
10
|
+
import com.facebook.react.viewmanagers.GraniteVideoViewManagerInterface
|
|
11
|
+
import com.facebook.react.viewmanagers.GraniteVideoViewManagerDelegate
|
|
12
|
+
import com.facebook.react.common.MapBuilder
|
|
13
|
+
import run.granite.video.event.DefaultVideoEventDispatcherFactory
|
|
14
|
+
import run.granite.video.event.VideoEventDispatcherFactory
|
|
15
|
+
import run.granite.video.event.VideoEventListenerAdapter
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* React Native ViewManager for GraniteVideoView.
|
|
19
|
+
*
|
|
20
|
+
* This manager supports:
|
|
21
|
+
* - Provider selection via `providerId` prop
|
|
22
|
+
* - All standard video props (source, paused, volume, etc.)
|
|
23
|
+
* - Commands for playback control
|
|
24
|
+
*
|
|
25
|
+
* The ViewManager uses dependency injection for the event dispatcher factory,
|
|
26
|
+
* making it testable without React Native context.
|
|
27
|
+
*/
|
|
28
|
+
@ReactModule(name = GraniteVideoViewManager.NAME)
|
|
29
|
+
class GraniteVideoViewManager(
|
|
30
|
+
private val eventDispatcherFactory: VideoEventDispatcherFactory = DefaultVideoEventDispatcherFactory()
|
|
31
|
+
) : SimpleViewManager<GraniteVideoView>(),
|
|
32
|
+
GraniteVideoViewManagerInterface<GraniteVideoView> {
|
|
33
|
+
|
|
34
|
+
private val mDelegate: ViewManagerDelegate<GraniteVideoView> = GraniteVideoViewManagerDelegate(this)
|
|
35
|
+
|
|
36
|
+
override fun getDelegate(): ViewManagerDelegate<GraniteVideoView> = mDelegate
|
|
37
|
+
|
|
38
|
+
override fun getName(): String = NAME
|
|
39
|
+
|
|
40
|
+
override fun createViewInstance(context: ThemedReactContext): GraniteVideoView {
|
|
41
|
+
val view = GraniteVideoView(context)
|
|
42
|
+
|
|
43
|
+
// Set up event dispatcher and listener
|
|
44
|
+
val dispatcher = eventDispatcherFactory.create(view)
|
|
45
|
+
view.eventListener = VideoEventListenerAdapter(
|
|
46
|
+
dispatcher = dispatcher,
|
|
47
|
+
viewIdProvider = { view.id }
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return view
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override fun onDropViewInstance(view: GraniteVideoView) {
|
|
54
|
+
super.onDropViewInstance(view)
|
|
55
|
+
view.release()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Props
|
|
59
|
+
@ReactProp(name = "source")
|
|
60
|
+
override fun setSource(view: GraniteVideoView?, source: ReadableMap?) {
|
|
61
|
+
@Suppress("UNCHECKED_CAST")
|
|
62
|
+
view?.setSource(source?.toHashMap()?.filterValues { it != null } as? Map<String, Any>)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@ReactProp(name = "paused")
|
|
66
|
+
override fun setPaused(view: GraniteVideoView?, paused: Boolean) {
|
|
67
|
+
view?.setPaused(paused)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@ReactProp(name = "muted")
|
|
71
|
+
override fun setMuted(view: GraniteVideoView?, muted: Boolean) {
|
|
72
|
+
view?.setMuted(muted)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@ReactProp(name = "volume", defaultFloat = 1.0f)
|
|
76
|
+
override fun setVolume(view: GraniteVideoView?, volume: Float) {
|
|
77
|
+
view?.setVolume(volume)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@ReactProp(name = "rate", defaultFloat = 1.0f)
|
|
81
|
+
override fun setRate(view: GraniteVideoView?, rate: Float) {
|
|
82
|
+
view?.setRate(rate)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@ReactProp(name = "repeat")
|
|
86
|
+
override fun setRepeat(view: GraniteVideoView?, repeat: Boolean) {
|
|
87
|
+
view?.setRepeat(repeat)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@ReactProp(name = "resizeMode")
|
|
91
|
+
override fun setResizeMode(view: GraniteVideoView?, resizeMode: String?) {
|
|
92
|
+
view?.setResizeMode(resizeMode ?: "contain")
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@ReactProp(name = "controls")
|
|
96
|
+
override fun setControls(view: GraniteVideoView?, controls: Boolean) {
|
|
97
|
+
view?.setControls(controls)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@ReactProp(name = "fullscreen")
|
|
101
|
+
override fun setFullscreen(view: GraniteVideoView?, fullscreen: Boolean) {
|
|
102
|
+
view?.setFullscreen(fullscreen)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@ReactProp(name = "pictureInPicture")
|
|
106
|
+
override fun setPictureInPicture(view: GraniteVideoView?, pictureInPicture: Boolean) {
|
|
107
|
+
view?.setPictureInPicture(pictureInPicture)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@ReactProp(name = "playInBackground")
|
|
111
|
+
override fun setPlayInBackground(view: GraniteVideoView?, playInBackground: Boolean) {
|
|
112
|
+
view?.setPlayInBackground(playInBackground)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@ReactProp(name = "playWhenInactive")
|
|
116
|
+
override fun setPlayWhenInactive(view: GraniteVideoView?, playWhenInactive: Boolean) {
|
|
117
|
+
view?.setPlayWhenInactive(playWhenInactive)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@ReactProp(name = "bufferConfig")
|
|
121
|
+
override fun setBufferConfig(view: GraniteVideoView?, bufferConfig: ReadableMap?) {
|
|
122
|
+
@Suppress("UNCHECKED_CAST")
|
|
123
|
+
view?.setBufferConfig(bufferConfig?.toHashMap()?.filterValues { it != null } as? Map<String, Any>)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@ReactProp(name = "maxBitRate")
|
|
127
|
+
override fun setMaxBitRate(view: GraniteVideoView?, maxBitRate: Int) {
|
|
128
|
+
view?.setMaxBitRate(maxBitRate)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@ReactProp(name = "minLoadRetryCount")
|
|
132
|
+
override fun setMinLoadRetryCount(view: GraniteVideoView?, minLoadRetryCount: Int) {
|
|
133
|
+
view?.setMinLoadRetryCount(minLoadRetryCount)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@ReactProp(name = "selectedAudioTrack")
|
|
137
|
+
override fun setSelectedAudioTrack(view: GraniteVideoView?, selectedAudioTrack: ReadableMap?) {
|
|
138
|
+
@Suppress("UNCHECKED_CAST")
|
|
139
|
+
view?.setSelectedAudioTrack(selectedAudioTrack?.toHashMap()?.filterValues { it != null } as? Map<String, Any>)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@ReactProp(name = "selectedTextTrack")
|
|
143
|
+
override fun setSelectedTextTrack(view: GraniteVideoView?, selectedTextTrack: ReadableMap?) {
|
|
144
|
+
@Suppress("UNCHECKED_CAST")
|
|
145
|
+
view?.setSelectedTextTrack(selectedTextTrack?.toHashMap()?.filterValues { it != null } as? Map<String, Any>)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@ReactProp(name = "selectedVideoTrack")
|
|
149
|
+
override fun setSelectedVideoTrack(view: GraniteVideoView?, selectedVideoTrack: ReadableMap?) {
|
|
150
|
+
val type = selectedVideoTrack?.getString("type") ?: "auto"
|
|
151
|
+
val value = selectedVideoTrack?.getInt("value") ?: 0
|
|
152
|
+
view?.setSelectedVideoTrack(type, value)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@ReactProp(name = "viewType")
|
|
156
|
+
override fun setViewType(view: GraniteVideoView?, viewType: String?) {
|
|
157
|
+
view?.setUseTextureView(viewType == "texture")
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@ReactProp(name = "useTextureView")
|
|
161
|
+
override fun setUseTextureView(view: GraniteVideoView?, useTextureView: Boolean) {
|
|
162
|
+
view?.setUseTextureView(useTextureView)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@ReactProp(name = "useSecureView")
|
|
166
|
+
override fun setUseSecureView(view: GraniteVideoView?, useSecureView: Boolean) {
|
|
167
|
+
view?.setUseSecureView(useSecureView)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@ReactProp(name = "shutterColor")
|
|
171
|
+
override fun setShutterColor(view: GraniteVideoView?, shutterColor: String?) {
|
|
172
|
+
shutterColor?.let {
|
|
173
|
+
try {
|
|
174
|
+
view?.setShutterColor(Color.parseColor(it))
|
|
175
|
+
} catch (e: Exception) {
|
|
176
|
+
// Invalid color
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@ReactProp(name = "hideShutterView")
|
|
182
|
+
override fun setHideShutterView(view: GraniteVideoView?, hideShutterView: Boolean) {
|
|
183
|
+
view?.setHideShutterView(hideShutterView)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Other props with default implementations
|
|
187
|
+
@ReactProp(name = "poster")
|
|
188
|
+
override fun setPoster(view: GraniteVideoView?, poster: String?) {}
|
|
189
|
+
|
|
190
|
+
@ReactProp(name = "posterResizeMode")
|
|
191
|
+
override fun setPosterResizeMode(view: GraniteVideoView?, posterResizeMode: String?) {}
|
|
192
|
+
|
|
193
|
+
@ReactProp(name = "automaticallyWaitsToMinimizeStalling")
|
|
194
|
+
override fun setAutomaticallyWaitsToMinimizeStalling(view: GraniteVideoView?, value: Boolean) {}
|
|
195
|
+
|
|
196
|
+
@ReactProp(name = "preferredForwardBufferDuration")
|
|
197
|
+
override fun setPreferredForwardBufferDuration(view: GraniteVideoView?, value: Double) {}
|
|
198
|
+
|
|
199
|
+
@ReactProp(name = "drm")
|
|
200
|
+
override fun setDrm(view: GraniteVideoView?, drm: ReadableMap?) {}
|
|
201
|
+
|
|
202
|
+
@ReactProp(name = "localSourceEncryptionKeyScheme")
|
|
203
|
+
override fun setLocalSourceEncryptionKeyScheme(view: GraniteVideoView?, value: String?) {}
|
|
204
|
+
|
|
205
|
+
@ReactProp(name = "adTagUrl")
|
|
206
|
+
override fun setAdTagUrl(view: GraniteVideoView?, value: String?) {}
|
|
207
|
+
|
|
208
|
+
@ReactProp(name = "adLanguage")
|
|
209
|
+
override fun setAdLanguage(view: GraniteVideoView?, value: String?) {}
|
|
210
|
+
|
|
211
|
+
@ReactProp(name = "showNotificationControls")
|
|
212
|
+
override fun setShowNotificationControls(view: GraniteVideoView?, value: Boolean) {}
|
|
213
|
+
|
|
214
|
+
@ReactProp(name = "disableFocus")
|
|
215
|
+
override fun setDisableFocus(view: GraniteVideoView?, value: Boolean) {}
|
|
216
|
+
|
|
217
|
+
@ReactProp(name = "disableDisconnectError")
|
|
218
|
+
override fun setDisableDisconnectError(view: GraniteVideoView?, value: Boolean) {}
|
|
219
|
+
|
|
220
|
+
@ReactProp(name = "focusable")
|
|
221
|
+
override fun setFocusable(view: GraniteVideoView?, value: Boolean) {}
|
|
222
|
+
|
|
223
|
+
@ReactProp(name = "preventsDisplaySleepDuringVideoPlayback")
|
|
224
|
+
override fun setPreventsDisplaySleepDuringVideoPlayback(view: GraniteVideoView?, value: Boolean) {}
|
|
225
|
+
|
|
226
|
+
@ReactProp(name = "fullscreenAutorotate")
|
|
227
|
+
override fun setFullscreenAutorotate(view: GraniteVideoView?, value: Boolean) {}
|
|
228
|
+
|
|
229
|
+
@ReactProp(name = "fullscreenOrientation")
|
|
230
|
+
override fun setFullscreenOrientation(view: GraniteVideoView?, value: String?) {}
|
|
231
|
+
|
|
232
|
+
@ReactProp(name = "contentStartTime")
|
|
233
|
+
override fun setContentStartTime(view: GraniteVideoView?, value: Double) {}
|
|
234
|
+
|
|
235
|
+
@ReactProp(name = "allowsExternalPlayback")
|
|
236
|
+
override fun setAllowsExternalPlayback(view: GraniteVideoView?, value: Boolean) {}
|
|
237
|
+
|
|
238
|
+
@ReactProp(name = "audioOutput")
|
|
239
|
+
override fun setAudioOutput(view: GraniteVideoView?, value: String?) {}
|
|
240
|
+
|
|
241
|
+
@ReactProp(name = "ignoreSilentSwitch")
|
|
242
|
+
override fun setIgnoreSilentSwitch(view: GraniteVideoView?, value: String?) {}
|
|
243
|
+
|
|
244
|
+
@ReactProp(name = "mixWithOthers")
|
|
245
|
+
override fun setMixWithOthers(view: GraniteVideoView?, value: String?) {}
|
|
246
|
+
|
|
247
|
+
@ReactProp(name = "enableDebug")
|
|
248
|
+
override fun setEnableDebug(view: GraniteVideoView?, value: Boolean) {}
|
|
249
|
+
|
|
250
|
+
@ReactProp(name = "enableDebugThread")
|
|
251
|
+
override fun setEnableDebugThread(view: GraniteVideoView?, value: Boolean) {}
|
|
252
|
+
|
|
253
|
+
// Commands
|
|
254
|
+
override fun seek(view: GraniteVideoView?, time: Double, tolerance: Double) {
|
|
255
|
+
view?.seekCommand(time, tolerance)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
override fun adjustVolume(view: GraniteVideoView?, volume: Float) {
|
|
259
|
+
view?.setVolumeCommand(volume)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
override fun setFullScreen(view: GraniteVideoView?, fullscreen: Boolean) {
|
|
263
|
+
view?.setFullScreenCommand(fullscreen)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
override fun loadSource(view: GraniteVideoView?, uri: String?) {
|
|
267
|
+
uri?.let { view?.setSourceCommand(it) }
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
override fun pause(view: GraniteVideoView?) {
|
|
271
|
+
view?.pauseCommand()
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
override fun resume(view: GraniteVideoView?) {
|
|
275
|
+
view?.resumeCommand()
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
override fun enterPictureInPicture(view: GraniteVideoView?) {
|
|
279
|
+
view?.enterPictureInPictureCommand()
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
override fun exitPictureInPicture(view: GraniteVideoView?) {
|
|
283
|
+
view?.exitPictureInPictureCommand()
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Event registration
|
|
287
|
+
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> {
|
|
288
|
+
return MapBuilder.builder<String, Any>()
|
|
289
|
+
.put("topVideoLoadStart", MapBuilder.of("registrationName", "onVideoLoadStart"))
|
|
290
|
+
.put("topVideoLoad", MapBuilder.of("registrationName", "onVideoLoad"))
|
|
291
|
+
.put("topVideoError", MapBuilder.of("registrationName", "onVideoError"))
|
|
292
|
+
.put("topVideoProgress", MapBuilder.of("registrationName", "onVideoProgress"))
|
|
293
|
+
.put("topVideoSeek", MapBuilder.of("registrationName", "onVideoSeek"))
|
|
294
|
+
.put("topVideoEnd", MapBuilder.of("registrationName", "onVideoEnd"))
|
|
295
|
+
.put("topVideoBuffer", MapBuilder.of("registrationName", "onVideoBuffer"))
|
|
296
|
+
.put("topVideoBandwidthUpdate", MapBuilder.of("registrationName", "onVideoBandwidthUpdate"))
|
|
297
|
+
.put("topVideoPlaybackStateChanged", MapBuilder.of("registrationName", "onVideoPlaybackStateChanged"))
|
|
298
|
+
.put("topVideoPlaybackRateChange", MapBuilder.of("registrationName", "onVideoPlaybackRateChange"))
|
|
299
|
+
.put("topVideoVolumeChange", MapBuilder.of("registrationName", "onVideoVolumeChange"))
|
|
300
|
+
.put("topVideoIdle", MapBuilder.of("registrationName", "onVideoIdle"))
|
|
301
|
+
.put("topVideoReadyForDisplay", MapBuilder.of("registrationName", "onVideoReadyForDisplay"))
|
|
302
|
+
.put("topVideoAudioFocusChanged", MapBuilder.of("registrationName", "onVideoAudioFocusChanged"))
|
|
303
|
+
.put("topVideoAudioBecomingNoisy", MapBuilder.of("registrationName", "onVideoAudioBecomingNoisy"))
|
|
304
|
+
.put("topVideoFullscreenPlayerWillPresent", MapBuilder.of("registrationName", "onVideoFullscreenPlayerWillPresent"))
|
|
305
|
+
.put("topVideoFullscreenPlayerDidPresent", MapBuilder.of("registrationName", "onVideoFullscreenPlayerDidPresent"))
|
|
306
|
+
.put("topVideoFullscreenPlayerWillDismiss", MapBuilder.of("registrationName", "onVideoFullscreenPlayerWillDismiss"))
|
|
307
|
+
.put("topVideoFullscreenPlayerDidDismiss", MapBuilder.of("registrationName", "onVideoFullscreenPlayerDidDismiss"))
|
|
308
|
+
.put("topVideoPictureInPictureStatusChanged", MapBuilder.of("registrationName", "onVideoPictureInPictureStatusChanged"))
|
|
309
|
+
.put("topVideoControlsVisibilityChange", MapBuilder.of("registrationName", "onVideoControlsVisibilityChange"))
|
|
310
|
+
.put("topVideoAspectRatio", MapBuilder.of("registrationName", "onVideoAspectRatio"))
|
|
311
|
+
.put("topVideoTransferEnd", MapBuilder.of("registrationName", "onVideoTransferEnd"))
|
|
312
|
+
.build()
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
companion object {
|
|
316
|
+
const val NAME = "GraniteVideoView"
|
|
317
|
+
}
|
|
318
|
+
}
|