mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-18 06:10:43 -07:00
fix(stories): hard-cut the audio graph on stop so long imports actually halt
source.stop() was the only thing happening when a clip was halted, and on long imported buffers (multi-minute MP3s scheduled via source.start with a duration argument) it was silently failing to halt the buffer in some browsers — pause left the music playing and seek stacked another source on top of the original. The mute-the-WaveSurfer-element fix was a different bug along the same path; this is the one that actually addresses the duplicated audio. ActiveSource now carries the per-clip GainNode alongside the source, and stopSource detaches the onended handler before calling stop() (so the natural-end callback can't race with explicit teardown and re-delete a freshly rescheduled entry at the same id), then disconnects both nodes inside their own try/catch blocks. Even when stop() doesn't actually halt the buffer the graph is severed — no path from source to destination, no audio.
This commit is contained in:
@@ -5,6 +5,7 @@ import { useStoryStore } from '@/stores/storyStore';
|
||||
|
||||
interface ActiveSource {
|
||||
source: AudioBufferSourceNode;
|
||||
clipGain: GainNode;
|
||||
itemId: string;
|
||||
generationId: string;
|
||||
startTimeMs: number;
|
||||
@@ -61,11 +62,29 @@ export function useStoryPlayback(items: StoryItemDetail[] | undefined) {
|
||||
const stopSource = useCallback((itemId: string) => {
|
||||
const activeSource = activeSourcesRef.current.get(itemId);
|
||||
if (activeSource) {
|
||||
// Detach onended first so the natural-end handler doesn't race with
|
||||
// the explicit teardown below and re-delete a fresh entry that has
|
||||
// already been re-scheduled at this id.
|
||||
activeSource.source.onended = null;
|
||||
try {
|
||||
activeSource.source.stop();
|
||||
} catch {
|
||||
// Source may have already stopped
|
||||
}
|
||||
// Hard-cut the audio graph regardless of whether stop() actually
|
||||
// halted the buffer. Long imports were leaking audio when stop()
|
||||
// was called on a source that was scheduled with a multi-minute
|
||||
// duration; disconnecting from the destination guarantees silence.
|
||||
try {
|
||||
activeSource.source.disconnect();
|
||||
} catch {
|
||||
// already disconnected
|
||||
}
|
||||
try {
|
||||
activeSource.clipGain.disconnect();
|
||||
} catch {
|
||||
// already disconnected
|
||||
}
|
||||
activeSourcesRef.current.delete(itemId);
|
||||
}
|
||||
}, []);
|
||||
@@ -274,6 +293,7 @@ export function useStoryPlayback(items: StoryItemDetail[] | undefined) {
|
||||
|
||||
const activeSource: ActiveSource = {
|
||||
source,
|
||||
clipGain,
|
||||
itemId: item.id,
|
||||
generationId: item.generation_id,
|
||||
startTimeMs: item.start_time_ms,
|
||||
|
||||
Reference in New Issue
Block a user