feat(moq-hls): expose the export observers a recorder needs#2381
Closed
kixelated wants to merge 1 commit into
Closed
feat(moq-hls): expose the export observers a recorder needs#2381kixelated wants to merge 1 commit into
kixelated wants to merge 1 commit into
Conversation
The export Broadcaster is built to SERVE HLS on demand: it exposes `rendition()` (look up one) and `master_playlist()`, but keeps rendition enumeration and the change signals internal. A consumer that RECORDS a broadcast (mirrors every rendition to durable storage) needs to observe the same state the server does, so expose it read-only: - `Broadcaster::renditions() -> Vec<Arc<Rendition>>`: snapshot every current rendition, not just serve one by name. - `Broadcaster::updated() -> watch::Receiver<usize>`: catalog-sync signal (fires on add/remove/reconfigure), so a recorder re-enumerates on catalog changes. The broadcast-level analogue of `Rendition::updated`. - `Broadcaster::is_closed()` / `closed()`: was pub(crate); a recorder awaits `closed()` to finalize. - `Rendition::updated()`: was pub(crate); the per-rendition timeline signal a recorder awaits before fetching newly-closed segments via `segment()`. - `Rendition::playable()`: was pub(crate); lets a recorder gate on real content. All read-only accessors over existing state; no behavior change to the serve path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Sorry @kixelated, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
kixelated
added a commit
that referenced
this pull request
Jul 18, 2026
The export module was built to *serve* HLS on demand. A recorder that mirrors
every rendition and every segment to storage had to reach into raw signals
(a `watch::Receiver<usize>` of the rendition count, a per-rendition
`watch::Receiver<u64>` timeline epoch) and re-derive the whole reconcile + fetch
loop itself. Replace those with two cursors in the moq-net idiom, so the engine
drives the loop once and the consumer just pulls the next element:
- `renditions::{Producer, Consumer}` + `Event`: the Consumer yields one
`Added`/`Removed` at a time, replaying the current set as `Added` and ending
(`None`) when the source closes. A reconfigure is a `Removed` then `Added`.
The Producer owns the current set (reconciled by `sync`, moved off Broadcaster)
and the serve path reads it synchronously.
- `segments::{Producer, Consumer}` + `Segment`: rehomes `timeline.rs`. The
Producer is the timeline window (kio-backed, replacing the tokio watch); the
Consumer's `next()` awaits the next *finalized* segment, FETCHes and transmuxes
it, and yields the CMAF bytes with duration + PDT. Runs dry (`None`) on clean
end or reset.
The HTTP serve path keeps its pull API (`rendition`, `master_playlist`,
`playlist`, `segment`), now `pub(crate)` since only in-crate callers use it; the
long-poll waits via `Rendition::wait_playable`. No leaky `watch` types remain in
the public surface. Supersedes the raw-accessor approach in #2381.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
kixelated
added a commit
that referenced
this pull request
Jul 18, 2026
The export module was built to *serve* HLS on demand. A recorder that mirrors
every rendition and every segment to storage had to reach into raw signals
(a `watch::Receiver<usize>` of the rendition count, a per-rendition
`watch::Receiver<u64>` timeline epoch) and re-derive the whole reconcile + fetch
loop itself. Replace those with two cursors in the moq-net idiom, so the engine
drives the loop once and the consumer just pulls the next element:
- `renditions::{Producer, Consumer}` + `Event`: the Consumer yields one
`Added`/`Removed` at a time, replaying the current set as `Added` and ending
(`None`) when the source closes. A reconfigure is a `Removed` then `Added`.
The Producer owns the current set (reconciled by `sync`, moved off Broadcaster)
and the serve path reads it synchronously.
- `segments::{Producer, Consumer}` + `Segment`: rehomes `timeline.rs`. The
Producer is the timeline window (kio-backed, replacing the tokio watch); the
Consumer's `next()` awaits the next *finalized* segment, FETCHes and transmuxes
it, and yields the CMAF bytes with duration + PDT. Runs dry (`None`) on clean
end or reset.
The HTTP serve path keeps its pull API (`rendition`, `master_playlist`,
`playlist`, `segment`), now `pub(crate)` since only in-crate callers use it; the
long-poll waits via `Rendition::wait_playable`. No leaky `watch` types remain in
the public surface. Supersedes the raw-accessor approach in #2381.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
kixelated
added a commit
that referenced
this pull request
Jul 18, 2026
The export module was built to *serve* HLS on demand. A recorder that mirrors
every rendition and every segment to storage had to reach into raw signals
(a `watch::Receiver<usize>` of the rendition count, a per-rendition
`watch::Receiver<u64>` timeline epoch) and re-derive the whole reconcile + fetch
loop itself. Replace those with two cursors in the moq-net idiom, so the engine
drives the loop once and the consumer just pulls the next element:
- `renditions::{Producer, Consumer}` + `Event`: the Consumer yields one
`Added`/`Removed` at a time, replaying the current set as `Added` and ending
(`None`) when the source closes. A reconfigure is a `Removed` then `Added`.
The Producer owns the current set (reconciled by `sync`, moved off Broadcaster)
and the serve path reads it synchronously.
- `segments::{Producer, Consumer}` + `Segment`: rehomes `timeline.rs`. The
Producer is the timeline window (kio-backed, replacing the tokio watch); the
Consumer's `next()` awaits the next *finalized* segment, FETCHes and transmuxes
it, and yields the CMAF bytes with duration + PDT. Runs dry (`None`) on clean
end or reset.
The HTTP serve path keeps its pull API (`rendition`, `master_playlist`,
`playlist`, `segment`), now `pub(crate)` since only in-crate callers use it; the
long-poll waits via `Rendition::wait_playable`. No leaky `watch` types remain in
the public surface. Supersedes the raw-accessor approach in #2381.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
kixelated
added a commit
that referenced
this pull request
Jul 18, 2026
The export module was built to *serve* HLS on demand. A recorder that mirrors
every rendition and every segment to storage had to reach into raw signals
(a `watch::Receiver<usize>` of the rendition count, a per-rendition
`watch::Receiver<u64>` timeline epoch) and re-derive the whole reconcile + fetch
loop itself. Replace those with two cursors in the moq-net idiom, so the engine
drives the loop once and the consumer just pulls the next element:
- `renditions::{Producer, Consumer}` + `Event`: the Consumer yields one
`Added`/`Removed` at a time, replaying the current set as `Added` and ending
(`None`) when the source closes. A reconfigure is a `Removed` then `Added`.
The Producer owns the current set (reconciled by `sync`, moved off Broadcaster)
and the serve path reads it synchronously.
- `segments::{Producer, Consumer}` + `Segment`: rehomes `timeline.rs`. The
Producer is the timeline window (kio-backed, replacing the tokio watch); the
Consumer's `next()` awaits the next *finalized* segment, FETCHes and transmuxes
it, and yields the CMAF bytes with duration + PDT. Runs dry (`None`) on clean
end or reset.
The HTTP serve path keeps its pull API (`rendition`, `master_playlist`,
`playlist`, `segment`), now `pub(crate)` since only in-crate callers use it; the
long-poll waits via `Rendition::wait_playable`. No leaky `watch` types remain in
the public surface. Supersedes the raw-accessor approach in #2381.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
kixelated
added a commit
that referenced
this pull request
Jul 18, 2026
The export module was built to *serve* HLS on demand. A recorder that mirrors
every rendition and every segment to storage had to reach into raw signals
(a `watch::Receiver<usize>` of the rendition count, a per-rendition
`watch::Receiver<u64>` timeline epoch) and re-derive the whole reconcile + fetch
loop itself. Replace those with two cursors in the moq-net idiom, so the engine
drives the loop once and the consumer just pulls the next element:
- `renditions::{Producer, Consumer}` + `Event`: the Consumer yields one
`Added`/`Removed` at a time, replaying the current set as `Added` and ending
(`None`) when the source closes. A reconfigure is a `Removed` then `Added`.
The Producer owns the current set (reconciled by `sync`, moved off Broadcaster)
and the serve path reads it synchronously.
- `segments::{Producer, Consumer}` + `Segment`: rehomes `timeline.rs`. The
Producer is the timeline window (kio-backed, replacing the tokio watch); the
Consumer's `next()` awaits the next *finalized* segment, FETCHes and transmuxes
it, and yields the CMAF bytes with duration + PDT. Runs dry (`None`) on clean
end or reset.
The HTTP serve path keeps its pull API (`rendition`, `master_playlist`,
`playlist`, `segment`), now `pub(crate)` since only in-crate callers use it; the
long-poll waits via `Rendition::wait_playable`. No leaky `watch` types remain in
the public surface. Supersedes the raw-accessor approach in #2381.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The
export::Broadcasteris built to serve HLS on demand: it exposesrendition()(look up one) andmaster_playlist(), but keeps rendition enumeration and the change signals internal. A consumer that records a broadcast (mirrors every rendition to durable storage) needs to observe the same state the server does, so this exposes it read-only:Broadcaster::renditions() -> Vec<Arc<Rendition>>— snapshot every current rendition, not just serve one by name.Broadcaster::updated() -> watch::Receiver<usize>— catalog-sync signal (fires on add/remove/reconfigure), so a recorder re-enumerates on catalog changes. The broadcast-level analogue ofRendition::updated.Broadcaster::is_closed()/closed()— werepub(crate); a recorder awaitsclosed()to finalize.Rendition::updated()— waspub(crate); the per-rendition timeline signal a recorder awaits before fetching newly-closed segments viasegment().Rendition::playable()— waspub(crate); lets a recorder gate on real content.Why
All read-only accessors over existing state; no behavior change to the serve path. This unblocks moq.pro's VOD recorder, which archives a broadcast by driving the export as a fetch-on-demand mirror (enumerate renditions, await
updated(), fetch each completedsegment()/init(), persist to object storage).Risk
Purely additive visibility + two new accessor methods; nothing on the existing HTTP serve path changes.