使用JavaScript的AudioContext API时如何全局获取音频文件时长
AudioContext获取音频时长全局不可用问题解答
问题原因
- 本质是异步执行顺序问题:XMLHttpRequest网络请求、
AudioContext.decodeAudioData()音频解码都是异步非阻塞操作,JavaScript主线程不会等待异步操作完成再执行后续同步代码,会先执行所有同步任务,再处理异步回调队列里的任务。 - 代码实际执行顺序如下:
- 声明全局变量
durationTime,初始化AudioContext实例、配置并发送XHR音频请求 - 立刻执行全局位置的
console.log,此时音频请求还未返回,durationTime为初始值0 - 等XHR请求完成触发
load事件,执行fileLoaded回调:调用decodeAudioData后不会等待解码完成,会立刻执行fileLoaded内的console.log,此时解码未完成,durationTime仍为0 - 音频解码完成后才会执行
fileDecoded回调,此时才给durationTime赋正确的时长值,所以仅在这个回调内能拿到有效结果。
- 声明全局变量
实现全局可用的方案
保证所有使用durationTime的逻辑,都在音频解码完成的异步通知触发后再执行即可,常用有3种实现方案:
1. 回调函数方案
将需要用到时长的逻辑封装为函数,在解码完成的回调中直接调用:
let durationTime; const context = new AudioContext(); const request = new XMLHttpRequest(); request.addEventListener('load', fileLoaded); request.open('GET', '/sounds/2W.mp3'); request.responseType = 'arraybuffer'; request.send(); function fileLoaded() { if (request.status < 400) { context.decodeAudioData(request.response, fileDecoded); } } function fileDecoded(audioBuffer) { durationTime = audioBuffer.duration; // 所有需要用到durationTime的业务逻辑在这里触发调用 handleAudioLogic(); } function handleAudioLogic() { // 此处可以正常使用全局的durationTime console.log('全局可用的时长:', durationTime); }
2. Promise封装方案
适合现代JS项目,支持用async/await简化异步逻辑调用:
let durationTime; // 封装获取音频时长的通用Promise方法 function getAudioDuration(url) { return new Promise((resolve, reject) => { const context = new AudioContext(); const request = new XMLHttpRequest(); request.addEventListener('load', () => { if (request.status < 400) { context.decodeAudioData( request.response, buffer => resolve(buffer.duration), reject ); } else { reject(new Error('音频资源请求失败')); } }); request.addEventListener('error', reject); request.open('GET', url); request.responseType = 'arraybuffer'; request.send(); }) } // 调用方式1:then链式调用 getAudioDuration('/sounds/2W.mp3') .then(duration => { durationTime = duration; // 后续业务逻辑 console.log('全局可用的时长:', durationTime); }) .catch(err => console.error('获取时长失败:', err)); // 调用方式2:async/await写法(更简洁) async function initAudioLogic() { try { durationTime = await getAudioDuration('/sounds/2W.mp3'); // 后续业务逻辑 console.log('全局可用的时长:', durationTime); } catch (err) { console.error(err); } } initAudioLogic();
3. 自定义事件方案
如果需要在多个独立模块中使用时长,可以用自定义事件做全局通知:
let durationTime; const context = new AudioContext(); const request = new XMLHttpRequest(); request.addEventListener('load', fileLoaded); request.open('GET', '/sounds/2W.mp3'); request.responseType = 'arraybuffer'; request.send(); function fileLoaded() { if (request.status < 400) { context.decodeAudioData(request.response, fileDecoded); } } function fileDecoded(audioBuffer) { durationTime = audioBuffer.duration; // 触发全局自定义事件,通知时长已准备完成 window.dispatchEvent(new CustomEvent('audioDurationReady', { detail: durationTime })); } // 任意需要用到时长的位置,监听自定义事件即可 window.addEventListener('audioDurationReady', (e) => { console.log('拿到全局可用的时长:', e.detail); // 对应模块的业务逻辑 });
注意:不要使用轮询判断
durationTime是否赋值的方案,会额外消耗性能且稳定性差,一定要通过异步回调/通知的方式使用该变量。
内容的提问来源于stack exchange,提问作者Jongkeun Choi
相关产品推荐
相关产品推荐

