HTML 标签不支持多音轨混合,必须用 Web Audio API 解码、对齐、加权叠加 PCM 数据;直接并发播放多个 会导致不同步、无法读取原始数据、数字削波和失步。

HTML <audio></audio> 标签本身不支持多音轨混合——它只能播放单一路媒体流。所谓“多音轨混合”,必须用 Web Audio API 手动解码、对齐、加权叠加 PCM 数据,否则你看到的只是多个音频元素并发播放,不同步、不可控、必爆音。
为什么不能直接用多个
常见错误是给页面塞 3 个 <audio></audio>,然后同时调 .play()。结果往往是:
- 各音频独立解码,起始误差常达 ±80–120ms,几秒后明显拖拍
- 浏览器禁止 JS 直接读取
<audio></audio>的原始 PCM 数据,无法做加法运算 - 两路满幅信号(-1.0 ~ +1.0)直接相加,输出值超出 [-1, 1] 范围 → 触发数字削波(clipping),出现刺耳爆音
- 没有统一时间轴,
currentTime各自为政,暂停/跳转/变速时完全失步
正确做法:用 Web Audio API 构建混音链路
核心是复用一个 AudioContext,把所有音轨预加载为 AudioBuffer,再通过节点连接实现可控叠加:
- 只创建一次
AudioContext,后续所有操作都复用它 - 用
fetch().then(r => r.arrayBuffer())获取音频二进制,再调context.decodeAudioData()解码;解码后缓存buffer,避免重复阻塞主线程 - 每轨用
context.createBufferSource()实例,启动时传入精确时间戳:source.start(context.currentTime)或source.start(context.currentTime + 0.01, offset) - 混音推荐走统一
GainNode:每轨.connect(mixGain),再mixGain.connect(context.destination) - 设
mixGain.gain.setValueAtTime(0.5)是安全起点——两路满幅信号叠加后不会越界
立体声下混成单声道的坑点
不是简单丢掉右声道,也不是用 ChannelMergerNode 拼接——它只合并通道,不做计算。正确下混必须手动平均:
立即学习“前端免费学习笔记(深入)”;
- 用
buffer.getChannelData(0)和buffer.getChannelData(1)分别提取左右声道Float32Array - 新建单声道
AudioBuffer,循环写入mono[i] = (left[i] + right[i]) / 2 - 或者更工程化:用
ChannelSplitterNode拆出左右,各自接GainNode设gain.value = 0.5,再一起连到context.destination——Web Audio 会自动无缩放相加
实时切换音轨或动态混音的注意事项
如果要做“点击按钮切换背景音乐+人声轨”,别改 <audio></audio> 的 src,而是:
- 提前加载全部音轨的
AudioBuffer,存在 Map 或对象里,键名对应轨道 ID - 切换时,停掉当前
BufferSourceNode(调.stop()),再用新 buffer 创建新 source 并.start(context.currentTime) - 若需淡入淡出,用
gain.exponentialRampToValueAtTime()控制增益变化,别硬切 - 所有操作必须在用户手势(如 click)后首次调用
context.resume(),否则 Chrome/Firefox 静音
真正难的不是写几行 connect 代码,而是时间戳对齐、增益缩放边界、缓冲区复用时机和用户交互触发链的衔接——这些地方一松懈,混音就变成噪音合集。



















