Skip to content

moq-net: add per-track cache age (SUBSCRIBE_OK), reconcile draft field order, port timescale to js/net#1585

Merged
kixelated merged 2 commits into
devfrom
claude/quirky-johnson-7ab239
Jun 1, 2026
Merged

moq-net: add per-track cache age (SUBSCRIBE_OK), reconcile draft field order, port timescale to js/net#1585
kixelated merged 2 commits into
devfrom
claude/quirky-johnson-7ab239

Conversation

@kixelated

@kixelated kixelated commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a publisher-controlled cache age to Track (replacing the hard-coded 5s group-eviction window), announced in lite-05 SUBSCRIBE_OK so relays re-serve with the same window; each subscriber's stale window is clamped to it. Also reconciles the SUBSCRIBE_OK field order with the draft and ports timescale negotiation to js/net so JS↔Rust lite-05 is wire-compatible again.

Implements Publisher Cache from the moq-lite-05 draft (moq-dev/drafts#23): "max age, in ms, of the oldest group the publisher will retain," analogous to HTTP Cache-Control: max-age.

Cache age

  • rs/moq-net model: MAX_GROUP_AGEpub const DEFAULT_CACHE (5s). New Track.cache: Duration (default 5s, with_cache, manual Default, serde as integer-ms skipping default). evict_expired uses Track.cache. stale clamped to cache in TrackProducer::subscribe, TrackWeak::subscribe, TrackSubscriber::update.
  • publisher/subscriber: publisher announces track.cache; subscriber stamps info.cache onto the reconstructed local Track.
  • js/net: mirrored Track.cache + SUBSCRIBE_OK wire field.

SUBSCRIBE_OK field order (reconciled with the draft)

The draft orders the trailing fields Timescale, Cache, Compression, but the merged timescale impl (#1439) emitted Compression, Timescale. Per the draft, both Rust and JS now encode/decode Timescale, Cache, Compression. lite-05 is WIP/unreleased, so this additive + reorder needs no version bump.

Timescale port to js/net

PR #1439 added per-frame timestamps to the Rust moq-net wire but never touched js/net, leaving JS↔Rust lite-05 wire-incompatible. This PR:

  • adds timescale to the JS SUBSCRIBE_OK, and
  • has the JS group-stream reader consume the per-frame zigzag-delta timestamp varint when the negotiated timescale is non-zero, keeping frame framing in sync with a timestamped (Rust) publisher.

js/net doesn't yet surface or produce per-frame moq-net timestamps (it reads media timestamps from the container payload), so JS publishers advertise timescale = 0 and the decoded delta is read only to stay in sync. Fully surfacing/producing per-frame timestamps in JS (the js/hang container migration that Rust did across moq-mux) is a separate follow-up.

Open items for the draft author

Test plan

  • cargo test -p moq-net (350 pass) incl. cache_age_controls_eviction, stale_clamped_to_cache, cache_roundtrips_on_lite05, cache_absent_before_lite05
  • cargo test -p hang (catalog round-trips unaffected; cache skipped at default)
  • just rs check (clippy -D warnings + rustfmt)
  • just js check + biome; bun test in js/net (149 pass) incl. updated SUBSCRIBE_OK round-trip (timescale + cache + compression) and integration tests

🤖 Generated with Claude Code

(Written by Claude)

kixelated and others added 2 commits June 1, 2026 13:12
Replace the hard-coded 5s group-eviction window in TrackProducer with a
publisher-controlled `Track.cache` duration (default 5s, via `with_cache`).
The producer evicts old groups using this value, and each subscriber's
`stale` window is clamped to it: a group can't be waited for longer than
the publisher keeps it around.

The cache age is announced in the lite-05 SUBSCRIBE_OK (Publisher Cache,
varint milliseconds) so a relay re-serves with the same eviction window.
On the wire it follows the existing compression and timescale fields. The
struct field serializes into the hang catalog as integer milliseconds and
is skipped when it equals the default, so existing catalogs are unchanged.

Mirrored into js/net (Track.cache + SUBSCRIBE_OK wire) for parity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…js/net

Reorder the lite-05 SUBSCRIBE_OK trailing fields to match
draft-lcurley-moq-lite-05: Timescale, Cache, Compression (the merged
timescale impl had emitted Compression, Timescale).

Port timescale negotiation to js/net so it stays wire-compatible with a
timestamped (Rust) publisher: SubscribeOk gains a `timescale` field, and a
group-stream reader consumes the per-frame zigzag-delta timestamp varint
when the negotiated timescale is non-zero. js/net doesn't surface or
produce per-frame timestamps yet (it reads media timestamps from the
container payload), so publishers advertise timescale 0 and the decoded
delta is read only to keep frame framing in sync.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kixelated kixelated changed the title moq-net: add per-track cache age (SUBSCRIBE_OK Publisher Cache) moq-net: add per-track cache age (SUBSCRIBE_OK), reconcile draft field order, port timescale to js/net Jun 1, 2026
@kixelated kixelated enabled auto-merge (squash) June 1, 2026 20:36
@kixelated kixelated merged commit b934b13 into dev Jun 1, 2026
1 check passed
@kixelated kixelated deleted the claude/quirky-johnson-7ab239 branch June 1, 2026 20:49
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