Claude-skill-registry expo-audio
Guide for using expo-audio to implement audio playback and recording in React Native apps. Apply when working with audio features, sound playback, recording, or text-to-speech functionality.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/expo-audio" ~/.claude/skills/majiayu000-claude-skill-registry-expo-audio && rm -rf "$T"
manifest:
skills/data/expo-audio/SKILL.mdsource content
Expo Audio (expo-audio)
Guide for using
expo-audio to implement audio playback and recording in React
Native apps.
Overview
- Package:
(replaces deprecatedexpo-audio
)expo-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:
(iOS only): String for NSMicrophoneUsageDescription. Set tomicrophonePermission
to disable.false
(Android only): Boolean to enable RECORD_AUDIO permission (default:recordAudioAndroid
)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:
hook (recommended for React components)useAudioPlayer()
function (for imperative usage outside components)createAudioPlayer()
AudioRecorder
The
AudioRecorder class handles audio recording. Use:
hook (recommended for React components)useAudioRecorder()
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
: Number (0.0 to 1.0) - Current playback volumevolume
: Boolean - Whether audio is currently playingisPlaying
: Boolean - Whether audio source is loadedisLoaded
: Number - Total duration in seconds (null if not loaded)duration
: Number - Current playback position in secondscurrentTime
Methods
: Start or resume playbackplay()
: Pause playbackpause()
: Seek to specific positionseekTo(seconds: number)
: Release player resources (required forrelease()
)createAudioPlayer
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
: Prepare recorder with optionsprepareToRecordAsync(options?)
: Start recordingrecord()
: Stop recording (returns URI)stop()
: Pause recordingpause()
: Release recorder resourcesrelease()
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:
doesn't reset position when playback finishes. Callexpo-audio
to replay.seekTo(0) - Hook-based API: Prefer
overuseAudioPlayer()Audio.Sound.createAsync() - Direct property access: Use
instead ofplayer.volume = 0.5player.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
for better browser compatibilitykbumsik/opus-media-recorder - 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
in React components for automatic lifecycle managementuseAudioPlayer() - Release resources: Always call
when usingrelease()
manuallycreateAudioPlayer() - Reset position for replay: Call
before replaying soundsseekTo(0) - Configure audio mode: Set
for meditation/notification soundsplaysInSilentMode: true - Handle errors: Wrap audio operations in try-catch blocks
- Preload sounds: Load sounds on app start for better UX