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