Migrating from expo-av to expo-audio
Imports
// Before
import { Audio } from 'expo-av';
// After
import { useAudioPlayer, useAudioRecorder, RecordingPresets, AudioModule, setAudioModeAsync } from 'expo-audio';Audio Playback
Before (expo-av)
const [sound, setSound] = useState<Audio.Sound>();
async function playSound() {
const { sound } = await Audio.Sound.createAsync(require('./audio.mp3'));
setSound(sound);
await sound.playAsync();
}
useEffect(() => {
return sound ? () => { sound.unloadAsync(); } : undefined;
}, [sound]);After (expo-audio)
const player = useAudioPlayer(require('./audio.mp3'));
// Play
player.play();Audio Recording
Before (expo-av)
const [recording, setRecording] = useState<Audio.Recording>();
async function startRecording() {
await Audio.requestPermissionsAsync();
await Audio.setAudioModeAsync({ allowsRecordingIOS: true, playsInSilentModeIOS: true });
const { recording } = await Audio.Recording.createAsync(Audio.RecordingOptionsPresets.HIGH_QUALITY);
setRecording(recording);
}
async function stopRecording() {
await recording?.stopAndUnloadAsync();
const uri = recording?.getURI();
}After (expo-audio)
const recorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);
async function startRecording() {
await AudioModule.requestRecordingPermissionsAsync();
await recorder.prepareToRecordAsync();
recorder.record();
}
async function stopRecording() {
await recorder.stop();
const uri = recorder.uri;
}Audio Mode
Before (expo-av)
await Audio.setAudioModeAsync({
allowsRecordingIOS: true,
playsInSilentModeIOS: true,
staysActiveInBackground: true,
interruptionModeIOS: InterruptionModeIOS.DoNotMix,
});After (expo-audio)
await setAudioModeAsync({
playsInSilentMode: true,
shouldPlayInBackground: true,
interruptionMode: 'doNotMix',
});API Mapping
| expo-av | expo-audio |
|---|---|
Audio.Sound.createAsync() | useAudioPlayer(source) |
sound.playAsync() | player.play() |
sound.pauseAsync() | player.pause() |
sound.setPositionAsync(ms) | player.seekTo(seconds) |
sound.setVolumeAsync(vol) | player.volume = vol |
sound.setRateAsync(rate) | player.playbackRate = rate |
sound.setIsLoopingAsync(loop) | player.loop = loop |
sound.unloadAsync() | Automatic via hook |
playbackStatus.positionMillis | player.currentTime (seconds) |
playbackStatus.durationMillis | player.duration (seconds) |
playbackStatus.isPlaying | player.playing |
Audio.Recording.createAsync() | useAudioRecorder(preset) |
Audio.RecordingOptionsPresets.* | RecordingPresets.* |
recording.stopAndUnloadAsync() | recorder.stop() |
recording.getURI() | recorder.uri |
Audio.requestPermissionsAsync() | AudioModule.requestRecordingPermissionsAsync() |
Key Differences
- No auto-reset on finish: After
play()completes, the player stays paused at the end. To replay, callplayer.seekTo(0)thenplay() - Time in seconds: expo-audio uses seconds, not milliseconds (matching web standards)
- Immediate loading: Audio loads immediately when the hook mounts—no explicit preloading needed
- Automatic cleanup: No need to call
unloadAsync(), hooks handle resource cleanup on unmount - Multiple players: Create multiple
useAudioPlayerinstances and store them—all load immediately - Direct property access: Set volume, rate, loop directly on the player object (
player.volume = 0.5)
API Reference
https://docs.expo.dev/versions/latest/sdk/audio/