@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @granite-js/video
2
+
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 260daab: feat: introduce support react native 0.84
@@ -0,0 +1,72 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ # ============================================================
6
+ # GraniteVideo Default Provider Configuration
7
+ # ============================================================
8
+ # Priority: GRANITE_VIDEO_DEFAULT_PROVIDER > GRANITE_DEFAULT_PROVIDER_ALL > true (default)
9
+ #
10
+ # Examples:
11
+ # Include default provider (default):
12
+ # pod install
13
+ #
14
+ # Exclude default provider for video only:
15
+ # GRANITE_VIDEO_DEFAULT_PROVIDER=false pod install
16
+ #
17
+ # Exclude default providers for all Granite packages:
18
+ # GRANITE_DEFAULT_PROVIDER_ALL=false pod install
19
+ #
20
+ # Exclude all but override video to include:
21
+ # GRANITE_DEFAULT_PROVIDER_ALL=false GRANITE_VIDEO_DEFAULT_PROVIDER=true pod install
22
+ # ============================================================
23
+ resolve_default_provider = lambda do |specific_key, fallback_key, default_value|
24
+ if ENV.key?(specific_key)
25
+ ENV[specific_key] == 'true'
26
+ elsif ENV.key?(fallback_key)
27
+ ENV[fallback_key] == 'true'
28
+ else
29
+ default_value
30
+ end
31
+ end
32
+
33
+ use_default_provider = resolve_default_provider.call(
34
+ 'GRANITE_VIDEO_DEFAULT_PROVIDER',
35
+ 'GRANITE_DEFAULT_PROVIDER_ALL',
36
+ true
37
+ )
38
+
39
+ # Exclude AVPlayerProvider when not using default provider
40
+ exclude_patterns = []
41
+ exclude_patterns << "ios/Providers/AVPlayerProvider.swift" unless use_default_provider
42
+
43
+ Pod::Spec.new do |s|
44
+ s.name = "GraniteVideo"
45
+ s.version = package["version"]
46
+ s.summary = package["description"]
47
+ s.homepage = package["homepage"]
48
+ s.license = package["license"]
49
+ s.authors = package["author"]
50
+
51
+ s.platforms = { :ios => min_ios_version_supported }
52
+ s.source = { :git => "https://github.com/toss/granite.git", :tag => "#{s.version}" }
53
+
54
+ s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
55
+ s.exclude_files = exclude_patterns if exclude_patterns.any?
56
+
57
+ # Preprocessor definitions for conditional compilation
58
+ preprocessor_defs = ['$(inherited)']
59
+ preprocessor_defs << 'GRANITE_VIDEO_DEFAULT_PROVIDER=1' if use_default_provider
60
+
61
+ s.pod_target_xcconfig = {
62
+ 'CLANG_ENABLE_MODULES' => 'YES',
63
+ 'DEFINES_MODULE' => 'YES',
64
+ 'SWIFT_OBJC_INTERFACE_HEADER_NAME' => 'GraniteVideo-Swift.h',
65
+ 'GCC_PREPROCESSOR_DEFINITIONS' => preprocessor_defs.join(' ')
66
+ }
67
+
68
+ s.frameworks = ["AVFoundation", "AVKit", "CoreMedia"]
69
+
70
+ # React Native modules dependencies (Fabric/TurboModule)
71
+ install_modules_dependencies(s)
72
+ end
@@ -0,0 +1,232 @@
1
+ # GraniteVideo Android
2
+
3
+ Android native module for React Native video player library.
4
+
5
+ ## Module Structure
6
+
7
+ ```
8
+ packages/video/
9
+ ├── android/ → granite-video (Core)
10
+ └── android-media3/ → granite-video-media3 (Media3 ExoPlayer implementation)
11
+ ```
12
+
13
+ - **granite-video**: Video player interface, registry, React Native view
14
+ - **granite-video-media3**: Default implementation based on Media3 ExoPlayer
15
+
16
+ ---
17
+
18
+ ## Basic Usage
19
+
20
+ ### 1. Add Dependencies
21
+
22
+ ```gradle
23
+ // app/build.gradle
24
+ dependencies {
25
+ implementation project(':granite-video')
26
+ implementation project(':granite-video-media3') // Media3 ExoPlayer
27
+ }
28
+ ```
29
+
30
+ ### 2. Register React Native Packages
31
+
32
+ ```kotlin
33
+ // MainApplication.kt
34
+ import run.granite.video.GraniteVideoPackage
35
+ import run.granite.video.media3.GraniteVideoMedia3Package
36
+
37
+ override fun getPackages(): List<ReactPackage> {
38
+ val packages = PackageList(this).packages.toMutableList()
39
+ packages.add(GraniteVideoPackage())
40
+ packages.add(GraniteVideoMedia3Package()) // Auto-registers Media3 provider
41
+ return packages
42
+ }
43
+ ```
44
+
45
+ ---
46
+
47
+ ## Media3 Provider (Default)
48
+
49
+ Media3 ExoPlayer is the default video provider and is enabled by default.
50
+
51
+ ### Disabling Media3
52
+
53
+ To disable Media3 and use a custom provider, you can use either:
54
+
55
+ **Option 1: gradle.properties**
56
+
57
+ ```properties
58
+ graniteVideo.useMedia3=false
59
+ ```
60
+
61
+ **Option 2: Environment variable** (takes priority)
62
+
63
+ ```bash
64
+ export GRANITE_VIDEO_USE_MEDIA3=false
65
+ ```
66
+
67
+ Priority: Environment variable > gradle.properties > default (true)
68
+
69
+ ### Custom Provider Registration
70
+
71
+ When Media3 is disabled, register your custom provider in your Application class:
72
+
73
+ ```kotlin
74
+ class MyApplication : Application() {
75
+ override fun onCreate() {
76
+ super.onCreate()
77
+
78
+ GraniteVideoRegistry.registerFactory("custom") { MyCustomProvider() }
79
+ GraniteVideoRegistry.setDefaultProvider("custom")
80
+ }
81
+ }
82
+ ```
83
+
84
+ ---
85
+
86
+ ## Custom Provider Implementation
87
+
88
+ To use a different player (VLC, ijkplayer, etc.) instead of Media3, implement a custom provider.
89
+
90
+ ### 1. Implement GraniteVideoProvider Interface
91
+
92
+ ```kotlin
93
+ package com.example.video
94
+
95
+ import android.content.Context
96
+ import android.view.View
97
+ import run.granite.video.provider.*
98
+
99
+ class MyCustomProvider : GraniteVideoProvider {
100
+
101
+ // Required: Provider identification
102
+ override val providerId: String = "custom"
103
+ override val providerName: String = "My Custom Player"
104
+
105
+ // Required: Delegate (event callbacks)
106
+ override var delegate: GraniteVideoDelegate? = null
107
+
108
+ // Required: State properties
109
+ override val currentTime: Double get() = /* current playback position */
110
+ override val duration: Double get() = /* total duration */
111
+ override val isPlaying: Boolean get() = /* is playing */
112
+
113
+ // Required: Create player view
114
+ override fun createPlayerView(context: Context): View {
115
+ // Return player view (e.g., SurfaceView, TextureView)
116
+ return MyPlayerView(context)
117
+ }
118
+
119
+ // Required: Load source
120
+ override fun loadSource(source: GraniteVideoSource) {
121
+ val uri = source.uri ?: return
122
+ // Load media from URI
123
+ // Handle source.headers, source.drm, etc.
124
+
125
+ // Fire load start event
126
+ delegate?.onLoadStart(
127
+ isNetwork = uri.startsWith("http"),
128
+ type = source.type ?: "unknown",
129
+ uri = uri
130
+ )
131
+ }
132
+
133
+ override fun unload() {
134
+ // Release resources
135
+ }
136
+
137
+ // Required: Playback control
138
+ override fun play() {
139
+ // Start playback
140
+ delegate?.onPlaybackStateChanged(isPlaying = true, isSeeking = false, isLooping = false)
141
+ }
142
+
143
+ override fun pause() {
144
+ // Pause playback
145
+ delegate?.onPlaybackStateChanged(isPlaying = false, isSeeking = false, isLooping = false)
146
+ }
147
+
148
+ override fun seek(time: Double, tolerance: Double) {
149
+ val previousTime = currentTime
150
+ // Seek to position
151
+ delegate?.onSeek(currentTime = previousTime, seekTime = time)
152
+ }
153
+
154
+ // Optional: Volume, rate, repeat, etc.
155
+ override fun setVolume(volume: Float) { /* ... */ }
156
+ override fun setRate(rate: Float) { /* ... */ }
157
+ override fun setRepeat(shouldRepeat: Boolean) { /* ... */ }
158
+
159
+ // Optional: Release resources
160
+ fun release() {
161
+ unload()
162
+ delegate = null
163
+ }
164
+ }
165
+ ```
166
+
167
+ ### 2. Create Provider Initialization Package
168
+
169
+ ```kotlin
170
+ package com.example.video
171
+
172
+ import com.facebook.react.ReactPackage
173
+ import com.facebook.react.bridge.NativeModule
174
+ import com.facebook.react.bridge.ReactApplicationContext
175
+ import com.facebook.react.uimanager.ViewManager
176
+ import run.granite.video.provider.GraniteVideoRegistry
177
+
178
+ class MyCustomVideoPackage : ReactPackage {
179
+
180
+ init {
181
+ // Register provider when package is instantiated
182
+ GraniteVideoRegistry.registerFactory("custom") { MyCustomProvider() }
183
+ GraniteVideoRegistry.setDefaultProvider("custom")
184
+ }
185
+
186
+ override fun createNativeModules(ctx: ReactApplicationContext): List<NativeModule> = emptyList()
187
+ override fun createViewManagers(ctx: ReactApplicationContext): List<ViewManager<*, *>> = emptyList()
188
+ }
189
+ ```
190
+
191
+ ### 3. Register Package in MainApplication
192
+
193
+ ```kotlin
194
+ // MainApplication.kt
195
+ import run.granite.video.GraniteVideoPackage
196
+ import com.example.video.MyCustomVideoPackage
197
+
198
+ override fun getPackages(): List<ReactPackage> {
199
+ val packages = PackageList(this).packages.toMutableList()
200
+
201
+ // Core Package (required)
202
+ packages.add(GraniteVideoPackage())
203
+
204
+ // Custom Provider Package (instead of Media3Package)
205
+ packages.add(MyCustomVideoPackage())
206
+
207
+ return packages
208
+ }
209
+ ```
210
+
211
+ ---
212
+
213
+ ## Provider Event Callbacks
214
+
215
+ Providers communicate events via `delegate`:
216
+
217
+ | Method | Description |
218
+ | --------------------------------------------------------- | ------------------------------------------ |
219
+ | `onLoadStart(isNetwork, type, uri)` | Source load started |
220
+ | `onLoad(data)` | Load complete (duration, dimensions, etc.) |
221
+ | `onProgress(data)` | Playback progress |
222
+ | `onError(error)` | Error occurred |
223
+ | `onEnd()` | Playback ended |
224
+ | `onBuffer(isBuffering)` | Buffering state changed |
225
+ | `onPlaybackStateChanged(isPlaying, isSeeking, isLooping)` | Playback state changed |
226
+ | `onSeek(currentTime, seekTime)` | Seek completed |
227
+
228
+ ---
229
+
230
+ ## License
231
+
232
+ Apache License 2.0
@@ -0,0 +1,117 @@
1
+ buildscript {
2
+ ext.getExtOrDefault = {name ->
3
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['GraniteVideo_' + name]
4
+ }
5
+
6
+ repositories {
7
+ google()
8
+ mavenCentral()
9
+ }
10
+
11
+ dependencies {
12
+ classpath "com.android.tools.build:gradle:8.7.2"
13
+ // noinspection DifferentKotlinGradleVersion
14
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
15
+ }
16
+ }
17
+
18
+
19
+ apply plugin: "com.android.library"
20
+ apply plugin: "kotlin-android"
21
+
22
+ apply plugin: "com.facebook.react"
23
+
24
+ def getExtOrIntegerDefault(name) {
25
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["GraniteVideo_" + name]).toInteger()
26
+ }
27
+
28
+ // Check useMedia3 flag from environment variable or gradle.properties (default: true)
29
+ // Priority: Environment variable > root gradle.properties > module gradle.properties > default (true)
30
+ def useMedia3Flag = System.getenv('GRANITE_VIDEO_USE_MEDIA3') ?: rootProject.findProperty('graniteVideo.useMedia3')?.toString() ?: project.findProperty('graniteVideo.useMedia3')?.toString()
31
+ def useMedia3 = useMedia3Flag != 'false'
32
+
33
+ android {
34
+ namespace "run.granite.video"
35
+
36
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
37
+
38
+ defaultConfig {
39
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
40
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
41
+
42
+ // Pass flag to BuildConfig for conditional registration
43
+ buildConfigField "boolean", "USE_MEDIA3", useMedia3.toString()
44
+ }
45
+
46
+ buildFeatures {
47
+ buildConfig true
48
+ }
49
+
50
+ buildTypes {
51
+ release {
52
+ minifyEnabled false
53
+ }
54
+ }
55
+
56
+ lintOptions {
57
+ disable "GradleCompatible"
58
+ }
59
+
60
+ compileOptions {
61
+ sourceCompatibility JavaVersion.VERSION_1_8
62
+ targetCompatibility JavaVersion.VERSION_1_8
63
+ }
64
+
65
+ sourceSets {
66
+ main {
67
+ java {
68
+ srcDirs += ["generated/java", "generated/jni"]
69
+ // Conditionally include Media3 sources
70
+ if (useMedia3) {
71
+ srcDirs += ["src/media3/java"]
72
+ }
73
+ }
74
+ // Use different manifest based on Media3 flag
75
+ // - media3 manifest includes ContentProvider for auto-initialization
76
+ // - main manifest is empty (for custom provider use case)
77
+ if (useMedia3) {
78
+ manifest.srcFile "src/media3/AndroidManifest.xml"
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ repositories {
85
+ mavenCentral()
86
+ google()
87
+ }
88
+
89
+ def kotlin_version = getExtOrDefault("kotlinVersion")
90
+
91
+ dependencies {
92
+ implementation "com.facebook.react:react-android"
93
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
94
+
95
+ // Media3 ExoPlayer dependencies (conditional)
96
+ if (useMedia3) {
97
+ implementation "androidx.media3:media3-exoplayer:1.2.1"
98
+ implementation "androidx.media3:media3-exoplayer-dash:1.2.1"
99
+ implementation "androidx.media3:media3-exoplayer-hls:1.2.1"
100
+ implementation "androidx.media3:media3-exoplayer-smoothstreaming:1.2.1"
101
+ implementation "androidx.media3:media3-datasource:1.2.1"
102
+ implementation "androidx.media3:media3-ui:1.2.1"
103
+ }
104
+
105
+ // Test dependencies
106
+ testImplementation "io.kotest:kotest-runner-junit5:5.9.1"
107
+ testImplementation "io.kotest:kotest-assertions-core:5.9.1"
108
+ testImplementation "io.kotest:kotest-property:5.9.1"
109
+ testImplementation "io.mockk:mockk:1.13.9"
110
+ testImplementation "org.robolectric:robolectric:4.11.1"
111
+ testImplementation "junit:junit:4.13.2"
112
+ }
113
+
114
+ tasks.withType(Test).configureEach {
115
+ useJUnitPlatform()
116
+ }
117
+
@@ -0,0 +1,8 @@
1
+ GraniteVideo_kotlinVersion=2.1.20
2
+ GraniteVideo_minSdkVersion=24
3
+ GraniteVideo_targetSdkVersion=35
4
+ GraniteVideo_compileSdkVersion=35
5
+ GraniteVideo_ndkVersion=27.1.12297006
6
+
7
+ # Media3 provider enabled by default
8
+ graniteVideo.useMedia3=true
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,70 @@
1
+ package run.granite.video
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
5
+ import com.facebook.react.bridge.ReactMethod
6
+ import com.facebook.react.bridge.Promise
7
+ import com.facebook.react.bridge.ReadableMap
8
+ import run.granite.video.provider.GraniteVideoRegistry
9
+
10
+ class GraniteVideoModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
11
+
12
+ override fun getName(): String = NAME
13
+
14
+ @ReactMethod
15
+ fun clearCache(promise: Promise) {
16
+ try {
17
+ GraniteVideoRegistry.createProvider()?.clearCache()
18
+ promise.resolve(null)
19
+ } catch (e: Exception) {
20
+ promise.reject("CLEAR_CACHE_ERROR", e.message, e)
21
+ }
22
+ }
23
+
24
+ @ReactMethod
25
+ fun getWidevineLevel(promise: Promise) {
26
+ try {
27
+ val level = GraniteVideoRegistry.createProvider()?.getWidevineLevel() ?: 0
28
+ promise.resolve(level)
29
+ } catch (e: Exception) {
30
+ promise.reject("WIDEVINE_ERROR", e.message, e)
31
+ }
32
+ }
33
+
34
+ @ReactMethod
35
+ fun isCodecSupported(mimeType: String, width: Int, height: Int, promise: Promise) {
36
+ try {
37
+ val supported = GraniteVideoRegistry.createProvider()?.isCodecSupported(mimeType, width, height) ?: false
38
+ promise.resolve(supported)
39
+ } catch (e: Exception) {
40
+ promise.reject("CODEC_ERROR", e.message, e)
41
+ }
42
+ }
43
+
44
+ @ReactMethod
45
+ fun isHEVCSupported(promise: Promise) {
46
+ try {
47
+ val supported = GraniteVideoRegistry.createProvider()?.isHEVCSupported() ?: false
48
+ promise.resolve(supported)
49
+ } catch (e: Exception) {
50
+ promise.reject("HEVC_ERROR", e.message, e)
51
+ }
52
+ }
53
+
54
+ @ReactMethod
55
+ fun getCurrentPosition(viewTag: Int, promise: Promise) {
56
+ // This would need to find the view by tag and get the current position
57
+ // For now, return 0
58
+ promise.resolve(0.0)
59
+ }
60
+
61
+ @ReactMethod
62
+ fun save(viewTag: Int, options: ReadableMap?, promise: Promise) {
63
+ // Save functionality - would need implementation
64
+ promise.reject("NOT_IMPLEMENTED", "Save is not implemented on Android")
65
+ }
66
+
67
+ companion object {
68
+ const val NAME = "GraniteVideoModule"
69
+ }
70
+ }
@@ -0,0 +1,43 @@
1
+ package run.granite.video
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ /**
9
+ * React Native package for GraniteVideo core functionality.
10
+ *
11
+ * This package provides the video view and module.
12
+ * Video provider registration is handled automatically via ContentProvider
13
+ * when USE_MEDIA3 is enabled (default).
14
+ *
15
+ * ## Basic Usage
16
+ * ```kotlin
17
+ * // Default configuration (uses Media3)
18
+ * packages.add(GraniteVideoPackage())
19
+ * ```
20
+ *
21
+ * ## Custom Provider Configuration
22
+ * To disable Media3 and use a custom provider:
23
+ * ```
24
+ * // In gradle.properties
25
+ * graniteVideo.useMedia3=false
26
+ * ```
27
+ *
28
+ * Then register your custom provider:
29
+ * ```kotlin
30
+ * GraniteVideoRegistry.registerFactory("custom") { MyCustomProvider() }
31
+ * GraniteVideoRegistry.setDefaultProvider("custom")
32
+ * ```
33
+ */
34
+ class GraniteVideoPackage : ReactPackage {
35
+
36
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
37
+ return listOf(GraniteVideoViewManager())
38
+ }
39
+
40
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
41
+ return listOf(GraniteVideoModule(reactContext))
42
+ }
43
+ }