WebAudio基础实现多缓冲区音频独立播放问题求助
问题原因
你当前代码无法触发play5()是因为存在以下几个核心问题:
- 没有定义
play5()函数:仅给按钮绑定了点击事件,没有编写对应的函数实现,点击后自然没有响应 - 全局唯一的
sample变量冲突:所有播放逻辑共用同一个全局变量存储音频源实例,后续播放会覆盖之前的实例,不仅会导致前序播放被意外中断,stop()方法也只能停止最后一次触发的音频 - 音频资源重复加载:每次点击播放都会重新请求、解码音频文件,既会产生播放延迟,也浪费带宽
- 音频上下文解锁逻辑冗余:
faked变量仅用于首次用户交互时解锁AudioContext,写在每个播放函数里会导致只要触发过一次播放,其他播放函数里的解锁逻辑就不会执行,虽然AudioContext只需要解锁一次,但硬编码到每个函数中容易出现适配问题
优化实现方案
你可以直接用通用播放逻辑替代每个音频单独写函数的方案,支持多音频独立/叠加播放,代码如下:
<button onclick="playAudio('one')">I</button> <button onclick="playAudio('five')">V</button> <button onclick="stopAllAudio()">停止所有播放</button> <script> let faked = true; if (!window.AudioContext && window.webkitAudioContext) { window.AudioContext = window.webkitAudioContext; faked = false; } const audioContext = new AudioContext(); // 缓存解码后的音频buffer,避免重复加载 const audioBufferCache = {}; // 存储所有正在播放的音频源实例,支持多音频同时控制 const playingSamples = []; // 通用播放函数,传入音频标识 function playAudio(audioKey) { // 首次交互解锁AudioContext if (!faked) { faked = true; const emptyBuffer = audioContext.createBuffer(1, 1, audioContext.sampleRate); const tempSource = audioContext.createBufferSource(); tempSource.buffer = emptyBuffer; tempSource.connect(audioContext.destination); tempSource.start(); } // 已经缓存过直接播放 if (audioBufferCache[audioKey]) { playBuffer(audioBufferCache[audioKey]); return; } // 没缓存就加载解码,可根据自己的文件路径规则调整映射逻辑 fetch(`major/${audioKey === 'one' ? 'one' : 'five'}.mp3`) .then(response => response.arrayBuffer()) .then(arrayBuffer => { if (audioContext.decodeAudioData.length === 2) { // Safari兼容 return new Promise(resolve => { audioContext.decodeAudioData(arrayBuffer, buffer => { resolve(buffer); }); }); } else { return audioContext.decodeAudioData(arrayBuffer); } }) .then(audioBuffer => { audioBufferCache[audioKey] = audioBuffer; playBuffer(audioBuffer); }) .catch(e => console.error('音频加载失败', e)); } // 播放音频buffer的公共方法 function playBuffer(buffer) { const sample = audioContext.createBufferSource(); sample.buffer = buffer; sample.connect(audioContext.destination); // 音频播放结束后自动从播放列表移除 sample.onended = () => { const index = playingSamples.indexOf(sample); if (index > -1) playingSamples.splice(index, 1); }; playingSamples.push(sample); sample.start(); } function stopAllAudio() { playingSamples.forEach(sample => { try { sample.stop() } catch(e) {} }); playingSamples.length = 0; } </script>
如果你一定要保留每个音频对应单独的play1()、play5()函数的写法,只需要复制play1()的逻辑,修改函数名为play5(),同时把请求的音频路径改成你第二个音频的路径,将sample变量改为函数内部局部变量避免互相覆盖即可。
内容的提问来源于stack exchange,提问作者quarter-sawn
相关产品推荐
相关产品推荐

