Fix TV episode navigation and live resume freshness
This commit is contained in:
parent
54c23e076a
commit
8e40ac0869
34 changed files with 793 additions and 101 deletions
|
|
@ -24,6 +24,13 @@ class NavigationManager
|
|||
log()
|
||||
}
|
||||
|
||||
/** Replace only the current page, preserving every parent entry. */
|
||||
fun replaceTop(destination: Destination) {
|
||||
if (backStack.isEmpty()) backStack.add(destination)
|
||||
else backStack[backStack.lastIndex] = destination
|
||||
log()
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the specified [Destination], but reset the back stack to Home first
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -82,9 +82,11 @@ class PlayExternalViewModel
|
|||
) : ViewModel() {
|
||||
val launched = savedStateHandle.getMutableStateFlow("launched", false)
|
||||
val state = MutableStateFlow(PlayExternalState())
|
||||
private var watchScope: String? = null
|
||||
|
||||
fun init(destination: Destination) {
|
||||
Timber.v("init called: %s", destination)
|
||||
watchScope = "${serverRepository.currentServer?.id}:${serverRepository.currentUser?.id}"
|
||||
state.update { it.copy(loading = LoadingState.Loading) }
|
||||
viewModelScope.launchDefault {
|
||||
val prefs = userPreferencesService.getCurrent()
|
||||
|
|
@ -358,6 +360,7 @@ class PlayExternalViewModel
|
|||
failed = false,
|
||||
),
|
||||
)
|
||||
watchScope?.let { com.github.damontecres.wholphin.util.PlaybackWatchState.stopped(context, it) }
|
||||
}
|
||||
} else {
|
||||
Timber.w(
|
||||
|
|
|
|||
|
|
@ -830,10 +830,12 @@ class PlaybackViewModel
|
|||
}
|
||||
|
||||
val playbackItemState = PlaybackItemState(playback)
|
||||
val watchScope = "${serverRepository.currentServer?.id}:${serverRepository.currentUser?.id}"
|
||||
val activityListener =
|
||||
TrackActivityPlaybackListener(
|
||||
api = api,
|
||||
player = player,
|
||||
onStopped = { com.github.damontecres.wholphin.util.PlaybackWatchState.stopped(context, watchScope) },
|
||||
getState = { playbackItemState },
|
||||
)
|
||||
player.addListener(activityListener)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package com.github.damontecres.wholphin.util
|
||||
|
||||
import android.content.Context
|
||||
import org.justinvisser.wolfskin.conductarr.FileCacheStore
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import timber.log.Timber
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
/** Only an acknowledged stop invalidates the captured server/user's watch projections. */
|
||||
object PlaybackWatchState {
|
||||
private val mutableRevisions = MutableStateFlow<Map<String, Long>>(emptyMap())
|
||||
val revisions = mutableRevisions.asStateFlow()
|
||||
|
||||
/** Remove persisted watch grids before publishing the captured user's acknowledgement. */
|
||||
fun stopped(context: Context, scope: String) {
|
||||
try {
|
||||
FileCacheStore(File(context.filesDir, "wolfskin-library-cache")).clear(scope)
|
||||
} catch (exception: IOException) {
|
||||
Timber.w(exception, "Could not invalidate playback watch snapshots")
|
||||
}
|
||||
val time = System.currentTimeMillis()
|
||||
mutableRevisions.update { previous -> previous + (scope to maxOf(time, (previous[scope] ?: 0L) + 1L)) }
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,8 @@ import com.github.damontecres.wholphin.ui.launchIO
|
|||
import com.github.damontecres.wholphin.ui.playback.CurrentPlayback
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.playStateApi
|
||||
import org.jellyfin.sdk.model.api.PlayMethod
|
||||
|
|
@ -31,9 +33,12 @@ import kotlin.time.Duration.Companion.seconds
|
|||
class TrackActivityPlaybackListener(
|
||||
private val api: ApiClient,
|
||||
private val player: Player,
|
||||
private val onStopped: () -> Unit = {},
|
||||
private val getState: () -> PlaybackItemState?,
|
||||
) : Player.Listener {
|
||||
private val coroutineScope = CoroutineScope(WholphinDispatchers.Main)
|
||||
private val reports = Mutex()
|
||||
@Volatile private var released = false
|
||||
private val task: TimerTask =
|
||||
object : TimerTask() {
|
||||
override fun run() {
|
||||
|
|
@ -70,19 +75,23 @@ class TrackActivityPlaybackListener(
|
|||
|
||||
val delay = 5.seconds.inWholeMilliseconds
|
||||
// Every x seconds, check if the video is playing
|
||||
TIMER.schedule(task, delay, delay)
|
||||
initialized = true
|
||||
if (!released) {
|
||||
TIMER.schedule(task, delay, delay)
|
||||
initialized = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun release() {
|
||||
// player.removeListener(this)
|
||||
if (released) return
|
||||
released = true
|
||||
task.cancel()
|
||||
TIMER.purge()
|
||||
val position = player.currentPosition.milliseconds
|
||||
val stoppedState = getState.invoke()
|
||||
launch("reportPlaybackStopped") {
|
||||
getState.invoke()?.let { state ->
|
||||
stoppedState?.let { state ->
|
||||
Timber.v("reportPlaybackStopped for ${state.itemId} at $position")
|
||||
if (position < Duration.ZERO) {
|
||||
Timber.w("Negative position when reporting playback stopped: %s", position)
|
||||
|
|
@ -96,6 +105,7 @@ class TrackActivityPlaybackListener(
|
|||
liveStreamId = state.liveStreamId,
|
||||
),
|
||||
)
|
||||
onStopped()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -117,6 +127,7 @@ class TrackActivityPlaybackListener(
|
|||
|
||||
private fun saveActivity(position: Long) {
|
||||
launch("saveActivity") {
|
||||
if (released) return@launch
|
||||
getState.invoke()?.let { state ->
|
||||
val calcPosition =
|
||||
withContext(WholphinDispatchers.Main) {
|
||||
|
|
@ -152,7 +163,7 @@ class TrackActivityPlaybackListener(
|
|||
) {
|
||||
coroutineScope.launchIO {
|
||||
try {
|
||||
block.invoke(this)
|
||||
reports.withLock { block.invoke(this) }
|
||||
} catch (ex: Exception) {
|
||||
Timber.w(ex, "Exception during %s", name)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue