Skip to content

feat(moq-cli): per-sink frame-drop latency for the export gateways#1998

Merged
kixelated merged 3 commits into
mainfrom
claude/relaxed-kare-6d9097
Jul 2, 2026
Merged

feat(moq-cli): per-sink frame-drop latency for the export gateways#1998
kixelated merged 3 commits into
mainfrom
claude/relaxed-kare-6d9097

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Summary

export --latency-max previously only reached the stdout container path (added in #1985). The gateway egress sinks (rtmp, srt, hls) build their own moq-mux exports internally and never set a latency, so they fell back to the default (0 = drop stale groups aggressively) with no way to configure the frame-drop budget. Closes #1987.

The frame-drop latency is the moq-level "how long to wait for a stalled group before skipping to a newer one" — the moq_mux::container::Consumer::with_latency knob (the ordered/skip consumer). It's media-agnostic, and every egress pulls frames through it, so all of them can carry it.

Rather than a global export flag that silently applies to only some sinks, each sink owns its own --latency-max so the default fits the transport:

sink flag default notes
fmp4 / mkv / ts / flv (stdout) --latency-max 500ms shared Container args
rtmp --latency-max 500ms export-only (ExportArgs)
hls --latency-max 10s exposes the lib's existing Config.latency; generous so live GOPs aren't skipped while segments build
srt --latency 200ms reuses the SRT receive buffer as the skip threshold
rtc WebRTC is real-time, doesn't buffer

Changes

moq-rtmp (additive public API):

  • Play::with_latency() and Client::with_latency() builders + listen::Config.latency (defaults to 0 = today's behavior, #[non_exhaustive] Config so run() stays compatible). Wired into FlvExport::with_latency on both the serve and dial egress paths.

moq-srt (internal only, no public API change):

  • The SRT receive latency (Server::bind / dial::publish / Config.latency) now also drives the egress ts::Export skip threshold. SRT paces egress on the media clock, so the receive buffer and the skip threshold are the same end-to-end budget.

moq-cli:

  • Removed the global Export-level latency flag; pushed --latency-max down onto each stdout container (new shared Container args struct; Fragmented flattens it) and onto hls.
  • Split rtmp::Args into the shared endpoint (Args, used by import) and rtmp::ExportArgs (flattens Args + --latency-max) so the frame-drop knob only appears on export rtmp, not import rtmp.
  • srt needed no CLI change — the library now consumes the --latency it was already passing.

doc/bin/cli.md: documents the per-sink latency knobs.

Notes for reviewers

  • Public API (per branch-targeting rules): moq-rtmp gains Play::with_latency, Client::with_latency, and Config.latency — all additive, non-breaking. moq-srt has no public API change. moq-cli renames/reshapes CLI flags (--latency-max is now per-sink; the flag added in moq-cli: unified endpoint grammar (binary renamed to moq) #1985 was not yet released). Nothing wire-level. Targeting main.
  • The relay does not embed moq_rtmp::run / moq_srt::run in this repo, so no relay blast radius; the additive Config.latency keeps run() compatible for any out-of-tree embedder.
  • Behavior change: SRT egress previously dropped stale groups at 0 latency; it now uses the SRT receive latency (default 200ms). Arguably a fix for a paced egress.

Test plan

  • cargo clippy + cargo test on moq-cli / moq-rtmp / moq-srt (green)
  • rustdoc -D warnings (no broken intra-doc links)
  • cargo fmt --check, taplo format --check
  • moq export <sink> --help shows each sink's own latency flag; export rtmp has --latency-max, import rtmp does not; the --connect|--listen group is still enforced through the flatten
  • Not e2e-tested against a live RTMP/SRT/HLS player

(Written by Claude Opus 4.8)

`export --latency-max` previously only reached the stdout container path
(#1985). The gateway egress sinks built their own moq-mux exports and never
set a latency, so they fell back to 0 (drop stale groups aggressively) with no
way to configure the frame-drop budget.

Thread the moq-level frame-drop latency (the OrderedConsumer skip threshold,
`moq_mux::container::Consumer::with_latency`) into each export sink, and give
each sink its own knob so the default fits the transport rather than exposing a
global flag that silently applies to only some sinks:

- Container stdout (fmp4/mkv/ts/flv): `--latency-max` (500ms), via a shared
  `Container` args struct.
- rtmp: `--latency-max` (500ms) on a new export-only `ExportArgs`, wired to the
  new `moq_rtmp::Play::with_latency` / `Client::with_latency` / `Config.latency`
  (additive; the FLV export now sets it on both the serve and dial paths).
- hls: `--latency-max` (10s, generous so live GOPs aren't skipped while segments
  build), exposing the existing `moq_hls::export::Config.latency` the CLI never
  wired up.
- srt: reuses its transport `--latency` as the skip threshold (SRT paces egress
  on the media clock, so the receive buffer and the skip threshold are the same
  budget); threaded internally, no public API change.
- rtc: none. WebRTC is real-time and doesn't buffer.

The moq-rtmp `Config` field is additive (`#[non_exhaustive]`, defaults to 0), so
`run()` stays compatible for embedders. The relay does not embed
`moq_rtmp::run` / `moq_srt::run` in this repo.

Closes #1987.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @kixelated, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fe120ba0-4975-4a15-9086-a6efc9ef5580

📥 Commits

Reviewing files that changed from the base of the PR and between fa8f0e3 and f7eb814.

📒 Files selected for processing (1)
  • rs/moq-srt/src/ts.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • rs/moq-srt/src/ts.rs

Walkthrough

This change adds per-transport latency-max configuration for export sinks. Stdout container args now carry latency_max, HLS and RTMP export args gain their own --latency-max flags, and RTMP client/listener paths apply the configured latency through new with_latency builders. SRT now stores latency on the server and pending request path, passes it into Subscriber::new, and applies it to the TS exporter. Documentation in cli.md was updated to describe the transport-specific behavior.

Changes

Documentation: Added stalled-group latency behavior and per-transport default knobs to cli.md.
Stdout sink args: Added shared Container latency args and threaded resolved latency into SubscribeArgs.
HLS export: Added latency_max CLI arg and passed it into export config.
RTMP CLI/core: Added export latency args and applied latency to RTMP publish/accept flows.
SRT export/server: Stored latency on server/request state and passed it into TS subscriber export.

Sequence Diagram(s)

sequenceDiagram
  participant ExportArgs
  participant listen_export
  participant Play
  participant connect_export
  participant Client
  ExportArgs->>listen_export: latency_max
  listen_export->>Play: play.with_latency(latency)
  Play->>Play: FlvExport.with_latency(self.latency)
  ExportArgs->>connect_export: latency_max
  connect_export->>Client: with_latency(latency)
  Client->>Client: FlvExport.with_latency(self.latency)
Loading

Related PRs: None identified.
Suggested labels: enhancement, cli, rtmp, srt, hls, documentation
Suggested reviewers: None identified.
Poem
A stream may wait, then skip ahead,
with knobs for each transport instead.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the per-sink latency changes in the export gateways.
Description check ✅ Passed The description matches the changeset and explains the per-sink latency behavior clearly.
Linked Issues check ✅ Passed The RTMP, HLS, SRT, and stdout export paths are wired to latency controls, with RTC unchanged, matching #1987.
Out of Scope Changes check ✅ Passed The changes stay within the gateway crates, CLI, and docs, with no unrelated code paths introduced.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/relaxed-kare-6d9097

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kixelated kixelated enabled auto-merge (squash) July 2, 2026 03:26

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rs/moq-srt/src/ts.rs`:
- Around line 78-91: Update the doc comment on Subscriber::new in ts.rs to
soften the latency description: it should describe latency as the locally
configured SRT receive latency used as the skip threshold, not a negotiated
end-to-end budget. Keep the existing behavior in
OriginConsumer::announced_broadcast and ts::Export::new untouched, and revise
the wording so it says the muxer reuses the configured SRT latency budget rather
than claiming it always matches the peer’s actual buffer.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a4213d19-8d2f-4e3f-b58b-f76be31d6bb6

📥 Commits

Reviewing files that changed from the base of the PR and between be9bded and fa8f0e3.

📒 Files selected for processing (11)
  • doc/bin/cli.md
  • rs/moq-cli/src/args.rs
  • rs/moq-cli/src/hls.rs
  • rs/moq-cli/src/main.rs
  • rs/moq-cli/src/rtmp.rs
  • rs/moq-rtmp/src/dial.rs
  • rs/moq-rtmp/src/listen.rs
  • rs/moq-rtmp/src/server.rs
  • rs/moq-srt/src/dial.rs
  • rs/moq-srt/src/server.rs
  • rs/moq-srt/src/ts.rs

Comment thread rs/moq-srt/src/ts.rs
…tiated, SRT value

srt-tokio doesn't expose the post-handshake negotiated latency, so the skip
threshold reuses the locally configured receive latency. A peer that requests a
higher receive latency gets a larger actual buffer than the skip threshold.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kixelated kixelated merged commit 4def7dd into main Jul 2, 2026
2 checks passed
@kixelated kixelated deleted the claude/relaxed-kare-6d9097 branch July 2, 2026 03:41
@moq-bot moq-bot Bot mentioned this pull request Jul 2, 2026
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.

moq-cli: wire --max-latency (frame-drop latency) into the gateway egress paths

1 participant