Skip to content

feat(apps): follow a broadcast's announcement as its online signal and log epochs - #4970

Closed
kixelated wants to merge 7 commits into
mainfrom
quest/m0/broadcast-epoch/apps
Closed

kixelated wants to merge 7 commits into
mainfrom
quest/m0/broadcast-epoch/apps

Conversation

@kixelated

@kixelated kixelated commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Finishes quest/m0/broadcast-epoch/apps.md. The publish sides already mint an epoch per run (#4942). This PR covers the watch sides and the logs.

Problem

A restarted publisher has to reach viewers on the default version too. moq-lite 06 carries no epoch, so the first cut (restart moq play on Unroutable) only worked over opt-in lite-07.

Approach

Maintainer decision (2026-10-07): lite-07 stays opt-in. A broadcast's announcement is its online/offline signal. Players start when it is announced, start over on a new announcement, stop when it ends, and wait for it to return indefinitely. Recovery never hinges on a subscription error such as Unroutable.

  • moq play follows announced() for its path instead of Unroutable. A Start, or an Update to a route without an epoch, starts over with a fresh catalog, decoders, and presentation clock. An Update that keeps an epoch is the same instance and is ignored. On End it plays out what it holds and waits for the path to return. It no longer exits when a broadcast ends: only a closed origin, a closed window, or a catalog with nothing playable ends it. The Unroutable classifier and the moq-json dependency are gone.
  • @moq/watch: lite-06 end-then-start already worked; a new test covers it. A route without an epoch changing in place did not restart anything, so Broadcast now requests the path again when that happens. Everything reading out.active then starts over on a fresh handle. An Update that keeps an epoch keeps the handle.
  • @moq/watch video/audio (from the first commit): a new instance at the same name skips the rendition catch-up gate, re-anchors Sync, and resets the shared clock.
  • demo/web drops a tile on end and rebuilds it on start, and each tile is a <moq-watch>, so it follows the same rule. No change needed.
  • Logs show the epoch: moq publishers go through a crate::moq::announce helper that mints and logs it. moq-boy logs its epoch, moq play logs each announced epoch, and the JS lite announce debug lines include it.
  • Docs: doc/bin/cli.md (Play, Redundant publishers) and doc/lib/js/watch.md describe the online/offline behavior. The quest README records the decision.

Decisions (made unattended, recommended option taken)

  1. On End, should moq play cut playback or play out what it holds? ✅ Play it out (a clean stop finishes its tracks anyway). / Cut at once.
  2. Which errors end moq play? ✅ Only a catalog with nothing this build can play ("fail loud"). A broadcast that fails on the wire is logged, and the player waits for the next announcement. / Any error. / No error.
  3. How does @moq/watch pick up a change in place to a route without an epoch? ✅ Broadcast watches the covering route and requests again, which hands out a fresh handle. js/net keeps its shared consume. / Change js/net to evict the consume on such an update.

Impact

  • No wire change. No exported API change.
  • Behavior: moq play keeps watching after a broadcast ends instead of exiting. Over lite-06, any change to the covering route restarts playback, including a benign re-price such as a GOAWAY drain. @moq/watch does the same, and that also clears a refusal.

Verification

  • New moq play tests (mocked time): a_broadcast_that_returns_plays_again (lite-06 end then start, 60 s offline gap), an_overlapping_restart_plays_the_new_broadcast (lite-06 restart while the old route stands, which surfaces as an Update once it goes), and a_republish_plays_the_new_broadcast (epochs). All three fail on the previous code, which exited when a broadcast ended. just rs play: 41 pass.
  • New @moq/watch tests: follows a broadcast that ends and is announced again and starts over when a route without an epoch changes in place. The second fails without the fix. Watch suite: 295 pass.
  • just check (scoped) passes, apart from the known shared-machine memlock failures in moq-uring. just test interop --all passes. The full Rust workspace without moq-uring passes too (6226 tests).

Open decision

lite-06 overlapping restart where the new route wins while the old still stands: a front without an epoch never swaps routes, and a new request joins that front. So starting over on the Update gets the old broadcast again, at the relay and on the client (js/net shares one consume per path on purpose). When the old route goes later, nothing new is announced, so viewers stay on the dead broadcast. The test covers the order that works (the old route keeps the path until it goes). I recommend a moq-net and js/net change: a request joins a front without an epoch only while the route it resolved through still wins, which mirrors the epoch rule. That needs a maintainer call on relay dedup under route churn, so it is a follow-up here, not part of this PR.

Follow-ups

  • The open decision above.
  • After feat!: replace moq --hop with --epoch and stop stamping unnamed publishers #4969 lands, crate::moq::announce takes the run's --epoch (one-line conflicts in each publisher).
  • @moq/publish re-creates its catalog track under the same epoch after an unannounce and re-announce, so catalog group sequences may restart under one (path, epoch).
  • Sync.reset() leaves out.timestamp at the old timeline's maximum.

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

kixelated and others added 3 commits October 6, 2026 23:46
moq play restarts on a new publisher instance when the old one ends with
Unroutable, and @moq/watch switches video at once and re-anchors the shared
clock on a republish. Publishers in moq-cli and moq-boy log the epoch they
announce.

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

Outcome: the quest is done apart from one maintainer decision. Restarts switch viewers quickly only over lite-07, which is still opt-in. A default (lite-06) publisher's epoch never reaches the relay, so viewers stall until the old route goes. I recommend promoting lite-07 to the default offer before the release this line gates. The PR stays a draft until that's decided.

(Written by Claude Opus 5.5)

kixelated and others added 4 commits October 7, 2026 08:49
moq play starts on an announcement, starts over on a new one (a new epoch,
or any change to a route without one), plays out what it holds when the
announcement ends, and waits for it to return instead of exiting.
@moq/watch re-requests its broadcast when a route without an epoch changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title feat(apps): follow a republished broadcast and log publisher epochs feat(apps): follow a broadcast's announcement as its online signal and log epochs Oct 7, 2026
@kixelated
kixelated deleted the quest/m0/broadcast-epoch/apps branch October 8, 2026 18:28
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