@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,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>
|