expo-audio
npx skills add https://github.com/jchaselubitz/drill-app --skill expo-audio
Agent 安装分布
Skill 文档
Expo Audio (expo-audio)
Guide for using expo-audio to implement audio playback and recording in React
Native apps.
Overview
- Package:
expo-audio(replaces deprecatedexpo-av) - Platform: Android, iOS, tvOS, Web
- Bundled version: ~1.1.1
- Documentation: https://docs.expo.dev/versions/latest/sdk/audio/
Installation
npx expo install expo-audio
Configuration
app.json Plugin Configuration
Add the expo-audio plugin to your app.json:
{
"expo": {
"plugins": [
[
"expo-audio",
{
"microphonePermission": "Allow $(PRODUCT_NAME) to access your microphone.",
"recordAudioAndroid": true
}
]
]
}
}
Configurable properties:
microphonePermission(iOS only): String for NSMicrophoneUsageDescription. Set tofalseto disable.recordAudioAndroid(Android only): Boolean to enable RECORD_AUDIO permission (default:true)
Background Audio (iOS)
For background audio playback on iOS, add UIBackgroundModes to app.json:
{
"expo": {
"ios": {
"infoPlist": {
"UIBackgroundModes": ["audio"]
}
}
}
}
Core Concepts
AudioPlayer
The AudioPlayer class handles audio playback. You can create players using:
useAudioPlayer()hook (recommended for React components)createAudioPlayer()function (for imperative usage outside components)
AudioRecorder
The AudioRecorder class handles audio recording. Use:
useAudioRecorder()hook (recommended for React components)
Usage Patterns
Playing Sounds (React Hook – Recommended)
Use useAudioPlayer hook in React components:
import { Button, View } from "react-native";
import { useAudioPlayer } from "expo-audio";
const audioSource = require("./assets/sound.mp3");
export default function App() {
const player = useAudioPlayer(audioSource);
return (
<View>
<Button title="Play" onPress={() => player.play()} />
<Button title="Pause" onPress={() => player.pause()} />
<Button
title="Replay"
onPress={() => {
player.seekTo(0);
player.play();
}}
/>
</View>
);
}
Important: Unlike expo-av, expo-audio doesn’t automatically reset
playback position when audio finishes. After play(), the player stays paused
at the end. To replay, call seekTo(seconds) to reset position.
Playing Sounds (Imperative – Outside Components)
For imperative usage (e.g., utility functions), use createAudioPlayer:
import { AudioPlayer, createAudioPlayer, setAudioModeAsync } from "expo-audio";
let player: AudioPlayer | null = null;
export async function loadSound(uri: string): Promise<void> {
// Configure audio mode
await setAudioModeAsync({
playsInSilentMode: true,
allowsRecording: false,
});
// Create player
player = createAudioPlayer({ uri });
}
export async function playSound(volume: number = 1.0): Promise<void> {
if (!player) {
await loadSound("https://example.com/sound.mp3");
}
if (player) {
player.volume = volume;
player.seekTo(0);
player.play();
}
}
export async function releaseSound(): Promise<void> {
if (player) {
player.release();
player = null;
}
}
â ï¸ Memory Management: When using createAudioPlayer, you must manually call
release() when done to prevent memory leaks.
Recording Sounds
import { useEffect, useState } from "react";
import { Button, View } from "react-native";
import {
AudioModule,
RecordingPresets,
setAudioModeAsync,
useAudioRecorder,
useAudioRecorderState,
} from "expo-audio";
export default function App() {
const audioRecorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);
const recorderState = useAudioRecorderState(audioRecorder);
const record = async () => {
await audioRecorder.prepareToRecordAsync();
audioRecorder.record();
};
const stopRecording = async () => {
// Recording available on `audioRecorder.uri`
await audioRecorder.stop();
};
useEffect(() => {
(async () => {
const status = await AudioModule.requestRecordingPermissionsAsync();
if (!status.granted) {
Alert.alert("Permission to access microphone was denied");
}
await setAudioModeAsync({
playsInSilentMode: true,
allowsRecording: true,
});
})();
}, []);
return (
<View>
<Button
title={recorderState.isRecording ? "Stop Recording" : "Start Recording"}
onPress={recorderState.isRecording ? stopRecording : record}
/>
</View>
);
}
AudioPlayer API
Properties
volume: Number (0.0 to 1.0) – Current playback volumeisPlaying: Boolean – Whether audio is currently playingisLoaded: Boolean – Whether audio source is loadedduration: Number – Total duration in seconds (null if not loaded)currentTime: Number – Current playback position in seconds
Methods
play(): Start or resume playbackpause(): Pause playbackseekTo(seconds: number): Seek to specific positionrelease(): Release player resources (required forcreateAudioPlayer)
Event Listeners
Use useAudioPlayerStatus() hook to react to player state changes:
import { useAudioPlayer, useAudioPlayerStatus } from "expo-audio";
const player = useAudioPlayer(source);
const status = useAudioPlayerStatus(player);
// status.isPlaying, status.currentTime, status.duration, etc.
AudioRecorder API
Methods
prepareToRecordAsync(options?): Prepare recorder with optionsrecord(): Start recordingstop(): Stop recording (returns URI)pause(): Pause recordingrelease(): Release recorder resources
Recording Presets
import { RecordingPresets } from "expo-audio";
// Available presets:
RecordingPresets.HIGH_QUALITY;
RecordingPresets.LOW_QUALITY;
Audio Mode Configuration
Use setAudioModeAsync() to configure audio behavior:
import { setAudioModeAsync } from "expo-audio";
await setAudioModeAsync({
playsInSilentMode: true, // Play even when device is in silent mode
allowsRecording: false, // Allow recording (required for recording)
interruptionMode: "duck", // 'duck' | 'mix' | 'doNotMix'
});
Common Patterns
Preload Audio on App Start
// app/_layout.tsx
import { useEffect } from "react";
import { loadGongSound, unloadGongSound } from "../lib/audio";
export default function RootLayout() {
useEffect(() => {
loadGongSound();
return () => {
unloadGongSound();
};
}, []);
return <Stack />;
}
Play Sound with Volume Control
import { createAudioPlayer, setAudioModeAsync } from "expo-audio";
let player: AudioPlayer | null = null;
export async function playSound(volume: number = 1.0): Promise<void> {
if (!player) {
await setAudioModeAsync({ playsInSilentMode: true });
player = createAudioPlayer({ uri: "https://example.com/sound.mp3" });
}
if (player) {
player.volume = volume;
player.seekTo(0);
player.play();
}
}
Replay Sound (Reset Position)
// Important: expo-audio doesn't auto-reset position
player.seekTo(0); // Reset to beginning
player.play(); // Play from start
Migration from expo-av
Key Differences
- No auto-reset:
expo-audiodoesn’t reset position when playback finishes. CallseekTo(0)to replay. - Hook-based API: Prefer
useAudioPlayer()overAudio.Sound.createAsync() - Direct property access: Use
player.volume = 0.5instead ofplayer.setVolumeAsync(0.5) - Simplified API: Fewer methods, more direct property access
Migration Example
Before (expo-av):
const { sound } = await Audio.Sound.createAsync({ uri });
await sound.setVolumeAsync(0.5);
await sound.setPositionAsync(0);
await sound.playAsync();
After (expo-audio):
const player = createAudioPlayer({ uri });
player.volume = 0.5;
player.seekTo(0);
player.play();
Web Considerations
- MediaRecorder on Chrome may produce WebM files missing duration metadata (known Chromium issue)
- Consider using polyfills like
kbumsik/opus-media-recorderfor better browser compatibility - Web browsers require HTTPS for microphone access (MediaDevices getUserMedia security)
Permissions
Request Recording Permissions
import { AudioModule } from "expo-audio";
const status = await AudioModule.requestRecordingPermissionsAsync();
if (!status.granted) {
// Handle permission denied
}
Check Permission Status
const status = await AudioModule.getRecordingPermissionsAsync();
// status.granted, status.canAskAgain, etc.
Best Practices
- Use hooks in components: Prefer
useAudioPlayer()in React components for automatic lifecycle management - Release resources: Always call
release()when usingcreateAudioPlayer()manually - Reset position for replay: Call
seekTo(0)before replaying sounds - Configure audio mode: Set
playsInSilentMode: truefor meditation/notification sounds - Handle errors: Wrap audio operations in try-catch blocks
- Preload sounds: Load sounds on app start for better UX