Skip to content
Console

Record a room (grid layout)

Record every participant in an RTC room to S3 as HLS. A segment sink takes a single composed stream, so an RTC room has to pass through video_mixer and audio_mixer first — connecting a room directly is rejected at submit time.

You need two things:

  • A subscribe token for the room. See LiveKit token docs.
  • A bucket plus a key pair that can write to it. Any S3-compatible endpoint works via endpoint and forcePathStyle; GCS and Azure are also supported — see Storage config.
Terminal window
curl -X POST "https://api.avflow.dev/v1/jobs" \
-H "Authorization: Bearer ${AVFLOW_API_KEY}" \
-H "Content-Type: application/json" \
-d @public/examples/02-room-grid-recording.json

Or inline:

{
"name": "room-grid-recording",
"sources": [{
"name": "room_src",
"type": "livekit",
"config": {
"serverUrl": "wss://your-project.livekit.cloud",
"token": "<subscribe-token>"
}
}],
"nodes": [{
"name": "mix_video",
"type": "video_mixer",
"inputs": ["room_src"],
"config": {
"canvas": { "width": 1280, "height": 720, "fps": 30 },
"layout": { "mode": "grid", "grid": { "maxColumns": 3, "gap": 4 } }
}
}, {
"name": "mix_audio",
"type": "audio_mixer",
"inputs": ["room_src"]
}],
"sinks": [{
"name": "segment_out",
"type": "segment",
"inputs": ["mix_video", "mix_audio"],
"config": {
"storageType": "s3",
"storageConfig": {
"bucket": "recordings",
"region": "us-east-1",
"accessKeyId": "<key-id>",
"secretAccessKey": "<secret>",
"pathPrefix": "rooms/{roomName}/{date}/",
"filename": "{roomName}_{timestamp}"
},
"segmentDurationSec": 6,
"encoding": { "videoCodec": "h264", "audioCodec": "aac" }
}
}],
"policies": { "maxDurationSec": 14400, "idleTimeoutSec": 120 }
}

encoding is optional: omitted codecs default to H.264 + AAC. Drop a media type with select.mediaTypes if the sink should not receive it.

Terminal window
curl "https://api.avflow.dev/v1/jobs/room-grid-recording" \
-H "Authorization: Bearer ${AVFLOW_API_KEY}"

Wait for status: "running". Segments start appearing under your pathPrefix within a few segment durations. {roomName}, {date}, and {timestamp} are expanded at write time — see Path templates.

Terminal window
curl -X DELETE "https://api.avflow.dev/v1/jobs/room-grid-recording" \
-H "Authorization: Bearer ${AVFLOW_API_KEY}"

Stopping matters for more than billing: the playlist is finalized on stop. Until then you have a live playlist and the segments written so far. If your application processes the recording afterwards, wait for a terminal status — or a webhook — rather than reading the playlist while the job runs.

policies.maxDurationSec above caps the recording at four hours as a safety net, and idleTimeoutSec ends the job two minutes after the room goes quiet, so an abandoned meeting stops billing on its own.

The sink writes HLS. To get a single MP4 on your laptop, remux the finalized playlist with ffmpeg:

Terminal window
ffmpeg -i ./hls/index.m3u8 -c copy recording.mp4

Install ffmpeg, when to wait for #EXT-X-ENDLIST, private-bucket download, and captions are in Convert HLS to MP4.

Follow the speaker instead of a grid. Change the layout to speaker mode with an ordered mainPriority:

{
"mode": "speaker",
"speaker": { "mainPriority": ["screen_share", "active_speaker"], "mainRatio": 0.76 }
}

A screen share takes the main region, otherwise the loudest participant holds it. Listing active_speaker requires an audio_mixer in the job — loudness is measured there — which this pipeline already has.

Add a transcript. Wire an asr node into the sink and you get a WebVTT subtitle rendition beside the video, uploaded on finalize. segment is the only sink that can carry captions without a video track. See the meeting recording use case for a complete job that also turns the transcript into notes.

Keep a rolling window instead of the whole session. Set maxPlaylistEntries for a live sliding-window playlist. The finalized VOD playlist includes every segment regardless.

See Cost examples.