Skip to content
This repository was archived by the owner on Mar 30, 2026. It is now read-only.

Commit c135313

Browse files
refactor: make use of mpv_node and new flows in mpv-lib (#271)
1 parent 9535aa7 commit c135313

34 files changed

Lines changed: 555 additions & 731 deletions

app/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ android {
100100
}
101101
}
102102

103+
kotlin {
104+
compilerOptions {
105+
freeCompilerArgs.addAll("-Xwhen-guards", "-Xcontext-parameters")
106+
}
107+
}
108+
103109
room {
104110
schemaDirectory("$projectDir/schemas")
105111
}

app/src/main/java/live/mehiz/mpvkt/App.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package live.mehiz.mpvkt
22

33
import android.app.Application
4+
import live.mehiz.mpvkt.di.AppModule
45
import live.mehiz.mpvkt.di.DatabaseModule
56
import live.mehiz.mpvkt.di.FileManagerModule
67
import live.mehiz.mpvkt.di.PreferencesModule
@@ -22,6 +23,7 @@ class App : Application(), KoinStartup {
2223
override fun onKoinStartup() = koinConfiguration {
2324
androidContext(this@App)
2425
modules(
26+
AppModule,
2527
PreferencesModule,
2628
DatabaseModule,
2729
FileManagerModule,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package live.mehiz.mpvkt.di
2+
3+
import kotlinx.serialization.json.Json
4+
import org.koin.dsl.module
5+
6+
// generic dependencies for the app's needs
7+
val AppModule = module {
8+
single {
9+
Json {
10+
isLenient = true
11+
ignoreUnknownKeys = true
12+
}
13+
}
14+
}

app/src/main/java/live/mehiz/mpvkt/ui/home/HomeScreen.kt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Column
1010
import androidx.compose.foundation.layout.Row
1111
import androidx.compose.foundation.layout.fillMaxSize
1212
import androidx.compose.foundation.layout.padding
13+
import androidx.compose.foundation.text.input.rememberTextFieldState
1314
import androidx.compose.material.icons.Icons
1415
import androidx.compose.material.icons.filled.FileOpen
1516
import androidx.compose.material.icons.filled.FolderOpen
@@ -27,6 +28,7 @@ import androidx.compose.material3.Scaffold
2728
import androidx.compose.material3.Text
2829
import androidx.compose.material3.TopAppBar
2930
import androidx.compose.runtime.Composable
31+
import androidx.compose.runtime.LaunchedEffect
3032
import androidx.compose.runtime.getValue
3133
import androidx.compose.runtime.mutableStateOf
3234
import androidx.compose.runtime.remember
@@ -79,27 +81,25 @@ object HomeScreen : Screen {
7981
horizontalAlignment = Alignment.CenterHorizontally,
8082
verticalArrangement = Arrangement.Center,
8183
) {
82-
var uri by remember { mutableStateOf("") }
84+
val uri = rememberTextFieldState()
8385
var isUrlValid by remember { mutableStateOf(true) }
86+
LaunchedEffect(uri.text) {
87+
isUrlValid = uri.text.isNotEmpty() || isURLValid(uri.text.toString())
88+
}
8489
OutlinedTextField(
85-
value = uri,
90+
state = uri,
8691
label = { Text(stringResource(R.string.home_url_input_label)) },
87-
onValueChange = {
88-
uri = it
89-
isUrlValid = it.isBlank() || isURLValid(it)
90-
},
9192
supportingText = {
9293
Text(if (isUrlValid) "" else stringResource(R.string.home_invalid_protocol))
9394
},
9495
trailingIcon = {
9596
if (!isUrlValid) Icon(Icons.Filled.Info, null)
9697
},
97-
isError = !isUrlValid,
98-
maxLines = 5,
98+
isError = !isUrlValid
9999
)
100100
Button(
101-
onClick = { playFile(uri, context) },
102-
enabled = uri.isNotBlank() && isUrlValid,
101+
onClick = { playFile(uri.text.toString(), context) },
102+
enabled = uri.text.isNotBlank() && isUrlValid,
103103
) {
104104
Row(
105105
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.smaller),

app/src/main/java/live/mehiz/mpvkt/ui/player/MPVView.kt

Lines changed: 7 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -30,37 +30,6 @@ class MPVView(context: Context, attributes: AttributeSet) : BaseMPVView(context,
3030

3131
var isExiting = false
3232

33-
val duration: Int?
34-
get() = MPVLib.getPropertyInt("duration")
35-
36-
var timePos: Int?
37-
get() = MPVLib.getPropertyInt("time-pos")
38-
set(position) = MPVLib.setPropertyInt("time-pos", position!!)
39-
40-
var paused: Boolean?
41-
get() = MPVLib.getPropertyBoolean("pause")
42-
set(paused) = MPVLib.setPropertyBoolean("pause", paused!!)
43-
44-
val hwdecActive: String
45-
get() = MPVLib.getPropertyString("hwdec-current") ?: "no"
46-
47-
var playbackSpeed: Double?
48-
get() = MPVLib.getPropertyDouble("speed")
49-
set(speed) = MPVLib.setPropertyDouble("speed", speed!!)
50-
51-
var subDelay: Double?
52-
get() = MPVLib.getPropertyDouble("sub-delay")
53-
set(delay) = MPVLib.setPropertyDouble("sub-delay", delay!!)
54-
55-
var secondarySubDelay: Double?
56-
get() = MPVLib.getPropertyDouble("secondary-sub-delay")
57-
set(delay) = MPVLib.setPropertyDouble("secondary-sub-delay", delay!!)
58-
59-
val videoH: Int?
60-
get() = MPVLib.getPropertyInt("video-params/h")
61-
val videoAspect: Double?
62-
get() = MPVLib.getPropertyDouble("video-params/aspect")
63-
6433
/**
6534
* Returns the video aspect ratio. Rotation is taken into account.
6635
*/
@@ -78,11 +47,7 @@ class MPVView(context: Context, attributes: AttributeSet) : BaseMPVView(context,
7847
return v?.toIntOrNull() ?: -1
7948
}
8049
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
81-
if (value == -1) {
82-
MPVLib.setPropertyString(name, "no")
83-
} else {
84-
MPVLib.setPropertyInt(name, value)
85-
}
50+
if (value == -1) MPVLib.setPropertyString(name, "no") else MPVLib.setPropertyInt(name, value)
8651
}
8752
}
8853

@@ -134,14 +99,14 @@ class MPVView(context: Context, attributes: AttributeSet) : BaseMPVView(context,
13499
override fun postInitOptions() {
135100
when (decoderPreferences.debanding.get()) {
136101
Debanding.None -> {}
137-
Debanding.CPU -> MPVLib.command(arrayOf("vf", "add", "@deband:gradfun=radius=12"))
102+
Debanding.CPU -> MPVLib.command("vf", "add", "@deband:gradfun=radius=12")
138103
Debanding.GPU -> MPVLib.setOptionString("deband", "yes")
139104
}
140105

141106
advancedPreferences.enabledStatisticsPage.get().let {
142107
if (it != 0) {
143-
MPVLib.command(arrayOf("script-binding", "stats/display-stats-toggle"))
144-
MPVLib.command(arrayOf("script-binding", "stats/display-page-$it"))
108+
MPVLib.command("script-binding", "stats/display-stats-toggle")
109+
MPVLib.command("script-binding", "stats/display-page-$it")
145110
}
146111
}
147112
}
@@ -152,7 +117,7 @@ class MPVView(context: Context, attributes: AttributeSet) : BaseMPVView(context,
152117
return false
153118
}
154119

155-
var mapped = KeyMapping.map.get(event.keyCode)
120+
var mapped = KeyMapping[event.keyCode]
156121
if (mapped == null) {
157122
// Fallback to produced glyph
158123
if (!event.isPrintingKey) {
@@ -181,35 +146,14 @@ class MPVView(context: Context, attributes: AttributeSet) : BaseMPVView(context,
181146

182147
val action = if (event.action == KeyEvent.ACTION_DOWN) "keydown" else "keyup"
183148
mod.add(mapped)
184-
MPVLib.command(arrayOf(action, mod.joinToString("+")))
149+
MPVLib.command(action, mod.joinToString("+"))
185150

186151
return true
187152
}
188153

189154
private val observedProps = mapOf(
190-
"chapter" to MPVLib.mpvFormat.MPV_FORMAT_INT64,
191-
"chapter-list" to MPVLib.mpvFormat.MPV_FORMAT_NONE,
192-
"track-list" to MPVLib.mpvFormat.MPV_FORMAT_NONE,
193-
194-
"time-pos" to MPVLib.mpvFormat.MPV_FORMAT_INT64,
195-
"demuxer-cache-time" to MPVLib.mpvFormat.MPV_FORMAT_INT64,
196-
"duration" to MPVLib.mpvFormat.MPV_FORMAT_INT64,
197-
"volume" to MPVLib.mpvFormat.MPV_FORMAT_INT64,
198-
"volume-max" to MPVLib.mpvFormat.MPV_FORMAT_INT64,
199-
200-
"sid" to MPVLib.mpvFormat.MPV_FORMAT_STRING,
201-
"secondary-sid" to MPVLib.mpvFormat.MPV_FORMAT_STRING,
202-
"aid" to MPVLib.mpvFormat.MPV_FORMAT_STRING,
203-
204-
"speed" to MPVLib.mpvFormat.MPV_FORMAT_DOUBLE,
205-
"video-params/aspect" to MPVLib.mpvFormat.MPV_FORMAT_DOUBLE,
206-
207-
"hwdec-current" to MPVLib.mpvFormat.MPV_FORMAT_STRING,
208-
"hwdec" to MPVLib.mpvFormat.MPV_FORMAT_STRING,
209-
210155
"pause" to MPVLib.mpvFormat.MPV_FORMAT_FLAG,
211-
"paused-for-cache" to MPVLib.mpvFormat.MPV_FORMAT_FLAG,
212-
"seeking" to MPVLib.mpvFormat.MPV_FORMAT_FLAG,
156+
"video-params/aspect" to MPVLib.mpvFormat.MPV_FORMAT_DOUBLE,
213157
"eof-reached" to MPVLib.mpvFormat.MPV_FORMAT_FLAG,
214158

215159
"user-data/mpvkt/show_text" to MPVLib.mpvFormat.MPV_FORMAT_STRING,

app/src/main/java/live/mehiz/mpvkt/ui/player/MediaPlaybackService.kt

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import androidx.core.app.NotificationCompat
2323
import androidx.media.MediaBrowserServiceCompat
2424
import androidx.media.session.MediaButtonReceiver
2525
import `is`.xyz.mpv.MPVLib
26+
import `is`.xyz.mpv.MPVNode
2627
import live.mehiz.mpvkt.R
2728
import live.mehiz.mpvkt.preferences.GesturePreferences
2829
import org.koin.android.ext.android.inject
@@ -45,21 +46,25 @@ class MediaPlaybackService : MediaBrowserServiceCompat(), MPVLib.EventObserver {
4546
private val binder = MediaPlaybackBinder()
4647
private var mediaTitle = ""
4748
private var mediaArtist = ""
48-
private var positionMs: Long
49-
get() = (MPVLib.getPropertyDouble("time-pos") * 1000L).toLong()
50-
set(value) = MPVLib.command(arrayOf("seek", (value / 1000f).toString(), "absolute"))
51-
private val durationMs: Long
52-
get() = (MPVLib.getPropertyDouble("duration") * 1000L).toLong()
53-
private var paused: Boolean
49+
private var positionMs: Long?
50+
get() = MPVLib.getPropertyDouble("time-pos")?.times(1000L)?.toLong()
51+
set(value) = MPVLib.command("seek", (value!! / 1000f).toString(), "absolute")
52+
private val durationMs: Long?
53+
get() = (MPVLib.getPropertyDouble("duration")?.times(1000L))?.toLong()
54+
private var paused: Boolean?
5455
get() = MPVLib.getPropertyBoolean("pause")
55-
set(value) = MPVLib.command(arrayOf("set", "pause", if (value) "yes" else "no"))
56+
set(value) = MPVLib.command("set", "pause", if (value == true) "yes" else "no")
5657

5758
private lateinit var mediaSession: MediaSessionCompat
5859

5960
private lateinit var audioManager: AudioManager
6061
private var audioFocusRequest: AudioFocusRequest? = null
6162
private var audioFocusCallback: AudioManager.OnAudioFocusChangeListener? = null
6263

64+
init {
65+
MPVLib.addObserver(this)
66+
}
67+
6368
@Suppress("EmptyFunctionBlock")
6469
override fun eventProperty(property: String) {
6570
}
@@ -86,6 +91,7 @@ class MediaPlaybackService : MediaBrowserServiceCompat(), MPVLib.EventObserver {
8691
updateMediaSessionMetadata()
8792
updateNotification()
8893
}
94+
8995
"media-title" -> {
9096
mediaTitle = value
9197
updateMediaSessionMetadata()
@@ -98,6 +104,10 @@ class MediaPlaybackService : MediaBrowserServiceCompat(), MPVLib.EventObserver {
98104
override fun eventProperty(property: String, value: Double) {
99105
}
100106

107+
@Suppress("EmptyFunctionBlock")
108+
override fun eventProperty(property: String, value: MPVNode) {
109+
}
110+
101111
@Suppress("EmptyFunctionBlock")
102112
override fun event(eventId: Int) {
103113
}
@@ -111,8 +121,17 @@ class MediaPlaybackService : MediaBrowserServiceCompat(), MPVLib.EventObserver {
111121
override fun onCreate() {
112122
super.onCreate()
113123

114-
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
124+
audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
115125
MPVLib.addObserver(this)
126+
mapOf(
127+
"pause" to MPVLib.mpvFormat.MPV_FORMAT_FLAG,
128+
"duration" to MPVLib.mpvFormat.MPV_FORMAT_DOUBLE,
129+
"time-pos" to MPVLib.mpvFormat.MPV_FORMAT_DOUBLE,
130+
"media-title" to MPVLib.mpvFormat.MPV_FORMAT_STRING,
131+
"metadata/artist" to MPVLib.mpvFormat.MPV_FORMAT_STRING,
132+
).onEach {
133+
MPVLib.observeProperty(it.key, it.value)
134+
}
116135

117136
setupMediaSession()
118137
setupAudioFocus()
@@ -213,7 +232,7 @@ class MediaPlaybackService : MediaBrowserServiceCompat(), MPVLib.EventObserver {
213232
when (focusChange) {
214233
AudioManager.AUDIOFOCUS_LOSS -> pauseMedia()
215234
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> pauseMedia()
216-
AudioManager.AUDIOFOCUS_GAIN -> if (!paused) playMedia()
235+
AudioManager.AUDIOFOCUS_GAIN -> if (paused == false) playMedia()
217236
}
218237
}
219238

@@ -284,19 +303,19 @@ class MediaPlaybackService : MediaBrowserServiceCompat(), MPVLib.EventObserver {
284303
}
285304

286305
private fun seekForward() {
287-
positionMs += gesturePreferences.doubleTapToSeekDuration.get() * 1000L
306+
positionMs = positionMs?.plus(gesturePreferences.doubleTapToSeekDuration.get() * 1000L)
288307
}
289308

290309
private fun seekBackward() {
291-
positionMs -= gesturePreferences.doubleTapToSeekDuration.get() * 1000L
310+
positionMs = positionMs?.minus(gesturePreferences.doubleTapToSeekDuration.get() * 1000L)
292311
}
293312

294313
private fun updateMediaSessionMetadata() {
295314
try {
296315
val metadataBuilder = MediaMetadataCompat.Builder()
297316
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, mediaTitle)
298317
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, mediaArtist)
299-
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, durationMs)
318+
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, durationMs ?: 0L)
300319
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, mediaTitle)
301320
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mediaTitle.hashCode().toString())
302321

@@ -316,7 +335,15 @@ class MediaPlaybackService : MediaBrowserServiceCompat(), MPVLib.EventObserver {
316335
try {
317336
val stateBuilder = PlaybackStateCompat.Builder()
318337
.setActions(getAvailableActions())
319-
.setState(if (paused) PlaybackStateCompat.STATE_PAUSED else PlaybackStateCompat.STATE_PLAYING, positionMs, 1.0f)
338+
.setState(
339+
if (paused == true) {
340+
PlaybackStateCompat.STATE_PAUSED
341+
} else {
342+
PlaybackStateCompat.STATE_PLAYING
343+
},
344+
positionMs ?: 0,
345+
1.0f,
346+
)
320347

321348
mediaSession.setPlaybackState(stateBuilder.build())
322349
} catch (e: Exception) {
@@ -374,12 +401,12 @@ class MediaPlaybackService : MediaBrowserServiceCompat(), MPVLib.EventObserver {
374401
.setContentIntent(pendingOpenAppIntent)
375402
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
376403
.setOnlyAlertOnce(true)
377-
.setOngoing(!paused)
404+
.setOngoing(paused == false)
378405
.addAction(R.drawable.baseline_fast_rewind_24, getString(R.string.notification_rewind), pendingSkipBackwardIntent)
379406
.addAction(
380-
if (!paused) R.drawable.baseline_pause_24 else R.drawable.baseline_play_arrow_24,
381-
if (!paused) getString(R.string.notification_pause) else getString(R.string.notification_play),
382-
if (!paused) pendingPauseIntent else pendingPlayIntent,
407+
if (paused == false) R.drawable.baseline_pause_24 else R.drawable.baseline_play_arrow_24,
408+
if (paused == false) getString(R.string.notification_pause) else getString(R.string.notification_play),
409+
if (paused == false) pendingPauseIntent else pendingPlayIntent,
383410
)
384411
.addAction(
385412
R.drawable.baseline_fast_forward_24,

0 commit comments

Comments
 (0)