Skip to content

feat(archive): rewind a track into its recording, then splice to live - #4163

Open
kixelated wants to merge 4 commits into
quest/m1/archive/READMEfrom
quest/m1/archive/dvr
Open

kixelated wants to merge 4 commits into
quest/m1/archive/READMEfrom
quest/m1/archive/dvr

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Problem

The archive line records a broadcast (Writer) and serves it back (Reader), but nothing lets a viewer seek into a bounded recording and come back to live. Quest quest/m1/archive/dvr.md.

Approach

  • moq_archive::Rewind pairs a live broadcast with the replay broadcast a Reader serves. Rewind::seek(track, at) reads the replay's timeline, starts at the first group of the newest retained segment starting at or before at (the oldest one when at predates the window), and FETCHes each advertised group in order through the normal fetch_group miss chain.
  • Once the next group is past everything the timeline advertises, the track subscribes to the live broadcast floored at that group (Subscription::start plus the local set_groups), so the splice reaches back into the live cache instead of jumping to the newest group. The recording keeps source sequences, so nothing repeats or rewinds.
  • The timeline decides what is requested: pops skip expired groups before they are fetched, and an advertised group whose FETCH is NotFound is an ordinary gap.
  • The reader's private Index moved to the crate root and gained next and seek lookups.
  • Tests drive a real Writer + Reader + live source: seek within the window, seek before it, expiry mid-seek, missing groups (never produced and lost objects), a writer restart spanned by one seek, a track the recording never names, and the splice. A writer test proves an unbounded restart continues a DVR's segment numbering without rewriting any retained object. Mutating the splice floor or the gap handling fails the matching test.

Group eviction in moq-net: not needed

A served group that later expires stays in the replay track's moq_net cache until the pool reclaims it. No API is added: Rewind consults the timeline before every FETCH, so it never asks for an expired group (the expiry test passes even with NotFound handling disabled, proving the pop path alone skips them). A direct FETCH of such a group can still hit the cache, which is harmless because its bytes are immutable and the writer never reuses a sequence. Recorded in the line README.

Impact

  • New public API in moq-archive (0.0.x): rewind module, Rewind { new, seek }, rewind::Track { is_live, poll_next_group, next_group }, re-exported as moq_archive::Rewind.
  • No wire, format, or catalog change.

Alternatives

  • A group-eviction API on moq_net tracks: rejected above.
  • Preferring the live cache over the recording whenever it still holds a group: less deterministic; the recording is the source until its head.
  • Seeking to an exact group inside a segment: needs group timestamps, which the timeline does not carry; segment granularity matches HLS.

Follow-ups

Closes #2275

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

kixelated and others added 4 commits September 25, 2026 09:41
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Maintainer confirmed the API shape: keep moq_archive::Rewind and rewind::Track. The PR stays a draft only until the line's moq-json dev-dependency fix lands and just check passes.

(Written by Claude Opus 5.5)

@kixelated
kixelated marked this pull request as ready for review September 26, 2026 14:33
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-26T14:37:44.494831Z d1ae638 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1ae6386fb

ℹ️ 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".

let timescale = Timescale::new(scale).map_err(|_| Error::Timescale(scale))?;
let at = at.convert(timescale).map_err(|_| Error::Overflow)?.value();

let subscriber = self.archive.track(&self.timeline.track)?.subscribe(None).await?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Load the complete retained timeline before seeking

For any archive or DVR retaining more than moq_mux::timeline::CHECKPOINT_RECORDS (256) records, this default subscription can start at the latest timeline group. Its checkpoint contains only the newest 256 records and emits Skip for the older indices; because Archive::apply ignores Skip, seeking to an older timestamp silently starts at the oldest checkpoint record rather than the actual retained segment. Reconstruct the complete retained timeline, or explicitly reject timestamps outside the available checkpoint.

Useful? React with 👍 / 👎.


fn apply(&mut self, event: moq_json::Result<Option<window::Event<Record>>>) -> Result<()> {
match event.map_err(|err| Error::Timeline(err.to_string()))? {
Some(window::Event::Push { index, value }) => self.index.push(index, &value),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Index only the track being rewound

Each seek creates an Archive for one requested track, but every timeline push indexes and clones the spans for every track in the record even though subsequent lookups use only self.name. With many simultaneous viewers, recorded tracks, and retained segments, this makes rewind state grow as viewers × tracks × segments instead of viewers × segments; filter ingestion to the requested track and benchmark both fan-out axes.

AGENTS.md reference: AGENTS.md:L35-L35

Useful? React with 👍 / 👎.

This branch has not been deployed

No deployments
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