From 64e881f51c72894e58eb312414f0519cbba99b45 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Sat, 4 Oct 2025 19:33:21 -0400 Subject: [PATCH] Show playback debug info --- .../dolphin/preferences/AppPreference.kt | 24 ++-- .../preferences/AppPreferencesSerializer.kt | 1 + .../dolphin/ui/playback/PlaybackContent.kt | 11 +- .../dolphin/ui/playback/PlaybackControls.kt | 2 +- .../dolphin/ui/playback/PlaybackOverlay.kt | 27 ++++- .../dolphin/ui/playback/PlaybackTrackInfo.kt | 113 ++++++++++++++++++ .../dolphin/ui/playback/PlaybackViewModel.kt | 25 ++-- app/src/main/proto/DolphinDataStore.proto | 1 + app/src/main/res/values/strings.xml | 1 + 9 files changed, 177 insertions(+), 28 deletions(-) create mode 100644 app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackTrackInfo.kt diff --git a/app/src/main/java/com/github/damontecres/dolphin/preferences/AppPreference.kt b/app/src/main/java/com/github/damontecres/dolphin/preferences/AppPreference.kt index 59a3afd0..14efc50b 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/preferences/AppPreference.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/preferences/AppPreference.kt @@ -187,18 +187,17 @@ sealed interface AppPreference { summaryOff = R.string.disabled, ) -// val PlaybackDebugInfo = -// AppSwitchPreference( -// title = R.string.playback_debug_info, -// prefKey = R.string.pref_key_show_playback_debug_info, -// defaultValue = false, -// getter = { it.playbackPreferences.showDebugInfo }, -// setter = { prefs, value -> -// prefs.updatePlaybackPreferences { showDebugInfo = value } -// }, -// summaryOn = R.string.show, -// summaryOff = R.string.hide, -// ) + val PlaybackDebugInfo = + AppSwitchPreference( + title = R.string.playback_debug_info, + defaultValue = false, + getter = { it.playbackPreferences.showDebugInfo }, + setter = { prefs, value -> + prefs.updatePlaybackPreferences { showDebugInfo = value } + }, + summaryOn = R.string.show, + summaryOff = R.string.hide, + ) // val AutoCheckForUpdates = // AppSwitchPreference( @@ -246,6 +245,7 @@ val basicPreferences = AppPreference.RewatchNextUp, AppPreference.PlayThemeMusic, AppPreference.OssLicenseInfo, + AppPreference.PlaybackDebugInfo, ), ), ) diff --git a/app/src/main/java/com/github/damontecres/dolphin/preferences/AppPreferencesSerializer.kt b/app/src/main/java/com/github/damontecres/dolphin/preferences/AppPreferencesSerializer.kt index 77e1bf0f..524b9651 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/preferences/AppPreferencesSerializer.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/preferences/AppPreferencesSerializer.kt @@ -25,6 +25,7 @@ class AppPreferencesSerializer AppPreference.SkipBack.defaultValue.seconds.inWholeMilliseconds controllerTimeoutMs = AppPreference.ControllerTimeout.defaultValue seekBarSteps = AppPreference.SeekBarSteps.defaultValue.toInt() + showDebugInfo = AppPreference.PlaybackDebugInfo.defaultValue }.build() homePagePreferences = HomePagePreferences diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackContent.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackContent.kt index a2184bba..52f53c0b 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackContent.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackContent.kt @@ -70,6 +70,7 @@ fun PlaybackContent( modifier: Modifier = Modifier, viewModel: PlaybackViewModel = hiltViewModel(), ) { + val prefs = preferences.appPreferences.playbackPreferences val context = LocalContext.current val scope = rememberCoroutineScope() LaunchedEffect(destination.itemId) { @@ -88,6 +89,7 @@ fun PlaybackContent( val currentPlayback by viewModel.currentPlayback.observeAsState(null) var cues by remember { mutableStateOf>(listOf()) } + var showDebugInfo by remember { mutableStateOf(prefs.showDebugInfo) } // TODO move to viewmodel? val cueListener = @@ -246,7 +248,9 @@ fun PlaybackContent( contentScale = it.scale } - PlaybackAction.ShowDebug -> TODO() + PlaybackAction.ShowDebug -> { + showDebugInfo = !showDebugInfo + } PlaybackAction.ShowPlaylist -> TODO() PlaybackAction.ShowVideoFilterDialog -> TODO() is PlaybackAction.ToggleAudio -> { @@ -259,12 +263,11 @@ fun PlaybackContent( } }, onSeekBarChange = seekBarState::onValueChange, - showDebugInfo = false, + showDebugInfo = showDebugInfo, scale = contentScale, playbackSpeed = playbackSpeed, moreButtonOptions = MoreButtonOptions(mapOf()), - subtitleIndex = currentPlayback?.subtitleIndex, - audioIndex = currentPlayback?.audioIndex, + currentPlayback = currentPlayback, audioStreams = audioStreams, trickplayInfo = trickplay, trickplayUrlFor = viewModel::getTrickplayUrl, diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackControls.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackControls.kt index c613f765..32ff20d5 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackControls.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackControls.kt @@ -289,7 +289,7 @@ fun LeftPlaybackButtons( val options = buildList { addAll(moreButtonOptions.options.keys) - add(if (showDebugInfo) "Hide transcode info" else "Show transcode info") + add(if (showDebugInfo) "Hide debug info" else "Show debug info") } BottomDialog( choices = options, diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackOverlay.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackOverlay.kt index 44f2b38e..5d58b2b0 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackOverlay.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackOverlay.kt @@ -2,6 +2,7 @@ package com.github.damontecres.dolphin.ui.playback import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.layout.Arrangement @@ -35,9 +36,11 @@ import androidx.media3.common.Player import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import com.github.damontecres.dolphin.data.model.Chapter +import com.github.damontecres.dolphin.ui.AppColors import com.github.damontecres.dolphin.ui.cards.ChapterCard import com.github.damontecres.dolphin.ui.ifElse import com.github.damontecres.dolphin.ui.isNotNullOrBlank +import com.github.damontecres.dolphin.ui.letNotEmpty import org.jellyfin.sdk.model.api.TrickplayInfo import kotlin.time.Duration @@ -60,8 +63,7 @@ fun PlaybackOverlay( scale: ContentScale, playbackSpeed: Float, moreButtonOptions: MoreButtonOptions, - subtitleIndex: Int?, - audioIndex: Int?, + currentPlayback: CurrentPlayback?, audioStreams: List, modifier: Modifier = Modifier, subtitle: String? = null, @@ -168,8 +170,8 @@ fun PlaybackOverlay( seekEnabled = seekEnabled, seekBarInteractionSource = seekBarInteractionSource, moreButtonOptions = moreButtonOptions, - subtitleIndex = subtitleIndex, - audioIndex = audioIndex, + subtitleIndex = currentPlayback?.subtitleIndex, + audioIndex = currentPlayback?.audioIndex, audioStreams = audioStreams, playbackSpeed = playbackSpeed, scale = scale, @@ -276,5 +278,22 @@ fun PlaybackOverlay( } } } + AnimatedVisibility( + showDebugInfo && controllerViewState.controlsVisible, + modifier = + Modifier + .align(Alignment.TopStart), + ) { + currentPlayback?.tracks?.letNotEmpty { + PlaybackTrackInfo( + trackSupport = it, + modifier = + Modifier + .align(Alignment.TopStart) + .padding(16.dp) + .background(AppColors.TransparentBlack50), + ) + } + } } } diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackTrackInfo.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackTrackInfo.kt new file mode 100644 index 00000000..1e60c681 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackTrackInfo.kt @@ -0,0 +1,113 @@ +package com.github.damontecres.dolphin.ui.playback + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.tv.material3.Icon +import androidx.tv.material3.MaterialTheme +import androidx.tv.material3.Text +import com.github.damontecres.dolphin.util.TrackSupport + +@Composable +fun PlaybackTrackInfo( + trackSupport: List, + modifier: Modifier = Modifier, +) { + val selectedWeight = .5f + val weights = listOf(.25f, .4f, .5f, 1f, 1f) + val textStyle = + MaterialTheme.typography.bodySmall.copy(color = MaterialTheme.colorScheme.onBackground) + + LazyColumn( + modifier = modifier, + ) { + item { + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier, + ) { + val texts = + listOf( + "ID", + "Type", + "Codec", + "Supported", + "Labels", + ) + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.weight(selectedWeight), + ) { + Text( + text = "Selected", + style = textStyle, + ) + } + texts.forEachIndexed { index, text -> + Box( + contentAlignment = Alignment.CenterStart, + modifier = Modifier.weight(weights[index]), + ) { + Text( + text = text, + style = textStyle, + ) + } + } + } + } + items(trackSupport) { track -> + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier, + ) { + val texts = + listOf( + track.id ?: "", + track.type.name, + track.codecs ?: "", + track.supported.name, + track.labels.joinToString(", "), + ) + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.weight(selectedWeight), + ) { + if (track.selected) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = null, + tint = MaterialTheme.colorScheme.onBackground, + modifier = Modifier.size(12.dp), + ) + } else { + Text( + text = "-", + style = textStyle, + ) + } + } + texts.forEachIndexed { index, text -> + Box( + contentAlignment = Alignment.CenterStart, + modifier = Modifier.weight(weights[index]), + ) { + Text( + text = text, + style = textStyle, + ) + } + } + } + } + } +} diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackViewModel.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackViewModel.kt index 33ed8bf8..b8786ec0 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackViewModel.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackViewModel.kt @@ -19,6 +19,8 @@ import com.github.damontecres.dolphin.preferences.UserPreferences import com.github.damontecres.dolphin.ui.nav.Destination import com.github.damontecres.dolphin.util.ExceptionHandler import com.github.damontecres.dolphin.util.TrackActivityPlaybackListener +import com.github.damontecres.dolphin.util.TrackSupport +import com.github.damontecres.dolphin.util.checkForSupport import com.github.damontecres.dolphin.util.profile.PlaybackListener import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext @@ -224,6 +226,7 @@ class PlaybackViewModel allowAudioStreamCopy = true, autoOpenLiveStream = true, mediaSourceId = null, + alwaysBurnInSubtitleWhenTranscoding = false, ), ).content val source = response.mediaSources.firstOrNull() @@ -251,16 +254,19 @@ class PlaybackViewModel val decision = StreamDecision(itemId, transcodeType, mediaUrl) Timber.v("Playback decision: $decision") + val playback = + CurrentPlayback( + itemId, + audioIndex, + subtitleIndex, + source.id?.toUUIDOrNull(), + listOf(), + ) + withContext(Dispatchers.Main) { duration.value = source.runTimeTicks?.ticks stream.value = decision - currentPlayback.value = - CurrentPlayback( - itemId, - audioIndex, - subtitleIndex, - source.id?.toUUIDOrNull(), - ) + currentPlayback.value = playback player.setMediaItem( decision.mediaItem, positionMs, @@ -277,6 +283,10 @@ class PlaybackViewModel audioIndex, subtitleIndex, ) + currentPlayback.value = + currentPlayback.value?.copy( + tracks = checkForSupport(tracks), + ) player.removeListener(this) } } @@ -338,6 +348,7 @@ data class CurrentPlayback( val audioIndex: Int?, val subtitleIndex: Int?, val mediaSourceId: UUID?, + val tracks: List, ) private val Format.idAsInt: Int? diff --git a/app/src/main/proto/DolphinDataStore.proto b/app/src/main/proto/DolphinDataStore.proto index c88f9e0c..dd5d8842 100644 --- a/app/src/main/proto/DolphinDataStore.proto +++ b/app/src/main/proto/DolphinDataStore.proto @@ -8,6 +8,7 @@ message PlaybackPreferences { int64 skip_back_ms = 2; int64 controller_timeout_ms = 3; int32 seek_bar_steps = 4; + bool show_debug_info = 5; } message HomePagePreferences{ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fb473082..3d921620 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,4 +44,5 @@ Genres Play theme music License information + Show playback debug info