You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取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);

问题根源

  1. requestAnimationFrame执行时机误差:requestAnimationFrame与浏览器渲染帧同步,回调执行时机并非精确的音频时钟节点,音频结束后回调可能仍会触发一次,导致计算出的currentTime超过总时长。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 02:03:35