FluxyChat

How-to Guides

Huddles — audio, video, and screen share

WebRTC huddles with worker call sessions, captions, and optional recording.

Huddles combine the SDK createHuddle() helper with worker call sessions. Default media path is Cloudflare Realtime SFU (no rooms on the SFU — the Durable Object is the room). LiveKit JWT mint is a fallback when LIVEKIT_* is set.

Realtime SFU signaling

The Worker holds REALTIME_SFU_APP_ID and REALTIME_SFU_APP_SECRET. Browsers never see the app secret.

curl -X POST "$WORKER_URL/rooms/{roomId}/realtime/sessions" \
  -H "Authorization: Bearer $MEMBER_JWT" \
  -H "Content-Type: application/json" \
  -d '{"sessionDescription":{"type":"offer","sdp":"v=0..."}}'

Same sequence as Cloudflare's Echo HTML: create session with the offer, wait for ICE, POST .../tracks with local mid + trackName, then pull peers from GET /rooms/{roomId}/realtime/tracks. The app secret never goes to the browser.

GET /admin/calls/token with no provider returns that signaling map when Realtime SFU is configured, otherwise a LiveKit token or stub.

Dashboard: Huddles

Bill cap (not Cloudflare's meter)

Realtime includes 1 TB/month then $0.05/GB. The Worker cannot read Cloudflare's usage, so it keeps its own high estimate and refuses new huddles when that estimate hits the cap.

Defaults (override with Worker env):

  • REALTIME_SFU_MONTHLY_GB_CAP=80 (stop at 80 GB estimated, not 1000)
  • REALTIME_SFU_MAX_CONCURRENT=2
  • REALTIME_SFU_MAX_SESSION_SECONDS=1200 (20 minutes, then we settle the session)
  • Video off unless REALTIME_SFU_ALLOW_VIDEO=true
  • Panic: REALTIME_SFU_DISABLED=true

GET /rooms/{roomId}/realtime/budget returns the current estimate. Apply D1 migration 0225_huddle_sfu_budget.sql.

Create and start a call

curl -X POST "$WORKER_URL/admin/calls" \
  -H "Authorization: Bearer $ADMIN_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "roomId": "room_team",
    "provider": "cloudflare-realtime",
    "recordingEnabled": true,
    "maxParticipants": 8
  }'

curl -X POST "$WORKER_URL/admin/calls/{callId}/start" \
  -H "Authorization: Bearer $ADMIN_JWT"

curl -X POST "$WORKER_URL/admin/calls/join" \
  -H "Authorization: Bearer $ADMIN_JWT" \
  -H "Content-Type: application/json" \
  -d '{"callId":"...","displayName":"Alex","role":"host"}'

Screen share and recording

Update participant state:

curl -X PATCH "$WORKER_URL/admin/calls/participants" \
  -H "Authorization: Bearer $ADMIN_JWT" \
  -H "Content-Type: application/json" \
  -d '{"callId":"...","screenSharing":true}'

Toggle server-side recording:

curl -X POST "$WORKER_URL/admin/calls/{callId}/recording" \
  -H "Authorization: Bearer $ADMIN_JWT" \
  -H "Content-Type: application/json" \
  -d '{"enabled":true}'

SDK — local huddle

import { createHuddle } from "@fluxy-chat/sdk";

const huddle = createHuddle({
  roomId: "room_team",
  audioEnabled: true,
  videoEnabled: true,
  screenShareEnabled: true,
  captionsEnabled: true,
  recordingConsent: false,
  maxParticipants: 8,
});

await huddle.join();
await huddle.startScreenShare();
huddle.giveRecordingConsent();

Stats

curl "$WORKER_URL/admin/calls/active" -H "Authorization: Bearer $ADMIN_JWT"
curl "$WORKER_URL/admin/calls/stats" -H "Authorization: Bearer $ADMIN_JWT"

Optional E2E encryption can be layered via room E2E keys — see Voice huddles core doc.

On this page