@@ -26,12 +26,20 @@ import androidx.compose.ui.geometry.Offset
2626import androidx.compose.ui.geometry.Size
2727import androidx.compose.ui.graphics.Color
2828import androidx.compose.ui.input.pointer.pointerInput
29+ import androidx.compose.ui.semantics.ProgressBarRangeInfo
30+ import androidx.compose.ui.semantics.contentDescription
31+ import androidx.compose.ui.semantics.progressBarRangeInfo
32+ import androidx.compose.ui.semantics.setProgress
33+ import androidx.compose.ui.semantics.stateDescription
34+ import androidx.compose.ui.semantics.semantics
35+ import androidx.compose.ui.res.stringResource
2936import androidx.compose.ui.text.style.TextAlign
3037import androidx.compose.ui.unit.dp
3138import androidx.compose.ui.unit.sp
3239import androidx.media3.common.Player
3340import androidx.media3.common.util.UnstableApi
3441import androidx.media3.ui.compose.state.rememberProgressStateWithTickInterval
42+ import dev.typetype.android.R
3543import dev.typetype.android.domain.stream.SponsorBlockSegment
3644import kotlin.time.Duration.Companion.milliseconds
3745
@@ -60,14 +68,16 @@ fun PlayerTimeBar(
6068
6169 val durationMs = progressState.durationMs.coerceAtLeast(0L )
6270 val displayedPosMs = scrubPositionMs ? : progressState.currentPositionMs.coerceIn(0L , durationMs)
71+ val positionLabel = formatPlayerTime(displayedPosMs)
72+ val durationLabel = formatPlayerTime(durationMs)
6373
6474 Row (
6575 modifier = modifier,
6676 verticalAlignment = Alignment .CenterVertically ,
6777 horizontalArrangement = Arrangement .spacedBy(8 .dp),
6878 ) {
6979 Text (
70- text = formatTime(displayedPosMs) ,
80+ text = positionLabel ,
7181 style = MaterialTheme .typography.labelSmall.copy(fontSize = 12 .sp),
7282 color = Color .White ,
7383 modifier = if (compact) {
@@ -88,13 +98,19 @@ fun PlayerTimeBar(
8898 scrubPositionMs = null
8999 },
90100 onScrubCancelled = { scrubPositionMs = null },
101+ accessibilityLabel = stringResource(R .string.player_timeline),
102+ accessibilityStateDescription = stringResource(
103+ R .string.player_timeline_position,
104+ positionLabel,
105+ durationLabel,
106+ ),
91107 modifier = Modifier
92108 .weight(1f )
93109 .padding(horizontal = if (compact) 2 .dp else 4 .dp)
94110 .height(if (compact) COMPACT_TIMELINE_HEIGHT else TIMELINE_HEIGHT ),
95111 )
96112 Text (
97- text = formatTime(durationMs) ,
113+ text = durationLabel ,
98114 style = MaterialTheme .typography.labelSmall.copy(fontSize = 12 .sp),
99115 color = Color .White .copy(alpha = 0.7f ),
100116 modifier = if (compact) {
@@ -115,12 +131,28 @@ internal fun TimelineTrack(
115131 onScrub : (Long ) -> Unit ,
116132 onScrubFinished : (Long ) -> Unit ,
117133 onScrubCancelled : () -> Unit ,
134+ accessibilityLabel : String ,
135+ accessibilityStateDescription : String ,
118136 modifier : Modifier = Modifier ,
119137) {
120138 val activeColor = MaterialTheme .colorScheme.primary
121139 val inactiveColor = Color .White .copy(alpha = 0.3f )
122140 Box (
123141 modifier = modifier
142+ .semantics {
143+ contentDescription = accessibilityLabel
144+ stateDescription = accessibilityStateDescription
145+ progressBarRangeInfo = ProgressBarRangeInfo (
146+ current = positionMs.toFloat(),
147+ range = 0f .. durationMs.coerceAtLeast(1L ).toFloat(),
148+ )
149+ setProgress { target ->
150+ val targetMs = target.toLong().coerceIn(0L , durationMs.coerceAtLeast(0L ))
151+ onScrub(targetMs)
152+ onScrubFinished(targetMs)
153+ true
154+ }
155+ }
124156 .pointerInput(durationMs) {
125157 detectTapGestures { offset ->
126158 val targetMs = offset.x.toPositionMs(size.width.toFloat(), durationMs)
@@ -207,7 +239,7 @@ private fun Float.toPositionMs(width: Float, durationMs: Long): Long {
207239 return ((this / width).coerceIn(0f , 1f ) * durationMs).toLong()
208240}
209241
210- private fun formatTime (ms : Long ): String {
242+ internal fun formatPlayerTime (ms : Long ): String {
211243 val total = ms.milliseconds.inWholeSeconds
212244 val h = total / 3600
213245 val m = (total % 3600 ) / 60
0 commit comments