Web Audio API播放/停止按钮问题:重复播放时旧音频叠加
解决Web Audio API播放/停止音频叠加问题
你的问题核心在于没有跟踪并终止已创建的音频源节点,只是暂停/关闭上下文或者刷新页面,导致之前的音频源还在继续播放。下面是针对性的解决方案:
核心思路
- 维护一个数组存储所有正在播放的
BufferSource节点 - 点击停止时,遍历数组调用每个节点的
stop()方法终止播放,同时清空数组 - 每次点击播放前,先执行停止逻辑,确保旧音频完全终止后再启动新播放
修改后的完整代码
<script> try { window.AudioContext = window.AudioContext || window.webkitAudioContext; const ctx = new AudioContext(); } catch(e) { alert('Web Audio API is not supported in this browser'); } const btn = document.querySelector('button'); const btn_stop = document.querySelector('#button_stop'); // 修正选择器,对应按钮id let activeSources = []; // 存储所有活跃的音频源节点 let c4; // 存储解码后的音频数据 fetch("http://localhost:8000/3saudio/C4v9.wav") .then(data => data.arrayBuffer()) .then(arrayBuffer => ctx.decodeAudioData(arrayBuffer)) .then(decodedAudio => { c4 = decodedAudio; }); function playnote(note, delay) { const playSound = ctx.createBufferSource(); const detune = 0; const nt = c4; playSound.buffer = nt; const gainNode = ctx.createGain(); gainNode.gain.value = 1; gainNode.gain.setValueAtTime(1, ctx.currentTime + delay); gainNode.gain.exponentialRampToValueAtTime(0.0005, ctx.currentTime + 1.25 + delay); if (detune > 0) { playSound.detune.value = detune; } playSound.connect(gainNode).connect(ctx.destination); playSound.start(ctx.currentTime + delay); // 将音频源加入活跃数组,播放结束后自动移除 activeSources.push(playSound); playSound.addEventListener('ended', () => { const index = activeSources.indexOf(playSound); if (index !== -1) { activeSources.splice(index, 1); } }); } function step51() { let del = 0; const start = 12; const keys = ['c4','c4','c4','c4', 'c4']; for (let i = 0; i < 10 ; i++) { playnote(keys[start+0], del + 0); playnote(keys[start+0], del + .25); playnote(keys[start+4], del + .5); playnote(keys[start+4], del + .75); playnote(keys[start+7], del + 1); playnote(keys[start+7], del + 1.25); del += 1.25; // 修正原代码bug:让循环内的音频按时间顺序播放 } } // 统一停止所有音频的工具函数 function stopAllAudio() { activeSources.forEach(source => { try { source.stop(); } catch(e) { // 忽略已停止节点的报错 } }); activeSources = []; // 可选:快速静音,防止残留音频片段 const masterGain = ctx.createGain(); masterGain.gain.setValueAtTime(0, ctx.currentTime); ctx.destination.connect(masterGain); } btn.addEventListener('click', function() { if (ctx.state === "suspended") { ctx.resume(); } // 先终止所有旧音频,再启动新播放 stopAllAudio(); step51(); }); btn_stop.addEventListener('click', function() { stopAllAudio(); }); </script>
关键改动说明
activeSources数组:精准跟踪所有正在播放的音频源节点,确保能逐个终止stopAllAudio函数:统一处理停止逻辑,清空活跃节点数组,避免内存泄漏- 播放按钮前置停止逻辑:点击播放时先终止旧音频,彻底避免叠加播放
- 修正原代码bug:
step51中添加del += 1.25,让循环内的音频按时间顺序播放(原代码所有音频会重叠在同一时间区间) - 选择器修正:将
btn_stop的选择器改为#button_stop,对应实际按钮的id属性 - 变量作用域优化:把
keys移到step51函数内,避免全局变量污染
这样修改后,点击停止会立刻终止所有正在播放的音频,再次点击播放会从头开始,不会出现叠加问题,也不需要刷新页面或关闭AudioContext。
内容的提问来源于stack exchange,提问作者Raj A
相关产品推荐
相关产品推荐

