Curently using expo + react native and encountering challenges handling media playback state efficiently. With expo's AV libraries we're given an imperative interface around a playback resource, with a parameter to hook into playback state updates:
// this runs every 50ms (or faster) during active playback
const handlePlaybackStatusUpdate = (nextStatus: AVPlaybackStatus) => {
// do something in here
}
const loadedSound = (await Audio.Sound.createAsync({ uri }, playbackConfig, handlePlaybackStatusUpdate)).sound
// ...
await loadedSound.playAsync()
Understanding that 'high performance' is caveated by limitations around thread communication in RN apps ,
My implementation goals are:
I've been struggling to find a way to efficiently process these playback status updates. For further context. Playback status exposes many dimensions of playback state - isPlaying, isBuffering, didJustFinish, durationMillis (length of playback in ms), positionMillis (current position within playback) etc. Based on experimentation and observation, positionMillis is the only value that changes in the vast majority of updates (barring those related to commands like playAsync, pauseAsync etc.) Hence the below attempts to expose a stream of updates based on measuring position, independent from the rest of the context's state.
A few things I've tried:
const PlaybackProvider = ({ children }: { children: ReactNode }) => {
const [playbackStatus, setPlaybackStatus] = useState<PlaybackStatus>()
// ...
await Audio.Sound.createAsync({ uri }, playbackConfig, setPlaybackStatus)
const usePlaybackPosition = (
onUpdate: (position: number, duration: number) => Promise<void> | void,
interval = 200
) => {
const { playbackStatusRef } = usePlayback()
const [timer, setTimer] = useState(0)
useInterval(() => {
const { positionMillis, durationMillis } = playbackStatusRef?.current ?? {}
onUpdate(positionMillis, durationMillis)
setTimer(timer + 1)
}, interval)
}
const handlePlaybackStatusUpdate = (newStatus?: AVPlaybackStatus): void => {
// save a ref for components that want to listen to position
playbackStatusRef.current = newStatus
if (newStatus === undefined) {
setPlaybackStatus(PlaybackStatus.None)
return
}
if (!newStatus.isLoaded) {
if (newStatus.error) {
console.log(`Encountered a fatal error during playback: ${newStatus.error}`)
setPlaybackStatus(PlaybackStatus.Error)
return
}
} else {
const { isPlaying, isBuffering, positionMillis, didJustFinish, isLooping } = newStatus
let nextStatus: PlaybackStatus = PlaybackStatus.Idle
if (isPlaying) {
nextStatus = PlaybackStatus.Playing
} else if (positionMillis > 0) {
nextStatus = PlaybackStatus.Paused
} else {
// not playing and position is 0
nextStatus = PlaybackStatus.Idle
}
if (didJustFinish && !isLooping) {
nextStatus = PlaybackStatus.Finished
}
if (nextStatus !== playbackStatus) {
// this conditional ends up running TONS of times before playbackStatus actually changes
setPlaybackStatus(nextStatus)
}
}
}
I am hoping to keep a solution simple and orthodox re: react best practices. Although the React docssuggest that media playback is a valid use of refs, so maybe just need to commit to and harden a ref-based interface. My hope is that smarter composition/refactoring of the component hierarchy + hooks/context to replicate/share behavior will be sufficient.
There are likely some incorrect assumptions above. I would be grateful to have these pointed out. Likewise, any and all insights regarding known limitations/issues with media playback in expo/react-native are greatly appreciated. Thank you for the help!
One optimization we did was to debounce the onPlaybackStatusUpdate
hook. Only process updates every n
milliseconds. Sharing piece of the code for reference (not the full implementation):
const debouncedPlaybackStatusUpdate = useMemo(
() => debounce(onPlaybackStatusUpdate, 100),
[],
);
async function playSound(status: boolean) {
if (!status) {
await soundObj.playAsync();
soundObj.setOnPlaybackStatusUpdate(debouncedPlaybackStatusUpdate);
} else {
await soundObj.pauseAsync();
}
}
This should reduce majority of re-renders.
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.