MusicD Remote · design note

The Queue Rewind Problem

Played tracks disappear from the Queue screen because Roon's extension API cannot look backwards. Here is exactly what it can do, what rewinding would cost, and what the app does instead.

Shipped v1.7.80 Findings from node-roon-api-transport Status two open questions

What you see

the reported behaviour

A track finishes and it is gone from the queue. Pick a track further down the list and everything you skipped over goes with it. The queue only ever shows what is playing and what is coming — there is no way to look back at what just played, and no way to return to it.

That is not a gap in the app. It is the shape of the API underneath it.

Everything Roon's transport API can do

node-roon-api-transport, complete surface

22 verbs · the three that touch a queue are marked
  • change_settings
  • change_volume
  • control
  • convenience_switch
  • get_outputs
  • get_zones
  • group_outputs
  • mute
  • mute_all
  • pause_all
  • play_from_here
  • seek
  • standby
  • subscribe_outputs
  • subscribe_queue
  • subscribe_zones
  • toggle_standby
  • transfer_zone
  • ungroup_outputs
  • zone_by_object
  • zone_by_output_id
  • zone_by_zone_id
subscribe_queue reads the queue forward from the current track. play_from_here jumps to an item that is still in it. That is the whole of it.

What is not there

  • No queue history. Nothing reports a track that has already left.
  • No remove. A queued track cannot be taken out.
  • No insert or reorder. Nothing can place a track at a position.
  • No clear. The queue is only ever replaced, as a side effect of Play Now.

The only way to put anything into a queue is the browse hierarchy: navigate to the album, open it, find the track, open its action menu, invoke Play Now, Add Next or Queue. Every one of those is a round trip to the Core.

Where the tracks go

the queue, as the API sees it

Played & skipped
First · Second · Third

Not in the subscription. Their queue ids are spent.

▶ now
Reported
Current · Up Next · …

What subscribe_queue returns, and all it returns.

A departed track is not merely hidden — the queue item that identified it no longer exists, so play_from_here has nothing to aim at.

What rewinding would cost

counted from the browse path, per track

With no queue-write verb, restoring the queue around a played track means playing that track and then re-queueing every track that followed it — each one a full browse navigation:

Rewinding into a queue of…Tracks to rebuildCore calls
20 tracks19152
40 tracks39312
100 tracks99792
250 tracks2491,992

The call count is not even the worst of it. The rebuild has to open with Play Now, which destroys the live queue before anything refills it — so an interruption anywhere in the middle leaves the zone worse off than when you started. The app already runs this pattern for playlist import, one track at a time behind a progress toast, and that is with a queue nobody is standing in the middle of.

What shipped instead

v1.7.77 — v1.7.80

Keep

Played tracks are retained and folded away

A collapsed “N played earlier” disclosure above the Now playing divider, newest sitting against it. A skipped track shows how far it got (0:12 / 3:20) so a skip reads as a skip and not as a very short track.

Free

Built from a signal already arriving

Departures are recorded from the zone push the extension already handles, at the one moment the outgoing track and how much of it played are both known. It costs no extra Core calls at all. Resolving a track back to its library album is likewise free — an index built from ordinary album opens.

Limit

Tapping one plays it next, not from there

A single browse navigation that inserts the track after the current one and leaves the queue standing. It is honestly a different action from Roon's own, and the screen says so rather than pretending to rewind.

Pick

Several at once, in the order you choose

Selecting turns each row into a numbered pick, because the order you tap is not the order the rows are shown in — and it is the order they will play in. The whole selection goes as one request: separate ones would race and interleave into an arbitrary queue order, which is the thing the feature exists to get right.

Declined

No queue rebuilding

Hundreds of Core calls, minutes of traffic, and a destroyed queue if it is interrupted. The arithmetic above is the reason, and it does not improve with cleverness.

The record is per zone and in memory. It describes a listening session; one that outlived a restart would offer tracks the zone's queue has no relationship to any more. A zone that disappears and comes back starts clean.

The open question

one experiment, decisive either way

Will play_from_here accept a queue id that has already been consumed?

Nothing in the API documents an answer, and it cannot be settled without a live Core. If it works, retaining the original queue ids turns a perfect rewind into a single call and everything above becomes unnecessary. If it errors — the likelier outcome, since the queue item is gone — the design here stands as the honest ceiling.

Worth running once before any further work on this.

Does a repeated Add Next stack, or keep its own order?

A multi-track “play next” only lands in the right order if the sends go out in the order that arrives right. Add Next puts an item immediately after the current track, so issued repeatedly it should stack each new one in front of the last — meaning the sends have to go out backwards. That is what ships.

It cannot be settled by probing, because finding out costs a real insert and there is no verb to remove one again. So it is an assumption in one named function, and if a queue ever comes out reversed the fix is to stop reversing — one line, nothing else to change.