Skip to content

Commit

Permalink
Convert queue service to core functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
nielsvanvelzen committed May 30, 2023
1 parent 04cb64b commit eb68aaa
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import org.jellyfin.playback.core.model.PlayState
import org.jellyfin.playback.core.model.PlaybackOrder
import org.jellyfin.playback.core.queue.Queue
import org.jellyfin.playback.core.queue.item.QueueEntry
import org.jellyfin.playback.core.queue.queue
import org.jellyfin.playback.jellyfin.queue.item.BaseItemDtoUserQueueEntry
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.model.api.BaseItemDto
Expand All @@ -44,7 +43,7 @@ class RewriteMediaManager(
get() = currentAudioQueue.size()

override val currentAudioQueuePosition: Int
get() = if ((playbackManager.queue?.currentItemPosition ?: -1) >= 0) 0 else -1
get() = if ((playbackManager.state.queue.entryIndex.value) >= 0) 0 else -1

override val currentAudioPosition: Long
get() = playbackManager.state.positionInfo.active.inWholeMilliseconds
Expand All @@ -53,11 +52,11 @@ class RewriteMediaManager(
get() = (currentAudioQueuePosition + 1).toString()

override val currentAudioQueueDisplaySize: String
get() = ((playbackManager.state.queue.value as? BaseItemQueue)?.items?.size
get() = ((playbackManager.state.queue.current.value as? BaseItemQueue)?.items?.size
?: currentAudioQueue.size()).toString()

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

override fun toggleRepeat(): Boolean {
isRepeatMode = !isRepeatMode
Expand Down Expand Up @@ -123,21 +122,21 @@ class RewriteMediaManager(
}

launch {
playbackManager.state.queue.collect {
playbackManager.state.queue.current.collect {
notifyListeners {
onQueueStatusChanged(hasAudioQueueItems())
}
}
}

launch {
playbackManager.state.currentEntry.collect {
playbackManager.state.queue.entry.collect {
// Get all items as BaseRowItem
val items = (playbackManager.state.queue.value as? BaseItemQueue)
val items = (playbackManager.state.queue.current.value as? BaseItemQueue)
?.items
.orEmpty()
.run {
val currentItemIndex = playbackManager.queue?.currentItemPosition ?: -1
val currentItemIndex = playbackManager.state.queue.entryIndex.value ?: -1
// Drop previous items
if (currentItemIndex >= 0) drop(currentItemIndex) else this
}
Expand Down Expand Up @@ -216,7 +215,7 @@ class RewriteMediaManager(
}

override fun playFrom(ndx: Int): Boolean = runBlocking {
playbackManager.queue?.setIndex(ndx) != null
playbackManager.state.queue.setIndex(ndx) != null
}

override fun shuffleAudioQueue() {
Expand All @@ -229,29 +228,29 @@ class RewriteMediaManager(
}

override val nextAudioItem: BaseItemDto?
get() = runBlocking { (playbackManager.queue?.peekNext() as? BaseItemDtoUserQueueEntry)?.baseItem }
get() = runBlocking { (playbackManager.state.queue.peekNext() as? BaseItemDtoUserQueueEntry)?.baseItem }

override val prevAudioItem: BaseItemDto?
get() = runBlocking { (playbackManager.queue?.peekPrevious() as? BaseItemDtoUserQueueEntry)?.baseItem }
get() = runBlocking { (playbackManager.state.queue.peekPrevious() as? BaseItemDtoUserQueueEntry)?.baseItem }

override fun hasNextAudioItem(): Boolean = runBlocking {
playbackManager.queue?.peekNext() != null
playbackManager.state.queue.peekNext() != null
}

override fun hasPrevAudioItem(): Boolean = (playbackManager.queue?.currentItemPosition ?: 0) > 0
override fun hasPrevAudioItem(): Boolean = playbackManager.state.queue.entryIndex.value > 0

override fun nextAudioItem(): Int {
runBlocking { playbackManager.queue?.next() }
runBlocking { playbackManager.state.queue.next() }
notifyListeners { onQueueStatusChanged(hasAudioQueueItems()) }

return playbackManager.queue?.currentItemPosition ?: -1
return playbackManager.state.queue.entryIndex.value
}

override fun prevAudioItem(): Int {
runBlocking { playbackManager.queue?.previous() }
runBlocking { playbackManager.state.queue.previous() }
notifyListeners { onQueueStatusChanged(hasAudioQueueItems()) }

return playbackManager.queue?.currentItemPosition ?: -1
return playbackManager.state.queue.entryIndex.value
}

override fun stopAudio(releasePlayer: Boolean) {
Expand Down
5 changes: 2 additions & 3 deletions playback/core/src/main/kotlin/PlaybackManager.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.jellyfin.playback.core

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
Expand All @@ -8,7 +9,6 @@ import org.jellyfin.playback.core.backend.PlayerBackend
import org.jellyfin.playback.core.mediastream.MediaStreamResolver
import org.jellyfin.playback.core.mediastream.MediaStreamService
import org.jellyfin.playback.core.plugin.PlayerService
import org.jellyfin.playback.core.queue.QueueService
import timber.log.Timber
import kotlin.reflect.KClass

Expand All @@ -22,15 +22,14 @@ class PlaybackManager internal constructor(
val backendService = BackendService()

private val job = SupervisorJob(parentJob)
val state = MutablePlayerState(options, backendService)
val state: PlayerState = MutablePlayerState(options, CoroutineScope(Job(job)), backendService)

init {
backendService.switchBackend(backend)
services.forEach { it.initialize(this, state, Job(job)) }

// Add default services
addService(MediaStreamService(mediaStreamResolvers))
addService(QueueService { entry -> state.setCurrentEntry(entry) })
}

fun addService(service: PlayerService) {
Expand Down
30 changes: 13 additions & 17 deletions playback/core/src/main/kotlin/PlayerState.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.jellyfin.playback.core

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
Expand All @@ -10,14 +11,14 @@ import org.jellyfin.playback.core.model.PlayState
import org.jellyfin.playback.core.model.PlaybackOrder
import org.jellyfin.playback.core.model.PositionInfo
import org.jellyfin.playback.core.model.VideoSize
import org.jellyfin.playback.core.queue.DefaultPlayerQueueState
import org.jellyfin.playback.core.queue.EmptyQueue
import org.jellyfin.playback.core.queue.PlayerQueueState
import org.jellyfin.playback.core.queue.Queue
import org.jellyfin.playback.core.queue.item.QueueEntry
import kotlin.time.Duration

interface PlayerState {
val queue: StateFlow<Queue>
val currentEntry: StateFlow<QueueEntry?>
val queue: PlayerQueueState
val playState: StateFlow<PlayState>
val speed: StateFlow<Float>
val videoSize: StateFlow<VideoSize>
Expand All @@ -30,9 +31,8 @@ interface PlayerState {
*/
val positionInfo: PositionInfo

// Queueing

fun play(queue: Queue)
// Queue management
fun play(playQueue: Queue)
fun stop()

// Pausing
Expand All @@ -55,13 +55,10 @@ interface PlayerState {

class MutablePlayerState(
private val options: PlaybackManagerOptions,
scope: CoroutineScope,
private val backendService: BackendService,
) : PlayerState {
private val _queue = MutableStateFlow<Queue>(EmptyQueue)
override val queue: StateFlow<Queue> get() = _queue.asStateFlow()

private val _currentEntry = MutableStateFlow<QueueEntry?>(null)
override val currentEntry: StateFlow<QueueEntry?> get() = _currentEntry.asStateFlow()
override val queue: PlayerQueueState

private val _playState = MutableStateFlow(PlayState.STOPPED)
override val playState: StateFlow<PlayState> get() = _playState.asStateFlow()
Expand Down Expand Up @@ -90,14 +87,13 @@ class MutablePlayerState(

override fun onMediaStreamEnd(mediaStream: MediaStream) = Unit
})
}

fun setCurrentEntry(currentEntry: QueueEntry?) {
_currentEntry.value = currentEntry
queue = DefaultPlayerQueueState(this, scope, backendService)
}

override fun play(queue: Queue) {
_queue.value = queue
override fun play(playQueue: Queue) {
queue.replaceQueue(playQueue)
backendService.backend?.play()
}

override fun pause() {
Expand All @@ -111,7 +107,7 @@ class MutablePlayerState(

override fun stop() {
backendService.backend?.stop()
_queue.value = EmptyQueue
queue.replaceQueue(EmptyQueue)
}

override fun seek(to: Duration) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class MediaSessionService(
}

coroutineScope.launch {
state.currentEntry.collect { item ->
state.queue.entry.collect { item ->
val mediaItem = withContext(Dispatchers.IO) { item?.metadata?.toMediaItemWithBitmaps() }
glue.currentMediaItem = mediaItem
glue.notifyCallbacks { onCurrentMediaItemChanged(glue, mediaItem) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.playback.core.plugin.PlayerService
import org.jellyfin.playback.core.queue.queue
import timber.log.Timber

class MediaStreamService(
private val mediaStreamResolvers: List<MediaStreamResolver>,
) : PlayerService() {
override suspend fun onInitialize() {
coroutineScope.launch {
state.currentEntry.collect { entry ->
state.queue.entry.collect { entry ->
if (entry != null) {
val stream = mediaStreamResolvers.firstNotNullOfOrNull { it.getStream(entry) }
if (stream != null) {
Expand All @@ -26,7 +25,7 @@ class MediaStreamService(
// Preload next item
// TODO: Do this once current stream is near it's end instead
// TODO: Move to queue service
val nextItem = manager.queue?.peekNext()
val nextItem = state.queue.peekNext()
if (nextItem != null) {
val stream = mediaStreamResolvers.firstNotNullOfOrNull { it.getStream(nextItem) }
if (stream != null) {
Expand Down
Loading

0 comments on commit eb68aaa

Please sign in to comment.