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

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闭包特性导致回调捕获了旧的状态值:

  1. stopRecording中调用的setRecordedDuration和setCurrentSeconds是异步更新状态的,不会立即修改组件的状态值。
  2. loadAudio在这两个状态更新完成前就被执行,此时设置的onPlaybackStatusUpdate回调会捕获当前渲染周期的状态值——也就是录制结束时的currentSeconds(115.5)和未更新的recordedDuration(0)。
  3. 后续调用playAudio时,组件已经完成状态更新,所以playAudio能拿到最新的currentSeconds(0)和recordedDuration(4.5)。
  4. onPlaybackStatusUpdate作为Expo AV的回调,一旦设置就会一直持有创建它时的闭包上下文,后续触发时仍使用旧状态值,导致和playAudio的输出不一致。

解决方法

使用useRef保存最新状态值,因为ref的.current属性是可变的,不会被闭包捕获旧值:

  1. 定义两个ref变量:
const currentSecondsRef = useRef<number>(timerMaxDuration);
const recordedDurationRef = useRef<number>(0);
  1. 更新状态的同时同步更新ref:
// 在stopRecording中
const newRecordedDuration = +(timerMaxDuration - currentSeconds).toFixed(1);
setRecordedDuration(newRecordedDuration);
recordedDurationRef.current = newRecordedDuration;
setCurrentSeconds(0);
currentSecondsRef.current = 0;
  1. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 04:50:13