简体   繁体   中英

Expo AV playback callback strange behaviour with state properties

I am starting to go nuts with this one. I am recording an audio, once I stop the recording as part of the this process I also load the audio so that it is ready to be played back when necessary and here I do setOnPlaybackStatusUpdate . I use the playback callback so that I can update a my currentSeconds state based on positionMillis .

The problem is the currentSeconds and recordedDuration state values that I am getting. How come their values change between playAudio method which triggers the audio to be played and onPlaybackStatusUpdate which is the callback method?

When I output in the console their values in both methods this is what I obtain when I expect them to be the same:

In playAudio - currentSeconds: 0
In playAudio - recordedDuration: 4.5
In onPlaybackStatusUpdate - currentSeconds: 115.5
In onPlaybackStatusUpdate - recordedDuration: 0

And here is the code:

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();

            // To hear sound through speaker and not earpiece on iOS
            await Audio.setAudioModeAsync({ allowsRecordingIOS: false });

            const recordedURI = AudioRecorder.current.getURI();
            SetRecordingURI(recordedURI)

            AudioRecorder.current = new Audio.Recording();
            send('STOP')

            setRecordedDuration(+(timerMaxDuration - currentSeconds).toFixed(1)) // there is subtraction because during the recording there is a countdown from timerMaxDuration
            setCurrentSeconds(0)
            // Load audio after recording so that it is ready to be played
            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) {
                if (playerStatus.isPlaying === false) {
                    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))
            }
        }
    }

Ok so there was nothing wrong with the playback callback. The thing is that both the playback callback is an arrow functions which means that the only property value that will change inside the callback is the one of the argument playbackStatus , the other properties' values will remain the same as the time the function was created.

A walkaround in React is to use useEffect in the following way, which allows to access the state values currentSeconds and recordedDuration :

useEffect(() => {
        if(currentSeconds >= recordedDuration)
            stopAudio()
    }, [currentSeconds]);

    const onPlaybackStatusUpdate = playbackStatus => {
        if (playbackStatus.isPlaying)
            setCurrentSeconds(+(playbackStatus.positionMillis / 1000).toFixed(1))
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM