react-native-realtime-audio
Version:
Play audio buffers in realtime
61 lines (43 loc) • 5.86 kB
Markdown
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Overview
`react-native-realtime-audio` is an Expo Module (not a plain RN library) that provides real-time audio playback, recording, VAD-gated recording, and waveform visualization on iOS, Android, and web. It is consumed by an Expo app via autolinking and exposes both imperative classes (e.g. `RealtimeAudioPlayer`) and native views (e.g. `RealtimeAudioPlayerView`).
## Common commands
Library (run from repo root):
- `bun run build` — `expo-module build` (compiles `src/` → `build/`, the published JS).
- `bun run build:plugin` — `expo-module build plugin` (compiles `plugin/src/` → `plugin/build/`, the config plugin loaded via `app.plugin.js`).
- `bun run lint` — `expo-module lint` (uses `.eslintrc.js` extending `universe/native` + `universe/web`).
- `bun run test` — `expo-module test`.
- `bun run clean` — `expo-module clean`.
- `bun run open:ios` / `bun run open:android` — open the example app's native projects in Xcode / Android Studio.
Example app (`cd example`):
- `bun install`, then `bun run ios` or `bun run android` (these run `expo run:*`, which prebuilds and compiles native code — needed after any iOS/Android source change).
- `bun run start` for Metro only; `bun run web` for web.
- The example uses `expo.autolinking.nativeModulesDir = ".."` so it picks up the parent module without publishing.
- **`example/ios` and `example/android` are not checked in** — they are regenerated by `expo prebuild` (which `expo run:*` invokes automatically when missing). All native config for the example app comes from `example/app.json` plus the library's own config plugin (`../app.plugin.js`). If you need a project-specific native edit, add it to a config plugin rather than hand-editing the generated dirs.
Requirements: Node 22 (for bun via corepack), Expo SDK 50+, iOS 15.1+, Android API 26+.
## Architecture
### Three parallel module families
The library ships four Expo modules with the same shape repeated three times: **Player**, **Recorder**, **VADRecorder** (plus a top-level `RealtimeAudioModule` that just exposes `checkAndRequestAudioPermissions`). For each family there is:
- TS: `src/RealtimeAudio{Player,Recorder,VADRecorder}Module.ts` (thin `requireNativeModule` wrapper) + `src/RealtimeAudio{...}View.tsx` (`requireNativeView` wrapper) + a `src/RealtimeAudio{...}.ts` class type.
- iOS (Swift, in `ios/`): `RealtimeAudio{...}Module.swift` (Expo `Module` definition with both a `Class(...)` and a `View(...)` registration) backed by `RealtimeAudio{...}.swift` (the engine) and `RealtimeAudio{...}View.swift` (the native view).
- Android (Kotlin, `android/src/main/java/com/paulingalls/realtimeaudio/`): same triple — `Module.kt`, engine `.kt`, `View.kt`.
Each module's `Class(...)` block exposes the imperative API (`addBuffer`, `pause`, `resume`, `stop` / `startRecording`, `stopRecording` / `startListening`, `stopListening`). The matching `View(...)` block re-exposes those same functions plus the visualization `Prop`s (`waveformColor`, `visualizer`, `audioFormat`). Keep the JS, iOS, and Android surfaces in sync when changing any of them — names of events, props, and async functions must match across all three.
Module names registered in `expo-module.config.json` are `RealtimeAudioModule`, `RealtimeAudioPlayerModule`, `RealtimeAudioRecorderModule`, `RealtimeAudioVADRecorderModule`. The TS `requireNativeModule` / `requireNativeView` calls reference the *Expo module name* (e.g. `"RealtimeAudioPlayer"`), not the class name.
### Visualizations
Both platforms have a `BaseAudioView` that owns a swappable `AudioVisualization` (default `BarGraphVisualizer`). The four visualizer types — `barGraph`, `linearWaveform`, `circularWaveform`, `tripleCircle` (see `Visualizers` enum in `src/RealtimeAudio.types.ts`) — are mapped from the `visualizer` string prop inside each Module's `Prop("visualizer")` block. Adding a visualizer requires: a new file on each platform (e.g. `ios/FooVisualizer.swift`, `android/.../FooVisualizer.kt`), a new enum entry in `RealtimeAudio.types.ts`, and a new `else if` branch in **every** view-bearing module on both platforms.
Views stop playback / drawing when offscreen (see `BaseAudioView` lifecycle hooks — `didMoveToWindow` on iOS, `onAttached/DetachedFromWindow` on Android). Recent commits show this pattern was added specifically to fix Android crashes; preserve it.
### VAD
Voice Activity Detection uses Silero VAD via ONNX Runtime. The model file `silero_vad_16k_op15.onnx` is bundled into both platforms (iOS via `s.resource_bundles` in the podspec; Android via `src/main/assets/`) and loaded by `VadIterator.{swift,kt}`. Native deps:
- iOS: `onnxruntime-objc` (CocoaPods, declared in `ios/RealtimeAudio.podspec`).
- Android: `com.microsoft.onnxruntime:onnxruntime-android` and `com.tagtraum:pcmsampledsp` (the latter requires the **beatunes.com** Maven repo — see below).
### Config plugin
`plugin/src/index.ts` is an Expo config plugin (re-exported by `app.plugin.js` from the built output) that:
1. Adds `NSMicrophoneUsageDescription` on iOS.
2. Appends the `https://www.beatunes.com/repo/maven2` repository to the consumer app's `app/build.gradle` (idempotent: it greps for `beatunes.com` first). This is required for the `pcmsampledsp` dependency.
3. Adds `RECORD_AUDIO` and `MODIFY_AUDIO_SETTINGS` Android permissions.
Anything that changes the plugin must be rebuilt with `bun run build:plugin` before it takes effect for consumers.
### Build outputs
- `build/` — published JS (referenced by `package.json` `main`/`types`).
- `plugin/build/` — compiled config plugin (referenced by `app.plugin.js`).
Both are checked-in artifacts of `expo-module build` / `expo-module build plugin`; rebuild before publishing.