Fix & refactor some image aspect ratios (#222)

Fix image aspect ratios for episode cards that differ from the video's
aspect ratio.

Also use the video's aspect ratio for chapter cards instead of assuming
16x9.

Dev: Create constants for the main ratios used

Fixes #215
This commit is contained in:
damontecres 2025-11-14 16:13:18 -05:00 committed by GitHub
parent 6a912c0af3
commit 72fb732e63
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 72 additions and 42 deletions

View file

@ -74,6 +74,12 @@ object Cards {
val playedPercentHeight = 6.dp val playedPercentHeight = 6.dp
} }
object AspectRatios {
const val WIDE = 16f / 9f
const val FOUR_THREE = 4f / 3f
const val TALL = 2f / 3f
}
@Preview( @Preview(
device = "spec:parent=tv_1080p", device = "spec:parent=tv_1080p",
backgroundColor = 0xFF383535, backgroundColor = 0xFF383535,

View file

@ -34,6 +34,7 @@ import androidx.tv.material3.Text
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.ui.AppColors import com.github.damontecres.wholphin.ui.AppColors
import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.Cards import com.github.damontecres.wholphin.ui.Cards
import com.github.damontecres.wholphin.ui.FontAwesome import com.github.damontecres.wholphin.ui.FontAwesome
import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.isNotNullOrBlank
@ -53,7 +54,7 @@ fun BannerCard(
favorite: Boolean = false, favorite: Boolean = false,
playPercent: Double = 0.0, playPercent: Double = 0.0,
cardHeight: Dp = 140.dp * .85f, cardHeight: Dp = 140.dp * .85f,
aspectRatio: Float = 16f / 9, aspectRatio: Float = AspectRatios.WIDE,
interactionSource: MutableInteractionSource? = null, interactionSource: MutableInteractionSource? = null,
) { ) {
var imageError by remember { mutableStateOf(false) } var imageError by remember { mutableStateOf(false) }

View file

@ -30,9 +30,9 @@ fun ChapterCard(
position: Duration, position: Duration,
imageUrl: String?, imageUrl: String?,
onClick: () -> Unit, onClick: () -> Unit,
aspectRatio: Float,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
cardHeight: Dp = 140.dp * .85f, cardHeight: Dp = 140.dp * .85f,
aspectRatio: Float = 16f / 9,
onLongClick: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null,
interactionSource: MutableInteractionSource? = null, interactionSource: MutableInteractionSource? = null,
) { ) {

View file

@ -21,6 +21,7 @@ import com.github.damontecres.wholphin.data.model.Chapter
@Composable @Composable
fun ChapterRow( fun ChapterRow(
chapters: List<Chapter>, chapters: List<Chapter>,
aspectRatio: Float,
onClick: (Chapter) -> Unit, onClick: (Chapter) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onLongClick: ((Chapter) -> Unit)? = null, onLongClick: ((Chapter) -> Unit)? = null,
@ -50,6 +51,7 @@ fun ChapterRow(
position = item.position, position = item.position,
imageUrl = item.imageUrl, imageUrl = item.imageUrl,
onClick = { onClick(item) }, onClick = { onClick(item) },
aspectRatio = aspectRatio,
modifier = Modifier, modifier = Modifier,
onLongClick = onLongClick?.let { { it(item) } }, onLongClick = onLongClick?.let { { it(item) } },
) )

View file

@ -30,6 +30,7 @@ import androidx.tv.material3.CardDefaults
import androidx.tv.material3.Text import androidx.tv.material3.Text
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.ui.AppColors import com.github.damontecres.wholphin.ui.AppColors
import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.enableMarquee import com.github.damontecres.wholphin.ui.enableMarquee
import com.github.damontecres.wholphin.ui.seasonEpisode import com.github.damontecres.wholphin.ui.seasonEpisode
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -63,7 +64,7 @@ fun EpisodeCard(
} else { } else {
focusedAfterDelay = false focusedAfterDelay = false
} }
val aspectRatio = dto?.primaryImageAspectRatio?.toFloat() ?: (2f / 3f) val aspectRatio = dto?.primaryImageAspectRatio?.toFloat() ?: AspectRatios.TALL
val width = imageHeight * aspectRatio val width = imageHeight * aspectRatio
val height = imageWidth * (1f / aspectRatio) val height = imageWidth * (1f / aspectRatio)
Column( Column(

View file

@ -25,6 +25,7 @@ import androidx.tv.material3.CardDefaults
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text import androidx.tv.material3.Text
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.enableMarquee import com.github.damontecres.wholphin.ui.enableMarquee
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -38,7 +39,7 @@ fun GridCard(
onLongClick: () -> Unit, onLongClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
imageAspectRatio: Float = 2f / 3f, imageAspectRatio: Float = AspectRatios.TALL,
) { ) {
val dto = item?.data val dto = item?.data
val focused by interactionSource.collectIsFocusedAsState() val focused by interactionSource.collectIsFocusedAsState()

View file

@ -22,6 +22,7 @@ import androidx.compose.ui.unit.dp
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text import androidx.tv.material3.Text
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.util.FocusPair import com.github.damontecres.wholphin.util.FocusPair
@Composable @Composable
@ -112,7 +113,8 @@ fun BannerItemRow(
name = title, name = title,
imageUrl = item?.imageUrl, imageUrl = item?.imageUrl,
aspectRatio = aspectRatio =
aspectRatioOverride ?: item?.data?.primaryImageAspectRatio?.toFloat() ?: (16f / 9), aspectRatioOverride ?: item?.data?.primaryImageAspectRatio?.toFloat()
?: AspectRatios.WIDE,
cornerText = item?.data?.indexNumber?.let { "E$it" }, cornerText = item?.data?.indexNumber?.let { "E$it" },
played = item?.data?.userData?.played ?: false, played = item?.data?.userData?.played ?: false,
playPercent = item?.data?.userData?.playedPercentage ?: 0.0, playPercent = item?.data?.userData?.playedPercentage ?: 0.0,

View file

@ -22,6 +22,7 @@ import androidx.tv.material3.Card
import androidx.tv.material3.CardDefaults import androidx.tv.material3.CardDefaults
import androidx.tv.material3.Text import androidx.tv.material3.Text
import com.github.damontecres.wholphin.data.model.Person import com.github.damontecres.wholphin.data.model.Person
import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.enableMarquee import com.github.damontecres.wholphin.ui.enableMarquee
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -81,7 +82,7 @@ fun PersonCard(
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(2f / 3f), // TODO, .aspectRatio(AspectRatios.TALL), // TODO,
) )
} }
Column( Column(

View file

@ -26,6 +26,7 @@ import androidx.tv.material3.Card
import androidx.tv.material3.CardDefaults import androidx.tv.material3.CardDefaults
import androidx.tv.material3.Text import androidx.tv.material3.Text
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.enableMarquee import com.github.damontecres.wholphin.ui.enableMarquee
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -42,7 +43,7 @@ fun SeasonCard(
imageWidth: Dp = Dp.Unspecified, imageWidth: Dp = Dp.Unspecified,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
showImageOverlay: Boolean = false, showImageOverlay: Boolean = false,
aspectRatio: Float = item?.data?.primaryImageAspectRatio?.toFloat() ?: (2f / 3f), aspectRatio: Float = item?.data?.primaryImageAspectRatio?.toFloat() ?: AspectRatios.TALL,
) = SeasonCard( ) = SeasonCard(
title = item?.title, title = item?.title,
subtitle = item?.subtitle, subtitle = item?.subtitle,
@ -82,7 +83,7 @@ fun SeasonCard(
imageWidth: Dp = Dp.Unspecified, imageWidth: Dp = Dp.Unspecified,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
showImageOverlay: Boolean = false, showImageOverlay: Boolean = false,
aspectRatio: Float = (2f / 3f), aspectRatio: Float = AspectRatios.TALL,
) { ) {
val focused by interactionSource.collectIsFocusedAsState() val focused by interactionSource.collectIsFocusedAsState()
val spaceBetween by animateDpAsState(if (focused) 12.dp else 4.dp) val spaceBetween by animateDpAsState(if (focused) 12.dp else 4.dp)
@ -102,7 +103,6 @@ fun SeasonCard(
} else { } else {
focusedAfterDelay = false focusedAfterDelay = false
} }
// val aspectRatio = dto?.primaryImageAspectRatio?.toFloat() ?: (2f / 3f)
val width = imageHeight * aspectRatio val width = imageHeight * aspectRatio
val height = imageWidth * (1f / aspectRatio) val height = imageWidth * (1f / aspectRatio)
Column( Column(

View file

@ -51,9 +51,11 @@ import com.github.damontecres.wholphin.data.model.LocalTrailer
import com.github.damontecres.wholphin.data.model.Person import com.github.damontecres.wholphin.data.model.Person
import com.github.damontecres.wholphin.data.model.RemoteTrailer import com.github.damontecres.wholphin.data.model.RemoteTrailer
import com.github.damontecres.wholphin.data.model.Trailer import com.github.damontecres.wholphin.data.model.Trailer
import com.github.damontecres.wholphin.data.model.aspectRatioFloat
import com.github.damontecres.wholphin.data.model.chooseSource import com.github.damontecres.wholphin.data.model.chooseSource
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.TrailerService import com.github.damontecres.wholphin.services.TrailerService
import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.Cards import com.github.damontecres.wholphin.ui.Cards
import com.github.damontecres.wholphin.ui.cards.ChapterRow import com.github.damontecres.wholphin.ui.cards.ChapterRow
import com.github.damontecres.wholphin.ui.cards.ItemRow import com.github.damontecres.wholphin.ui.cards.ItemRow
@ -486,6 +488,7 @@ fun MovieDetailsContent(
item { item {
ChapterRow( ChapterRow(
chapters = chapters, chapters = chapters,
aspectRatio = movie.data.aspectRatioFloat ?: AspectRatios.WIDE,
onClick = { onClick = {
position = CHAPTER_ROW position = CHAPTER_ROW
playOnClick.invoke(it.position) playOnClick.invoke(it.position)

View file

@ -42,8 +42,8 @@ import coil3.compose.AsyncImage
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.ChosenStreams import com.github.damontecres.wholphin.data.ChosenStreams
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.aspectRatioFloat
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
import com.github.damontecres.wholphin.ui.cards.BannerCard import com.github.damontecres.wholphin.ui.cards.BannerCard
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
@ -216,10 +216,8 @@ fun SeriesOverviewContent(
?.data ?.data
?.primaryImageAspectRatio ?.primaryImageAspectRatio
?.toFloat() ?.toFloat()
?.coerceAtLeast( ?.coerceAtLeast(AspectRatios.FOUR_THREE)
episode.data.aspectRatioFloat ?: (4f / 3f), ?: (AspectRatios.WIDE),
)
?: (16f / 9), // TODO some episode images don't match the file's aspect ratio
cornerText = cornerText, cornerText = cornerText,
played = episode?.data?.userData?.played ?: false, played = episode?.data?.userData?.played ?: false,
playPercent = playPercent =

View file

@ -46,6 +46,7 @@ import coil3.compose.AsyncImage
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.Cards import com.github.damontecres.wholphin.ui.Cards
import com.github.damontecres.wholphin.ui.cards.BannerCard import com.github.damontecres.wholphin.ui.cards.BannerCard
import com.github.damontecres.wholphin.ui.cards.ItemRow import com.github.damontecres.wholphin.ui.cards.ItemRow
@ -353,11 +354,10 @@ fun HomePageContent(
.fillMaxWidth() .fillMaxWidth()
.animateItem(), .animateItem(),
cardContent = { index, item, cardModifier, onClick, onLongClick -> cardContent = { index, item, cardModifier, onClick, onLongClick ->
// TODO better aspect ration handling?
BannerCard( BannerCard(
name = item?.data?.seriesName ?: item?.name, name = item?.data?.seriesName ?: item?.name,
imageUrl = item?.imageUrl, imageUrl = item?.imageUrl,
aspectRatio = (2f / 3f), aspectRatio = AspectRatios.TALL,
cornerText = cornerText =
item?.data?.indexNumber?.let { "E$it" } item?.data?.indexNumber?.let { "E$it" }
?: item?.data?.childCount?.let { if (it > 0) it.toString() else null }, ?: item?.data?.childCount?.let { if (it > 0) it.toString() else null },

View file

@ -45,6 +45,7 @@ import coil3.compose.AsyncImagePreviewHandler
import coil3.compose.LocalAsyncImagePreviewHandler import coil3.compose.LocalAsyncImagePreviewHandler
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.ui.AppColors import com.github.damontecres.wholphin.ui.AppColors
import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.PreviewTvSpec import com.github.damontecres.wholphin.ui.PreviewTvSpec
import com.github.damontecres.wholphin.ui.theme.WholphinTheme import com.github.damontecres.wholphin.ui.theme.WholphinTheme
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
@ -62,7 +63,7 @@ fun NextUpEpisode(
onClick: () -> Unit, onClick: () -> Unit,
timeLeft: Duration?, timeLeft: Duration?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
aspectRatio: Float = 16f / 9, aspectRatio: Float = AspectRatios.WIDE,
) { ) {
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
@ -202,7 +203,7 @@ private fun NextUpEpisodePreview() {
"But it just keeps going and going.", "But it just keeps going and going.",
imageUrl = "", imageUrl = "",
onClick = {}, onClick = {},
aspectRatio = 4f / 3, aspectRatio = AspectRatios.FOUR_THREE,
timeLeft = 30.seconds, timeLeft = 30.seconds,
modifier = modifier =
Modifier Modifier

View file

@ -50,7 +50,9 @@ import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.Chapter import com.github.damontecres.wholphin.data.model.Chapter
import com.github.damontecres.wholphin.data.model.ItemPlayback import com.github.damontecres.wholphin.data.model.ItemPlayback
import com.github.damontecres.wholphin.data.model.Playlist import com.github.damontecres.wholphin.data.model.Playlist
import com.github.damontecres.wholphin.data.model.aspectRatioFloat
import com.github.damontecres.wholphin.ui.AppColors import com.github.damontecres.wholphin.ui.AppColors
import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.TimeFormatter import com.github.damontecres.wholphin.ui.TimeFormatter
import com.github.damontecres.wholphin.ui.cards.ChapterCard import com.github.damontecres.wholphin.ui.cards.ChapterCard
import com.github.damontecres.wholphin.ui.cards.SeasonCard import com.github.damontecres.wholphin.ui.cards.SeasonCard
@ -75,6 +77,7 @@ private val subtitleTextSize = 18.sp
*/ */
@Composable @Composable
fun PlaybackOverlay( fun PlaybackOverlay(
item: BaseItem?,
title: String?, title: String?,
subtitleStreams: List<SubtitleStream>, subtitleStreams: List<SubtitleStream>,
chapters: List<Chapter>, chapters: List<Chapter>,
@ -236,6 +239,7 @@ fun PlaybackOverlay(
name = chapter.name, name = chapter.name,
position = chapter.position, position = chapter.position,
imageUrl = chapter.imageUrl, imageUrl = chapter.imageUrl,
aspectRatio = item?.data?.aspectRatioFloat ?: AspectRatios.WIDE,
onClick = { onClick = {
playerControls.seekTo(chapter.position.inWholeMilliseconds) playerControls.seekTo(chapter.position.inWholeMilliseconds)
controllerViewState.hideControls() controllerViewState.hideControls()

View file

@ -67,6 +67,7 @@ import com.github.damontecres.wholphin.data.model.ItemPlayback
import com.github.damontecres.wholphin.data.model.Playlist import com.github.damontecres.wholphin.data.model.Playlist
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.preferences.skipBackOnResume import com.github.damontecres.wholphin.preferences.skipBackOnResume
import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.LoadingPage import com.github.damontecres.wholphin.ui.components.LoadingPage
@ -320,6 +321,7 @@ fun PlaybackPage(
.padding(WindowInsets.systemBars.asPaddingValues()) .padding(WindowInsets.systemBars.asPaddingValues())
.fillMaxSize() .fillMaxSize()
.background(Color.Transparent), .background(Color.Transparent),
item = currentPlayback?.item,
title = title, title = title,
subtitle = subtitle, subtitle = subtitle,
subtitleStreams = subtitleStreams, subtitleStreams = subtitleStreams,
@ -476,7 +478,9 @@ fun PlaybackPage(
).joinToString(" - "), ).joinToString(" - "),
description = it.data.overview, description = it.data.overview,
imageUrl = it.imageUrl, imageUrl = it.imageUrl,
aspectRatio = it.data.primaryImageAspectRatio?.toFloat() ?: (16f / 9), aspectRatio =
it.data.primaryImageAspectRatio?.toFloat()
?: AspectRatios.WIDE,
onClick = { onClick = {
viewModel.reportInteraction() viewModel.reportInteraction()
viewModel.playUpNextUp() viewModel.playUpNextUp()

View file

@ -73,7 +73,6 @@ import org.jellyfin.sdk.api.client.extensions.trickplayApi
import org.jellyfin.sdk.api.client.extensions.userLibraryApi import org.jellyfin.sdk.api.client.extensions.userLibraryApi
import org.jellyfin.sdk.api.client.extensions.videosApi import org.jellyfin.sdk.api.client.extensions.videosApi
import org.jellyfin.sdk.api.sockets.subscribe import org.jellyfin.sdk.api.sockets.subscribe
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.DeviceProfile import org.jellyfin.sdk.model.api.DeviceProfile
import org.jellyfin.sdk.model.api.MediaSegmentDto import org.jellyfin.sdk.model.api.MediaSegmentDto
@ -138,7 +137,7 @@ class PlaybackViewModel
private lateinit var preferences: UserPreferences private lateinit var preferences: UserPreferences
private lateinit var deviceProfile: DeviceProfile private lateinit var deviceProfile: DeviceProfile
private lateinit var itemId: UUID private lateinit var itemId: UUID
private lateinit var dto: BaseItemDto private lateinit var item: BaseItem
private var activityListener: TrackActivityPlaybackListener? = null private var activityListener: TrackActivityPlaybackListener? = null
val nextUp = MutableLiveData<BaseItem?>() val nextUp = MutableLiveData<BaseItem?>()
@ -200,10 +199,11 @@ class PlaybackViewModel
} else { } else {
queriedItem queriedItem
} }
val item = BaseItem.from(base, api)
val played = val played =
play( play(
base, item,
destination.positionMs, destination.positionMs,
destination.itemPlayback, destination.itemPlayback,
destination.forceTranscoding, destination.forceTranscoding,
@ -224,25 +224,25 @@ class PlaybackViewModel
} }
private suspend fun play( private suspend fun play(
base: BaseItemDto, item: BaseItem,
positionMs: Long, positionMs: Long,
itemPlayback: ItemPlayback? = null, itemPlayback: ItemPlayback? = null,
forceTranscoding: Boolean = false, forceTranscoding: Boolean = false,
): Boolean = ): Boolean =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
Timber.i("Playing ${base.id}") Timber.i("Playing ${item.id}")
autoSkippedSegments.clear() autoSkippedSegments.clear()
if (base.type !in supportItemKinds) { if (item.type !in supportItemKinds) {
showToast( showToast(
context, context,
"Unsupported type '${base.type}', skipping...", "Unsupported type '${item.type}', skipping...",
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT,
) )
return@withContext false return@withContext false
} }
val isLiveTv = base.type == BaseItemKind.TV_CHANNEL val isLiveTv = item.type == BaseItemKind.TV_CHANNEL
dto = base val base = item.data
val title = val title =
if (base.type == BaseItemKind.EPISODE) { if (base.type == BaseItemKind.EPISODE) {
base.seriesName base.seriesName
@ -354,7 +354,7 @@ class PlaybackViewModel
this@PlaybackViewModel.subtitleStreams.value = subtitleStreams this@PlaybackViewModel.subtitleStreams.value = subtitleStreams
changeStreams( changeStreams(
base, item,
itemPlaybackToUse, itemPlaybackToUse,
audioIndex, audioIndex,
subtitleIndex, subtitleIndex,
@ -374,7 +374,7 @@ class PlaybackViewModel
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
private suspend fun changeStreams( private suspend fun changeStreams(
item: BaseItemDto, item: BaseItem,
currentItemPlayback: ItemPlayback = this@PlaybackViewModel.currentItemPlayback.value!!, currentItemPlayback: ItemPlayback = this@PlaybackViewModel.currentItemPlayback.value!!,
audioIndex: Int?, audioIndex: Int?,
subtitleIndex: Int?, subtitleIndex: Int?,
@ -495,7 +495,8 @@ class PlaybackViewModel
val playback = val playback =
CurrentPlayback( CurrentPlayback(
listOf(), item = item,
tracks = listOf(),
playMethod = transcodeType, playMethod = transcodeType,
playSessionId = response.playSessionId, playSessionId = response.playSessionId,
liveStreamId = source.liveStreamId, liveStreamId = source.liveStreamId,
@ -563,7 +564,7 @@ class PlaybackViewModel
} }
} }
val trickPlayInfo = val trickPlayInfo =
item.trickplay item.data.trickplay
?.get(source.id) ?.get(source.id)
?.values ?.values
?.firstOrNull() ?.firstOrNull()
@ -577,7 +578,7 @@ class PlaybackViewModel
fun changeAudioStream(index: Int) { fun changeAudioStream(index: Int) {
viewModelScope.launchIO { viewModelScope.launchIO {
changeStreams( changeStreams(
dto, item,
currentItemPlayback.value!!, currentItemPlayback.value!!,
index, index,
currentItemPlayback.value?.subtitleIndex, currentItemPlayback.value?.subtitleIndex,
@ -590,7 +591,7 @@ class PlaybackViewModel
fun changeSubtitleStream(index: Int?): Job = fun changeSubtitleStream(index: Int?): Job =
viewModelScope.launchIO { viewModelScope.launchIO {
changeStreams( changeStreams(
dto, item,
currentItemPlayback.value!!, currentItemPlayback.value!!,
currentItemPlayback.value?.audioIndex, currentItemPlayback.value?.audioIndex,
index, index,
@ -600,7 +601,7 @@ class PlaybackViewModel
} }
fun getTrickplayUrl(index: Int): String? { fun getTrickplayUrl(index: Int): String? {
val itemId = dto.id val itemId = item.id
val mediaSourceId = currentItemPlayback.value?.sourceId val mediaSourceId = currentItemPlayback.value?.sourceId
val trickPlayInfo = trickplay.value ?: return null val trickPlayInfo = trickplay.value ?: return null
return api.trickplayApi.getTrickplayTileImageUrl( return api.trickplayApi.getTrickplayTileImageUrl(
@ -732,7 +733,7 @@ class PlaybackViewModel
viewModelScope.launchIO { viewModelScope.launchIO {
cancelUpNextEpisode() cancelUpNextEpisode()
val item = it.getAndAdvance() val item = it.getAndAdvance()
val played = play(item.data, 0) val played = play(item, 0)
if (!played) { if (!played) {
playUpNextUp() playUpNextUp()
} }
@ -747,7 +748,7 @@ class PlaybackViewModel
viewModelScope.launchIO { viewModelScope.launchIO {
cancelUpNextEpisode() cancelUpNextEpisode()
val item = it.getPreviousAndReverse() val item = it.getPreviousAndReverse()
val played = play(item.data, 0) val played = play(item, 0)
if (!played) { if (!played) {
playPrevious() playPrevious()
} }
@ -765,7 +766,7 @@ class PlaybackViewModel
viewModelScope.launchIO { viewModelScope.launchIO {
val toPlay = playlist.advanceTo(item.id) val toPlay = playlist.advanceTo(item.id)
if (toPlay != null) { if (toPlay != null) {
val played = play(toPlay.data, 0) val played = play(toPlay, 0)
if (!played) { if (!played) {
playUpNextUp() playUpNextUp()
} }
@ -799,7 +800,7 @@ class PlaybackViewModel
PlayMethod.DIRECT_STREAM, PlayMethod.DIRECT_PLAY -> { PlayMethod.DIRECT_STREAM, PlayMethod.DIRECT_PLAY -> {
Timber.w("Playback error during ${it.playMethod}, falling back to transcoding") Timber.w("Playback error during ${it.playMethod}, falling back to transcoding")
changeStreams( changeStreams(
dto, item,
currentItemPlayback.value!!, currentItemPlayback.value!!,
currentItemPlayback.value?.audioIndex, currentItemPlayback.value?.audioIndex,
currentItemPlayback.value?.subtitleIndex, currentItemPlayback.value?.subtitleIndex,
@ -918,8 +919,12 @@ class PlaybackViewModel
while (maxAttempts > 0 && subtitleCount == newCount) { while (maxAttempts > 0 && subtitleCount == newCount) {
maxAttempts-- maxAttempts--
delay(1500) delay(1500)
dto = api.userLibraryApi.getItem(itemId = dto.id).content item =
val mediaSource = chooseSource(dto, it) BaseItem.from(
api.userLibraryApi.getItem(itemId = item.id).content,
api,
)
val mediaSource = chooseSource(item.data, it)
if (mediaSource == null) { if (mediaSource == null) {
// This shouldn't happen, but just in case // This shouldn't happen, but just in case
showToast( showToast(
@ -976,7 +981,7 @@ class PlaybackViewModel
audioIndex += 1 audioIndex += 1
} }
changeStreams( changeStreams(
dto, item,
currentItemPlayback.value!!, currentItemPlayback.value!!,
audioIndex, audioIndex,
newStream.index, newStream.index,
@ -1006,6 +1011,7 @@ class PlaybackViewModel
} }
data class CurrentPlayback( data class CurrentPlayback(
val item: BaseItem,
val tracks: List<TrackSupport>, val tracks: List<TrackSupport>,
val playMethod: PlayMethod, val playMethod: PlayMethod,
val playSessionId: String?, val playSessionId: String?,