Add live captions to a LiveKit voice room

Publish an audio room as a captioned vertical video without building a speech pipeline or a headless browser farm.

The pipeline

  1. LiveKit audio
  2. ASR node
  3. caption data events
  4. web capture overlay
  5. RTMP output

What this replaces

Build it yourself

  • Forward every participant track to a speech service
  • Align transcript events with speakers and timing
  • Render captions into a vertical canvas
  • Run and record a browser at production quality

With AVFlow

  • Let the ASR node produce text events
  • Send events to your LiveKit client or backend
  • Render captions in your own web page
  • Capture that page with web_capture and push it to RTMP

The validated Job

This is the same file 22-voice-room-captions.json serves. Replace platform credentials, output URLs, and storage secrets before running it.

22-voice-room-captions.json Download
{
  "name": "voiceroom-captions",
  "sources": [
    {
      "name": "room",
      "type": "livekit",
      "config": {
        "serverUrl": "wss://your-project.livekit.cloud",
        "token": "<subscribe-token>",
        "select": {
          "mediaTypes": [
            "audio"
          ],
          "excludeIdentities": [
            "avflow-captions"
          ]
        }
      }
    },
    {
      "name": "overlay",
      "type": "web_capture",
      "config": {
        "url": "https://your-app.example.com/overlay/captions?room=voice-room",
        "captureElement": "canvas#stage",
        "fps": 30,
        "captureAudio": false,
        "waitForSelector": {
          "selector": "canvas#stage[data-ready=\"true\"]",
          "timeout": 20000
        }
      }
    },
    {
      "name": "carrier",
      "type": "video_generator",
      "config": {
        "width": 16,
        "height": 16,
        "fps": 5,
        "backgroundColor": "#000000"
      }
    }
  ],
  "nodes": [
    {
      "name": "captions",
      "type": "asr",
      "inputs": [
        "room"
      ],
      "config": {
        "language": "multi"
      }
    },
    {
      "name": "floor",
      "type": "audio_mixer",
      "inputs": [
        "room"
      ],
      "config": {}
    }
  ],
  "sinks": [
    {
      "name": "to_room",
      "type": "livekit",
      "inputs": [
        "carrier",
        {
          "name": "captions",
          "select": {
            "mediaTypes": [
              "data"
            ]
          }
        }
      ],
      "config": {
        "serverUrl": "wss://your-project.livekit.cloud",
        "token": "<publish-token>",
        "videoTrackName": "avflow-caption-carrier"
      }
    },
    {
      "name": "live",
      "type": "rtmp_push",
      "inputs": [
        {
          "name": "overlay",
          "select": {
            "mediaTypes": [
              "video"
            ]
          }
        },
        "floor"
      ],
      "config": {
        "urls": [
          "rtmp://ingest.example.com/live/<stream-key>"
        ],
        "encoding": {
          "videoCodec": "h264",
          "audioCodec": "aac",
          "videoBitrateBps": 3500000,
          "audioBitrateBps": 128000,
          "keyframeIntervalSec": 2
        }
      }
    }
  ],
  "policies": {
    "maxDurationSec": 14400,
    "idleTimeoutSec": 120
  }
}
Submit it API
curl -X POST "https://api.avflow.dev/v1/jobs" \
  -H "Authorization: Bearer $AVFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d @22-voice-room-captions.json

Know the limits before you ship

  • You write the overlay page; AVFlow captures it rather than designing it for you.
  • The ASR node emits text events — summarization and display logic stay in your app.
  • A caption-capable streaming sink still needs a video input to carry subtitles.

Run this LiveKit pipeline

Generate the Job in Playground, then run it with $20 promotional credit.