diff --git a/data/playback/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/data/PlaybackRepositoryImpl.kt b/data/playback/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/data/PlaybackRepositoryImpl.kt index fc1ae70..cfe88f1 100644 --- a/data/playback/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/data/PlaybackRepositoryImpl.kt +++ b/data/playback/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/data/PlaybackRepositoryImpl.kt @@ -56,8 +56,8 @@ internal class PlaybackRepositoryImpl( private val playbackTiming = currentPosition.map { currentPosition -> PlaybackTiming( - positionMs = currentPosition.inWholeMilliseconds, - durationMs = duration.inWholeMilliseconds, + position = currentPosition, + duration = duration, ) } diff --git a/data/playback/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/model/PlaybackTiming.kt b/data/playback/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/model/PlaybackTiming.kt index 23ad9f7..feef5db 100644 --- a/data/playback/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/model/PlaybackTiming.kt +++ b/data/playback/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/model/PlaybackTiming.kt @@ -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, +) diff --git a/data/subtitle/src/commonMain/kotlin/fr/ajaury/gwenedeg/subtitle/data/LrcParser.kt b/data/subtitle/src/commonMain/kotlin/fr/ajaury/gwenedeg/subtitle/data/LrcParser.kt index b593fc2..2454290 100644 --- a/data/subtitle/src/commonMain/kotlin/fr/ajaury/gwenedeg/subtitle/data/LrcParser.kt +++ b/data/subtitle/src/commonMain/kotlin/fr/ajaury/gwenedeg/subtitle/data/LrcParser.kt @@ -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 } } diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/PlayerControl.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/PlayerControl.kt index 2bc1de5..892a17e 100644 --- a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/PlayerControl.kt +++ b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/PlayerControl.kt @@ -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, diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/PlayerScreen.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/PlayerScreen.kt index 1e1246a..95584fc 100644 --- a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/PlayerScreen.kt +++ b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/PlayerScreen.kt @@ -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, ), diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/components/PlaybackProgress.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/components/PlaybackProgress.kt index 331c025..10420a7 100644 --- a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/components/PlaybackProgress.kt +++ b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/components/PlaybackProgress.kt @@ -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(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), ) } } diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/viewmodel/PlayerUiState.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/viewmodel/PlayerUiState.kt index b63ec64..e2f5fba 100644 --- a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/viewmodel/PlayerUiState.kt +++ b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/viewmodel/PlayerUiState.kt @@ -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 diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/viewmodel/PlayerViewModel.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/viewmodel/PlayerViewModel.kt index 244518a..3c25203 100644 --- a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/viewmodel/PlayerViewModel.kt +++ b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/viewmodel/PlayerViewModel.kt @@ -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) {