MoQ is under active development. APIs will change, but we keep backwards wire compatibility.

Skip to content

Libraries ​

Two primary implementations and six bindings, all speaking the same wire protocol. A publisher in Python is consumable by a subscriber in Swift.

LanguagePackageBest for
Rustmoq-net, hang, and friends on crates.ioServers, CLIs, native apps, anything that needs the full stack including hardware codecs.
TypeScript@moq/* on npmBrowsers (WebTransport + WebCodecs) and Node/Bun/Deno.
SwiftMoq via SwiftPMiOS, iPadOS, macOS.
Kotlindev.moq:moq on Maven CentralAndroid and the JVM.
Pythonmoq-rs on PyPIScripts, ML pipelines, voice agents.
Gomoq.dev/moqGo services and tooling.
Dartmoq on pub.devFlutter apps.
Cmoq-cC/C++ and any language with a C FFI.

How they relate ​

Rust is the reference implementation; every server-side tool is built on it. TypeScript is a from-scratch browser implementation. The other six wrap the Rust core: Python, Kotlin, Swift, Go, and Dart are generated from one UniFFI crate (moq-ffi) and then wrapped in an idiomatic layer, while C gets a hand-written stable ABI (moq-c). A feature added to the core lands in all of them together.

What every binding can do ​

The wrapped bindings share one feature set, so the language pages only show how it looks in that language:

  • Connect to a relay with TLS options (system roots, custom CA, fingerprint pinning, mTLS) and a JWT in the URL, or serve sessions yourself and accept or reject each request by path.
  • Reconnect automatically with backoff when the transport drops, with status/epoch reporting each (re)connect and backoff tunable down to retrying forever. The peer's inbound QUIC stream limit is configurable for subscribe-heavy clients.
  • Discover broadcasts by prefix, wait for a specific one, or request one by path, including a path a prefix claim serves on demand. Advertise an exact path with create_broadcast then announce / unannounce (a broadcast is invisible to local consumers and peers alike until announced), or claim a path prefix with dynamic(prefix, route).
  • Publish and subscribe to media with the hang catalog filled in from the bitstream, plus raw pixels or PCM in and out with the codec running inside the binding (VideoToolbox, Media Foundation, NVENC, openh264, Opus). A publisher follows the connection's send estimate through session.bandwidth(): reserve a share for an app-owned encoder, or pass the handle when encoding so the built-in video encoder follows the grant.
  • Connection health. stats() snapshots RTT, send/receive estimates, and byte/packet counters. bandwidth() divides that send estimate among tracks sharing the connection.
  • Raw tracks of arbitrary bytes with timestamps, sparse or replayed groups, per-subscriber priority and max age, and best-effort datagrams.
  • JSON tracks in snapshot mode (latest value, merge-patch deltas, optional compression) or stream mode (append log).
  • Fetch a single group by sequence from the cache, decoded through the container or raw.
  • Serve on demand: accept track and broadcast requests as they arrive instead of publishing up front.
  • Catalog extensions: write your own section next to video and audio, and read others' back.
  • Routes: see which relays a broadcast came through, and advertise a cost as a standby publisher: warm cost for what pulling costs today, undiscounted cold for what it costs with nothing cached (omit cold to price both alike).
  • Errors distinguish auth rejection (don't retry) from shutdown (expected) from transport failure. A protocol error carries the peer's session or stream code, a known kind when recognized, and keeps an application or unknown code without loss.

Dart is the exception on codecs: its published binaries carry no encoder or decoder, so it moves already-encoded frames.

Licensed under MIT or Apache-2.0