Skip to content
expo-audio logo

Expo Audio (expo-audio)

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.

SKILL.md

Full skill instructions

Expo Audio (expo-audio)

Guide for using expo-audio to implement audio playback and recording in React Native apps.

Overview

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 to false to 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 volume
  • isPlaying: Boolean - Whether audio is currently playing
  • isLoaded: Boolean - Whether audio source is loaded
  • duration: Number - Total duration in seconds (null if not loaded)
  • currentTime: Number - Current playback position in seconds

Methods

  • play(): Start or resume playback
  • pause(): Pause playback
  • seekTo(seconds: number): Seek to specific position
  • release(): Release player resources (required for 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

  • prepareToRecordAsync(options?): Prepare recorder with options
  • record(): Start recording
  • stop(): Stop recording (returns URI)
  • pause(): Pause recording
  • release(): 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

  1. No auto-reset: expo-audio doesn't reset position when playback finishes. Call seekTo(0) to replay.
  2. Hook-based API: Prefer useAudioPlayer() over Audio.Sound.createAsync()
  3. Direct property access: Use player.volume = 0.5 instead of player.setVolumeAsync(0.5)
  4. 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-recorder for 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

  1. Use hooks in components: Prefer useAudioPlayer() in React components for automatic lifecycle management
  2. Release resources: Always call release() when using createAudioPlayer() manually
  3. Reset position for replay: Call seekTo(0) before replaying sounds
  4. Configure audio mode: Set playsInSilentMode: true for meditation/​notification sounds
  5. Handle errors: Wrap audio operations in try-catch blocks
  6. Preload sounds: Load sounds on app start for better UX

References

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View