Show playback debug info

This commit is contained in:
Damontecres 2025-10-04 19:33:21 -04:00
parent 9cc2fc40d3
commit 64e881f51c
No known key found for this signature in database
9 changed files with 177 additions and 28 deletions

View file

@ -187,18 +187,17 @@ sealed interface AppPreference<T> {
summaryOff = R.string.disabled, summaryOff = R.string.disabled,
) )
// val PlaybackDebugInfo = val PlaybackDebugInfo =
// AppSwitchPreference( AppSwitchPreference(
// title = R.string.playback_debug_info, title = R.string.playback_debug_info,
// prefKey = R.string.pref_key_show_playback_debug_info, defaultValue = false,
// defaultValue = false, getter = { it.playbackPreferences.showDebugInfo },
// getter = { it.playbackPreferences.showDebugInfo }, setter = { prefs, value ->
// setter = { prefs, value -> prefs.updatePlaybackPreferences { showDebugInfo = value }
// prefs.updatePlaybackPreferences { showDebugInfo = value } },
// }, summaryOn = R.string.show,
// summaryOn = R.string.show, summaryOff = R.string.hide,
// summaryOff = R.string.hide, )
// )
// val AutoCheckForUpdates = // val AutoCheckForUpdates =
// AppSwitchPreference( // AppSwitchPreference(
@ -246,6 +245,7 @@ val basicPreferences =
AppPreference.RewatchNextUp, AppPreference.RewatchNextUp,
AppPreference.PlayThemeMusic, AppPreference.PlayThemeMusic,
AppPreference.OssLicenseInfo, AppPreference.OssLicenseInfo,
AppPreference.PlaybackDebugInfo,
), ),
), ),
) )

View file

@ -25,6 +25,7 @@ class AppPreferencesSerializer
AppPreference.SkipBack.defaultValue.seconds.inWholeMilliseconds AppPreference.SkipBack.defaultValue.seconds.inWholeMilliseconds
controllerTimeoutMs = AppPreference.ControllerTimeout.defaultValue controllerTimeoutMs = AppPreference.ControllerTimeout.defaultValue
seekBarSteps = AppPreference.SeekBarSteps.defaultValue.toInt() seekBarSteps = AppPreference.SeekBarSteps.defaultValue.toInt()
showDebugInfo = AppPreference.PlaybackDebugInfo.defaultValue
}.build() }.build()
homePagePreferences = homePagePreferences =
HomePagePreferences HomePagePreferences

View file

@ -70,6 +70,7 @@ fun PlaybackContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: PlaybackViewModel = hiltViewModel(), viewModel: PlaybackViewModel = hiltViewModel(),
) { ) {
val prefs = preferences.appPreferences.playbackPreferences
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
LaunchedEffect(destination.itemId) { LaunchedEffect(destination.itemId) {
@ -88,6 +89,7 @@ fun PlaybackContent(
val currentPlayback by viewModel.currentPlayback.observeAsState(null) val currentPlayback by viewModel.currentPlayback.observeAsState(null)
var cues by remember { mutableStateOf<List<Cue>>(listOf()) } var cues by remember { mutableStateOf<List<Cue>>(listOf()) }
var showDebugInfo by remember { mutableStateOf(prefs.showDebugInfo) }
// TODO move to viewmodel? // TODO move to viewmodel?
val cueListener = val cueListener =
@ -246,7 +248,9 @@ fun PlaybackContent(
contentScale = it.scale contentScale = it.scale
} }
PlaybackAction.ShowDebug -> TODO() PlaybackAction.ShowDebug -> {
showDebugInfo = !showDebugInfo
}
PlaybackAction.ShowPlaylist -> TODO() PlaybackAction.ShowPlaylist -> TODO()
PlaybackAction.ShowVideoFilterDialog -> TODO() PlaybackAction.ShowVideoFilterDialog -> TODO()
is PlaybackAction.ToggleAudio -> { is PlaybackAction.ToggleAudio -> {
@ -259,12 +263,11 @@ fun PlaybackContent(
} }
}, },
onSeekBarChange = seekBarState::onValueChange, onSeekBarChange = seekBarState::onValueChange,
showDebugInfo = false, showDebugInfo = showDebugInfo,
scale = contentScale, scale = contentScale,
playbackSpeed = playbackSpeed, playbackSpeed = playbackSpeed,
moreButtonOptions = MoreButtonOptions(mapOf()), moreButtonOptions = MoreButtonOptions(mapOf()),
subtitleIndex = currentPlayback?.subtitleIndex, currentPlayback = currentPlayback,
audioIndex = currentPlayback?.audioIndex,
audioStreams = audioStreams, audioStreams = audioStreams,
trickplayInfo = trickplay, trickplayInfo = trickplay,
trickplayUrlFor = viewModel::getTrickplayUrl, trickplayUrlFor = viewModel::getTrickplayUrl,

View file

@ -289,7 +289,7 @@ fun LeftPlaybackButtons(
val options = val options =
buildList { buildList {
addAll(moreButtonOptions.options.keys) 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( BottomDialog(
choices = options, choices = options,

View file

@ -2,6 +2,7 @@ package com.github.damontecres.dolphin.ui.playback
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -35,9 +36,11 @@ import androidx.media3.common.Player
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text import androidx.tv.material3.Text
import com.github.damontecres.dolphin.data.model.Chapter 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.cards.ChapterCard
import com.github.damontecres.dolphin.ui.ifElse import com.github.damontecres.dolphin.ui.ifElse
import com.github.damontecres.dolphin.ui.isNotNullOrBlank import com.github.damontecres.dolphin.ui.isNotNullOrBlank
import com.github.damontecres.dolphin.ui.letNotEmpty
import org.jellyfin.sdk.model.api.TrickplayInfo import org.jellyfin.sdk.model.api.TrickplayInfo
import kotlin.time.Duration import kotlin.time.Duration
@ -60,8 +63,7 @@ fun PlaybackOverlay(
scale: ContentScale, scale: ContentScale,
playbackSpeed: Float, playbackSpeed: Float,
moreButtonOptions: MoreButtonOptions, moreButtonOptions: MoreButtonOptions,
subtitleIndex: Int?, currentPlayback: CurrentPlayback?,
audioIndex: Int?,
audioStreams: List<AudioStream>, audioStreams: List<AudioStream>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
subtitle: String? = null, subtitle: String? = null,
@ -168,8 +170,8 @@ fun PlaybackOverlay(
seekEnabled = seekEnabled, seekEnabled = seekEnabled,
seekBarInteractionSource = seekBarInteractionSource, seekBarInteractionSource = seekBarInteractionSource,
moreButtonOptions = moreButtonOptions, moreButtonOptions = moreButtonOptions,
subtitleIndex = subtitleIndex, subtitleIndex = currentPlayback?.subtitleIndex,
audioIndex = audioIndex, audioIndex = currentPlayback?.audioIndex,
audioStreams = audioStreams, audioStreams = audioStreams,
playbackSpeed = playbackSpeed, playbackSpeed = playbackSpeed,
scale = scale, 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),
)
}
}
} }
} }

View file

@ -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<TrackSupport>,
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,
)
}
}
}
}
}
}

View file

@ -19,6 +19,8 @@ import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.nav.Destination import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.util.ExceptionHandler import com.github.damontecres.dolphin.util.ExceptionHandler
import com.github.damontecres.dolphin.util.TrackActivityPlaybackListener 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 com.github.damontecres.dolphin.util.profile.PlaybackListener
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
@ -224,6 +226,7 @@ class PlaybackViewModel
allowAudioStreamCopy = true, allowAudioStreamCopy = true,
autoOpenLiveStream = true, autoOpenLiveStream = true,
mediaSourceId = null, mediaSourceId = null,
alwaysBurnInSubtitleWhenTranscoding = false,
), ),
).content ).content
val source = response.mediaSources.firstOrNull() val source = response.mediaSources.firstOrNull()
@ -251,16 +254,19 @@ class PlaybackViewModel
val decision = StreamDecision(itemId, transcodeType, mediaUrl) val decision = StreamDecision(itemId, transcodeType, mediaUrl)
Timber.v("Playback decision: $decision") Timber.v("Playback decision: $decision")
val playback =
CurrentPlayback(
itemId,
audioIndex,
subtitleIndex,
source.id?.toUUIDOrNull(),
listOf(),
)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
duration.value = source.runTimeTicks?.ticks duration.value = source.runTimeTicks?.ticks
stream.value = decision stream.value = decision
currentPlayback.value = currentPlayback.value = playback
CurrentPlayback(
itemId,
audioIndex,
subtitleIndex,
source.id?.toUUIDOrNull(),
)
player.setMediaItem( player.setMediaItem(
decision.mediaItem, decision.mediaItem,
positionMs, positionMs,
@ -277,6 +283,10 @@ class PlaybackViewModel
audioIndex, audioIndex,
subtitleIndex, subtitleIndex,
) )
currentPlayback.value =
currentPlayback.value?.copy(
tracks = checkForSupport(tracks),
)
player.removeListener(this) player.removeListener(this)
} }
} }
@ -338,6 +348,7 @@ data class CurrentPlayback(
val audioIndex: Int?, val audioIndex: Int?,
val subtitleIndex: Int?, val subtitleIndex: Int?,
val mediaSourceId: UUID?, val mediaSourceId: UUID?,
val tracks: List<TrackSupport>,
) )
private val Format.idAsInt: Int? private val Format.idAsInt: Int?

View file

@ -8,6 +8,7 @@ message PlaybackPreferences {
int64 skip_back_ms = 2; int64 skip_back_ms = 2;
int64 controller_timeout_ms = 3; int64 controller_timeout_ms = 3;
int32 seek_bar_steps = 4; int32 seek_bar_steps = 4;
bool show_debug_info = 5;
} }
message HomePagePreferences{ message HomePagePreferences{

View file

@ -44,4 +44,5 @@
<string name="genres">Genres</string> <string name="genres">Genres</string>
<string name="play_theme_music">Play theme music</string> <string name="play_theme_music">Play theme music</string>
<string name="license_info">License information</string> <string name="license_info">License information</string>
<string name="playback_debug_info">Show playback debug info</string>
</resources> </resources>