From bd25dee1f0b2a889facceb27899b36013bdb998c Mon Sep 17 00:00:00 2001 From: Antoine Jaury Date: Fri, 3 Jul 2026 13:54:25 +0200 Subject: [PATCH] refactor: use SSoT for playback speed --- data/playback/build.gradle.kts | 2 ++ .../player/data/PlaybackRepositoryImpl.kt | 25 ++++++++++++++++++- .../player/domain/PlaybackRepository.kt | 2 -- .../player/ui/viewmodel/PlayerViewModel.kt | 9 +------ 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/data/playback/build.gradle.kts b/data/playback/build.gradle.kts index 285a7a7..be8b700 100644 --- a/data/playback/build.gradle.kts +++ b/data/playback/build.gradle.kts @@ -11,10 +11,12 @@ kotlin { commonMain.dependencies { // Core modules implementation(projects.core.audioplayer) + implementation(projects.core.coroutines) implementation(projects.core.logging) implementation(projects.core.model) // Data modules + implementation(projects.data.preferences) implementation(projects.data.records) implementation(projects.data.resources) implementation(projects.data.subtitle) 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 cfe88f1..5af3821 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 @@ -1,5 +1,6 @@ package fr.ajaury.gwenedeg.player.data +import fr.ajaury.gwenedeg.core.coroutines.domain.DispatcherProvider import fr.ajaury.gwenedeg.core.logging.domain.Logger import fr.ajaury.gwenedeg.core.model.Phrase import fr.ajaury.gwenedeg.player.domain.AudioPlayer @@ -7,12 +8,16 @@ import fr.ajaury.gwenedeg.player.domain.PlaybackRepository import fr.ajaury.gwenedeg.player.model.PlaybackState import fr.ajaury.gwenedeg.player.model.PlaybackTiming import fr.ajaury.gwenedeg.player.model.PlayerState +import fr.ajaury.gwenedeg.preferences.domain.PreferencesRepository import fr.ajaury.gwenedeg.records.model.Record import fr.ajaury.gwenedeg.resources.domain.ResourceReader import fr.ajaury.gwenedeg.subtitle.domain.GetCurrentSubtitleIndexUseCase import fr.ajaury.gwenedeg.subtitle.domain.SubtitleRepository import fr.ajaury.gwenedeg.subtitle.model.Subtitle +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -22,6 +27,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -30,9 +36,15 @@ internal class PlaybackRepositoryImpl( private val audioPlayer: AudioPlayer, private val resourceReader: ResourceReader, private val subtitleRepository: SubtitleRepository, + private val preferencesRepository: PreferencesRepository, private val getCurrentSubtitleIndex: GetCurrentSubtitleIndexUseCase, + private val dispatcherProvider: DispatcherProvider, private val logger: Logger, ) : PlaybackRepository { + // Audio operations must run on the main thread (e.g. ExoPlayer), so keep the scope on it. + private val scope = CoroutineScope(SupervisorJob() + dispatcherProvider.main) + private var speedObservationJob: Job? = null + private val recordTitle = MutableStateFlow(Phrase(transcription = "")) private val subtitle = MutableStateFlow(Subtitle(emptyList())) @@ -91,6 +103,17 @@ internal class PlaybackRepositoryImpl( playAudio(filePath = record.audioResourcePath) subtitle.value = loadSubtitle(record = record) recordTitle.value = record.title + + observePlaybackSpeed() + } + + private fun observePlaybackSpeed() { + speedObservationJob?.cancel() + speedObservationJob = scope.launch { + preferencesRepository.playbackPreferences.collect { playbackPreferences -> + setSpeed(speed = playbackPreferences.speed) + } + } } private fun playAudio(filePath: String) { @@ -136,7 +159,7 @@ internal class PlaybackRepositoryImpl( audioPlayer.seekTo(position) } - override fun setSpeed(speed: Float) { + private fun setSpeed(speed: Float) { audioPlayer.setSpeed(speed) } diff --git a/data/playback/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/domain/PlaybackRepository.kt b/data/playback/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/domain/PlaybackRepository.kt index ea39365..53f6711 100644 --- a/data/playback/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/domain/PlaybackRepository.kt +++ b/data/playback/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/domain/PlaybackRepository.kt @@ -25,8 +25,6 @@ interface PlaybackRepository { fun seekTo(position: Duration) - fun setSpeed(speed: Float) - fun seekToSentence(sentenceIndex: Int) fun goToPreviousSentence() 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 03c097d..2e6f85a 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 @@ -62,7 +62,6 @@ class PlayerViewModel( if (alreadyLoadedRecord != null) { logger.debug("Record $recordId already loaded") playbackRepository.load(record = alreadyLoadedRecord) - applyCurrentSpeed() return@launch } @@ -73,16 +72,10 @@ class PlayerViewModel( } playbackRepository.load(record = loadedRecord) - applyCurrentSpeed() record.value = loadedRecord } } - private suspend fun applyCurrentSpeed() { - val playbackPreferences = preferencesRepository.playbackPreferences.firstOrNull() ?: return - playbackRepository.setSpeed(playbackPreferences.speed) - } - fun performMainPlayAction() { when (uiState.value.playerState.playbackState) { PlaybackState.PLAYING -> playbackRepository.pause() @@ -113,9 +106,9 @@ class PlayerViewModel( PlaybackPreferences.MIN_SPEED, PlaybackPreferences.MAX_SPEED, ) + viewModelScope.launch { preferencesRepository.setPlaybackSpeed(clampedSpeed) - playbackRepository.setSpeed(clampedSpeed) } }