Three changes requested:
1. Server proxies Emby, browser never talks to it directly. Login is
now POST /api/auth/login {username,password} on our own backend;
the backend calls Emby's AuthenticateByName server-side and mints
its own opaque session (httpOnly cookie), storing
{embyToken,userId,userName} in memory. The browser never sees or
holds a real Emby access token. Every other call - search, item
details, images, HLS streaming, party actions - goes to our own
/api/* origin, authenticated by that cookie; server/src/embyProxy.js
forwards to Emby server-side and streams the response back
(including a playlist-rewrite pass for HLS .m3u8 so hls.js never
gets handed a URL pointing at Emby directly). This also sidesteps
the CORS problem the previous direct-to-Emby design had.
2. EMBY_SERVER_URL is backend-only config (.env.example); the login
form no longer asks for a server address at all.
3. client/css/app.css now uses colors read directly off the live Emby
server's own dark theme (--theme-primary-color-* etc, fetched via a
JS probe against the running client, not guessed): accent green
#52b54b/#6ccf65, near-black background #151216.
Tested for real: ran the server locally against the actual
EMBY_SERVER_URL and submitted intentionally-wrong credentials through
the UI - confirmed the full round trip (browser -> our backend -> real
Emby -> real 401 -> our error message) with no CORS issue, since it's
all server-to-server now. Login itself hasn't been exercised with real
credentials, so catalog/streaming remain unverified - see
docs/architecture.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
No plugin API exists for adding a button to Emby's player or item
detail page (confirmed by an Emby developer on their own forum, and
by research since). The only way to reach that would be patching
Emby's shipped web client files directly - outside the plugin,
breaks on every image update, needs manual reapplication. Not worth
the ongoing cost, per discussion.
New direction: a standalone app with its own player. Reads Emby's
catalog/streams directly from the browser (no plugin, no menu
integration fights); a small Node backend only coordinates party
membership and relays playback state over WebSocket - it never
touches a media byte. Auth is Emby-only, as chosen: the browser signs
in against Emby directly and this backend validates whatever token it
gets handed by asking Emby itself (GET /Users/{userId}), never seeing
a password.
Control model changed too: now that we own the player, host controls
playback and everyone else follows (event-driven play/pause/seek +
a 3s heartbeat, drift-corrected on the follower side) - the old
plugin's "everyone keeps their own controls, shared reference
position" was specifically a concession to nudging pre-existing Emby
app sessions we didn't control, which no longer applies.
- server/: Express + ws backend (party lifecycle, WebSocket protocol,
Emby token validation)
- client/: plain-JS/ES-modules frontend (login, search, item detail,
party player) - no build step
- Smoke-tested locally end to end against a stubbed Emby URL: server
starts, routing/auth-gating works, error handling degrades
gracefully (network failure, invalid token) - see the session
transcript for the actual browser test run
- docs/architecture.md rewritten around the new design, including why
the plugin was abandoned and a fresh "assumptions to verify" list
(CORS between this app's origin and Emby's is the biggest unknown)
- src/WatchParty/ (the Emby plugin) kept as-is for reference, no
longer the active direction
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Per user feedback: no separate control panel, no chat. Found that
PluginPageInfo has EnableInUserMenu/EnableInMainMenu (confirmed via
reflection on the real MediaBrowser.Model assembly, plus an Emby
community thread showing another plugin using EnableInUserMenu=true)
- a real, if barely documented, way to surface a plugin page to
regular users, not just admins, through the exact same
IHasWebPages/GetPages() mechanism as the admin settings page.
- Plugin.cs now registers a second page (partyPage.html) with
EnableInUserMenu=true + EnableInMainMenu=true
- partyPage.html replaces the old standalone control panel: no login
form, it runs inside the already-authenticated Emby web client and
calls the REST API via the global ApiClient
- Removed web/control-panel/ (the standalone app, its own login flow,
and the custom GET /WatchParty/app* static file routes in
WatchPartyService.cs) - no longer needed
- docs/architecture.md and README.md updated: the new mechanism, and
the honest limitation that comes with it (reaches Web/Electron
clients, not official mobile/TV apps - those allowlist plugin pages
client-side)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Installed .NET 8 SDK (per-user, winget needed admin elevation this
machine doesn't have) and restored the real MediaBrowser.Server.Core /
MediaBrowser.Common packages, then fixed everything the compiler and
a bit of reflection surfaced:
- PlaybackProgressEventArgs/PlaybackStopEventArgs are in
MediaBrowser.Controller.Library, not ...Session
- IAuthorizationContext/AuthorizationInfo are in MediaBrowser.Controller.Net
- Item/user/session ids (SessionInfo.Id/UserId, BaseItemDto.Id) are plain
string on this SDK version, not Guid - Party/PartyMember updated
accordingly (Party.Id stays a Guid; it's minted by this plugin, not Emby)
- Reading the calling user needed an extra step:
IAuthorizationContext.GetAuthorizationInfo(Request).UserId is a `long`
(internal row id); .User.Id is the one matching SessionInfo.UserId,
but it's a Guid so it's reformatted with .ToString("N")
docs/architecture.md's "Assumptions to verify" now reflects what was
actually confirmed vs. what's still only compile-checked (HTTP status
codes, content-type headers, the JS auth header format, the dashboard
config page) pending a run against a real server.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Design: coordinate parties on top of Emby's existing session
remote-control commands (SendPlaystateCommand) rather than building a
new player. Each member keeps their own Emby app/controls; the server
periodically corrects drift and mirrors pause/unpause against one
reference member's reported position.
- src/WatchParty: plugin skeleton (BasePlugin, IServerEntryPoint,
IService/[Route] API, in-memory party store, drift-correction loop)
- web/control-panel: plain HTML/JS/CSS companion page to create/join
parties and resync, served by the plugin itself
- docs/architecture.md: full design, sources consulted, and an
explicit list of SDK details that still need verifying against a
real build (no .NET SDK / Emby SDK assemblies available in this
environment to compile against)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>