mirror of
https://github.com/bbara04/Purefin.git
synced 2026-03-31 17:10:08 +02:00
Fix playback progress updates on exit
This commit is contained in:
@@ -15,8 +15,9 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -80,7 +81,8 @@ private fun SeriesScreenInternal(
|
|||||||
}
|
}
|
||||||
return series.seasons.first()
|
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) {
|
val nextUpEpisode = remember(series) {
|
||||||
series.seasons.firstNotNullOfOrNull { season ->
|
series.seasons.firstNotNullOfOrNull { season ->
|
||||||
season.episodes.firstOrNull { !it.watched }
|
season.episodes.firstOrNull { !it.watched }
|
||||||
@@ -89,8 +91,8 @@ private fun SeriesScreenInternal(
|
|||||||
|
|
||||||
val seriesDownloadState by viewModel.seriesDownloadState.collectAsState()
|
val seriesDownloadState by viewModel.seriesDownloadState.collectAsState()
|
||||||
val seasonDownloadState by viewModel.seasonDownloadState.collectAsState()
|
val seasonDownloadState by viewModel.seasonDownloadState.collectAsState()
|
||||||
LaunchedEffect(selectedSeason.value) {
|
LaunchedEffect(selectedSeason.id, selectedSeason.episodes) {
|
||||||
viewModel.observeSeasonDownloadState(selectedSeason.value.episodes)
|
viewModel.observeSeasonDownloadState(selectedSeason.episodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -133,12 +135,12 @@ private fun SeriesScreenInternal(
|
|||||||
SeriesActionButtons(
|
SeriesActionButtons(
|
||||||
nextUpEpisode = nextUpEpisode,
|
nextUpEpisode = nextUpEpisode,
|
||||||
seriesDownloadState = seriesDownloadState,
|
seriesDownloadState = seriesDownloadState,
|
||||||
selectedSeason = selectedSeason.value,
|
selectedSeason = selectedSeason,
|
||||||
seasonDownloadState = seasonDownloadState,
|
seasonDownloadState = seasonDownloadState,
|
||||||
onDownloadOptionSelected = { option ->
|
onDownloadOptionSelected = { option ->
|
||||||
when (option) {
|
when (option) {
|
||||||
SeriesDownloadOption.SEASON ->
|
SeriesDownloadOption.SEASON ->
|
||||||
viewModel.downloadSeason(selectedSeason.value.episodes)
|
viewModel.downloadSeason(selectedSeason.episodes)
|
||||||
SeriesDownloadOption.SERIES ->
|
SeriesDownloadOption.SERIES ->
|
||||||
viewModel.downloadSeries(series)
|
viewModel.downloadSeries(series)
|
||||||
SeriesDownloadOption.SMART ->
|
SeriesDownloadOption.SMART ->
|
||||||
@@ -157,11 +159,11 @@ private fun SeriesScreenInternal(
|
|||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
SeasonTabs(
|
SeasonTabs(
|
||||||
seasons = series.seasons,
|
seasons = series.seasons,
|
||||||
selectedSeason = selectedSeason.value,
|
selectedSeason = selectedSeason,
|
||||||
onSelect = { selectedSeason.value = it }
|
onSelect = { selectedSeasonId = it.id }
|
||||||
)
|
)
|
||||||
EpisodeCarousel(
|
EpisodeCarousel(
|
||||||
episodes = selectedSeason.value.episodes,
|
episodes = selectedSeason.episodes,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
if(series.cast.isNotEmpty()) {
|
if(series.cast.isNotEmpty()) {
|
||||||
|
|||||||
@@ -235,6 +235,16 @@ class PlayerManager @Inject constructor(
|
|||||||
_playbackState.update { it.copy(error = null) }
|
_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() {
|
private suspend fun applyTrackPreferences() {
|
||||||
val context = currentMediaContext ?: return
|
val context = currentMediaContext ?: return
|
||||||
val preferences = trackPreferencesRepository.getMediaPreferences(context.preferenceKey).firstOrNull() ?: return
|
val preferences = trackPreferencesRepository.getMediaPreferences(context.preferenceKey).firstOrNull() ?: return
|
||||||
@@ -307,15 +317,7 @@ class PlayerManager @Inject constructor(
|
|||||||
private fun startProgressLoop() {
|
private fun startProgressLoop() {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
val duration = player.duration.takeIf { it > 0 } ?: _progress.value.durationMs
|
_progress.value = snapshotProgress()
|
||||||
val position = player.currentPosition
|
|
||||||
val buffered = player.bufferedPosition
|
|
||||||
_progress.value = PlaybackProgressSnapshot(
|
|
||||||
durationMs = duration,
|
|
||||||
positionMs = position,
|
|
||||||
bufferedMs = buffered,
|
|
||||||
isLive = player.isCurrentMediaItemLive
|
|
||||||
)
|
|
||||||
delay(500)
|
delay(500)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ class ProgressManager @Inject constructor(
|
|||||||
private var lastDurationMs: Long = 0L
|
private var lastDurationMs: Long = 0L
|
||||||
private var isPaused: Boolean = false
|
private var isPaused: Boolean = false
|
||||||
|
|
||||||
|
fun syncProgress(snapshot: PlaybackProgressSnapshot) {
|
||||||
|
lastPositionMs = snapshot.positionMs
|
||||||
|
lastDurationMs = snapshot.durationMs
|
||||||
|
}
|
||||||
|
|
||||||
fun bind(
|
fun bind(
|
||||||
playbackState: StateFlow<PlaybackStateSnapshot>,
|
playbackState: StateFlow<PlaybackStateSnapshot>,
|
||||||
progress: StateFlow<PlaybackProgressSnapshot>,
|
progress: StateFlow<PlaybackProgressSnapshot>,
|
||||||
|
|||||||
@@ -251,6 +251,7 @@ class PlayerViewModel @Inject constructor(
|
|||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
autoHideJob?.cancel()
|
autoHideJob?.cancel()
|
||||||
|
progressManager.syncProgress(playerManager.snapshotProgress())
|
||||||
progressManager.release()
|
progressManager.release()
|
||||||
playerManager.release()
|
playerManager.release()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,11 @@ import hu.bbara.purefin.core.model.Movie
|
|||||||
import hu.bbara.purefin.feature.download.DownloadState
|
import hu.bbara.purefin.feature.download.DownloadState
|
||||||
import hu.bbara.purefin.feature.download.MediaDownloadManager
|
import hu.bbara.purefin.feature.download.MediaDownloadManager
|
||||||
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.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jellyfin.sdk.model.UUID
|
import org.jellyfin.sdk.model.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -23,8 +26,14 @@ class MovieScreenViewModel @Inject constructor(
|
|||||||
private val mediaDownloadManager: MediaDownloadManager
|
private val mediaDownloadManager: MediaDownloadManager
|
||||||
): ViewModel() {
|
): ViewModel() {
|
||||||
|
|
||||||
private val _movie = MutableStateFlow<Movie?>(null)
|
private val _movieId = MutableStateFlow<UUID?>(null)
|
||||||
val movie = _movie.asStateFlow()
|
|
||||||
|
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)
|
private val _downloadState = MutableStateFlow<DownloadState>(DownloadState.NotDownloaded)
|
||||||
val downloadState: StateFlow<DownloadState> = _downloadState.asStateFlow()
|
val downloadState: StateFlow<DownloadState> = _downloadState.asStateFlow()
|
||||||
@@ -34,7 +43,7 @@ class MovieScreenViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onPlay() {
|
fun onPlay() {
|
||||||
val id = _movie.value?.id?.toString() ?: return
|
val id = movie.value?.id?.toString() ?: return
|
||||||
navigationManager.navigate(Route.PlayerRoute(mediaId = id))
|
navigationManager.navigate(Route.PlayerRoute(mediaId = id))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,24 +52,16 @@ class MovieScreenViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun selectMovie(movieId: UUID) {
|
fun selectMovie(movieId: UUID) {
|
||||||
|
_movieId.value = movieId
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val movie = mediaRepository.movies.value[movieId]
|
mediaDownloadManager.observeDownloadState(movieId.toString()).collect {
|
||||||
if (movie == null) {
|
_downloadState.value = it
|
||||||
_movie.value = null
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
_movie.value = movie
|
|
||||||
|
|
||||||
launch {
|
|
||||||
mediaDownloadManager.observeDownloadState(movieId.toString()).collect {
|
|
||||||
_downloadState.value = it
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDownloadClick() {
|
fun onDownloadClick() {
|
||||||
val movieId = _movie.value?.id ?: return
|
val movieId = movie.value?.id ?: return
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
when (_downloadState.value) {
|
when (_downloadState.value) {
|
||||||
is DownloadState.NotDownloaded, is DownloadState.Failed -> {
|
is DownloadState.NotDownloaded, is DownloadState.Failed -> {
|
||||||
|
|||||||
Reference in New Issue
Block a user