如何合并重复M4A音频并关联audio标签,使控制器时长显示正确
实现音频重复合并并显示正确总时长的方案
核心思路
要让原生audio控件的进度条显示15分钟总时长,不能仅靠前端循环播放,必须将音频数据在内存中合并为一个完整的音频对象,再赋值给audio元素,这样控件才能识别真实的总时长。
具体实现代码
HTML部分(保留原结构)
<audio id="audio" controls> Your browser does not support the audio element. </audio>
JavaScript部分(实现音频合并)
const audioElement = document.getElementById('audio'); async function mergeAndLoadAudio() { // 加载原始音频文件 const response = await fetch('Countdown.m4a'); const arrayBuffer = await response.arrayBuffer(); const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const originalBuffer = await audioContext.decodeAudioData(arrayBuffer); // 创建三倍长度的新音频缓冲区 const mergedBuffer = audioContext.createBuffer( originalBuffer.numberOfChannels, originalBuffer.length * 3, originalBuffer.sampleRate ); // 循环三次拷贝原始音频数据到新缓冲区 for (let channel = 0; channel < originalBuffer.numberOfChannels; channel++) { const originalChannelData = originalBuffer.getChannelData(channel); const mergedChannelData = mergedBuffer.getChannelData(channel); mergedChannelData.set(originalChannelData, 0); mergedChannelData.set(originalChannelData, originalBuffer.length); mergedChannelData.set(originalChannelData, originalBuffer.length * 2); } // 将合并后的缓冲区转为WAV格式Blob,赋值给audio元素 const wavBlob = await bufferToWav(mergedBuffer); audioElement.src = URL.createObjectURL(wavBlob); } // 辅助函数:AudioBuffer转WAV格式Blob function bufferToWav(buffer) { return new Promise(resolve => { const totalLength = buffer.length * buffer.numberOfChannels * 2 + 44; const arrayBuffer = new ArrayBuffer(totalLength); const view = new DataView(arrayBuffer); const channels = []; let offset = 0; let pos = 0; // 写入WAV文件头 const writeUint16 = value => { view.setUint16(pos, value, true); pos += 2; }; const writeUint32 = value => { view.setUint32(pos, value, true); pos += 4; }; writeUint32(0x46464952); // "RIFF"标识 writeUint32(totalLength - 8); // 文件总长度减8 writeUint32(0x45564157); // "WAVE"标识 writeUint32(0x20746d66); // "fmt "子块标识 writeUint32(16); // PCM格式子块大小 writeUint16(1); // PCM编码格式 writeUint16(buffer.numberOfChannels); // 声道数 writeUint32(buffer.sampleRate); // 采样率 writeUint32(buffer.sampleRate * 2 * buffer.numberOfChannels); // 比特率 writeUint16(buffer.numberOfChannels * 2); // 块对齐 writeUint16(16); // 比特深度 writeUint32(0x61746164); // "data"子块标识 writeUint32(totalLength - pos - 4); // 音频数据长度 // 写入各声道音频数据 for (let i = 0; i < buffer.numberOfChannels; i++) { channels.push(buffer.getChannelData(i)); } while (pos < totalLength) { for (let i = 0; i < buffer.numberOfChannels; i++) { const sample = Math.max(-1, Math.min(1, channels[i][offset])); const encodedSample = sample < 0 ? sample * 0x8000 : sample * 0x7FFF; view.setInt16(pos, encodedSample, true); pos += 2; } offset++; } resolve(new Blob([arrayBuffer], { type: 'audio/wav' })); }); } // 页面加载完成后执行合并逻辑 window.addEventListener('load', mergeAndLoadAudio);
注意事项
- 上述代码将音频转为WAV格式,浏览器兼容性好;若需保留m4a格式,需引入额外的音频编码库(如基于WebAssembly的MP4编码工具),但WAV足够满足前端播放需求。
- 确保音频文件与页面同域,或服务器配置了CORS允许跨域访问,否则fetch会失败。
- 合并后的音频会占用一定内存,5分钟音频重复三次的规模在主流浏览器中都能正常处理。
内容的提问来源于stack exchange,提问作者Rational Guru
相关产品推荐
相关产品推荐

