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.Bundle
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
- // MARK: - Lifecycle
89
+ override fun onDetachedFromWindow() {
90
+ super.onDetachedFromWindow()
91
+ destroy()
92
+ }
93
93
 
94
- private val lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks {
95
- override fun onActivityPaused(activity: Activity) {
96
- if (nativePtr != 0L && isInitialized && activity == appContext.currentActivity) {
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 app = context.applicationContext as? Application
125
- app?.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
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
- MPVLib.nativeSetOptionString(nativePtr, "hwdec-codecs", "h264,hevc,mpeg4,mpeg2video,vp8,vp9,av1")
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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-mpv",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Expo module wrapping libmpv for video playback on iOS and Android",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",