Best practices
When working with audio in a web or mobile application, following best practices ensures optimal performance, user experience, and maintainability. Here are some key best practices to consider when using the React Native Audio API:
AudioContext Management
-
Single Audio Context: Create one instance of
AudioContextin order to easily and efficiently manage the audio layer's state in your application. Creating many instances could lead to undefined behavior. Some of them could still be inrunningstate while others could besuspendedorclosed, if you do not manage them by yourself. -
Resume before play: On mobile,
AudioContextmay start in thesuspendedstate. Callresume()before starting playback or recording that depends on the context. -
Clean up: Always close the
AudioContextusing theclose()method when it is no longer needed. This releases system audio resources and prevents memory leaks. For long-lived apps that keep audio available in the background, prefersuspend()/resume()over repeatedly creating and closing contexts. -
Suspend when not in use: Suspend the
AudioContextwhen audio is not needed to save system resources and battery life, especially on mobile devices. RunningAudioContextis still playing silence even if there is no playing source node connected to thedestination. Additionally, on iOS devices, the state of theAudioContextis directly related with state of the lock screen. If a runningAudioContextexists, it is impossible to set lock screen state tostate_paused. -
Configure the audio session early: Set
AudioManager.setAudioSessionOptions()once at startup — for exampleiosCategory: 'playback'for media apps oriosCategory: 'playAndRecord'when recording and playback coexist.
AudioRecorder Management
-
Single AudioRecorder: It is preferred to create only a single instance of the
AudioRecorderclass for the best performance, memory, and battery consumption. While an idle recorder has minimal impact, switching between separate recorder instances might have a noticeable impact on the device. -
Activate the session before recording: Call
AudioManager.setAudioSessionActivity(true)and enableobserveAudioInterruptions()beforestart()orresume(). -
Rotate files for crash resilience: When recording to disk, set
rotateIntervalBytes(for example every 1 MB) so a crash only loses the current segment. Merge segments afterward withconcatAudioFiles(). -
Subscribe and unsubscribe to callbacks: If you use
onAudioReady()for metering or visualization, always clear the callback when recording stops or the component unmounts. Use a lower sample rate for analysis callbacks than for playback to reduce CPU usage.
Architecture
-
Create a singleton class to manage the audio layer: Instead of storing
AudioContextor nodes directly in your React components usinguseStateoruseRef, consider creating a singleton class that encapsulates the audio layer logic using React Native Audio API. This class can manage the lifecycle of theAudioContext, handle audio nodes, and provide methods for playing, pausing, and stopping audio. This approach promotes separation of concerns and makes it easier to manage audio state across your application. -
Separate native logic from React state: Keep
AudioContext,AudioRecorder, and node graph management in a service singleton. Use a React context or custom event emitter for UI state and to bridge playback/recording events into components. -
Serialize async audio operations: Route mutations such as play, pause, seek, and cleanup through a single promise queue. Concurrent calls from UI gestures, lock-screen controls, and interruption handlers can otherwise race and leave the graph in an inconsistent state.
-
Guard concurrent sessions: Use a state machine (for example
idle→recording→paused) and reject overlappingstart()/stop()calls. This prevents duplicate recorder sessions and simplifies error recovery. -
Clean up at the app root: On provider unmount, remove
AudioManagerevent listeners, hidePlaybackNotificationManager/RecordingNotificationManagernotifications, and reset recorder state.
Source nodes and audio graph
-
Scheduled source nodes are single-use:
AudioBufferSourceNode,OscillatorNode, and otherAudioScheduledSourceNodesubclasses can bestart()ed only once. Create a new node to replay a sound, but reuse the sameAudioBuffer— nodes are inexpensive to create. -
Use
AudioBufferQueueSourceNodefor chunked playback: When audio arrives in segments (streaming TTS, progressive download), enqueue buffers into a queue source node rather than recreating the entire graph per chunk.
AudioParam changes
- Schedule smooth transitions: Direct, immediate changes to
gain,frequency, or other parameters can cause audible clicks. UselinearRampToValueAtTime(),exponentialRampToValueAtTime(), orsetTargetAtTime()for fades, sweeps, and envelope shapes.
System integration
-
Handle audio interruptions: Listen for
interruptionevents viaAudioManager.addSystemEventListener(). Pause playback or recording whenevent.type === 'began'. Onended, resume only ifevent.shouldResumeistrue— a short delay (around 1 second) before resuming gives the OS time to settle. -
Configure background audio in Expo: For background playback or recording, enable the required options in the
react-native-audio-apiExpo plugin — iOS background mode, Android foreground service, and the appropriate permissions. -
Wire lock-screen and notification controls once per session: Register
PlaybackNotificationManagerorRecordingNotificationManagerlisteners when a session starts and remove them on cleanup. On iOS, lock-screen play/pause state follows theAudioContext— you mustsuspend()the context to show paused. For recording indicators on iOS, use a Live Activity withexpo-widgetsinstead ofRecordingNotificationManager.
Performance
-
Throttle position callbacks: Set
onPositionChangedIntervalon queue source nodes to limit how often progress events fire. Avoid driving UI updates on every audio buffer. -
Avoid per-frame allocations in visualizations: When passing analyser or recorder data into Reanimated shared values, mutate the existing
Float32Arrayin place rather than allocating a new one each frame. See the audio visualization guide for patterns. -
Decouple analysis rate from playback rate: Metering and waveform previews do not need the same sample rate as playback. Use a lower rate for
onAudioReady()orAnalyserNodewhen full fidelity is not required.
Debugging
Do not pass AudioNode instances or raw JSI objects (e.g. the value returned by context.context.createGain()) directly to console.log.
Logging a node enumerates its properties and may warm internal caches. In development, repeatedly logging nodes — especially in a tight loop — can cause the console to retain references and make objects appear to leak.
Prefer logging plain values instead:
console.log({ channelCount: node.channelCount, numberOfInputs: node.numberOfInputs });