Emby Server plugin: watch party feature (session sync via Emby's remote-control commands) + companion web control panel
Find a file
Nicolas f25f719e5f Fix the real crash: partyPage.html was missing data-controller
Traced the exact "Cannot read properties of undefined (reading
lifeCycleAbortSignal)" error by fetching the target server's actual
viewmanager.js and pluginmanager.js directly (both are unauthenticated
static JS) instead of guessing further from docs.

Root cause: Emby's modern ViewManager (v4.10.0.40) only creates a
view.controller - required before it calls any lifecycle method on
the view - when the view's root element has a data-controller
attribute resolving to an AMD module via
pluginManager.getConfigurationResourceUrl(). partyPage.html never had
one (only the legacy data-role="page" attribute), so
newView.controller stayed undefined and getResumeOptions crashed
reading a property off it. configPage.html (the admin settings page)
apparently survives without this because dashboard pages go through a
different/older loading path.

Fix: added data-controller="__plugin/WatchPartyRoomController" to
partyPage.html, and a third PluginPageInfo/embedded resource
(partyPageController.js) - a minimal AMD module for it to resolve to.
ViewManager mixes in baseview.js's onResume/onPause/lifeCycleAbortSignal
automatically since this controller doesn't define its own, so it only
needs to exist.

Also checked (live JS probe, not a guess): pluginmanager.js has a
hardcoded plugin-id allowlist (allowPluginPages), but it's bypassed
whenever appHost.supports('restrictedplugins') is falsy - confirmed
falsy here, so it isn't blocking anything on this server's web client.

docs/architecture.md updated with the full trace for posterity.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 20:17:14 +02:00
.github/workflows Scaffold Emby Watch Party plugin 2026-09-13 14:02:43 +02:00
docs Fix the real crash: partyPage.html was missing data-controller 2026-09-13 20:17:14 +02:00
src/WatchParty Fix the real crash: partyPage.html was missing data-controller 2026-09-13 20:17:14 +02:00
.gitignore Scaffold Emby Watch Party plugin 2026-09-13 14:02:43 +02:00
LICENSE Scaffold Emby Watch Party plugin 2026-09-13 14:02:43 +02:00
README.md Integrate Watch Party into Emby's own UI instead of a standalone app 2026-09-13 19:44:31 +02:00

Watch Party for Emby

An Emby Server plugin that lets a group of users on the same server watch the same title together. Nobody switches players: everyone keeps using their normal Emby app (web, mobile, TV, whatever), and the plugin nudges playback back in sync using the same remote-control commands Emby already uses for casting / "play on another device".

See docs/architecture.md for the full design, including which parts are solidly confirmed against Emby's SDK docs and which still need verification against your exact server build.

How it's meant to work

  1. You start playing a title in your normal Emby app.
  2. In Emby's web client (or the Electron desktop app), open Watch Party from the menu and start a party. You get a 6-character join code. No separate login — it's a real page inside Emby, using your existing session.
  3. Friends open the same Watch Party menu item, start playing the same title in their own Emby app, and enter the code to join.
  4. The server treats one member (the host, by default) as the reference position, and periodically nudges everyone else's session back in sync (seek correction, and mirroring pause/unpause) — see docs/architecture.md.

The Watch Party page is a companion panel, not a video player — it never touches the actual video stream, playback stays in your normal Emby app.

Known limitation: the menu item shows up on Web and Electron clients. Official mobile/TV apps don't surface arbitrary plugin pages, so Watch Party won't appear there — see docs/architecture.md.

Status

Builds clean (0 errors, 0 warnings) against the real MediaBrowser.Server.Core / MediaBrowser.Common 4.9.1.90 assemblies from NuGet, and is installed on a real Emby Server (Docker) — the plugin loads and its admin page shows up. The actual feature (partyPage.html, reached via EnableInUserMenu) hasn't been exercised live yet. See docs/architecture.md for exactly what was wrong on the first guess, what was fixed, and what's still open.

Requirements

  • Emby Server (developed against 4.9.5.0; should work on nearby 4.9.x/4.8.x builds, but the plugin ABI does shift between versions)
  • .NET SDK 8.0 to build (installed per-user under %USERPROFILE%\.dotnet in this environment, since the machine-wide winget install needed admin elevation that wasn't available — see git history if you need to redo this on another machine)

Project layout

src/WatchParty/          the plugin itself (C#, netstandard2.0)
  Plugin.cs              plugin registration + both plugin pages
  Configuration/         plugin settings, the admin settings page, and
                         partyPage.html - the actual user-facing feature,
                         integrated into Emby's own menu (not a separate app)
  Parties/               Party / PartyMember model + in-memory PartyManager
  Sync/                  the drift-correction background loop
  Api/                   REST endpoints, called from partyPage.html
docs/architecture.md     full design doc

Building

dotnet build src/WatchParty/WatchParty.csproj

To have a build drop the DLL straight into a local Emby Server's plugin folder, set EMBY_PLUGINS_DIR before building, e.g.:

EMBY_PLUGINS_DIR="C:/ProgramData/Emby-Server/programdata/plugins" dotnet build src/WatchParty/WatchParty.csproj

Adjust the path for however your server is installed (Windows service vs. portable install vs. Docker volume).

Roadmap

  • Design doc + scaffold
  • Build against the real SDK, fix whatever the TODO(verify) spots got wrong
  • Install on a real Emby Server (Docker)
  • Integrate into Emby's own UI instead of a standalone web app (EnableInUserMenu — see docs/architecture.md)
  • Try a full party end-to-end with two accounts
  • Live (WebSocket) updates in the party page instead of polling
  • Per-party override of sync settings instead of only server-wide defaults

No chat/reactions — deliberately out of scope, not just deferred.

License

MIT — see LICENSE.