@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,346 @@
1
+ package run.granite.video.provider
2
+
3
+ import android.content.Context
4
+ import android.view.View
5
+
6
+ // ============================================================
7
+ // Enums
8
+ // ============================================================
9
+
10
+ enum class GraniteVideoResizeMode {
11
+ CONTAIN,
12
+ COVER,
13
+ STRETCH,
14
+ NONE
15
+ }
16
+
17
+ enum class GraniteVideoDrmType {
18
+ NONE,
19
+ WIDEVINE,
20
+ PLAYREADY,
21
+ CLEARKEY
22
+ }
23
+
24
+ enum class GraniteVideoAudioOutput {
25
+ SPEAKER,
26
+ EARPIECE
27
+ }
28
+
29
+ // ============================================================
30
+ // Data Classes
31
+ // ============================================================
32
+
33
+ data class GraniteVideoSource(
34
+ val uri: String? = null,
35
+ val type: String? = null,
36
+ val startPosition: Double = 0.0,
37
+ val cropStart: Double = 0.0,
38
+ val cropEnd: Double = 0.0,
39
+ val headers: Map<String, String>? = null,
40
+ val drm: GraniteVideoDrmConfig? = null
41
+ )
42
+
43
+ data class GraniteVideoDrmConfig(
44
+ val type: GraniteVideoDrmType = GraniteVideoDrmType.NONE,
45
+ val licenseServer: String? = null,
46
+ val headers: Map<String, String>? = null,
47
+ val contentId: String? = null
48
+ )
49
+
50
+ data class GraniteVideoBufferConfig(
51
+ val minBufferMs: Int = 15000,
52
+ val maxBufferMs: Int = 50000,
53
+ val bufferForPlaybackMs: Int = 2500,
54
+ val bufferForPlaybackAfterRebufferMs: Int = 5000,
55
+ val backBufferDurationMs: Int = 0,
56
+ val cacheSizeMB: Int = 0
57
+ )
58
+
59
+ data class GraniteVideoSelectedTrack(
60
+ val type: String = "system",
61
+ val value: String? = null
62
+ )
63
+
64
+ // ============================================================
65
+ // Event Data Classes
66
+ // ============================================================
67
+
68
+ data class GraniteVideoLoadData(
69
+ val currentTime: Double = 0.0,
70
+ val duration: Double = 0.0,
71
+ val naturalWidth: Double = 0.0,
72
+ val naturalHeight: Double = 0.0,
73
+ val orientation: String = "landscape"
74
+ )
75
+
76
+ data class GraniteVideoProgressData(
77
+ val currentTime: Double = 0.0,
78
+ val playableDuration: Double = 0.0,
79
+ val seekableDuration: Double = 0.0
80
+ )
81
+
82
+ data class GraniteVideoErrorData(
83
+ val code: Int = 0,
84
+ val domain: String = "",
85
+ val localizedDescription: String = "",
86
+ val errorString: String = ""
87
+ )
88
+
89
+ // ============================================================
90
+ // Delegate Interface
91
+ // ============================================================
92
+
93
+ interface GraniteVideoDelegate {
94
+ fun onLoadStart(isNetwork: Boolean, type: String, uri: String) {}
95
+ fun onLoad(data: GraniteVideoLoadData) {}
96
+ fun onError(error: GraniteVideoErrorData) {}
97
+ fun onProgress(data: GraniteVideoProgressData) {}
98
+ fun onSeek(currentTime: Double, seekTime: Double) {}
99
+ fun onEnd() {}
100
+ fun onBuffer(isBuffering: Boolean) {}
101
+ fun onBandwidthUpdate(bitrate: Double, width: Int, height: Int) {}
102
+ fun onPlaybackStateChanged(isPlaying: Boolean, isSeeking: Boolean, isLooping: Boolean) {}
103
+ fun onPlaybackRateChange(rate: Float) {}
104
+ fun onVolumeChange(volume: Float) {}
105
+ fun onIdle() {}
106
+ fun onReadyForDisplay() {}
107
+ fun onAudioFocusChanged(hasAudioFocus: Boolean) {}
108
+ fun onAudioBecomingNoisy() {}
109
+ fun onFullscreenPlayerWillPresent() {}
110
+ fun onFullscreenPlayerDidPresent() {}
111
+ fun onFullscreenPlayerWillDismiss() {}
112
+ fun onFullscreenPlayerDidDismiss() {}
113
+ fun onPictureInPictureStatusChanged(isActive: Boolean) {}
114
+ fun onControlsVisibilityChanged(isVisible: Boolean) {}
115
+ fun onAspectRatioChanged(width: Double, height: Double) {}
116
+ fun onTransferEnd(uri: String, bytesTransferred: Long) {}
117
+ }
118
+
119
+ // ============================================================
120
+ // Provider Interface
121
+ // ============================================================
122
+
123
+ interface GraniteVideoProvider {
124
+ // Required - Provider Identification
125
+ val providerId: String
126
+ val providerName: String
127
+
128
+ // Required - View Creation
129
+ fun createPlayerView(context: Context): View
130
+
131
+ // Required - Source Loading
132
+ fun loadSource(source: GraniteVideoSource)
133
+ fun unload()
134
+
135
+ // Required - Playback Control
136
+ fun play()
137
+ fun pause()
138
+ fun seek(time: Double, tolerance: Double = 0.0)
139
+
140
+ // Required - Properties
141
+ var delegate: GraniteVideoDelegate?
142
+ val currentTime: Double
143
+ val duration: Double
144
+ val isPlaying: Boolean
145
+
146
+ // Optional - Volume
147
+ fun setVolume(volume: Float) {}
148
+ fun setMuted(muted: Boolean) {}
149
+
150
+ // Optional - Rate
151
+ fun setRate(rate: Float) {}
152
+
153
+ // Optional - Repeat
154
+ fun setRepeat(shouldRepeat: Boolean) {}
155
+
156
+ // Optional - Resize Mode
157
+ fun setResizeMode(mode: GraniteVideoResizeMode) {}
158
+
159
+ // Optional - Background Playback
160
+ fun setPlayInBackground(enabled: Boolean) {}
161
+ fun setPlayWhenInactive(enabled: Boolean) {}
162
+
163
+ // Optional - Audio Output
164
+ fun setAudioOutput(output: GraniteVideoAudioOutput) {}
165
+
166
+ // Optional - Fullscreen
167
+ fun setFullscreen(fullscreen: Boolean, animated: Boolean = true) {}
168
+ fun setFullscreenAutorotate(autorotate: Boolean) {}
169
+ fun setFullscreenOrientation(orientation: String) {}
170
+
171
+ // Optional - Picture in Picture
172
+ fun setPictureInPictureEnabled(enabled: Boolean) {}
173
+ fun enterPictureInPicture() {}
174
+ fun exitPictureInPicture() {}
175
+
176
+ // Optional - Controls
177
+ fun setControlsEnabled(enabled: Boolean) {}
178
+ fun setPreventsDisplaySleepDuringVideoPlayback(prevents: Boolean) {}
179
+
180
+ // Optional - Buffer Config
181
+ fun setBufferConfig(config: GraniteVideoBufferConfig) {}
182
+ fun setMaxBitRate(bitRate: Int) {}
183
+ fun setMinLoadRetryCount(count: Int) {}
184
+
185
+ // Optional - Track Selection
186
+ fun setSelectedAudioTrack(track: GraniteVideoSelectedTrack) {}
187
+ fun setSelectedTextTrack(track: GraniteVideoSelectedTrack) {}
188
+ fun setSelectedVideoTrack(type: String, value: Int) {}
189
+
190
+ // Optional - DRM
191
+ fun setDrmConfig(config: GraniteVideoDrmConfig) {}
192
+
193
+ // Optional - View Type
194
+ fun setUseTextureView(useTexture: Boolean) {}
195
+ fun setUseSecureView(useSecure: Boolean) {}
196
+
197
+ // Optional - Shutter
198
+ fun setShutterColor(color: Int) {}
199
+ fun setHideShutterView(hide: Boolean) {}
200
+
201
+ // Optional - Cache Management
202
+ fun clearCache() {}
203
+
204
+ // Optional - Codec Support
205
+ fun isCodecSupported(mimeType: String, width: Int, height: Int): Boolean = false
206
+ fun isHEVCSupported(): Boolean = false
207
+ fun getWidevineLevel(): Int = 0
208
+
209
+ // Lifecycle - Release resources
210
+ fun release() {}
211
+ }
212
+
213
+ // ============================================================
214
+ // Provider Info
215
+ // ============================================================
216
+
217
+ data class ProviderInfo(
218
+ val id: String,
219
+ val name: String
220
+ )
221
+
222
+ // ============================================================
223
+ // Registry Singleton
224
+ // ============================================================
225
+
226
+ object GraniteVideoRegistry {
227
+ // Multi-provider storage: id -> factory
228
+ private val providers = mutableMapOf<String, () -> GraniteVideoProvider>()
229
+ private var defaultProviderId: String? = null
230
+
231
+ // Legacy support
232
+ private var legacyFactory: (() -> GraniteVideoProvider)? = null
233
+ private var legacyProvider: GraniteVideoProvider? = null
234
+
235
+ // ============================================================
236
+ // New API - Multi-Provider Support
237
+ // ============================================================
238
+
239
+ /**
240
+ * Register a provider factory with a specific ID.
241
+ * If the ID already exists, it will be overwritten.
242
+ */
243
+ fun registerFactory(id: String, factory: () -> GraniteVideoProvider) {
244
+ providers[id] = factory
245
+ }
246
+
247
+ /**
248
+ * Create a provider instance by ID.
249
+ * Returns null if the ID is not registered.
250
+ */
251
+ fun createProvider(id: String): GraniteVideoProvider? {
252
+ return providers[id]?.invoke()
253
+ }
254
+
255
+ /**
256
+ * Set the default provider ID.
257
+ * When createProvider() is called without an ID, this provider will be created.
258
+ */
259
+ fun setDefaultProvider(id: String) {
260
+ defaultProviderId = id
261
+ }
262
+
263
+ /**
264
+ * Get list of all registered provider IDs.
265
+ */
266
+ fun getAvailableProviders(): List<String> {
267
+ return providers.keys.toList()
268
+ }
269
+
270
+ /**
271
+ * Get provider info by ID.
272
+ * Creates a temporary instance to read providerId and providerName.
273
+ */
274
+ fun getProviderInfo(id: String): ProviderInfo? {
275
+ val factory = providers[id] ?: return null
276
+ val provider = factory()
277
+ return ProviderInfo(id = provider.providerId, name = provider.providerName)
278
+ }
279
+
280
+ /**
281
+ * Clear all registered providers and reset state.
282
+ * Primarily used for testing.
283
+ */
284
+ @androidx.annotation.VisibleForTesting
285
+ fun clear() {
286
+ providers.clear()
287
+ defaultProviderId = null
288
+ legacyFactory = null
289
+ legacyProvider = null
290
+ }
291
+
292
+ // ============================================================
293
+ // Legacy API - Backward Compatibility
294
+ // ============================================================
295
+
296
+ /**
297
+ * Register a provider instance (legacy).
298
+ */
299
+ fun register(provider: GraniteVideoProvider) {
300
+ legacyProvider = provider
301
+ // Also register in new system if it has an ID
302
+ registerFactory(provider.providerId) { provider }
303
+ }
304
+
305
+ /**
306
+ * Register a provider factory without ID (legacy).
307
+ * The factory will be used as fallback when no ID is specified.
308
+ */
309
+ fun registerFactory(factory: () -> GraniteVideoProvider) {
310
+ legacyFactory = factory
311
+ // Create one instance to get the ID and register
312
+ val instance = factory()
313
+ registerFactory(instance.providerId, factory)
314
+ }
315
+
316
+ /**
317
+ * Create a provider instance (legacy/default).
318
+ * Priority:
319
+ * 1. Default provider ID if set
320
+ * 2. Legacy factory
321
+ * 3. Legacy provider instance
322
+ * 4. First available provider
323
+ */
324
+ fun createProvider(): GraniteVideoProvider? {
325
+ // 1. Try default provider
326
+ defaultProviderId?.let { id ->
327
+ return providers[id]?.invoke()
328
+ }
329
+
330
+ // 2. Try legacy factory (also registered in providers map)
331
+ legacyFactory?.let { return it() }
332
+
333
+ // 3. Try legacy provider
334
+ legacyProvider?.let { return it }
335
+
336
+ // 4. Return first available
337
+ return providers.values.firstOrNull()?.invoke()
338
+ }
339
+
340
+ /**
341
+ * Check if any provider is registered.
342
+ */
343
+ fun hasProvider(): Boolean {
344
+ return providers.isNotEmpty() || legacyFactory != null || legacyProvider != null
345
+ }
346
+ }
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3
+ <application>
4
+ <provider
5
+ android:name="run.granite.video.provider.media3.Media3ContentProvider"
6
+ android:authorities="${applicationId}.granite-video-media3-init"
7
+ android:exported="false" />
8
+ </application>
9
+ </manifest>