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
相关产品推荐
相关产品推荐

