Skip to content

Commit

Permalink
Update ytmkt, several headless player fixes
Browse files Browse the repository at this point in the history
Update ytmkt to 0.2.1
Handle mpv client seek buffering
Account for time delay in mpv client seek action
Check canPlay in headless player
  • Loading branch information
toasterofbread committed May 19, 2024
1 parent 1ca634d commit 6dde651
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 36 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ kotlin.version=2.0.0-RC1
okio.version=3.6.0
clikt.version=4.4.0
mediasession.version=0.0.2
ytm.version=0.1.2
ytm.version=0.2.1
20 changes: 10 additions & 10 deletions src/commonMain/kotlin/cinterop/mpv/LibMpvClient.kt.disabled
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ abstract class LibMpvClient(

override fun release() { throw NotImplementedError() }

protected fun runCommand(name: String, vararg args: Any?, check_result: Boolean = true): Int = throw NotImplementedError()
protected inline fun <reified V> getProperty(name: String): V = throw NotImplementedError()
protected inline fun <reified T: Any> setProperty(name: String, value: T) { throw NotImplementedError() }
protected fun observeProperty(name: String, cls: KClass<*>) { throw NotImplementedError() }
protected inline fun <reified T> MemScope.getPointerOf(v: T? = null): CPrimitiveVar = throw NotImplementedError()
protected fun getFormatOf(cls: KClass<*>): MpvFormat = throw NotImplementedError()
protected fun waitForEvent(): mpv_event? = throw NotImplementedError()
protected fun requestLogMessages() { throw NotImplementedError() }
protected fun addHook(name: String, priority: Int = 0) { throw NotImplementedError() }
protected fun continueHook(id: ULong) { throw NotImplementedError() }
internal fun runCommand(name: String, vararg args: Any?, check_result: Boolean = true): Int = throw NotImplementedError()
internal inline fun <reified V> getProperty(name: String): V = throw NotImplementedError()
internal inline fun <reified T: Any> setProperty(name: String, value: T) { throw NotImplementedError() }
internal fun observeProperty(name: String, cls: KClass<*>) { throw NotImplementedError() }
internal inline fun <reified T> MemScope.getPointerOf(v: T? = null): CPrimitiveVar = throw NotImplementedError()
internal fun getFormatOf(cls: KClass<*>): MpvFormat = throw NotImplementedError()
internal fun waitForEvent(): mpv_event? = throw NotImplementedError()
internal fun requestLogMessages() { throw NotImplementedError() }
internal fun addHook(name: String, priority: Int = 0) { throw NotImplementedError() }
internal fun continueHook(id: ULong) { throw NotImplementedError() }
}
26 changes: 16 additions & 10 deletions src/commonMain/kotlin/cinterop/mpv/LibMpvClient.kt.enabled
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ abstract class LibMpvClient(
args.getOrNull(i)?.toString()?.cstr?.getPointer(this)
}.toCValues()

protected fun runCommand(name: String, vararg args: Any?, check_result: Boolean = true): Int =
internal fun runCommand(name: String, vararg args: Any?, check_result: Boolean = true): Int =
memScoped {
val result: Int = mpv_command(ctx, buildArgs(listOf(name).plus(args)))

Expand All @@ -67,7 +67,7 @@ abstract class LibMpvClient(
return result
}

protected inline fun <reified V> getProperty(name: String): V =
internal inline fun <reified V> getProperty(name: String): V =
memScoped {
if (V::class == String::class) {
val string: CPointer<ByteVarOf<Byte>> = mpv_get_property_string(ctx, name)
Expand Down Expand Up @@ -103,7 +103,7 @@ abstract class LibMpvClient(
return extractor()
}

protected inline fun <reified T: Any> setProperty(name: String, value: T) = memScoped {
internal inline fun <reified T: Any> setProperty(name: String, value: T) = memScoped {
if (value is String) {
mpv_set_property_string(ctx, name, value)
return@memScoped
Expand All @@ -114,20 +114,26 @@ abstract class LibMpvClient(
mpv_set_property(ctx, name, format, pointer.ptr)
}

protected fun observeProperty(name: String, cls: KClass<*>) {
internal fun observeProperty(name: String, cls: KClass<*>) {
val format: MpvFormat = getFormatOf(cls)
mpv_observe_property(ctx, 0UL, name, format)

try {
mpv_observe_property(ctx, 0UL, name, format)
}
catch (e: Throwable) {
throw RuntimeException("Call to mpv_observe_property for $name with format $format failed", e)
}
}

protected inline fun <reified T> MemScope.getPointerOf(v: T? = null): CPrimitiveVar =
internal inline fun <reified T> MemScope.getPointerOf(v: T? = null): CPrimitiveVar =
when (T::class) {
Boolean::class -> alloc<BooleanVar>().apply { if (v != null) value = v as Boolean }
Int::class -> alloc<IntVar>().apply { if (v != null) value = v as Int }
Double::class -> alloc<DoubleVar>().apply { if (v != null) value = v as Double }
else -> throw NotImplementedError(T::class.toString())
}

protected fun getFormatOf(cls: KClass<*>): MpvFormat =
internal fun getFormatOf(cls: KClass<*>): MpvFormat =
when (cls) {
Boolean::class -> MPV_FORMAT_FLAG
Int::class -> MPV_FORMAT_INT64
Expand All @@ -138,15 +144,15 @@ abstract class LibMpvClient(
internal fun waitForEvent(): mpv_event? =
mpv_wait_event(ctx, -1.0)?.pointed

protected fun requestLogMessages() {
internal fun requestLogMessages() {
mpv_request_log_messages(ctx, "stats")
}

protected fun addHook(name: String, priority: Int = 0) {
internal fun addHook(name: String, priority: Int = 0) {
mpv_hook_add(ctx, 0UL, name, priority)
}

protected fun continueHook(id: ULong) {
internal fun continueHook(id: ULong) {
mpv_hook_continue(ctx, id)
}
}
26 changes: 23 additions & 3 deletions src/commonMain/kotlin/cinterop/mpv/MpvClientEventLoop.kt.enabled
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import cinterop.utils.safeToKString
import kotlinx.cinterop.*

internal suspend fun MpvClientImpl.eventLoop() = withContext(Dispatchers.IO) {
observeProperty("core-idle", Boolean::class)
observeProperty("seeking", Boolean::class)

var waiting_for_seek_end: Boolean = false

while (true) {
val event: mpv_event? = waitForEvent()

Expand All @@ -32,9 +37,9 @@ internal suspend fun MpvClientImpl.eventLoop() = withContext(Dispatchers.IO) {
MPV_EVENT_FILE_LOADED -> {
onEvent(SpMsPlayerEvent.ReadyToPlay(), clientless = true)

song_initial_seek_position_ms?.also { position_ms ->
seekToTime(position_ms)
song_initial_seek_position_ms = null
song_initial_seek_time?.also { time ->
seekToTime(time.elapsedNow().inWholeMilliseconds)
song_initial_seek_time = null
}
}
MPV_EVENT_SHUTDOWN -> {
Expand All @@ -48,6 +53,21 @@ internal suspend fun MpvClientImpl.eventLoop() = withContext(Dispatchers.IO) {
val playing: Boolean = !data.data.pointedAs<BooleanVar>().value
onEvent(SpMsPlayerEvent.PropertyChanged("is_playing", JsonPrimitive(playing)), clientless = true)
}
"seeking" -> {
if (waiting_for_seek_end && !getProperty<Boolean>("seeking")) {
waiting_for_seek_end = false
onEvent(SpMsPlayerEvent.ReadyToPlay(), clientless = true)
}
}
}
}

MPV_EVENT_SEEK -> {
if (getProperty<Boolean>("seeking")) {
waiting_for_seek_end = true
}
else {
onEvent(SpMsPlayerEvent.ReadyToPlay(), clientless = true)
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/commonMain/kotlin/cinterop/mpv/MpvClientImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import spms.server.SpMs
import spms.socketapi.shared.SpMsPlayerRepeatMode
import spms.socketapi.shared.SpMsPlayerState
import kotlin.math.roundToInt
import kotlin.time.*
import cinterop.utils.safeToKString

private const val URL_PREFIX: String = "spmp://"
Expand All @@ -29,7 +30,7 @@ abstract class MpvClientImpl(headless: Boolean = true, playlist_auto_progress: B
private var auth_headers: Map<String, String>? = null
private val local_files: MutableMap<String, String> = mutableMapOf()

internal var song_initial_seek_position_ms: Long? = null
internal var song_initial_seek_time: TimeMark? = null

private fun urlToId(url: String): String? = if (url.startsWith(URL_PREFIX)) url.drop(URL_PREFIX.length) else null
private fun idToUrl(item_id: String): String = URL_PREFIX + item_id
Expand Down Expand Up @@ -114,7 +115,7 @@ abstract class MpvClientImpl(headless: Boolean = true, playlist_auto_progress: B

override fun seekToItem(index: Int, position_ms: Long) {
if (position_ms > 0) {
song_initial_seek_position_ms = position_ms
song_initial_seek_time = TimeSource.Monotonic.markNow() - with (Duration) { position_ms.milliseconds }
}

val max: Int = item_count - 1
Expand Down Expand Up @@ -259,7 +260,6 @@ abstract class MpvClientImpl(headless: Boolean = true, playlist_auto_progress: B
// requestLogMessages()

addHook("on_load")
observeProperty("core-idle", Boolean::class)

coroutine_scope.launch {
eventLoop()
Expand Down
4 changes: 4 additions & 0 deletions src/commonMain/kotlin/spms/player/headless/HeadlessPlayer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ abstract class HeadlessPlayer(private val enable_logging: Boolean = true): Playe
}

override fun play() {
if (!canPlay()) {
return
}

withLock {
log("play(): Running=$player_running")

Expand Down
20 changes: 11 additions & 9 deletions src/commonMain/kotlin/spms/server/SpMs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,24 +57,26 @@ class SpMs(
return cached
}

override fun canPlay(): Boolean = !playback_waiting_for_clients
override fun canPlay(): Boolean = this@SpMs.canPlay()
override fun onEvent(event: SpMsPlayerEvent, clientless: Boolean) = onPlayerEvent(event, clientless)
override fun onShutdown() = onPlayerShutdown()
}
else
object : MpvClientImpl(headless = !enable_gui) {
override fun canPlay(): Boolean {
if (playback_waiting_for_clients) {
val waiting_for_clients: List<SpMsClient> = clients.filter { it.type.playsAudio() && !it.ready_to_play }
println("Call to canPlay returning false, waiting for the following clients: $waiting_for_clients")
return false
}
return true
}
override fun canPlay(): Boolean = this@SpMs.canPlay()
override fun onEvent(event: SpMsPlayerEvent, clientless: Boolean) = onPlayerEvent(event, clientless)
override fun onShutdown() = onPlayerShutdown()
}

private fun canPlay(): Boolean {
if (playback_waiting_for_clients) {
val waiting_for_clients: List<SpMsClient> = clients.filter { it.type.playsAudio() && !it.ready_to_play }
println("Call to canPlay returning false, waiting for the following clients: $waiting_for_clients")
return false
}
return true
}

private val media_session: SpMsMediaSession? =
try {
if (enable_media_session) SpMsMediaSession.create(player)
Expand Down

0 comments on commit 6dde651

Please sign in to comment.