@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/GraniteVideo.podspec +72 -0
  3. package/android/README.md +232 -0
  4. package/android/build.gradle +117 -0
  5. package/android/gradle.properties +8 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/run/granite/video/GraniteVideoModule.kt +70 -0
  8. package/android/src/main/java/run/granite/video/GraniteVideoPackage.kt +43 -0
  9. package/android/src/main/java/run/granite/video/GraniteVideoView.kt +384 -0
  10. package/android/src/main/java/run/granite/video/GraniteVideoViewManager.kt +318 -0
  11. package/android/src/main/java/run/granite/video/event/GraniteVideoEvents.kt +273 -0
  12. package/android/src/main/java/run/granite/video/event/VideoEventDispatcher.kt +66 -0
  13. package/android/src/main/java/run/granite/video/event/VideoEventListenerAdapter.kt +157 -0
  14. package/android/src/main/java/run/granite/video/provider/GraniteVideoProvider.kt +346 -0
  15. package/android/src/media3/AndroidManifest.xml +9 -0
  16. package/android/src/media3/java/run/granite/video/provider/media3/ExoPlayerProvider.kt +386 -0
  17. package/android/src/media3/java/run/granite/video/provider/media3/Media3ContentProvider.kt +29 -0
  18. package/android/src/media3/java/run/granite/video/provider/media3/Media3Initializer.kt +25 -0
  19. package/android/src/media3/java/run/granite/video/provider/media3/factory/ExoPlayerFactory.kt +32 -0
  20. package/android/src/media3/java/run/granite/video/provider/media3/factory/MediaSourceFactory.kt +61 -0
  21. package/android/src/media3/java/run/granite/video/provider/media3/factory/TrackSelectorFactory.kt +26 -0
  22. package/android/src/media3/java/run/granite/video/provider/media3/factory/VideoSurfaceFactory.kt +62 -0
  23. package/android/src/media3/java/run/granite/video/provider/media3/listener/ExoPlayerEventListener.kt +104 -0
  24. package/android/src/media3/java/run/granite/video/provider/media3/scheduler/ProgressScheduler.kt +56 -0
  25. package/android/src/test/java/run/granite/video/GraniteVideoViewRobolectricTest.kt +598 -0
  26. package/android/src/test/java/run/granite/video/event/VideoEventListenerAdapterTest.kt +319 -0
  27. package/android/src/test/java/run/granite/video/helpers/FakeGraniteVideoProvider.kt +161 -0
  28. package/android/src/test/java/run/granite/video/helpers/TestProgressScheduler.kt +42 -0
  29. package/android/src/test/java/run/granite/video/provider/GraniteVideoRegistryTest.kt +232 -0
  30. package/android/src/test/java/run/granite/video/provider/ProviderContractTest.kt +174 -0
  31. package/android/src/test/java/run/granite/video/provider/media3/listener/ExoPlayerEventListenerTest.kt +243 -0
  32. package/android/src/test/resources/kotest.properties +2 -0
  33. package/dist/module/GraniteVideo.js +458 -0
  34. package/dist/module/GraniteVideo.js.map +1 -0
  35. package/dist/module/GraniteVideoNativeComponent.ts +265 -0
  36. package/dist/module/index.js +7 -0
  37. package/dist/module/index.js.map +1 -0
  38. package/dist/module/package.json +1 -0
  39. package/dist/module/types.js +4 -0
  40. package/dist/module/types.js.map +1 -0
  41. package/dist/typescript/GraniteVideo.d.ts +12 -0
  42. package/dist/typescript/GraniteVideoNativeComponent.d.ts +189 -0
  43. package/dist/typescript/index.d.ts +5 -0
  44. package/dist/typescript/types.d.ts +328 -0
  45. package/ios/GraniteVideoComponentsProvider.h +10 -0
  46. package/ios/GraniteVideoProvider.swift +280 -0
  47. package/ios/GraniteVideoView.h +15 -0
  48. package/ios/GraniteVideoView.mm +661 -0
  49. package/ios/Providers/AVPlayerProvider.swift +541 -0
  50. package/package.json +106 -0
  51. package/src/GraniteVideo.tsx +575 -0
  52. package/src/GraniteVideoNativeComponent.ts +265 -0
  53. package/src/index.ts +8 -0
  54. 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
+ }