Mobile audio recording is a core feature in many modern apps, powering everything from voice memos to podcast tools and user-generated content platforms. If you’re developing a mobile app with React Native, the Expo framework provides a robust set of APIs—especially the expo-av module—that simplifies cross-platform audio capture. But moving from a basic demo to a reliable, production-ready audio recorder means addressing a host of real-world challenges: managing permissions, supporting background recording, handling file storage, and reliably uploading recordings.
Let’s explore how to build a production-grade audio recorder using Expo and React Native, focusing on background recording, file management, and upload strategies. Along the way, you’ll find practical code examples and architectural tips for scalable, maintainable implementations.
Setting Up Expo AV for Audio Recording
The foundation for any React Native audio app is the expo-av package. This library abstracts away platform-specific details and offers a unified API for audio playback and recording.
First, ensure you have the required dependencies:
expo install expo-av
You’ll also need to handle permissions for microphone access. For iOS, update your app.json with the appropriate usage description:
{
"expo": {
"ios": {
"infoPlist": {
"NSMicrophoneUsageDescription": "We need access to your microphone to record audio."
}
}
}
}
For Android, Expo manages permissions automatically, but you can customize messages in your AndroidManifest.xml if you eject.
Requesting Permissions
Before starting recording, always request user permission:
import { Audio } from 'expo-av';
async function requestAudioPermission() {
const { status } = await Audio.requestPermissionsAsync();
if (status !== 'granted') {
throw new Error('Microphone permission not granted');
}
}
Implementing the Recorder
The Audio.Recording API is your entry point for capturing audio. Here’s a basic hook to encapsulate recording logic:
import { useRef, useState } from 'react';
import { Audio, AVPlaybackStatus } from 'expo-av';
export function useAudioRecorder() {
const [isRecording, setIsRecording] = useState(false);
const [recordingUri, setRecordingUri] = useState<string | null>(null);
const recordingRef = useRef<Audio.Recording | null>(null);
async function startRecording() {
await requestAudioPermission();
await Audio.setAudioModeAsync({
allowsRecordingIOS: true,
playsInSilentModeIOS: true,
staysActiveInBackground: true, // critical for background recording
});
const recording = new Audio.Recording();
await recording.prepareToRecordAsync(
Audio.RECORDING_OPTIONS_PRESET_HIGH_QUALITY
);
await recording.startAsync();
recordingRef.current = recording;
setIsRecording(true);
}
async function stopRecording() {
const recording = recordingRef.current;
if (!recording) return;
await recording.stopAndUnloadAsync();
const uri = recording.getURI();
setRecordingUri(uri);
setIsRecording(false);
recordingRef.current = null;
return uri;
}
return {
isRecording,
recordingUri,
startRecording,
stopRecording,
};
}
UI Integration
A simple recording UI could look like this:
import React from 'react';
import { Button, View, Text } from 'react-native';
import { useAudioRecorder } from './useAudioRecorder';
export default function AudioRecorderScreen() {
const { isRecording, recordingUri, startRecording, stopRecording } = useAudioRecorder();
return (
<View>
<Button
title={isRecording ? 'Stop Recording' : 'Start Recording'}
onPress={isRecording ? stopRecording : startRecording}
/>
{recordingUri && (
<Text>Recorded file: {recordingUri}</Text>
)}
</View>
);
}
Supporting Background Recording
One limitation of Expo-managed workflows is that true background recording (while the app is fully backgrounded) may be limited, particularly on iOS. However, by setting staysActiveInBackground: true in Audio.setAudioModeAsync, you maximize the chances that the recording continues if the user briefly switches apps.
For apps requiring robust background recording (e.g., call recorders, long-form interviews), consider:
-
Ejecting to Expo Bare workflow: This lets you use native modules or third-party libraries with deeper background support, like
react-native-audio-recorder-player. - Minimize interruptions: Educate users about keeping your app in the foreground for uninterrupted recording.
- Handle interruptions: Listen for app state changes and gracefully stop or pause recording if the app is backgrounded beyond what the OS allows.
import { AppState } from 'react-native';
useEffect(() => {
const subscription = AppState.addEventListener('change', (nextAppState) => {
if (nextAppState.match(/inactive|background/)) {
// Optionally stop or pause recording here
}
});
return () => subscription.remove();
}, []);
Managing Files and Storage
After recording, the audio file lives on the device, typically in a temporary location. For production apps, consider:
-
Moving files to persistent storage: Use
FileSystem.moveAsyncfromexpo-file-system. - Naming conventions: Generate unique, timestamped filenames to avoid collisions.
- Cleanup: Provide mechanisms to delete or archive old files to save device space.
Example: Moving a file to the app’s document directory with a unique name.
import * as FileSystem from 'expo-file-system';
async function moveRecordingToDocuments(uri: string): Promise<string> {
const fileName = `recording_${Date.now()}.m4a`;
const newPath = FileSystem.documentDirectory + fileName;
await FileSystem.moveAsync({ from: uri, to: newPath });
return newPath;
}
Uploading Audio Files
Uploading audio to a server is a common next step. There are a few patterns to ensure reliability:
Simple Direct Upload
For short recordings and reliable networks, upload as soon as the recording is finished:
async function uploadAudioFile(uri: string) {
const fileInfo = await FileSystem.getInfoAsync(uri);
const formData = new FormData();
formData.append('file', {
uri,
name: 'audio.m4a',
type: 'audio/m4a',
} as any);
await fetch('https://your-api/upload', {
method: 'POST',
body: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
});
}
Handling Offline and Background Uploads
For robust apps, implement upload queues and retry logic:
- Queue uploads: Store a list of pending uploads in local storage.
- Retry failed uploads: Use libraries like react-native-background-upload in the bare workflow, or roll your own with background tasks.
- User feedback: Show upload progress and error messages.
Security and Privacy
- Encrypt files before upload if sensitive.
- Request consent before recording or uploading audio.
Audio Playback for Review
Letting users replay their recordings before uploading is a valuable UX feature. expo-av makes this straightforward:
async function playRecording(uri: string) {
const { sound } = await Audio.Sound.createAsync({ uri });
await sound.playAsync();
}
Integrate playback controls in your UI for a polished experience.
Testing and Edge Cases
A production-grade audio recorder must handle:
- Interrupted recordings (incoming calls, backgrounding)
- Short, empty, or silent recordings
- Storage limitations
- Network failures during upload
- Permission revocations
Automate testing with tools like Jest and Detox, and log errors for diagnostics.
Alternative Libraries and Workflows
While Expo’s managed workflow and expo-av suffice for many use cases, consider alternatives if you need:
- Advanced background recording
- Non-standard audio formats
- Low-level audio processing
Alternatives include:
- react-native-audio-recorder-player (bare workflow, more control)
- react-native-sound
- Custom native modules
Choose based on your app’s requirements and your willingness to handle native configuration.
Key Takeaways
Building a reliable, user-friendly audio recorder with Expo and React Native is achievable with careful attention to details beyond basic recording. Leverage expo-av for rapid development, but plan for production needs:
- Always manage permissions and inform users.
- Use
staysActiveInBackgroundfor best-effort background support. - Organize files for persistence and easy management.
- Implement robust upload patterns, especially for unreliable networks.
- Handle edge cases with grace.
- Evaluate alternative libraries if you need deeper background or format support.
With these patterns, you’ll be able to deliver a polished, production-ready mobile audio app—whether you’re building voice notes, interviews, or creative audio tools.
Top comments (0)