npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mindinventory/react-native-nitro-realtime-audio

v1.2.0

Published

A high-performance audio recording library for React Native powered by Nitro Modules.

Readme

react-native-nitro-realtime-audio

License: MIT React Native Expo Nitro Modules

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 Callback

Playback

JavaScript / Network
    │
    ▼
ArrayBuffer
    │
    ▼
Native PCM Buffer
    │
    ▼
Native Audio Player
    │
    ▼
Speaker

This makes it possible to build pipelines such as:

Microphone
    │
    ▼
Native Recorder
    │
    ▼
ArrayBuffer
    │
    ▼
JavaScript
    │
    ▼
WebSocket / AI / DSP
    │
    ▼
ArrayBuffer
    │
    ▼
Native Player
    │
    ▼
Speaker

Why 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-modules

or:

yarn add @mindinventory/react-native-nitro-realtime-audio react-native-nitro-modules

or for Expo:

npx expo install @mindinventory/react-native-nitro-realtime-audio react-native-nitro-modules

Note

react-native-nitro-modules is a required peer dependency.

Install iOS dependencies:

cd ios
pod install

Expo 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
44100

getMicrophonePermissionStatus(): 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: 24000 and channels: 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:    ArrayBuffer

The number of bytes represented by a PCM duration can be calculated using:

bytes =
  sampleRate
  × channels
  × bytesPerSample
  × durationSeconds

For PCM16:

bytesPerSample = 2

For example, 20 ms of 24 kHz mono PCM16 contains:

24000 × 1 × 2 × 0.020
= 960 bytes

Example 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
PCM16

Initialize 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).