feat(bindings): fetch and decode a retained media group - #2827
Conversation
Add `MoqBroadcastConsumer.fetch_media_group`: fetch one retained group by sequence and decode its track container into media frames, without opening a live subscription or applying latency-based group skipping. This sits between the existing `fetch_group` (raw byte payloads) and `subscribe_media` (live, container-decoded), reusing `MoqFetchGroupOptions` for delivery priority. The container is parsed before the fetch is issued so invalid CMAF init data does not leave a dynamic group request waiting for a consumer that can never read it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wrap `fetch_media_group` as `BroadcastConsumer.fetchMediaGroup` and add `MediaGroupConsumer`, an `AsyncSequence` over the finite group's decoded frames that cancels the native read when iteration ends. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Alias `MediaGroupConsumer` and add a `frames()` Flow over a fetched group, matching the cancellation handling of the existing consumer flows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`fetch_media_group` decoded its group inline in moq-ffi: a pending queue, a `Container::read` loop, and a hand-rolled copy of the first-frame keyframe invariant that `container::Consumer` already implements. That put a decoding primitive in the bindings layer, where no Rust caller can reach it, and left the invariant stated in two places. Move it into `moq_mux::container::GroupConsumer`: the group-scoped counterpart to `container::Consumer`, reading exactly the group it is handed with no subscription, group ordering, or latency skipping. `read`/`poll_read` mirror the track-level consumer, so moq-hls can use it to serve a segment from a FETCH without going through a live subscription. moq-ffi now wraps it, and the keyframe invariant lives only in moq-mux. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 14 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (13)
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. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8468bcaef0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| /// Unlike [`Self::subscribe_media`], this does not create a live subscription or apply | ||
| /// latency-based group skipping. The returned consumer reads exactly the requested group | ||
| /// until [`MoqMediaGroupConsumer::next`] returns `None`. | ||
| pub async fn fetch_media_group( |
There was a problem hiding this comment.
Add fetched-media support to the remaining bindings
When this FFI API ships, the ergonomic Python and Go packages still cannot use it: py/moq-rs/moq/subscribe.py and go/wrapper/moq/subscribe.go expose raw fetch_group and live subscribe_media, but have no fetched-media consumer or method, and rs/libmoq plus the Python, Go, and C docs are also unchanged. Add the corresponding wrappers, exports, tests, and documentation so the public binding surfaces remain synchronized. (Written by GPT-5.6 Sol)
AGENTS.md reference: AGENTS.md:L170-L174
Useful? React with 👍 / 👎.
| } | ||
|
|
||
| /// Fetch one complete group and decode its track container into media frames. | ||
| public func fetchMediaGroup( |
There was a problem hiding this comment.
Bump the Swift wrapper version before publishing
The new Swift API will not reach package users because this commit leaves swift/VERSION at 0.4.3, which was already assigned by commit 05c7bf3. The checked release-swift-lib.yml treats an existing mirror tag as the release gate and skips publication, so bump the wrapper version alongside these source changes. (Written by GPT-5.6 Sol)
Useful? React with 👍 / 👎.
Summary
This is the
fetch_media_grouphalf of #2764, split out so it can land onmainnow. The commits are Qizot's, rebased and stripped of the timeline surface.Fetch one retained group by sequence and decode it through the rendition's container, without opening a live subscription. It sits between the two things we already have:
fetch_grouphands back raw payloads,subscribe_mediadecodes but only live. Neither lets a binding client pull a single past group as media frames, which is what a seek, a thumbnail, or a segment-at-a-time exporter needs.Why the timeline part is not here
The rest of #2764 exposes
hang::catalog::Timelineand its group index through the FFI. That model is per media track onmain(VideoConfig.timeline/AudioConfig.timeline, one record per group), anddevhas already replaced it:maindev{group, pts}{segment, pts, duration, tracks}duration_max<rendition>.timeline.ztimeline.z(DEFAULT_NAME)Shipping the
mainshape through uniffi meansMoqVideo.timeline/MoqAudio.timelinehave to be removed later andMoqTimelineEntryreshaped, which is a semver break in publisheddev.moq:moqand Swift packages rather than an additive change. Better to wrap the segment model once it settles. Nothing here depends on it.Public API changes
All additive, hence
main.moq-muxaddscontainer::GroupConsumer.moq-ffiaddsMoqMediaGroupConsumerandMoqBroadcastConsumer.fetch_media_group.MediaGroupConsumerandBroadcastConsumer.fetchMediaGroup.MediaGroupConsumeralias and aframes()Flow.The refactor
Qizot's version decoded the group inline in moq-ffi: a pending queue, a
Container::readloop, and a hand-rolled copy of the first-frame keyframe invariant thatcontainer::Consumeralready implements. That put a decoding primitive in the bindings layer where no Rust caller can reach it, and stated the invariant in two places.The last commit moves it to
moq_mux::container::GroupConsumer, the group-scoped counterpart tocontainer::Consumer: it reads exactly the group it is handed, with no subscription, group ordering, or latency skipping, and mirrorsread/poll_read. moq-hls wants this same primitive to serve a segment from a FETCH. moq-ffi now wraps it, and the keyframe invariant lives in one place.Test plan
cargo nextest run -p moq-mux(two new tests: a legacy group read to completion, and a CMAF fragment whose batch is handed back one frame at a time)cargo nextest run -p moq-ffi(56 passing, including the three fetch-media-group tests carried over from feat(bindings): expose retained media group fetching #2764)just check/just testNote that
just checkdoes not compile moq-ffi or libmoq, so the moq-ffi tests above were run directly.Cross-package sync
doc/lib/{kt,swift}and both wrapper READMEs are updated. Skipped rows, per the table in CLAUDE.md:rs/libmoqanddoc/lib/c: no C ABI is added.py/andgo/wrapper: both need hand-written wrappers on top of the generated bindings, and feat(bindings): expose retained media group fetching #2764 did not add them either. This leaves Swift/Kotlin able to fetch media groups while Python and Go cannot, which is a parity gap worth a follow-up rather than a reason to hold this.Original PR: #2764. Commits 1-4 are authored by @Qizot.
(Written by Opus 5)