refactor: prefer using Duration than milliseconds

This commit is contained in:
2026-07-03 11:17:15 +02:00
parent 16bb6b4c6b
commit beb1973978
8 changed files with 43 additions and 33 deletions
@@ -56,8 +56,8 @@ internal class PlaybackRepositoryImpl(
private val playbackTiming = currentPosition.map { currentPosition ->
PlaybackTiming(
positionMs = currentPosition.inWholeMilliseconds,
durationMs = duration.inWholeMilliseconds,
position = currentPosition,
duration = duration,
)
}
@@ -1,9 +1,9 @@
package fr.ajaury.gwenedeg.player.model
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
data class PlaybackTiming(
val positionMs: Long = 0L,
val durationMs: Long = 0L,
) {
val progress: Float
get() = if (durationMs > 0) (positionMs.toFloat() / durationMs).coerceIn(0f, 1f) else 0f
}
val position: Duration = 0.milliseconds,
val duration: Duration = 0.milliseconds,
)
@@ -1,6 +1,9 @@
package fr.ajaury.gwenedeg.subtitle.data
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
/**
* Parses [LRC](https://en.wikipedia.org/wiki/LRC_(file_format)) subtitle content.
@@ -26,15 +29,15 @@ internal class LrcParser {
.findAll(tags.value)
.mapNotNull { match ->
TimeCodedText(
startTime = match.toMs().milliseconds,
startTime = match.asDuration(),
text = text,
).takeIf { text.isNotBlank() }
}.toList()
}
private fun MatchResult.toMs(): Long {
private fun MatchResult.asDuration(): Duration {
val (minutes, seconds, fraction) = destructured
val fractionMs = if (fraction.length == 2) fraction.toLong() * 10 else fraction.toLong()
return minutes.toLong() * 60_000 + seconds.toLong() * 1_000 + fractionMs
return minutes.toInt().minutes + seconds.toInt().seconds + fractionMs.toInt().milliseconds
}
}
@@ -20,6 +20,8 @@ import fr.ajaury.gwenedeg.player.ui.components.PreviousButton
import fr.ajaury.gwenedeg.player.ui.components.SpeedPreferenceButton
import fr.ajaury.gwenedeg.preferences.model.PlaybackPreferences
import fr.ajaury.gwenedeg.theme.GwenedegTheme
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
@Composable
fun PlayerControl(
@@ -30,7 +32,7 @@ fun PlayerControl(
canGoToNextSentence: Boolean,
isSpeedActive: Boolean,
playbackSpeed: Float,
onSeek: (Float) -> Unit = {},
onSeek: (Duration) -> Unit = {},
onMainPlayActionButtonClicked: () -> Unit = {},
onPreviousSentenceClicked: () -> Unit = {},
onNextSentenceClicked: () -> Unit = {},
@@ -95,7 +97,7 @@ private fun PlayerControlPreview() {
GwenedegTheme {
PlayerControl(
playbackState = PlaybackState.PLAYING,
playbackTiming = PlaybackTiming(positionMs = 1234, durationMs = 5000),
playbackTiming = PlaybackTiming(position = 1.2.seconds, duration = 5.seconds),
canGoToPreviousSentence = true,
canGoToNextSentence = true,
isSpeedActive = false,
@@ -110,7 +112,7 @@ private fun PlayerControlFirstSentencePreview() {
GwenedegTheme {
PlayerControl(
playbackState = PlaybackState.PLAYING,
playbackTiming = PlaybackTiming(positionMs = 1234, durationMs = 5000),
playbackTiming = PlaybackTiming(position = 1.2.seconds, duration = 5.seconds),
canGoToPreviousSentence = false,
canGoToNextSentence = true,
playbackSpeed = PlaybackPreferences.DEFAULT_SPEED,
@@ -125,7 +127,7 @@ private fun PlayerControlActiveSpeedPreview() {
GwenedegTheme {
PlayerControl(
playbackState = PlaybackState.PLAYING,
playbackTiming = PlaybackTiming(positionMs = 1234, durationMs = 5000),
playbackTiming = PlaybackTiming(position = 1.2.seconds, duration = 5.seconds),
canGoToPreviousSentence = true,
canGoToNextSentence = true,
playbackSpeed = 0.7f,
@@ -140,7 +142,7 @@ private fun PlayerControlLastSentencePreview() {
GwenedegTheme {
PlayerControl(
playbackState = PlaybackState.PLAYING,
playbackTiming = PlaybackTiming(positionMs = 1234, durationMs = 5000),
playbackTiming = PlaybackTiming(position = 1.2.seconds, duration = 5.seconds),
canGoToPreviousSentence = true,
canGoToNextSentence = false,
playbackSpeed = PlaybackPreferences.DEFAULT_SPEED,
@@ -38,7 +38,9 @@ import fr.ajaury.gwenedeg.subtitle.model.SubtitleLine
import fr.ajaury.gwenedeg.theme.GwenedegTheme
import org.koin.compose.viewmodel.koinViewModel
import org.koin.core.parameter.parametersOf
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@Composable
fun PlayerScreen(
@@ -75,7 +77,7 @@ fun PlayerScreen(
fun PlayerScreen(
uiState: PlayerUiState,
onMainPlayActionButtonClicked: () -> Unit = {},
onSeekToTimePart: (progress: Float) -> Unit = {},
onSeekToTimePart: (progress: Duration) -> Unit = {},
onSeekToSentence: (position: Int) -> Unit = {},
onPreviousSentenceClicked: () -> Unit = {},
onNextSentenceClicked: () -> Unit = {},
@@ -218,7 +220,7 @@ private fun PlayerScreenPlayingPreview() {
),
playerState = PlayerState(
playbackState = PlaybackState.PLAYING,
playbackTiming = PlaybackTiming(positionMs = 1234, durationMs = 5000),
playbackTiming = PlaybackTiming(position = 1.2.seconds, duration = 5.seconds),
subtitleLines = previewSubtitleLines,
currentSubtitleIndex = 1,
),
@@ -242,7 +244,7 @@ private fun PlayerScreenPausedPreview() {
),
playerState = PlayerState(
playbackState = PlaybackState.PAUSED,
playbackTiming = PlaybackTiming(positionMs = 1234, durationMs = 5000),
playbackTiming = PlaybackTiming(position = 1.2.seconds, duration = 5.seconds),
subtitleLines = previewSubtitleLines,
currentSubtitleIndex = 1,
),
@@ -266,7 +268,7 @@ private fun PlayerScreenEndedPreview() {
),
playerState = PlayerState(
playbackState = PlaybackState.ENDED,
playbackTiming = PlaybackTiming(positionMs = 1234, durationMs = 5000),
playbackTiming = PlaybackTiming(position = 1.2.seconds, duration = 5.seconds),
subtitleLines = previewSubtitleLines,
currentSubtitleIndex = 2,
),
@@ -11,21 +11,25 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import fr.ajaury.gwenedeg.player.model.PlaybackTiming
import fr.ajaury.gwenedeg.theme.GwenedegTheme
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@Composable
fun PlaybackProgress(
playbackTiming: PlaybackTiming,
onSeek: (Float) -> Unit = {},
onSeek: (Duration) -> Unit = {},
) {
var seekProgress by remember { mutableStateOf<Float?>(null) }
Slider(
value = seekProgress ?: playbackTiming.progress,
value = seekProgress ?: playbackTiming.position.inWholeMilliseconds.toFloat(),
valueRange = 0f..playbackTiming.duration.inWholeMilliseconds.toFloat(),
onValueChange = { seekProgress = it },
onValueChangeFinished = {
seekProgress?.let { fraction ->
if (playbackTiming.durationMs > 0) {
onSeek(fraction)
if (playbackTiming.duration.isPositive()) {
onSeek(fraction.toInt().milliseconds)
}
}
seekProgress = null
@@ -39,7 +43,7 @@ fun PlaybackProgress(
private fun PlaybackProgressPreview() {
GwenedegTheme {
PlaybackProgress(
playbackTiming = PlaybackTiming(0, 0),
playbackTiming = PlaybackTiming(0.seconds, 0.seconds),
)
}
}
@@ -49,7 +53,7 @@ private fun PlaybackProgressPreview() {
private fun PlaybackProgressInProgressPreview() {
GwenedegTheme {
PlaybackProgress(
playbackTiming = PlaybackTiming(7000, 10000),
playbackTiming = PlaybackTiming(7.seconds, 10.seconds),
)
}
}
@@ -59,7 +63,7 @@ private fun PlaybackProgressInProgressPreview() {
private fun PlaybackProgressEndedPreview() {
GwenedegTheme {
PlaybackProgress(
playbackTiming = PlaybackTiming(10000, 10000),
playbackTiming = PlaybackTiming(10.seconds, 10.seconds),
)
}
}
@@ -4,6 +4,7 @@ import fr.ajaury.gwenedeg.core.model.Phrase
import fr.ajaury.gwenedeg.player.model.PlayerState
import fr.ajaury.gwenedeg.preferences.model.PlaybackPreferences
import fr.ajaury.gwenedeg.preferences.model.SubtitlePreferences
import kotlin.time.Duration.Companion.seconds
data class PlayerUiState(
val recordTitle: Phrase = Phrase(transcription = ""),
@@ -12,7 +13,7 @@ data class PlayerUiState(
val playbackSpeed: Float = PlaybackPreferences.DEFAULT_SPEED,
) {
val canGoToPreviousSentence: Boolean
get() = playerState.playbackTiming.positionMs > 2000
get() = playerState.playbackTiming.position > 2.seconds
val canGoToNextSentence: Boolean
get() = (playerState.currentSubtitleIndex ?: Int.MAX_VALUE) < playerState.subtitleLines.lastIndex
@@ -19,7 +19,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
class PlayerViewModel(
@@ -89,10 +89,8 @@ class PlayerViewModel(
}
}
fun seekToTimePart(progress: Float) {
val playbackTiming = uiState.value.playerState.playbackTiming
val position = (progress.toDouble() * playbackTiming.durationMs).milliseconds
playbackRepository.seekTo(position)
fun seekToTimePart(progress: Duration) {
playbackRepository.seekTo(progress)
}
fun seekToSentence(sentenceIndex: Int) {