Video Utils Runner
The Video Utils runner (video-utils) turns the H.264 video that Capture streams inside its protobuf payloads into something the HEAT dashboard can play back in sync with the session clock. Its main template, video-atlas-encode, composites every admitted headset stream into one atlas (an N-up grid) and publishes it as a ladder of Media Source Extensions (MSE) fragments plus a manifest.
Read this before adding video to a session template: the output is not a downloadable video file, and the node has to sit at a specific place in the DAG.
What the output is, and is not
- The atlas is an MSE fragment ladder: one init segment plus one fragmented-MP4 media segment per time window (8 seconds by default), described by a JSON manifest. The dashboard’s
VideoPlaybackcomponent fetches fragments on demand and feeds them to aMediaSourcebuffer, seeks by window, and follows the realm clock. - There is no single MP4 to download and play in a desktop player. Individual fragments are not self-contained files, the manifest is the only thing that gives them meaning, and the windows can be rewritten in place as data arrives. If a downloadable recording is a requirement, that is a separate export feature, not this node.
- Playback is progressive and contiguous by design: every window between the first and last carries media, and a seat with no data in a window is rendered black rather than leaving a hole, because a hole in an MSE buffer stalls the browser rather than showing “no video”.
Node template selection
| Template | Purpose | Limitations | Details |
|---|---|---|---|
video-atlas-encode | Decode admitted StreamComponent H.264 streams straight from the capture protobuf blobs, composite them into one atlas, and publish an MSE fragment ladder plus manifest for the VideoPlayback dashboard component. | Reads the capture input node directly (not hydrated CSV or JSON); single parent; CPU bound (decode dominates); requires retention.skipArchive; output is for the HEAT dashboard only. | video-atlas-encode |
adaptive-video-encode | Catalogue the StreamComponent video streams present in the capture (names, codec, dimensions, chunk counts) and push the catalogue onto a child input node. | Catalogue only: the HLS and DASH ladder it was designed for was never built, so it produces no playable video. Use it to see what video a capture holds before configuring the atlas. | adaptive-video-encode |
Which node
Expected topology
The atlas node must be a direct child of the capture input node that receives the protobuf uploads. It decodes the StreamComponent access units itself. Do not hang it off hydrate-protobuf or any tabular node: those strip the video, and an intermediate “extracted video” node would only duplicate tens of gigabytes.
Three publishing shapes are in use:
-
Standalone dimension: an Arbex script reads the manifest (the atlas node’s current output), republishes it as one
valuechannel, and builds a single-row layout with aVideoPlaybackcolumn. Good for a smoke test and for a dedicated video view. -
Inside an existing dashboard: an Arbex script wraps the manifest under a key (for example
output.videoAtlas) so ajson-mergecan carry it beside the other producers, and the dashboard script publishes thevaluechannel and mounts the column. The manifest must pass through unchanged: each window’sinputHashis the token the player uses to notice a rewritten window. -
Inside a ComposableChart: the
video_playbackslice renders the seat tiles in the chart stack, follows the chart realm clock, and is bounded to that clock (a vignette realm plays only its vignette), pairing with theplaybackscrubber andevents_laneslices. See the ComposableChart slice reference.
The channel id and name must be identical, because the components resolve the channel by both.
From capture to pixels
Each seat is a tile in the atlas. The player draws each tile into its own canvas region, so one hardware decoder serves every seat at once.
Memory and CPU
- Decode is the dominant cost, and it is irreducible: every source frame is a reference frame, so nothing can be skipped. Cost scales linearly with the number of admitted streams. The default admits the left eye only; the right eye is nearly the same view and would double the work.
- Memory is bounded by the atlas, not by sources times seats. Decoding is tick-synchronous: for each output tick the node decodes only what that tick needs from each stream, scales it into its tile, and releases it. On the full 24th August export this took peak memory from about 3.8 GB to about 250 MB for the same output. The atlas is capped at 1920 by 1088.
- Each window decodes independently with a short lead-in (default 1 second) so a window’s bytes depend only on the data inside its gather range. That is what makes the result order-independent, at a cost of roughly one eighth extra decode.
- The runner declares
cpuLimit: 4000mandmemoryLimit: 4096Mi. The encoder is pinned to four threads (see the node page for why), so four cores is the useful ceiling per task. - On the dashboard side the player keeps a bounded number of windows in the MSE buffer and evicts the oldest, so browser memory does not grow with session length.