mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Support for different seek bar implementations
This commit is contained in:
parent
9c95685385
commit
a7046a942e
5 changed files with 154 additions and 63 deletions
|
|
@ -97,7 +97,7 @@ fun PlaybackControls(
|
||||||
controllerViewState: ControllerViewState,
|
controllerViewState: ControllerViewState,
|
||||||
onPlaybackActionClick: (PlaybackAction) -> Unit,
|
onPlaybackActionClick: (PlaybackAction) -> Unit,
|
||||||
showDebugInfo: Boolean,
|
showDebugInfo: Boolean,
|
||||||
onSeekProgress: (Float) -> Unit,
|
onSeekProgress: (Long) -> Unit,
|
||||||
showPlay: Boolean,
|
showPlay: Boolean,
|
||||||
previousEnabled: Boolean,
|
previousEnabled: Boolean,
|
||||||
nextEnabled: Boolean,
|
nextEnabled: Boolean,
|
||||||
|
|
@ -194,7 +194,7 @@ fun SeekBar(
|
||||||
isEnabled: Boolean,
|
isEnabled: Boolean,
|
||||||
intervals: Int,
|
intervals: Int,
|
||||||
controllerViewState: ControllerViewState,
|
controllerViewState: ControllerViewState,
|
||||||
onSeekProgress: (Float) -> Unit,
|
onSeekProgress: (Long) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
) {
|
) {
|
||||||
|
|
@ -213,17 +213,18 @@ fun SeekBar(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
SeekBarImpl(
|
IntervalSeekBarImpl(
|
||||||
progress = progress,
|
progress = progress,
|
||||||
bufferedProgress = bufferedProgress,
|
bufferedProgress = bufferedProgress,
|
||||||
onSeek = {
|
onSeek = {
|
||||||
onSeekProgress(it)
|
onSeekProgress(it)
|
||||||
},
|
},
|
||||||
controllerViewState = controllerViewState,
|
controllerViewState = controllerViewState,
|
||||||
intervals = intervals,
|
// intervals = intervals,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
enabled = isEnabled,
|
enabled = isEnabled,
|
||||||
|
durationMs = player.contentDuration,
|
||||||
)
|
)
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
import androidx.compose.runtime.mutableLongStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -33,7 +33,7 @@ fun PlaybackOverlay(
|
||||||
nextEnabled: Boolean,
|
nextEnabled: Boolean,
|
||||||
seekEnabled: Boolean,
|
seekEnabled: Boolean,
|
||||||
onPlaybackActionClick: (PlaybackAction) -> Unit,
|
onPlaybackActionClick: (PlaybackAction) -> Unit,
|
||||||
onSeekBarChange: (Float) -> Unit,
|
onSeekBarChange: (Long) -> Unit,
|
||||||
showDebugInfo: Boolean,
|
showDebugInfo: Boolean,
|
||||||
scale: ContentScale,
|
scale: ContentScale,
|
||||||
playbackSpeed: Float,
|
playbackSpeed: Float,
|
||||||
|
|
@ -48,8 +48,8 @@ fun PlaybackOverlay(
|
||||||
seekBarInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
seekBarInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
) {
|
) {
|
||||||
// Will be used for preview/trick play images
|
// Will be used for preview/trick play images
|
||||||
var seekProgressPercent by remember { mutableFloatStateOf(-1f) }
|
var seekProgressMs by remember { mutableLongStateOf(-1L) }
|
||||||
val seekProgressMs = seekProgressPercent * playerControls.duration
|
var seekProgressPercent = (seekProgressMs.toDouble() / playerControls.duration).toFloat()
|
||||||
val seekBarFocused by seekBarInteractionSource.collectIsFocusedAsState()
|
val seekBarFocused by seekBarInteractionSource.collectIsFocusedAsState()
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
|
|
@ -97,7 +97,7 @@ fun PlaybackOverlay(
|
||||||
),
|
),
|
||||||
previewImageUrl = imageUrl,
|
previewImageUrl = imageUrl,
|
||||||
duration = playerControls.duration,
|
duration = playerControls.duration,
|
||||||
seekProgress = seekProgressPercent,
|
seekProgressMs = seekProgressMs,
|
||||||
videoWidth = trickplayInfo.width,
|
videoWidth = trickplayInfo.width,
|
||||||
videoHeight = trickplayInfo.height,
|
videoHeight = trickplayInfo.height,
|
||||||
trickPlayInfo = trickplayInfo,
|
trickPlayInfo = trickplayInfo,
|
||||||
|
|
@ -114,7 +114,7 @@ fun PlaybackOverlay(
|
||||||
controllerViewState = controllerViewState,
|
controllerViewState = controllerViewState,
|
||||||
showDebugInfo = showDebugInfo,
|
showDebugInfo = showDebugInfo,
|
||||||
onSeekProgress = {
|
onSeekProgress = {
|
||||||
seekProgressPercent = it
|
seekProgressMs = it
|
||||||
onSeekBarChange(it)
|
onSeekBarChange(it)
|
||||||
},
|
},
|
||||||
showPlay = showPlay,
|
showPlay = showPlay,
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package com.github.damontecres.dolphin.ui.playback
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import android.view.KeyEvent
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.focusable
|
import androidx.compose.foundation.focusable
|
||||||
|
|
@ -30,8 +31,10 @@ import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.mutableLongStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
|
@ -39,15 +42,21 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.StrokeCap
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
|
import androidx.compose.ui.input.key.KeyEventType
|
||||||
|
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||||
|
import androidx.compose.ui.input.key.type
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import com.github.damontecres.dolphin.ui.handleDPadKeyEvents
|
import com.github.damontecres.dolphin.ui.handleDPadKeyEvents
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SeekBarImpl(
|
fun SteppedSeekBarImpl(
|
||||||
progress: Float,
|
progress: Float,
|
||||||
|
durationMs: Long,
|
||||||
bufferedProgress: Float,
|
bufferedProgress: Float,
|
||||||
onSeek: (seekProgress: Float) -> Unit,
|
onSeek: (Long) -> Unit,
|
||||||
controllerViewState: ControllerViewState,
|
controllerViewState: ControllerViewState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
intervals: Int = 10,
|
intervals: Int = 10,
|
||||||
|
|
@ -55,10 +64,6 @@ fun SeekBarImpl(
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
) {
|
) {
|
||||||
val isFocused by interactionSource.collectIsFocusedAsState()
|
val isFocused by interactionSource.collectIsFocusedAsState()
|
||||||
val color = MaterialTheme.colorScheme.border
|
|
||||||
val animatedIndicatorHeight by animateDpAsState(
|
|
||||||
targetValue = 6.dp.times((if (isFocused) 2f else 1f)),
|
|
||||||
)
|
|
||||||
var hasSeeked by remember { mutableStateOf(false) }
|
var hasSeeked by remember { mutableStateOf(false) }
|
||||||
var seekProgress by remember { mutableFloatStateOf(progress) }
|
var seekProgress by remember { mutableFloatStateOf(progress) }
|
||||||
val progressToUse = if (isFocused && hasSeeked) seekProgress else progress
|
val progressToUse = if (isFocused && hasSeeked) seekProgress else progress
|
||||||
|
|
@ -68,25 +73,91 @@ fun SeekBarImpl(
|
||||||
|
|
||||||
val offset = 1f / intervals
|
val offset = 1f / intervals
|
||||||
|
|
||||||
val handleSeekEventModifier =
|
val seek = { percent: Float ->
|
||||||
Modifier.handleDPadKeyEvents(
|
onSeek((percent * durationMs).toLong())
|
||||||
onCenter = {
|
}
|
||||||
controllerViewState.pulseControls()
|
|
||||||
onSeek(seekProgress)
|
SeekBarDisplay(
|
||||||
},
|
enabled = enabled,
|
||||||
onLeft = {
|
progress = progressToUse,
|
||||||
controllerViewState.pulseControls()
|
bufferedProgress = bufferedProgress,
|
||||||
seekProgress = (progressToUse - offset).coerceAtLeast(0f)
|
onLeft = {
|
||||||
hasSeeked = true
|
controllerViewState.pulseControls()
|
||||||
onSeek(seekProgress)
|
seekProgress = (progressToUse - offset).coerceAtLeast(0f)
|
||||||
},
|
hasSeeked = true
|
||||||
onRight = {
|
seek(seekProgress)
|
||||||
controllerViewState.pulseControls()
|
},
|
||||||
seekProgress = (progressToUse + offset).coerceAtMost(1f)
|
onRight = {
|
||||||
hasSeeked = true
|
controllerViewState.pulseControls()
|
||||||
onSeek(seekProgress)
|
seekProgress = (progressToUse + offset).coerceAtMost(1f)
|
||||||
},
|
hasSeeked = true
|
||||||
)
|
seek(seekProgress)
|
||||||
|
},
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
|
@Composable
|
||||||
|
fun IntervalSeekBarImpl(
|
||||||
|
progress: Float,
|
||||||
|
durationMs: Long,
|
||||||
|
bufferedProgress: Float,
|
||||||
|
onSeek: (Long) -> Unit,
|
||||||
|
controllerViewState: ControllerViewState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
|
enabled: Boolean = true,
|
||||||
|
) {
|
||||||
|
val isFocused by interactionSource.collectIsFocusedAsState()
|
||||||
|
var hasSeeked by remember { mutableStateOf(false) }
|
||||||
|
var seekPositionMs by remember { mutableLongStateOf((progress * durationMs).toLong()) }
|
||||||
|
val progressToUse by remember { derivedStateOf { if (isFocused && hasSeeked) seekPositionMs else (progress * durationMs).toLong() } }
|
||||||
|
|
||||||
|
LaunchedEffect(isFocused) {
|
||||||
|
if (!isFocused) hasSeeked = false
|
||||||
|
}
|
||||||
|
|
||||||
|
val offset = 30.seconds.inWholeMilliseconds
|
||||||
|
|
||||||
|
SeekBarDisplay(
|
||||||
|
enabled = enabled,
|
||||||
|
progress = (progressToUse.toDouble() / durationMs).toFloat(),
|
||||||
|
bufferedProgress = bufferedProgress,
|
||||||
|
onLeft = {
|
||||||
|
controllerViewState.pulseControls()
|
||||||
|
seekPositionMs = (progressToUse - offset).coerceAtLeast(0L)
|
||||||
|
hasSeeked = true
|
||||||
|
onSeek(seekPositionMs)
|
||||||
|
},
|
||||||
|
onRight = {
|
||||||
|
controllerViewState.pulseControls()
|
||||||
|
seekPositionMs = (progressToUse + offset).coerceAtMost(durationMs)
|
||||||
|
hasSeeked = true
|
||||||
|
onSeek(seekPositionMs)
|
||||||
|
},
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SeekBarDisplay(
|
||||||
|
progress: Float,
|
||||||
|
bufferedProgress: Float,
|
||||||
|
onLeft: () -> Unit,
|
||||||
|
onRight: () -> Unit,
|
||||||
|
interactionSource: MutableInteractionSource,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
) {
|
||||||
|
val color = MaterialTheme.colorScheme.border
|
||||||
|
|
||||||
|
val isFocused by interactionSource.collectIsFocusedAsState()
|
||||||
|
val animatedIndicatorHeight by animateDpAsState(
|
||||||
|
targetValue = 6.dp.times((if (isFocused) 2f else 1f)),
|
||||||
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|
@ -98,8 +169,25 @@ fun SeekBarImpl(
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(animatedIndicatorHeight)
|
.height(animatedIndicatorHeight)
|
||||||
.padding(horizontal = 4.dp)
|
.padding(horizontal = 4.dp)
|
||||||
.then(handleSeekEventModifier)
|
.onPreviewKeyEvent { event ->
|
||||||
.focusable(enabled = enabled, interactionSource = interactionSource),
|
val trigger =
|
||||||
|
event.type == KeyEventType.KeyUp || event.nativeKeyEvent.repeatCount > 0
|
||||||
|
when (event.nativeKeyEvent.keyCode) {
|
||||||
|
KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT -> {
|
||||||
|
if (trigger) onLeft.invoke()
|
||||||
|
return@onPreviewKeyEvent true
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT -> {
|
||||||
|
if (trigger) onRight.invoke()
|
||||||
|
return@onPreviewKeyEvent true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}.handleDPadKeyEvents(
|
||||||
|
onLeft = onLeft,
|
||||||
|
onRight = onRight,
|
||||||
|
).focusable(enabled = enabled, interactionSource = interactionSource),
|
||||||
onDraw = {
|
onDraw = {
|
||||||
val yOffset = size.height.div(2)
|
val yOffset = size.height.div(2)
|
||||||
drawLine(
|
drawLine(
|
||||||
|
|
@ -126,7 +214,7 @@ fun SeekBarImpl(
|
||||||
end =
|
end =
|
||||||
Offset(
|
Offset(
|
||||||
// x = size.width.times(if (isSelected) seekProgress else progress),
|
// x = size.width.times(if (isSelected) seekProgress else progress),
|
||||||
x = size.width.times(progressToUse),
|
x = size.width.times(progress),
|
||||||
y = yOffset,
|
y = yOffset,
|
||||||
),
|
),
|
||||||
strokeWidth = size.height,
|
strokeWidth = size.height,
|
||||||
|
|
@ -135,7 +223,7 @@ fun SeekBarImpl(
|
||||||
drawCircle(
|
drawCircle(
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
radius = size.height + 2,
|
radius = size.height + 2,
|
||||||
center = Offset(x = size.width.times(progressToUse), y = yOffset),
|
center = Offset(x = size.width.times(progress), y = yOffset),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,13 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.common.listen
|
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.flow.consumeAsFlow
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.flow.debounce
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -37,23 +36,26 @@ class SeekBarState(
|
||||||
var isEnabled by mutableStateOf(player.isCommandAvailable(Player.COMMAND_SEEK_FORWARD))
|
var isEnabled by mutableStateOf(player.isCommandAvailable(Player.COMMAND_SEEK_FORWARD))
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private var job: Job? = null
|
private val channel = Channel<Long>(CONFLATED)
|
||||||
|
|
||||||
fun onValueChange(progress: Float) {
|
fun onValueChange(positionMs: Long) {
|
||||||
job?.cancel()
|
channel.trySend(positionMs)
|
||||||
job =
|
}
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
delay(750L)
|
@OptIn(FlowPreview::class)
|
||||||
withContext(Dispatchers.Main) {
|
suspend fun observe() {
|
||||||
player.seekTo((player.duration * progress).toLong())
|
channel
|
||||||
}
|
.consumeAsFlow()
|
||||||
|
.debounce { 750L }
|
||||||
|
.collect {
|
||||||
|
player.seekTo(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun observe(): Nothing =
|
// suspend fun observe(): Nothing =
|
||||||
player.listen { events ->
|
// player.listen { events ->
|
||||||
if (events.contains(Player.EVENT_AVAILABLE_COMMANDS_CHANGED)) {
|
// if (events.contains(Player.EVENT_AVAILABLE_COMMANDS_CHANGED)) {
|
||||||
isEnabled = isCommandAvailable(Player.COMMAND_SEEK_FORWARD)
|
// isEnabled = isCommandAvailable(Player.COMMAND_SEEK_FORWARD)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ fun Modifier.offsetByPercent(xPercentage: Float) =
|
||||||
fun SeekPreviewImage(
|
fun SeekPreviewImage(
|
||||||
previewImageUrl: String,
|
previewImageUrl: String,
|
||||||
duration: Long,
|
duration: Long,
|
||||||
seekProgress: Float,
|
seekProgressMs: Long,
|
||||||
videoWidth: Int?,
|
videoWidth: Int?,
|
||||||
videoHeight: Int?,
|
videoHeight: Int?,
|
||||||
trickPlayInfo: TrickplayInfo,
|
trickPlayInfo: TrickplayInfo,
|
||||||
|
|
@ -86,7 +86,7 @@ fun SeekPreviewImage(
|
||||||
val heightPx = with(LocalDensity.current) { height.toPx().toInt() }
|
val heightPx = with(LocalDensity.current) { height.toPx().toInt() }
|
||||||
val widthPx = with(LocalDensity.current) { width.toPx().toInt() }
|
val widthPx = with(LocalDensity.current) { width.toPx().toInt() }
|
||||||
|
|
||||||
val index = (duration * seekProgress / trickPlayInfo.interval).toInt() // Which tile
|
val index = (seekProgressMs.toDouble() / trickPlayInfo.interval).toInt() // Which tile
|
||||||
val numberOfTitlesPerImage = trickPlayInfo.tileHeight * trickPlayInfo.tileWidth
|
val numberOfTitlesPerImage = trickPlayInfo.tileHeight * trickPlayInfo.tileWidth
|
||||||
val imageIndex = index % numberOfTitlesPerImage
|
val imageIndex = index % numberOfTitlesPerImage
|
||||||
|
|
||||||
|
|
@ -118,7 +118,7 @@ fun SeekPreviewImage(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = (seekProgress * duration / 1000).toLong().seconds.toString(),
|
text = (seekProgressMs / 1000L).seconds.toString(),
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue