React.js中如何按顺序播放socket逐次推送的多个音频文件
解决方案
核心思路
当前问题的根源是每次接收到音频流都会直接覆盖播放器的src属性,前序还没播放的音频会被直接冲掉,解决方案核心逻辑如下:
- 新增FIFO(先进先出)队列缓存所有接收到的音频流数据,不会直接覆盖当前播放内容
- 新增播放状态标记,避免同一时间触发多次播放逻辑
- 监听音频播放结束/出错事件,播放完成后自动按顺序读取队列中下一个音频进行播放
完整实现代码
首先在组件内新增所需的状态引用:
// 存储待播放音频的队列 const audioQueueRef = useRef([]); // 标记当前是否处于播放状态 const isPlayingRef = useRef(false); // 原有播放器引用不变 const pollyPlayerRef = useRef(null);
新增队列播放控制函数:
const playNext = useCallback(async () => { // 队列为空时标记为未播放,终止流程 if (audioQueueRef.current.length === 0) { isPlayingRef.current = false; return; } isPlayingRef.current = true; // 取出队首的音频数据 const audioBlob = audioQueueRef.current.shift(); const audioUrl = URL.createObjectURL(audioBlob); // 播放结束处理逻辑 const handlePlayEnd = () => { // 释放blob资源,避免内存泄漏 URL.revokeObjectURL(audioUrl); // 移除事件监听避免重复绑定 pollyPlayerRef.current.removeEventListener('ended', handlePlayEnd); pollyPlayerRef.current.removeEventListener('error', handlePlayError); // 播放下一个音频 playNext(); }; // 播放出错处理逻辑 const handlePlayError = (err) => { console.error('音频播放失败', err); URL.revokeObjectURL(audioUrl); pollyPlayerRef.current.removeEventListener('ended', handlePlayEnd); pollyPlayerRef.current.removeEventListener('error', handlePlayError); // 出错时跳过当前音频,继续播放下一个,可根据业务需求调整为重试逻辑 playNext(); }; // 绑定事件监听 pollyPlayerRef.current.addEventListener('ended', handlePlayEnd); pollyPlayerRef.current.addEventListener('error', handlePlayError); try { pollyPlayerRef.current.src = audioUrl; await pollyPlayerRef.current.load(); await pollyPlayerRef.current.play(); } catch (err) { console.error('音频启动播放失败', err); handlePlayError(err); } }, []);
修改原有的socket监听逻辑:
useEffect(() => { const handleNewsAudioStream = async (AudioStream) => { if (speech && settings.rFeeds) { const uInt8Array = new Uint8Array(AudioStream); const arrayBuffer = uInt8Array.buffer; const blob = new Blob([arrayBuffer]); // 将音频推送到队列,不直接触发播放 audioQueueRef.current.push(blob); // 若当前未处于播放状态,启动播放流程 if (!isPlayingRef.current) { playNext(); } } }; socket.on("newsAudioStream", handleNewsAudioStream); // 组件卸载时的清理逻辑,避免内存泄漏 return () => { socket.off("newsAudioStream", handleNewsAudioStream); audioQueueRef.current = []; isPlayingRef.current = false; if (pollyPlayerRef.current) { pollyPlayerRef.current.pause(); pollyPlayerRef.current.src = ''; } }; }, [settings.rFeeds, speech, playNext]);
注意事项
- 所有通过
URL.createObjectURL创建的资源必须在使用完成后调用URL.revokeObjectURL释放,长期运行下会导致严重的内存泄漏 - 可根据业务需求扩展队列最大长度限制、播放错误重试、队列清空、暂停恢复播放等逻辑
- 部分浏览器对自动播放有限制,需要确保用户已经和页面有过交互,否则
play()方法会抛出异常
内容的提问来源于stack exchange,提问作者χerфmiи
相关产品推荐
相关产品推荐

