Keep activity tracking alive even after errors (#209)

Previously if the activity tracker got an error from the server during
one of the reports, the timer thread would stop and no further reports
would be sent.

This PR fixes that so it will continue to attempt to send reports since
if playback is continuing without error, the report error is likely
transient. For example, I observed the server returning an error during
its DB optimization scheduled task.

I think this will fix #208
This commit is contained in:
damontecres 2025-11-13 11:36:57 -05:00 committed by GitHub
parent 58395e9adf
commit 5061277fd1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -4,10 +4,10 @@ import androidx.annotation.OptIn
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import com.github.damontecres.wholphin.data.model.ItemPlayback import com.github.damontecres.wholphin.data.model.ItemPlayback
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.playback.CurrentPlayback import com.github.damontecres.wholphin.ui.playback.CurrentPlayback
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.playStateApi import org.jellyfin.sdk.api.client.extensions.playStateApi
@ -49,7 +49,7 @@ class TrackActivityPlaybackListener(
private var initialized = false private var initialized = false
fun init() { fun init() {
coroutineScope.launch(Dispatchers.IO + ExceptionHandler()) { launch("reportPlaybackStart") {
Timber.v("reportPlaybackStart for ${itemPlayback.itemId}") Timber.v("reportPlaybackStart for ${itemPlayback.itemId}")
api.playStateApi.reportPlaybackStart( api.playStateApi.reportPlaybackStart(
PlaybackStartInfo( PlaybackStartInfo(
@ -77,7 +77,7 @@ class TrackActivityPlaybackListener(
task.cancel() task.cancel()
TIMER.purge() TIMER.purge()
val position = player.currentPosition.milliseconds val position = player.currentPosition.milliseconds
coroutineScope.launch(Dispatchers.IO + ExceptionHandler()) { launch("reportPlaybackStopped") {
Timber.v("reportPlaybackStopped for ${itemPlayback.itemId} at $position") Timber.v("reportPlaybackStopped for ${itemPlayback.itemId} at $position")
api.playStateApi.reportPlaybackStopped( api.playStateApi.reportPlaybackStopped(
PlaybackStopInfo( PlaybackStopInfo(
@ -107,7 +107,7 @@ class TrackActivityPlaybackListener(
} }
private fun saveActivity(position: Long) { private fun saveActivity(position: Long) {
coroutineScope.launch(Dispatchers.IO + ExceptionHandler()) { launch("saveActivity") {
val calcPosition = val calcPosition =
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
(if (position >= 0) position else player.currentPosition) (if (position >= 0) position else player.currentPosition)
@ -135,6 +135,19 @@ class TrackActivityPlaybackListener(
} }
} }
private fun launch(
name: String,
block: suspend CoroutineScope.() -> Unit,
) {
coroutineScope.launchIO {
try {
block.invoke(this)
} catch (ex: Exception) {
Timber.w(ex, "Exception during %s for %s", name, itemPlayback.itemId)
}
}
}
companion object { companion object {
private const val TAG = "TrackActivityPlaybackListener" private const val TAG = "TrackActivityPlaybackListener"