mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2026-10-22 23:42:14 +03:00
Merge branch 'jukeboxstart' into 'develop'
fix: jukebox fails to send start action when starting playback with empty queue Closes #1266 See merge request ultrasonic/ultrasonic!1437
This commit is contained in:
commit
cd30bcfd2f
1 file changed
+41
-22
@@ -174,13 +174,17 @@ class JukeboxMediaPlayer(
|
||||
if (mediaItemIndex !in playlist.indices) return
|
||||
tasks.remove(Skip::class.java)
|
||||
tasks.remove(Stop::class.java)
|
||||
tasks.remove(Start::class.java)
|
||||
// Preserve a start request that has not been sent yet, so a play() followed by a
|
||||
// seek cannot silently cancel starting the jukebox. Event-sourced: only re-queued
|
||||
// when a Start task is actually pending, never from derived "desired" state.
|
||||
val startWasPending = tasks.remove(Start::class.java)
|
||||
startStatusUpdate()
|
||||
val positionSeconds = (positionMs / 1000).toInt()
|
||||
if (jukeboxStatus != null) {
|
||||
jukeboxStatus!!.positionSeconds = positionSeconds
|
||||
}
|
||||
tasks.add(Skip(mediaItemIndex, positionSeconds))
|
||||
if (startWasPending) tasks.add(Start())
|
||||
currentIndex = mediaItemIndex
|
||||
updateAvailableCommands()
|
||||
}
|
||||
@@ -206,35 +210,43 @@ class JukeboxMediaPlayer(
|
||||
null, false -> Player.STATE_IDLE
|
||||
}
|
||||
|
||||
// Note: The transport commands (play/pause, prepare, stop, seek) must ALWAYS be
|
||||
// advertised, even when the local playlist is still empty.
|
||||
//
|
||||
// Media3 silently drops player calls in the MediaController if the corresponding command
|
||||
// is not in the controller's cached copy of the available commands. This cache is updated
|
||||
// *asynchronously* from the session. When playback is started with an empty queue, the app
|
||||
// calls setMediaItems/prepare/play in rapid succession. If the available commands here
|
||||
// depended on the playlist being non-empty, the controller would evaluate the stale
|
||||
// "empty" command set, silently drop the prepare/play calls, and the Start action would
|
||||
// never be sent to the server (see https://github.com/ultrasonic/ultrasonic/issues/1008).
|
||||
//
|
||||
// For a remote jukebox the authoritative queue lives on the server, so a locally empty
|
||||
// playlist says nothing about the ability to play. All methods below are guarded anyway.
|
||||
override fun getAvailableCommands(): Player.Commands {
|
||||
val commandsBuilder = Player.Commands.Builder().addAll(
|
||||
Player.COMMAND_PLAY_PAUSE,
|
||||
Player.COMMAND_PREPARE,
|
||||
Player.COMMAND_STOP,
|
||||
Player.COMMAND_CHANGE_MEDIA_ITEMS,
|
||||
Player.COMMAND_GET_TIMELINE,
|
||||
Player.COMMAND_GET_CURRENT_MEDIA_ITEM,
|
||||
Player.COMMAND_GET_METADATA,
|
||||
Player.COMMAND_SEEK_BACK,
|
||||
Player.COMMAND_SEEK_FORWARD,
|
||||
Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM,
|
||||
Player.COMMAND_SEEK_TO_MEDIA_ITEM,
|
||||
Player.COMMAND_SEEK_TO_PREVIOUS,
|
||||
Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM,
|
||||
Player.COMMAND_GET_DEVICE_VOLUME,
|
||||
Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS,
|
||||
Player.COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS
|
||||
)
|
||||
if (isPlaying) commandsBuilder.add(Player.COMMAND_STOP)
|
||||
if (playlist.isNotEmpty()) {
|
||||
if (currentIndex < playlist.size - 1) {
|
||||
commandsBuilder.addAll(
|
||||
Player.COMMAND_GET_CURRENT_MEDIA_ITEM,
|
||||
Player.COMMAND_GET_METADATA,
|
||||
Player.COMMAND_PLAY_PAUSE,
|
||||
Player.COMMAND_PREPARE,
|
||||
Player.COMMAND_SEEK_BACK,
|
||||
Player.COMMAND_SEEK_FORWARD,
|
||||
Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM,
|
||||
Player.COMMAND_SEEK_TO_MEDIA_ITEM,
|
||||
// Seeking back is always available
|
||||
Player.COMMAND_SEEK_TO_PREVIOUS,
|
||||
Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM
|
||||
Player.COMMAND_SEEK_TO_NEXT,
|
||||
Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM
|
||||
)
|
||||
if (currentIndex < playlist.size - 1) {
|
||||
commandsBuilder.addAll(
|
||||
Player.COMMAND_SEEK_TO_NEXT,
|
||||
Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM
|
||||
)
|
||||
}
|
||||
}
|
||||
return commandsBuilder.build()
|
||||
}
|
||||
@@ -678,7 +690,7 @@ class JukeboxMediaPlayer(
|
||||
if (!running.get()) return
|
||||
tasks.remove(Skip::class.java)
|
||||
tasks.remove(Stop::class.java)
|
||||
tasks.remove(Start::class.java)
|
||||
val startWasPending = tasks.remove(Start::class.java)
|
||||
val ids: MutableList<String> = ArrayList()
|
||||
for (item in playlist) {
|
||||
ids.add(item.mediaId)
|
||||
@@ -686,6 +698,9 @@ class JukeboxMediaPlayer(
|
||||
|
||||
if (ids.isNotEmpty()) {
|
||||
tasks.add(SetPlaylist(ids))
|
||||
// Preserve a start request that arrived while the queue was being replaced,
|
||||
// so that a play() before/parallel to setMediaItems() cannot be lost.
|
||||
if (startWasPending) tasks.add(Start())
|
||||
} else {
|
||||
tasks.add(ClearPlaylist())
|
||||
}
|
||||
@@ -730,17 +745,21 @@ class JukeboxMediaPlayer(
|
||||
|
||||
fun poll(): JukeboxTask? = queue.poll(QUEUE_POLL_INTERVAL_SECONDS, TimeUnit.SECONDS)
|
||||
|
||||
fun remove(taskClass: Class<out JukeboxTask?>) {
|
||||
fun remove(taskClass: Class<out JukeboxTask?>): Boolean {
|
||||
try {
|
||||
var removed = false
|
||||
val iterator = queue.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val task = iterator.next()
|
||||
if (taskClass == task.javaClass) {
|
||||
iterator.remove()
|
||||
removed = true
|
||||
}
|
||||
}
|
||||
return removed
|
||||
} catch (x: Throwable) {
|
||||
Timber.w(x, "Failed to clean-up task queue.")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user