Make TrackActivityPlaybackListener more generic

This commit is contained in:
Damontecres 2026-03-16 13:30:25 -04:00
parent 9badecf086
commit 6c26891bff
No known key found for this signature in database
2 changed files with 49 additions and 23 deletions

View file

@ -63,6 +63,7 @@ import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.ui.toServerString import com.github.damontecres.wholphin.ui.toServerString
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.PlaybackItemState
import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener
import com.github.damontecres.wholphin.util.checkForSupport import com.github.damontecres.wholphin.util.checkForSupport
import com.github.damontecres.wholphin.util.mpv.mpvDeviceProfile import com.github.damontecres.wholphin.util.mpv.mpvDeviceProfile
@ -737,12 +738,12 @@ class PlaybackViewModel
player.removeListener(it) player.removeListener(it)
} }
val playbackItemState = PlaybackItemState(playback, currentItemPlayback)
val activityListener = val activityListener =
TrackActivityPlaybackListener( TrackActivityPlaybackListener(
api = api, api = api,
player = player, player = player,
playback = playback, getState = { playbackItemState },
itemPlayback = currentItemPlayback,
) )
player.addListener(activityListener) player.addListener(activityListener)
this@PlaybackViewModel.activityListener = activityListener this@PlaybackViewModel.activityListener = activityListener

View file

@ -11,6 +11,7 @@ import kotlinx.coroutines.Dispatchers
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
import org.jellyfin.sdk.model.api.PlayMethod
import org.jellyfin.sdk.model.api.PlaybackOrder import org.jellyfin.sdk.model.api.PlaybackOrder
import org.jellyfin.sdk.model.api.PlaybackProgressInfo import org.jellyfin.sdk.model.api.PlaybackProgressInfo
import org.jellyfin.sdk.model.api.PlaybackStartInfo import org.jellyfin.sdk.model.api.PlaybackStartInfo
@ -20,6 +21,7 @@ import org.jellyfin.sdk.model.extensions.inWholeTicks
import timber.log.Timber import timber.log.Timber
import java.util.Timer import java.util.Timer
import java.util.TimerTask import java.util.TimerTask
import java.util.UUID
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@ -30,8 +32,7 @@ import kotlin.time.Duration.Companion.seconds
class TrackActivityPlaybackListener( class TrackActivityPlaybackListener(
private val api: ApiClient, private val api: ApiClient,
private val player: Player, private val player: Player,
val playback: CurrentPlayback, private val getState: () -> PlaybackItemState,
val itemPlayback: ItemPlayback,
) : Player.Listener { ) : Player.Listener {
private val coroutineScope = CoroutineScope(Dispatchers.Main) private val coroutineScope = CoroutineScope(Dispatchers.Main)
private val task: TimerTask = private val task: TimerTask =
@ -50,20 +51,21 @@ class TrackActivityPlaybackListener(
fun init() { fun init() {
launch("reportPlaybackStart") { launch("reportPlaybackStart") {
Timber.v("reportPlaybackStart for ${itemPlayback.itemId}") val state = getState.invoke()
Timber.v("reportPlaybackStart for ${state.itemId}")
api.playStateApi.reportPlaybackStart( api.playStateApi.reportPlaybackStart(
PlaybackStartInfo( PlaybackStartInfo(
canSeek = true, canSeek = true,
itemId = itemPlayback.itemId, itemId = state.itemId,
isPaused = withContext(Dispatchers.Main) { !player.isPlaying }, isPaused = withContext(Dispatchers.Main) { !player.isPlaying },
playMethod = playback.playMethod, playMethod = state.playMethod,
repeatMode = RepeatMode.REPEAT_NONE, repeatMode = RepeatMode.REPEAT_NONE,
playbackOrder = PlaybackOrder.DEFAULT, playbackOrder = PlaybackOrder.DEFAULT,
isMuted = false, isMuted = false,
audioStreamIndex = itemPlayback.audioIndex.takeIf { itemPlayback.audioIndexEnabled }, audioStreamIndex = state.audioStreamIndex,
subtitleStreamIndex = itemPlayback.subtitleIndex.takeIf { itemPlayback.subtitleIndexEnabled }, subtitleStreamIndex = state.subtitleStreamIndex,
playSessionId = playback.playSessionId, playSessionId = state.playSessionId,
liveStreamId = playback.liveStreamId, liveStreamId = state.liveStreamId,
), ),
) )
val delay = 5.seconds.inWholeMilliseconds val delay = 5.seconds.inWholeMilliseconds
@ -78,14 +80,15 @@ class TrackActivityPlaybackListener(
TIMER.purge() TIMER.purge()
val position = player.currentPosition.milliseconds val position = player.currentPosition.milliseconds
launch("reportPlaybackStopped") { launch("reportPlaybackStopped") {
Timber.v("reportPlaybackStopped for ${itemPlayback.itemId} at $position") val state = getState.invoke()
Timber.v("reportPlaybackStopped for ${state.itemId} at $position")
api.playStateApi.reportPlaybackStopped( api.playStateApi.reportPlaybackStopped(
PlaybackStopInfo( PlaybackStopInfo(
itemId = itemPlayback.itemId, itemId = state.itemId,
positionTicks = position.inWholeTicks, positionTicks = position.inWholeTicks,
failed = false, failed = false,
playSessionId = playback.playSessionId, playSessionId = state.playSessionId,
liveStreamId = playback.liveStreamId, liveStreamId = state.liveStreamId,
), ),
) )
} }
@ -108,27 +111,28 @@ class TrackActivityPlaybackListener(
private fun saveActivity(position: Long) { private fun saveActivity(position: Long) {
launch("saveActivity") { launch("saveActivity") {
val state = getState.invoke()
val calcPosition = val calcPosition =
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
(if (position >= 0) position else player.currentPosition) (if (position >= 0) position else player.currentPosition)
} }
if (calcPosition > 0) { if (calcPosition > 0) {
val isPaused = withContext(Dispatchers.Main) { !player.isPlaying } val isPaused = withContext(Dispatchers.Main) { !player.isPlaying }
Timber.v("saveActivity: itemId=${itemPlayback.itemId}, pos=$calcPosition") Timber.v("saveActivity: itemId=${state.itemId}, pos=$calcPosition")
api.playStateApi.reportPlaybackProgress( api.playStateApi.reportPlaybackProgress(
PlaybackProgressInfo( PlaybackProgressInfo(
itemId = itemPlayback.itemId, itemId = state.itemId,
positionTicks = calcPosition.milliseconds.inWholeTicks, positionTicks = calcPosition.milliseconds.inWholeTicks,
canSeek = true, canSeek = true,
isPaused = isPaused, isPaused = isPaused,
isMuted = false, isMuted = false,
playMethod = playback.playMethod, playMethod = state.playMethod,
repeatMode = RepeatMode.REPEAT_NONE, repeatMode = RepeatMode.REPEAT_NONE,
playbackOrder = PlaybackOrder.DEFAULT, playbackOrder = PlaybackOrder.DEFAULT,
audioStreamIndex = itemPlayback.audioIndex.takeIf { itemPlayback.audioIndexEnabled }, audioStreamIndex = state.audioStreamIndex,
subtitleStreamIndex = itemPlayback.subtitleIndex.takeIf { itemPlayback.subtitleIndexEnabled }, subtitleStreamIndex = state.subtitleStreamIndex,
playSessionId = playback.playSessionId, playSessionId = state.playSessionId,
liveStreamId = playback.liveStreamId, liveStreamId = state.liveStreamId,
), ),
) )
} }
@ -143,7 +147,7 @@ class TrackActivityPlaybackListener(
try { try {
block.invoke(this) block.invoke(this)
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.w(ex, "Exception during %s for %s", name, itemPlayback.itemId) Timber.w(ex, "Exception during %s", name)
} }
} }
} }
@ -154,3 +158,24 @@ class TrackActivityPlaybackListener(
private val TIMER by lazy { Timer("$TAG-timer", true) } private val TIMER by lazy { Timer("$TAG-timer", true) }
} }
} }
data class PlaybackItemState(
val itemId: UUID,
val playMethod: PlayMethod,
val audioStreamIndex: Int? = null,
val subtitleStreamIndex: Int? = null,
val playSessionId: String? = null,
val liveStreamId: String? = null,
) {
constructor(
playback: CurrentPlayback,
itemPlayback: ItemPlayback,
) : this(
itemId = itemPlayback.itemId,
playMethod = playback.playMethod,
audioStreamIndex = itemPlayback.audioIndex.takeIf { itemPlayback.audioIndexEnabled },
subtitleStreamIndex = itemPlayback.subtitleIndex.takeIf { itemPlayback.subtitleIndexEnabled },
playSessionId = playback.playSessionId,
liveStreamId = playback.liveStreamId,
)
}