mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 08:01:20 +02:00
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:
parent
6a912c0af3
commit
72fb732e63
16 changed files with 72 additions and 42 deletions
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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) }
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -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) } },
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 =
|
||||||
|
|
|
||||||
|
|
@ -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 },
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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?,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue