You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>

关键改动说明

  1. activeSources数组:精准跟踪所有正在播放的音频源节点,确保能逐个终止
  2. stopAllAudio函数:统一处理停止逻辑,清空活跃节点数组,避免内存泄漏
  3. 播放按钮前置停止逻辑:点击播放时先终止旧音频,彻底避免叠加播放
  4. 修正原代码bug:step51中添加del += 1.25,让循环内的音频按时间顺序播放(原代码所有音频会重叠在同一时间区间)
  5. 选择器修正:将btn_stop的选择器改为#button_stop,对应实际按钮的id属性
  6. 变量作用域优化:把keys移到step51函数内,避免全局变量污染

这样修改后,点击停止会立刻终止所有正在播放的音频,再次点击播放会从头开始,不会出现叠加问题,也不需要刷新页面或关闭AudioContext。

内容的提问来源于stack exchange,提问作者Raj A

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 04:50:18