All checks were successful
Publier l'image Docker / publish (push) Successful in 7s
Big UI pass towards looking/behaving like Emby. Deferred: music (needs
its own player + a real-time collaborative playlist - different enough
subsystem to be its own pass; sidebar/routing already treat it as a
first-class collectionType, page just says "not yet" for now).
Layout:
- client/js/shell.js: persistent sidebar (one entry per Emby library,
from GET /api/catalog/libraries which now also returns
collectionType) + a #mainContent the router swaps on navigation.
router.js made idempotent (start() safe to call more than once) so
a login -> shell boot can happen twice in one page lifetime (logout
then back in) without piling up duplicate hashchange listeners.
- app.js's boot() renders either the bare login screen or the full
shell depending on session state; login.js calls it again on
success instead of navigate() (no router running yet at that point).
Browsing, by collection type:
- views/home.js (route /home): a carousel of every active party
server-wide, a join-by-code fallback, then one "recently added"
carousel per non-music library (GET /api/catalog/libraries/:id/recent,
Emby's Items/Latest).
- views/library.js (route /library/:id): dispatches on collectionType -
movies/tvshows get the tile grid (Series tiles link into /series/:id
instead of starting a party directly), music gets an honest
placeholder.
- views/series.js (route /series/:id): season tabs + episode list
(new GET /api/catalog/series/:id/seasons and .../episodes, Emby's
/Shows/{id}/Seasons and /Episodes).
- tiles.js: poster-tile rendering shared by home.js and library.js so
they can't drift apart.
Episode queue / "next episode":
- POST /api/parties now builds a queue when starting from an episode
(fetches the rest of the series, from that episode onward) -
partyManager.js gained queue/queueIndex/advanceQueue.
- New WS message {type:"next"} (host-only) advances the queue and
broadcasts {type:"itemChanged", itemId, itemName, ...}; every client
reloads the player against the new item. player.js shows "Épisode
suivant" when hasNext, and auto-sends next() on the video's `ended`
event.
Player: language/subtitles/quality:
- New GET /api/catalog/item/:id/playback-options exposes PlaybackInfo's
MediaStreams as audio/subtitle track lists.
- master.m3u8 now forwards AudioStreamIndex/SubtitleStreamIndex/
MaxStreamingBitrate to PlaybackInfo; player.js has three <select>s
(host-only) that reload the stream at the same position on change.
Subtitles end up burned into the transcode (via SubtitleStreamIndex),
not a toggleable track - simpler than sidecar WebVTT for this pass.
"Quality" is a MaxStreamingBitrate ceiling, not true adaptive bitrate.
Verified by driving a browser against mocked /api/* responses (see
session history): sidebar renders/highlights correctly, home carousels
render (parties + one row per non-music library, music correctly
skipped), library grid dispatch works, series page season-tab
switching + episode list render and the season/episode navigation
flow works end to end. Real Emby response shapes for all of this
(Views' CollectionType, Seasons/Episodes, Items/Latest, PlaybackInfo's
MediaStreams) and the queue/track-picker protocol are NOT yet
confirmed against a live server - see docs/architecture.md.
Removed client/js/views/browse.js, superseded by home.js + library.js.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
112 lines
4.8 KiB
Markdown
112 lines
4.8 KiB
Markdown
# Watch Party for Emby
|
|
|
|
A standalone web app for watching the same title, in sync, with other users
|
|
of the same Emby server. It reads your Emby catalog and streams your Emby
|
|
media, but it's its own app with its own player — not an Emby plugin.
|
|
|
|
See [docs/architecture.md](docs/architecture.md) for the full design,
|
|
including why this replaced an earlier Emby-plugin attempt (still in
|
|
[src/WatchParty/](src/WatchParty/) for reference — see below).
|
|
|
|
## How it works
|
|
|
|
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.
|
|
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.
|
|
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.
|
|
|
|
Everything — catalog, images, video, sync — flows through this app's own
|
|
backend, which proxies whatever it needs from Emby server-side. The browser
|
|
never talks to Emby directly and never holds an Emby access token; it only
|
|
ever has a session cookie for this app.
|
|
|
|
Music isn't part of this yet — it needs its own player and a collaborative
|
|
playlist, different enough to be its own pass. The sidebar already lists a
|
|
Music library if you have one; the page just says so for now.
|
|
|
|
## Status
|
|
|
|
Login, and the rendering/navigation logic for the sidebar/home/library/series
|
|
pages, have been exercised end to end (login against the real Emby server
|
|
with intentionally-wrong credentials; the pages against mocked API
|
|
responses). See [docs/architecture.md](docs/architecture.md#assumptions-to-verify)
|
|
for exactly what that does and doesn't confirm — the real Emby response
|
|
shapes this now depends on (library `CollectionType`, seasons/episodes,
|
|
`PlaybackInfo`'s media streams) and actual HLS playback are still unverified.
|
|
|
|
## Project layout
|
|
|
|
```
|
|
server/ Node.js backend: proxies ALL Emby access (auth, catalog,
|
|
images, HLS streaming) and coordinates party/room state +
|
|
realtime sync. The browser only ever talks to this.
|
|
src/index.js Express app + REST endpoints
|
|
src/embyAuth.js server-side login against Emby + session cookies
|
|
src/embyProxy.js generic Emby request/response proxying (incl. HLS)
|
|
src/wsHandler.js WebSocket protocol (see comments in the file)
|
|
src/partyManager.js in-memory room state
|
|
|
|
client/ Static frontend (plain JS, ES modules, no build step).
|
|
Talks only to server/ - never to Emby directly.
|
|
js/shell.js persistent sidebar + #mainContent the router swaps
|
|
js/tiles.js poster-tile rendering shared by home.js and library.js
|
|
js/embyApi.js calls our own /api/* routes (auth, catalog, playback options)
|
|
js/party.js our own backend's party REST + WebSocket client
|
|
js/views/ one file per screen: login, home, library, series, player
|
|
|
|
src/WatchParty/ The earlier Emby-plugin attempt. Builds fine and installs,
|
|
but Emby has no supported extension point for a player
|
|
button, and even menu integration required reverse-
|
|
engineering undocumented internals - see
|
|
docs/architecture.md for the full story. Kept for
|
|
reference; not the active direction.
|
|
|
|
docs/architecture.md full design doc for the standalone app
|
|
```
|
|
|
|
## Running locally
|
|
|
|
```bash
|
|
cd server
|
|
npm install
|
|
EMBY_SERVER_URL=https://your-emby-server npm start
|
|
```
|
|
|
|
Then open `http://localhost:3000`.
|
|
|
|
## Deploying
|
|
|
|
Build the Docker image from the **repo root** (it needs both `server/` and
|
|
`client/`):
|
|
|
|
```bash
|
|
docker build -f server/Dockerfile -t watchparty .
|
|
docker run -d --name watchparty -p 3000:3000 -e EMBY_SERVER_URL=https://theater.media.kyuno.fr watchparty
|
|
```
|
|
|
|
Then put it behind your existing reverse proxy like everything else
|
|
(`theater.media.kyuno.fr`, `git.dev.kyuno.fr`, ...), e.g.
|
|
`watchparty.kyuno.fr` → `http://<container>:3000`.
|
|
|
|
## Roadmap
|
|
|
|
- [x] Design + scaffold the standalone app
|
|
- [x] Proxy all Emby access server-side; Emby-themed UI (sidebar, carousels)
|
|
- [x] TV show navigation (Series → Seasons → Episodes) + episode auto-queue
|
|
- [x] Player language/subtitle/quality pickers
|
|
- [ ] Try a full party end-to-end with two real accounts (still the main gap)
|
|
- [ ] Music: dedicated player, artist/album browsing, collaborative playlist
|
|
- [ ] Tune transcoding target / support direct play when possible
|
|
|
|
No chat, no OIDC — deliberately out of scope (Emby-only auth was the
|
|
explicit choice; see docs/architecture.md).
|
|
|
|
## License
|
|
|
|
MIT — see [LICENSE](LICENSE).
|