Skip to content

Commit

Permalink
Refactor QueueEntry design
Browse files Browse the repository at this point in the history
  • Loading branch information
nielsvanvelzen committed May 5, 2024
1 parent 73626e1 commit 01642e6
Show file tree
Hide file tree
Showing 27 changed files with 204 additions and 164 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ import org.jellyfin.playback.core.model.PlayState
import org.jellyfin.playback.core.model.PlaybackOrder
import org.jellyfin.playback.core.model.RepeatMode
import org.jellyfin.playback.core.queue.Queue
import org.jellyfin.playback.core.queue.item.QueueEntry
import org.jellyfin.playback.jellyfin.queue.item.BaseItemDtoUserQueueEntry
import org.jellyfin.playback.core.queue.QueueEntry
import org.jellyfin.playback.jellyfin.queue.baseItem
import org.jellyfin.playback.jellyfin.queue.createBaseItemQueueEntry
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.model.api.BaseItemDto
import kotlin.math.max
Expand Down Expand Up @@ -59,7 +60,7 @@ class RewriteMediaManager(
?: currentAudioQueue.size()).toString()

override val currentAudioItem: BaseItemDto?
get() = (playbackManager.state.queue.entry.value as? BaseItemDtoUserQueueEntry)?.baseItem
get() = playbackManager.state.queue.entry.value?.baseItem

override fun toggleRepeat(): Boolean {
val newMode = when (playbackManager.state.repeatMode.value) {
Expand Down Expand Up @@ -277,7 +278,7 @@ class RewriteMediaManager(

override suspend fun getItem(index: Int): QueueEntry? {
val item = items.getOrNull(index) ?: return null
return BaseItemDtoUserQueueEntry.build(api, item)
return createBaseItemQueueEntry(api, item)
}
}
}
8 changes: 8 additions & 0 deletions playback/core/src/main/kotlin/element/ElementKey.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.jellyfin.playback.core.element

/**
* A key to identify the type of an element.
*/
class ElementKey<T : Any>(val name: String) {
override fun toString(): String = "ElementKey $name"
}
26 changes: 26 additions & 0 deletions playback/core/src/main/kotlin/element/ElementsContainer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.jellyfin.playback.core.element

import java.util.concurrent.ConcurrentHashMap

/**
* Container to hold elements identified with an [ElementKey].
*/
open class ElementsContainer {
private val elements = ConcurrentHashMap<ElementKey<*>, Any?>()

fun <T : Any> get(key: ElementKey<T>): T = getOrNull(key)
?: error("No element found for key $key.")

@Suppress("UNCHECKED_CAST")
fun <T : Any> getOrNull(key: ElementKey<T>): T? = elements[key] as T?

operator fun <T : Any> contains(key: ElementKey<T>): Boolean = elements.containsKey(key)

fun <T : Any> put(key: ElementKey<T>, value: T) {
elements[key] = value
}

fun <T : Any> remove(key: ElementKey<T>) {
elements.remove(key)
}
}
40 changes: 40 additions & 0 deletions playback/core/src/main/kotlin/element/delegates.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.jellyfin.playback.core.element

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

/**
* Delegate for an optional element.
*/
fun <T : Any> element(
key: ElementKey<T>
) = object : ReadWriteProperty<ElementsContainer, T?> {
override fun getValue(thisRef: ElementsContainer, property: KProperty<*>): T? =
thisRef.getOrNull(key)

override fun setValue(thisRef: ElementsContainer, property: KProperty<*>, value: T?) {
if (value == null) thisRef.remove(key)
else thisRef.put(key, value)
}
}

/**
* Delegate for an required element.
*/
fun <T : Any> requiredElement(
key: ElementKey<T>,
computeDefault: () -> T,
) = object : ReadWriteProperty<ElementsContainer, T> {
override fun getValue(thisRef: ElementsContainer, property: KProperty<*>): T {
val value = thisRef.getOrNull(key)
if (value != null) return value

val default = computeDefault()
thisRef.put(key, default)
return default
}

override fun setValue(thisRef: ElementsContainer, property: KProperty<*>, value: T) {
thisRef.put(key, value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import org.jellyfin.playback.core.PlaybackManager
import org.jellyfin.playback.core.model.PlayState
import org.jellyfin.playback.core.model.PlaybackOrder
import org.jellyfin.playback.core.model.RepeatMode
import org.jellyfin.playback.core.queue.metadata
import timber.log.Timber
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import androidx.media3.session.MediaStyleNotificationHelper
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.jellyfin.playback.core.plugin.PlayerService
import org.jellyfin.playback.core.queue.item.QueueEntry
import org.jellyfin.playback.core.queue.QueueEntry
import org.jellyfin.playback.core.queue.metadata

class MediaSessionService(
private val androidContext: Context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package org.jellyfin.playback.core.mediasession
import androidx.core.net.toUri
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import org.jellyfin.playback.core.queue.item.QueueEntryMetadata
import org.jellyfin.playback.core.queue.QueueEntryMetadata

fun QueueEntryMetadata.toMediaItem() = MediaItem.Builder().apply {
if (mediaId != null) setMediaId(mediaId)
Expand Down
2 changes: 1 addition & 1 deletion playback/core/src/main/kotlin/mediastream/MediaStream.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.jellyfin.playback.core.mediastream

import org.jellyfin.playback.core.queue.item.QueueEntry
import org.jellyfin.playback.core.queue.QueueEntry

interface MediaStream {
val identifier: String
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.jellyfin.playback.core.mediastream

import org.jellyfin.playback.core.queue.item.QueueEntry
import org.jellyfin.playback.core.queue.QueueEntry
import org.jellyfin.playback.core.support.PlaySupportReport

/**
Expand Down
2 changes: 0 additions & 2 deletions playback/core/src/main/kotlin/queue/EmptyQueue.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.jellyfin.playback.core.queue

import org.jellyfin.playback.core.queue.item.QueueEntry

data object EmptyQueue : Queue {
override val size: Int = 0
override suspend fun getItem(index: Int): QueueEntry? = null
Expand Down
2 changes: 0 additions & 2 deletions playback/core/src/main/kotlin/queue/PagedQueue.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.jellyfin.playback.core.queue

import org.jellyfin.playback.core.queue.item.QueueEntry

abstract class PagedQueue(
private val pageSize: Int = 10,
) : Queue {
Expand Down
1 change: 0 additions & 1 deletion playback/core/src/main/kotlin/queue/PlayerQueueState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import org.jellyfin.playback.core.mediastream.PlayableMediaStream
import org.jellyfin.playback.core.model.PlayState
import org.jellyfin.playback.core.model.PlaybackOrder
import org.jellyfin.playback.core.model.RepeatMode
import org.jellyfin.playback.core.queue.item.QueueEntry
import org.jellyfin.playback.core.queue.order.DefaultOrderIndexProvider
import org.jellyfin.playback.core.queue.order.OrderIndexProvider
import org.jellyfin.playback.core.queue.order.RandomOrderIndexProvider
Expand Down
2 changes: 0 additions & 2 deletions playback/core/src/main/kotlin/queue/Queue.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.jellyfin.playback.core.queue

import org.jellyfin.playback.core.queue.item.QueueEntry

/**
* A queue contains all items in the current playback session. This includes already played items,
* the currently playing item and future items.
Expand Down
9 changes: 9 additions & 0 deletions playback/core/src/main/kotlin/queue/QueueEntry.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.jellyfin.playback.core.queue

import org.jellyfin.playback.core.element.ElementsContainer

/**
* The QueueEntry is a single item in a queue and can represent any supported media type.
* All related data is stored in elements via the [ElementsContainer].
*/
class QueueEntry : ElementsContainer()
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.jellyfin.playback.core.queue.item
package org.jellyfin.playback.core.queue

import org.jellyfin.playback.core.element.ElementKey
import org.jellyfin.playback.core.element.requiredElement
import java.time.LocalDate
import kotlin.time.Duration

Expand Down Expand Up @@ -33,3 +35,10 @@ data class QueueEntryMetadata(
val Empty = QueueEntryMetadata()
}
}

private val metadataKey = ElementKey<QueueEntryMetadata>("QueueEntryMetadata")

/**
* Get or set the [QueueEntryMetadata] for this [QueueEntry]. Defaults to [QueueEntryMetadata.Empty].
*/
var QueueEntry.metadata by requiredElement(metadataKey) { QueueEntryMetadata.Empty }
2 changes: 0 additions & 2 deletions playback/core/src/main/kotlin/queue/SequenceQueue.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.jellyfin.playback.core.queue

import org.jellyfin.playback.core.queue.item.QueueEntry

abstract class SequenceQueue : Queue {
companion object {
const val MAX_SIZE = 100
Expand Down
36 changes: 0 additions & 36 deletions playback/core/src/main/kotlin/queue/item/QueueEntry.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import org.jellyfin.playback.core.mediastream.MediaConversionMethod
import org.jellyfin.playback.core.mediastream.MediaStream
import org.jellyfin.playback.core.mediastream.MediaStreamContainer
import org.jellyfin.playback.core.mediastream.PlayableMediaStream
import org.jellyfin.playback.core.queue.item.QueueEntry
import org.jellyfin.playback.core.queue.QueueEntry
import org.jellyfin.playback.core.support.PlaySupportReport
import org.jellyfin.playback.jellyfin.queue.item.BaseItemDtoUserQueueEntry
import org.jellyfin.playback.jellyfin.queue.baseItem
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.audioApi
import org.jellyfin.sdk.api.client.extensions.dynamicHlsApi
Expand Down Expand Up @@ -51,18 +51,18 @@ class AudioMediaStreamResolver(
queueEntry: QueueEntry,
testStream: (stream: MediaStream) -> PlaySupportReport,
): PlayableMediaStream? {
if (queueEntry !is BaseItemDtoUserQueueEntry) return null
if (queueEntry.baseItem.type != BaseItemKind.AUDIO) return null
val baseItem = queueEntry.baseItem
if (baseItem == null || baseItem.type != BaseItemKind.AUDIO) return null

val mediaInfo = getPlaybackInfo(queueEntry.baseItem)
val mediaInfo = getPlaybackInfo(baseItem)

// Test for direct play support
val directPlayStream = mediaInfo.getDirectPlayStream()
if (testStream(directPlayStream).canPlay) {
return directPlayStream.toPlayableMediaStream(
queueEntry = queueEntry,
url = api.audioApi.getAudioStreamUrl(
itemId = queueEntry.baseItem.id,
itemId = baseItem.id,
mediaSourceId = mediaInfo.mediaSource.id,
playSessionId = mediaInfo.playSessionId,
static = true,
Expand All @@ -78,7 +78,7 @@ class AudioMediaStreamResolver(
return remuxStream.toPlayableMediaStream(
queueEntry = queueEntry,
url = api.audioApi.getAudioStreamByContainerUrl(
itemId = queueEntry.baseItem.id,
itemId = baseItem.id,
mediaSourceId = mediaInfo.mediaSource.id,
playSessionId = mediaInfo.playSessionId,
container = container,
Expand All @@ -96,7 +96,7 @@ class AudioMediaStreamResolver(
return transcodeStream.toPlayableMediaStream(
queueEntry = queueEntry,
url = api.dynamicHlsApi.getMasterHlsAudioPlaylistUrl(
itemId = queueEntry.baseItem.id,
itemId = baseItem.id,
mediaSourceId = requireNotNull(mediaInfo.mediaSource.id),
playSessionId = mediaInfo.playSessionId,
tag = mediaInfo.mediaSource.eTag,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package org.jellyfin.playback.jellyfin.mediastream
import org.jellyfin.playback.core.mediastream.MediaConversionMethod
import org.jellyfin.playback.core.mediastream.MediaStream
import org.jellyfin.playback.core.mediastream.PlayableMediaStream
import org.jellyfin.playback.core.queue.item.QueueEntry
import org.jellyfin.playback.core.queue.QueueEntry
import org.jellyfin.playback.core.support.PlaySupportReport
import org.jellyfin.playback.jellyfin.queue.item.BaseItemDtoUserQueueEntry
import org.jellyfin.playback.jellyfin.queue.baseItem
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.universalAudioApi
import org.jellyfin.sdk.model.api.BaseItemKind
Expand All @@ -19,13 +19,13 @@ class UniversalAudioMediaStreamResolver(
queueEntry: QueueEntry,
testStream: (stream: MediaStream) -> PlaySupportReport,
): PlayableMediaStream? {
if (queueEntry !is BaseItemDtoUserQueueEntry) return null
if (queueEntry.baseItem.type != BaseItemKind.AUDIO) return null
val baseItem = queueEntry.baseItem
if (baseItem == null || baseItem.type != BaseItemKind.AUDIO) return null

val mediaInfo = getPlaybackInfo(queueEntry.baseItem)
val mediaInfo = getPlaybackInfo(baseItem)

val url = api.universalAudioApi.getUniversalAudioStreamUrl(
itemId = queueEntry.baseItem.id,
itemId = baseItem.id,
mediaSourceId = mediaInfo.mediaSource.id,
enableRedirection = false,
enableRemoteMedia = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import org.jellyfin.playback.core.mediastream.PlayableMediaStream
import org.jellyfin.playback.core.model.PlayState
import org.jellyfin.playback.core.model.RepeatMode
import org.jellyfin.playback.core.plugin.PlayerService
import org.jellyfin.playback.jellyfin.queue.item.BaseItemDtoUserQueueEntry
import org.jellyfin.playback.jellyfin.queue.baseItem
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.playStateApi
import org.jellyfin.sdk.model.api.PlayMethod
Expand Down Expand Up @@ -40,12 +40,6 @@ class PlaySessionService(
}.launchIn(coroutineScope)
}

private val PlayableMediaStream.baseItem
get() = when (val entry = queueEntry) {
is BaseItemDtoUserQueueEntry -> entry.baseItem
else -> null
}

private val MediaConversionMethod.playMethod
get() = when (this) {
MediaConversionMethod.None -> PlayMethod.DIRECT_PLAY
Expand Down Expand Up @@ -94,12 +88,12 @@ class PlaySessionService(
// backend.
return state.queue
.peekNext(15)
.filterIsInstance<BaseItemDtoUserQueueEntry>()
.map { QueueItem(id = it.baseItem.id, playlistItemId = it.baseItem.playlistItemId) }
.mapNotNull { it.baseItem }
.map { QueueItem(id = it.id, playlistItemId = it.playlistItemId) }
}

private suspend fun sendStreamStart(stream: PlayableMediaStream) {
val item = stream.baseItem ?: return
val item = stream.queueEntry.baseItem ?: return
api.playStateApi.reportPlaybackStart(PlaybackStartInfo(
itemId = item.id,
playSessionId = stream.identifier,
Expand All @@ -117,7 +111,7 @@ class PlaySessionService(
}

private suspend fun sendStreamUpdate(stream: PlayableMediaStream) {
val item = stream.baseItem ?: return
val item = stream.queueEntry.baseItem ?: return
api.playStateApi.reportPlaybackProgress(PlaybackProgressInfo(
itemId = item.id,
playSessionId = stream.identifier,
Expand All @@ -135,7 +129,7 @@ class PlaySessionService(
}

private suspend fun sendStreamStop(stream: PlayableMediaStream) {
val item = stream.baseItem ?: return
val item = stream.queueEntry.baseItem ?: return
api.playStateApi.reportPlaybackStopped(PlaybackStopInfo(
itemId = item.id,
playSessionId = stream.identifier,
Expand Down
Loading

0 comments on commit 01642e6

Please sign in to comment.