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 🙏

© 2025 – Pkg Stats / Ryan Hefner

expo-audio-studio

v1.3.1

Published

Professional audio recording & playback with advanced Voice Activity Detection for React Native

Downloads

743

Readme


Demo

What's included

  • Record high-quality audio (WAV/PCM16 format, 16kHz, 16-bit mono)
  • Capture real-time audio chunks during recording (PCM base64 encoded)
  • Play audio with speed control and seeking
  • Detect when someone is speaking (voice activity detection)
  • Get real-time amplitude data and waveform visualizations
  • Join multiple audio files together
  • Works the same on iOS and Android
  • Full TypeScript support

Installation

npm install expo-audio-studio

Setup permissions

Add the plugin to your app.config.ts to automatically configure microphone permissions:

export default {
  plugins: [
    [
      'expo-audio-studio',
      {
        microphonePermission:
          'Allow $(PRODUCT_NAME) to access your microphone for audio recording',
      },
    ],
  ],
};

Or use it without options for default permissions:

export default {
  plugins: ['expo-audio-studio'],
};

This adds:

  • iOS: Microphone usage description
  • Android: Audio recording permissions

Build your app

Since this uses native code, you'll need a development build:

# Prebuild to apply the plugin
npx expo prebuild

# Create development build
npx expo run:ios
npx expo run:android

Getting started

Record audio

import ExpoAudioStudio from 'expo-audio-studio';

// Request permission first
const permission = await ExpoAudioStudio.requestMicrophonePermission();
if (!permission.granted) {
  console.log('Microphone permission denied');
  return;
}

// Listen to recording events
const subscription = ExpoAudioStudio.addListener(
  'onRecorderStatusChange',
  (event: AudioRecordingStateChangeEvent) => {
    console.log('Recording status:', event.status);
  }
);

// Start recording
const filePath = ExpoAudioStudio.startRecording();
console.log('Recording to:', filePath);

// Stop recording
const finalPath = ExpoAudioStudio.stopRecording();
console.log('Recording saved to:', finalPath);

// Cleanup
subscription.remove();

Detect speech

import ExpoAudioStudio from 'expo-audio-studio';

// Choose how often you want events
ExpoAudioStudio.setVADEventMode('onEveryFrame'); // Real-time (default)
// setVADEventMode('onChange');  // Only state changes
// setVADEventMode('throttled', 100); // Every 100ms

// Enable VAD
ExpoAudioStudio.setVADEnabled(true);

// Listen to voice activity
const vadSubscription = ExpoAudioStudio.addListener(
  'onVoiceActivityDetected',
  (event: VoiceActivityEvent) => {
    if (event.isStateChange) {
      // State just changed - someone started or stopped talking
      console.log(
        event.isVoiceDetected ? 'Started talking!' : 'Stopped talking'
      );
    }
    console.log('Confidence:', event.confidence);
    console.log('Event type:', event.eventType);
  }
);

// Start recording - VAD will automatically start
ExpoAudioStudio.startRecording();

Capture audio chunks

import ExpoAudioStudio from 'expo-audio-studio';
import { encode as base64Encode, decode as base64Decode } from 'base-64';

// Enable chunk listening (disabled by default)
ExpoAudioStudio.setListenToChunks(true);

// Store chunks as they arrive
const audioChunks: string[] = [];

// Listen to audio chunks during recording
const chunkSubscription = ExpoAudioStudio.addListener(
  'onAudioChunk',
  (event: AudioChunkEvent) => {
    // event.base64 contains raw PCM audio data (Int16, 16kHz, mono)
    audioChunks.push(event.base64);
    console.log('Received chunk, total:', audioChunks.length);
  }
);

// Start recording - chunks will be sent in real-time
ExpoAudioStudio.startRecording();

// Later, when stopping...
ExpoAudioStudio.stopRecording();

// Process the chunks - decode, concatenate, add WAV header, etc.
// See example/components/ChunkRecorder.tsx for full implementation

// Cleanup
chunkSubscription.remove();
ExpoAudioStudio.setListenToChunks(false);

Play audio

import ExpoAudioStudio from 'expo-audio-studio';

// Listen to playback events
const playerSubscription = ExpoAudioStudio.addListener(
  'onPlayerStatusChange',
  (event: PlayerStatusChangeEvent) => {
    console.log('Playing:', event.isPlaying);
  }
);

// Start playback
ExpoAudioStudio.startPlaying('/path/to/audio/file.wav');

// Control playback speed
ExpoAudioStudio.setPlaybackSpeed('1.5'); // 1.5x speed

// Cleanup
playerSubscription.remove();

API Reference

Recording Functions

| Function | Description | Returns | | -------------------------------- | ---------------------------------- | -------------------------- | | startRecording(directoryPath?) | Start audio recording | string - File path | | stopRecording() | Stop recording | string - Final file path | | pauseRecording() | Pause recording | string - Status message | | resumeRecording() | Resume recording | string - Status message | | setListenToChunks(enabled) | Enable/disable real-time chunk capture | boolean - Enabled state | | lastRecording() | Get last recording path | string or null |

Playback Functions

| Function | Description | Returns | | ------------------------- | ---------------------------- | ----------------- | | startPlaying(path) | Start audio playback | string - Status | | stopPlayer() | Stop playback | string - Status | | pausePlayer() | Pause playback | string - Status | | resumePlayer() | Resume playback | string - Status | | setPlaybackSpeed(speed) | Set playback speed (0.5-2.0) | string - Status | | seekTo(position) | Seek to position in seconds | string - Status |

Voice Activity Detection

| Function | Description | Returns | | -------------------------------------- | --------------------------------- | ----------------- | | setVADEnabled(enabled) | Enable/disable VAD | string - Status | | setVoiceActivityThreshold(threshold) | Set detection threshold (0.0-1.0) | string - Status | | setVADEventMode(mode, throttleMs?) | Control event frequency | string - Status |

Audio Analysis

| Function | Description | Returns | | ---------------------------------------- | ----------------------------- | ------------------------------ | | getDuration(uri) | Get audio file duration | number - Duration in seconds | | getAudioAmplitudes(fileUrl, barsCount) | Get waveform data (dB values) | object - Amplitude data | | setAmplitudeUpdateFrequency(hz) | Set amplitude update rate | string - Status |

Constants

| Constant | Description | Returns | | ----------------- | -------------------------------------------- | --------- | | currentPosition | Current playback position in seconds | number | | meterLevel | Current audio level during recording (in dB) | number | | playerStatus | Detailed player status information | object | | isVADActive | Whether VAD is currently active | boolean | | isVADEnabled | Whether VAD is enabled by user preference | boolean |

File Management

| Function | Description | Returns | | --------------------------------------- | ----------------------- | ---------------------- | | listRecordings(directoryPath?) | List audio files | array - File list | | joinAudioFiles(filePaths, outputPath) | Concatenate audio files | string - Output path |

Event Listeners

Recording Events

import ExpoAudioStudio from 'expo-audio-studio';

ExpoAudioStudio.addListener(
  'onRecorderStatusChange',
  (event: AudioRecordingStateChangeEvent) => {
    // event.status: 'recording' | 'stopped' | 'paused' | 'resumed' | 'error'
  }
);

const subscription = ExpoAudioStudio.addListener(
  'onRecorderAmplitude',
  (event: AudioMeteringEvent) => {
    // event.amplitude: number (dB level)
  }
);

// Listen to audio chunks (requires setListenToChunks(true))
const chunkSubscription = ExpoAudioStudio.addListener(
  'onAudioChunk',
  (event: AudioChunkEvent) => {
    // event.base64: string - Base64 encoded PCM audio data
    // Format: Int16 samples, 16kHz sample rate, mono channel
  }
);

Voice Activity Events

import ExpoAudioStudio from 'expo-audio-studio';

const subscription = ExpoAudioStudio.addListener(
  'onVoiceActivityDetected',
  (event: VoiceActivityEvent) => {
    // event.isVoiceDetected: boolean - Is someone speaking?
    // event.confidence: number - How confident is the detection (0.0-1.0)
    // event.timestamp: number - When this happened
    // event.isStateChange: boolean - Did the state just change?
    // event.previousState: boolean - What was the previous state
    // event.eventType: 'speech_start' | 'speech_continue' | 'silence_start' | 'silence_continue'
  }
);

Playback Events

import ExpoAudioStudio from 'expo-audio-studio';

// Listen to player status changes
const subscription = ExpoAudioStudio.addListener(
  'onPlayerStatusChange',
  (event: PlayerStatusChangeEvent) => {
    // event.isPlaying: boolean
    // event.didJustFinish: boolean
  }
);

More examples

Save to a custom folder

const filePath = startRecording('/path/to/custom/directory');

iOS audio session setup

Note: Don't reconfigure the audio session while recording or playing - this can freeze your app.

import ExpoAudioStudio from 'expo-audio-studio';

await ExpoAudioStudio.configureAudioSession({
  category: 'playAndRecord',
  mode: 'default',
  options: {
    defaultToSpeaker: true,
    allowBluetooth: true,
  },
});

await ExpoAudioStudio.activateAudioSession();

Adjust voice detection sensitivity

// More sensitive (for quiet rooms)
ExpoAudioStudio.setVoiceActivityThreshold(0.3);

// Less sensitive (for noisy places)
ExpoAudioStudio.setVoiceActivityThreshold(0.7);

ExpoAudioStudio.setVADEnabled(true);

Real-world example: Voice-activated recording

Example 1

import ExpoAudioStudio from 'expo-audio-studio';

// Only notify on state changes for battery efficiency
ExpoAudioStudio.setVADEventMode('onChange');
ExpoAudioStudio.setVADEnabled(true);

const subscription = ExpoAudioStudio.addListener(
  'onVoiceActivityDetected',
  (event: VoiceActivityEvent) => {
    if (event.eventType === 'speech_start') {
      console.log('🎤 Voice detected');
    } else if (event.eventType === 'silence_start') {
      console.log('🔇 Silence detected');
    }
  }
);

Example 2

import ExpoAudioStudio from 'expo-audio-studio';

// Only notify in every 250ms
ExpoAudioStudio.setVADEventMode('throttled', 250);
ExpoAudioStudio.setVADEnabled(true);

const subscription = ExpoAudioStudio.addListener(
  'onVoiceActivityDetected',
  (event: VoiceActivityEvent) => {
    if (event.isVoiceDetected) {
      console.log('Voice detected');
    } else {
      console.log('Silence detected');
    }
  }
);

Get waveform data

const waveformData = ExpoAudioStudio.getAudioAmplitudes(
  '/path/to/file.wav',
  100
);
console.log('Amplitude values:', waveformData.amplitudes);

// Normalize for UI visualization (dB to 0-1 range)
const normalized = waveformData.amplitudes.map(dB =>
  Math.max(0, (dB + 60) / 60)
);

const duration = ExpoAudioStudio.getDuration('/path/to/file.wav');

Merge audio files

const inputFiles = [
  '/path/to/file1.wav',
  '/path/to/file2.wav',
  '/path/to/file3.wav',
];

const outputPath = '/path/to/joined_audio.wav';
const result = ExpoAudioStudio.joinAudioFiles(inputFiles, outputPath);
console.log('Joined file created:', result);

Audio format

Recordings use WAV format (PCM16, 16kHz, 16-bit mono) on both platforms. This provides good quality while keeping file sizes reasonable.

Additional formats are on the roadmap.

Voice detection details

Both iOS and Android now fire the same events, so you can expect identical behavior.

Controlling event frequency

You can choose how often you want to receive voice detection events:

// Get events for every audio frame processed (~32ms, about 30 per second)
// Perfect for real-time visualizations or instant response
ExpoAudioStudio.setVADEventMode('onEveryFrame');

// Only get notified when voice state changes (speech starts/stops)
// Battery-friendly and great for simple on/off detection
ExpoAudioStudio.setVADEventMode('onChange');

// Get updates every X milliseconds, plus immediate state changes
// Nice balance between real-time and performance
ExpoAudioStudio.setVADEventMode('throttled', 250); // every 250ms

Which one should you use?

  • Building a live voice visualizer? Use onEveryFrame
  • Just need to know when someone starts/stops talking? Use onChange
  • Want periodic updates without overwhelming your app? Use throttled with 100-250ms

You can change the mode anytime, even while recording is happening. The setting sticks around until you change it again.

Under the hood

iOS uses Apple's Core ML Sound Classification:

  • Real confidence scores from machine learning (0.0-1.0)
  • Analyzes audio in 1.5 second windows with smart overlap
  • Works on iOS 14.0 and up

Android uses Silero VAD:

  • Compact neural network implementation
  • Fixed confidence values (0.85 for voice, 0.15 for silence)
  • Processes 32 ms chunks at 16 kHz

Platform requirements

  • iOS: 14.0 or higher (for voice detection)
  • Android: API 21 (Android 5.0) or higher
  • Web: Coming soon

What's under the hood

Android libraries:

iOS frameworks:

  • AVFoundation - for recording/playback
  • Core ML - for voice detection

Everything is MIT licensed.

Running the Example

cd example
npm install
npx expo run:ios
# or
npx expo run:android
# Web support coming soon!

Building from Source

# Install dependencies
npm install

# Build TypeScript
npm run build

# Run tests
npm test

What's next

Streaming & real-time processing

  • Stream audio chunks in real time with configurable chunk sizes
  • Access raw PCM16 data during recording
  • Reduce latency for live transcription and other low-latency pipelines
  • Use the stream for speech-to-text or custom analysis

More formats

  • M4A, MP3, and FLAC recording options
  • Configurable quality settings

Web support

  • WebRTC-based recording
  • Browser-side voice detection
  • Same API across native and web targets

Other features

  • Stereo and multi-channel recording
  • Real-time audio effects
  • Additional analytics hooks

Got ideas? Open a discussion!

Contributing

Pull requests are welcome! Check out the Contributing Guide for details.

  1. Fork the repo
  2. Create a branch: git checkout -b feature/my-feature
  3. Make your changes
  4. Push and open a pull request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Built with Expo Modules API
  • Android WAV recording powered by AndroidWaveRecorder by @squti
  • Android Voice Activity Detection powered by Silero VAD by @gkonovalov
  • iOS Voice Activity Detection using Apple's Core ML Sound Classification
  • Built for production use in audio applications

Support