如何获取AudioContext播放音频时的准确当前时间?
音频播放准确获取当前时间问题排查与修复方案
问题场景
需要在音频播放过程中获取准确的当前时间:播放开始时记录为0,结束时等于音频总时长。但现有基于AudioContext的代码出现异常,例如音频结束时出现currentTime:36.02133333333333大于totalDuration:36.006875的情况。
原代码如下:
HTML代码
<div id="selector"> <label for="valueSelection">Choose a value <select id="valueSelection"> <option>off</option> <option>on</option> </select> </label> </div> <audio></audio>
JavaScript代码
const audioUrl = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/128337/ongoing-thing-crop.ogg'; /*test url2 - https://s.cdpn.io/1202/Star_Wars_original_opening_crawl_1977.mp3*/ const div = document.querySelector('div'); const audioContext = new(window.AudioContext || window.webkitAudioContext)(); let bufferSource = audioContext.createBufferSource(); let analyser = audioContext.createAnalyser(); let gainNode = audioContext.createGain(); let startTime; async function setupAudio(url) { const response = await fetch(url); const audioData = await response.arrayBuffer(); const buffer = await audioContext.decodeAudioData(audioData); bufferSource.buffer = buffer; bufferSource.connect(analyser); analyser.connect(gainNode); gainNode.connect(audioContext.destination); function updateTime() { let currentTime = audioContext.currentTime - startTime; console.log(`currentTime:${currentTime}`,`totalDuration${buffer.duration}`); if (currentTime < buffer.duration) { requestAnimationFrame(updateTime); } } div.addEventListener('change', () => { startTime = audioContext.currentTime; bufferSource.start(0); requestAnimationFrame(updateTime); }); } setupAudio(audioUrl);
问题根源
- requestAnimationFrame执行时机误差:
requestAnimationFrame与浏览器渲染帧同步,回调执行时机并非精确的音频时钟节点,音频结束后回调可能仍会触发一次,导致计算出的currentTime超过总时长。 - BufferSource复用错误:全局的
bufferSource只能被启动一次,重复调用start会触发报错,同时打乱时间计算逻辑。
正确实现方案
核心优化点
- 监听BufferSource的
ended事件,精准捕获音频结束时机,终止时间更新。 - 每次播放创建新的BufferSource实例,避免复用导致的错误。
- 限制
currentTime最大值为音频总时长,防止计算值超出范围。
修复后完整代码
HTML代码
<div id="selector"> <label for="valueSelection">选择播放状态 <select id="valueSelection"> <option>off</option> <option>on</option> </select> </label> </div> <audio></audio>
JavaScript代码
const audioUrl = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/128337/ongoing-thing-crop.ogg'; const div = document.querySelector('div'); const audioContext = new (window.AudioContext || window.webkitAudioContext)(); let analyser = audioContext.createAnalyser(); let gainNode = audioContext.createGain(); let updateId = null; async function setupAudio(url) { const response = await fetch(url); const audioData = await response.arrayBuffer(); const buffer = await audioContext.decodeAudioData(audioData); analyser.connect(gainNode); gainNode.connect(audioContext.destination); function updateTime(startTime) { // 限制当前时间不超过总时长 let currentTime = Math.min(audioContext.currentTime - startTime, buffer.duration); console.log(`currentTime:${currentTime}`, `totalDuration:${buffer.duration}`); if (currentTime < buffer.duration) { updateId = requestAnimationFrame(() => updateTime(startTime)); } } div.addEventListener('change', () => { // 取消之前的更新任务,避免重复执行 if (updateId) { cancelAnimationFrame(updateId); } // 每次播放创建新的BufferSource实例 const bufferSource = audioContext.createBufferSource(); bufferSource.buffer = buffer; bufferSource.connect(analyser); const startTime = audioContext.currentTime; bufferSource.start(0); updateTime(startTime); // 监听音频结束事件,停止更新并输出准确结束时间 bufferSource.addEventListener('ended', () => { cancelAnimationFrame(updateId); console.log(`currentTime:${buffer.duration}`, `totalDuration:${buffer.duration}`); }); }); } setupAudio(audioUrl);
优化说明
- 每次播放创建新BufferSource:解决了原代码中BufferSource只能启动一次的问题,避免报错和逻辑混乱。
- 监听
ended事件:在音频真正结束时立即停止时间更新,确保不会出现超时计算。 - 限制currentTime最大值:通过
Math.min确保计算出的当前时间不会超过音频总时长,即使回调延迟执行也能保证数值准确。 - 取消多余动画帧:切换播放状态时先取消之前的
requestAnimationFrame任务,避免多个更新任务同时运行。
内容的提问来源于stack exchange,提问作者noviceCoder
相关产品推荐
相关产品推荐

