From 762e8f287d6bc06e3f52113722bd9935e36c0aec Mon Sep 17 00:00:00 2001 From: damontecres <154766448+damontecres@users.noreply.github.com> Date: Sat, 8 Nov 2025 09:34:12 -0500 Subject: [PATCH] When scrubbing show position's time even without preview images (#175) When scrubbing along the seek bar, always show the position's time even if there is no trickplay/preview image to display. --- .../wholphin/ui/playback/PlaybackOverlay.kt | 71 ++++++++------- .../wholphin/ui/playback/SeekPreviewImage.kt | 86 ++++++++----------- 2 files changed, 77 insertions(+), 80 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackOverlay.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackOverlay.kt index 9d050b02..f07d6683 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackOverlay.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackOverlay.kt @@ -342,37 +342,50 @@ fun PlaybackOverlay( (playerControls.currentPosition.toFloat() / playerControls.duration) } } - if (trickplayInfo != null) { - AnimatedVisibility( - seekProgressPercent >= 0 && seekBarFocused, - ) { - val tilesPerImage = trickplayInfo.tileWidth * trickplayInfo.tileHeight - val index = - (seekProgressMs / trickplayInfo.interval).toInt() / tilesPerImage - val imageUrl = trickplayUrlFor(index) - if (imageUrl != null) { - Box( - modifier = - Modifier - .align(Alignment.Center) - .fillMaxWidth(.95f), - ) { - SeekPreviewImage( - modifier = - Modifier - .align(Alignment.BottomStart) - .offsetByPercent( - xPercentage = seekProgressPercent.coerceIn(0f, 1f), - ).padding(bottom = controllerHeight - titleHeight - subtitleHeight), - previewImageUrl = imageUrl, - duration = playerControls.duration, - seekProgressMs = seekProgressMs, - videoWidth = trickplayInfo.width, - videoHeight = trickplayInfo.height, - trickPlayInfo = trickplayInfo, - ) + AnimatedVisibility( + seekProgressPercent >= 0 && seekBarFocused, + ) { + Box( + modifier = + Modifier + .align(Alignment.Center) + .fillMaxWidth(.95f), + ) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + modifier = + Modifier + .align(Alignment.BottomStart) + .offsetByPercent( + xPercentage = seekProgressPercent.coerceIn(0f, 1f), + ).padding(bottom = controllerHeight - titleHeight - subtitleHeight), + ) { + if (trickplayInfo != null) { + val tilesPerImage = trickplayInfo.tileWidth * trickplayInfo.tileHeight + val index = + (seekProgressMs / trickplayInfo.interval).toInt() / tilesPerImage + val imageUrl = trickplayUrlFor(index) + + if (imageUrl != null) { + SeekPreviewImage( + modifier = + Modifier, + previewImageUrl = imageUrl, + duration = playerControls.duration, + seekProgressMs = seekProgressMs, + videoWidth = trickplayInfo.width, + videoHeight = trickplayInfo.height, + trickPlayInfo = trickplayInfo, + ) + } } + Text( + text = (seekProgressMs / 1000L).seconds.toString(), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.labelLarge, + ) } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/SeekPreviewImage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/SeekPreviewImage.kt index 6f59d455..1f060b15 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/SeekPreviewImage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/SeekPreviewImage.kt @@ -2,12 +2,9 @@ package com.github.damontecres.wholphin.ui.playback import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter @@ -17,7 +14,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.tv.material3.MaterialTheme -import androidx.tv.material3.Text import coil3.compose.AsyncImage import coil3.request.CachePolicy import coil3.request.ImageRequest @@ -25,7 +21,6 @@ import coil3.request.transformations import com.github.damontecres.wholphin.ui.CoilTrickplayTransformation import com.github.damontecres.wholphin.ui.isNotNullOrBlank import org.jellyfin.sdk.model.api.TrickplayInfo -import kotlin.time.Duration.Companion.seconds fun Modifier.offsetByPercent( xPercentage: Float, @@ -85,55 +80,44 @@ fun SeekPreviewImage( ) { val context = LocalContext.current - Column( - modifier = modifier, - verticalArrangement = Arrangement.spacedBy(8.dp), - horizontalAlignment = Alignment.CenterHorizontally, + if (previewImageUrl.isNotNullOrBlank() && + videoWidth != null && + videoHeight != null ) { - if (previewImageUrl.isNotNullOrBlank() && - videoWidth != null && - videoHeight != null - ) { - val height = 160.dp - val width = height * (videoWidth.toFloat() / videoHeight) - val heightPx = with(LocalDensity.current) { height.toPx().toInt() } - val widthPx = with(LocalDensity.current) { width.toPx().toInt() } + val height = 160.dp + val width = height * (videoWidth.toFloat() / videoHeight) + val heightPx = with(LocalDensity.current) { height.toPx().toInt() } + val widthPx = with(LocalDensity.current) { width.toPx().toInt() } - val index = (seekProgressMs.toDouble() / trickPlayInfo.interval).toInt() // Which tile - val numberOfTitlesPerImage = trickPlayInfo.tileHeight * trickPlayInfo.tileWidth - val imageIndex = index % numberOfTitlesPerImage + val index = (seekProgressMs.toDouble() / trickPlayInfo.interval).toInt() // Which tile + val numberOfTitlesPerImage = trickPlayInfo.tileHeight * trickPlayInfo.tileWidth + val imageIndex = index % numberOfTitlesPerImage - AsyncImage( - modifier = - Modifier - .width(width) - .height(height) - .background(Color.Black) - .border(1.5.dp, color = MaterialTheme.colorScheme.border), - model = - ImageRequest - .Builder(context) - .data(previewImageUrl) - .memoryCachePolicy(CachePolicy.ENABLED) - .transformations( - CoilTrickplayTransformation( - widthPx, - heightPx, - trickPlayInfo.tileHeight, - trickPlayInfo.tileWidth, - imageIndex, - index, - ), - ).build(), - contentScale = ContentScale.None, - contentDescription = null, - placeholder = placeHolder, - ) - } - Text( - text = (seekProgressMs / 1000L).seconds.toString(), - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.labelLarge, + AsyncImage( + modifier = + modifier + .width(width) + .height(height) + .background(Color.Black) + .border(1.5.dp, color = MaterialTheme.colorScheme.border), + model = + ImageRequest + .Builder(context) + .data(previewImageUrl) + .memoryCachePolicy(CachePolicy.ENABLED) + .transformations( + CoilTrickplayTransformation( + widthPx, + heightPx, + trickPlayInfo.tileHeight, + trickPlayInfo.tileWidth, + imageIndex, + index, + ), + ).build(), + contentScale = ContentScale.None, + contentDescription = null, + placeholder = placeHolder, ) } }