moq-srt 0.2.8

Bidirectional SRT gateway for Media over QUIC
Documentation

moq-srt

SRT gateway for Media over QUIC, both directions.

SRT carries MPEG-TS. This crate runs an SRT listener and routes each connection by its stream id m= mode:

  • m=publish (the default): ingest. Demux the connection's transport stream with moq-mux and publish it into a MoQ origin as an ordinary broadcast. The contribution-ingest analogue of moq-cli HLS import and moq-rtc's WHIP.
  • m=request: egress. Re-mux a broadcast from the origin back to MPEG-TS and stream it to the caller, so vlc srt://... and ffmpeg -i srt://... can play any broadcast the origin carries (H.264/H.265 video, AAC/AC-3/MP2 audio).

Pure Rust: SRT is provided by srt-tokio, with no libsrt or ffmpeg dependency.

Library

Two entry points. Config + run is the unauthenticated convenience: a relay embeds ingest by calling run against its own origin, so the ingested media is published locally with no extra hop. For auth, drive Server / Request directly (see Auth below).

let mut srt = moq_srt::Config::default();
srt.listen = Some("0.0.0.0:9000".parse()?);
srt.prefix = "live/".to_string();

// `origin` is your relay's local origin (e.g. `cluster.origin.clone()`).
tokio::select! {
    res = moq_srt::run(origin, srt) => res?,
    // ... your relay's accept loop, web server, etc.
}

CLI

A command-line interface is provided by the moq-cli binary, on top of this library.

Feed any SRT source:

# Publish: lands at broadcast `live/cam0`.
ffmpeg -re -i input.mp4 -c copy -f mpegts \
  'srt://127.0.0.1:9000?streamid=#!::r=cam0,m=publish'

# Request: play `live/cam0` back out as MPEG-TS.
ffplay 'srt://127.0.0.1:9000?streamid=#!::r=cam0,m=request'
vlc    'srt://127.0.0.1:9000?streamid=#!::r=cam0,m=request'

A request waits for the broadcast to be announced, so a player may connect before the publisher does.

Routing

Each connection's broadcast path and direction come from its SRT stream id:

  • Standard form #!::r=<resource>,m=<mode> -> <resource>, with m=request selecting egress and anything else (including absent) selecting ingest.
  • Otherwise the raw stream id (e.g. OBS-style app/key), always ingest.

--srt-prefix is prepended to namespace a listener's streams. First publisher on a path wins; a second publish of the same path is rejected. Requests don't claim a path, so any number of players can pull the same broadcast.

Auth

run is unauthenticated: anyone who can reach the UDP port can publish or request any broadcast. Gate it with a host firewall or a private network, or bring your own auth by driving Server / Request directly, mirroring moq-native's Server / Request:

let mut server = moq_srt::Server::bind("0.0.0.0:9000".parse()?, None).await?;
while let Some(request) = server.accept().await {
    // Inspect `request.resource()` / `request.stream_id()` (treat the stream id
    // as a token if you like), verify it, and pick the broadcast path.
    match request {
        moq_srt::Request::Publish(publish) => {
            tokio::spawn(publish.accept(&origin, "live/cam0"));
        }
        moq_srt::Request::Subscribe(subscribe) => {
            tokio::spawn(subscribe.accept(&consumer, "live/cam0"));
        }
    }
    // ...or call `.reject()` on the `Publish` / `Subscribe` instead of `.accept()`
    // to deny it.
}

SRT passphrase encryption is a separate, planned next step.