expo-mpv 0.1.5 → 0.1.7
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/README.md
CHANGED
|
@@ -189,29 +189,6 @@ const media = await playerRef.current?.getMediaInfo();
|
|
|
189
189
|
- `getCurrentTrackIds()`
|
|
190
190
|
- `getMediaInfo()` — returns codec, resolution, fps, bitrate, hwdec status, pixel format, colorspace
|
|
191
191
|
|
|
192
|
-
### API changes
|
|
193
|
-
|
|
194
|
-
If you used an earlier iOS-only version, this release adds and/or formalizes:
|
|
195
|
-
|
|
196
|
-
- Android support
|
|
197
|
-
- `hwdec` prop for platform-specific hardware decode control
|
|
198
|
-
- `stop()`
|
|
199
|
-
- `removeSubtitle(trackId)`
|
|
200
|
-
- `reloadSubtitles()`
|
|
201
|
-
- `setPropertyString(name, value)`
|
|
202
|
-
- `getCurrentTrackIds()`
|
|
203
|
-
- `getMediaInfo()` for codec, resolution, fps, bitrate, hwdec status
|
|
204
|
-
|
|
205
|
-
The README examples now assume the shared iOS/Android API surface rather than an iOS-only integration flow.
|
|
206
|
-
|
|
207
|
-
### Migration notes
|
|
208
|
-
|
|
209
|
-
- Add `"expo-mpv"` to your Expo plugins list if you previously relied on manual native setup
|
|
210
|
-
- Keep `expo-build-properties` for the iOS 16 deployment target
|
|
211
|
-
- Re-run `npx expo prebuild` after upgrading so the plugin can wire native changes into both platforms
|
|
212
|
-
- Android native dependencies are now fetched as part of the build, so CI should allow Gradle network access on first build
|
|
213
|
-
- If you wrapped the player with platform checks because Android was unsupported, you can simplify that logic once your Android app is configured
|
|
214
|
-
|
|
215
192
|
## CJK Subtitle Rendering
|
|
216
193
|
|
|
217
194
|
This module bundles [Noto Sans CJK SC](https://github.com/notofonts/noto-cjk) (SIL Open Font License) for Chinese/Japanese/Korean subtitle rendering.
|
|
@@ -242,18 +219,6 @@ React Native (JS)
|
|
|
242
219
|
|
|
243
220
|
On simulator, `vo=gpu` is used instead of `vo=gpu-next` to avoid a crash in `MTLSimDriver` caused by XPC shared memory size limits when libplacebo uploads video frame textures.
|
|
244
221
|
|
|
245
|
-
## License transition
|
|
246
|
-
|
|
247
|
-
This project is now licensed under `GPL-3.0-only` instead of MIT.
|
|
248
|
-
|
|
249
|
-
That means:
|
|
250
|
-
|
|
251
|
-
- source code and derivative distributions must remain under GPLv3-compatible terms
|
|
252
|
-
- if you redistribute a modified version, you must also provide the corresponding source under GPLv3
|
|
253
|
-
- downstream consumers should review compatibility before bundling this module into closed-source products
|
|
254
|
-
|
|
255
|
-
Bundled third-party assets and dependencies may carry their own licenses. For example, the bundled Noto font remains under the SIL Open Font License.
|
|
256
|
-
|
|
257
222
|
## License
|
|
258
223
|
|
|
259
224
|
[GPL-3.0](./LICENSE)
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
package expo.modules.mpv
|
|
2
2
|
|
|
3
|
-
import android.app.Activity
|
|
4
|
-
import android.app.Application
|
|
5
3
|
import android.content.Context
|
|
6
|
-
import android.os.
|
|
4
|
+
import android.os.Build
|
|
7
5
|
import android.os.Handler
|
|
8
6
|
import android.os.Looper
|
|
9
7
|
import android.view.SurfaceHolder
|
|
10
8
|
import android.view.SurfaceView
|
|
9
|
+
import android.view.View
|
|
11
10
|
import expo.modules.kotlin.AppContext
|
|
12
11
|
import expo.modules.kotlin.viewevent.EventDispatcher
|
|
13
12
|
import expo.modules.kotlin.views.ExpoView
|
|
@@ -30,7 +29,7 @@ class ExpoMpvView(context: Context, appContext: AppContext) : ExpoView(context,
|
|
|
30
29
|
private var nativePtr: Long = 0
|
|
31
30
|
private var isInitialized = false
|
|
32
31
|
private var pendingSource: String? = null
|
|
33
|
-
private var pendingHwdec: String = "mediacodec"
|
|
32
|
+
private var pendingHwdec: String = if (isEmulator()) "no" else "mediacodec"
|
|
34
33
|
private val mainHandler = Handler(Looper.getMainLooper())
|
|
35
34
|
private var progressRunnable: Runnable? = null
|
|
36
35
|
|
|
@@ -85,49 +84,36 @@ class ExpoMpvView(context: Context, appContext: AppContext) : ExpoView(context,
|
|
|
85
84
|
}
|
|
86
85
|
}
|
|
87
86
|
})
|
|
88
|
-
|
|
89
|
-
setupLifecycle()
|
|
90
87
|
}
|
|
91
88
|
|
|
92
|
-
|
|
89
|
+
override fun onDetachedFromWindow() {
|
|
90
|
+
super.onDetachedFromWindow()
|
|
91
|
+
destroy()
|
|
92
|
+
}
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
override fun onWindowVisibilityChanged(visibility: Int) {
|
|
95
|
+
super.onWindowVisibilityChanged(visibility)
|
|
96
|
+
if (nativePtr != 0L && isInitialized) {
|
|
97
|
+
if (visibility == View.GONE || visibility == View.INVISIBLE) {
|
|
97
98
|
setPropertyString("vid", "no")
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
override fun onActivityResumed(activity: Activity) {
|
|
101
|
-
if (nativePtr != 0L && isInitialized && activity == appContext.currentActivity) {
|
|
99
|
+
} else {
|
|
102
100
|
setPropertyString("vid", "auto")
|
|
103
101
|
}
|
|
104
102
|
}
|
|
105
|
-
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
|
|
106
|
-
override fun onActivityStarted(activity: Activity) {}
|
|
107
|
-
override fun onActivityStopped(activity: Activity) {}
|
|
108
|
-
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
|
|
109
|
-
override fun onActivityDestroyed(activity: Activity) {}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
private fun setupLifecycle() {
|
|
113
|
-
val app = context.applicationContext as? Application ?: return
|
|
114
|
-
app.registerActivityLifecycleCallbacks(lifecycleCallbacks)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
override fun onDetachedFromWindow() {
|
|
118
|
-
super.onDetachedFromWindow()
|
|
119
|
-
destroy()
|
|
120
103
|
}
|
|
121
104
|
|
|
122
105
|
fun destroy() {
|
|
123
106
|
stopProgressTimer()
|
|
124
|
-
val
|
|
125
|
-
|
|
126
|
-
if (nativePtr != 0L) {
|
|
127
|
-
MPVLib.nativeDestroy(nativePtr)
|
|
128
|
-
nativePtr = 0
|
|
129
|
-
}
|
|
107
|
+
val ptr = nativePtr
|
|
108
|
+
nativePtr = 0
|
|
130
109
|
isInitialized = false
|
|
110
|
+
if (ptr != 0L) {
|
|
111
|
+
// Destroy on background thread to avoid blocking the main thread
|
|
112
|
+
// (pthread_join + mpv_terminate_destroy can take seconds)
|
|
113
|
+
Thread {
|
|
114
|
+
MPVLib.nativeDestroy(ptr)
|
|
115
|
+
}.start()
|
|
116
|
+
}
|
|
131
117
|
}
|
|
132
118
|
|
|
133
119
|
// MARK: - MPV Setup (two-phase: create+options, then initialize after surface)
|
|
@@ -145,7 +131,19 @@ class ExpoMpvView(context: Context, appContext: AppContext) : ExpoView(context,
|
|
|
145
131
|
MPVLib.nativeSetOptionString(nativePtr, "vo", "gpu")
|
|
146
132
|
MPVLib.nativeSetOptionString(nativePtr, "gpu-context", "android")
|
|
147
133
|
MPVLib.nativeSetOptionString(nativePtr, "hwdec", pendingHwdec)
|
|
148
|
-
|
|
134
|
+
if (pendingHwdec != "no") {
|
|
135
|
+
MPVLib.nativeSetOptionString(nativePtr, "hwdec-codecs", "h264,hevc,mpeg4,mpeg2video,vp8,vp9,av1")
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (isEmulator()) {
|
|
139
|
+
android.util.Log.w("ExpoMpv", "Emulator detected, hardware decoding disabled (using software decoding)")
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Network stream caching
|
|
143
|
+
MPVLib.nativeSetOptionString(nativePtr, "cache", "yes")
|
|
144
|
+
MPVLib.nativeSetOptionString(nativePtr, "demuxer-max-bytes", "150M")
|
|
145
|
+
MPVLib.nativeSetOptionString(nativePtr, "demuxer-max-back-bytes", "50M")
|
|
146
|
+
MPVLib.nativeSetOptionString(nativePtr, "cache-pause", "yes")
|
|
149
147
|
|
|
150
148
|
// General
|
|
151
149
|
MPVLib.nativeSetOptionString(nativePtr, "force-window", "yes")
|
|
@@ -455,4 +453,24 @@ class ExpoMpvView(context: Context, appContext: AppContext) : ExpoView(context,
|
|
|
455
453
|
if (nativePtr == 0L) return
|
|
456
454
|
MPVLib.nativeCommand(nativePtr, (listOf(cmd) + args).toTypedArray())
|
|
457
455
|
}
|
|
456
|
+
|
|
457
|
+
companion object {
|
|
458
|
+
private fun isEmulator(): Boolean {
|
|
459
|
+
return (Build.FINGERPRINT.startsWith("generic")
|
|
460
|
+
|| Build.FINGERPRINT.startsWith("unknown")
|
|
461
|
+
|| Build.MODEL.contains("google_sdk")
|
|
462
|
+
|| Build.MODEL.contains("Emulator")
|
|
463
|
+
|| Build.MODEL.contains("Android SDK built for x86")
|
|
464
|
+
|| Build.BOARD == "QC_Reference_Phone"
|
|
465
|
+
|| Build.MANUFACTURER.contains("Genymotion")
|
|
466
|
+
|| Build.HOST.startsWith("Build")
|
|
467
|
+
|| Build.BRAND.startsWith("generic")
|
|
468
|
+
|| Build.DEVICE.startsWith("generic")
|
|
469
|
+
|| Build.PRODUCT == "google_sdk"
|
|
470
|
+
|| Build.PRODUCT.startsWith("sdk")
|
|
471
|
+
|| Build.PRODUCT.endsWith("_cf")
|
|
472
|
+
|| Build.HARDWARE.contains("goldfish")
|
|
473
|
+
|| Build.HARDWARE.contains("ranchu"))
|
|
474
|
+
}
|
|
475
|
+
}
|
|
458
476
|
}
|
|
@@ -461,8 +461,12 @@ JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void * /*reserved*/) {
|
|
|
461
461
|
auto fn = (av_jni_set_java_vm_fn)dlsym(avcodec, "av_jni_set_java_vm");
|
|
462
462
|
if (fn) {
|
|
463
463
|
fn(vm, nullptr);
|
|
464
|
-
LOGI("av_jni_set_java_vm set");
|
|
464
|
+
LOGI("av_jni_set_java_vm set — MediaCodec hardware decoding available");
|
|
465
|
+
} else {
|
|
466
|
+
LOGE("av_jni_set_java_vm symbol not found in libavcodec.so — MediaCodec hwdec may not work");
|
|
465
467
|
}
|
|
468
|
+
} else {
|
|
469
|
+
LOGE("Failed to dlopen libavcodec.so — MediaCodec hardware decoding unavailable");
|
|
466
470
|
}
|
|
467
471
|
|
|
468
472
|
jclass cls = env->FindClass("expo/modules/mpv/MPVLib");
|