From b6019bd211ef62a10bc55ba67602037885ab5eb6 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Sun, 13 Sep 2026 23:47:12 +0200 Subject: [PATCH] Live lobby, host handoff reflected live, remove join codes, fix player scroll Three requested changes plus one found-in-passing bug fix: 1. Home page's "watch parties en cours" is now a live Socket.IO subscription (connectLobby -> "lobby" room), not a one-shot fetch - updates the instant anything changes anywhere (create/join/leave). Host name + member count come from the snapshot; elapsed time ticks purely client-side from a createdUtc timestamp (no point pushing a recomputed duration every second for every viewer). home.js returns this subscription from its router cleanup, same leak-prevention pattern as the player's socket. 2. Host handoff is now live end to end, not just correct in the data model. PartyManager.leave already promoted another member and destroyed an empty party (that part predates this change) - what was missing was any way for an already-open follower player to find out it became host. applyLeave (shared by the socket leave/ disconnect handlers and the REST leave endpoint, so this and the lobby broadcast only live in one place) now emits a personal "hostChanged" to the new host's own socket; player.js's becomeHost() unlocks controls live. becomeHost is written to tolerate being called more than once safely (addEventListener no-ops on a repeat identical listener; the heartbeat setInterval is explicitly guarded since it has no such idempotence - calling it twice would leak the first interval, the exact bug class the previous pass fixed). 3. No more join codes - PartyManager no longer generates or tracks one. The live lobby list is the only discovery mechanism now; a code copied out of band doesn't add anything on top of a list that's already always current. POST /api/parties/:partyId/join replaces :joinCode; the "Rejoindre avec un code" UI is gone. Found while touching this: the home page's "Rejoindre" button used to navigate to the player WITHOUT ever calling the join endpoint first - it loaded fine (GET /api/parties/:id never checked membership) but the realtime "join" was silently rejected server-side (not an actual member), so sync never worked for anyone joining this way. Fixed to await joinParty() before navigating, matching how the library grid's tiles already did it. Also: .playerWrap was width:100%/height:100vh inside .mainContent's own padding, so the two stacked and made the page taller than the viewport - an outer scrollbar despite the video already being full- height. Changed to position:fixed/inset:0 so it fills the real viewport regardless of ancestor padding. Verified live against a real running server (see session history): lobby subscription renders host/member-count/ticking-elapsed correctly from a faked "parties" push (createdUtc 2:05 in the past rendered "2:06" after ~1s more, proving the client-side tick is real, not static); the join-then-navigate fix calls the join endpoint before navigating; .playerWrap computes to position:fixed with all four insets at 0. docs/architecture.md has the full writeup. Co-Authored-By: Claude Sonnet 5 --- README.md | 13 +++-- client/css/app.css | 11 +++-- client/js/party.js | 33 +++++++++---- client/js/views/home.js | 96 +++++++++++++++++++++++++------------ client/js/views/player.js | 53 +++++++++++++------- docs/architecture.md | 68 ++++++++++++++++++++++++++ server/src/index.js | 27 ++++++----- server/src/partyManager.js | 52 +++++++++----------- server/src/socketHandler.js | 95 +++++++++++++++++++++++++----------- 9 files changed, 315 insertions(+), 133 deletions(-) diff --git a/README.md b/README.md index 39654d0..1a15b0e 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,18 @@ including why this replaced an earlier Emby-plugin attempt (still in 1. Sign in with your Emby account (just username/password — the server address is fixed server-side, see Deploying below). 2. A sidebar lists your Emby libraries; the home page shows every ongoing - watch party plus a "recently added" row per library, Emby-style. + watch party live (host, how long it's been running, member count) plus a + "recently added" row per library, Emby-style. No join codes — if a party + is running, it's already on the list, updating in real time. 3. Pick a movie/episode tile and hit "Lancer une watch party" (or drill into - a Series' seasons/episodes first) — you become the host — or join one - with a 6-character code. + a Series' seasons/episodes first) — you become the host — or hit + "Rejoindre" on any party already listed on the home page. 4. The host's play/pause/seek drives everyone else's player automatically; starting from an episode queues the rest of the series so the host can - just hit "Épisode suivant" instead of restarting a party each time. + just hit "Épisode suivant" instead of restarting a party each time. If + the host leaves, the party hands off to another member automatically + (their player unlocks live, no reload needed) — only closing once + everyone's gone. Everything — catalog, images, video, sync — flows through this app's own backend, which proxies whatever it needs from Emby server-side. The browser diff --git a/client/css/app.css b/client/css/app.css index ab469f6..a0cca9c 100644 --- a/client/css/app.css +++ b/client/css/app.css @@ -412,9 +412,14 @@ button.linklike:hover { background: none; color: var(--accent-bright); } /* ---- Player ---- */ .playerWrap { - position: relative; - width: 100%; - height: 100vh; + /* Fixed + inset so it fills the actual viewport regardless of + .mainContent's own padding (which used to add to 100vh and push the + whole page taller than the screen, causing an outer scrollbar). Also + covers the sidebar while playing, like most video players' theater + mode - "Quitter" is how you get back to it. */ + position: fixed; + inset: 0; + z-index: 20; background: #000; } diff --git a/client/js/party.js b/client/js/party.js index e9daf7c..5b36904 100644 --- a/client/js/party.js +++ b/client/js/party.js @@ -3,6 +3,9 @@ // Realtime is Socket.IO (window.io, loaded via