Four smaller fixes from real usage:
- MusicBar.ts: "votes needed" for skip depends on the connected-member
count (renderMembers/"members") AND the vote tally (applyMusicState/
"queueUpdated") - the label was only ever recomputed from the latter,
so a plain join/leave with no vote left it stale. Cached the last
MusicPublicState and factored the label math into its own
updateVoteSkipLabel(), called from both handlers now.
- Home.ts: the listen party this tab is already in still showed
"Rejoindre" on the home page. Checks each card's partyId against
musicBar/MusicBar.ts's own getActiveMusicPartyId() (already tracked
client-side) and swaps the button for an inert "En écoute" label on
that one card. No equivalent case for video - its player is a
full-screen takeover, so you can't be looking at this list from inside
one.
- MusicBar.html/.css/.ts: a read-only progress bar (a thin strip along
the bar's own top edge). Deliberately not seekable - no host to
arbitrate a jump in this model, and MusicQueueService has no "seek"
concept server-side today; that would be a real protocol addition, out
of scope here. Fill width is just currentTime/duration, ticked by the
same interval that already updates the position text.
- tiles.ts (new wireSearchFilter), views/library, views/artist,
views/album: a search input per catalog page, filtering whatever's
already rendered client-side (a library page tops out at a few hundred
items - well within "just filter the DOM" territory, no server round
trip per keystroke). Shared helper so the three pages behave
identically. The existing /api/catalog/search endpoint (video types
only, never wired into any UI) is a separate, unrelated feature - a
global cross-library search - left as-is.
Verified against the real compiled bundle: a "members" push alone
updates the skip label; the home page's own party shows "En écoute" while
others still show "Rejoindre"; the progress fill tracks a simulated
currentTime correctly; typing into a library's search box filters tiles
live with the empty-state notice appearing only when nothing matches.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reported: selecting a subtitle track (only subtitles, not audio/quality)
made the request "time out". Confirmed from the browser's own error:
hls.js gave up with a fatal fragLoadTimeOut after 4 retries, each hitting
its default 10s fragment-load timeout - not a hang, and not a repeat of
the earlier "override doesn't reach Emby" bug (see docs/architecture.md's
Player-track-selection section) - the same error's frag.baseurl already
shows SubtitleMethod=Encode correctly present in the request Emby
received, so the override chain works end to end. It's just slow: burning
a subtitle in forces a real re-encode (not a rendition, not a remux),
which measurably takes Emby longer than 10s to produce a first segment
for.
Fixed by giving both players' Hls instances explicit longer timeouts
(manifestLoadingTimeOut/levelLoadingTimeOut: 30s, fragLoadingTimeOut: 60s)
instead of hls.js's untouched defaults - applied to musicBar/MusicBar.ts
too, not just the video player, since music tracks go through the same
PlaybackInfo/transcode pipeline and could hit the same latency for a
quality change even without subtitles being involved there today.
global.d.ts's HlsStatic constructor gained an optional HlsConfig
parameter (only the four properties actually used) so this doesn't need
an `as any`/unsafe cast to pass config through.
Verified the property names are real and actually honored by hls.js
1.5.17 (the exact pinned CDN build), not silently ignored: constructed a
real Hls instance with this config and read back hls.config.* - all four
values came back as set, not as hls.js's own (tighter) defaults.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix: player settings menu stuck permanently open
----------------------------------------------
.hide { display: none; } and Player.css's .settingsPanel { display: flex;
... } are both single-class selectors - equal specificity - so whichever
one esbuild happened to bundle later into app.css won the `display`
property on an element carrying both classes. Player.css's own
display:flex always landed after base.css's .hide, so #settingsPanel
showed regardless of the hide class ever being toggled - the toggle logic
itself was correct the whole time, it just had no visible effect.
Fixed at the utility class, not the one call site: `.hide` now uses
`display: none !important`, which wins a same-specificity tie regardless
of bundling order - closes off the whole bug class, since several other
components already set their own `display` for a visible state.
Feature: music player is now a persistent bottom bar, not a page
------------------------------------------------------------------
The music player was its own route (/party/:id -> views/musicPlayer/),
which meant navigating anywhere else - the sidebar, an album to queue up
more tracks - tore the whole thing down (router.ts calls a route's
cleanup, which used to call leaveParty, on every hash change). No way to
browse and listen at the same time.
- client/src/musicBar/MusicBar.ts (new): a Shell-level singleton, mounted
once into a #musicBar element that's a sibling of #mainContent, never
touched by route changes. Renders as a fixed bottom bar. Exposes
initMusicBar/joinMusicParty/leaveMusicParty instead of the router's
render()-returns-cleanup pattern, since it never registers as a route.
- views/player/Player.ts's dispatcher: a music party no longer renders a
page - it calls joinMusicParty() and bounces back to /home (a no-op for
every reachable call site except album's). A video party now calls
leaveMusicParty() first (harmless no-op if nothing's active) - starting
or joining a video watch party always leaves the listen party, since
every party-join call site already funnels through this one dispatcher.
- views/album/Album.ts: the last per-track "start a party" UI now that
the music player's own inline browse panel is gone (browsing just
means navigating there normally). Each track's button starts a new
party or adds to whichever one is already active, decided by
getActiveMusicPartyId() at CLICK time (not render time) so the action
taken is always correct even if the label is momentarily stale; every
button is relabeled in place after a successful click. Starting a
party from here no longer navigates away at all.
- views/musicPlayer/ deleted entirely (MusicPlayer.ts/.html/.css) -
superseded by musicBar/.
- Comment updates in global.d.ts/presence.css/trackList.css/party.ts/
types.ts that named the old module by path.
Verified both fixes by rebuilding and driving the real compiled bundle
through a local static server, with fetch/window.io/window.Hls mocked:
starting a party from an album track leaves the sidebar/page in place and
relabels the other tracks; adding a track posts to the already-active
party's id; navigating into a video party hides the bar, disconnects its
socket, and posts a real leave for the music party before the video
player renders; the settings gear opens/closes correctly for the host in
that same run.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>