feat: enhance media item retrieval to include resume position calculation

This commit is contained in:
2026-02-05 20:13:06 +01:00
parent 3fed91aa27
commit 6977acc60f
3 changed files with 46 additions and 8 deletions

View File

@@ -9,11 +9,11 @@ import org.jellyfin.sdk.api.client.Response
import org.jellyfin.sdk.api.client.extensions.authenticateUserByName
import org.jellyfin.sdk.api.client.extensions.itemsApi
import org.jellyfin.sdk.api.client.extensions.mediaInfoApi
import org.jellyfin.sdk.api.client.extensions.playStateApi
import org.jellyfin.sdk.api.client.extensions.tvShowsApi
import org.jellyfin.sdk.api.client.extensions.userApi
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
import org.jellyfin.sdk.api.client.extensions.userViewsApi
import org.jellyfin.sdk.api.client.extensions.playStateApi
import org.jellyfin.sdk.api.client.extensions.videosApi
import org.jellyfin.sdk.createJellyfin
import org.jellyfin.sdk.model.ClientInfo
@@ -24,12 +24,12 @@ 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.MediaSourceInfo
import org.jellyfin.sdk.model.api.PlayMethod
import org.jellyfin.sdk.model.api.PlaybackInfoDto
import org.jellyfin.sdk.model.api.PlaybackOrder
import org.jellyfin.sdk.model.api.PlaybackProgressInfo
import org.jellyfin.sdk.model.api.PlaybackStartInfo
import org.jellyfin.sdk.model.api.PlaybackStopInfo
import org.jellyfin.sdk.model.api.PlayMethod
import org.jellyfin.sdk.model.api.RepeatMode
import org.jellyfin.sdk.model.api.SubtitleDeliveryMethod
import org.jellyfin.sdk.model.api.SubtitleProfile
@@ -189,7 +189,7 @@ class JellyfinApiClient @Inject constructor(
}
val result = api.userLibraryApi.getItem(
itemId = mediaId,
userId = getUserId(),
userId = getUserId()
)
Log.d("getItemInfo", result.content.toString())
return result.content

View File

@@ -5,6 +5,8 @@ import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import dagger.hilt.android.scopes.ViewModelScoped
import hu.bbara.purefin.client.JellyfinApiClient
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.MediaSourceInfo
import java.util.UUID
import javax.inject.Inject
@@ -13,7 +15,7 @@ class MediaRepository @Inject constructor(
private val jellyfinApiClient: JellyfinApiClient
) {
suspend fun getMediaItem(mediaId: UUID): MediaItem? {
suspend fun getMediaItem(mediaId: UUID): Pair<MediaItem, Long?>? {
val mediaSources = jellyfinApiClient.getMediaSources(mediaId)
val selectedMediaSource = mediaSources.firstOrNull() ?: return null
val playbackUrl = jellyfinApiClient.getMediaPlaybackUrl(
@@ -21,12 +23,42 @@ class MediaRepository @Inject constructor(
mediaSourceId = selectedMediaSource.id
) ?: return null
val baseItem = jellyfinApiClient.getItemInfo(mediaId)
return createMediaItem(
// Calculate resume position
val resumePositionMs = calculateResumePosition(baseItem, selectedMediaSource)
val mediaItem = createMediaItem(
mediaId = mediaId.toString(),
playbackUrl = playbackUrl,
title = baseItem?.name ?: selectedMediaSource.name,
subtitle = "S${baseItem!!.parentIndexNumber}:E${baseItem.indexNumber}"
)
return Pair(mediaItem, resumePositionMs)
}
private fun calculateResumePosition(
baseItem: BaseItemDto?,
mediaSource: MediaSourceInfo
): Long? {
val userData = baseItem?.userData ?: return null
// Get runtime in ticks
val runtimeTicks = mediaSource.runTimeTicks ?: baseItem.runTimeTicks ?: 0L
if (runtimeTicks == 0L) return null
// Get saved playback position from userData
val playbackPositionTicks = userData.playbackPositionTicks ?: 0L
if (playbackPositionTicks == 0L) return null
// Convert ticks to milliseconds
val positionMs = playbackPositionTicks / 10_000
// Calculate percentage for threshold check
val percentage = (playbackPositionTicks.toDouble() / runtimeTicks.toDouble()) * 100.0
// Apply thresholds: resume only if 5% ≤ progress ≤ 95%
return if (percentage in 5.0..95.0) positionMs else null
}
suspend fun getNextUpMediaItems(episodeId: UUID, existingIds: Set<String>, count: Int = 2): List<MediaItem> {

View File

@@ -9,7 +9,6 @@ import hu.bbara.purefin.player.manager.PlayerManager
import hu.bbara.purefin.player.manager.ProgressManager
import hu.bbara.purefin.player.model.PlayerUiState
import hu.bbara.purefin.player.model.TrackOption
import javax.inject.Inject
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
@@ -18,6 +17,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.util.UUID
import javax.inject.Inject
@HiltViewModel
class PlayerViewModel @Inject constructor(
@@ -133,9 +133,15 @@ class PlayerViewModel @Inject constructor(
return
}
viewModelScope.launch {
val mediaItem = mediaRepository.getMediaItem(uuid)
if (mediaItem != null) {
val result = mediaRepository.getMediaItem(uuid)
if (result != null) {
val (mediaItem, resumePositionMs) = result
playerManager.play(mediaItem)
// Seek to resume position after play() is called
resumePositionMs?.let { playerManager.seekTo(it) }
if (dataErrorMessage != null) {
dataErrorMessage = null
_uiState.update { it.copy(error = null) }