Skip to content
Console

video_mixer

Combine up to 16 video inputs into a single composited stream. Layout modes: grid, speaker, or custom.

For a single full-canvas tile (no thumbnail strip), use layout.mode: "speaker" with speaker.maxThumbnails: 0.

Terminal window
curl -X POST "https://api.avflow.dev/v1/jobs" \
-H "Authorization: Bearer ${AVFLOW_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "grid-mix",
"sources": [{
"name": "room_src",
"type": "livekit",
"config": {
"serverUrl": "wss://your-project.livekit.cloud",
"token": "<token>"
}
}],
"nodes": [{
"name": "mix_video",
"type": "video_mixer",
"inputs": ["room_src"],
"config": {
"canvas": { "width": 1280, "height": 720, "fps": 30, "backgroundColor": "#1a1a2e" },
"layout": {
"mode": "grid",
"common": { "borderRadius": 8 },
"grid": { "maxColumns": 3, "gap": 4, "fit": "cover" }
}
}
}],
"sinks": [{
"name": "rtmp_out",
"type": "rtmp_push",
"inputs": ["mix_video"],
"config": {
"urls": ["rtmp://live.example.com/app/key"],
"encoding": { "videoCodec": "h264" }
}
}]
}'

See also 03-speaker-layout.json.

FieldTypeDefaultDescription
canvasobjectrequiredOutput canvas size, frame rate, and background. See canvas.
layoutobjectrequiredLayout mode and mode-specific options. See layout.
FieldTypeDefaultDescription
widthintCanvas width in pixels. Clamped to 1080p: max long edge 1920, short edge 1080 (landscape or portrait).
heightintCanvas height in pixels (same clamp as width).
fpsint30Compositor output frame rate (1–60). Independent of upstream track fps: slower inputs hold the last frame; faster inputs drop to the latest frame per tick. Downstream encoders follow this cadence.
backgroundColorstring"#000000"Canvas clear color (CSS color).
coordinateSystemstring"pixel"How layout.custom.regions interpret width / height / offsetX / offsetY: pixel (absolute px) or ratio (fraction of canvas, 0–1).

Only the sub-object matching mode is used (grid, speaker, or custom). Unused siblings are ignored. Unknown / empty mode falls back to grid.

FieldTypeDefaultDescription
modestring"grid"Layout algorithm: grid | speaker | custom.
commonobjectVisual options shared by all modes. See layout.common.
gridobjectUsed when mode is grid. See layout.grid.
speakerobjectUsed when mode is speaker. See layout.speaker.
customobjectUsed when mode is custom. See layout.custom.
FieldTypeDefaultDescription
emitLayoutInfoboolfalseEmits avflow.videoMixerLayout metadata whenever the layout changes. Supporting sinks forward it through their metadata path; streaming sinks replay the last layout on every video keyframe.
borderRadiusnumber0Corner radius for each layout slot. CSS-like: (0,1] → horizontal ratio×width, vertical ratio×height (ellipse on non-square; 0.5 = fully rounded); >1 = circular pixel radius. In custom mode, this is only the fallback when a region omits style.borderRadius (or sets it to 0). Negative values clamp to 0.
showTilesstring"with_video"Which input slots participate in grid / speaker. Ignored in custom (regions always resolve). See Show tiles. Legacy slotVisibility / has_video are still accepted on input.
avatarobjectStyles the per-participant no-video circle. See avatar.
nameLabelobjectoffAlways-on name pill over each tile (including custom regions). See nameLabel.

RTC multi-party sources (LiveKit / Axonkit / Daily / Agora) emit a participant join event when a remote participant enters. The mixer creates an identity-level avatar tile (not a fake media track). If the participant publishes any video track (camera or screen share), that join tile is hidden; when the last video track ends and they are still in the room, it returns. Leaving the room removes it.

ValueBehavior
with_video (default)Only slots with an active video frame (join placeholders are hidden)
allJoin placeholders and video slots; inactive / no-video tiles show participant avatar
activeSlots with active video or current audio activity (requires an audio_mixer wired for speaker levels; a speaking join placeholder can still appear)

Styles the no-video fill. The image comes from participant profile metadata (avatarUrl); when absent, the full displayName / identity is drawn inside the circle (auto-shrunk to fit). In custom layout, avatar applies only when the region has no placeholder; region.placeholder overrides avatar for that region.

FieldTypeDefaultDescription
sizeRationumber0.4Circle diameter as a fraction of min(tileW, tileH) (clamped to (0,1]).
textColorstringText color for the name fallback inside the circle.
backgroundColorstringderivedFixed circle fill; empty derives a stable color per identity.
FieldTypeDefaultDescription
enabledboolfalseDraw the name pill when true.
positionstring"bottom-left"Tile corner: bottom-left | bottom-right | top-left | top-right.
fontSizenumber14Font size in px (max 48).
fontFamilystringCSS font-family.
colorstringText color.
backgroundColorstringPill background color.
opacitynumber0Pill background alpha (01).
paddingnumber5Vertical inner padding in px; horizontal padding is 1.5× in the renderer.
borderRadiusnumber0Pill corner radius. CSS-like: (0,1] = ratio×pill axes; >1 = px.
maxWidthRationumber0.9Caps label width as a fraction of the tile width (01).

Equal-size tiling of every visible slot.

FieldTypeDefaultDescription
maxColumnsint0 (auto)Cap on columns (110). 0 = auto (⌈√n⌉).
gapnumber0Pixel spacing between cells (≥0).
fitstring"cover"How each tile’s video scales: cover | contain | fill. Unknown / stretchfill when stretch; other unknowns → cover.

One large main tile plus a thumbnail strip. Main and strip split the canvas (not an overlay). Thumbnails are a single row (top/bottom) or single column (left/right); they do not wrap into multiple rows/columns.

FieldTypeDefaultDescription
mainPriorityarraysoft fallbackOrdered rules for which slot is main. Each entry is a kind string or a MatchSource object; first match wins. Kind strings: screen_share, active_speaker. Listing active_speaker explicitly requires an audio_mixer in the job. If omitted, active_speaker is only a soft fallback (no hard validation).
mainRationumber0.75Main tile’s share of the canvas along the strip axis (01). The strip gets the remainder.
mainGapnumber0Pixel gap between the main tile and the thumbnail strip (≥0).
thumbnailGapnumber0Pixel gap between adjacent thumbnails in the strip (≥0).
mainFitstring"auto"Main tile scale: auto | cover | contain | fill. auto picks per track — screen_sharecontain, others → cover.
maxThumbnailsint | omit6Cap on thumbnail count (016). Omit / unset → default 6. Explicit 0 = main only (full-canvas main, no strip).
thumbnailPositionstring"bottom"Strip edge: top | bottom | left | right.
thumbnailRationumber16/9Each thumbnail’s width/height aspect. Non-positive values reset to 16/9. Thumbnails are fit (contain) into this ratio inside the strip.
thumbnailAlignstringsee notePacks the thumbnail group within the strip. Horizontal strips (top/bottom): left | center | right (default left). Vertical strips (left/right): top | center | bottom (default top). Cross-axis or unknown values fall back to the orientation default.
thumbnailFitstring"cover"Thumbnail scale: auto | cover | contain | fill.

Explicit regions for PiP, overlays, and branded scenes. showTiles does not filter regions.

FieldTypeDefaultDescription
regionsarray[]List of Region objects.

Coordinates use canvas.coordinateSystem (pixel or ratio).

FieldTypeDefaultDescription
namestringrequiredRegion id (for debugging / layout reports).
sourceobjectslot 0MatchSource selecting which input fills the region. Omit → default slot 0.
widthnumberrequiredRegion width (px or ratio; ≥0).
heightnumberrequiredRegion height (px or ratio; ≥0).
offsetXnumber0Left offset (px or ratio).
offsetYnumber0Top offset (px or ratio).
zIndexint0Draw order; higher draws on top.
fitstringrenderer defaultVideo scale: cover | contain | fill. Empty leaves the renderer default.
placeholderobjectStatic fill when no matching / active video. Overrides common.avatar for this region. See placeholder.
styleobjectRegion chrome. See style.

Used by layout.custom.regions[].source and object entries in speaker.mainPriority. All set fields must match; the first matching slot in sort order wins. Semantic fields are tried first; when none match, index selects a roster position.

FieldTypeDescription
identitystringParticipant identity
sourceNamestringJob source name
trackSidstringTrack SID
trackNamestringTrack name
trackSourcestringTrack source kind (e.g. camera, screen_share)
indexint0-based index after join-time sort (within the filtered slot list)
FieldTypeDescription
imageobjectOptional image fill. See below.
textobjectOptional text fill. See below.

placeholder.image

FieldTypeDefaultDescription
urlstringImage URL.
fitstringcover | contain | fill. Empty leaves renderer default.
borderRadiusnumber0CSS-like: (0,1] = ratio×image axes; >1 = px.
widthnumberOptional width hint (≥0).
heightnumberOptional height hint (≥0).

placeholder.text

FieldTypeDefaultDescription
contentstringText to draw.
positionstring"center"Within the region: top | bottom | center.
fontSizeintFont size in px (≥0).
fontFamilystringCSS font-family.
colorstringText color.
FieldTypeDefaultDescription
backgroundColorstringRegion background.
borderColorstringBorder color.
borderWidthnumber0Border thickness in px (≥0).
borderRadiusnumber0CSS-like: (0,1] = ratio×region width/height; >1 = px. When unset/0, layout.common.borderRadius is used as fallback.
opacitynumberBackground alpha (01; clamped).

Requires video-capable inputs. Pair with audio_mixer when you also need mixed audio, speaker levels (showTiles: "active"), or mainPriority: "active_speaker".

Each 1s pipeline metrics tick reports OffscreenCanvas composite wall time for video_mixer nodes:

PrometheusMeaning
avflow_mixer_compose_duration_ms_maxSlowest single output frame in the last tick
avflow_mixer_compose_duration_ms_sumSum of per-frame compose times in the last tick

Approximate average frame cost: mixer_compose_duration_ms_sum / video_frames over the same window. For 30 fps output, keep max below ~33 ms; sustained values above the frame budget mean the mixer is CPU-bound and effective output fps will drop.

$0.002/min of actual processing time. Node pricing.