Cast a listen party too, and fix a slow/freezing catalog #1

Merged
kyuno merged 2 commits from feedback/queue-cast-catalog-perf into master 2026-09-15 22:39:48 +02:00
Owner

Summary

  • Casting now works for listen parties too, not just video - reuses the same CastTokenService/PlaybackDriver the video case introduced, and re-casts in place on every track change instead of dropping the session (unlike video, where any item change just drops back to local).
  • The listen-party queue popup now spans the bar's full width instead of a fixed 480px column, and its "File d'attente" header stays put while the track list itself scrolls.
  • Fixed a slow, freeze-prone catalog: getLibraryItems was fetching an unused Overview field for every item, getArtists had no cap at all, and a library grid inserted up to 300 tiles in one synchronous innerHTML write - now chunked across animation frames.

See docs/architecture.md for the full writeup of each (new sections: "Casting a listen party", "The catalog API being slow, and the front freezing while it loads", plus a follow-up note under the original queue-layout section).

Test plan

  • npm run typecheck / npm test / npm run build on both server/ and client/ (122/122 server tests passing)
  • Chunked-rendering mechanism verified directly against a real DOM (synthetic 300-item list, confirms every item lands and a button in the last chunk is still clickable through the new delegated listener)
  • Not verified: no real Chromecast or Emby login reachable from this sandbox - the on-TV re-cast round trip and the catalog's real-world before/after latency are unconfirmed

🤖 Generated with Claude Code

## Summary - Casting now works for listen parties too, not just video - reuses the same `CastTokenService`/`PlaybackDriver` the video case introduced, and re-casts in place on every track change instead of dropping the session (unlike video, where any item change just drops back to local). - The listen-party queue popup now spans the bar's full width instead of a fixed 480px column, and its "File d'attente" header stays put while the track list itself scrolls. - Fixed a slow, freeze-prone catalog: `getLibraryItems` was fetching an unused `Overview` field for every item, `getArtists` had no cap at all, and a library grid inserted up to 300 tiles in one synchronous `innerHTML` write - now chunked across animation frames. See `docs/architecture.md` for the full writeup of each (new sections: "Casting a listen party", "The catalog API being slow, and the front freezing while it loads", plus a follow-up note under the original queue-layout section). ## Test plan - [x] `npm run typecheck` / `npm test` / `npm run build` on both `server/` and `client/` (122/122 server tests passing) - [x] Chunked-rendering mechanism verified directly against a real DOM (synthetic 300-item list, confirms every item lands and a button in the *last* chunk is still clickable through the new delegated listener) - [ ] Not verified: no real Chromecast or Emby login reachable from this sandbox - the on-TV re-cast round trip and the catalog's real-world before/after latency are unconfirmed 🤖 Generated with [Claude Code](https://claude.com/claude-code)
kyuno added 1 commit 2026-09-15 21:33:06 +02:00
Three separate reports handled together.

Cast was video-only by design (docs/architecture.md flagged it as
video-parties-only for v1) - asked for directly, and turned out to need
almost nothing new server-side: CastTokenService and the 4 streaming
routes' sessionForStream fallback were already keyed on nothing more
specific than partyId. cast.ts's localDriver only needed its parameter
widened from HTMLVideoElement to HTMLMediaElement - both already share
the same currentTime/paused/duration/play()/pause() surface. MusicBar.ts
gained the same activeDriver indirection Player.ts already has, with one
real difference from the video case: a listen party's queue advances
far more often than a video party changes items, so dropping the cast
on every track (the video path's own choice there) would have made this
worse than not having it - a track change while casting instead calls
startCast() again, reusing the already-connected session (a
cache-busting query param added since the content URL itself never
changes between tracks - the server alone decides what's current).
Falls back to local playback if a re-cast throws, same recovery
instinct loadTrack's own hls.js try/catch already has.

The queue popup: reported separately, a fixed 480px width and a header
that scrolled away with the list - both leftover CSS from before the
row shape was reworked, never revisited since. Width cap dropped;
overflow/max-height moved off the outer .musicBarQueue (which wraps the
header too) down onto #queueList alone.

The catalog: still too slow, the front freezing while it loads.
CatalogService.getLibraryItems was asking Emby for Overview on every
one of up to 300 items and never using it (views/library's tiles only
ever show a poster/name/year) - dropped. getArtists had no Limit at
all, unlike every sibling query. Separately, tiles.ts's shared render
path built one HTML string for a whole batch and inserted it via a
single innerHTML write - fine for a 16-item carousel, a real hitch for
a 300-tile grid. renderTilesInChunks inserts the same tileHtml output
40 at a time across requestAnimationFrame callbacks instead;
wireTileButtons switched to one delegated listener so a tile from a
later chunk is still clickable.

Verified: full typecheck/test/build on both projects (122/122 server
tests). The chunked-render mechanism was verified directly - a
synthetic 300-item list against a real DOM confirms every item lands
and a button in the last chunk still fires through the delegated
listener. Not verified: no real Chromecast or Emby login reachable from
this sandbox, so the actual on-TV re-cast round trip and the
before/after catalog latency against a real large library are both
unconfirmed - same standing gap this project already flags for its
other casting work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyuno added 1 commit 2026-09-15 22:17:42 +02:00
Run CI's test job on PRs too, not just pushes to master
Some checks failed
Publier l'image Docker / test (pull_request) Failing after 4s
Publier l'image Docker / publish (pull_request) Has been skipped
d815f4e0f5
Discovered while opening the first-ever PR against this repo (previous
sessions always pushed straight to master): publish-docker.yml only
ever triggered on push (branches: [master]) or a tag - there was no
pull_request trigger at all, so a PR sat with zero CI signal until
whatever merged it also happened to push to master for the first time.

Added pull_request (branches: [master]) alongside the existing
triggers. The publish job needed its own guard for this to be safe -
it has no event-type check today, just needs: test, so without one, a
PR's own CI run would have gone on to build and push a real Docker
image (latest included) to the production registry from
unreviewed, unmerged code. if: github.event_name != 'pull_request'
keeps a PR run to the test job alone; push/tag/workflow_dispatch are
unaffected.

Note: a pull_request trigger only takes effect once it exists on the
BASE branch (master) - GitHub/Forgejo Actions won't run a trigger a PR
branch merely adds to its own copy of the workflow, for the obvious
reason (a PR from anywhere could otherwise grant itself a run with
repo secrets). So this doesn't give the PR carrying it its own CI run -
only the next one, once this merges. Verified by reading the existing
workflow and the job dependency it already had (needs: test with no
event guard); not verified against a real PR run (can't be, until this
lands on master) - flagged the same way this project already flags
what it can't check from here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyuno merged commit 78b6b00ae2 into master 2026-09-15 22:39:48 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: kyuno/watchparty#1
No description provided.