React.js实现每秒调用函数更新音频播放器进度条与计时器
解决React音频播放器进度条无法持续更新的问题
我来帮你搞定这个进度条只更一次的问题!先看看你的代码里的核心问题:你现在只在音频元数据加载完成时(loadedmetadata事件)更新了一次进度,但没有设置持续监听进度变化的机制,而且切换音频时也没清理旧的监听,导致第二次播放时状态更新逻辑混乱。
下面是具体的修改方案和完整代码:
问题分析
- 你的
updateProgress只绑定了loadedmetadata事件,这个事件只会在音频元数据加载完成时触发一次,不会持续更新进度。 - 每次调用
playAudio创建新Audio实例时,旧音频的事件监听没被移除,容易造成内存泄漏和状态冲突。 - 状态更新时直接使用
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
相关产品推荐
相关产品推荐

