@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
package/CHANGELOG.md
ADDED
|
@@ -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,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
|
+
}
|