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

Skip to content

GStreamer Plugin ​

Two elements: moqsink publishes a pipeline to a relay, moqsrc subscribes to a broadcast and exposes one source pad per rendition.

bash
# Inspect (Nix bundles the plugin with gst-launch)
nix shell github:moq-dev/moq/release#moq-gst --command gst-inspect-1.0 moq

# Play the public test broadcast
nix shell github:moq-dev/moq/release#moq-gst --command gst-launch-1.0 -e \
  moqsrc name=s url=https://cdn.moq.dev/demo broadcast=bbb.hang \
  s.video_0 ! queue ! decodebin3 ! videoconvert ! autovideosink \
  s.audio_0 ! queue ! decodebin3 ! audioconvert ! autoaudiosink

# Publish a test pattern
gst-launch-1.0 -e videotestsrc is-live=true ! x264enc tune=zerolatency ! h264parse \
  ! video/x-h264,stream-format=byte-stream,alignment=au ! mux.sink_0 \
  moqsink name=mux url=https://cdn.moq.dev/anon broadcast=<your-name>.hang sink_0::encoder=true

Install via apt install gstreamer1.0-moq or dnf install gstreamer1-moq (Install), or build with cargo build -p moq-gst and point GST_PLUGIN_PATH_1_0 at the output. http:// URLs pin the relay's certificate fingerprint automatically, so local development needs no TLS setup. That scheme is for localhost only: the fingerprint is fetched unauthenticated and the WebSocket fallback runs as cleartext ws://, so use https:// for anything else.

moqsink ​

CodecCaps
H.264, H.265, AV1, VP8, VP9video/x-h264, video/x-h265, video/x-av1, video/x-vp8, video/x-vp9
AAC, MP3, Opusaudio/mpeg, audio/x-opus
Captionstext/x-raw (one WebVTT cue per buffer, PTS is the cue start and the buffer duration its end)
Opaque dataapplication/octet-stream (raw bytes on a named track, one group per buffer)

A text/x-raw pad is how captions get in: ffmpeg cannot mux a subtitle track into fragmented MP4, so moq import fmp4 can't carry one, while a demuxer that resolves timed text (qtdemux on a 3GPP timed-text track) can feed the pad directly. A cue with no duration is dropped rather than left on screen.

Each sink_%u request pad is one track. Pad properties: track names it (default: after the codec), container=loc publishes it as LOC instead of the legacy hang container, encoder=true marks it as fed by a local encoder, and track-status/track-error report its lifecycle. Element properties: url, broadcast, tls-disable-verify, quic-idle-timeout, quic-keep-alive, and read-only status, connected, moq-version, and estimated-send-rate, estimated-recv-rate, connection-stats, and sessions. The sink reconnects for as long as the pipeline runs and only reports failed on an answer redialing can't change, such as a rejected token.

connection-stats is null while disconnected. While connected it is a GstStructure named moq-connection-stats containing the transport metrics available from the active backend. Missing metrics are omitted rather than reported as zero. Its possible guint64 fields are rtt-us, estimated-send-rate-bps, estimated-recv-rate-bps, bytes-sent, bytes-received, bytes-lost, packets-sent, packets-received, and packets-lost. Poll the property for current counters; property notification marks connection and disconnection edges.

sessions is a GstStructure named moq-sessions with guint64 fields started and ended, the cumulative connect and disconnect counts for the current element session in the same shape as the relay's sessions track. One read returns both counters from the same instant, so started - ended is 1 while connected and 0 otherwise, reconnects are started - 1 once started is at least 1, and a rate is the delta over any window you sample. Unlike status, a connection that drops before you poll still moves both counters.

Set encoder=true on audio and video pads a local encoder feeds (x264enc, opusenc, ...). The pad then measures how late each frame reaches the sink behind its running time and raises the catalog jitter by the spread, and delay by how far it trails the earliest such pad, so players buffer for an encoder that delivers irregularly or behind the others. Leave it off, the default, for file, demuxed, and network media: their arrival reflects the disk or the network, not the original encoder, and a GStreamer segment cannot tell the two apart. Text and opaque pads refuse it.

After a pause, flush, or changed TIME segment, the next media buffer starts a new timeline epoch and resets the handoff baseline. The pause does not inflate advertised jitter, and previously measured maxima remain. Resumed timestamps must continue forward on the broadcast media clock.

A video pad joining mid-GOP drops delta frames until its first keyframe. If a source rewinds below the producer's live edge without signalling a break, the pad drops that frame and waits for a keyframe at or beyond the live edge. It keeps the rendition alive and preserves the media timeline; it does not shift rewound timestamps forward.

moqsrc ​

Pads are named by kind and appear as the catalog announces renditions: video_0, video_1, audio_0. Link the pad you want by name; the terse moqsrc ! decodebin3 form links only the first pad offered, which may be audio. Each pad sends EOS when its rendition ends. A rendition that leaves the catalog keeps its pad until its track ends, while one whose format changes is replaced by a new pad. Properties: url, broadcast, tls-disable-verify.

Debug with GST_DEBUG=*:4 for GStreamer and RUST_LOG=debug for the plugin.

Licensed under MIT or Apache-2.0