mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Improvement to playback activity tracking
This commit is contained in:
parent
29e13cba0f
commit
763589fa0d
3 changed files with 68 additions and 33 deletions
|
|
@ -12,7 +12,9 @@ import androidx.compose.foundation.layout.Arrangement
|
|||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
|
|
@ -375,8 +377,10 @@ fun Controller(
|
|||
Text(
|
||||
text = "Chapters",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
modifier = Modifier.padding(start = 16.dp, top = 16.dp),
|
||||
)
|
||||
} else {
|
||||
Spacer(Modifier.height(32.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ import org.jellyfin.sdk.model.api.MediaSegmentDto
|
|||
import org.jellyfin.sdk.model.api.MediaSegmentType
|
||||
import org.jellyfin.sdk.model.api.MediaSourceInfo
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import org.jellyfin.sdk.model.api.PlayMethod
|
||||
import org.jellyfin.sdk.model.api.PlaybackInfoDto
|
||||
import org.jellyfin.sdk.model.api.SubtitlePlaybackMode
|
||||
import org.jellyfin.sdk.model.api.TrickplayInfo
|
||||
|
|
@ -74,7 +75,7 @@ enum class TranscodeType {
|
|||
|
||||
data class StreamDecision(
|
||||
val itemId: UUID,
|
||||
val type: TranscodeType,
|
||||
val type: PlayMethod,
|
||||
val url: String,
|
||||
)
|
||||
|
||||
|
|
@ -188,7 +189,7 @@ class PlaybackViewModel
|
|||
}?.sortedWith(compareBy<AudioStream> { it.language }.thenByDescending { it.channels })
|
||||
.orEmpty()
|
||||
|
||||
// TODO audio selection based on channel layout, etc
|
||||
// TODO audio selection based on channel layout or preferences or default
|
||||
val audioLanguage = preferences.userConfig.audioLanguagePreference
|
||||
val audioIndex =
|
||||
if (audioLanguage != null) {
|
||||
|
|
@ -236,22 +237,14 @@ class PlaybackViewModel
|
|||
SubtitlePlaybackMode.NONE -> null
|
||||
}
|
||||
|
||||
Timber.v("base.mediaStreams=${base.mediaStreams}")
|
||||
Timber.v("subtitleTracks=$subtitleStreams")
|
||||
Timber.v("audioStreams=$audioStreams")
|
||||
// Timber.v("base.mediaStreams=${base.mediaStreams}")
|
||||
// Timber.v("subtitleTracks=$subtitleStreams")
|
||||
// Timber.v("audioStreams=$audioStreams")
|
||||
Timber.d("Selected audioIndex=$audioIndex, subtitleIndex=$subtitleIndex")
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
this@PlaybackViewModel.audioStreams.value = audioStreams
|
||||
this@PlaybackViewModel.subtitleStreams.value = subtitleStreams
|
||||
this@PlaybackViewModel.activityListener?.let {
|
||||
it.release()
|
||||
player.removeListener(it)
|
||||
}
|
||||
val activityListener =
|
||||
TrackActivityPlaybackListener(api, itemId, player)
|
||||
player.addListener(activityListener)
|
||||
this@PlaybackViewModel.activityListener = activityListener
|
||||
|
||||
changeStreams(
|
||||
itemId,
|
||||
|
|
@ -328,9 +321,9 @@ class PlaybackViewModel
|
|||
}
|
||||
val transcodeType =
|
||||
when {
|
||||
source.supportsDirectPlay -> TranscodeType.DIRECT_PLAY
|
||||
source.supportsDirectStream -> TranscodeType.DIRECT_STREAM
|
||||
source.supportsTranscoding -> TranscodeType.TRANSCODE
|
||||
source.supportsDirectPlay -> PlayMethod.DIRECT_PLAY
|
||||
source.supportsDirectStream -> PlayMethod.DIRECT_STREAM
|
||||
source.supportsTranscoding -> PlayMethod.TRANSCODE
|
||||
else -> throw Exception("No supported playback method")
|
||||
}
|
||||
val decision = StreamDecision(itemId, transcodeType, mediaUrl)
|
||||
|
|
@ -370,9 +363,24 @@ class PlaybackViewModel
|
|||
subtitleIndex,
|
||||
source.id?.toUUIDOrNull(),
|
||||
listOf(),
|
||||
playMethod = transcodeType,
|
||||
playSessionId = response.playSessionId,
|
||||
)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
this@PlaybackViewModel.activityListener?.let {
|
||||
it.release()
|
||||
player.removeListener(it)
|
||||
}
|
||||
val activityListener =
|
||||
TrackActivityPlaybackListener(
|
||||
api = api,
|
||||
player = player,
|
||||
playback = playback,
|
||||
)
|
||||
player.addListener(activityListener)
|
||||
this@PlaybackViewModel.activityListener = activityListener
|
||||
|
||||
duration.value = source.runTimeTicks?.ticks
|
||||
stream.value = decision
|
||||
currentPlayback.value = playback
|
||||
|
|
@ -581,6 +589,8 @@ data class CurrentPlayback(
|
|||
val subtitleIndex: Int?,
|
||||
val mediaSourceId: UUID?,
|
||||
val tracks: List<TrackSupport>,
|
||||
val playMethod: PlayMethod,
|
||||
val playSessionId: String?,
|
||||
)
|
||||
|
||||
private val Format.idAsInt: Int?
|
||||
|
|
|
|||
|
|
@ -3,21 +3,22 @@ package com.github.damontecres.dolphin.util
|
|||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.github.damontecres.dolphin.ui.playback.CurrentPlayback
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
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.PlaybackProgressInfo
|
||||
import org.jellyfin.sdk.model.api.PlaybackStartInfo
|
||||
import org.jellyfin.sdk.model.api.PlaybackStopInfo
|
||||
import org.jellyfin.sdk.model.api.RepeatMode
|
||||
import org.jellyfin.sdk.model.extensions.inWholeTicks
|
||||
import timber.log.Timber
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
|
@ -29,8 +30,8 @@ import kotlin.time.Duration.Companion.seconds
|
|||
@OptIn(UnstableApi::class)
|
||||
class TrackActivityPlaybackListener(
|
||||
private val api: ApiClient,
|
||||
private val itemId: UUID,
|
||||
private val player: Player,
|
||||
var playback: CurrentPlayback,
|
||||
) : Player.Listener {
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.Main)
|
||||
private val task: TimerTask
|
||||
|
|
@ -41,6 +42,21 @@ class TrackActivityPlaybackListener(
|
|||
private var incrementedPlayCount = AtomicBoolean(false)
|
||||
|
||||
init {
|
||||
coroutineScope.launch(Dispatchers.IO + ExceptionHandler()) {
|
||||
api.playStateApi.reportPlaybackStart(
|
||||
PlaybackStartInfo(
|
||||
canSeek = true,
|
||||
itemId = playback.itemId,
|
||||
isPaused = withContext(Dispatchers.Main) { !player.isPlaying },
|
||||
playMethod = playback.playMethod,
|
||||
repeatMode = RepeatMode.REPEAT_NONE,
|
||||
playbackOrder = PlaybackOrder.DEFAULT,
|
||||
isMuted = false,
|
||||
audioStreamIndex = playback.audioIndex,
|
||||
subtitleStreamIndex = playback.subtitleIndex,
|
||||
),
|
||||
)
|
||||
}
|
||||
val saveActivityInterval = 10.seconds.inWholeMilliseconds
|
||||
val delay = 1.seconds.inWholeMilliseconds
|
||||
// Every x seconds, check if the video is playing
|
||||
|
|
@ -74,12 +90,13 @@ class TrackActivityPlaybackListener(
|
|||
fun release() {
|
||||
task.cancel()
|
||||
TIMER.purge()
|
||||
coroutineScope.launch(ExceptionHandler()) {
|
||||
coroutineScope.launch(Dispatchers.IO + ExceptionHandler()) {
|
||||
api.playStateApi.reportPlaybackStopped(
|
||||
PlaybackStopInfo(
|
||||
itemId = itemId,
|
||||
positionTicks = player.currentPosition.milliseconds.inWholeTicks,
|
||||
itemId = playback.itemId,
|
||||
positionTicks = withContext(Dispatchers.Main) { player.currentPosition.milliseconds.inWholeTicks },
|
||||
failed = false,
|
||||
playSessionId = playback.playSessionId,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -98,28 +115,32 @@ class TrackActivityPlaybackListener(
|
|||
|
||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||
if (playbackState == Player.STATE_ENDED) {
|
||||
Timber.Forest.v("onPlaybackStateChanged STATE_ENDED")
|
||||
// TODO mark as watched
|
||||
Timber.v("onPlaybackStateChanged STATE_ENDED")
|
||||
saveActivity(player.duration)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveActivity(position: Long) {
|
||||
coroutineScope.launch(ExceptionHandler()) {
|
||||
val totalDuration = totalPlayDurationMilliseconds.get()
|
||||
coroutineScope.launch(Dispatchers.IO + ExceptionHandler()) {
|
||||
// val totalDuration = totalPlayDurationMilliseconds.get()
|
||||
val calcPosition =
|
||||
(if (position >= 0) position else player.currentPosition).milliseconds
|
||||
Timber.Forest.v("saveActivity: itemId=$itemId, pos=$calcPosition")
|
||||
// TODO parameters
|
||||
withContext(Dispatchers.Main) {
|
||||
(if (position >= 0) position else player.currentPosition).milliseconds
|
||||
}
|
||||
// Timber.v("saveActivity: itemId=$itemId, pos=$calcPosition")
|
||||
api.playStateApi.reportPlaybackProgress(
|
||||
PlaybackProgressInfo(
|
||||
itemId = itemId,
|
||||
itemId = playback.itemId,
|
||||
positionTicks = calcPosition.inWholeTicks,
|
||||
canSeek = true,
|
||||
isPaused = !player.isPlaying,
|
||||
isPaused = withContext(Dispatchers.Main) { !player.isPlaying },
|
||||
isMuted = false,
|
||||
playMethod = PlayMethod.DIRECT_PLAY,
|
||||
playMethod = playback.playMethod,
|
||||
repeatMode = RepeatMode.REPEAT_NONE,
|
||||
playbackOrder = PlaybackOrder.DEFAULT,
|
||||
playSessionId = playback.playSessionId,
|
||||
audioStreamIndex = playback.audioIndex,
|
||||
subtitleStreamIndex = playback.subtitleIndex,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue