All checks were successful
Publier l'image Docker / publish (push) Successful in 7s
server/src/index.js: GET /api/catalog/libraries now filters Emby's /Views
response to VISIBLE_COLLECTION_TYPES (movies/tvshows/music/musicvideos)
before returning it. Emby's /Views can return all sorts of things this app
has no UI for at all - boxset collections, playlists, live TV, photos,
books, a mixed-content folder with no CollectionType - none of which
should ever have shown up in the sidebar. Filtering once at the source
means every consumer (the sidebar, home.js's per-library carousels, direct
navigation to /library/:id) is restricted the same way without repeating
the check in each place; a stale link to a now-hidden library's id just
falls into library.js's existing "Bibliothèque introuvable" state, since
it looks the id up in this same filtered list.
Found while making musicvideos a real visible category: its items are
Emby's own 'MusicVideo' type (distinct from the generic 'Video'), which
WATCH_PARTY_TYPES didn't include - every tile in a music-videos library
would have rendered with no "Lancer une watch party" button at all, only
"Voir sur Emby". Added it.
Also fixed two stale doc/comment references while touching this code:
the sidebar hasn't shown per-type icons since the earlier "no emoji"
request (plain text links only), and views/home.js's party carousel has
used the live lobby Socket.IO subscription instead of a GET /api/parties
fetch since that endpoint was removed - both docs/architecture.md and an
inline comment still described the old behavior.
Verified: a plain filter/map unit test confirms exactly
movies/tvshows/music/musicvideos survive against a mock /Views response
seeded with one of every excluded type (photos/livetv/boxsets/playlists/
books/homevideos/mixed/no-type-at-all); WATCH_PARTY_TYPES.has('MusicVideo')
checked directly; and mounting shell.js + library.js against a mocked
already-filtered libraries response renders exactly the 4 expected sidebar
links and a working "Lancer une watch party" button on a MusicVideo tile.
docs/architecture.md has the full writeup.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
129 lines
5.9 KiB
Markdown
129 lines
5.9 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 (movies, TV, music, music videos —
|
|
anything else Emby might have, like photos or a boxset collection, isn't
|
|
shown); the home page shows every ongoing
|
|
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 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. 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.
|
|
5. Everyone present shows up as a small avatar overlay on the video (their
|
|
initials, on the player itself). If you're not the host, clicking your
|
|
own avatar asks the host to pause — everyone sees the request, including
|
|
the host, without needing chat.
|
|
|
|
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/socketHandler.js Socket.IO 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 + Socket.IO 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
|
|
- [x] Socket.IO instead of raw WebSockets; fixed two real memory leaks
|
|
(dead-connection cleanup server-side, view teardown on navigation
|
|
client-side) — see docs/architecture.md
|
|
- [x] Live "watch parties en cours" list (host, elapsed time, member count),
|
|
live host handoff, no join codes
|
|
- [x] Presence overlay (avatars) + "request pause" in the player
|
|
- [ ] 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).
|