feat(player): batch relative seek requests

This commit is contained in:
2026-05-01 20:06:21 +02:00
parent d815812e79
commit bfc007f24f
2 changed files with 58 additions and 1 deletions

View File

@@ -49,6 +49,9 @@ class PlayerViewModel @Inject constructor(
val controlsVisible: StateFlow<Boolean> = _controlsVisible.asStateFlow() val controlsVisible: StateFlow<Boolean> = _controlsVisible.asStateFlow()
private val controlsAutoHidePolicy = ControlsAutoHidePolicy(DEFAULT_CONTROLS_AUTO_HIDE_MS) private val controlsAutoHidePolicy = ControlsAutoHidePolicy(DEFAULT_CONTROLS_AUTO_HIDE_MS)
private val seekByCollector = SeekByCollector(viewModelScope) { deltaMs ->
playerManager.seekBy(deltaMs)
}
private var autoHideJob: Job? = null private var autoHideJob: Job? = null
private var dataErrorMessage: String? = null private var dataErrorMessage: String? = null
@@ -191,18 +194,21 @@ class PlayerViewModel @Inject constructor(
} }
fun seekTo(positionMs: Long) { fun seekTo(positionMs: Long) {
seekByCollector.clear()
playerManager.seekTo(positionMs) playerManager.seekTo(positionMs)
} }
fun seekBy(deltaMs: Long) { fun seekBy(deltaMs: Long) {
playerManager.seekBy(deltaMs) seekByCollector.seekBySoon(deltaMs)
} }
fun seekToLiveEdge() { fun seekToLiveEdge() {
seekByCollector.clear()
playerManager.seekToLiveEdge() playerManager.seekToLiveEdge()
} }
fun skipActiveSegment() { fun skipActiveSegment() {
seekByCollector.clear()
playerManager.skipActiveSegment() playerManager.skipActiveSegment()
} }
@@ -240,11 +246,13 @@ class PlayerViewModel @Inject constructor(
} }
fun next(autoHideDelayMs: Long = DEFAULT_CONTROLS_AUTO_HIDE_MS) { fun next(autoHideDelayMs: Long = DEFAULT_CONTROLS_AUTO_HIDE_MS) {
seekByCollector.clear()
playerManager.next() playerManager.next()
showControls(autoHideDelayMs) showControls(autoHideDelayMs)
} }
fun previous(autoHideDelayMs: Long = DEFAULT_CONTROLS_AUTO_HIDE_MS) { fun previous(autoHideDelayMs: Long = DEFAULT_CONTROLS_AUTO_HIDE_MS) {
seekByCollector.clear()
playerManager.previous() playerManager.previous()
showControls(autoHideDelayMs) showControls(autoHideDelayMs)
} }
@@ -258,6 +266,7 @@ class PlayerViewModel @Inject constructor(
} }
fun playQueueItem(id: String) { fun playQueueItem(id: String) {
seekByCollector.clear()
playerManager.play(id.toUuidOrNull() ?: return) playerManager.play(id.toUuidOrNull() ?: return)
showControls() showControls()
} }

View File

@@ -0,0 +1,48 @@
package hu.bbara.purefin.player.viewmodel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class SeekByCollector(
private val scope: CoroutineScope,
private val intervalMs: Long = 120,
private val seekBy: (Long) -> Unit,
) {
private var pendingSeekMs = 0L
private var flushJob: Job? = null
fun seekBySoon(deltaMs: Long) {
pendingSeekMs += deltaMs
if (flushJob?.isActive != true) {
scheduleFlush()
}
}
fun clear() {
pendingSeekMs = 0
flushJob?.cancel()
flushJob = null
}
private fun scheduleFlush() {
flushJob = scope.launch {
delay(intervalMs)
flush()
}
}
private fun flush() {
val deltaMs = pendingSeekMs
pendingSeekMs = 0
if (deltaMs != 0L) {
seekBy(deltaMs)
}
if (pendingSeekMs != 0L) {
scheduleFlush()
} else {
flushJob = null
}
}
}