Rust
The reference implementation. Every crate is on crates.io with API docs on docs.rs.
Crates
| Crate | Does |
|---|---|
| moq-net | The pub/sub layer: sessions, origins, broadcasts, tracks, groups, frames. Transport-agnostic. |
| moq-pattern | Exact path patterns: grammar, matching, and set algebra. Re-exported by moq-net and moq-auth. |
| moq-tokio | Stands up QUIC with noq, TLS, WebSocket fallback, and iroh, from config or CLI flags. |
| moq-sock | Dual-stack socket binding, SO_REUSEPORT groups steered by QUIC connection ID, and CPU pinning for thread-per-core listeners. |
| moq-uring | Experimental Linux io_uring worker: one pinned thread per ring serving moq-lite over its own QUIC stack. |
| hang | The media layer: catalog, containers, ordered frame delivery. |
| moq-mux | Import and export fMP4/CMAF, MPEG-TS, Matroska, FLV, and Annex-B. |
| moq-archive | Versioned hang recordings on any object_store backend: track layout, .info JSON, and segment objects. |
| moq-video | Native capture, hardware encode/decode (Apple, Windows, NVIDIA, VAAPI, V4L2, Android), and GPU rendering. |
| moq-v4l | Safe Video4Linux 2 bindings with the kernel headers checked in, so a build needs no libclang. |
| moq-audio | Microphone and speaker, Opus/PCM/AAC codecs, echo cancellation. |
| moq-transcode | Just-in-time rendition ladders, GPU-resident on NVIDIA. |
| moq-auth | The authorization contract: requests, grants, leases, the HTTP client, the reference server, JWT keys, signing, and verification, plus listing live sessions and pushing a re-check. |
| moq-room | Headless rooms: announce-derived roster, token claims, and a chat track. |
| moq-json | JSON over tracks: snapshots with merge-patch deltas, or append logs. |
| moq-flate | Opaque payloads over tracks, optionally compressed with group-scoped DEFLATE: snapshots or append logs. |
| moq-e2ee | End-to-end encryption of groups, datagrams, and track names, scoped to a publisher epoch. |
| moq-loc, moq-msf | The IETF LOC container and MSF catalog. |
| moq-stats | Publish and consume relay traffic counters as tracks. |
| moq-hls, moq-rtmp, moq-srt, moq-rtc | The gateways, as libraries you can embed with your own auth. |
| moq-ffi, moq-c | The UniFFI core behind the language bindings, and the C ABI. |
| moq-relay, moq-cli | The binaries, also usable as crates. |
| web-transport | The QUIC/WebTransport/qmux transports, in a sibling repository. |
Quick start
moq-tokio configures the endpoint; moq-net does the protocol.
// The Origin is the local hub: the session fills it with remote broadcasts
// and serves your local broadcasts out of it.
let origin = moq_tokio::origin::spawn();
let client = moq_tokio::connect::Config::default().init(Default::default())?;
let url = url::Url::parse("https://cdn.moq.dev/anon")?;
// Reconnects on its own; `closed()` resolves when it gives up.
let session = client.with_origin(origin.clone()).connect(url);
// Subscribe: wait for a route, resolve the broadcast at its path, read the catalog.
let consumer = origin.consume();
let mut announced = consumer.announced();
while let Some(event) = announced.next().await {
// Skip retractions, and `Live`, which marks the end of what was already live.
let moq_net::announce::Event::Start(update) = event else { continue };
let broadcast = consumer.request_broadcast(&update.prefix).await?;
let catalog = broadcast
.track(hang::Catalog::DEFAULT_NAME)?
.subscribe(hang::Catalog::default_subscription())
.await?;
// moq-mux decodes the catalog; moq-video / moq-audio decode the media.
}// Publish: create and announce a broadcast on the origin, then fill it.
let mut broadcast = origin.publish("my-stream.hang", Default::default())?;
// moq-mux (from a container) or moq-video / moq-audio (from a device) fill it.
// The route retracts on `unannounce()` or when the broadcast ends. To serve a whole
// subtree on demand instead, `origin.dynamic("room", Default::default())?` yields
// each requested path for the application to accept or reject.Before exiting, session.close().await delivers what was already queued, such as the tracks you just finished, within one second. Then Client::close (on a clone of the client) sends the QUIC close before the runtime stops.
The examples run the session and the origin work concurrently (tokio::select! or spawn), since the announcement loop is live. Runnable examples: rs/hang/examples/video.rs (publish) and subscribe.rs. URLs may be https:// (WebTransport, with raw QUIC preferred for native), moql:///moqt:// (raw QUIC), or iroh://. A ?jwt= query carries the token. http:// is for a relay on localhost only: it fetches the certificate fingerprint unauthenticated before upgrading, so never send a token over it. Connections race QUIC against WebSocket and remember which won.
The Default::default() above is the QUIC transport section, and the same value serves a dial and a listener:
let mut quic = moq_tokio::quic::Config::default();
quic.congestion_control = Some(moq_tokio::quic::CongestionControl::Delay);
quic.receive_window = Some(64 << 20); // whole connection, in bytes
quic.stream_receive_window = Some(8 << 20); // per stream
quic.send_window = Some(32 << 20); // unacknowledged data we may hold
let client = moq_tokio::connect::Config::default().init(quic)?;Unset flow-control windows keep the transport defaults, except receive_window, which defaults to 64 MiB because the transport's is unlimited. init errors on a knob the transport cannot honor rather than dropping it: iroh cannot disable GSO. quic::Resolved::default() is what an untouched config resolves to, so read the defaults from there. The relay reference documents each field.
Connection monitoring
Connection::monitor() returns a cloneable moq_tokio::connection::Monitor that observes the live session across reconnects without keeping the connection loop alive. Its stats() and snapshot() return None between connections; connection::Snapshot pairs transport statistics with the negotiated protocol. Use presence() for cumulative connects and disconnects, or presence_changed().await to wait for those counters to change.
WebAssembly
moq-net compiles to wasm32-unknown-unknown and rides the browser's own WebTransport through the web-transport crate, so Rust logic can be shared between native and web. Skip moq-tokio there, build the transport with web_transport::ClientBuilder, and drive the returned session future with wasm_bindgen_futures::spawn_local (nothing is Send on wasm). If you just want MoQ in a page, the TypeScript libraries are the easier path.
Conventions
The API is producer/consumer pairs at every level (origin, broadcast, track, group). Producers write and consumers read; cloning a consumer shares the subscription, and the last clone dropping closes it. Everything is async, executor-agnostic, and errors are typed enums with thiserror.