Report watch-party streams to Emby's own Sessions API (admin dashboard) #6
Loading…
Reference in a new issue
No description provided.
Delete branch "feature/emby-sessions-dashboard"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
/Sessions/Playing,/Progress,/StoppedAPI shape against the server's own/openapi.jsonbefore writing anything (this exact neighboring area already got a wrong-but-schema-plausible request shape past review twice before, perdocs/architecture.md).EmbySessionService+CatalogService.negotiatePlaybackSession; driven from the existing periodic sweep inindex.tsrather than new hooks scattered everywhere.Test plan
npm run typecheck/npm test/npm run build(134/134 server tests)negotiatePlaybackSession's null-on-failure contract, everyEmbySessionServicemethod's exact request body, failures swallowed not thrown,leave()stopping a member's real open sessionX-Emby-AuthorizationDevice/Client/Version, onlyX-Emby-Token) is genuinely unconfirmed - worth watching the real dashboard the first time a party runs after this merges.🤖 Generated with Claude Code
Asked for directly: Emby's admin "now playing" dashboard should see this app's streams the same way it sees a real web/mobile/TV client's. This is exactly the follow-up "Sync watch-party progress back to Emby" already anticipated and deliberately deferred at the time ("wiring clean start/stop into every transition... is real complexity for a secondary payoff") - that section also explains why this app abandoned Emby's session-shaped PlayingItems endpoints entirely in favor of direct UserData writes. Given that history (two separate rounds of a wrong-but-schema-plausible request shape for that other, now-abandoned endpoint family), this one was built checking the real API first rather than assuming. Confirmed against the real server's own /openapi.json that the actual session-lifecycle family is POST /Sessions/Playing (start), /Sessions/Playing/Progress, and /Sessions/Playing/Stopped - distinct from PlayingItems. None of the three schemas declare a required list at all, which - given the neighboring history - was read as "don't trust that": ItemId/MediaSourceId/PlaySessionId/PositionTicks are sent on every call anyway, matching what a real client obviously would, rather than risking a repeat of "technically matches the schema, still 400s". One real Emby session per PARTY MEMBER, not one per party - a faithful copy of how a real client behaves, and the actual point of "let admin see who's watching what" would be lost by collapsing everyone onto one shared entry. New server/src/services/EmbySessionService.ts mirrors PlaystateService's own best-effort shape (every failure caught and logged, never thrown) but stays its own class - PlaystateService's own opening comment is specifically about why it doesn't use a session-shaped endpoint, so folding this into it would muddy that exact distinction. Getting a MediaSourceId/PlaySessionId per member needed a new CatalogService.negotiatePlaybackSession - a fresh PlaybackInfo negotiation under that specific member's own account, since Emby scopes PlaybackInfo per user and PartyStreamService's existing shared negotiation is never guaranteed valid for reporting under everyone else's account too. Returns null on any failure rather than throwing - this only ever feeds an admin-dashboard nicety, never something that should disrupt real playback. Driven entirely from index.ts's existing periodic sweep rather than new hooks scattered across every queue-advance/track-skip call site - that sweep already visits every connected member on every ~20s tick, already positioned to notice a member's current item no longer matches their cached session and react (stop the old one if any, negotiate fresh ids, start the new one; same item as last tick just sends Progress with the cached ids). PartyMember gained the nullable embySession field this tracks. Explicit stop calls also happen at PartyService.leave() (alongside the existing CastTokenService .revokeForMember) and removeIdleParties (a backstop for a disconnect that never triggered a clean leave). Verified: full typecheck/test/build (134/134 server tests - new coverage for negotiatePlaybackSession's null-on-any-failure contract, every EmbySessionService method's exact request shape, that a non-ok response/thrown error is swallowed not propagated, and that leave() calls stop with a member's real cached session info but only when they actually had one open). Not verified: an actual live write against the real server - deliberately not attempted, since that would mean using a real account's Emby token for a genuine state-changing call, beyond the read-only diagnostic use this session's shared API key was ever meant for. So while the request shape is checked against the real schema and the per-member logic is unit-tested in full, whether Emby's dashboard actually displays these sessions the way a native client's would - correct device/client labeling in particular, since this app never sends the X-Emby-Authorization Device/Client/Version header a real client would, only X-Emby-Token - is genuinely unconfirmed. Worth watching the real dashboard the first time a party runs after this ships. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>