Skip to content

Commit 30c2a0f

Browse files
neobuddy89joeyhuab
authored andcommitted
SystemUI: Smoother seekbar for ongoing chip popup ui
Bug Fixes: 1. Seekbar can't be used when paused. 2. The controller polls position every MEDIA_UPDATE_INTERVAL_MS = 1000ms, so progressFraction only changes once per second, causing the bar to jump in 1-second steps creating visible jitter when moved manually. Signed-off-by: Pranav Vashi <neobuddy89@gmail.com>
1 parent f950e17 commit 30c2a0f

2 files changed

Lines changed: 38 additions & 8 deletions

File tree

packages/SystemUI/src/com/android/systemui/statusbar/OnGoingActionProgressController.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,10 @@ class OnGoingActionProgressController(
677677
fun onSeek(fraction: Float) {
678678
val duration = mediaSessionHelper.getTotalDuration()
679679
if (duration <= 0) return
680-
mediaSessionHelper.seekTo((fraction * duration).toLong().coerceIn(0L, duration))
680+
val seekPos = (fraction * duration).toLong().coerceIn(0L, duration)
681+
mediaSessionHelper.seekTo(seekPos)
682+
currentProgress = seekPos.toInt()
683+
updateProgressState()
681684
collapseExpandViewWithDelay()
682685
collapseMediaControlsWithDelay()
683686
}

packages/SystemUI/src/com/android/systemui/statusbar/OngoingActionProgressCompose.kt

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import androidx.compose.runtime.key
5454
import androidx.compose.runtime.mutableStateOf
5555
import androidx.compose.runtime.remember
5656
import androidx.compose.runtime.rememberCoroutineScope
57+
import androidx.compose.runtime.rememberUpdatedState
5758
import androidx.compose.runtime.setValue
5859
import androidx.compose.ui.Alignment
5960
import androidx.compose.ui.Modifier
@@ -104,6 +105,7 @@ import kotlinx.coroutines.CoroutineScope
104105
import kotlinx.coroutines.Dispatchers
105106
import kotlinx.coroutines.SupervisorJob
106107
import kotlinx.coroutines.cancel
108+
import kotlinx.coroutines.delay
107109
import kotlinx.coroutines.flow.MutableStateFlow
108110
import kotlinx.coroutines.flow.StateFlow
109111
import kotlinx.coroutines.launch
@@ -472,7 +474,8 @@ private fun MiniMediaPlayer(
472474
}
473475

474476
SeekBarCompose(
475-
progressFraction = progressFraction(state),
477+
progressMs = progressMs,
478+
durationMs = durationMs,
476479
isPlaying = state.isMediaPlaying,
477480
onSeek = onSeek,
478481
modifier = Modifier.fillMaxWidth().height(28.dp)
@@ -571,13 +574,38 @@ private fun ControlButton(
571574

572575
@Composable
573576
private fun SeekBarCompose(
574-
progressFraction: Float,
577+
progressMs: Long,
578+
durationMs: Long,
575579
isPlaying: Boolean,
576580
onSeek: (Float) -> Unit,
577581
modifier: Modifier = Modifier,
578582
) {
579583
var isScrubbing by remember { mutableStateOf(false) }
580584

585+
val onSeekRef = rememberUpdatedState(onSeek)
586+
587+
val serverFraction = if (durationMs > 0) (progressMs.toFloat() / durationMs).coerceIn(0f, 1f) else 0f
588+
var displayFraction by remember { mutableStateOf(serverFraction) }
589+
590+
LaunchedEffect(progressMs, durationMs, isPlaying) {
591+
if (isScrubbing) return@LaunchedEffect
592+
593+
displayFraction = serverFraction
594+
595+
if (!isPlaying || durationMs <= 0) return@LaunchedEffect
596+
597+
val startWallMs = System.currentTimeMillis()
598+
val startProgressMs = progressMs
599+
while (true) {
600+
delay(16L) // ~60 fps
601+
if (isScrubbing) break
602+
val elapsed = System.currentTimeMillis() - startWallMs
603+
val interpolated = ((startProgressMs + elapsed).toFloat() / durationMs).coerceIn(0f, 1f)
604+
displayFraction = interpolated
605+
if (interpolated >= 1f) break
606+
}
607+
}
608+
581609
AndroidView(
582610
factory = { ctx ->
583611
SeekBar(ctx).apply {
@@ -632,26 +660,25 @@ private fun SeekBarCompose(
632660

633661
setOnSeekBarChangeListener(
634662
object : SeekBar.OnSeekBarChangeListener {
635-
override fun onProgressChanged(sb: SeekBar?, v: Int, fromUser: Boolean) {
636-
if (fromUser) onSeek(v / 10_000f)
637-
}
663+
override fun onProgressChanged(sb: SeekBar?, v: Int, fromUser: Boolean) = Unit
638664

639665
override fun onStartTrackingTouch(sb: SeekBar?) {
640666
isScrubbing = true
641667
}
642668

643669
override fun onStopTrackingTouch(sb: SeekBar?) {
670+
sb?.let { onSeekRef.value(it.progress / 10_000f) }
644671
isScrubbing = false
645672
}
646673
}
647674
)
648675
}
649676
},
650677
update = { bar ->
651-
val target = (progressFraction * 10_000f).toInt().coerceIn(0, 10_000)
678+
val target = (displayFraction * 10_000f).toInt().coerceIn(0, 10_000)
652679

653680
if (!isScrubbing) {
654-
// Keep QS reset behavior if you want it
681+
// Keep QS reset behaviour for track-restart detection.
655682
if (
656683
target <= SeekBarObserver.RESET_ANIMATION_THRESHOLD_MS &&
657684
bar.progress > SeekBarObserver.RESET_ANIMATION_THRESHOLD_MS

0 commit comments

Comments
 (0)