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

React.js实现每秒调用函数更新音频播放器进度条与计时器

解决React音频播放器进度条无法持续更新的问题

我来帮你搞定这个进度条只更一次的问题!先看看你的代码里的核心问题:你现在只在音频元数据加载完成时(loadedmetadata事件)更新了一次进度,但没有设置持续监听进度变化的机制,而且切换音频时也没清理旧的监听,导致第二次播放时状态更新逻辑混乱。

下面是具体的修改方案和完整代码:

问题分析

  1. 你的updateProgress只绑定了loadedmetadata事件,这个事件只会在音频元数据加载完成时触发一次,不会持续更新进度。
  2. 每次调用playAudio创建新Audio实例时,旧音频的事件监听没被移除,容易造成内存泄漏和状态冲突。
  3. 状态更新时直接使用this.state.audio.currentTime可能因为React异步更新的特性拿到旧值,应该直接用当前操作的audio实例。

修改后的完整代码

1. 补充状态(用来存储事件监听引用,方便后续清理)

state = {
  playing: false,
  playing_story: '',
  audio: null,
  progress: 0,
  time_total: 0,
  time_played: 0,
  // 存储事件监听函数的引用,用于后续移除
  progressListeners: null
};

2. 重写updateProgress函数,添加持续进度监听

updateProgress(audio) {
  if (!audio) return;

  // 监听元数据加载,初始化总时长
  const handleLoadedMetadata = () => {
    this.setState({
      time_total: audio.duration,
      time_played: audio.currentTime,
      progress: audio.currentTime / audio.duration
    });
    console.log('time total', audio.duration);
  };

  // 监听音频进度变化,实时更新状态(浏览器原生事件,进度变化时自动触发)
  const handleTimeUpdate = () => {
    this.setState({
      time_played: audio.currentTime,
      progress: audio.currentTime / audio.duration
    });
    console.log('time played', audio.currentTime);
    console.log('progress', audio.currentTime / audio.duration);
  };

  // 绑定事件监听
  audio.addEventListener('loadedmetadata', handleLoadedMetadata);
  audio.addEventListener('timeupdate', handleTimeUpdate);

  // 存储监听函数引用,方便后续移除
  this.setState({
    progressListeners: { handleLoadedMetadata, handleTimeUpdate }
  });
}

3. 修改playAudio函数,切换音频时清理旧监听

playAudio(playing_url) {
  // 先清理旧音频的事件监听,避免内存泄漏和状态冲突
  if (this.state.audio && this.state.progressListeners) {
    const { audio, progressListeners } = this.state;
    audio.removeEventListener('loadedmetadata', progressListeners.handleLoadedMetadata);
    audio.removeEventListener('timeupdate', progressListeners.handleTimeUpdate);
  }

  const newAudio = new Audio(playing_url);
  this.updateProgress(newAudio);

  if (!this.state.playing) {
    newAudio.play();
    this.setState({ playing: true, playing_story: playing_url, audio: newAudio });
  } else {
    if (this.state.playing_story === playing_url) {
      this.state.audio.pause();
      this.setState({ playing: false });
    } else {
      this.state.audio.pause();
      newAudio.play();
      this.setState({
        playing: true,
        playing_story: playing_url,
        audio: newAudio,
        progress: 0
      });
    }
  }
}

4. 组件卸载时清理资源(防止内存泄漏)

componentWillUnmount() {
  if (this.state.audio && this.state.progressListeners) {
    const { audio, progressListeners } = this.state;
    // 移除监听
    audio.removeEventListener('loadedmetadata', progressListeners.handleLoadedMetadata);
    audio.removeEventListener('timeupdate', progressListeners.handleTimeUpdate);
    // 暂停并销毁音频实例
    audio.pause();
    this.setState({ audio: null });
  }
}

为什么这样修改?

  • 用浏览器原生的timeupdate事件代替定时调用:这个事件会在音频播放进度变化时自动触发(每秒约4-6次),比手动设置setInterval更准确,也不会出现定时器和音频不同步的问题。
  • 每次切换音频时清理旧监听:避免多个音频实例同时触发状态更新,导致进度条混乱,同时防止内存泄漏。
  • 组件卸载时彻底清理资源:确保页面关闭或组件销毁时,音频和事件监听都被正确释放。

内容的提问来源于stack exchange,提问作者Dan Cristian

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 10:04:36