mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
refactor: update media retrieval to use Flow and improve error handling in PlaylistQueue loading
This commit is contained in:
@@ -46,19 +46,16 @@ class CompositeMediaRepository @Inject constructor(
|
|||||||
override suspend fun getMovie(id: UUID): Flow<Movie?> {
|
override suspend fun getMovie(id: UUID): Flow<Movie?> {
|
||||||
return activeRepository
|
return activeRepository
|
||||||
.flatMapLatest { it.getMovie(id) }
|
.flatMapLatest { it.getMovie(id) }
|
||||||
.stateIn(scope, SharingStarted.Companion.Eagerly, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getSeries(id: UUID): Flow<Series?> {
|
override suspend fun getSeries(id: UUID): Flow<Series?> {
|
||||||
return activeRepository
|
return activeRepository
|
||||||
.flatMapLatest { it.getSeries(id) }
|
.flatMapLatest { it.getSeries(id) }
|
||||||
.stateIn(scope, SharingStarted.Companion.Eagerly, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getEpisode(id: UUID): Flow<Episode?> {
|
override suspend fun getEpisode(id: UUID): Flow<Episode?> {
|
||||||
return activeRepository
|
return activeRepository
|
||||||
.flatMapLatest { it.getEpisode(id) }
|
.flatMapLatest { it.getEpisode(id) }
|
||||||
.stateIn(scope, SharingStarted.Companion.Eagerly, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun observeSeriesWithContent(seriesId: UUID): Flow<Series?> {
|
override fun observeSeriesWithContent(seriesId: UUID): Flow<Series?> {
|
||||||
@@ -69,4 +66,4 @@ class CompositeMediaRepository @Inject constructor(
|
|||||||
val repository = onlineRepository
|
val repository = onlineRepository
|
||||||
repository.updateWatchProgress(mediaId, positionMs, durationMs)
|
repository.updateWatchProgress(mediaId, positionMs, durationMs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -61,29 +61,28 @@ class PlayerManager @Inject constructor(
|
|||||||
private val mediaSegmentManager = MediaSegmentManager(player as ExoPlayer)
|
private val mediaSegmentManager = MediaSegmentManager(player as ExoPlayer)
|
||||||
|
|
||||||
private val _currentMediaId = MutableStateFlow<UUID?>(null)
|
private val _currentMediaId = MutableStateFlow<UUID?>(null)
|
||||||
val currentPlayableMedia: StateFlow<PlayableMedia?> 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<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>())
|
private val _playlist = MutableStateFlow(emptyList<PlayableMedia>())
|
||||||
val playlist: StateFlow<List<PlayableMedia>> = _playlist.asStateFlow()
|
val playlist: StateFlow<List<PlayableMedia>> = _playlist.asStateFlow()
|
||||||
|
|
||||||
|
val currentPlayableMedia: Flow<PlayableMedia?> =
|
||||||
|
combine(_currentMediaId, _playlist) { mediaId, playlist ->
|
||||||
|
playlist.firstOrNull { it.id == mediaId }
|
||||||
|
}
|
||||||
|
|
||||||
|
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 listener = object : Player.Listener {
|
private val listener = object : Player.Listener {
|
||||||
override fun onPositionDiscontinuity(
|
override fun onPositionDiscontinuity(
|
||||||
oldPosition: Player.PositionInfo,
|
oldPosition: Player.PositionInfo,
|
||||||
@@ -102,13 +101,17 @@ class PlayerManager @Inject constructor(
|
|||||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||||
_currentMediaId.value = mediaItem?.mediaId?.let { UUID.fromString(it) }
|
_currentMediaId.value = mediaItem?.mediaId?.let { UUID.fromString(it) }
|
||||||
scope.launch {
|
scope.launch {
|
||||||
currentPlayableMedia.value?.let { currentMedia ->
|
val currentMedia = _playlist.value.firstOrNull { it.id == _currentMediaId.value }
|
||||||
// Only updatePlaylist when episodes are being played. First item is handled when playMedia is being called first time.
|
if (currentMedia == null) {
|
||||||
if (currentMedia is PlayableMedia.Episode) {
|
_playbackState.update { it.copy(error = "Media not found in playlist") }
|
||||||
updatePlaylist()
|
return@launch
|
||||||
}
|
|
||||||
seekTo(currentMedia.resumePositionMs)
|
|
||||||
}
|
}
|
||||||
|
// 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 }
|
val index = _playlist.value.indexOfFirst { it.id == mediaId }
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
player.seekToDefaultPosition(index)
|
player.seekToDefaultPosition(index)
|
||||||
player.playWhenReady = true
|
|
||||||
} else {
|
} else {
|
||||||
_playbackState.update { it.copy(error = "Media not found in playlist") }
|
_playbackState.update { it.copy(error = "Media not found in playlist") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun playNewMedia(mediaId: UUID) {
|
private suspend fun playNewMedia(mediaId: UUID) {
|
||||||
val playableMedia = playableMediaRepository.getPlayableMedia(mediaId)
|
var playableMedia = playableMediaRepository.getPlayableMedia(mediaId)
|
||||||
if (playableMedia == null) {
|
if (playableMedia == null) {
|
||||||
_playbackState.update { it.copy(error = "Media not found") }
|
_playbackState.update { it.copy(error = "Media not found") }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
_playlist.update { it + playableMedia }
|
||||||
player.setMediaItem(playableMedia.mediaItem)
|
player.setMediaItem(playableMedia.mediaItem)
|
||||||
player.prepare()
|
player.prepare()
|
||||||
player.playWhenReady = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updatePlaylist() {
|
private suspend fun updatePlaylist() {
|
||||||
@@ -289,8 +291,8 @@ class PlayerManager @Inject constructor(
|
|||||||
_playbackState.update { it.copy(error = null) }
|
_playbackState.update { it.copy(error = null) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun applyTrackPreferences() {
|
private suspend fun applyTrackPreferences() {
|
||||||
val preferences = currentPlayableMedia.value?.preferences ?: return
|
val preferences = currentPlayableMedia.firstOrNull()?.preferences ?: return
|
||||||
|
|
||||||
val currentTrackState = _tracks.value
|
val currentTrackState = _tracks.value
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ class PlayerViewModel @Inject constructor(
|
|||||||
_uiState.update { state ->
|
_uiState.update { state ->
|
||||||
state.copy(
|
state.copy(
|
||||||
queue = playlist.mapNotNull { playableMedia ->
|
queue = playlist.mapNotNull { playableMedia ->
|
||||||
playableMedia.toPlaylistElementUiModel(currentPlayableMedia.value?.id)
|
playableMedia.toPlaylistElementUiModel(currentPlayableMedia.first()?.id)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,10 +86,9 @@ class InMemoryMediaRepository @Inject constructor(
|
|||||||
episodesState.update { current -> current + (episode.id to episode) }
|
episodesState.update { current -> current + (episode.id to episode) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val episodeFlow = episodesState.map { it[id] }
|
val episode = episodesState.value[id] ?: return flowOf(null)
|
||||||
val seriesId = episodeFlow.first()!!.seriesId
|
observeSeriesWithContent(seriesId = episode.seriesId)
|
||||||
observeSeriesWithContent(seriesId = seriesId)
|
return episodesState.map { it[id] }
|
||||||
return episodeFlow
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun upsertMovies(movies: List<Movie>) {
|
fun upsertMovies(movies: List<Movie>) {
|
||||||
|
|||||||
Reference in New Issue
Block a user