Add metadata to media session (#934)

## Description
Adds some metadata about playing media for consumption in remote control
apps such as Google Home or others.

### Related issues
Partially address #889, doesn't add explicit controls yet, but Google
Home works

### Testing
Nvidia shield + Google Home app

## Screenshots
iOS Google Home app:

![ios_google_home](https://github.com/user-attachments/assets/680ccae5-b66e-404a-a3a1-7c89991d841b)


## AI or LLM usage
None
This commit is contained in:
Ray 2026-02-20 14:28:57 -05:00 committed by GitHub
parent a8ab002dd1
commit dfd9acf561
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 38 additions and 2 deletions

View file

@ -159,13 +159,14 @@ class ImageUrlService
imageType: ImageType,
fillWidth: Int? = null,
fillHeight: Int? = null,
useSeriesForPrimary: Boolean? = null,
): String? =
if (item != null) {
getItemImageUrl(
itemId = item.id,
itemType = item.type,
seriesId = item.data.seriesId,
useSeriesForPrimary = item.useSeriesForPrimary,
useSeriesForPrimary = useSeriesForPrimary ?: item.useSeriesForPrimary,
imageTags = item.data.imageTags.orEmpty(),
imageType = imageType,
parentThumbId = item.data.parentThumbItemId,

View file

@ -1,7 +1,12 @@
package com.github.damontecres.wholphin.ui.playback
import androidx.core.net.toUri
import androidx.media3.common.MediaMetadata
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.Chapter
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.TrickplayInfo
import org.jellyfin.sdk.model.extensions.ticks
data class CurrentMediaInfo(
val sourceId: String?,
@ -15,3 +20,22 @@ data class CurrentMediaInfo(
val EMPTY = CurrentMediaInfo(null, null, listOf(), listOf(), listOf(), null)
}
}
fun BaseItem.toMediaMetadata(imageUrl: String?): MediaMetadata =
MediaMetadata
.Builder()
.setTitle(title)
.setSubtitle(subtitle)
.setReleaseYear(data.productionYear)
.setDescription(data.overview)
.setArtworkUri(imageUrl?.toUri())
.setDurationMs(data.runTimeTicks?.ticks?.inWholeMilliseconds)
.setMediaType(
when (type) {
BaseItemKind.MOVIE -> MediaMetadata.MEDIA_TYPE_MOVIE
BaseItemKind.EPISODE -> MediaMetadata.MEDIA_TYPE_TV_SHOW
BaseItemKind.VIDEO -> MediaMetadata.MEDIA_TYPE_VIDEO
BaseItemKind.TV_CHANNEL, BaseItemKind.CHANNEL, BaseItemKind.LIVE_TV_CHANNEL -> MediaMetadata.MEDIA_TYPE_TV_CHANNEL
else -> MediaMetadata.MEDIA_TYPE_VIDEO
},
).build()

View file

@ -41,6 +41,7 @@ import com.github.damontecres.wholphin.preferences.SkipSegmentBehavior
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.DatePlayedService
import com.github.damontecres.wholphin.services.DeviceProfileService
import com.github.damontecres.wholphin.services.ImageUrlService
import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.services.PlayerFactory
import com.github.damontecres.wholphin.services.PlaylistCreationResult
@ -95,6 +96,7 @@ import org.jellyfin.sdk.api.client.extensions.videosApi
import org.jellyfin.sdk.api.sockets.subscribe
import org.jellyfin.sdk.model.DeviceInfo
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.api.MediaSegmentDto
import org.jellyfin.sdk.model.api.MediaSegmentType
import org.jellyfin.sdk.model.api.MediaStreamType
@ -137,6 +139,7 @@ class PlaybackViewModel
private val refreshRateService: RefreshRateService,
val streamChoiceService: StreamChoiceService,
private val userPreferencesService: UserPreferencesService,
private val imageUrlService: ImageUrlService,
@Assisted private val destination: Destination,
) : ViewModel(),
Player.Listener,
@ -671,7 +674,15 @@ class PlaybackViewModel
MediaItem
.Builder()
.setMediaId(itemId.toString())
.setUri(mediaUrl.toUri())
.setMediaMetadata(
item.toMediaMetadata(
imageUrlService.getItemImageUrl(
item,
ImageType.PRIMARY,
useSeriesForPrimary = true,
),
),
).setUri(mediaUrl.toUri())
.setSubtitleConfigurations(listOfNotNull(externalSubtitle))
.apply {
when (source.container) {