Repository navigation
Conversation
Quest-Claim: ac9230c586257352
The relay serves its lapsed copy's stale tail before its source answers, the consumer starts on it, then waits newest-first for a backlog the decoder drops. The fix spans the relay and both consumers, so it waits on a maintainer pick. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Firefox viewer resuming from a pause read the relay's pre-pause audio, then waited the whole hole wait for a replayed backlog that arrives newest first, staying silent while the picture played. The container consumer now takes the player's clock and skips a hole once the clock reaches the group past it, since nothing missing could still play. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ne keeps waiting The consumer's hole timer now follows the playhead's anchor, so a re-anchor past the group after a hole wakes it. The max-delay video decoder reads older groups to rebuild references, so it keeps the plain wait. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 30 minutes. View limit details
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. |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the full PR at 6a4d109: the container consumer now stops waiting for a missing group once the player's clock passes it, and both @moq/watch decoders pass their clock in.
ConsumerProps.playhead/Playhead: Adds apassedcondition to the missing-group skip in#next. It fires onceplayhead.now()reaches#bound(), the start of the first timestamped group in the buffer.- Hole timer: The timer re-runs when
playhead.anchorchanges. It wakes at whichever comes first: the end of the remaining wait, or the moment the clock reaches the bound. It now also arms when the wait itself is unlimited. Sync.anchor: ADerivedoverreference,delay, andplaying, soSyncsatisfiesPlayhead.Derivedholds no resources, so nothing needs closing.- Decoders: Audio always passes
this.sync. Video passes it only on the lane that drives the clock (#drives, fixed in the constructor), so the warmup lane can still read older groups. - Docs and quests: Updates the prose in
audio-jitter.md, removes the finished quest, and adds the Rust follow-up quest.
Checked: group.start (microseconds of the frame timestamp) and Sync.now() (media time) use the same timeline. Sync.now() is undefined during buffered startup, so the start-up path is unchanged. The zero-length timer case uses the same bound and clock as the skip check, so it adds no busy loop. bun test src/container/consumer.holes.test.ts passes, 14 of 14. The JS hole rule now differs from Rust's moq-mux consumer until quest/m2/audio-playout/rust-playhead-holes.md lands, as the PR states.
claude-opus-5-5 | 𝕏

Finishes quest/m0/nightly/firefox-resume-audio.md.
Summary
Nightly's Firefox
after resumewindow stayed silent for about half a second while the picture played. The cause is in how the player waits for missing audio after a pause:Fix:
@moq/hang's containerConsumertakes an optionalplayhead, the player's playout clock. A missing group whose successor starts at or before the media time presenting now could only arrive too late to play. So the consumer skips the hole then, and the hole's timer also wakes when the clock reaches it. Both@moq/watchdecoders pass theirSync.Decisions
Feed::Idle,answered). The "stale" tail was simply the newest thing the publisher had, because the publisher had stopped producing during the pause. A consumer starting at the live edge less its reach still starts on that tail. So I changed neither the relay nor the consumer's start. The fix lands where the time actually went: the hole wait. I made this further call myself.31 -> 36).moq-muxconsumer has no caller with a pause and rejoin today, and the repository rule is that API needs a consumer. Parity is a follow-up quest.Validation
taskset -c 4-5 nix develop --command just test media --media-engines firefox --media-cases rename,pause. It passes on three or four cores, andpausealone never reaches the bug, because withoutrenamethe publisher stops producing.after resumewindow opened with 6, 12, 9, and 14 silent samples while video advanced, the CI signature. After the fix, 8 of 10 runs reached the window. 6 of them started audible. Two started silent: fix-3, whose picture was frozen at the same moment (the whole page starved), and fix-9, where audio stalled for about 0.3 s on smaller holes near the live edge. Neither showed CI's wait-out of the large hole. Separately, some two-core runs failcoded-size growthaudio/video sync, unchanged from before the fix, on a host starved harder than CI.consumer.holes.test.ts. The hole ends exactly when the clock reaches the group past it (100 ms), still at the 500 ms wait when the clock trails or when there is no clock. Removing the rule fails the first case.just checkpasses. After the round-1 fixes, three more two-core runs: every resume window started audible; one failed later in the window on scattered underflows from the starved host.Related: the interop-green
replacementcase (moq-dev/moq#8797) is likely not covered. Its failing join reads a backlog about a second behind the clock with no hole after delivery, which this change does not skip. Three local Chromiumreplacementruns passed, but that case fails only about 1 in 6 runs on a loaded host.Public API / wire impact
@moq/hang: new optionalConsumerProps.playheadand exportedPlayheadinterface (now()plus ananchorgetter that changes when the clock jumps). Additive, no migration.@moq/watch:Sync.anchor, so aSyncis aPlayhead. Additive.Caused by
No single PR. The publisher's on-demand replay (retained audio replayed on rejoin) meets the consumer's in-order hole wait. Each is correct on its own.
Follow-ups
Models
Size: L
Implementer: Claude, claude-opus-5-5, unknown effort (arm A)
(Written by Claude Opus 5.5)
Review
Rewritten by
quest reviewafter each round.Head: 6a4d109
Base: origin/main at 250a13a
Risk: medium,
the base has no RISK.md to name what is always high or customer-facing, so every change is mediumClassified: medium by default
Reviewer: Codex, gpt-6.1-sol, high effort
Session:
01a1df3b-42a9-7da2-8c07-7323c233728c, round 2Score: 10/10,
Both earlier findings are fixed, and source review of the intervening changes found no new issues; builds and tests were not run.Rounds: 2; first round 2 (medium 2); findings 2 (medium 2): fixed 2, refuted 0, open 0
Findings: none
The last round's findings, at 984f024:
js/watch/src/video/decoder.ts:728:Preserve the warmup decoder's allowance for older groups;js/watch/src/video/decoder.ts:730 passes the playhead only when #drives is true, preserving the secondary decoder's warmup allowance.js/hang/src/container/consumer.ts:1076:Recompute the hole deadline when the clock reference changes;js/watch/src/sync.ts:480 derives anchor from reference, delay, and playing, and js/hang/src/container/consumer.ts:1073 subscribes to it so clock jumps reschedule the hole deadline.