feat: add artwork thumbnails to player queue

- Inject UserSessionRepository into MediaRepository to access server URL
- Build artwork URLs using JellyfinImageHelper for both initial and next-up episodes
- Add artworkUrl parameter to MediaItem metadata via setArtworkUri()
- Fix PlayerQueuePanel thumbnail display with proper 4:3 aspect ratio
- Increase next-up queue count from 2 to 5 episodes
This commit is contained in:
2026-02-09 20:32:59 +01:00
parent 57a9f4f236
commit c1f733d1f3
2 changed files with 28 additions and 11 deletions

View File

@@ -1,6 +1,5 @@
package hu.bbara.purefin.player.data
import android.net.Uri
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import dagger.hilt.android.scopes.ViewModelScoped
@@ -9,10 +8,16 @@ import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.MediaSourceInfo
import java.util.UUID
import javax.inject.Inject
import androidx.core.net.toUri
import hu.bbara.purefin.image.JellyfinImageHelper
import hu.bbara.purefin.session.UserSessionRepository
import kotlinx.coroutines.flow.first
import org.jellyfin.sdk.model.api.ImageType
@ViewModelScoped
class MediaRepository @Inject constructor(
private val jellyfinApiClient: JellyfinApiClient
private val jellyfinApiClient: JellyfinApiClient,
private val userSessionRepository: UserSessionRepository
) {
suspend fun getMediaItem(mediaId: UUID): Pair<MediaItem, Long?>? {
@@ -27,11 +32,15 @@ class MediaRepository @Inject constructor(
// Calculate resume position
val resumePositionMs = calculateResumePosition(baseItem, selectedMediaSource)
val serverUrl = userSessionRepository.serverUrl.first()
val artworkUrl = JellyfinImageHelper.toImageUrl(serverUrl, mediaId, ImageType.PRIMARY)
val mediaItem = createMediaItem(
mediaId = mediaId.toString(),
playbackUrl = playbackUrl,
title = baseItem?.name ?: selectedMediaSource.name,
subtitle = "S${baseItem!!.parentIndexNumber}:E${baseItem.indexNumber}"
title = baseItem?.name ?: selectedMediaSource.name!!,
subtitle = "S${baseItem!!.parentIndexNumber}:E${baseItem.indexNumber}",
artworkUrl = artworkUrl
)
return Pair(mediaItem, resumePositionMs)
@@ -61,7 +70,8 @@ class MediaRepository @Inject constructor(
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 = 5): List<MediaItem> {
val serverUrl = userSessionRepository.serverUrl.first()
val episodes = jellyfinApiClient.getNextEpisodes(episodeId = episodeId, count = count)
return episodes.mapNotNull { episode ->
val id = episode.id ?: return@mapNotNull null
@@ -75,11 +85,13 @@ class MediaRepository @Inject constructor(
mediaId = id,
mediaSourceId = selectedMediaSource.id
) ?: return@mapNotNull null
val artworkUrl = JellyfinImageHelper.toImageUrl(serverUrl, id, ImageType.PRIMARY)
createMediaItem(
mediaId = stringId,
playbackUrl = playbackUrl,
title = episode.name ?: selectedMediaSource.name,
subtitle = "S${episode.parentIndexNumber}:E${episode.indexNumber}"
title = episode.name ?: selectedMediaSource.name!!,
subtitle = "S${episode.parentIndexNumber}:E${episode.indexNumber}",
artworkUrl = artworkUrl
)
}
}
@@ -87,15 +99,17 @@ class MediaRepository @Inject constructor(
private fun createMediaItem(
mediaId: String,
playbackUrl: String,
title: String?,
subtitle: String?
title: String,
subtitle: String?,
artworkUrl: String
): MediaItem {
val metadata = MediaMetadata.Builder()
.setTitle(title)
.setSubtitle(subtitle)
.setArtworkUri(artworkUrl.toUri())
.build()
return MediaItem.Builder()
.setUri(Uri.parse(playbackUrl))
.setUri(playbackUrl.toUri())
.setMediaId(mediaId)
.setMediaMetadata(metadata)
.build()

View File

@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -134,7 +135,9 @@ private fun QueueRow(
PurefinAsyncImage(
model = artworkUrl,
contentDescription = null,
modifier = Modifier.fillMaxWidth()
modifier = Modifier
.fillMaxWidth()
.aspectRatio(4f / 3f)
)
}
}