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

JavaScript检测Oscillator振荡器音频自然播放结束问题

问题原因

Web Audio API 中的 OscillatorNode 属于无限时长音频源,本身不存在「自然播放完毕」的状态:只要不主动调用stop()方法,它会持续生成音频采样信号,哪怕你通过GainNode把输出增益设为0实现静音,振荡器节点本身依然处于运行状态,永远不会触发ended事件。
你当前的代码逻辑只是通过调整增益值实现摩斯电码的通断静音,从未调用过oscillator.stop(),因此绑定的ended回调永远不会执行。

正确实现方案

你在遍历摩斯电码字符时,已经通过变量t累加计算出了整段音频的精确结束时间(和音频上下文时钟绑定,精度远高于setTimeout),只需要在启动振荡器时,传入预计算的结束时间调用stop()方法,让振荡器在播放完成的时间点自动停止,即可正常触发ended回调。
这种实现方式是Web Audio标准推荐的预调度方案,不会受主线程任务阻塞影响,时间精度和音频播放完全对齐。

修正后的核心代码

let AudioContext = window.AudioContext || window.webkitAudioContext;
let ctx = new AudioContext();
let dot = 1.2 / 15;
let oscillator = null; // 补全变量声明,避免全局污染
let oscillatorStatus = false;

function morsecode_audio_play() {
    morsecode_textToSpeech_stop()
    document.querySelector('#play_audio_button_morse').innerHTML = '<button id="stop_audio_morse">⏹️</button>';
    document.querySelector('#stop_audio_morse').addEventListener('click', morsecode_audio_stop);

    oscillator = ctx.createOscillator();
    let t = ctx.currentTime;

    oscillator.type = "sine";
    oscillator.frequency.value = 600;
  
    let gainNode = ctx.createGain();
    gainNode.gain.setValueAtTime(0, t);
    
    let morsecode = inputMorse.value.split("");

    morsecode.forEach(function(letter) {
        switch(letter) {
            case ".":
                gainNode.gain.setValueAtTime(1, t);
                t += dot;
                gainNode.gain.setValueAtTime(0, t);
                t += dot;
                break;
            case "-":
                gainNode.gain.setValueAtTime(1, t);
                t += 3 * dot;
                gainNode.gain.setValueAtTime(0, t);
                t += dot;
                break;
            case " ":
                t += 7 * dot; 
                break;
        }  
    });

    oscillator.connect(gainNode);
    gainNode.connect(ctx.destination);
    oscillator.start();
    // 核心新增:在预计算的结束时间点自动停止振荡器
    oscillator.stop(t);
    oscillatorStatus = true;

    oscillator.addEventListener('ended', () => {
        console.log('oscillator ended');
        // 停止后断开节点连接,释放资源
        oscillator.disconnect();
        gainNode.disconnect();
        oscillatorStatus = false;
        morsecode_audio_load();
    })

    return false;
}

注意事项

  • 不要使用setTimeout监听播放结束:JS定时器属于宏任务,会受页面渲染、其他脚本执行阻塞影响,时间偏差可达几十到上百毫秒,和音频播放进度无法对齐。
  • 停止播放的逻辑(morsecode_audio_stop方法)中,直接调用oscillator.stop(ctx.currentTime)即可立刻停止音频,同样会触发ended回调执行收尾逻辑,不需要额外写重复的重置代码。
  • 每次播放新建的振荡器、增益节点,在播放结束后要调用disconnect()断开连接,避免内存泄漏和多个节点叠加发声的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 04:09:14