mirror of
https://github.com/bbara04/Purefin.git
synced 2026-03-31 17:10:08 +02:00
feat: enhance media item retrieval to include resume position calculation
This commit is contained in:
@@ -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.authenticateUserByName
|
||||||
import org.jellyfin.sdk.api.client.extensions.itemsApi
|
import org.jellyfin.sdk.api.client.extensions.itemsApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.mediaInfoApi
|
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.tvShowsApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.userApi
|
import org.jellyfin.sdk.api.client.extensions.userApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.userViewsApi
|
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.api.client.extensions.videosApi
|
||||||
import org.jellyfin.sdk.createJellyfin
|
import org.jellyfin.sdk.createJellyfin
|
||||||
import org.jellyfin.sdk.model.ClientInfo
|
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.DeviceProfile
|
||||||
import org.jellyfin.sdk.model.api.ItemFields
|
import org.jellyfin.sdk.model.api.ItemFields
|
||||||
import org.jellyfin.sdk.model.api.MediaSourceInfo
|
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.PlaybackInfoDto
|
||||||
import org.jellyfin.sdk.model.api.PlaybackOrder
|
import org.jellyfin.sdk.model.api.PlaybackOrder
|
||||||
import org.jellyfin.sdk.model.api.PlaybackProgressInfo
|
import org.jellyfin.sdk.model.api.PlaybackProgressInfo
|
||||||
import org.jellyfin.sdk.model.api.PlaybackStartInfo
|
import org.jellyfin.sdk.model.api.PlaybackStartInfo
|
||||||
import org.jellyfin.sdk.model.api.PlaybackStopInfo
|
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.RepeatMode
|
||||||
import org.jellyfin.sdk.model.api.SubtitleDeliveryMethod
|
import org.jellyfin.sdk.model.api.SubtitleDeliveryMethod
|
||||||
import org.jellyfin.sdk.model.api.SubtitleProfile
|
import org.jellyfin.sdk.model.api.SubtitleProfile
|
||||||
@@ -189,7 +189,7 @@ class JellyfinApiClient @Inject constructor(
|
|||||||
}
|
}
|
||||||
val result = api.userLibraryApi.getItem(
|
val result = api.userLibraryApi.getItem(
|
||||||
itemId = mediaId,
|
itemId = mediaId,
|
||||||
userId = getUserId(),
|
userId = getUserId()
|
||||||
)
|
)
|
||||||
Log.d("getItemInfo", result.content.toString())
|
Log.d("getItemInfo", result.content.toString())
|
||||||
return result.content
|
return result.content
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import androidx.media3.common.MediaItem
|
|||||||
import androidx.media3.common.MediaMetadata
|
import androidx.media3.common.MediaMetadata
|
||||||
import dagger.hilt.android.scopes.ViewModelScoped
|
import dagger.hilt.android.scopes.ViewModelScoped
|
||||||
import hu.bbara.purefin.client.JellyfinApiClient
|
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 java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -13,7 +15,7 @@ class MediaRepository @Inject constructor(
|
|||||||
private val jellyfinApiClient: JellyfinApiClient
|
private val jellyfinApiClient: JellyfinApiClient
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun getMediaItem(mediaId: UUID): MediaItem? {
|
suspend fun getMediaItem(mediaId: UUID): Pair<MediaItem, Long?>? {
|
||||||
val mediaSources = jellyfinApiClient.getMediaSources(mediaId)
|
val mediaSources = jellyfinApiClient.getMediaSources(mediaId)
|
||||||
val selectedMediaSource = mediaSources.firstOrNull() ?: return null
|
val selectedMediaSource = mediaSources.firstOrNull() ?: return null
|
||||||
val playbackUrl = jellyfinApiClient.getMediaPlaybackUrl(
|
val playbackUrl = jellyfinApiClient.getMediaPlaybackUrl(
|
||||||
@@ -21,12 +23,42 @@ class MediaRepository @Inject constructor(
|
|||||||
mediaSourceId = selectedMediaSource.id
|
mediaSourceId = selectedMediaSource.id
|
||||||
) ?: return null
|
) ?: return null
|
||||||
val baseItem = jellyfinApiClient.getItemInfo(mediaId)
|
val baseItem = jellyfinApiClient.getItemInfo(mediaId)
|
||||||
return createMediaItem(
|
|
||||||
|
// Calculate resume position
|
||||||
|
val resumePositionMs = calculateResumePosition(baseItem, selectedMediaSource)
|
||||||
|
|
||||||
|
val mediaItem = createMediaItem(
|
||||||
mediaId = mediaId.toString(),
|
mediaId = mediaId.toString(),
|
||||||
playbackUrl = playbackUrl,
|
playbackUrl = playbackUrl,
|
||||||
title = baseItem?.name ?: selectedMediaSource.name,
|
title = baseItem?.name ?: selectedMediaSource.name,
|
||||||
subtitle = "S${baseItem!!.parentIndexNumber}:E${baseItem.indexNumber}"
|
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> {
|
suspend fun getNextUpMediaItems(episodeId: UUID, existingIds: Set<String>, count: Int = 2): List<MediaItem> {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import hu.bbara.purefin.player.manager.PlayerManager
|
|||||||
import hu.bbara.purefin.player.manager.ProgressManager
|
import hu.bbara.purefin.player.manager.ProgressManager
|
||||||
import hu.bbara.purefin.player.model.PlayerUiState
|
import hu.bbara.purefin.player.model.PlayerUiState
|
||||||
import hu.bbara.purefin.player.model.TrackOption
|
import hu.bbara.purefin.player.model.TrackOption
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -18,6 +17,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class PlayerViewModel @Inject constructor(
|
class PlayerViewModel @Inject constructor(
|
||||||
@@ -133,9 +133,15 @@ class PlayerViewModel @Inject constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val mediaItem = mediaRepository.getMediaItem(uuid)
|
val result = mediaRepository.getMediaItem(uuid)
|
||||||
if (mediaItem != null) {
|
if (result != null) {
|
||||||
|
val (mediaItem, resumePositionMs) = result
|
||||||
|
|
||||||
playerManager.play(mediaItem)
|
playerManager.play(mediaItem)
|
||||||
|
|
||||||
|
// Seek to resume position after play() is called
|
||||||
|
resumePositionMs?.let { playerManager.seekTo(it) }
|
||||||
|
|
||||||
if (dataErrorMessage != null) {
|
if (dataErrorMessage != null) {
|
||||||
dataErrorMessage = null
|
dataErrorMessage = null
|
||||||
_uiState.update { it.copy(error = null) }
|
_uiState.update { it.copy(error = null) }
|
||||||
|
|||||||
Reference in New Issue
Block a user