Skip to content

feat(moq-srt): SRT contribution ingest gateway#1747

Merged
kixelated merged 2 commits into
devfrom
claude/lucid-carson-f59a57
Jun 16, 2026
Merged

feat(moq-srt): SRT contribution ingest gateway#1747
kixelated merged 2 commits into
devfrom
claude/lucid-carson-f59a57

Conversation

@kixelated

@kixelated kixelated commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

What

A new rs/moq-srt crate (library + binary): a pure-Rust SRT contribution ingest gateway. It runs an SRT listener, demuxes the MPEG-TS each connection carries via moq-mux, and publishes the result into a MoQ origin as ordinary broadcasts. The contribution-ingest analogue of moq-hls's import and moq-rtc's WHIP. No libsrt or ffmpeg (srt-tokio).

  OBS / ffmpeg / encoder
        │  SRT (MPEG-TS)         ┌──────────── moq-srt ────────────┐
        └────────────────────────▶ srt listener                    │
                                 │   └─▶ moq-mux ts::Import         │
                                 │         └─▶ OriginProducer ──────┼──▶ relay / subscribers
                                 └──────────────────────────────────┘

Why (replaces moq-pro#112)

This replaces the relay-embedding approach in moq-pro#112 (moq-ingest), which embedded the whole moq-relay as a library and so forced a Rust 1.95 toolchain bump and dual moq-native versions. Instead, a relay depends on moq-srt with default-features = false and calls moq_srt::run(origin, config) against its own origin:

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

tokio::select! {
    res = moq_srt::run(cluster.origin.clone(), srt) => res?,
    // ... the relay's own accept loop / web server
}

No extra hop, no toolchain bump, no dual moq-native.

Structure (matches moq-hls)

  • Library surface is just Config + run. Feature-gated server keeps the core library to srt-tokio / moq-net / moq-mux; moq-native / clap / axum / rustls / url / sd-notify are optional behind server (default-on). A relay embeds with default-features = false.
  • error.rs: thiserror #[non_exhaustive] Error + Result. Config is a plain #[non_exhaustive] struct (built by the binary's clap args, so CLI deps stay out of the embeddable lib).
  • Binary has two modes: serve (local QUIC/WebTransport server + /certificate.sha256 endpoint + optional --dir) and publish (forward to a remote --relay), mirroring moq-hls import.

Usage

# Serve directly as a local relay:
moq-srt serve --server-bind [::]:443 --tls-generate localhost \
  --srt-listen 0.0.0.0:9000 --srt-prefix live/

# Or forward to a remote relay:
moq-srt publish --relay https://relay.example.com \
  --srt-listen 0.0.0.0:9000 --srt-prefix live/

# 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'

Routing derives the broadcast path from the SRT stream id (#!::r=<resource> or raw app/key), with --srt-prefix namespacing and first-publisher-wins (a duplicate stream id is rejected).

Public API

New crate, so all surface is additive (no breaking changes to existing crates):

  • moq_srt::Config — plain #[non_exhaustive] struct (listen, prefix, latency) with Default.
  • moq_srt::run(origin: OriginProducer, config: Config) -> Result<()>.
  • moq_srt::{Error, Result}.

Targets dev per the branch rules (new public library API). Uses the dev moq-net API (BroadcastInfo, publish_broadcast -> Result<OriginPublish>, with_publisher).

Heads-up

  • SRT auth. The listener is currently unauthenticated; gate it with a firewall / private network. SRT passphrase encryption is the planned next step.
  • Added to the workspace members / default-members / dependencies (as default-features = false).

Verification

cargo build -p moq-srt (and --no-default-features), cargo clippy -p moq-srt --all-targets (clean), cargo fmt --check (via nix), and the 5 unit tests (stream-id routing + path-claim guard) all pass. Not yet exercised against a live SRT source end-to-end.

(Written by Claude)

Unrelated dev-CI fixes (second commit)

The full-workspace CI build surfaced two pre-existing breakages on dev, neither caused by moq-srt (it compiles clean on its own). Fixed in a separate commit so they're easy to drop or fold into the in-flight origin API work:

  • moq-ffi: subscribe_catalog / subscribe_media became async, but a test still called .unwrap() on the returned future. Added .await.
  • moq-native: a qmux bump (0.1.2 → 0.1.3) grew qmux::Session to ~216 bytes, re-tripping clippy::large_enum_variant on RequestKind. Boxed the WebSocket variant, matching the existing Quinn(Box<…>) pattern.

Verified locally against the CI gates: cargo check (default / --no-default-features / --all-features), cargo clippy --workspace --all-targets -- -D warnings, cargo doc -D warnings, cargo test --all-features (compile), cargo sort, cargo shear, taplo, and cargo fmt all pass.

@kixelated kixelated force-pushed the claude/lucid-carson-f59a57 branch from 961f6e3 to 2623dd2 Compare June 15, 2026 23:32
kixelated and others added 2 commits June 15, 2026 20:30
New `rs/moq-srt` crate (library + binary) that ingests SRT, demuxes the
MPEG-TS each connection carries via moq-mux, and publishes the result into
a MoQ origin as ordinary broadcasts. Pure Rust (srt-tokio), no libsrt or
ffmpeg.

This replaces the relay-embedding approach in moq-pro#112: instead of
embedding moq-relay as a library, a relay depends on moq-srt with
`default-features = false` and calls `moq_srt::run(origin, config)` against
its own origin. No extra hop, no toolchain bump, no dual moq-native.

Structured to match the sibling gateway crate moq-hls:
- Library surface is just `Config` + `run`; feature-gated `server` keeps the
  core lib to srt-tokio/moq-net/moq-mux, with moq-native/clap/axum/rustls
  optional behind `server` (default).
- `error.rs` thiserror Error/Result; plain `#[non_exhaustive]` Config built
  by the binary's clap args.
- Binary has two modes: `serve` (local QUIC/WebTransport server + cert
  endpoint) and `publish` (forward to a remote relay), mirroring
  moq-hls import.

Routing derives the broadcast path from the SRT stream id (`#!::r=<resource>`
or raw `app/key`), with `--srt-prefix` namespacing and first-publisher-wins.
The listener is unauthenticated for now (gate via firewall/private network);
SRT passphrase auth is the planned next step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two pre-existing breakages on dev surfaced by the full workspace CI build,
neither caused by the new moq-srt crate:

- moq-ffi: `subscribe_catalog` / `subscribe_media` became `async`, but a
  test still called `.unwrap()` on the returned future. Add `.await`.
- moq-native: a qmux bump (0.1.2 -> 0.1.3) grew `qmux::Session` to ~216
  bytes, re-tripping `clippy::large_enum_variant` on `RequestKind`. Box the
  `WebSocket` variant, matching the existing `Quinn(Box<..>)` pattern.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kixelated kixelated force-pushed the claude/lucid-carson-f59a57 branch from 2623dd2 to 62da759 Compare June 16, 2026 03:30
@kixelated kixelated enabled auto-merge (squash) June 16, 2026 03:42
@kixelated kixelated disabled auto-merge June 16, 2026 05:27
@kixelated kixelated enabled auto-merge (squash) June 16, 2026 05:27
@kixelated kixelated disabled auto-merge June 16, 2026 05:27
@kixelated kixelated merged commit df68111 into dev Jun 16, 2026
1 check was pending
@kixelated kixelated deleted the claude/lucid-carson-f59a57 branch June 16, 2026 05:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant