Chrome中使用ToneJS遇AudioContext无法启动问题的原因与解决
问题原因与解决方案
这个问题我做Web音频项目时也踩过坑,本质是浏览器的自动播放安全政策在限制你!
为什么会出现这个错误?
现代浏览器(包括Chrome)为了避免未经用户允许的自动播放音频打扰用户,制定了严格规则:AudioContext必须在用户主动触发的交互事件(比如点击、按键、触摸屏幕)之后,才能被创建或者恢复(resume)。
你刷新页面后,ToneJS会自动初始化AudioContext,但这个过程是页面加载时自动执行的,并没有绑定任何用户手势,所以浏览器会直接把这个AudioContext挂起(进入suspended状态)。当你按下按键触发triggerAttack时,因为上下文没被激活,就会抛出那个错误。
至于控制台输入Tone.context.resume()能恢复,是因为你手动在控制台输入命令属于用户主动操作,符合浏览器的手势要求,所以允许激活上下文。
几种可行的解决方案
方案1:绑定全局用户手势事件恢复上下文
在页面加载后,绑定一个点击或者按键事件,第一次用户交互时恢复Tone的上下文,之后就可以正常使用了:
// 页面加载后先确保上下文处于暂停状态(Tone默认可能已经是suspended) Tone.context.suspend(); // 监听页面点击,只执行一次恢复操作 document.addEventListener('click', () => { if (Tone.context.state === 'suspended') { Tone.context.resume(); console.log('音频上下文已激活,现在可以正常演奏啦!'); } }, { once: true }); // 你的原有代码 var keyToPitch = { "z":"C3", "s":"C#3", "x":"D3", "d":"D#3", "c":"E3", "v":"F3", "g":"F#3", "b":"G3", "h":"G#3", "n":"A3", "j":"A#3", "m":"B3", ",":"C4" } var synth = new Tone.PolySynth(6, Tone.Synth, { "oscillator" : { "type": "sawtooth", "partials" : [0, 2, 3, 4], } }).toMaster(); function onkeydown(e){ // 额外做个检查,避免用户还没点击就按键的情况 if (Tone.context.state === 'suspended') { Tone.context.resume().then(() => { synth.triggerAttack(keyToPitch[e.key], Tone.context.currentTime); }); } else { synth.triggerAttack(keyToPitch[e.key], Tone.context.currentTime); } } function onkeyup(e){ synth.triggerRelease(keyToPitch[e.key]) } window.addEventListener('keydown', onkeydown) window.addEventListener('keyup', onkeyup)
方案2:延迟初始化Synth到第一次用户交互时
把创建Synth的逻辑放到第一次用户按键或者点击事件里,这样AudioContext会在用户手势内被创建,自然就不会被挂起:
var keyToPitch = { "z":"C3", "s":"C#3", "x":"D3", "d":"D#3", "c":"E3", "v":"F3", "g":"F#3", "b":"G3", "h":"G#3", "n":"A3", "j":"A#3", "m":"B3", ",":"C4" } let synth; // 初始化Synth的函数 function initSynth() { synth = new Tone.PolySynth(6, Tone.Synth, { "oscillator" : { "type": "sawtooth", "partials" : [0, 2, 3, 4], } }).toMaster(); // 初始化完成后,移除临时的按键监听 window.removeEventListener('keydown', initOnFirstKey); } // 第一次按键时初始化Synth并触发声音 function initOnFirstKey(e) { initSynth(); synth.triggerAttack(keyToPitch[e.key], Tone.context.currentTime); } function onkeydown(e){ synth.triggerAttack(keyToPitch[e.key], Tone.context.currentTime); } function onkeyup(e){ synth.triggerRelease(keyToPitch[e.key]); } // 先绑定临时的按键监听,第一次按键时初始化 window.addEventListener('keydown', initOnFirstKey); window.addEventListener('keyup', onkeyup);
方案3:给用户添加明确的激活提示
如果你的项目是面向普通用户,最好在页面上显示一个“点击激活音频”的按钮,引导用户主动触发交互,体验会更友好:
<button id="activateAudio">点击激活音频演奏</button>
document.getElementById('activateAudio').addEventListener('click', () => { if (Tone.context.state === 'suspended') { Tone.context.resume(); document.getElementById('activateAudio').style.display = 'none'; } }); // 后续的Synth初始化和事件监听和方案1一致
内容的提问来源于stack exchange,提问作者drenl
相关产品推荐
相关产品推荐

