diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/Extensions.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/Extensions.kt
index c7b2469b..e4b7c8a3 100644
--- a/app/src/main/java/com/github/damontecres/dolphin/ui/Extensions.kt
+++ b/app/src/main/java/com/github/damontecres/dolphin/ui/Extensions.kt
@@ -27,6 +27,7 @@ import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
@@ -174,6 +175,9 @@ fun playOnClickSound(
val Duration.roundMinutes: Duration
get() = (this + 30.seconds).inWholeMinutes.minutes
+val Duration.roundSeconds: Duration
+ get() = (this + 30.milliseconds).inWholeSeconds.seconds
+
val BaseItemDto.timeRemaining: Duration?
get() =
userData?.playbackPositionTicks?.let {
diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/cards/ChapterCard.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/cards/ChapterCard.kt
index 4729280c..5803e23a 100644
--- a/app/src/main/java/com/github/damontecres/dolphin/ui/cards/ChapterCard.kt
+++ b/app/src/main/java/com/github/damontecres/dolphin/ui/cards/ChapterCard.kt
@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -17,6 +18,7 @@ import androidx.tv.material3.Card
import androidx.tv.material3.Text
import coil3.compose.AsyncImage
import com.github.damontecres.dolphin.ui.AppColors
+import com.github.damontecres.dolphin.ui.roundSeconds
import kotlin.time.Duration
@Composable
@@ -44,21 +46,23 @@ fun ChapterCard(
contentScale = ContentScale.Fit,
modifier = Modifier.fillMaxSize(),
)
- Column(
+ Box(
modifier =
Modifier
.align(Alignment.BottomStart)
.fillMaxWidth()
.background(AppColors.TransparentBlack50),
) {
- name?.let {
+ Column(modifier = Modifier.padding(4.dp)) {
+ name?.let {
+ Text(
+ text = it,
+ )
+ }
Text(
- text = it,
+ text = position.roundSeconds.toString(),
)
}
- Text(
- text = position.toString(),
- )
}
}
}
diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/detail/movie/MovieDetails.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/detail/movie/MovieDetails.kt
index efb32b1e..2b065931 100644
--- a/app/src/main/java/com/github/damontecres/dolphin/ui/detail/movie/MovieDetails.kt
+++ b/app/src/main/java/com/github/damontecres/dolphin/ui/detail/movie/MovieDetails.kt
@@ -194,7 +194,7 @@ fun MovieDetailsContent(
modifier =
Modifier
.bringIntoViewRequester(bringIntoViewRequester)
- .padding(bottom = 120.dp),
+ .padding(bottom = 56.dp),
) {
MovieDetailsHeader(
movie = movie,
diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/detail/series/FocusedEpisodeHeader.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/detail/series/FocusedEpisodeHeader.kt
index 7b79b88d..b334cf61 100644
--- a/app/src/main/java/com/github/damontecres/dolphin/ui/detail/series/FocusedEpisodeHeader.kt
+++ b/app/src/main/java/com/github/damontecres/dolphin/ui/detail/series/FocusedEpisodeHeader.kt
@@ -94,41 +94,40 @@ fun FocusedEpisodeHeader(
}
}
}
- dto.overview?.let { overview ->
- val interactionSource = remember { MutableInteractionSource() }
- val isFocused = interactionSource.collectIsFocusedAsState().value
- val bgColor =
- if (isFocused) {
- MaterialTheme.colorScheme.onPrimary.copy(alpha = .4f)
- } else {
- Color.Unspecified
- }
- Box(
+
+ val interactionSource = remember { MutableInteractionSource() }
+ val isFocused = interactionSource.collectIsFocusedAsState().value
+ val bgColor =
+ if (isFocused) {
+ MaterialTheme.colorScheme.onPrimary.copy(alpha = .4f)
+ } else {
+ Color.Unspecified
+ }
+ Box(
+ modifier =
+ Modifier
+ .background(bgColor, shape = RoundedCornerShape(8.dp))
+ .playSoundOnFocus(true)
+ .clickable(
+ enabled = true,
+ interactionSource = interactionSource,
+ indication = LocalIndication.current,
+ ) {
+ playOnClickSound(context)
+ overviewOnClick.invoke()
+ },
+ ) {
+ Text(
+ text = dto.overview ?: "",
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurface,
+ maxLines = 3,
+ overflow = TextOverflow.Ellipsis,
modifier =
Modifier
- .background(bgColor, shape = RoundedCornerShape(8.dp))
- .playSoundOnFocus(true)
- .clickable(
- enabled = true,
- interactionSource = interactionSource,
- indication = LocalIndication.current,
- ) {
- playOnClickSound(context)
- overviewOnClick.invoke()
- },
- ) {
- Text(
- text = overview,
- style = MaterialTheme.typography.bodyMedium,
- color = MaterialTheme.colorScheme.onSurface,
- maxLines = 3,
- overflow = TextOverflow.Ellipsis,
- modifier =
- Modifier
- .padding(8.dp)
- .height(60.dp),
- )
- }
+ .padding(8.dp)
+ .height(60.dp),
+ )
}
}
}
diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/detail/series/SeriesOverviewContent.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/detail/series/SeriesOverviewContent.kt
index b4940da2..768aab59 100644
--- a/app/src/main/java/com/github/damontecres/dolphin/ui/detail/series/SeriesOverviewContent.kt
+++ b/app/src/main/java/com/github/damontecres/dolphin/ui/detail/series/SeriesOverviewContent.kt
@@ -188,7 +188,7 @@ fun SeriesOverviewContent(
LazyRow(
state = state,
horizontalArrangement = Arrangement.spacedBy(16.dp),
- contentPadding = PaddingValues(start = 16.dp),
+ contentPadding = PaddingValues(horizontal = 16.dp),
modifier =
Modifier
.focusRestorer(firstItemFocusRequester)
diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackControls.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackControls.kt
index 32ff20d5..d44a7b38 100644
--- a/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackControls.kt
+++ b/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackControls.kt
@@ -142,6 +142,7 @@ fun PlaybackControls(
Column(
modifier = modifier.bringIntoViewRequester(bringIntoViewRequester),
horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(0.dp),
) {
SeekBar(
player = playerControls,
@@ -152,7 +153,7 @@ fun PlaybackControls(
intervals = seekBarIntervals,
modifier =
Modifier
- .padding(vertical = 8.dp)
+ .padding(vertical = 0.dp)
.fillMaxWidth(.95f),
)
Box(
@@ -516,11 +517,11 @@ fun PlaybackButton(
containerColor = AppColors.TransparentBlack25,
focusedContainerColor = selectedColor,
),
- contentPadding = PaddingValues(8.dp),
+ contentPadding = PaddingValues(4.dp),
modifier =
modifier
- .padding(8.dp)
- .size(56.dp, 56.dp)
+ .padding(4.dp)
+ .size(44.dp, 44.dp)
.onFocusChanged { onControllerInteraction.invoke() },
) {
Icon(
diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackOverlay.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackOverlay.kt
index 5d58b2b0..fc721695 100644
--- a/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackOverlay.kt
+++ b/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackOverlay.kt
@@ -32,6 +32,7 @@ import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import androidx.media3.common.Player
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
@@ -81,32 +82,33 @@ fun PlaybackOverlay(
// val chapterRowFocused = chapterInteractionSources.any { it.collectIsFocusedAsState().value }
var chapterRowFocused by remember { mutableStateOf(false) }
- val titleTextStyle = MaterialTheme.typography.displaySmall
- val subtitleTextStyle = MaterialTheme.typography.headlineMedium
+ val titleTextSize = 28.sp
+ val subtitleTextSize = 18.sp
val density = LocalDensity.current
val titleHeight =
- if (title.isNotNullOrBlank()) with(density) { titleTextStyle.fontSize.toDp() } else 0.dp
+ if (title.isNotNullOrBlank()) with(density) { titleTextSize.toDp() } else 0.dp
val subtitleHeight =
- if (subtitle.isNotNullOrBlank()) with(density) { subtitleTextStyle.fontSize.toDp() } else 0.dp
+ if (subtitle.isNotNullOrBlank()) with(density) { subtitleTextSize.toDp() } else 0.dp
// Calculate height based on content
// Base height (with or w/o chapters) + title + subtitle
// The extra 8dp is for padding between title, subtitle, and playback controls
// When chapter row is focused, the title/subtitle/playback controls will be hidden, but need extra height for the chapter images
val height by animateDpAsState(
- (if (chapters.isNotEmpty()) 184.dp else 140.dp) +
- (if (chapterRowFocused) 40.dp else 0.dp) +
+ 96.dp +
+ (if (chapters.isNotEmpty()) 40.dp else 0.dp) +
+ (if (chapterRowFocused) 80.dp else 0.dp) +
(
if (!chapterRowFocused && title.isNotNullOrBlank()) {
- titleHeight + 8.dp
+ titleHeight + 12.dp
} else {
0.dp
}
) +
(
if (!chapterRowFocused && subtitle.isNotNullOrBlank()) {
- subtitleHeight + 8.dp
+ subtitleHeight + 12.dp
} else {
0.dp
}
@@ -132,6 +134,7 @@ fun PlaybackOverlay(
modifier = Modifier,
) {
Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp),
modifier =
Modifier
.padding(start = 16.dp)
@@ -140,13 +143,15 @@ fun PlaybackOverlay(
title?.let {
Text(
text = it,
- style = titleTextStyle,
+ style = MaterialTheme.typography.titleLarge,
+ fontSize = titleTextSize,
)
}
subtitle?.let {
Text(
text = it,
- style = subtitleTextStyle,
+ style = MaterialTheme.typography.titleMedium,
+ fontSize = subtitleTextSize,
)
}
}
diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackViewModel.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackViewModel.kt
index 40ee49b1..1ab4658d 100644
--- a/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackViewModel.kt
+++ b/app/src/main/java/com/github/damontecres/dolphin/ui/playback/PlaybackViewModel.kt
@@ -21,7 +21,7 @@ import com.github.damontecres.dolphin.util.ExceptionHandler
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.formatDateTime
import com.github.damontecres.dolphin.util.subtitleMimeTypes
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
@@ -57,15 +57,7 @@ data class StreamDecision(
val itemId: UUID,
val type: TranscodeType,
val url: String,
-) {
- val mediaItem: MediaItem
- get() =
- MediaItem
- .Builder()
- .setMediaId(itemId.toString())
- .setUri(url.toUri())
- .build()
-}
+)
@HiltViewModel
class PlaybackViewModel
@@ -112,25 +104,26 @@ class PlaybackViewModel
val base = item?.data ?: api.userLibraryApi.getItem(itemId).content
dto = base
- val title = base.name
+ val title =
+ if (base.type == BaseItemKind.EPISODE) {
+ base.seriesName
+ } else {
+ base.name
+ }
val subtitle =
if (base.type == BaseItemKind.EPISODE) {
val season = base.parentIndexNumber?.toString()?.padStart(2, '0')
val episode = base.indexNumber?.toString()?.padStart(2, '0')
// TODO multi episode support
- if (season != null && episode != null) {
- buildString {
- if (base.seriesName != null) {
- append(base.seriesName)
- append(" - ")
- }
- append("S${season}E$episode")
+ buildList {
+ if (season != null && episode != null) {
+ add("S${season}E$episode")
}
- } else {
- null
- }
+ add(base.name)
+ add(base.premiereDate?.let { formatDateTime(it) })
+ }.filterNotNull().joinToString(" - ")
} else {
- null
+ base.productionYear?.toString()
}
withContext(Dispatchers.Main) {
this@PlaybackViewModel.title.value = title
@@ -186,7 +179,6 @@ class PlaybackViewModel
TrackActivityPlaybackListener(api, itemId, player)
addCloseable { activityListener.release() }
player.addListener(activityListener)
- player.addListener(PlaybackListener())
changeStreams(
itemId,
audioIndex,
diff --git a/app/src/main/java/com/github/damontecres/dolphin/util/profile/PlaybackListener.kt b/app/src/main/java/com/github/damontecres/dolphin/util/profile/PlaybackListener.kt
deleted file mode 100644
index 3d61cb41..00000000
--- a/app/src/main/java/com/github/damontecres/dolphin/util/profile/PlaybackListener.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.github.damontecres.dolphin.util.profile
-
-import androidx.media3.common.MediaItem
-import androidx.media3.common.PlaybackException
-import androidx.media3.common.Player
-import androidx.media3.common.Tracks
-import androidx.media3.common.text.CueGroup
-import com.github.damontecres.dolphin.ui.indexOfFirstOrNull
-import com.github.damontecres.dolphin.util.TrackSupportReason
-import com.github.damontecres.dolphin.util.TrackType
-import com.github.damontecres.dolphin.util.checkForSupport
-import timber.log.Timber
-import java.util.Locale
-
-class PlaybackListener : Player.Listener {
- var mediaIndexSubtitlesActivated = -1
- var currentPlaylistIndex = -2
-
- override fun onCues(cueGroup: CueGroup) {
- val subtitles = cueGroup.cues.ifEmpty { null }
- }
-
- override fun onTracksChanged(tracks: Tracks) {
- val trackInfo = checkForSupport(tracks)
- Timber.v("Track info: $trackInfo")
- val audioTracks =
- trackInfo
- .filter { it.type == TrackType.AUDIO && it.supported == TrackSupportReason.HANDLED }
- val audioIndex = audioTracks.indexOfFirstOrNull { it.selected }
- val audioOptions =
- audioTracks.map { it.labels.joinToString(", ").ifBlank { "Default" } }
- val captions =
- trackInfo.filter { it.type == TrackType.TEXT && it.supported == TrackSupportReason.HANDLED }
-
- // TODO user preference
- val captionsByDefault = true
- if (captionsByDefault && captions.isNotEmpty()) {
- // TODO Captions will be empty when transitioning to new media item
- // Only want to activate subtitles once in case the user turns them off
- mediaIndexSubtitlesActivated = currentPlaylistIndex
- val languageCode = Locale.getDefault().language
- captions.indexOfFirstOrNull { it.format.language == languageCode }?.let {
- Timber.v("Found default subtitle track for $languageCode: $it")
-// if (toggleSubtitles(player, null, it)) {
-// subtitleIndex = it
-// }
- }
- }
- }
-
- override fun onMediaItemTransition(
- mediaItem: MediaItem?,
- reason: Int,
- ) {
- }
-
- override fun onPlayerError(error: PlaybackException) {
- Timber.e(error, "Playback error")
- }
-}
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 09a7a336..ddc052a3 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,4 +1,6 @@
-
-
\ No newline at end of file
+
+