mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 14:55:43 +00:00
fix(orbit): three interlocking guest playback bugs (#525)
* fix(orbit): guest short-circuits queue-exhaustion fallback paths
When a guest's local queue runs out (single-track queue from `syncToHost`
empties on `audio:ended`), the player walks the standard fallback chain
in `next()`: radio top-up → infinite-queue → stop. The infinite-queue
branch builds a 6-track queue and calls `playTrack`, which trips
`orbitBulkGuard` and pops a "Add 6 tracks to the Orbit queue?" modal.
Hitting Cancel leaves playback frozen; "Add them all" injects unrelated
tracks into the host's shared queue.
In an active Orbit guest session the host owns the queue. Skip the
fallback paths entirely and just stop — the next `useOrbitGuest` pull
tick will sync to whatever the host advanced to.
Bonus side-effect: kills the deferred-promise race where a
`buildInfiniteQueueCandidates().then(...)` from a guest's track end
could resolve *after* a Catch Up replaced the queue and pop the modal
a second time against the now-current 1-track queue.
* fix(orbit): treat natural track-end as not-diverged in guest sync
When a guest's track ended naturally before the host advanced, the
divergence-detection branch read `player.isPlaying === false` and
classified it as the user manually paused — so it refused to load the
host's next track. The guest sat silent until they clicked Catch Up.
`handleAudioEnded` keeps `currentTrack` pinned to the just-ended track
and resets `currentTime` to 0, while a real manual pause leaves
`currentTime` somewhere mid-track. Use the 0-position discriminator to
classify natural-end as not-diverged so the host's new track loads.
Confirmed via the captured guest log buffer:
18:43:08.598 [track-change] host: VJkV5… → 6i6RP… BUT guest diverged
(player.isPlaying=false ≠ last.isPlaying=true)
— guest stuck for ~33s until Catch Up was pressed.
* fix(orbit): Catch Up polls until engine is ready before seeking
The 400 ms blind setTimeout in `onCatchUp` was too short for an
HTTP-streamed cold-start on high-latency links. If the audio engine
wasn't ready by then, `seek(fraction)` silently no-oped and playback
started at 0:00, making Catch Up effectively useless on exactly the
slow links where it's needed. Captured log shows a Catch Up bringing
the guest to posSec=30, then 5 s later the guest was at posSec=6
(playback restarted from the head).
Replace with the same poll-until-ready pattern `syncToHost` already
uses: check every 100 ms, fire the seek as soon as the engine reports
playing, fall back to a blind apply at the 4 s deadline.
* docs(changelog): add orbit guest playback fixes entry
* fix(orbit): debounce Catch Up button + match bar item height
Two follow-on UX fixes after PR #525's three primary bugs landed:
1. **Debounce visibility.** Drift is computed from an asymmetric signal:
guest's `currentTime` updates in coarse ~5 s chunks, while host's
position is extrapolated linearly via `(nowMs - posAt)`. Even on a
perfectly-synced session the diff swings ±5 s every tick, so the
button flickered in and out continuously. Show only after drift has
stayed over the 3 s threshold for ≥ 3 s of wall clock — measurement
noise is filtered out, real sustained drift still surfaces in time.
2. **Match neighbour height.** The button was 32 px tall against 26 px
for the other action buttons (.orbit-bar__settings) so every flicker
shifted the entire bar height. Set `height: 26 px` and tighten the
padding/font so the layout is stable regardless of visibility.
* fix(orbit): tighten queue-extension lockout + reliable initial-sync seek
Two follow-on fixes after the 4-bug umbrella:
**1. Local queue-extension paths fully off during Orbit.**
Phase check broadened from `active` to cover `starting` / `joining` /
`active` so a fetch-then-join race can't pop the bulk-add modal *after*
the join. The proactive infinite-queue topper inside `next()` (which
fires when ≤ 2 auto-tracks remain ahead) is now also gated, plus each
async `.then()` callback in the radio + infinite-queue paths re-checks
at resolution time. A `playTrack(... 6-track queue ...)` after the user
joined Orbit was the path that re-triggered the "Add 5 tracks?" modal
on a freshly-joined guest.
**2. `syncToHost` only seeks once the engine reports playing.**
The previous 2 s deadline-fallback applied the seek even when the
engine hadn't started, where the seek silently no-ops and the track
plays from 0:00. Symptom: clicking Catch Up makes the song "jump 50 %
forward" — that's the seek finally landing because the engine is now
ready, the initial-sync seek had already failed silently. New deadline
is 5 s, and on timeout we return `false` so the outer pull tick keeps
`lastAppliedRef` null and the 500 ms fast-poll retries.
* fix(orbit): double-click play button + hide preview during session
Two cucadmuh-flagged gaps:
**1. Double-click on the inline play button now reaches the orbit-add
path.** The album-track row's onDoubleClick already routes to
`addTrackToOrbit` when in Orbit, but the inline play button stopped
propagation on click — so clicking it twice just fired the "double-
click to add" hint toast and never touched the orbit queue. Add an
onDoubleClick on the button itself that delegates to the parent's
`onDoubleClickSong`.
**2. Track preview is suppressed during an Orbit session.** Preview
shares the Rust audio engine with the shared playback, so starting
one as a guest yanks the host's track out from under everyone. A new
`[data-orbit-active]` attribute on `<html>` (set whenever role is
host/guest and phase is starting/joining/active) hides every
preview button via a single CSS rule, and `previewStore.startPreview`
short-circuits as a defensive guard for keyboard shortcuts and any
programmatic callers.
This commit is contained in:
committed by
GitHub
parent
a702a5dd5b
commit
af1b9661f5
@@ -221,6 +221,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## Fixed
|
||||
|
||||
### Orbit — guest playback fixes
|
||||
|
||||
**By [@Psychotoxical](https://github.com/Psychotoxical), reported by nzxl + RavingGrob, PR [#525](https://github.com/Psychotoxical/psysonic/pull/525)**
|
||||
|
||||
* **All local queue-extension paths are suppressed for the entire Orbit session lifecycle**, not just while the session is `active`. Radio top-up, infinite-queue top-up, queue-exhaustion fallback, and the proactive "≤ 2 auto-tracks ahead" topper all refuse to extend the local queue while role is host or guest and phase is `starting` / `joining` / `active`. Even fetch promises that were already in flight re-check at resolution time, so a click-Join racing with a fetch can't pop the bulk-add modal *after* the join completes. Without this lockout the guest got a "Add 5 tracks to the Orbit queue?" prompt on track-end (offering to inject unrelated suggestions into the host's shared queue) and the local queue silently drifted off the host's playlist.
|
||||
* **Natural track-end no longer reads as "the guest manually paused"** — the divergence check in the guest pull tick now distinguishes the two via the player's `currentTime` (which `handleAudioEnded` resets to 0, while a real pause leaves it mid-track). Without the discriminator the guest sat silent on every host-driven track change that arrived in the 0–2.5 s window after the guest's own track had ended.
|
||||
* **Initial-sync and Catch Up both now wait for the audio engine to actually report playing before seeking** (up to 5 s on initial-sync, 4 s on Catch Up). The previous timeout-and-fire approach would drop a seek onto a not-yet-ready engine, where it silently no-oped — guest played from 0:00 while believing they were synced. The visible symptom was "I clicked Catch Up and the song jumped 50 % forward": the second click finally caught the engine ready, so the seek that should have happened on join finally landed.
|
||||
* **Catch Up button no longer flickers and no longer changes the bar height.** Drift is computed from a noisy signal (guest's `currentTime` updates in coarse chunks while host's position is extrapolated linearly), so the diff swings ±5 s on a normal session even when sync is fine — and the button popping in/out shifted the whole Orbit bar up and down because it was 6 px taller than the other action buttons. Visibility is now debounced (must stay over the threshold for ≥ 3 s before the button appears) and the button matches the 26 px height of its neighbours so the bar's vertical layout is stable.
|
||||
* **Double-clicking the inline play button on an album-track row now suggests/enqueues to the host's queue**, matching the existing double-click-on-row behaviour. Previously the button stopped propagation on click, so its double-click never reached the row's orbit-aware handler — clicking it twice just bounced the "double-click to add" hint toast.
|
||||
* **Track preview is now hidden + blocked during an Orbit session.** The preview path runs through the same Rust audio engine as the shared playback, so starting a preview as a guest would clobber the host's track in the local player. The preview button is hidden across all surfaces (album / artist / favourites / playlist / random-mix track lists) via a global `[data-orbit-active]` CSS rule, and `previewStore.startPreview` no-ops as a defensive guard for keyboard shortcuts and any programmatic callers.
|
||||
|
||||
### Context menu — render above the floating player bar
|
||||
|
||||
**By [@Psychotoxical](https://github.com/Psychotoxical), reported by Prymz, PR [#522](https://github.com/Psychotoxical/psysonic/pull/522)**
|
||||
|
||||
Reference in New Issue
Block a user