Skip to content

feat(moq-ffi)!: defer dynamic track accept so media tracks declare a timescale#1761

Merged
kixelated merged 4 commits into
devfrom
claude/happy-jang-9553c6
Jun 16, 2026
Merged

feat(moq-ffi)!: defer dynamic track accept so media tracks declare a timescale#1761
kixelated merged 4 commits into
devfrom
claude/happy-jang-9553c6

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Summary

A track handed out by MoqBroadcastDynamic.requested_track() was accepted eagerly with no timescale. That's right for raw tracks (moq-boy status/command), but wrong for media: a media track should declare the container timescale (hang::container::TIMESCALE) so per-frame timestamps ride the moq-net wire and the relay gets timing without parsing the payload.

The constraint is that the accept freezes the timescale into TrackInfo and a subscribe blocks until that accept, so the timescale must be chosen at the accept and can't be amended afterward. One generic requested_track() can't satisfy both raw (untimed) and media (timed) with a single hardcoded accept.

This PR defers the accept to the first producer operation, which knows the right timescale:

  • requested_track() returns a not-yet-accepted handle (wraps a TrackRequest).
  • publish_media_on_track accepts at the media timescale → the requested media track is now properly timed.
  • raw write_frame / append_group / consume / used / unused / finish accept untimed.
  • abort rejects a still-pending request (the subscriber's subscribe fails), or aborts an already-accepted one.

This is the follow-up to #1750's quick fix (which only made media tolerate an untimed requested track); the track is now timed at the source, as the maintainer's timestamp-on-wire convention wants.

Breaking change (behavior, not signature)

No FFI signatures change. The observable difference: the requesting subscriber now stays pending until the first producer op on the requested track, instead of unblocking at requested_track(). This is why it targets dev. swift/kt/go auto-bindings and libmoq are unaffected (no signature change; verified libmoq has no refs and builds clean). The kt/swift dynamic-broadcast doc examples (writeFrame / abort in a requestedTracks loop) remain valid.

Python

  • Broadened subscribe_media to also accept a bare Container — the consumer-side counterpart of the dynamic flow, where you subscribe before the catalog exists.
  • Restructured the dynamic tests to run the subscribe concurrently. This also fixes test_dynamic_track_request_can_publish_media, which never actually ran before (it was missing awaits and passed a Container where the wrapper wanted a catalog record).

Test plan

  • Rust: 22 moq-ffi tests pass (incl. all three dynamic_track_request*); clippy + fmt clean via nix.
  • Python: FFI wheel built via maturin; 31 local tests pass (incl. both dynamic tests); ruff + pyright clean.
  • libmoq + moq-ffi build clean; rebased onto current dev (post per-frame-duration removal) and re-verified.

🤖 Generated with Claude Code

(Written by Claude)

…timescale

A track from `requested_track()` was accepted eagerly with no timescale, which is
correct for raw tracks but wrong for media: a media track should declare the
container timescale so per-frame timestamps ride the moq-net wire for the relay.
The accept freezes the timescale, and a subscribe blocks until it, so the choice
must be made at accept and can't be amended later.

Defer the accept to the first producer operation, which knows the right timescale:

- `requested_track()` returns a not-yet-accepted handle (a `TrackRequest`).
- `publish_media_on_track` accepts at `hang::container::TIMESCALE` (timed media).
- raw `write_frame`/`append_group`/`consume`/`used`/`unused`/`finish` accept untimed.
- `abort` rejects a still-pending request, or aborts an accepted one.

No FFI signatures change, only behavior: the requesting subscriber now stays pending
until that first op instead of unblocking at `requested_track()`. Tests run the
subscribe concurrently to match.

py: broaden `subscribe_media` to accept a bare `Container` (the consumer side of the
dynamic flow) and fix the dynamic tests, which never actually ran before.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kixelated and others added 2 commits June 16, 2026 15:10
…edia test

uniffi's generated Container variant classes aren't seen as Container subtypes
statically, so passing Container.LEGACY() to subscribe_media (typed Video | Audio
| Container) tripped pyright in CI. Restore the cast that the original test used.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kixelated kixelated enabled auto-merge (squash) June 16, 2026 22:52
…ng defaults (#1763)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kixelated kixelated merged commit 2acd830 into dev Jun 16, 2026
7 checks passed
@kixelated kixelated deleted the claude/happy-jang-9553c6 branch June 16, 2026 23:10
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