Expo AV播放回调与State属性值异常问题
Expo AV播放回调与方法中状态值不一致的原因及解决方法
问题重现
控制台输出如下:
In playAudio - currentSeconds: 0 In playAudio - recordedDuration: 4.5 In onPlaybackStatusUpdate - currentSeconds: 115.5 In onPlaybackStatusUpdate - recordedDuration: 0
相关核心代码:
const AudioRecorder = useRef(new Audio.Recording()); const AudioPlayer = useRef(new Audio.Sound()); const timerMaxDuration = 120 const [currentSeconds, setCurrentSeconds] = useState<number>(timerMaxDuration); const [recordedDuration, setRecordedDuration] = useState<number>(0); const stopRecording = async () => { try { await AudioRecorder.current.stopAndUnloadAsync(); await Audio.setAudioModeAsync({ allowsRecordingIOS: false }); const recordedURI = AudioRecorder.current.getURI(); SetRecordingURI(recordedURI) AudioRecorder.current = new Audio.Recording(); send('STOP') setRecordedDuration(+(timerMaxDuration - currentSeconds).toFixed(1)) setCurrentSeconds(0) loadAudio(recordedURI) } catch (error) { console.log(error); } }; const loadAudio = async (recordedUri) => { try { const playerStatus = await AudioPlayer.current.getStatusAsync(); if (playerStatus.isLoaded === false) { AudioPlayer.current.setOnPlaybackStatusUpdate(onPlaybackStatusUpdate) await AudioPlayer.current.loadAsync({ uri: recordedUri }, { progressUpdateIntervalMillis: 20 }, true) } } catch (error) { console.log(error) } } const playAudio = async () => { console.log(`In playAudio - currentSeconds: ${currentSeconds}`) console.log(`In playAudio - recordedDuration: ${recordedDuration}`) try { const playerStatus = await AudioPlayer.current.getStatusAsync(); if (playerStatus.isLoaded && !playerStatus.isPlaying) { AudioPlayer.current.playAsync(); send('PLAY') } } catch (error) { console.log(error); } }; const onPlaybackStatusUpdate = playbackStatus => { if (playbackStatus.isPlaying) { console.log(`In onPlaybackStatusUpdate - currentSeconds: ${currentSeconds}`) console.log(`In onPlaybackStatusUpdate - recordedDuration: ${recordedDuration}`) if(currentSeconds >= recordedDuration){ stopAudio() } else{ setCurrentSeconds(+(playbackStatus.positionMillis / 1000).toFixed(1)) } } }
原因分析
核心问题是React闭包特性导致回调捕获了旧的状态值:
stopRecording中调用的setRecordedDuration和setCurrentSeconds是异步更新状态的,不会立即修改组件的状态值。loadAudio在这两个状态更新完成前就被执行,此时设置的onPlaybackStatusUpdate回调会捕获当前渲染周期的状态值——也就是录制结束时的currentSeconds(115.5)和未更新的recordedDuration(0)。- 后续调用
playAudio时,组件已经完成状态更新,所以playAudio能拿到最新的currentSeconds(0)和recordedDuration(4.5)。 onPlaybackStatusUpdate作为Expo AV的回调,一旦设置就会一直持有创建它时的闭包上下文,后续触发时仍使用旧状态值,导致和playAudio的输出不一致。
解决方法
使用useRef保存最新状态值,因为ref的.current属性是可变的,不会被闭包捕获旧值:
- 定义两个ref变量:
const currentSecondsRef = useRef<number>(timerMaxDuration); const recordedDurationRef = useRef<number>(0);
- 更新状态的同时同步更新ref:
// 在stopRecording中 const newRecordedDuration = +(timerMaxDuration - currentSeconds).toFixed(1); setRecordedDuration(newRecordedDuration); recordedDurationRef.current = newRecordedDuration; setCurrentSeconds(0); currentSecondsRef.current = 0;
- 在
onPlaybackStatusUpdate中使用ref的current值:
const onPlaybackStatusUpdate = playbackStatus => { if (playbackStatus.isPlaying) { console.log(`In onPlaybackStatusUpdate - currentSeconds: ${currentSecondsRef.current}`) console.log(`In onPlaybackStatusUpdate - recordedDuration: ${recordedDurationRef.current}`) if(currentSecondsRef.current >= recordedDurationRef.current){ stopAudio() } else{ const newCurrent = +(playbackStatus.positionMillis / 1000).toFixed(1); setCurrentSeconds(newCurrent); currentSecondsRef.current = newCurrent; } } }
这样就能保证onPlaybackStatusUpdate始终拿到最新状态值,与playAudio的输出保持一致。
内容的提问来源于stack exchange,提问作者Kronax
相关产品推荐
相关产品推荐

