@@ -91,6 +91,7 @@ import androidx.compose.runtime.getValue
9191import androidx.compose.runtime.livedata.observeAsState
9292import androidx.compose.runtime.mutableFloatStateOf
9393import androidx.compose.runtime.mutableIntStateOf
94+ import androidx.compose.runtime.mutableLongStateOf
9495import androidx.compose.runtime.mutableStateOf
9596import androidx.compose.runtime.remember
9697import androidx.compose.runtime.setValue
@@ -129,6 +130,7 @@ import android.os.Looper
129130import android.util.Log
130131import com.google.oboe.samples.powerplay.automation.IntentBasedTestSupport
131132import com.google.oboe.samples.powerplay.automation.IntentBasedTestSupport.LOG_TAG
133+ import kotlinx.coroutines.delay
132134
133135class MainActivity : ComponentActivity () {
134136
@@ -402,11 +404,33 @@ class MainActivity : ComponentActivity() {
402404
403405 var showInfoDialog by remember { mutableStateOf(false ) }
404406
407+ // Real-time progress slider state
408+ var assetsReady by remember { mutableStateOf(false ) }
409+ var playbackPosition by remember { mutableLongStateOf(0L ) }
410+ var isSeeking by remember { mutableStateOf(false ) }
411+ val duration = remember(playingSongIndex.intValue, assetsReady) { player.getDurationMillis(playingSongIndex.intValue) }
412+
413+ // Polling loop for slider position (~60fps)
414+ LaunchedEffect (isPlaying, offload.intValue) {
415+ if (isPlaying && offload.intValue != 3 ) {
416+ while (true ) {
417+ if (! isSeeking) {
418+ playbackPosition = player.getPlaybackPositionMillis()
419+ }
420+ delay(16 )
421+ }
422+ } else {
423+ playbackPosition = player.getPlaybackPositionMillis()
424+ }
425+ }
426+
405427 // Sync pager with song index when automation changes it
406428 LaunchedEffect (playingSongIndex.intValue) {
407429 if (pagerState.currentPage != playingSongIndex.intValue) {
408430 pagerState.animateScrollToPage(playingSongIndex.intValue)
409431 }
432+ // Update playback position when song changes
433+ playbackPosition = player.getPlaybackPositionMillis()
410434 }
411435
412436 LaunchedEffect (pagerState) {
@@ -434,6 +458,7 @@ class MainActivity : ComponentActivity() {
434458 }
435459 // Assets are now loaded, process any pending automation intent
436460 assetsLoaded = true
461+ assetsReady = true
437462 pendingAutomationIntent?.let {
438463 processIntent(it)
439464 pendingAutomationIntent = null
@@ -513,7 +538,51 @@ class MainActivity : ComponentActivity() {
513538 VinylAlbumCoverAnimation (isSongPlaying = false , painter = painter)
514539 }
515540 }
541+
516542 Spacer (modifier = Modifier .height(24 .dp))
543+
544+ // Progress Slider
545+ AnimatedVisibility (visible = offload.intValue != 3 ) {
546+ Column (
547+ modifier = Modifier
548+ .fillMaxWidth()
549+ .padding(horizontal = 32 .dp)
550+ ) {
551+ Slider (
552+ value = if (duration > 0 ) playbackPosition.toFloat() / duration else 0f ,
553+ onValueChange = { newValue ->
554+ isSeeking = true
555+ playbackPosition = (newValue * duration).toLong()
556+ },
557+ onValueChangeFinished = {
558+ player.seekTo(playbackPosition.toInt())
559+ isSeeking = false
560+ },
561+ colors = SliderDefaults .colors(
562+ thumbColor = MaterialTheme .colorScheme.primary,
563+ activeTrackColor = MaterialTheme .colorScheme.primary
564+ )
565+ )
566+ Row (
567+ modifier = Modifier .fillMaxWidth(),
568+ horizontalArrangement = Arrangement .SpaceBetween
569+ ) {
570+ Text (
571+ text = playbackPosition.convertToText(),
572+ fontSize = 12 .sp,
573+ color = Color .Gray
574+ )
575+ Text (
576+ text = duration.convertToText(),
577+ fontSize = 12 .sp,
578+ color = Color .Gray
579+ )
580+ }
581+ }
582+ }
583+
584+ Spacer (modifier = Modifier .height(16 .dp))
585+
517586 Row (
518587 horizontalArrangement = Arrangement .SpaceEvenly ,
519588 verticalAlignment = Alignment .CenterVertically
0 commit comments