From 81a350460620bd0e1cd46f7e5d71403611a67c0a Mon Sep 17 00:00:00 2001 From: Barnabas Balogh Date: Sat, 25 Apr 2026 22:31:39 +0200 Subject: [PATCH] refactor: update media retrieval to use Flow and improve error handling in PlaylistQueue loading --- .../purefin/data/CompositeMediaRepository.kt | 5 +- .../purefin/player/manager/PlayerManager.kt | 68 ++++++++++--------- .../player/viewmodel/PlayerViewModel.kt | 2 +- .../data/catalog/InMemoryMediaRepository.kt | 7 +- 4 files changed, 40 insertions(+), 42 deletions(-) diff --git a/core/src/main/java/hu/bbara/purefin/data/CompositeMediaRepository.kt b/core/src/main/java/hu/bbara/purefin/data/CompositeMediaRepository.kt index 06864947..5af954c5 100644 --- a/core/src/main/java/hu/bbara/purefin/data/CompositeMediaRepository.kt +++ b/core/src/main/java/hu/bbara/purefin/data/CompositeMediaRepository.kt @@ -46,19 +46,16 @@ class CompositeMediaRepository @Inject constructor( override suspend fun getMovie(id: UUID): Flow { return activeRepository .flatMapLatest { it.getMovie(id) } - .stateIn(scope, SharingStarted.Companion.Eagerly, null) } override suspend fun getSeries(id: UUID): Flow { return activeRepository .flatMapLatest { it.getSeries(id) } - .stateIn(scope, SharingStarted.Companion.Eagerly, null) } override suspend fun getEpisode(id: UUID): Flow { return activeRepository .flatMapLatest { it.getEpisode(id) } - .stateIn(scope, SharingStarted.Companion.Eagerly, null) } override fun observeSeriesWithContent(seriesId: UUID): Flow { @@ -69,4 +66,4 @@ class CompositeMediaRepository @Inject constructor( val repository = onlineRepository repository.updateWatchProgress(mediaId, positionMs, durationMs) } -} \ No newline at end of file +} diff --git a/core/src/main/java/hu/bbara/purefin/player/manager/PlayerManager.kt b/core/src/main/java/hu/bbara/purefin/player/manager/PlayerManager.kt index 9fdcc261..70688339 100644 --- a/core/src/main/java/hu/bbara/purefin/player/manager/PlayerManager.kt +++ b/core/src/main/java/hu/bbara/purefin/player/manager/PlayerManager.kt @@ -27,12 +27,12 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.update import kotlinx.coroutines.isActive import kotlinx.coroutines.launch @@ -61,29 +61,28 @@ class PlayerManager @Inject constructor( private val mediaSegmentManager = MediaSegmentManager(player as ExoPlayer) private val _currentMediaId = MutableStateFlow(null) - val currentPlayableMedia: StateFlow by lazy { - combine(_currentMediaId, _playlist) { mediaId, playlist -> - playlist.firstOrNull { it.id == mediaId } - }.stateIn(scope, SharingStarted.Eagerly, null) - } - - private var pendingSeekPositionMs: Long? = null - - private val _playbackState = MutableStateFlow(PlaybackStateSnapshot()) - val playbackState: StateFlow = _playbackState.asStateFlow() - - private val _progress = MutableStateFlow(PlaybackProgressSnapshot()) - val progress: StateFlow = _progress.asStateFlow() - - private val _metadata = MutableStateFlow(MetadataState()) - val metadata: StateFlow = _metadata.asStateFlow() - - private val _tracks = MutableStateFlow(TrackSelectionState()) - val tracks: StateFlow = _tracks.asStateFlow() - private val _playlist = MutableStateFlow(emptyList()) val playlist: StateFlow> = _playlist.asStateFlow() + val currentPlayableMedia: Flow = + combine(_currentMediaId, _playlist) { mediaId, playlist -> + playlist.firstOrNull { it.id == mediaId } + } + + private var pendingSeekPositionMs: Long? = null + private val _playbackState = MutableStateFlow(PlaybackStateSnapshot()) + + val playbackState: StateFlow = _playbackState.asStateFlow() + private val _progress = MutableStateFlow(PlaybackProgressSnapshot()) + + val progress: StateFlow = _progress.asStateFlow() + private val _metadata = MutableStateFlow(MetadataState()) + + val metadata: StateFlow = _metadata.asStateFlow() + private val _tracks = MutableStateFlow(TrackSelectionState()) + + val tracks: StateFlow = _tracks.asStateFlow() + private val listener = object : Player.Listener { override fun onPositionDiscontinuity( oldPosition: Player.PositionInfo, @@ -102,13 +101,17 @@ class PlayerManager @Inject constructor( override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { _currentMediaId.value = mediaItem?.mediaId?.let { UUID.fromString(it) } scope.launch { - currentPlayableMedia.value?.let { currentMedia -> - // Only updatePlaylist when episodes are being played. First item is handled when playMedia is being called first time. - if (currentMedia is PlayableMedia.Episode) { - updatePlaylist() - } - seekTo(currentMedia.resumePositionMs) + val currentMedia = _playlist.value.firstOrNull { it.id == _currentMediaId.value } + if (currentMedia == null) { + _playbackState.update { it.copy(error = "Media not found in playlist") } + return@launch } + // Only updatePlaylist when episodes are being played. First item is handled when playMedia is being called first time. + if (currentMedia is PlayableMedia.Episode) { + updatePlaylist() + } + seekTo(currentMedia.resumePositionMs) + resumePlayback() } } @@ -142,21 +145,20 @@ class PlayerManager @Inject constructor( val index = _playlist.value.indexOfFirst { it.id == mediaId } if (index != -1) { player.seekToDefaultPosition(index) - player.playWhenReady = true } else { _playbackState.update { it.copy(error = "Media not found in playlist") } } } private suspend fun playNewMedia(mediaId: UUID) { - val playableMedia = playableMediaRepository.getPlayableMedia(mediaId) + var playableMedia = playableMediaRepository.getPlayableMedia(mediaId) if (playableMedia == null) { _playbackState.update { it.copy(error = "Media not found") } return } + _playlist.update { it + playableMedia } player.setMediaItem(playableMedia.mediaItem) player.prepare() - player.playWhenReady = true } private suspend fun updatePlaylist() { @@ -289,8 +291,8 @@ class PlayerManager @Inject constructor( _playbackState.update { it.copy(error = null) } } - private fun applyTrackPreferences() { - val preferences = currentPlayableMedia.value?.preferences ?: return + private suspend fun applyTrackPreferences() { + val preferences = currentPlayableMedia.firstOrNull()?.preferences ?: return val currentTrackState = _tracks.value diff --git a/core/src/main/java/hu/bbara/purefin/player/viewmodel/PlayerViewModel.kt b/core/src/main/java/hu/bbara/purefin/player/viewmodel/PlayerViewModel.kt index ec201c96..e24045a3 100644 --- a/core/src/main/java/hu/bbara/purefin/player/viewmodel/PlayerViewModel.kt +++ b/core/src/main/java/hu/bbara/purefin/player/viewmodel/PlayerViewModel.kt @@ -126,7 +126,7 @@ class PlayerViewModel @Inject constructor( _uiState.update { state -> state.copy( queue = playlist.mapNotNull { playableMedia -> - playableMedia.toPlaylistElementUiModel(currentPlayableMedia.value?.id) + playableMedia.toPlaylistElementUiModel(currentPlayableMedia.first()?.id) } ) } diff --git a/data/src/main/java/hu/bbara/purefin/data/catalog/InMemoryMediaRepository.kt b/data/src/main/java/hu/bbara/purefin/data/catalog/InMemoryMediaRepository.kt index a63ec209..d0f46c87 100644 --- a/data/src/main/java/hu/bbara/purefin/data/catalog/InMemoryMediaRepository.kt +++ b/data/src/main/java/hu/bbara/purefin/data/catalog/InMemoryMediaRepository.kt @@ -86,10 +86,9 @@ class InMemoryMediaRepository @Inject constructor( episodesState.update { current -> current + (episode.id to episode) } } } - val episodeFlow = episodesState.map { it[id] } - val seriesId = episodeFlow.first()!!.seriesId - observeSeriesWithContent(seriesId = seriesId) - return episodeFlow + val episode = episodesState.value[id] ?: return flowOf(null) + observeSeriesWithContent(seriesId = episode.seriesId) + return episodesState.map { it[id] } } fun upsertMovies(movies: List) {