fix(orbit): event-driven host push + guest seekbar lock (#537)

* fix(orbit): event-driven host push on play/pause flips

Without this, the worst-case delay between "host hits pause" and "guest
stops" is two full polling windows (host's 2.5 s push tick + guest's
2.5 s read tick, plus network) — long enough for the guest to noticeably
run past the host. Subscribing to playerStore.isPlaying changes adds at
most one extra remote write per flip; non-flip state ticks still ride
the existing 2.5 s timer. The listener filters on isPlaying so the
per-second currentTime ticks don't trigger spurious pushes.

* fix(orbit): lock seekbar for guests — sync follows the host

Guests could drag/click/wheel the seekbar, which would jump the local
player and then snap back at the next host poll (2.5 s of inconsistent
UX) — or push the guest into a diverged state where Catch Up was the
only way back. The seekbar is host-controlled in Orbit; the guest input
path now reflects that.

- App.tsx exposes `data-orbit-role="host"|"guest"` on the root element
  alongside the existing `data-orbit-active` marker.
- WaveformSeek's container gains a `.waveform-seek-container` class so
  CSS can target it.
- Guest rule: `pointer-events: none` on children blocks click / drag /
  wheel / hover; the parent keeps `cursor: not-allowed` + reduced opacity
  so the disabled state is visually unambiguous.

Hosts and non-orbit users see no change.

* docs(changelog): credit PR #537 (orbit sync latency + guest seekbar)
This commit is contained in:
Frank Stellmacher
2026-05-11 13:34:02 +02:00
committed by GitHub
parent 64b33e6941
commit 123fbcc802
5 changed files with 39 additions and 2 deletions
+2
View File
@@ -255,6 +255,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* **Catch Up button stays clickable on high-latency sessions** (PR [#527](https://github.com/Psychotoxical/psysonic/pull/527)). On a real-world high-latency session the genuine drift fluctuates between ~1 s and ~8 s in lockstep with both sides' chunked `currentTime` updates — the previous single-stage debounce hid the button as soon as drift briefly dipped below 3 s even though the baseline drift was still 58 s, so the button "appeared and vanished too fast to click". Two-stage hysteresis now: show after drift > 3 s for 3 s, hide only after drift < 1 s for 1 s.
* **Initial-sync seek visually sticks on join** (PR [#528](https://github.com/Psychotoxical/psysonic/pull/528)). On join the waveform briefly showed the host's live position then snapped back to 0:00 with audio playing from the start — the post-`playTrack` poll fired `applyMirror`'s seek against `playTrack`'s optimistic `isPlaying: true`, before the Tauri `audio_play` had actually produced any samples. The seek's store update landed (waveform at 70 %) but the `audio_seek` debounced behind it no-oped on the not-ready engine, and the engine's first progress events from 0:00 overwrote the optimistic position. Now the poll waits for `currentTime > 0.1` before applying the seek, and `applyMirror` defers the play-state mirror by 200 ms so the seek's invoke wins the IPC ordering race against pause/resume.
* **Host single-track plays no longer wipe the Orbit queue** (PR [#529](https://github.com/Psychotoxical/psysonic/pull/529)). A `playTrack(track, [track])` call from any UI that passes an explicit 1-track replacement queue (e.g. OfflineLibrary's "Play this album" on a single-track album) slipped past the orbit bulk-guard (which only fires for `queue.length > 1`) and replaced the host's `playerStore.queue`. Since the host's queue *is* the shared Orbit queue, that one click destroyed every accepted guest suggestion + every upcoming track. Now intercepted: when role is host and the incoming queue is a single track, append + jump instead of replacing.
* **Host pause / resume reaches guests immediately** (PR [#537](https://github.com/Psychotoxical/psysonic/pull/537), reported by xrexy on Discord). The host previously pushed state only on a 2.5 s timer; combined with the guest's 2.5 s poll plus network latency, a `pause` could take up to ~5 s to land — long enough for the guest to noticeably run past the host. The host now also pushes state on every `isPlaying` flip, in addition to the timer. Non-flip ticks are unchanged, so baseline traffic stays the same.
* **Guest seekbar is read-only inside an Orbit session** (PR [#537](https://github.com/Psychotoxical/psysonic/pull/537), reported by xrexy on Discord). Drag / click / wheel / hover are all disabled on the waveform while you're a guest; the bar shows as dimmed with a `not-allowed` cursor so the disabled state is unambiguous. Previously a guest seek would jump the local player and either snap back at the next host poll (inconsistent UX) or push the guest into a diverged state where Catch Up was the only way back. Hosts and non-orbit users see no change.
### Context menu — render above the floating player bar