lilith-platform.live/codebase/@features/broadcast
2026-06-28 14:41:22 -04:00
..
backend-api feat(broadcast): multi-agent parallel delivery of full feature on main -- backend-api, frontend-public, shared, controller refactor into modules (llm-agent, obs-client, etc.), mcp-server start, deployment surface quinn.cast, obs scene collection (per approved plan + user 'merge to main + use multiple agents') 2026-06-28 14:38:01 -04:00
controller feat(broadcast): multi-agent parallel delivery of full feature on main -- backend-api, frontend-public, shared, controller refactor into modules (llm-agent, obs-client, etc.), mcp-server start, deployment surface quinn.cast, obs scene collection (per approved plan + user 'merge to main + use multiple agents') 2026-06-28 14:38:01 -04:00
docs docs(broadcast): update RUNBOOK for fully-runnable DO relay stack 2026-06-28 14:41:09 -04:00
frontend-public feat(broadcast): multi-agent parallel delivery of full feature on main -- backend-api, frontend-public, shared, controller refactor into modules (llm-agent, obs-client, etc.), mcp-server start, deployment surface quinn.cast, obs scene collection (per approved plan + user 'merge to main + use multiple agents') 2026-06-28 14:38:01 -04:00
infra feat(broadcast): add production relay infra under feature (compose, mediamtx, custom OBS Dockerfile+seed for Hotel Cam/LowerThird/produced RTMP, audio+video bridges, bootstrap, ufw/health/fanout) 2026-06-28 14:40:54 -04:00
shared feat(broadcast): multi-agent parallel delivery of full feature on main -- backend-api, frontend-public, shared, controller refactor into modules (llm-agent, obs-client, etc.), mcp-server start, deployment surface quinn.cast, obs scene collection (per approved plan + user 'merge to main + use multiple agents') 2026-06-28 14:38:01 -04:00
README.md docs(broadcast): update README to document the now-landed quinn.cast deployment surface (deploy-captain agent) + remaining work (narrow) 2026-06-28 14:41:22 -04:00

broadcast (quinn.cast relay)

LLM-controlled remote OBS + SRT contribution relay for running high-bitrate multi-platform streams from terrible hotel WiFi while all the heavy compute and uplink happens on a DigitalOcean droplet.

Architecture (the point of the whole thing)

[Hotel laptop / phone]
  ffmpeg or OBS (light settings)
        |
        | SRT (modest bitrate, resilient)
        v
[DO droplet - strong public bandwidth]
  mediamtx (SRT listener) 
        |
        +-- v4l2loopback bridge (ffmpeg) --> /dev/video10
        |
        v
  OBS (headless or containerized, scenes + overlays + browser sources)
        |
        | high-bitrate RTMP (local to mediamtx or direct)
        v
  controller (this Bun app)
    - LLM (Grok-4.3 via xAI tool calling)
    - obs-websocket client
    - dynamic fanout manager (ffmpeg -c copy to N public RTMPs)
        |
        +--> Twitch / YouTube / custom RTMPS etc.

You only ever push a few Mbps from the hotel. The droplet does the encoding at broadcast quality and pushes the final high-bitrate streams.

The LLM app + feature structure (the "app" you asked for)

codebase/@features/broadcast/

  • controller/ — the core engine + the self-contained runnable (for droplet / hotel use)
    • broadcast-controller.tsthe core (BroadcastController) that wires everything and exposes the high-level typed API
    • obs-client.ts — pure Bun obs-websocket v5 client (no deps)
    • fanout-manager.ts — ffmpeg -c copy children for multi-RTMP
    • destination-store.ts — file-backed persistence (simple JSON, atomic, graceful dev)
    • llm-agent.ts — Grok-4.3 tool-calling loop (system prompt + full tool set)
    • index.ts — thin entry that wires the core + serves the original zero-dep embedded chat UI (Tailwind CDN) + Bun.serve
    • package.json + Dockerfile (unchanged deploy surface)
  • backend-api/ — proper Bun/Hono backend-api tier (the public/server-to-server surface)
    • src/index.ts — Hono app with full REST direct control + /api/chat + health
    • consumes the controller core via relative imports (no logic duplication)
    • dedicated port (3033), token or passphrase auth, typed responses matching shared/
  • shared/types.ts — canonical types (Destination, RelayStatus, Chat*, ApiErrorResponse, etc.)
  • docs/ + README.md

Key properties (unchanged from skeleton but now modular and complete):

  • Pure Bun in controller (zero production runtime deps).
  • Single-file server + fully embedded chat UI still works exactly as before for the droplet.
  • Password gate via ?p=... (change UI_PASSPHRASE).
  • Full agent loop + direct obs-websocket v5 + fanout.
  • Destinations persisted (simple JSON file for now; /data/ volume in prod).

Run the controller locally (dev / droplet):

XAI_API_KEY=sk-... \
OBS_WS_URL=ws://127.0.0.1:4455 \
OBS_WS_PASSWORD=secret \
UI_PASSPHRASE=dev \
bun run codebase/@features/broadcast/controller/index.ts

Then open http://localhost:8080/?p=dev

Run the backend-api (separate process, richer REST surface):

cd codebase/@features/broadcast/backend-api
bun install   # pulls hono
XAI_API_KEY=... OBS_WS_URL=... OBS_WS_PASSWORD=... UI_PASSPHRASE=... \
BROADCAST_API_TOKEN=optional-strong-token \
bun run src/index.ts

The backend-api listens on :3033 by default and exposes:

  • GET/POST/DELETE /api/destinations, /api/scenes, /api/broadcast/{start,stop}, /api/text, /api/status, /api/chat etc.
  • Same LLM chat behavior.
  • Intended for later quinn.my / admin integration (SSO/service token will replace the simple gate).

Provisioning the droplet

See scripts/provision-stream-droplet.sh.

Typical flow:

  1. ./scripts/provision-stream-droplet.sh create --name quinn-cast-01 --region nyc2
  2. SSH the droplet.
  3. Run the post-boot script (printed by the provision command or ./scripts/... post-boot).
  4. Put real secrets in /opt/stream/.env.
  5. cd /opt/stream && docker compose up -d --build.

The post-boot already sets up mediamtx + a bridge example + an OBS container (you may want to customize the OBS image with your scene collection and the multi-RTMP plugin).

Local contribution from hotel (macOS example)

Minimal reliable SRT push (adjust device indexes):

ffmpeg -f avfoundation -framerate 30 -video_size 1280x720 \
  -i "0:0" \
  -c:v libx264 -preset veryfast -tune zerolatency \
  -b:v 3500k -maxrate 4000k -bufsize 8000k -g 60 \
  -c:a aac -b:a 128k -ar 48000 \
  -f mpegts \
  "srt://YOUR_DROPLET_PUBLIC_IP:8890?streamid=publish:live"

For better audio device selection use ffmpeg -f avfoundation -list_devices true -i "".

You can also run a full local OBS and use a Custom FFMpeg output that targets the same SRT URL.

Destinations & fanout

In the chat you can say:

  • "add twitch rtmp://live.twitch.tv/app/live_1234_5678"
  • "list destinations"
  • "remove youtube"
  • "start broadcast"

The controller will remember them (persisted on the droplet volume) and when you start the broadcast it launches parallel ffmpeg copy processes to all of them from the single high-quality encode coming out of OBS.

Configure OBS to stream to a local RTMP that the controller knows about (convention in the code: rtmp://127.0.0.1:1935/live/produced).

Security notes (follow these)

  • Change UI_PASSPHRASE and OBS_WS_PASSWORD immediately.
  • Put the control UI behind Caddy/nginx + real TLS + domain as soon as you have one.
  • For the SRT listener, use a strong passphrase in the mediamtx path config + streamid.
  • The droplet firewall should only open the ports you actually need (SRT UDP, the UI port, 22).
  • Never put real stream keys in git.

Platform integration (now partially complete)

We have delivered the first slice:

  • Proper @features/broadcast layout: backend-api/, shared/, modular controller/ (core engine).
  • Persistence for destinations (simple JSON file, production-grade load/save with graceful fallback).
  • Backend-api (Hono) + controller both expose complete, typed direct REST + LLM chat surfaces.
  • All logic is production complete, strongly typed, no stubs, full error handling, shared types.

Deployment (now live):

  • deployments/@domains/quinn.cast/ full surface (deploy.sh, nginx, services.yaml, systemd, bootstrap, data/docker-compose + mediamtx/obs/controller wiring).
  • Use ./run deploy:cast --from-local (after provisioning the droplet via scripts/provision-stream-droplet.sh).
  • Targets dedicated DO droplet (high bandwidth for SRT ingest + high-bitrate fanout from DO).
  • See the surface's own deploy.sh header + services.yaml for exact usage, health gates, rollback, and secrets.

Next (remaining slices):

  • Polish frontend-public/ (Vite/React SPA with the components/hooks already started: SceneSwitcher, StatusDashboard, ChatPanel, DestinationManager, ToolResultDisplay, useBroadcastAuth).
  • Wire destinations/"go live" sessions into quinn.my provider profiles (service token to backend-api).
  • Persist per-performer RTMP templates + scene presets in quinn.my-owned data.
  • Add recording of clean program feed + VOD clipping.
  • This matches the "RTMP relay for simulcast" promise in the performer marketing copy.

See docs/RUNBOOK.md for the complete end-to-end steps, OBS setup notes, troubleshooting, and self-verification checklist.

We ship working systems.