Drop below the modalities and stream your own live tracks — audio, video, robot/game frames — fanned out to every subscriber.
When a modality doesn’t fit, work with raw tracks yourself. You publish a named
track and any number of subscribers receive it live over QUIC — a robot, an
agent, a browser, and a recorder can all consume the same stream without you
tracking who’s listening. Tracks ride
MoQT (Media-over-QUIC Transport)
and fan out for free through the relay.You get there with a MoqtClient, shipped in every SDK with method names in
that language’s idiomatic case. Publish and subscribe the same way in each, and
the relay handles connect, backoff, and reconnect for you.
Robot telemetry, LIDAR, game state — opaque binary with a per-frame priority.
per group
Text
Reliable ordered messages (chat, control).
per 64 msgs
A track is addressed by a namespace + name (e.g. robot/turtlebot4-001
odom) and carries a capability string — an open-ended routing intent
("asr", "tts", "ros.telemetry", "media.passthrough") that the relay /
gateway uses to route the track to whatever registered that capability. You
route on intent, not on media kind.
connect returns immediately and dials in the background. The client
auto-reconnects with capped exponential backoff if the link drops, and
re-establishes every publication and subscription on each reconnect — your
code does nothing. on_state reports the lifecycle:
State
Meaning
Connecting
dialling the relay
Connected
session up; tracks (re)attached
Reconnecting
link dropped; retrying with backoff
Closed
you called close()
Failed
unrecoverable (e.g. bad URL)
You may publish or subscribe before the session is up — calls are queued
and replayed once connected (and the relay holds a subscribe for a namespace
that has not been announced yet, so a robot can subscribe to its command track
before any controller has connected).
Frame tracks carry opaque binary objects with a per-frame priority — the right
fit for robot telemetry and game state. Below: a publisher streaming a track
and a subscriber receiving it, fanned out through the relay.
Keep the subscription handle alive for as long as you want frames. In
garbage-collected languages (Python/Java/C#) it owns the native callback; if it
is collected, the engine calls into freed memory. The publication handle owns
the track — drop it (or call close) to stop publishing.
Audio tracks are the same shape with codec metadata instead of a priority —
publish_audio(ns, name, capability, sample_rate, channels, frame_ms) and
subscribe_audio(ns, name, on_frame), where on_frame(ts_us, bytes) delivers
one decoded object. Use them for live voice between an SDK client and an agent;
the capability (“asr”, “tts”, …) routes the track to the right module.
Telemetry vs commands: give command (inbound) tracks a distinct
namespace from telemetry — e.g. publish telemetry under robot/<id> and
subscribe commands under robot/<id>/ctl. If both share robot/<id>, the
relay can route a command subscription to the robot’s own telemetry announce.
Capability is the routing key. Two publishers may use different
capabilities on the same namespace; subscribers and modules select by intent.
Fan-out is free. The relay copies each object to every subscriber; the
publisher opens one stream per group regardless of subscriber count.
You get one publish/subscribe API in every SDK, but two runtimes sit behind it.
The C++, Python, and Go SDKs share one engine via the telequick_moqt_ffi
native library; the TypeScript SDK is a standalone WebTransport implementation
with its own queueing, late-join, and reconnect behaviour. Treat the two
families as separate conformance surfaces — a result on one does not
automatically carry to the other until they’re held to a common behaviour
contract (publish/subscribe APIs match; queue depth, filter handling, and
unsubscribe lifecycle can differ).See the per-language SDK reference for the full method list, and
Architecture for how the relay mesh fans tracks out.