React Native expo-av 如何预加载多音频消除连续播放的停顿?
解决方案
核心优化思路:
- 提前预加载所有数字音频资源,避免播放时才初始化实例、加载文件产生的耗时停顿
- 用播放队列统一管理待播放的音频序号,消除嵌套回调和重复绑定播放状态监听的冗余逻辑
- 复用统一的播放状态回调,音频播放完成后直接从预加载列表取对应音频无缝切换播放
完整实现代码
import React, { useState, useEffect } from 'react'; import { Audio } from 'expo-av'; // 提前定义所有音频资源 const audioAssets = [ require(`../../assets/en_num_0.mp3`), require(`../../assets/en_num_1.mp3`), require(`../../assets/en_num_2.mp3`), require(`../../assets/en_num_3.mp3`), require(`../../assets/en_num_4.mp3`), require(`../../assets/en_num_5.mp3`), require(`../../assets/en_num_6.mp3`), require(`../../assets/en_num_7.mp3`), require(`../../assets/en_num_8.mp3`), require(`../../assets/en_num_9.mp3`), ]; export default function NumberAudioPlayer() { // 存储所有预加载完成的sound实例 const [soundList, setSoundList] = useState([]); // 当前待播放的音频队列,存储数字序号 const [playQueue, setPlayQueue] = useState([]); // 当前播放到队列的索引位置 const [currentQueueIndex, setCurrentQueueIndex] = useState(0); // 组件挂载时预加载所有音频 useEffect(() => { let isMounted = true; const preloadAllAudios = async () => { const loadedSounds = []; for (let i = 0; i < audioAssets.length; i++) { // 预加载时不自动播放 const { sound } = await Audio.Sound.createAsync(audioAssets[i], { shouldPlay: false }); // 所有音频统一绑定同一个播放状态监听回调 sound.setOnPlaybackStatusUpdate(playStatusHandler); loadedSounds.push(sound); } if (isMounted) { setSoundList(loadedSounds); } }; preloadAllAudios(); // 组件卸载时释放所有音频资源,避免内存泄漏 return () => { isMounted = false; soundList.forEach(sound => { sound?.unloadAsync(); }); }; }, []); // 统一的播放状态监听回调 const playStatusHandler = async (status) => { if (!status.didJustFinish) return; // 当前音频播放完成,检查队列是否有下一个待播放音频 const nextIndex = currentQueueIndex + 1; if (nextIndex < playQueue.length) { // 直接播放预加载好的下一个音频,无加载耗时 setCurrentQueueIndex(nextIndex); const nextSound = soundList[playQueue[nextIndex]]; // 重置播放位置到开头 await nextSound.setPositionAsync(0); await nextSound.playAsync(); } }; // 支持传入任意数量的数字参数进行连续播放 async function playSound(...nums) { if (soundList.length === 0) return; // 预加载未完成时可自行添加加载提示 // 先停止当前正在播放的音频 if (playQueue.length > 0 && currentQueueIndex < playQueue.length) { const currentSound = soundList[playQueue[currentQueueIndex]]; await currentSound.stopAsync(); } // 更新播放队列,重置播放索引 setPlayQueue(nums); setCurrentQueueIndex(0); // 播放队列第一个音频 const firstSound = soundList[nums[0]]; await firstSound.setPositionAsync(0); await firstSound.playAsync(); } // 此处补充你的UI组件逻辑 return <></>; }
实现说明
- 预加载逻辑只会在组件挂载时执行一次,所有音频实例初始化完成后常驻内存,播放切换时不需要再做文件加载、实例初始化操作,完全消除了切换间隙的停顿
- 播放状态监听只需要绑定一次,不管连续播放多少个音频都不需要重复注册回调,也没有嵌套回调的冗余代码
- 支持传入任意数量的数字进行连续播放,不需要修改底层逻辑
- 若后续音频资源数量变大,可将全量预加载调整为预加载队列中接下来1-2个待播放音频,进一步降低内存占用
内容的提问来源于stack exchange,提问作者Stanley Yeung
相关产品推荐
相关产品推荐

