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.
This commit is contained in:
damontecres 2025-11-08 09:34:12 -05:00 committed by GitHub
parent 7bdf7952c9
commit 762e8f287d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 77 additions and 80 deletions

View file

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

View file

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