Repository navigation
quest: buffered audio converges on the clock; rename buffer to lookahead #8034
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| # [M] Buffered audio converges on the shared clock | ||
|
|
||
| ## Goal | ||
|
|
||
| A `<moq-watch>` with a nonzero `buffer` keeps lip sync in steady state: once | ||
| a stall, a rise of the `"auto"` delay, or publisher clock drift has left the | ||
| audio playhead behind the shared clock, the ring time-stretches back onto it. | ||
| It never skips, so a TTS utterance still plays every word. Today buffered | ||
| mode turns off the ring's catch-up (`playing = !this.#stalled && | ||
| !this.#buffered` in `js/watch/src/audio/ring-buffer.ts`), so any lag is | ||
| permanent. Audio trails video for the rest of the stream, and once the lag | ||
| passes `delay + buffer` the container consumer drops whole audio groups | ||
| ("skipping slow groups"), which is the cut-out reported in | ||
| [moq-dev/moq#8031](https://github.com/moq-dev/moq/issues/8031). | ||
|
|
||
| Boundaries: video already follows the shared clock and is unchanged. The | ||
| clock is not driven by audio, and a slow audio track never holds video. Live | ||
| mode (`buffer` of zero) keeps its own catch-up. Native playback has no | ||
| buffered mode. | ||
|
|
||
| ## Plan | ||
|
|
||
| Planned 2026-11-06 (maintainer, /quest-import of | ||
| [moq-dev/moq#8031](https://github.com/moq-dev/moq/issues/8031)). A | ||
| simulation on main with the real `Sync`, container `Consumer`, `Backpressure` | ||
| and `AudioRingBuffer` (a copy of `js/watch/src/audio/replay.ts` with buffered | ||
| mode on) measured audio behind video: about -12 ms live versus +105 ms | ||
| buffered under `"auto"` with jittery then calm phases; 0 versus +300 ms after | ||
| 400 ms stalls with `delay=100ms buffer=500ms`; and a publisher clock 1% fast | ||
| climbing to 1.4 s of lag with 2926 slow-group skips. The reporter's other | ||
| claim, that a delay above 500 ms breaks lip sync, did not reproduce (300, 800 | ||
| and 1500 ms all held about -22 ms). | ||
|
|
||
| Decisions: | ||
|
|
||
| - Direction: ✅ converge buffered audio by time-stretching / won't fix, | ||
| reply and close / sharpen docs and warn only. Steady-state lip sync comes | ||
| from each track converging on the shared clock, and buffered mode broke | ||
| that even for TTS with video. | ||
| - Rename `buffer`: keep / ✅ rename in m2, as | ||
| [Rename buffer to lookahead](/quest/m2/watch-lookahead.md). | ||
| - Milestone: ✅ m1, in this epic / m3. | ||
|
|
||
| Recommendations: | ||
|
|
||
| - Reuse [Watch](/quest/m1/audio-jitter-target/watch.md)'s stretch rather | ||
| than a second mechanism: buffered mode measures the playhead's lag behind | ||
| the shared clock and accelerates until it is back within the floor. It | ||
| never stretches ahead of the clock, since future-dated frames are | ||
| buffered mode's purpose. | ||
| - Extend the replay harness with buffered rows (the stall and the drift | ||
| scenarios above), budgeting audio-behind-video and slow-group skips, and | ||
| keep a TTS row asserting no frame is dropped. | ||
| - Update the `buffer` docs in `js/watch/src/element.ts`, `js/watch/src/sync.ts` | ||
| and `doc/lib/js/watch.md` to say live streams take their slack from | ||
| `delay`, and that a buffered player catches up after a stall by playing | ||
| slightly faster. | ||
|
|
||
| Public API: none expected. Wire: none. | ||
|
|
||
| ## Required | ||
|
|
||
| - [Watch](/quest/m1/audio-jitter-target/watch.md) - lands the ring's time stretch this reuses | ||
|
|
||
| ## Closes | ||
|
|
||
| - [#8031](https://github.com/moq-dev/moq/issues/8031) - buffered playback loses lip sync and cuts audio out on a live stream | ||
|
|
||
| ## Related | ||
|
|
||
| - [Rename buffer to lookahead](/quest/m2/watch-lookahead.md) - the name that invited the misuse | ||
| - [Buffered playback subscribes in ascending order](/quest/m1/buffered-ascending.md) - the other buffered-mode change to the audio path | ||
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # [S] Rename moq-watch's buffer to lookahead | ||
|
|
||
| ## Goal | ||
|
|
||
| `<moq-watch>`'s `buffer` property and attribute, and the `Player`/`Sync` | ||
| input behind them, are named `lookahead`. Users read `buffer` as a jitter | ||
| buffer and set it on live streams, where it disables catch-up | ||
| ([moq-dev/moq#8031](https://github.com/moq-dev/moq/issues/8031)). It is | ||
| really how far future-dated media, such as TTS written faster than real time, | ||
| may run ahead of the live edge. Live slack stays `delay`. | ||
|
|
||
| Boundaries: JavaScript only. Nothing in Rust, Python, or the bindings uses | ||
| the name. The buffered-ranges UI (`buffer-control.ts`) shows something else | ||
| and keeps its name. | ||
|
|
||
| ## Plan | ||
|
|
||
| Planned 2026-11-06 (maintainer, /quest-import of | ||
| [moq-dev/moq#8031](https://github.com/moq-dev/moq/issues/8031)). | ||
|
|
||
| Decisions: | ||
|
|
||
| - Rename: keep `buffer` / ✅ rename in m2. Breaking for `@moq/watch`, so it | ||
| joins the next breaking release. | ||
| - Name: ✅ `lookahead` / `ahead` / `burst`. It says what the value is, and | ||
| is already the word the docs use. | ||
|
|
||
| Settled by repository rules: no deprecated alias. `buffer` is removed | ||
| outright, and `js/watch/MIGRATE.md` gets an Unreleased entry showing the move. | ||
| Internal names derived from it (`buffered`, `parseBuffer`) follow when they | ||
| mean the same thing. Update `doc/lib/js/watch.md`, | ||
| `doc/concept/use-case/ai.md`, the demos, and quests that name the property, | ||
| such as [Buffered playback subscribes in ascending | ||
| order](/quest/m1/buffered-ascending.md). | ||
|
|
||
| Public API: breaking, `buffer` becomes `lookahead` on `<moq-watch>` and the | ||
| `Player`/`Sync` inputs. Wire: none. | ||
|
|
||
| ## Related | ||
|
|
||
| - [Buffered audio converges on the shared clock](/quest/m1/audio-jitter-target/buffered-converge.md) - the behavior fix for the same report |
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script executed:
Repository: moq-dev/moq
Length of output: 13618
Script executed:
Repository: moq-dev/moq
Length of output: 31562
Script executed:
Repository: moq-dev/moq
Length of output: 43842
Script executed:
Repository: moq-dev/moq
Length of output: 36162
Extend the fix beyond ring catch-up.
When buffered lag exceeds
delay + buffer,Consumer.#checkMaxDelay()closes and clears each skipped group. The decoder then continues with later frames after emitting a gap. Ring time-stretch cannot restore discarded speech. Update the plan to prevent consumer drops or replay skipped groups.LanguageTool
[style] ~47-~47: ‘lag behind’ might be wordy. Consider a shorter alternative.
Context: ...: buffered mode measures the playhead's lag behind the shared clock and accelerates unti...
(EN_WORDINESS_PREMIUM_LAG_BEHIND)