From f46482876611b763eacf5475ea83dccbedbc957e Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sun, 22 Oct 2023 12:49:40 +0200 Subject: [PATCH] Implement queue management in RewriteMediaManager --- .../ui/playback/LegacyMediaManager.java | 3 +- .../androidtv/ui/playback/MediaManager.kt | 2 +- .../playback/rewrite/RewriteMediaManager.kt | 93 ++++++++++++------- 3 files changed, 61 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/playback/LegacyMediaManager.java b/app/src/main/java/org/jellyfin/androidtv/ui/playback/LegacyMediaManager.java index 61df7f6810..1b6a11f424 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/playback/LegacyMediaManager.java +++ b/app/src/main/java/org/jellyfin/androidtv/ui/playback/LegacyMediaManager.java @@ -407,7 +407,7 @@ private void createAudioQueue(List items } @Override - public int queueAudioItem(org.jellyfin.sdk.model.api.BaseItemDto item) { + public void queueAudioItem(org.jellyfin.sdk.model.api.BaseItemDto item) { if (mCurrentAudioQueue == null) { createAudioQueue(new ArrayList()); clearUnShuffledQueue(); @@ -415,7 +415,6 @@ public int queueAudioItem(org.jellyfin.sdk.model.api.BaseItemDto item) { pushToUnShuffledQueue(); mCurrentAudioQueue.add(new AudioQueueItem(mCurrentAudioQueue.size(), item)); fireQueueStatusChange(); - return mCurrentAudioQueue.size()-1; } @Override diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/playback/MediaManager.kt b/app/src/main/java/org/jellyfin/androidtv/ui/playback/MediaManager.kt index 0c432671f2..6206b239b9 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/playback/MediaManager.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/playback/MediaManager.kt @@ -20,7 +20,7 @@ interface MediaManager { val managedAudioQueue: ItemRowAdapter? fun addAudioEventListener(listener: AudioEventListener) fun removeAudioEventListener(listener: AudioEventListener) - fun queueAudioItem(item: BaseItemDto?): Int + fun queueAudioItem(item: BaseItemDto) fun clearAudioQueue() fun clearAudioQueue(releasePlayer: Boolean) fun addToAudioQueue(items: List) diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/playback/rewrite/RewriteMediaManager.kt b/app/src/main/java/org/jellyfin/androidtv/ui/playback/rewrite/RewriteMediaManager.kt index cb9ddfa963..012bacbfc6 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/playback/rewrite/RewriteMediaManager.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/playback/rewrite/RewriteMediaManager.kt @@ -1,7 +1,6 @@ package org.jellyfin.androidtv.ui.playback.rewrite import android.content.Context -import android.widget.Toast import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.Job @@ -12,6 +11,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.jellyfin.androidtv.constant.QueryType +import org.jellyfin.androidtv.ui.itemhandling.AudioQueueItem import org.jellyfin.androidtv.ui.itemhandling.BaseRowItem import org.jellyfin.androidtv.ui.itemhandling.ItemRowAdapter import org.jellyfin.androidtv.ui.navigation.Destinations @@ -29,14 +29,17 @@ import org.jellyfin.playback.core.queue.item.QueueEntry import org.jellyfin.playback.jellyfin.queue.item.BaseItemDtoUserQueueEntry import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.model.api.BaseItemDto +import kotlin.math.max @Suppress("TooManyFunctions") class RewriteMediaManager( - private val context: Context, - private val api: ApiClient, + context: Context, + api: ApiClient, private val navigationRepository: NavigationRepository, private val playbackManager: PlaybackManager, ) : MediaManager { + private val queue = BaseItemQueue(api) + override fun hasAudioQueueItems(): Boolean = currentAudioQueue.size() > 0 && currentAudioItem != null override val currentAudioQueueSize: Int @@ -127,28 +130,31 @@ class RewriteMediaManager( } }.launchIn(this) - playbackManager.state.queue.entry.onEach { - // Get all items as BaseRowItem - val items = (playbackManager.state.queue.current.value as? BaseItemQueue) - ?.items - .orEmpty() - .run { - val currentItemIndex = playbackManager.state.queue.entryIndex.value ?: -1 - // Drop previous items - if (currentItemIndex >= 0) drop(currentItemIndex) else this - } - .map(::BaseRowItem) - .apply { - // Set first as playing - if (isNotEmpty()) first().playing = true - forEachIndexed { index, item -> item.index = index } - } - - // Update item row - currentAudioQueue.replaceAll(items) + playbackManager.state.queue.entry.onEach { updateAdapter() }.launchIn(this) + } - notifyListeners { onQueueReplaced() } - }.launchIn(this) + private fun updateAdapter() { + // Get all items as BaseRowItem + val items = queue + .items + // Map to audio queue items + .mapIndexed { index, item -> + AudioQueueItem(index, item).apply { + playing = playbackManager.state.queue.entryIndex.value == index + } + } + // Remove items before currently playing item + .drop(max(0, playbackManager.state.queue.entryIndex.value)) + + // Update item row + currentAudioQueue.replaceAll( + items, + areItemsTheSame = { old, new -> (old as AudioQueueItem).baseItem == (new as AudioQueueItem).baseItem }, + // The equals functions for BaseRowItem only compare by id + areContentsTheSame = { _, _ -> false }, + ) + + notifyListeners { onQueueReplaced() } } private fun notifyListeners(body: AudioEventListener.() -> Unit) { @@ -166,10 +172,8 @@ class RewriteMediaManager( } } - override fun queueAudioItem(item: BaseItemDto?): Int { - // TODO - Toast.makeText(context, "queueAudioItem() - Not yet implemented", Toast.LENGTH_LONG).show() - return 0 + override fun queueAudioItem(item: BaseItemDto) { + addToAudioQueue(listOf(item)) } override fun clearAudioQueue() { @@ -181,13 +185,32 @@ class RewriteMediaManager( } override fun addToAudioQueue(items: List) { - // TODO - Toast.makeText(context, "addToAudioQueue() - Not yet implemented", Toast.LENGTH_LONG).show() + if (items.isEmpty()) return + + val addIndex = when (playbackManager.state.playState.value) { + PlayState.PLAYING -> playbackManager.state.queue.entryIndex.value + 1 + else -> 0 + } + + queue.items.addAll(addIndex, items) + + if ( + playbackManager.state.queue.current.value != queue || + playbackManager.state.playState.value != PlayState.PLAYING + ) { + playbackManager.state.setPlaybackOrder(if (isShuffleMode) PlaybackOrder.SHUFFLE else PlaybackOrder.DEFAULT) + playbackManager.state.play(queue) + } + + updateAdapter() } override fun removeFromAudioQueue(ndx: Int) { - // TODO - Toast.makeText(context, "removeFromAudioQueue() - Not yet implemented", Toast.LENGTH_LONG).show() + // Disallow removing currently playing item (legacy UI cannot keep up) + if (playbackManager.state.queue.entryIndex.value == ndx) return + + queue.items.removeAt(ndx) + updateAdapter() } override val isPlayingAudio: Boolean @@ -195,7 +218,8 @@ class RewriteMediaManager( override fun playNow(context: Context, items: List, position: Int, shuffle: Boolean) { val filteredItems = items.drop(position) - val queue = BaseItemQueue(filteredItems, api) + queue.items.clear() + queue.items.addAll(filteredItems) playbackManager.state.setPlaybackOrder(if (shuffle) PlaybackOrder.SHUFFLE else PlaybackOrder.DEFAULT) playbackManager.state.play(queue) @@ -275,9 +299,10 @@ class RewriteMediaManager( } private class BaseItemQueue( - val items: List, private val api: ApiClient, ) : Queue { + val items = mutableListOf() + override val size: Int get() = items.size