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.

Track kinds

KindUse it forPer-object key
AudioContinuous voice (Opus / PCM / G.711).rollover ~1 s
VideoEncoded video; a group per keyframe (GOP).keyframe
FrameRobot telemetry, LIDAR, game state — opaque binary with a per-frame priority.per group
TextReliable 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

from telequick.moqt import MoqtClient

client = MoqtClient.connect("quic://relay.telequick.dev", token="…",
                            on_state=lambda s: print("state", s))
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:
StateMeaning
Connectingdialling the relay
Connectedsession up; tracks (re)attached
Reconnectinglink dropped; retrying with backoff
Closedyou called close()
Failedunrecoverable (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).

Publish & subscribe a frame track

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.
from telequick.moqt import MoqtClient

pub = MoqtClient.connect("quic://relay.telequick.dev")
track = pub.publish_frame("robot/turtlebot4-001", "odom",
                          capability="ros.telemetry", schema_tag="ros2/cdr")
track.write(ts_us, cdr_bytes, priority=200)

sub = MoqtClient.connect("quic://relay.telequick.dev")
sub.subscribe_frame("robot/turtlebot4-001", "odom",
                    lambda ts, priority, data: handle(priority, data))
import telequick "github.com/telequick/telequick-sdk/go/pkg"

pubc, _ := telequick.ConnectMoqt("quic://relay.telequick.dev", "", func(int) {})
track, _ := pubc.PublishFrame("robot/turtlebot4-001", "odom", "ros.telemetry", "ros2/cdr", 128)
track.Write(tsUs, cdrBytes, 200)

subc, _ := telequick.ConnectMoqt("quic://relay.telequick.dev", "", func(int) {})
subc.SubscribeFrame("robot/turtlebot4-001", "odom", func(ts uint64, priority uint8, data []byte) {
    handle(priority, data)
})
use telequick_sdk::moqt::MoqtClient;

let pubc = MoqtClient::connect("quic://relay.telequick.dev", "", |_| {}).unwrap();
let track = pubc.publish_frame("robot/turtlebot4-001", "odom", "ros.telemetry", "ros2/cdr", 128).unwrap();
track.write(ts_us, &cdr_bytes, 200);

let subc = MoqtClient::connect("quic://relay.telequick.dev", "", |_| {}).unwrap();
let _sub = subc.subscribe_frame("robot/turtlebot4-001", "odom",
    |ts, priority, data| handle(priority, data));
import com.telequick.sdk.Moqt;

Moqt.Client pubc = Moqt.Client.connect("quic://relay.telequick.dev", "", st -> {});
Moqt.FramePublication track = pubc.publishFrame("robot/turtlebot4-001", "odom", "ros.telemetry", "ros2/cdr", 128);
track.write(tsUs, cdrBytes, 200);

Moqt.Client subc = Moqt.Client.connect("quic://relay.telequick.dev", "", st -> {});
subc.subscribeFrame("robot/turtlebot4-001", "odom",
    (ts, priority, data) -> handle(priority, data));
using TeleQuick.SDK;

using var pubc = MoqtClient.Connect("quic://relay.telequick.dev");
using var track = pubc.PublishFrame("robot/turtlebot4-001", "odom", "ros.telemetry", "ros2/cdr", 128);
track.Write(tsUs, cdrBytes, 200);

using var subc = MoqtClient.Connect("quic://relay.telequick.dev");
using var sub = subc.SubscribeFrame("robot/turtlebot4-001", "odom",
    (ts, priority, data) => Handle(priority, data));
import { MoqtClient } from "@telequick/sdk/moqt";

const pubc = await MoqtClient.connect("https://relay.telequick.dev/moq");
const track = pubc.publishFrame("robot/turtlebot4-001", "odom",
    { capability: "ros.telemetry", schemaTag: "ros2/cdr", defaultPriority: 128 });
track.write(tsUs, cdrBytes, 200);

const subc = await MoqtClient.connect("https://relay.telequick.dev/moq");
subc.subscribeFrame("robot/turtlebot4-001", "odom",
    (ts, priority, data) => handle(priority, data));
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

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.

Namespacing & routing

  • 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.

How it works

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.