feat(player): track skippable segment type to gate intro and outro UI

Expose the active skippable segment type from the player state so the UI
can show the skip intro button only for intro segments and trigger the
next episode overlay when an outro segment is active.
This commit is contained in:
2026-06-19 12:00:16 +00:00
parent a4267d85ce
commit 54c895ff0f
4 changed files with 14 additions and 5 deletions

View File

@@ -62,6 +62,7 @@ import androidx.media3.ui.PlayerView
import androidx.media3.ui.SubtitleView
import hu.bbara.purefin.core.player.model.TimedMarker
import hu.bbara.purefin.core.player.viewmodel.PlayerViewModel
import hu.bbara.purefin.model.SegmentType
import hu.bbara.purefin.ui.common.visual.ValueChangeTimedVisibility
import hu.bbara.purefin.ui.screen.player.components.PlayerSeekBarTrack
import hu.bbara.purefin.ui.screen.player.components.TvIconButton
@@ -179,11 +180,12 @@ fun TvPlayerScreen(
val showSkipIntroButton = !controlsVisible
&& uiState.activeSkippableSegmentEndMs != null
&& uiState.activeSkippableSegmentType == SegmentType.INTRO
&& !uiState.isEnded
val showNextEpisodeOverlay = !controlsVisible
&& uiState.nextEpisode != null
&& uiState.durationMs > 0L
&& (uiState.durationMs - uiState.positionMs) <= 60_000L
&& ((uiState.durationMs - uiState.positionMs) <= 30_000L || uiState.activeSkippableSegmentType == SegmentType.OUTRO)
&& !uiState.isEnded
LaunchedEffect(showSkipIntroButton, showNextEpisodeOverlay) {
rootFocusRequester = when {

View File

@@ -43,6 +43,7 @@ import androidx.media3.ui.PlayerView
import androidx.media3.ui.SubtitleView
import hu.bbara.purefin.core.player.model.TimedMarker
import hu.bbara.purefin.core.player.viewmodel.PlayerViewModel
import hu.bbara.purefin.model.SegmentType
import hu.bbara.purefin.ui.common.visual.EmptyValueTimedVisibility
import hu.bbara.purefin.ui.common.visual.ValueChangeTimedVisibility
import hu.bbara.purefin.ui.screen.player.components.NextEpisodeOverlay
@@ -100,11 +101,11 @@ fun PlayerScreen(
}
val showSkipIntroButton = !controlsVisible && uiState.activeSkippableSegmentEndMs != null
val showSkipIntroButton = !controlsVisible && uiState.activeSkippableSegmentEndMs != null && uiState.activeSkippableSegmentType == SegmentType.INTRO
val showNextEpisodeOverlay = !controlsVisible
&& uiState.nextEpisode != null
&& uiState.durationMs > 0L
&& (uiState.durationMs - uiState.positionMs) <= 60_000L
&& ((uiState.durationMs - uiState.positionMs) <= 60_000L || uiState.activeSkippableSegmentType == SegmentType.OUTRO)
&& !uiState.isEnded
@@ -301,7 +302,7 @@ fun PlayerScreen(
.align(Alignment.BottomEnd)
.padding(
end = 24.dp,
bottom = 120.dp
bottom = 24.dp
)
) {
uiState.nextEpisode?.let { nextEpisode ->

View File

@@ -1,5 +1,7 @@
package hu.bbara.purefin.core.player.model
import hu.bbara.purefin.model.SegmentType
data class PlayerUiState(
val isPlaying: Boolean = false,
val isBuffering: Boolean = false,
@@ -11,6 +13,7 @@ data class PlayerUiState(
val positionMs: Long = 0L,
val bufferedMs: Long = 0L,
val activeSkippableSegmentEndMs: Long? = null,
val activeSkippableSegmentType: SegmentType? = null,
val error: String? = null,
val playbackSpeed: Float = 1f,
val chapters: List<TimedMarker> = emptyList(),

View File

@@ -117,7 +117,10 @@ class PlayerViewModel @Inject constructor(
viewModelScope.launch {
playerManager.activeSkippableSegment.collect { mediaSegment ->
_uiState.update {
it.copy(activeSkippableSegmentEndMs = mediaSegment?.endMs)
it.copy(
activeSkippableSegmentEndMs = mediaSegment?.endMs,
activeSkippableSegmentType = mediaSegment?.type
)
}
}
}