feat: add skip segment functionality to player

This commit is contained in:
2026-04-26 12:21:52 +02:00
parent 9906d473c5
commit 522c8273d5
13 changed files with 263 additions and 56 deletions

View File

@@ -689,6 +689,7 @@ private fun OverlayHost(
onSeek = { _ -> },
onSeekRelative = { _ -> },
onSeekLiveEdge = {},
onSkipSegment = {},
onNext = {},
onPrevious = {},
onOpenAudioPanel = {},
@@ -804,6 +805,7 @@ private fun TrackPanelHost(
onSeek = { _ -> },
onSeekRelative = { _ -> },
onSeekLiveEdge = {},
onSkipSegment = {},
onNext = {},
onPrevious = {},
onOpenAudioPanel = {

View File

@@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Pause
import androidx.compose.material.icons.outlined.SkipNext
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@@ -50,6 +51,7 @@ import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.PlayerView
import hu.bbara.purefin.player.viewmodel.ControlsAutoHideBlocker
import hu.bbara.purefin.player.viewmodel.PlayerViewModel
import hu.bbara.purefin.ui.screen.player.components.TvIconButton
import hu.bbara.purefin.ui.screen.player.components.TvPlayerControlsOverlay
import hu.bbara.purefin.ui.screen.player.components.TvPlayerLoadingErrorEndCard
import hu.bbara.purefin.ui.screen.player.components.TvTrackPanelType
@@ -116,6 +118,7 @@ fun TvPlayerScreen(
val qualityButtonFocusRequester = remember { FocusRequester() }
val audioButtonFocusRequester = remember { FocusRequester() }
val subtitlesButtonFocusRequester = remember { FocusRequester() }
val skipButtonFocusRequester = remember { FocusRequester() }
val expandPlaylist: () -> Unit = {
if (!isPlaylistExpanded) {
isPlaylistExpanded = true
@@ -158,6 +161,10 @@ fun TvPlayerScreen(
viewModel.seekToLiveEdge()
showTvControls()
}
val skipSegmentAndShowControls: () -> Unit = {
viewModel.skipActiveSegment()
showTvControls()
}
val nextAndShowControls: () -> Unit = {
viewModel.next(TV_CONTROLS_AUTO_HIDE_MS)
}
@@ -177,6 +184,10 @@ fun TvPlayerScreen(
if (controlsVisible) {
controlsFocusRequester.requestFocus()
} else {
uiState.activeSkippableSegmentEndMs?.let {
skipButtonFocusRequester.requestFocus()
return@LaunchedEffect
}
hiddenControlFocusRequester.requestFocus()
}
}
@@ -251,8 +262,10 @@ fun TvPlayerScreen(
.align(Alignment.Center)
)
val playerControlsVisible =
controlsVisible || isPlaylistExpanded || trackPanelType != null || uiState.isEnded || uiState.error != null
AnimatedVisibility(
visible = controlsVisible || isPlaylistExpanded || trackPanelType != null || uiState.isEnded || uiState.error != null,
visible = playerControlsVisible,
enter = fadeIn(),
exit = fadeOut()
) {
@@ -268,6 +281,7 @@ fun TvPlayerScreen(
onSeek = seekAndShowControls,
onSeekRelative = seekByAndShowControls,
onSeekLiveEdge = seekToLiveEdgeAndShowControls,
onSkipSegment = skipSegmentAndShowControls,
onNext = nextAndShowControls,
onPrevious = previousAndShowControls,
onOpenAudioPanel = { trackPanelType = TvTrackPanelType.AUDIO },
@@ -285,6 +299,24 @@ fun TvPlayerScreen(
)
}
AnimatedVisibility(
visible = !playerControlsVisible && uiState.activeSkippableSegmentEndMs != null,
enter = fadeIn(),
exit = fadeOut(),
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(end = 24.dp, bottom = 24.dp)
) {
TvIconButton(
icon = Icons.Outlined.SkipNext,
contentDescription = "Skip segment",
onClick = skipSegmentAndShowControls,
size = 64,
label = "Skip",
modifier = Modifier.focusRequester(skipButtonFocusRequester)
)
}
TvPlayerLoadingErrorEndCard(
modifier = Modifier.align(Alignment.Center),
uiState = uiState,

View File

@@ -5,13 +5,17 @@ import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -28,6 +32,7 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@Composable
@@ -37,6 +42,7 @@ internal fun TvIconButton(
onClick: () -> Unit,
size: Int = 52,
enabled: Boolean = true,
label: String? = null,
modifier: Modifier = Modifier
) {
val scheme = MaterialTheme.colorScheme
@@ -57,7 +63,7 @@ internal fun TvIconButton(
scaleY = scale
}
.alpha(if (enabled) 1f else 0.4f)
.widthIn(min = size.dp)
.widthIn(min = if (label == null) size.dp else 104.dp)
.height(size.dp)
.border(
width = if (isFocused) 2.dp else 0.dp,
@@ -79,11 +85,32 @@ internal fun TvIconButton(
.clickable(enabled = enabled) { onClick() },
contentAlignment = Alignment.Center
) {
if (label == null) {
Icon(
imageVector = icon,
contentDescription = contentDescription,
tint = scheme.onBackground,
modifier = Modifier.padding(8.dp)
)
} else {
Row(
horizontalArrangement = Arrangement.spacedBy(6.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = 16.dp)
) {
Icon(
imageVector = icon,
contentDescription = contentDescription,
tint = scheme.onBackground,
modifier = Modifier.size(28.dp)
)
Text(
text = label,
color = scheme.onBackground,
style = MaterialTheme.typography.labelLarge,
fontWeight = FontWeight.SemiBold
)
}
}
}
}

View File

@@ -65,6 +65,7 @@ internal fun TvPlayerControlsOverlay(
onSeek: (Long) -> Unit,
onSeekRelative: (Long) -> Unit,
onSeekLiveEdge: () -> Unit,
onSkipSegment: () -> Unit,
onNext: () -> Unit,
onPrevious: () -> Unit,
onOpenAudioPanel: () -> Unit,
@@ -110,6 +111,7 @@ internal fun TvPlayerControlsOverlay(
onSeek = onSeek,
onSeekRelative = onSeekRelative,
onSeekLiveEdge = onSeekLiveEdge,
onSkipSegment = onSkipSegment,
onNext = onNext,
onPrevious = onPrevious,
onOpenAudioPanel = onOpenAudioPanel,
@@ -172,6 +174,7 @@ private fun TvPlayerBottomSection(
onSeek: (Long) -> Unit,
onSeekRelative: (Long) -> Unit,
onSeekLiveEdge: () -> Unit,
onSkipSegment: () -> Unit,
onNext: () -> Unit,
onPrevious: () -> Unit,
onOpenAudioPanel: () -> Unit,
@@ -215,6 +218,24 @@ private fun TvPlayerBottomSection(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
if (uiState.activeSkippableSegmentEndMs != null) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 4.dp),
horizontalArrangement = Arrangement.End
) {
TvIconButton(
icon = Icons.Outlined.SkipNext,
contentDescription = "Skip segment",
onClick = onSkipSegment,
size = 64,
label = "Skip",
modifier = expandPlaylistModifier
)
}
Spacer(modifier = Modifier.height(6.dp))
}
Text(
text = formatTime(uiState.positionMs),
color = scheme.onSurface,

View File

@@ -32,12 +32,15 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.common.util.UnstableApi
import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.PlayerView
import hu.bbara.purefin.player.viewmodel.PlayerViewModel
import hu.bbara.purefin.ui.common.button.PurefinTextButton
import hu.bbara.purefin.ui.common.visual.EmptyValueTimedVisibility
import hu.bbara.purefin.ui.common.visual.ValueChangeTimedVisibility
import hu.bbara.purefin.ui.screen.player.components.PersistentOverlayContainer
@@ -168,8 +171,9 @@ fun PlayerScreen(
)
}
val playerControlsVisible = controlsVisible || uiState.isEnded || uiState.error != null
AnimatedVisibility(
visible = controlsVisible || uiState.isEnded || uiState.error != null,
visible = playerControlsVisible,
enter = fadeIn(),
exit = fadeOut()
) {
@@ -183,6 +187,7 @@ fun PlayerScreen(
onSeek = { viewModel.seekTo(it) },
onSeekRelative = { delta -> viewModel.seekBy(delta) },
onSeekLiveEdge = { viewModel.seekToLiveEdge() },
onSkipSegment = { viewModel.skipActiveSegment() },
onNext = { viewModel.next() },
onPrevious = { viewModel.previous() },
onSelectTrack = { viewModel.selectTrack(it) },
@@ -191,6 +196,26 @@ fun PlayerScreen(
)
}
AnimatedVisibility(
visible = !playerControlsVisible && uiState.activeSkippableSegmentEndMs != null,
enter = fadeIn(),
exit = fadeOut(),
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(end = 24.dp, bottom = 24.dp)
) {
PurefinTextButton(
onClick = { viewModel.skipActiveSegment() },
modifier = Modifier.padding(8.dp)
) {
Text(
text = "Skip",
fontSize = 20.sp,
fontWeight = FontWeight.ExtraBold
)
}
}
PlayerLoadingErrorEndCard(
modifier = Modifier.align(Alignment.Center),
uiState = uiState,

View File

@@ -35,10 +35,12 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.common.button.GhostIconButton
import hu.bbara.purefin.ui.common.button.PurefinIconButton
import androidx.compose.ui.unit.sp
import hu.bbara.purefin.player.model.PlayerUiState
import hu.bbara.purefin.player.model.TrackOption
import hu.bbara.purefin.ui.common.button.GhostIconButton
import hu.bbara.purefin.ui.common.button.PurefinIconButton
import hu.bbara.purefin.ui.common.button.PurefinTextButton
@Composable
fun PlayerControlsOverlay(
@@ -50,6 +52,7 @@ fun PlayerControlsOverlay(
onSeek: (Long) -> Unit,
onSeekRelative: (Long) -> Unit,
onSeekLiveEdge: () -> Unit,
onSkipSegment: () -> Unit,
onNext: () -> Unit,
onPrevious: () -> Unit,
onSelectTrack: (TrackOption) -> Unit,
@@ -100,6 +103,7 @@ fun PlayerControlsOverlay(
onSeekForward = { onSeekRelative(30_000) },
onSeekBackward = { onSeekRelative(-10_000) },
onSeekLiveEdge = onSeekLiveEdge,
onSkipSegment = onSkipSegment,
onSelectTrack = onSelectTrack,
onQueueSelected = onQueueSelected,
modifier = Modifier.align(Alignment.BottomCenter)
@@ -163,12 +167,33 @@ private fun BottomSection(
onSeekForward: () -> Unit,
onSeekBackward: () -> Unit,
onSeekLiveEdge: () -> Unit,
onSkipSegment: () -> Unit,
onSelectTrack: (TrackOption) -> Unit,
onQueueSelected: (String) -> Unit,
modifier: Modifier = Modifier
) {
val scheme = MaterialTheme.colorScheme
Column(modifier = modifier) {
if (uiState.activeSkippableSegmentEndMs != null) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp),
horizontalArrangement = Arrangement.End
) {
PurefinTextButton(
onClick = onSkipSegment,
modifier = Modifier.padding(8.dp)
) {
Text(
text = "Skip",
fontSize = 20.sp,
fontWeight = FontWeight.ExtraBold
)
}
}
Spacer(modifier = Modifier.height(6.dp))
}
Row(
modifier = Modifier
.fillMaxWidth()

View File

@@ -19,7 +19,8 @@ fun PurefinTextButton(
modifier = modifier,
enabled = enabled,
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
),
content = content
)

View File

@@ -1,69 +1,83 @@
package hu.bbara.purefin.player.manager
import android.os.Looper
import android.util.Log
import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.PlayerMessage
import hu.bbara.purefin.model.MediaSegment
import hu.bbara.purefin.model.SegmentType
import hu.bbara.purefin.player.model.SegmentStatus
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
@OptIn(UnstableApi::class)
class MediaSegmentManager(private val player: ExoPlayer) {
private var listener: MediaSegmentListener? = null
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
private var mediaSegments: List<MediaSegment> = emptyList()
private var activeSegment: MediaSegment? = null
interface MediaSegmentListener {
fun onEvent(segmentType: SegmentType, status: SegmentStatus)
fun onEvent(mediaSegment: MediaSegment, status: SegmentStatus)
}
init {
startPolling()
}
@Synchronized
fun registerListener(listener: MediaSegmentListener) {
this.listener?.let {
Log.w("PlayerMessageManager", "Listener was already register")
Log.w("MediaSegmentManager", "Listener was already register")
return
}
this.listener = listener
}
fun addMediaSegments(mediaSegments: List<MediaSegment>) {
val messageTarget = PlayerMessage.Target { messageType, payload ->
SegmentType.fromValue(messageType)?.let { segmentType ->
notifyListener(segmentType, payload as SegmentStatus)
this.mediaSegments = mediaSegments
activeSegment = null
}
fun release() {
scope.cancel()
}
private fun startPolling() {
scope.launch {
while (isActive) {
updateActiveSegment()
delay(1_000)
}
}
}
mediaSegments.forEach { mediaSegment ->
installMediaSegment(messageTarget, mediaSegment)
private fun updateActiveSegment() {
val currentPosition = player.currentPosition
val currentSegment = mediaSegments.firstOrNull { mediaSegment ->
currentPosition >= mediaSegment.startMs && currentPosition < mediaSegment.endMs
}
val previousSegment = activeSegment
if (previousSegment?.id == currentSegment?.id) return
previousSegment?.let {
notifyListener(it, SegmentStatus.END)
}
currentSegment?.let {
notifyListener(it, SegmentStatus.START)
}
activeSegment = currentSegment
}
}
private fun notifyListener(segmentType: SegmentType, status: SegmentStatus) {
private fun notifyListener(mediaSegment: MediaSegment, status: SegmentStatus) {
val listener = listener
if (listener == null) {
Log.w("PlayerMessageManager", "Listener was not register therefore it cannot notify")
Log.w("MediaSegmentManager", "Listener was not register therefore it cannot notify")
return
}
listener?.onEvent(segmentType, status)
}
private fun installMediaSegment(messageTarget: PlayerMessage.Target, mediaSegment: MediaSegment) {
player.createMessage(messageTarget)
.setType(mediaSegment.type.value)
.setPosition(mediaSegment.startMs)
.setPayload(SegmentStatus.START)
.setLooper(Looper.getMainLooper())
.setDeleteAfterDelivery(false)
.send()
player.createMessage(messageTarget)
.setType(mediaSegment.type.value)
.setPayload(SegmentStatus.END)
.setPosition(mediaSegment.endMs)
.setLooper(Looper.getMainLooper())
.setDeleteAfterDelivery(false)
.send()
Log.d("MediaSegmentManager", "Notify listener about $mediaSegment with status $status")
listener.onEvent(mediaSegment, status)
}
}

View File

@@ -14,10 +14,12 @@ import hu.bbara.purefin.data.PlaybackReportContext
import hu.bbara.purefin.model.AudioTrackProperties
import hu.bbara.purefin.model.MediaSegment
import hu.bbara.purefin.model.PlayableMedia
import hu.bbara.purefin.model.SegmentType
import hu.bbara.purefin.model.SubtitleTrackProperties
import hu.bbara.purefin.player.model.MetadataState
import hu.bbara.purefin.player.model.PlaybackProgressSnapshot
import hu.bbara.purefin.player.model.PlaybackStateSnapshot
import hu.bbara.purefin.player.model.SegmentStatus
import hu.bbara.purefin.player.model.TrackOption
import hu.bbara.purefin.player.model.TrackType
import hu.bbara.purefin.player.preference.TrackMatcher
@@ -80,8 +82,16 @@ class PlayerManager @Inject constructor(
val metadata: StateFlow<MetadataState> = _metadata.asStateFlow()
private val _tracks = MutableStateFlow(TrackSelectionState())
val tracks: StateFlow<TrackSelectionState> = _tracks.asStateFlow()
private val _activeSkippableSegment = MutableStateFlow<MediaSegment?>(null)
val activeSkippableSegment: StateFlow<MediaSegment?> = _activeSkippableSegment.asStateFlow()
private val mediaSegmentListener = object : MediaSegmentManager.MediaSegmentListener {
override fun onEvent(mediaSegment: MediaSegment, status: SegmentStatus) {
handleMediaSegmentEvent(mediaSegment, status)
}
}
private val listener = object : Player.Listener {
override fun onPositionDiscontinuity(
@@ -99,6 +109,7 @@ class PlayerManager @Inject constructor(
}
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
clearActiveSkippableSegment()
_currentMediaId.value = mediaItem?.mediaId?.let { UUID.fromString(it) }
scope.launch {
val currentMedia = _playlist.value.firstOrNull { it.id == _currentMediaId.value }
@@ -107,6 +118,7 @@ class PlayerManager @Inject constructor(
return@launch
}
seekTo(currentMedia.resumePositionMs)
installMediaSegments(currentMedia.mediaSegments)
// Only updatePlaylist when episodes are being played. First item is handled when playMedia is being called first time.
if (currentMedia is PlayableMedia.Episode) {
updatePlaylist()
@@ -125,6 +137,7 @@ class PlayerManager @Inject constructor(
}
init {
mediaSegmentManager.registerListener(mediaSegmentListener)
player.addListener(listener)
updateFromPlayer(player)
startProgressLoop()
@@ -192,6 +205,7 @@ class PlayerManager @Inject constructor(
fun seekTo(positionMs: Long) {
val target = clampSeekPosition(positionMs)
clearActiveSegmentIfPositionOutside(target)
pendingSeekPositionMs = target
_progress.update { progress ->
progress.copy(
@@ -219,6 +233,7 @@ class PlayerManager @Inject constructor(
fun next() {
clearPendingSeek()
clearActiveSkippableSegment()
if (player.hasNextMediaItem()) {
player.seekToNextMediaItem()
}
@@ -226,6 +241,7 @@ class PlayerManager @Inject constructor(
fun previous() {
clearPendingSeek()
clearActiveSkippableSegment()
if (player.hasPreviousMediaItem()) {
player.seekToPreviousMediaItem()
}
@@ -282,10 +298,17 @@ class PlayerManager @Inject constructor(
fun retry() {
clearPendingSeek()
clearActiveSkippableSegment()
player.prepare()
player.playWhenReady = true
}
fun skipActiveSegment() {
val mediaSegment = _activeSkippableSegment.value ?: return
seekTo(mediaSegment.endMs)
clearActiveSkippableSegment()
}
fun clearError() {
_playbackState.update { it.copy(error = null) }
}
@@ -342,6 +365,8 @@ class PlayerManager @Inject constructor(
}
fun release() {
clearActiveSkippableSegment()
mediaSegmentManager.release()
scope.cancel()
player.removeListener(listener)
player.release()
@@ -405,6 +430,29 @@ class PlayerManager @Inject constructor(
pendingSeekPositionMs = null
}
private fun clearActiveSkippableSegment() {
_activeSkippableSegment.value = null
}
private fun clearActiveSegmentIfPositionOutside(positionMs: Long) {
val activeSegment = _activeSkippableSegment.value ?: return
if (positionMs < activeSegment.startMs || positionMs >= activeSegment.endMs) {
clearActiveSkippableSegment()
}
}
private fun handleMediaSegmentEvent(mediaSegment: MediaSegment, status: SegmentStatus) {
if (mediaSegment.type == SegmentType.MAIN_CONTENT) return
when (status) {
SegmentStatus.START -> _activeSkippableSegment.value = mediaSegment
SegmentStatus.END -> {
if (_activeSkippableSegment.value?.id == mediaSegment.id) {
clearActiveSkippableSegment()
}
}
}
}
private fun mapPlayerError(error: PlaybackException?): String? {
return error?.errorCodeName ?: error?.localizedMessage ?: error?.message
}

View File

@@ -10,6 +10,7 @@ data class PlayerUiState(
val durationMs: Long = 0L,
val positionMs: Long = 0L,
val bufferedMs: Long = 0L,
val activeSkippableSegmentEndMs: Long? = null,
val error: String? = null,
val playbackSpeed: Float = 1f,
val chapters: List<TimedMarker> = emptyList(),

View File

@@ -121,6 +121,14 @@ class PlayerViewModel @Inject constructor(
}
}
viewModelScope.launch {
playerManager.activeSkippableSegment.collect { mediaSegment ->
_uiState.update {
it.copy(activeSkippableSegmentEndMs = mediaSegment?.endMs)
}
}
}
viewModelScope.launch {
combine(playerManager.playlist, playerManager.currentPlayableMedia) { playlist, currentPlayableMedia ->
playlist.mapNotNull { playableMedia ->
@@ -194,6 +202,10 @@ class PlayerViewModel @Inject constructor(
playerManager.seekToLiveEdge()
}
fun skipActiveSegment() {
playerManager.skipActiveSegment()
}
fun setControlsAutoHideDelay(autoHideDelayMs: Long) {
applyControlsAutoHideCommand(
controlsAutoHidePolicy.setAutoHideDelay(autoHideDelayMs)

View File

@@ -184,8 +184,8 @@ class DefaultPlayableMediaRepository @Inject constructor(
return MediaSegment(
id = itemId,
type = segmentType,
startMs = startTicks,
endMs = endTicks
startMs = startTicks / 10_000L,
endMs = endTicks / 10_000L
)
}
}

View File

@@ -32,7 +32,6 @@ import org.jellyfin.sdk.model.api.CollectionType
import org.jellyfin.sdk.model.api.DeviceProfile
import org.jellyfin.sdk.model.api.ItemFields
import org.jellyfin.sdk.model.api.MediaSegmentDto
import org.jellyfin.sdk.model.api.MediaSegmentType
import org.jellyfin.sdk.model.api.MediaSourceInfo
import org.jellyfin.sdk.model.api.MediaType
import org.jellyfin.sdk.model.api.PlayMethod
@@ -273,7 +272,7 @@ class JellyfinApiClient @Inject constructor(
}
val result = api.mediaSegmentsApi.getItemSegments(
itemId = mediaId,
includeSegmentTypes = listOf(MediaSegmentType.INTRO)
//includeSegmentTypes = listOf(MediaSegmentType.INTRO)
)
Log.d("getMediaSegments", result.toString())
result.content.items