diff --git a/app/src/main/java/hu/bbara/purefin/app/content/series/SeriesScreen.kt b/app/src/main/java/hu/bbara/purefin/app/content/series/SeriesScreen.kt index 5ea6294..9d4cb90 100644 --- a/app/src/main/java/hu/bbara/purefin/app/content/series/SeriesScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/app/content/series/SeriesScreen.kt @@ -15,8 +15,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -80,7 +81,8 @@ private fun SeriesScreenInternal( } return series.seasons.first() } - val selectedSeason = remember { mutableStateOf(getDefaultSeason()) } + var selectedSeasonId by remember(series.id) { mutableStateOf(getDefaultSeason().id) } + val selectedSeason = series.seasons.firstOrNull { it.id == selectedSeasonId } ?: getDefaultSeason() val nextUpEpisode = remember(series) { series.seasons.firstNotNullOfOrNull { season -> season.episodes.firstOrNull { !it.watched } @@ -89,8 +91,8 @@ private fun SeriesScreenInternal( val seriesDownloadState by viewModel.seriesDownloadState.collectAsState() val seasonDownloadState by viewModel.seasonDownloadState.collectAsState() - LaunchedEffect(selectedSeason.value) { - viewModel.observeSeasonDownloadState(selectedSeason.value.episodes) + LaunchedEffect(selectedSeason.id, selectedSeason.episodes) { + viewModel.observeSeasonDownloadState(selectedSeason.episodes) } Scaffold( @@ -133,12 +135,12 @@ private fun SeriesScreenInternal( SeriesActionButtons( nextUpEpisode = nextUpEpisode, seriesDownloadState = seriesDownloadState, - selectedSeason = selectedSeason.value, + selectedSeason = selectedSeason, seasonDownloadState = seasonDownloadState, onDownloadOptionSelected = { option -> when (option) { SeriesDownloadOption.SEASON -> - viewModel.downloadSeason(selectedSeason.value.episodes) + viewModel.downloadSeason(selectedSeason.episodes) SeriesDownloadOption.SERIES -> viewModel.downloadSeries(series) SeriesDownloadOption.SMART -> @@ -157,11 +159,11 @@ private fun SeriesScreenInternal( Spacer(modifier = Modifier.height(24.dp)) SeasonTabs( seasons = series.seasons, - selectedSeason = selectedSeason.value, - onSelect = { selectedSeason.value = it } + selectedSeason = selectedSeason, + onSelect = { selectedSeasonId = it.id } ) EpisodeCarousel( - episodes = selectedSeason.value.episodes, + episodes = selectedSeason.episodes, ) Spacer(modifier = Modifier.height(16.dp)) if(series.cast.isNotEmpty()) { diff --git a/core/player/src/main/java/hu/bbara/purefin/core/player/manager/PlayerManager.kt b/core/player/src/main/java/hu/bbara/purefin/core/player/manager/PlayerManager.kt index 295e70f..e006dc2 100644 --- a/core/player/src/main/java/hu/bbara/purefin/core/player/manager/PlayerManager.kt +++ b/core/player/src/main/java/hu/bbara/purefin/core/player/manager/PlayerManager.kt @@ -235,6 +235,16 @@ class PlayerManager @Inject constructor( _playbackState.update { it.copy(error = null) } } + fun snapshotProgress(): PlaybackProgressSnapshot { + val duration = player.duration.takeIf { it > 0 } ?: _progress.value.durationMs + return PlaybackProgressSnapshot( + durationMs = duration, + positionMs = player.currentPosition, + bufferedMs = player.bufferedPosition, + isLive = player.isCurrentMediaItemLive + ) + } + private suspend fun applyTrackPreferences() { val context = currentMediaContext ?: return val preferences = trackPreferencesRepository.getMediaPreferences(context.preferenceKey).firstOrNull() ?: return @@ -307,15 +317,7 @@ class PlayerManager @Inject constructor( private fun startProgressLoop() { scope.launch { while (isActive) { - val duration = player.duration.takeIf { it > 0 } ?: _progress.value.durationMs - val position = player.currentPosition - val buffered = player.bufferedPosition - _progress.value = PlaybackProgressSnapshot( - durationMs = duration, - positionMs = position, - bufferedMs = buffered, - isLive = player.isCurrentMediaItemLive - ) + _progress.value = snapshotProgress() delay(500) } } diff --git a/core/player/src/main/java/hu/bbara/purefin/core/player/manager/ProgressManager.kt b/core/player/src/main/java/hu/bbara/purefin/core/player/manager/ProgressManager.kt index 8597b1b..ec71152 100644 --- a/core/player/src/main/java/hu/bbara/purefin/core/player/manager/ProgressManager.kt +++ b/core/player/src/main/java/hu/bbara/purefin/core/player/manager/ProgressManager.kt @@ -29,6 +29,11 @@ class ProgressManager @Inject constructor( private var lastDurationMs: Long = 0L private var isPaused: Boolean = false + fun syncProgress(snapshot: PlaybackProgressSnapshot) { + lastPositionMs = snapshot.positionMs + lastDurationMs = snapshot.durationMs + } + fun bind( playbackState: StateFlow, progress: StateFlow, diff --git a/core/player/src/main/java/hu/bbara/purefin/core/player/viewmodel/PlayerViewModel.kt b/core/player/src/main/java/hu/bbara/purefin/core/player/viewmodel/PlayerViewModel.kt index a8d965b..f693265 100644 --- a/core/player/src/main/java/hu/bbara/purefin/core/player/viewmodel/PlayerViewModel.kt +++ b/core/player/src/main/java/hu/bbara/purefin/core/player/viewmodel/PlayerViewModel.kt @@ -251,6 +251,7 @@ class PlayerViewModel @Inject constructor( override fun onCleared() { super.onCleared() autoHideJob?.cancel() + progressManager.syncProgress(playerManager.snapshotProgress()) progressManager.release() playerManager.release() } diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/content/movie/MovieScreenViewModel.kt b/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/content/movie/MovieScreenViewModel.kt index b43f947..5714a8b 100644 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/content/movie/MovieScreenViewModel.kt +++ b/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/content/movie/MovieScreenViewModel.kt @@ -10,8 +10,11 @@ import hu.bbara.purefin.core.model.Movie import hu.bbara.purefin.feature.download.DownloadState import hu.bbara.purefin.feature.download.MediaDownloadManager 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.launch import org.jellyfin.sdk.model.UUID import javax.inject.Inject @@ -23,8 +26,14 @@ class MovieScreenViewModel @Inject constructor( private val mediaDownloadManager: MediaDownloadManager ): ViewModel() { - private val _movie = MutableStateFlow(null) - val movie = _movie.asStateFlow() + private val _movieId = MutableStateFlow(null) + + val movie: StateFlow = combine( + _movieId, + mediaRepository.movies + ) { movieId, movies -> + movieId?.let { movies[it] } + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null) private val _downloadState = MutableStateFlow(DownloadState.NotDownloaded) val downloadState: StateFlow = _downloadState.asStateFlow() @@ -34,7 +43,7 @@ class MovieScreenViewModel @Inject constructor( } fun onPlay() { - val id = _movie.value?.id?.toString() ?: return + val id = movie.value?.id?.toString() ?: return navigationManager.navigate(Route.PlayerRoute(mediaId = id)) } @@ -43,24 +52,16 @@ class MovieScreenViewModel @Inject constructor( } fun selectMovie(movieId: UUID) { + _movieId.value = movieId viewModelScope.launch { - val movie = mediaRepository.movies.value[movieId] - if (movie == null) { - _movie.value = null - return@launch - } - _movie.value = movie - - launch { - mediaDownloadManager.observeDownloadState(movieId.toString()).collect { - _downloadState.value = it - } + mediaDownloadManager.observeDownloadState(movieId.toString()).collect { + _downloadState.value = it } } } fun onDownloadClick() { - val movieId = _movie.value?.id ?: return + val movieId = movie.value?.id ?: return viewModelScope.launch { when (_downloadState.value) { is DownloadState.NotDownloaded, is DownloadState.Failed -> {