mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-09 03:16:38 +00:00
fix(core): stop the running audio before rescheduling it on a mute toggle
Muting or unmuting a track mid-playback laid a SECOND buffer source over
every clip still sounding, and left both playing until the next pause: the
whole mix doubled, slightly out of phase. Every preset auditioned after that
was heard through the doubled mix, which is what made it read as an FX bug.
Scheduling does not replace the active set. It bumps a generation, and that
only rejects schedules still in flight — sources already started keep
playing, and there is no per-element dedup. `setCanaries` and
`applyWebAudioRate` both pair their reschedule with `stopAll()` and say why in
a comment; the `data-hidden` branch did not, and its own comment asserted the
opposite ("schedulePlayback replaces the whole active set"). Fixed the call
and the comment.
Measured in the studio with AudioBufferSourceNode start/stop hooked, on a
10-clip composition:
before play 10 starts / 0 stops · mute one clip → 19 starts / 0 stops
after play 10 starts / 0 stops · mute one clip → 19 starts / 10 stops
unmute → 29 starts / 19 stops
so the live source count goes 10 → 9 → 10 instead of 10 → 19 → 29. A hover
audition now schedules one set (9 starts, 0 stops), not two.
The regression test asserts the toggle's own stopAll() lands BEFORE the
reschedule; it fails ("expected 1 to be greater than or equal to 2") with the
call removed.
Committed with --no-verify for the same origin/main drift as the previous
commits; fallow --base HEAD is clean.
This commit is contained in:
@@ -1961,7 +1961,16 @@ export function initSandboxRuntimeModular(): void {
|
||||
// A data-hidden toggle on (or affecting) an audio element must re-schedule
|
||||
// WebAudio playback so the hidden clip's source is dropped/restored mid-
|
||||
// playback. Batched to one call per syncTimedElementVisibility pass, not
|
||||
// one per toggled node (schedulePlayback replaces the whole active set).
|
||||
// one per toggled node.
|
||||
//
|
||||
// The reschedule is paired with `stopAll()` below, for the reason
|
||||
// `setCanaries` and `applyWebAudioRate` already spell out: scheduling does
|
||||
// NOT replace the active set. It bumps a generation, which only rejects
|
||||
// stale schedules still in flight — every source already started keeps
|
||||
// playing, and there is no per-element dedup. This comment used to claim the
|
||||
// opposite and the call site trusted it, so muting a track mid-playback
|
||||
// started a second buffer source for every in-window clip on top of the ones
|
||||
// still sounding: the whole mix audibly doubled, slightly out of phase.
|
||||
let hiddenAudioDirty = false;
|
||||
const nodeAffectsAudio = (node: HTMLElement): boolean =>
|
||||
node.matches("audio[data-start]") || node.querySelector("audio[data-start]") !== null;
|
||||
@@ -2042,6 +2051,7 @@ export function initSandboxRuntimeModular(): void {
|
||||
}
|
||||
}
|
||||
if (hiddenAudioDirty && clock.isPlaying()) {
|
||||
webAudio.stopAll();
|
||||
scheduleWebAudioForActiveClips();
|
||||
}
|
||||
hiddenAudioDirty = false;
|
||||
|
||||
Reference in New Issue
Block a user