feat(stage): LiveKit SFU stage audio [operator-action] (LC-512) #492
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/LC-512-livekit-stage-audio"
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?
LC-512 - Stage audio: LiveKit SFU integration [operator-action]
The media transport for the LC-494 stage. The full mesh caps at ~4-6 peers, so a large-audience stage needs an SFU; this routes stage audio through a self-hosted LiveKit server, wired to the existing stage control plane (roles + request-to-speak).
Why LiveKit
Self-hostable, the de-facto standard SFU, and it fits this app's "optional external service via env" pattern (LLM / STT / GIF). Server-side it needs only access-token minting - no Rust media SDK - and
connect-src wss:is already allowed, so no CSP change.Server (complete + unit-tested)
livekit::LiveKitConfig::from_env(LETS_CHAT_LIVEKIT_URL/_API_KEY/_API_SECRET), read on demand like the GIF picker (no AppState field -> no test churn).livekit::mint_tokenbuilds a LiveKit JWT (HS256, signed with the API secret) with avideogrant pinning the room (stage-{id}) and publish/subscribe rights.GET /room/{id}/stage/tokengates on configured + stage-on + room access + on-roster, and derivescanPublishfrom the live stage roster: speakers/hosts publish, listeners subscribe-only. A promote/demote therefore re-mints with new rights.Client
stage_media.jsreconciles against the per-viewer stage panel (data-lc-stage-livekit/joined/speaker): on join it lazy-loads the same-origin LiveKit SDK, fetches a token, connects, publishes mic if permitted, and plays subscribed audio; on leave it disconnects; on promote/demote it re-fetches a token and reconnects with the new publish right. If the SDK or server config is absent it silently no-ops (control plane still works).Build / vendoring
just vendor-jsfetches the pinnedlivekit-clientUMD intoserver/assets/vendor/(gitignored, produced at build liketailwind-built.css), wired intobuild/build-saas. Served same-origin - no runtime CDN, no CSP relaxation.Operator action
Run a self-hosted LiveKit server and set the three
LETS_CHAT_LIVEKIT_*vars (documented in CLAUDE.md). Unset = the stage still has roles + request-to-speak, just no audio. The build/Docker pipeline must runjust vendor-js(wired intojust build).Tests / verification
just check,just test,just test-saasall green; the token-minting claims are unit-tested. The actual media path (browser SDK <-> running LiveKit server) is environment-dependent and not exercised by the Rust suite - same caveat as all the WebRTC in this repo; it needs a live LiveKit server + two browsers to smoke-test.🤖 Generated with Claude Code
Adds livekit::LiveKitConfig (LETS_CHAT_LIVEKIT_URL/_API_KEY/_API_SECRET, read on demand like the GIF picker) and JWT access-token minting with LiveKit video grants. GET /room/{id}/stage/token gates on configured + stage-on + access + on-roster and derives publish rights from the live stage roster (speaker/host = canPublish, listener = subscribe-only), so a promote/demote re-mints with new rights. No Rust media SDK. Unit tests assert the grant claims for speaker vs listener and signature rejection. Operator-Action: To enable large-audience stage audio, run a self-hosted LiveKit server and set LETS_CHAT_LIVEKIT_URL (wss://...), LETS_CHAT_LIVEKIT_API_KEY, LETS_CHAT_LIVEKIT_API_SECRET. Unset = stage roles/request-to-speak still work, no audio. The build must run `just vendor-js` to fetch the browser SDK (already wired into `just build`/`build-saas`). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>