@mindinventory/react-native-nitro-realtime-audio
v1.2.0
Published
A high-performance audio recording library for React Native powered by Nitro Modules.
Maintainers
Keywords
Readme
react-native-nitro-realtime-audio
A high-performance real-time audio streaming library for React Native, powered by Nitro Modules.
It provides native 16-bit Linear PCM (PCM16) recording and playback with audio transferred between JavaScript and the native layer using standard ArrayBuffer objects.
The library is designed for real-time audio pipelines where audio needs to be captured, processed, transmitted, received, and played without relying on temporary audio files.
Designed for modern real-time audio applications such as:
- 🤖 AI Voice Assistants (OpenAI Realtime, Gemini Live)
- 🗣️ Speech-to-Text
- 🔊 Text-to-Speech playback
- 🌐 WebSocket audio streaming
- 🌐 WebRTC & VoIP
- 📈 Audio Visualizers
- 🎛️ Digital Signal Processing (DSP)
- 🎤 Voice Activity Detection (VAD)
Unlike traditional recording libraries that primarily save audio files, this library is built for real-time PCM streaming.
Audio can be captured from the microphone and streamed directly to JavaScript, while PCM audio received or generated in JavaScript can be streamed back to the native audio player.
Works with Expo Development Builds, EAS Build, and React Native CLI projects.
Features
- ⚡ Powered by Nitro Modules for extremely low-overhead native ↔ JavaScript communication
- 🎙️ Real-time microphone audio streaming
- ▶️ Real-time PCM16 audio playback
- 🌊 Streaming playback for continuously arriving PCM chunks
- 📦 Streams raw 16-bit PCM audio using standard
ArrayBuffer - 🔄 Automatic native audio resampling for recording
- 🎚️ Configurable sample rate, channels, and recording chunk duration
- 📦 Native buffering for smooth PCM playback
- 🔄 Supports record → process/network → playback pipelines
- 📱 Native support for Android and iOS
- 🚀 Compatible with Expo Development Builds and React Native CLI
- 🧩 Ideal for AI, WebRTC, DSP, speech processing, TTS, and custom audio pipelines
Architecture
The library supports PCM audio in both directions.
Recording
Microphone
│
▼
Native Audio Recorder
│
▼
Chunk Accumulator
│
▼
PCM16 AudioChunk
│
▼
ArrayBuffer
│
▼
JavaScript CallbackPlayback
JavaScript / Network
│
▼
ArrayBuffer
│
▼
Native PCM Buffer
│
▼
Native Audio Player
│
▼
SpeakerThis makes it possible to build pipelines such as:
Microphone
│
▼
Native Recorder
│
▼
ArrayBuffer
│
▼
JavaScript
│
▼
WebSocket / AI / DSP
│
▼
ArrayBuffer
│
▼
Native Player
│
▼
SpeakerWhy ArrayBuffer?
The library transfers raw PCM audio as ArrayBuffer instead of Base64 strings or temporary audio files.
This provides:
- Lower memory overhead
- Lower latency
- No Base64 encoding/decoding overhead
- Standard JavaScript binary format
- Easy interoperability with AI SDKs
- Easy WebSocket transmission
- Easy DSP processing
- Efficient native ↔ JavaScript audio transfer
For example, recorded audio can be sent directly to a WebSocket:
onAudioChunk((buffer) => {
websocket.send(buffer);
});Likewise, PCM received from a service can be passed directly to the player:
websocket.onmessage = (event) => {
playChunk(event.data);
};Installation
Install the library together with its required peer dependency:
npm install @mindinventory/react-native-nitro-realtime-audio react-native-nitro-modulesor:
yarn add @mindinventory/react-native-nitro-realtime-audio react-native-nitro-modulesor for Expo:
npx expo install @mindinventory/react-native-nitro-realtime-audio react-native-nitro-modulesNote
react-native-nitro-modulesis a required peer dependency.
Install iOS dependencies:
cd ios
pod installExpo Support
This library is compatible with:
- ✅ Expo Development Build
- ✅ Expo Prebuild
- ✅ Expo Bare Workflow
- ✅ React Native CLI
- ✅ EAS Build
Important
Since this library contains native code, it cannot run inside Expo Go.
Use an Expo Development Build or EAS Build instead.
iOS Setup
Add the microphone usage description to your Info.plist:
<key>NSMicrophoneUsageDescription</key>
<string>This app requires access to the microphone to record audio.</string>If your application only uses PCM playback and does not use microphone recording, microphone permission is not required for playback itself.
Android Setup
Add microphone permission to:
android/app/src/main/AndroidManifest.xml<uses-permission android:name="android.permission.RECORD_AUDIO" />This permission is required for microphone recording.
PCM playback itself does not require microphone permission.
API Reference
Permissions & Device Information
getPlatformName(): string
Returns the current platform.
'iOS';
'Android';getNativeSampleRate(): number
Returns the device's native hardware sample rate.
Example:
48000
44100getMicrophonePermissionStatus(): MicrophonePermissionStatus
Returns one of:
'granted';
'denied';
'undetermined';requestMicrophonePermission(): Promise<MicrophonePermissionStatus>
Requests microphone permission from the user.
const permission = await requestMicrophonePermission();
if (permission === 'granted') {
// Microphone can be used
}Recording
startRecording(config: AudioRecordingConfig): void
Starts recording microphone audio.
startRecording({
sampleRate: 24000,
channels: 1,
chunkDurationMs: 100,
});| Property | Description |
| ----------------- | ------------------------------------------------------------------------------- |
| sampleRate | Target output sample rate. Native audio is automatically resampled if required. |
| channels | 1 = Mono, 2 = Stereo |
| chunkDurationMs | Duration of each emitted audio chunk in milliseconds |
stopRecording(): void
Stops microphone recording and stops streaming new audio chunks.
stopRecording();isRecording(): boolean
Returns true if the native recorder is actively capturing audio.
const recording = isRecording();getCapturedBufferCount(): number
Returns the number of audio chunks captured during the current recording session.
const count = getCapturedBufferCount();
console.log('Captured chunks:', count);Useful for debugging and validating streaming behaviour.
onAudioChunk(callback)
Registers a callback that receives PCM audio chunks from the native recorder.
onAudioChunk((buffer: ArrayBuffer) => {
// buffer contains signed 16-bit PCM samples
});The callback is invoked whenever a chunk is completed by the native recorder.
For example:
onAudioChunk((buffer) => {
const samples = new Int16Array(buffer);
console.log('Samples:', samples.length);
});Or send the PCM directly to a real-time service:
onAudioChunk((buffer) => {
websocket.send(buffer);
});Playback
The library supports native playback of raw 16-bit Linear PCM (PCM16) audio.
Playback can be used for:
- AI voice responses
- Text-to-Speech audio
- WebSocket PCM streams
- Real-time voice applications
- Recorded PCM playback
- Custom audio processing pipelines
initializePlayer(config: AudioPlaybackConfig): void
Initializes the native PCM audio player.
Call this before sending PCM chunks for playback.
initializePlayer({
sampleRate: 24000,
channels: 1,
bufferSize: 4096,
});| Property | Description |
| ------------ | ----------------------------------------------- |
| sampleRate | Sample rate of the PCM data that will be played |
| channels | 1 = Mono, 2 = Stereo |
| bufferSize | Playback buffer configuration |
Important
The playback configuration must match the format of the PCM data being provided.
For example, if your PCM stream is 24 kHz mono PCM16, initialize the player with
sampleRate: 24000andchannels: 1.
playChunk(buffer: ArrayBuffer): void
Sends a PCM16 audio chunk to the native player.
playChunk(buffer);Chunks can be sent continuously as they become available.
For example:
onPcmChunk((buffer) => {
playChunk(buffer);
});Or when receiving audio from a WebSocket:
websocket.onmessage = (event) => {
playChunk(event.data);
};PCM chunks are buffered on the native side and consumed by the native audio player.
JavaScript does not need to schedule every chunk according to the hardware playback clock.
finishPlayback(): void
Signals that no more PCM chunks will be provided for the current playback stream.
finishPlayback();Use this when the source stream has ended naturally.
For example:
for (const chunk of pcmChunks) {
playChunk(chunk);
}
finishPlayback();finishPlayback() allows already-buffered audio to complete playback.
It is different from stopPlayback(), which is used to stop the current playback.
stopPlayback(): void
Stops the current playback.
stopPlayback();Use this when playback needs to be cancelled or stopped by the application.
releasePlayer(): void
Releases the native player resources.
releasePlayer();Call this when the player is no longer needed.
For example:
useEffect(() => {
initializePlayer({
sampleRate: 24000,
channels: 1,
bufferSize: 4096,
});
return () => {
releasePlayer();
};
}, []);PCMStreamer
PCMStreamer is a convenience helper for feeding PCM16 audio to the native player.
import {
NitroRealtimeAudio,
PCMStreamer,
} from '@mindinventory/react-native-nitro-realtime-audio';
const pcmStreamer = new PCMStreamer(NitroRealtimeAudio, {
sampleRate: 24000,
channels: 1,
chunkDurationMs: 20,
});Send PCM chunks:
pcmStreamer.enqueue(chunk);When no more chunks will arrive:
pcmStreamer.finish();Stop playback:
pcmStreamer.stop();You can also provide a complete PCM ArrayBuffer:
pcmStreamer.play(pcmBuffer);The buffer is split into chunks based on the configured sample rate, channels, and chunk duration before being forwarded to the native player.
Streaming Playback Example
A common use case is receiving PCM audio continuously from a WebSocket or AI service.
import {
initializePlayer,
playChunk,
finishPlayback,
releasePlayer,
} from '@mindinventory/react-native-nitro-realtime-audio';
initializePlayer({
sampleRate: 24000,
channels: 1,
bufferSize: 4096,
});
websocket.onmessage = (event) => {
const pcmBuffer = event.data;
playChunk(pcmBuffer);
};
websocket.onclose = () => {
finishPlayback();
};
// When the player is no longer needed
releasePlayer();The incoming chunks do not need to arrive exactly every 20 ms.
The native playback layer buffers incoming PCM and handles playback according to the native audio output.
Record and Play Example
The following example captures PCM chunks from the microphone and plays them back after recording stops.
import React, { useEffect, useRef, useState } from 'react';
import { View, Text, Button } from 'react-native';
import {
getMicrophonePermissionStatus,
requestMicrophonePermission,
startRecording,
stopRecording,
isRecording,
onAudioChunk,
initializePlayer,
playChunk,
finishPlayback,
stopPlayback,
releasePlayer,
} from '@mindinventory/react-native-nitro-realtime-audio';
export default function AudioDemo() {
const [recording, setRecording] = useState(false);
const [chunksReceived, setChunksReceived] = useState(0);
const recordedPcmChunks = useRef<ArrayBuffer[]>([]);
useEffect(() => {
initializePlayer({
sampleRate: 24000,
channels: 1,
bufferSize: 4096,
});
onAudioChunk((buffer) => {
recordedPcmChunks.current.push(buffer.slice(0));
setChunksReceived((count) => count + 1);
});
return () => {
stopPlayback();
releasePlayer();
};
}, []);
const start = async () => {
let permission = getMicrophonePermissionStatus();
if (permission !== 'granted') {
permission = await requestMicrophonePermission();
if (permission !== 'granted') {
return;
}
}
recordedPcmChunks.current = [];
setChunksReceived(0);
startRecording({
sampleRate: 24000,
channels: 1,
chunkDurationMs: 100,
});
setRecording(isRecording());
};
const stop = () => {
stopRecording();
setRecording(isRecording());
console.log(`Captured ${recordedPcmChunks.current.length} chunks`);
};
const play = () => {
stopPlayback();
initializePlayer({
sampleRate: 24000,
channels: 1,
bufferSize: 4096,
});
for (const chunk of recordedPcmChunks.current) {
playChunk(chunk);
}
finishPlayback();
};
return (
<View>
<Text>{recording ? 'Recording...' : 'Stopped'}</Text>
<Text>Chunks Received: {chunksReceived}</Text>
<Button title="Start Recording" onPress={start} disabled={recording} />
<Button title="Stop Recording" onPress={stop} disabled={!recording} />
<Button
title="Play Recording"
onPress={play}
disabled={recording || chunksReceived === 0}
/>
<Button title="Stop Playback" onPress={stopPlayback} />
</View>
);
}Recommended Configurations
Recording
| Use Case | Sample Rate | Channels | Chunk Duration | | ----------------- | ----------: | -------: | -------------: | | Voice AI | 24000 | Mono | 100 ms | | Whisper | 16000 | Mono | 100 ms | | WebRTC | 48000 | Mono | 20 ms | | General Recording | Native | Stereo | 100 ms |
The exact configuration should ultimately match the requirements of the service or audio pipeline consuming the PCM data.
Playback
For playback, configure the player to match the incoming PCM format.
For example, for 24 kHz mono PCM16:
initializePlayer({
sampleRate: 24000,
channels: 1,
bufferSize: 4096,
});Do not initialize the player with a different sample rate or channel count than the PCM stream unless your application performs the required conversion.
PCM Format
Audio chunks use:
Encoding: Linear PCM
Sample type: Signed Int16
Bit depth: 16-bit
Byte order: Little-endian
Transport: ArrayBufferThe number of bytes represented by a PCM duration can be calculated using:
bytes =
sampleRate
× channels
× bytesPerSample
× durationSecondsFor PCM16:
bytesPerSample = 2For example, 20 ms of 24 kHz mono PCM16 contains:
24000 × 1 × 2 × 0.020
= 960 bytesExample Application
The example application included in this repository demonstrates:
- 🎙️ Real-time microphone recording
- 📈 Live waveform visualization
- 📊 Live chunk statistics
- 📦 Raw PCM streaming
- 💾 WAV generation
- ▶️ PCM audio playback
- 🌊 Streaming PCM playback
- 🔄 Record → playback workflows
- 📋 Recording summaries
It serves as both a demo application and a reference implementation for integrating the library.
Notes
- Audio chunks contain signed 16-bit Linear PCM samples.
- Samples are stored in little-endian format.
- Audio is transferred using standard JavaScript
ArrayBuffer. - Recording does not automatically save audio files.
- Generate WAV files or encode MP3/AAC yourself using the streamed PCM data if file output is required.
- Recording chunk size depends on the configured sample rate, channel count, and chunk duration.
- Playback expects PCM16 data.
- Playback sample rate and channel count should match the incoming PCM stream.
- Native playback buffering is used to handle PCM chunks before they are played.
finishPlayback()should be used when a playback stream has naturally finished.stopPlayback()should be used when playback needs to be stopped.releasePlayer()should be used when native playback resources are no longer needed.
Best Practices
Process recording chunks immediately
The example application may store PCM chunks in memory so it can replay audio or generate a WAV file after recording.
For production applications such as AI assistants, speech recognition, WebRTC, or long-running streams, process each chunk immediately instead of storing every chunk in memory.
Example:
onAudioChunk((buffer) => {
websocket.send(buffer);
});Streaming chunks immediately keeps memory usage low even during long recording sessions.
Stream playback chunks as they arrive
For live audio, send incoming PCM chunks to the player as soon as they are available.
websocket.onmessage = (event) => {
playChunk(event.data);
};Do not accumulate an entire response in JavaScript before starting playback unless your application specifically requires offline playback.
Keep recording and playback formats consistent
When audio passes through an external service, make sure you know the format returned by that service.
For example:
Recording
24000 Hz
Mono
PCM16
↓
AI / WebSocket
↓
Playback
24000 Hz
Mono
PCM16Initialize playback using the format of the incoming playback stream, which may not always be the same as the recording format.
Finish streams properly
When the source has finished producing PCM:
finishPlayback();This indicates that no more chunks are expected for the current playback stream.
When playback needs to be cancelled:
stopPlayback();Release playback resources
When the player is no longer needed:
releasePlayer();For React components, this can typically be performed during cleanup:
useEffect(() => {
initializePlayer({
sampleRate: 24000,
channels: 1,
bufferSize: 4096,
});
return () => {
releasePlayer();
};
}, []);Roadmap
- ✅ Real-time PCM recording
- ✅ Configurable recording
- ✅ Native audio resampling
- ✅ Real-time PCM streaming
- ✅ PCM16 playback
- ✅ Streaming PCM playback
- ⏳ Audio session configuration
- ⏳ Voice Activity Detection (VAD)
- ⏳ Echo cancellation
- ⏳ Noise suppression
- ⏳ Automatic Gain Control (AGC)
Contributing
Contributions, bug reports, and feature requests are welcome.
Please read the Contributing Guide before opening a pull request.
License
MIT
Let us know
If you use our open-source libraries in your project, please make sure to credit us and give a star to www.mindinventory.com.
Built with ❤️ using React Native Builder Bob (create-react-native-library).
