Fix playback progress updates on exit

This commit is contained in:
Balogh Barnabás
2026-03-24 20:54:07 +01:00
parent ada0e9600a
commit 2e8c864522
5 changed files with 44 additions and 33 deletions

View File

@@ -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<Season>(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()) {

View File

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

View File

@@ -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<PlaybackStateSnapshot>,
progress: StateFlow<PlaybackProgressSnapshot>,

View File

@@ -251,6 +251,7 @@ class PlayerViewModel @Inject constructor(
override fun onCleared() {
super.onCleared()
autoHideJob?.cancel()
progressManager.syncProgress(playerManager.snapshotProgress())
progressManager.release()
playerManager.release()
}

View File

@@ -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<Movie?>(null)
val movie = _movie.asStateFlow()
private val _movieId = MutableStateFlow<UUID?>(null)
val movie: StateFlow<Movie?> = combine(
_movieId,
mediaRepository.movies
) { movieId, movies ->
movieId?.let { movies[it] }
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
private val _downloadState = MutableStateFlow<DownloadState>(DownloadState.NotDownloaded)
val downloadState: StateFlow<DownloadState> = _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 -> {