fix(stories): mute the clip waveform's media element so it can't bleed audio

The clip waveforms drawn inside each timeline track use WaveSurfer with the default MediaElement backend, which creates an internal <audio> element to drive playback timing. Web Audio in useStoryPlayback is what actually produces sound, but WaveSurfer's element was happily preloading and — after the first user gesture unlocked browser autoplay — playing the source URL through the page output too.

For TTS clips it was masked: they're short, both sources start at the same time, and stopping the BufferSourceNode at pause coincides with the natural end of the audio element. For long imports (a four-minute MP3) the BufferSourceNode stops on pause but WaveSurfer's element keeps going on its own track — which is exactly the "music keeps playing when I pause" symptom.

Hand WaveSurfer a muted <audio> element via the `media` option so the visual still loads peaks but the element itself can never produce sound. preload="metadata" keeps the load lightweight.
This commit is contained in:
Jamie Pine
2026-04-25 02:25:37 -07:00
parent 9f183e5832
commit e7846eaf77
@@ -82,8 +82,19 @@ function ClipWaveform({
const waveColor = getCSSVar('--accent-foreground');
// Hand WaveSurfer a muted <audio> element so the MediaElement backend
// can never bleed audio. Web Audio is doing the actual playback in
// useStoryPlayback; this clip waveform exists purely for the visual.
// Without this, long imported clips (MP3 / M4A) end up audible from
// wavesurfer's own element on top of the timeline, and that element
// doesn't get paused by stopAllSources().
const mediaElement = document.createElement('audio');
mediaElement.muted = true;
mediaElement.preload = 'metadata';
const wavesurfer = WaveSurfer.create({
container: waveformRef.current,
media: mediaElement,
waveColor,
progressColor: waveColor,
cursorWidth: 0,