Add media type API to set proper content type in audio attributes

This commit is contained in:
Niels van Velzen authored and Niels van Velzen committed 2026-02-03 09:00:05 +01:00
1 parent 7acfc41498
commit d41f992b62
5 files changed
+99 -10

No files matched your search

@@ -0,0 +1,21 @@
package org.jellyfin.playback.core.mediastream.mediatype
/**
* Definition of the type for a specific media.
*/
enum class MediaType {
/**
* Media type for primarily video content like movies, tv shows and live tv.
*/
Video,
/**
* Media type for primarily audio content like music, podcasts and audio books.
*/
Audio,
/**
* Unknown media type.
*/
Unknown,
}
@@ -0,0 +1,20 @@
package org.jellyfin.playback.core.mediastream.mediatype
import org.jellyfin.playback.core.element.ElementKey
import org.jellyfin.playback.core.element.elementFlow
import org.jellyfin.playback.core.element.requiredElement
import org.jellyfin.playback.core.queue.QueueEntry
private val mediaTypeKey = ElementKey<MediaType>("MediaType")
/**
* Get or set the media type this [QueueEntry]. A supported backend will use this to
* set correct audio attributes for the media.
*/
var QueueEntry.mediaType by requiredElement(mediaTypeKey) { MediaType.Unknown }
/**
* Get the flow of [mediaType].
* @see mediaType
*/
val QueueEntry.mediaTypeFlow by elementFlow(mediaTypeKey)
@@ -1,5 +1,6 @@
package org.jellyfin.playback.jellyfin.queue
import org.jellyfin.playback.core.mediastream.mediatype.mediaType
import org.jellyfin.playback.core.mediastream.normalizationGain
import org.jellyfin.playback.core.queue.QueueEntry
import org.jellyfin.playback.core.queue.QueueEntryMetadata
@@ -10,6 +11,8 @@ import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.extensions.ticks
import java.util.UUID
import org.jellyfin.playback.core.mediastream.mediatype.MediaType as PlayerMediaType
import org.jellyfin.sdk.model.api.MediaType as SdkMediaType
/**
* Create a [QueueEntry] from a [BaseItemDto].
@@ -46,6 +49,13 @@ fun createBaseItemQueueEntry(api: ApiClient, baseItem: BaseItemDto): QueueEntry
)
entry.baseItem = baseItem
entry.normalizationGain = baseItem.normalizationGain
entry.mediaType = when (baseItem.mediaType) {
SdkMediaType.VIDEO -> PlayerMediaType.Video
SdkMediaType.AUDIO -> PlayerMediaType.Audio
SdkMediaType.PHOTO,
SdkMediaType.BOOK,
SdkMediaType.UNKNOWN -> PlayerMediaType.Unknown
}
return entry
}
@@ -0,0 +1,24 @@
package org.jellyfin.playback.media3.exoplayer
import androidx.media3.common.AudioAttributes
import timber.log.Timber
class AudioAttributeState {
private var _audioAttributes: AudioAttributes? = null
fun updateAudioAttributes(
builder: AudioAttributes.Builder.() -> Unit,
onChange: (audioAttributes: AudioAttributes) -> Unit,
) {
val newAudioAttributes = AudioAttributes.Builder().apply {
builder()
}.build()
if (_audioAttributes != newAudioAttributes) {
Timber.d("Audio attributes changed")
onChange(newAudioAttributes)
_audioAttributes = newAudioAttributes
}
}
}
@@ -5,7 +5,6 @@ import android.content.Context
import android.view.ViewGroup
import androidx.annotation.OptIn
import androidx.core.content.getSystemService
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
@@ -27,6 +26,8 @@ import org.jellyfin.playback.core.backend.BasePlayerBackend
import org.jellyfin.playback.core.mediastream.MediaStream
import org.jellyfin.playback.core.mediastream.PlayableMediaStream
import org.jellyfin.playback.core.mediastream.mediaStream
import org.jellyfin.playback.core.mediastream.mediatype.MediaType
import org.jellyfin.playback.core.mediastream.mediatype.mediaType
import org.jellyfin.playback.core.mediastream.normalizationGain
import org.jellyfin.playback.core.model.PlayState
import org.jellyfin.playback.core.model.PositionInfo
@@ -53,7 +54,9 @@ class ExoPlayerBackend(
private var currentStream: PlayableMediaStream? = null
private var subtitleView: SubtitleView? = null
private var audioPipeline = ExoPlayerAudioPipeline()
private val audioPipeline = ExoPlayerAudioPipeline()
private val audioAttributeState = AudioAttributeState()
private val exoPlayer by lazy {
val dataSourceFactory = DefaultDataSource.Factory(
context,
@@ -95,9 +98,6 @@ class ExoPlayerBackend(
})
})
.setMediaSourceFactory(mediaSourceFactory)
.setAudioAttributes(AudioAttributes.Builder().apply {
setUsage(C.USAGE_MEDIA)
}.build(), true)
.setPauseAtEndOfMediaItems(true)
.build()
.also { player ->
@@ -208,19 +208,33 @@ class ExoPlayerBackend(
preparedItemIndex = exoPlayer.mediaItemCount - 1
}
// Determine how to play item
if (preparedItemIndex == exoPlayer.currentMediaItemIndex) return
Timber.i("Playing ${item.mediaStream?.url}")
// Seek to prepared media item
when (preparedItemIndex) {
exoPlayer.currentMediaItemIndex - 1 -> exoPlayer.seekToPreviousMediaItem()
exoPlayer.currentMediaItemIndex + 1 -> exoPlayer.seekToNextMediaItem()
exoPlayer.currentMediaItemIndex -> Unit
else -> exoPlayer.seekTo(preparedItemIndex, 0)
}
// Update audio attributes
val contentType = when (item.mediaType) {
MediaType.Video -> C.AUDIO_CONTENT_TYPE_MOVIE
MediaType.Audio -> C.AUDIO_CONTENT_TYPE_MUSIC
MediaType.Unknown -> C.AUDIO_CONTENT_TYPE_UNKNOWN
}
audioAttributeState.updateAudioAttributes(
builder = {
setContentType(contentType)
setUsage(C.USAGE_MEDIA)
},
onChange = { audioAttributes ->
exoPlayer.setAudioAttributes(audioAttributes, true)
}
)
// Enjoy!
Timber.i("Playing ${item.mediaStream?.url}")
exoPlayer.play()
}