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