refactor: update media retrieval to use Flow and improve error handling in PlaylistQueue loading

This commit is contained in:
2026-04-25 22:31:39 +02:00
parent b15453ab01
commit 81a3504606
4 changed files with 40 additions and 42 deletions

View File

@@ -46,19 +46,16 @@ class CompositeMediaRepository @Inject constructor(
override suspend fun getMovie(id: UUID): Flow<Movie?> {
return activeRepository
.flatMapLatest { it.getMovie(id) }
.stateIn(scope, SharingStarted.Companion.Eagerly, null)
}
override suspend fun getSeries(id: UUID): Flow<Series?> {
return activeRepository
.flatMapLatest { it.getSeries(id) }
.stateIn(scope, SharingStarted.Companion.Eagerly, null)
}
override suspend fun getEpisode(id: UUID): Flow<Episode?> {
return activeRepository
.flatMapLatest { it.getEpisode(id) }
.stateIn(scope, SharingStarted.Companion.Eagerly, null)
}
override fun observeSeriesWithContent(seriesId: UUID): Flow<Series?> {

View File

@@ -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,28 +61,27 @@ class PlayerManager @Inject constructor(
private val mediaSegmentManager = MediaSegmentManager(player as ExoPlayer)
private val _currentMediaId = MutableStateFlow<UUID?>(null)
val currentPlayableMedia: StateFlow<PlayableMedia?> by lazy {
private val _playlist = MutableStateFlow(emptyList<PlayableMedia>())
val playlist: StateFlow<List<PlayableMedia>> = _playlist.asStateFlow()
val currentPlayableMedia: Flow<PlayableMedia?> =
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<PlaybackStateSnapshot> = _playbackState.asStateFlow()
private val _progress = MutableStateFlow(PlaybackProgressSnapshot())
val progress: StateFlow<PlaybackProgressSnapshot> = _progress.asStateFlow()
private val _metadata = MutableStateFlow(MetadataState())
val metadata: StateFlow<MetadataState> = _metadata.asStateFlow()
private val _tracks = MutableStateFlow(TrackSelectionState())
val tracks: StateFlow<TrackSelectionState> = _tracks.asStateFlow()
private val _playlist = MutableStateFlow(emptyList<PlayableMedia>())
val playlist: StateFlow<List<PlayableMedia>> = _playlist.asStateFlow()
val tracks: StateFlow<TrackSelectionState> = _tracks.asStateFlow()
private val listener = object : Player.Listener {
override fun onPositionDiscontinuity(
@@ -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 ->
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

View File

@@ -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)
}
)
}

View File

@@ -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<Movie>) {