expo-mpv 0.1.7 → 0.1.8
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/android/src/main/java/expo/modules/mpv/ExpoMpvView.kt +128 -387
- package/android/src/main/java/expo/modules/mpv/MpvPlayer.kt +744 -0
- package/android/src/main/jni/mpv_jni.cpp +311 -106
- package/build/ExpoMpvView.d.ts +7 -1
- package/build/ExpoMpvView.d.ts.map +1 -1
- package/build/ExpoMpvView.js +7 -6
- package/build/ExpoMpvView.js.map +1 -1
- package/package.json +1 -1
- package/src/ExpoMpvView.tsx +39 -30
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
package expo.modules.mpv
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
-
import android.os.Build
|
|
5
|
-
import android.os.Handler
|
|
6
|
-
import android.os.Looper
|
|
7
4
|
import android.view.SurfaceHolder
|
|
8
5
|
import android.view.SurfaceView
|
|
9
6
|
import android.view.View
|
|
@@ -12,9 +9,6 @@ import expo.modules.kotlin.viewevent.EventDispatcher
|
|
|
12
9
|
import expo.modules.kotlin.views.ExpoView
|
|
13
10
|
|
|
14
11
|
class ExpoMpvView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
|
|
15
|
-
|
|
16
|
-
// MARK: - Event Dispatchers
|
|
17
|
-
|
|
18
12
|
private val onPlaybackStateChange by EventDispatcher()
|
|
19
13
|
private val onProgress by EventDispatcher()
|
|
20
14
|
private val onLoad by EventDispatcher()
|
|
@@ -24,453 +18,200 @@ class ExpoMpvView(context: Context, appContext: AppContext) : ExpoView(context,
|
|
|
24
18
|
private val onSeek by EventDispatcher()
|
|
25
19
|
private val onVolumeChange by EventDispatcher()
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
private
|
|
30
|
-
private var isInitialized = false
|
|
31
|
-
private var pendingSource: String? = null
|
|
32
|
-
private var pendingHwdec: String = if (isEmulator()) "no" else "mediacodec"
|
|
33
|
-
private val mainHandler = Handler(Looper.getMainLooper())
|
|
34
|
-
private var progressRunnable: Runnable? = null
|
|
35
|
-
|
|
36
|
-
// Cached property values from observe callbacks (thread-safe reads from main thread)
|
|
37
|
-
@Volatile private var cachedTimePos: Double = 0.0
|
|
38
|
-
@Volatile private var cachedDuration: Double = 0.0
|
|
39
|
-
@Volatile private var cachedCacheDuration: Double = 0.0
|
|
40
|
-
@Volatile private var cachedPause: Boolean = false
|
|
41
|
-
@Volatile private var cachedVolume: Double = 100.0
|
|
42
|
-
@Volatile private var cachedMute: Boolean = false
|
|
43
|
-
@Volatile private var cachedSpeed: Double = 1.0
|
|
44
|
-
@Volatile private var cachedVideoW: Long = 0
|
|
45
|
-
@Volatile private var cachedVideoH: Long = 0
|
|
46
|
-
|
|
47
|
-
// MARK: - Surface
|
|
48
|
-
|
|
49
|
-
private val surfaceView: SurfaceView = SurfaceView(context).apply {
|
|
21
|
+
private var isDestroyed = false
|
|
22
|
+
|
|
23
|
+
private val surfaceView = SurfaceView(context).apply {
|
|
50
24
|
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
51
25
|
}
|
|
52
26
|
|
|
53
|
-
|
|
27
|
+
private val player = MpvPlayer(
|
|
28
|
+
context,
|
|
29
|
+
object : MpvPlayer.Listener {
|
|
30
|
+
override fun onPlaybackStateChange(state: String, isPlaying: Boolean) {
|
|
31
|
+
onPlaybackStateChange(
|
|
32
|
+
mapOf(
|
|
33
|
+
"state" to state,
|
|
34
|
+
"isPlaying" to isPlaying,
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
}
|
|
54
38
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
39
|
+
override fun onProgress(position: Double, duration: Double, bufferedDuration: Double) {
|
|
40
|
+
onProgress(
|
|
41
|
+
mapOf(
|
|
42
|
+
"position" to position,
|
|
43
|
+
"duration" to duration,
|
|
44
|
+
"bufferedDuration" to bufferedDuration,
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
}
|
|
58
48
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
MPVLib.nativeAttachSurface(nativePtr, holder.surface)
|
|
68
|
-
initializeMpv()
|
|
69
|
-
} else {
|
|
70
|
-
// Surface recreated: reattach AFTER mpv_initialize
|
|
71
|
-
MPVLib.nativeReattachSurface(nativePtr, holder.surface)
|
|
72
|
-
}
|
|
73
|
-
pendingSource?.let { src ->
|
|
74
|
-
loadFile(src)
|
|
75
|
-
pendingSource = null
|
|
76
|
-
}
|
|
49
|
+
override fun onLoad(duration: Double, width: Int, height: Int) {
|
|
50
|
+
onLoad(
|
|
51
|
+
mapOf(
|
|
52
|
+
"duration" to duration,
|
|
53
|
+
"width" to width,
|
|
54
|
+
"height" to height,
|
|
55
|
+
)
|
|
56
|
+
)
|
|
77
57
|
}
|
|
78
58
|
|
|
79
|
-
override fun
|
|
59
|
+
override fun onError(message: String) {
|
|
60
|
+
onError(mapOf("error" to message))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
override fun onEnd(reason: String) {
|
|
64
|
+
onEnd(mapOf("reason" to reason))
|
|
65
|
+
}
|
|
80
66
|
|
|
81
|
-
override fun
|
|
82
|
-
|
|
83
|
-
MPVLib.nativeDetachSurface(nativePtr)
|
|
84
|
-
}
|
|
67
|
+
override fun onBuffer(isBuffering: Boolean) {
|
|
68
|
+
onBuffer(mapOf("isBuffering" to isBuffering))
|
|
85
69
|
}
|
|
86
|
-
|
|
70
|
+
|
|
71
|
+
override fun onSeek() {
|
|
72
|
+
onSeek(emptyMap<String, Any>())
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override fun onVolumeChange(volume: Double, muted: Boolean) {
|
|
76
|
+
onVolumeChange(
|
|
77
|
+
mapOf(
|
|
78
|
+
"volume" to volume,
|
|
79
|
+
"muted" to muted,
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
private val surfaceCallback = object : SurfaceHolder.Callback {
|
|
87
|
+
override fun surfaceCreated(holder: SurfaceHolder) {
|
|
88
|
+
player.setSurface(holder.surface)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) = Unit
|
|
92
|
+
|
|
93
|
+
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
|
94
|
+
player.setSurface(null)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
init {
|
|
99
|
+
addView(surfaceView)
|
|
100
|
+
setBackgroundColor(0xFF000000.toInt())
|
|
101
|
+
surfaceView.holder.addCallback(surfaceCallback)
|
|
87
102
|
}
|
|
88
103
|
|
|
89
104
|
override fun onDetachedFromWindow() {
|
|
90
|
-
super.onDetachedFromWindow()
|
|
91
105
|
destroy()
|
|
106
|
+
super.onDetachedFromWindow()
|
|
92
107
|
}
|
|
93
108
|
|
|
94
109
|
override fun onWindowVisibilityChanged(visibility: Int) {
|
|
95
110
|
super.onWindowVisibilityChanged(visibility)
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
setPropertyString("vid", "auto")
|
|
101
|
-
}
|
|
111
|
+
if (visibility == View.GONE || visibility == View.INVISIBLE) {
|
|
112
|
+
player.setPropertyString("vid", "no")
|
|
113
|
+
} else {
|
|
114
|
+
player.setPropertyString("vid", "auto")
|
|
102
115
|
}
|
|
103
116
|
}
|
|
104
117
|
|
|
105
118
|
fun destroy() {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
}
|
|
119
|
+
if (isDestroyed) return
|
|
120
|
+
isDestroyed = true
|
|
121
|
+
surfaceView.holder.removeCallback(surfaceCallback)
|
|
122
|
+
player.setSurface(null)
|
|
123
|
+
player.release()
|
|
117
124
|
}
|
|
118
125
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
private fun createMpv() {
|
|
122
|
-
nativePtr = MPVLib.nativeCreate()
|
|
123
|
-
if (nativePtr == 0L) {
|
|
124
|
-
onError(mapOf("error" to "Failed to create mpv instance"))
|
|
125
|
-
return
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
MPVLib.nativeSetCallback(nativePtr, this)
|
|
129
|
-
|
|
130
|
-
// Rendering
|
|
131
|
-
MPVLib.nativeSetOptionString(nativePtr, "vo", "gpu")
|
|
132
|
-
MPVLib.nativeSetOptionString(nativePtr, "gpu-context", "android")
|
|
133
|
-
MPVLib.nativeSetOptionString(nativePtr, "hwdec", pendingHwdec)
|
|
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")
|
|
147
|
-
|
|
148
|
-
// General
|
|
149
|
-
MPVLib.nativeSetOptionString(nativePtr, "force-window", "yes")
|
|
150
|
-
MPVLib.nativeSetOptionString(nativePtr, "keep-open", "yes")
|
|
151
|
-
MPVLib.nativeSetOptionString(nativePtr, "idle", "yes")
|
|
152
|
-
MPVLib.nativeSetOptionString(nativePtr, "input-default-bindings", "no")
|
|
153
|
-
MPVLib.nativeSetOptionString(nativePtr, "input-vo-keyboard", "no")
|
|
126
|
+
fun loadFile(url: String) {
|
|
127
|
+
player.loadFile(url)
|
|
154
128
|
}
|
|
155
129
|
|
|
156
|
-
|
|
157
|
-
|
|
130
|
+
fun play() {
|
|
131
|
+
player.play()
|
|
132
|
+
}
|
|
158
133
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
return
|
|
163
|
-
}
|
|
134
|
+
fun pause() {
|
|
135
|
+
player.pause()
|
|
136
|
+
}
|
|
164
137
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
// Observe properties
|
|
168
|
-
MPVLib.nativeObserveProperty(nativePtr, "pause", MPVLib.FORMAT_FLAG)
|
|
169
|
-
MPVLib.nativeObserveProperty(nativePtr, "duration", MPVLib.FORMAT_DOUBLE)
|
|
170
|
-
MPVLib.nativeObserveProperty(nativePtr, "time-pos", MPVLib.FORMAT_DOUBLE)
|
|
171
|
-
MPVLib.nativeObserveProperty(nativePtr, "paused-for-cache", MPVLib.FORMAT_FLAG)
|
|
172
|
-
MPVLib.nativeObserveProperty(nativePtr, "eof-reached", MPVLib.FORMAT_FLAG)
|
|
173
|
-
MPVLib.nativeObserveProperty(nativePtr, "volume", MPVLib.FORMAT_DOUBLE)
|
|
174
|
-
MPVLib.nativeObserveProperty(nativePtr, "mute", MPVLib.FORMAT_FLAG)
|
|
175
|
-
MPVLib.nativeObserveProperty(nativePtr, "speed", MPVLib.FORMAT_DOUBLE)
|
|
176
|
-
MPVLib.nativeObserveProperty(nativePtr, "demuxer-cache-duration", MPVLib.FORMAT_DOUBLE)
|
|
177
|
-
MPVLib.nativeObserveProperty(nativePtr, "video-params/w", MPVLib.FORMAT_INT64)
|
|
178
|
-
MPVLib.nativeObserveProperty(nativePtr, "video-params/h", MPVLib.FORMAT_INT64)
|
|
138
|
+
fun togglePlay() {
|
|
139
|
+
player.togglePlay()
|
|
179
140
|
}
|
|
180
141
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
fun onEvent(eventId: Int) {
|
|
184
|
-
when (eventId) {
|
|
185
|
-
MPVLib.EVENT_FILE_LOADED -> mainHandler.post {
|
|
186
|
-
onLoad(mapOf(
|
|
187
|
-
"duration" to (if (cachedDuration.isFinite()) cachedDuration else 0.0),
|
|
188
|
-
"width" to cachedVideoW.toInt(),
|
|
189
|
-
"height" to cachedVideoH.toInt()
|
|
190
|
-
))
|
|
191
|
-
startProgressTimer()
|
|
192
|
-
}
|
|
193
|
-
MPVLib.EVENT_SEEK -> mainHandler.post {
|
|
194
|
-
onSeek(emptyMap<String, Any>())
|
|
195
|
-
}
|
|
196
|
-
MPVLib.EVENT_SHUTDOWN -> {
|
|
197
|
-
isInitialized = false
|
|
198
|
-
}
|
|
199
|
-
}
|
|
142
|
+
fun stop() {
|
|
143
|
+
player.stop()
|
|
200
144
|
}
|
|
201
145
|
|
|
202
|
-
fun
|
|
203
|
-
|
|
204
|
-
"time-pos" -> cachedTimePos = (value as? Double) ?: 0.0
|
|
205
|
-
"duration" -> cachedDuration = (value as? Double) ?: 0.0
|
|
206
|
-
"demuxer-cache-duration" -> cachedCacheDuration = (value as? Double) ?: 0.0
|
|
207
|
-
"pause" -> cachedPause = (value as? Boolean) ?: false
|
|
208
|
-
"volume" -> cachedVolume = (value as? Double) ?: 0.0
|
|
209
|
-
"mute" -> cachedMute = (value as? Boolean) ?: false
|
|
210
|
-
"speed" -> cachedSpeed = (value as? Double) ?: 1.0
|
|
211
|
-
"video-params/w" -> cachedVideoW = (value as? Long) ?: 0
|
|
212
|
-
"video-params/h" -> cachedVideoH = (value as? Long) ?: 0
|
|
213
|
-
}
|
|
214
|
-
mainHandler.post {
|
|
215
|
-
when (name) {
|
|
216
|
-
"pause" -> {
|
|
217
|
-
val paused = (value as? Boolean) ?: false
|
|
218
|
-
onPlaybackStateChange(mapOf(
|
|
219
|
-
"state" to if (paused) "paused" else "playing",
|
|
220
|
-
"isPlaying" to !paused
|
|
221
|
-
))
|
|
222
|
-
if (paused) stopProgressTimer() else startProgressTimer()
|
|
223
|
-
}
|
|
224
|
-
"paused-for-cache" -> {
|
|
225
|
-
val buffering = value as? Boolean ?: false
|
|
226
|
-
onBuffer(mapOf("isBuffering" to buffering))
|
|
227
|
-
}
|
|
228
|
-
"volume", "mute" -> {
|
|
229
|
-
onVolumeChange(mapOf("volume" to cachedVolume, "muted" to cachedMute))
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
146
|
+
fun seekTo(position: Double) {
|
|
147
|
+
player.seekTo(position)
|
|
233
148
|
}
|
|
234
149
|
|
|
235
|
-
fun
|
|
236
|
-
|
|
237
|
-
stopProgressTimer()
|
|
238
|
-
if (reason == "error" && error.isNotEmpty()) {
|
|
239
|
-
onError(mapOf("error" to "Playback error: $error"))
|
|
240
|
-
}
|
|
241
|
-
onEnd(mapOf("reason" to reason))
|
|
242
|
-
}
|
|
150
|
+
fun seekBy(offset: Double) {
|
|
151
|
+
player.seekBy(offset)
|
|
243
152
|
}
|
|
244
153
|
|
|
245
|
-
fun
|
|
246
|
-
|
|
154
|
+
fun setSpeed(speed: Double) {
|
|
155
|
+
player.setSpeed(speed)
|
|
247
156
|
}
|
|
248
157
|
|
|
249
|
-
|
|
158
|
+
fun setVolume(volume: Double) {
|
|
159
|
+
player.setVolume(volume)
|
|
160
|
+
}
|
|
250
161
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
progressRunnable = object : Runnable {
|
|
254
|
-
override fun run() {
|
|
255
|
-
emitProgressEvent()
|
|
256
|
-
mainHandler.postDelayed(this, 250)
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
mainHandler.post(progressRunnable!!)
|
|
162
|
+
fun setMuted(muted: Boolean) {
|
|
163
|
+
player.setMuted(muted)
|
|
260
164
|
}
|
|
261
165
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
progressRunnable = null
|
|
166
|
+
fun setLooping(loop: Boolean) {
|
|
167
|
+
player.setLooping(loop)
|
|
265
168
|
}
|
|
266
169
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
val position = cachedTimePos
|
|
270
|
-
val duration = cachedDuration
|
|
271
|
-
val cachedDur = cachedCacheDuration
|
|
272
|
-
if (!position.isFinite() || !duration.isFinite()) return
|
|
273
|
-
onProgress(mapOf(
|
|
274
|
-
"position" to position,
|
|
275
|
-
"duration" to duration,
|
|
276
|
-
"bufferedDuration" to (if (cachedDur.isFinite()) cachedDur else 0.0)
|
|
277
|
-
))
|
|
170
|
+
fun setHwdec(mode: String) {
|
|
171
|
+
player.setHwdec(mode)
|
|
278
172
|
}
|
|
279
173
|
|
|
280
|
-
|
|
174
|
+
fun setSubtitleTrack(trackId: Int) {
|
|
175
|
+
player.setSubtitleTrack(trackId)
|
|
176
|
+
}
|
|
281
177
|
|
|
282
|
-
fun
|
|
283
|
-
|
|
284
|
-
pendingSource = url
|
|
285
|
-
return
|
|
286
|
-
}
|
|
287
|
-
if (!surfaceView.holder.surface.isValid) {
|
|
288
|
-
pendingSource = url
|
|
289
|
-
return
|
|
290
|
-
}
|
|
291
|
-
command("loadfile", url, "replace")
|
|
178
|
+
fun setAudioTrack(trackId: Int) {
|
|
179
|
+
player.setAudioTrack(trackId)
|
|
292
180
|
}
|
|
293
181
|
|
|
294
|
-
fun
|
|
295
|
-
|
|
296
|
-
fun togglePlay() {
|
|
297
|
-
val isPaused = getPropertyBoolean("pause")
|
|
298
|
-
setPropertyBoolean("pause", !isPaused)
|
|
182
|
+
fun addSubtitle(path: String, flag: String, title: String?, lang: String?) {
|
|
183
|
+
player.addSubtitle(path, flag, title, lang)
|
|
299
184
|
}
|
|
300
|
-
fun stop() { command("stop"); stopProgressTimer() }
|
|
301
|
-
fun seekTo(position: Double) { command("seek", position.toString(), "absolute") }
|
|
302
|
-
fun seekBy(offset: Double) { command("seek", offset.toString(), "relative") }
|
|
303
|
-
fun setSpeed(speed: Double) { setPropertyDouble("speed", speed) }
|
|
304
|
-
fun setVolume(volume: Double) { setPropertyDouble("volume", volume) }
|
|
305
|
-
fun setMuted(muted: Boolean) { setPropertyBoolean("mute", muted) }
|
|
306
|
-
fun setLooping(loop: Boolean) { setPropertyString("loop-file", if (loop) "inf" else "no") }
|
|
307
185
|
|
|
308
|
-
fun
|
|
309
|
-
|
|
310
|
-
// Runtime change — set as property
|
|
311
|
-
setPropertyString("hwdec", mode)
|
|
312
|
-
}
|
|
313
|
-
// Also store for next createMpv if view is recreated
|
|
314
|
-
pendingHwdec = mode
|
|
186
|
+
fun removeSubtitle(trackId: Int) {
|
|
187
|
+
player.removeSubtitle(trackId)
|
|
315
188
|
}
|
|
316
|
-
fun setSubtitleTrack(trackId: Int) { setPropertyLong("sid", trackId.toLong()) }
|
|
317
|
-
fun setAudioTrack(trackId: Int) { setPropertyLong("aid", trackId.toLong()) }
|
|
318
189
|
|
|
319
|
-
fun
|
|
320
|
-
|
|
321
|
-
if (title != null) args.add(title) else if (lang != null) args.add("")
|
|
322
|
-
if (lang != null) args.add(lang)
|
|
323
|
-
commandArray("sub-add", args)
|
|
190
|
+
fun reloadSubtitles() {
|
|
191
|
+
player.reloadSubtitles()
|
|
324
192
|
}
|
|
325
193
|
|
|
326
|
-
fun
|
|
327
|
-
|
|
328
|
-
|
|
194
|
+
fun setSubtitleDelay(seconds: Double) {
|
|
195
|
+
player.setSubtitleDelay(seconds)
|
|
196
|
+
}
|
|
329
197
|
|
|
330
198
|
fun setPropertyString(name: String, value: String) {
|
|
331
|
-
|
|
332
|
-
MPVLib.nativeSetPropertyString(nativePtr, name, value)
|
|
199
|
+
player.setPropertyString(name, value)
|
|
333
200
|
}
|
|
334
201
|
|
|
335
202
|
fun getPlaybackInfo(): Map<String, Any> {
|
|
336
|
-
return
|
|
337
|
-
"position" to (if (cachedTimePos.isFinite()) cachedTimePos else 0.0),
|
|
338
|
-
"duration" to (if (cachedDuration.isFinite()) cachedDuration else 0.0),
|
|
339
|
-
"isPlaying" to !cachedPause,
|
|
340
|
-
"speed" to cachedSpeed,
|
|
341
|
-
"volume" to cachedVolume,
|
|
342
|
-
"muted" to cachedMute
|
|
343
|
-
)
|
|
203
|
+
return player.getPlaybackInfo()
|
|
344
204
|
}
|
|
345
205
|
|
|
346
206
|
fun getTrackList(): List<Map<String, Any>> {
|
|
347
|
-
|
|
348
|
-
val count = getPropertyLong("track-list/count").toInt()
|
|
349
|
-
val tracks = mutableListOf<Map<String, Any>>()
|
|
350
|
-
for (i in 0 until count) {
|
|
351
|
-
val prefix = "track-list/$i"
|
|
352
|
-
val type = getPropertyString("$prefix/type") ?: "unknown"
|
|
353
|
-
val track = mutableMapOf<String, Any>(
|
|
354
|
-
"id" to getPropertyLong("$prefix/id").toInt(),
|
|
355
|
-
"type" to type,
|
|
356
|
-
"title" to (getPropertyString("$prefix/title") ?: ""),
|
|
357
|
-
"lang" to (getPropertyString("$prefix/lang") ?: ""),
|
|
358
|
-
"codec" to (getPropertyString("$prefix/codec") ?: ""),
|
|
359
|
-
"selected" to getPropertyBoolean("$prefix/selected"),
|
|
360
|
-
"isDefault" to getPropertyBoolean("$prefix/default"),
|
|
361
|
-
"isExternal" to getPropertyBoolean("$prefix/external")
|
|
362
|
-
)
|
|
363
|
-
when (type) {
|
|
364
|
-
"audio" -> {
|
|
365
|
-
track["channelCount"] = getPropertyLong("$prefix/demux-channel-count").toInt()
|
|
366
|
-
track["sampleRate"] = getPropertyLong("$prefix/demux-samplerate").toInt()
|
|
367
|
-
}
|
|
368
|
-
"video" -> {
|
|
369
|
-
track["width"] = getPropertyLong("$prefix/demux-w").toInt()
|
|
370
|
-
track["height"] = getPropertyLong("$prefix/demux-h").toInt()
|
|
371
|
-
track["fps"] = getPropertyDouble("$prefix/demux-fps")
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
tracks.add(track)
|
|
375
|
-
}
|
|
376
|
-
return tracks
|
|
207
|
+
return player.getTrackList()
|
|
377
208
|
}
|
|
378
209
|
|
|
379
210
|
fun getCurrentTrackIds(): Map<String, Int> {
|
|
380
|
-
|
|
381
|
-
return mapOf(
|
|
382
|
-
"vid" to getPropertyLong("vid").toInt(),
|
|
383
|
-
"aid" to getPropertyLong("aid").toInt(),
|
|
384
|
-
"sid" to getPropertyLong("sid").toInt()
|
|
385
|
-
)
|
|
211
|
+
return player.getCurrentTrackIds()
|
|
386
212
|
}
|
|
387
213
|
|
|
388
214
|
fun getMediaInfo(): Map<String, Any> {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
val hwdec = getPropertyString("hwdec") ?: ""
|
|
392
|
-
val hwdecCurrent = getPropertyString("hwdec-current") ?: ""
|
|
393
|
-
val videoCodec = getPropertyString("video-codec") ?: ""
|
|
394
|
-
val audioCodec = getPropertyString("audio-codec-name") ?: ""
|
|
395
|
-
val width = getPropertyLong("video-params/w").toInt()
|
|
396
|
-
val height = getPropertyLong("video-params/h").toInt()
|
|
397
|
-
val fps = getPropertyDouble("container-fps")
|
|
398
|
-
val videoBitrate = getPropertyDouble("video-bitrate")
|
|
399
|
-
val audioBitrate = getPropertyDouble("audio-bitrate")
|
|
400
|
-
val pixelFormat = getPropertyString("video-params/pixelformat") ?: ""
|
|
401
|
-
val colorspace = getPropertyString("video-params/colormatrix") ?: ""
|
|
402
|
-
|
|
403
|
-
return mapOf(
|
|
404
|
-
"hwdec" to hwdec,
|
|
405
|
-
"hwdecCurrent" to hwdecCurrent,
|
|
406
|
-
"videoCodec" to videoCodec,
|
|
407
|
-
"audioCodec" to audioCodec,
|
|
408
|
-
"width" to width,
|
|
409
|
-
"height" to height,
|
|
410
|
-
"fps" to (if (fps.isFinite()) fps else 0.0),
|
|
411
|
-
"videoBitrate" to (if (videoBitrate.isFinite()) videoBitrate else 0.0),
|
|
412
|
-
"audioBitrate" to (if (audioBitrate.isFinite()) audioBitrate else 0.0),
|
|
413
|
-
"pixelFormat" to pixelFormat,
|
|
414
|
-
"colorspace" to colorspace
|
|
415
|
-
)
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// MARK: - Property Helpers
|
|
419
|
-
|
|
420
|
-
private fun getPropertyDouble(name: String): Double {
|
|
421
|
-
if (nativePtr == 0L) return 0.0
|
|
422
|
-
return MPVLib.nativeGetPropertyDouble(nativePtr, name)
|
|
423
|
-
}
|
|
424
|
-
private fun getPropertyLong(name: String): Long {
|
|
425
|
-
if (nativePtr == 0L) return 0
|
|
426
|
-
return MPVLib.nativeGetPropertyLong(nativePtr, name)
|
|
427
|
-
}
|
|
428
|
-
private fun getPropertyBoolean(name: String): Boolean {
|
|
429
|
-
if (nativePtr == 0L) return false
|
|
430
|
-
return MPVLib.nativeGetPropertyBoolean(nativePtr, name)
|
|
431
|
-
}
|
|
432
|
-
private fun getPropertyString(name: String): String? {
|
|
433
|
-
if (nativePtr == 0L) return null
|
|
434
|
-
return MPVLib.nativeGetPropertyString(nativePtr, name)
|
|
435
|
-
}
|
|
436
|
-
private fun setPropertyDouble(name: String, value: Double) {
|
|
437
|
-
if (nativePtr == 0L) return
|
|
438
|
-
MPVLib.nativeSetPropertyDouble(nativePtr, name, value)
|
|
439
|
-
}
|
|
440
|
-
private fun setPropertyLong(name: String, value: Long) {
|
|
441
|
-
if (nativePtr == 0L) return
|
|
442
|
-
MPVLib.nativeSetPropertyLong(nativePtr, name, value)
|
|
443
|
-
}
|
|
444
|
-
private fun setPropertyBoolean(name: String, value: Boolean) {
|
|
445
|
-
if (nativePtr == 0L) return
|
|
446
|
-
MPVLib.nativeSetPropertyBoolean(nativePtr, name, value)
|
|
447
|
-
}
|
|
448
|
-
private fun command(vararg args: String) {
|
|
449
|
-
if (nativePtr == 0L) return
|
|
450
|
-
MPVLib.nativeCommand(nativePtr, args.toList().toTypedArray())
|
|
451
|
-
}
|
|
452
|
-
private fun commandArray(cmd: String, args: List<String>) {
|
|
453
|
-
if (nativePtr == 0L) return
|
|
454
|
-
MPVLib.nativeCommand(nativePtr, (listOf(cmd) + args).toTypedArray())
|
|
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
|
-
}
|
|
215
|
+
return player.getMediaInfo()
|
|
475
216
|
}
|
|
476
217
|
}
|