Skip to content

Real-time control

Everything on this page changes a system that is already running, without restarting anything.

Three kinds of topic, which are easy to confuse

Section titled “Three kinds of topic, which are easy to confuse”

Getting these mixed up is the main way people go wrong here, so it is worth being explicit:

KindRetained?Means
ConfigurationYes”This is the configuration.” A late subscriber gets the current value. Replaces what came before.
CommandNo”Do this, now.” An imperative that happens once. A late subscriber gets nothing, and should not.
StateYes”This is what is actually true.” Published by the service, read by everyone else.

A command must not be retained. A retained command is re-delivered to every new subscriber, so a service restarting would replay the last cut — which is precisely the kind of fault that looks like a ghost in the system.


Topic: phrame/tams-player/{instance}/CMD — state on …/STATUS.

Every command carries an operation field naming what it is.

Set play speed. Speed is a multiplier: 1.0 is normal, 0.5 half, 2.0 double, negative values play backwards, and 0 holds.

{ "operation": "set_play_speed", "speed": -2.0 }

Seek. The seek_type field selects how the position is expressed, which is the useful part — seeking by frame count and seeking by wall-clock time are genuinely different operations:

seek_typeExtra fieldGoes to
startthe beginning of the flow
endthe live edge
end_minussecondsthat far back from the live edge
timeorigination_time_nsan absolute origination time
percentagepercentthat far through the material
relativesecondsthat far from where it is now
framesframesthat many frames from here (negative goes back)
{ "operation": "seek", "seek_type": "frames", "frames": -10 }

Set the master clock. This is the one worth knowing about, because it is what makes several players agree to the frame:

{ "operation": "set_clock", "epoch_ns": 1788350000000000000, "base_frame": 4200 }

epoch_ns is a wall-clock instant and base_frame is the flow frame index that that instant corresponds to. Every player given the same pair computes the same absolute content target from the current time, so they stay locked together rather than each drifting from its own start. Publishing epoch_ns: 0 clears the clock and returns the player to free-running.

State is reported as one of playing, buffering, seeking, stopped or error.


A switch selects which of its inputs is live on its output.

The input can be named by URI or by index:

{ "active_stream": "vdi://40f7f177-0c8f-4f47-a305-6848a22c2d76" }

Scheduling a cut to a frame. A cut can carry the origination time at which it should take effect:

{
"active_stream": "vdi://40f7f177-0c8f-4f47-a305-6848a22c2d76",
"at_origination_time_ns": 1788350000000000000
}

Without that field the switch cuts as soon as the message arrives — which means the cut lands wherever network delay and message scheduling put it. With it, the switch holds the cut until the frame carrying that origination time reaches it, and cuts there. That is the difference between a cut that is approximately right and a cut that is frame-exact, and it is the mechanism to use for anything an audience will see.

A scheduled cut whose input cannot be found is abandoned rather than held indefinitely.

Related fields: add_input_stream adds an input to the switch’s set, and apply_scaler controls whether the switch’s scaler is applied to the outgoing stream.


Base topic from the mixer’s own configuration, conventionally AUDIO-MIXERS:

{base_topic}/{mixer_id}/inputs add or modify an input
{base_topic}/{mixer_id}/outputs add or modify an output
{base_topic}/{mixer_id}/mixes/{mix_id} configure one mix
{base_topic}/{mixer_id}/party_lines/{id} configure one party line

Inputs and outputs are addressed by id and carry the bus stream they map to:

{ "id": 43, "uri": "vdi://40f7f177-0c8f-4f47-a305-6848a22c2d76" }

Publishing the same id again modifies that input rather than adding a second — so these replace rather than accumulate, and an id is the thing to keep track of.


Controller mode — replacing a service’s whole configuration

Section titled “Controller mode — replacing a service’s whole configuration”

input-processor and output-processor can run static (everything from their config file) or in controller mode, where the live configuration arrives over MQTT and the service publishes what it is doing:

phrame/config/platforms/{platform}/services/input-processor/{uuid} configuration (retained)
phrame/input-processor/{uuid}/state state (retained)

The configuration topic carries the same JSON as the config file — see the input-processor and output-processor field references. It is a replacement, not a patch: what you publish is the whole configuration from that moment.

Because the topic is retained, a service that restarts picks up the current configuration immediately without anything having to notice it restarted. This is the same mechanism the platform uses to distribute configuration from Git — see Configuration & GitOps.


One further retained topic worth knowing, because it moves the whole platform’s configuration at once:

{prefix}/system/gitlab/ref {"ref":"<branch|tag|sha>"}

Covered under Configuration & GitOps, including why pointing it at a tag locks the deployment.