{"id":50746581,"url":"https://github.com/maitrungduc1410/react-native-waveform-recorder","last_synced_at":"2026-06-10T21:32:34.149Z","repository":{"id":359333636,"uuid":"1245574448","full_name":"maitrungduc1410/react-native-waveform-recorder","owner":"maitrungduc1410","description":"🎙️High-performance React Native audio recorder with a native live waveform","archived":false,"fork":false,"pushed_at":"2026-05-29T03:16:04.000Z","size":6756,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-29T05:12:38.197Z","etag":null,"topics":["audio-recorder","audio-visualizer","live-waveform","microphone","react-native","voice","voice-recorder","waveform"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maitrungduc1410.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-21T10:53:39.000Z","updated_at":"2026-05-29T03:16:08.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/maitrungduc1410/react-native-waveform-recorder","commit_stats":null,"previous_names":["maitrungduc1410/react-native-waveform-recorder"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/maitrungduc1410/react-native-waveform-recorder","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maitrungduc1410%2Freact-native-waveform-recorder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maitrungduc1410%2Freact-native-waveform-recorder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maitrungduc1410%2Freact-native-waveform-recorder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maitrungduc1410%2Freact-native-waveform-recorder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maitrungduc1410","download_url":"https://codeload.github.com/maitrungduc1410/react-native-waveform-recorder/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maitrungduc1410%2Freact-native-waveform-recorder/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34172196,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-10T02:00:07.152Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audio-recorder","audio-visualizer","live-waveform","microphone","react-native","voice","voice-recorder","waveform"],"created_at":"2026-06-10T21:32:34.038Z","updated_at":"2026-06-10T21:32:34.137Z","avatar_url":"https://github.com/maitrungduc1410.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# react-native-waveform-recorder\n\nHigh-performance React Native audio recorder with a **native live waveform**, multi-segment record/pause/resume, in-place preview + scrub playback, and a 64-bucket export ready for chat-bubble waveforms. Built on the New Architecture (Fabric), zero JS in the metering hot path.\n\n\u003cdiv align=\"center\"\u003e\n    \u003cimg src=\"./demo.png\" alt=\"Demo screenshot\" width=\"600\" /\u003e\n\u003c/div\u003e\n\n**Best paired with [`react-native-waveform-player`](https://github.com/maitrungduc1410/react-native-waveform-player)** — this library handles the recording side; the player handles playback. Use them together for a complete voice-message stack: record with a live waveform, then drop the resulting URI + 64-bucket peaks straight into the player for the chat bubble — no decode round-trip needed. Both libraries are intentionally standalone (zero shared peer deps) and share the same visual language.\n\n**Works with Expo** — fully supported via a [development build](https://docs.expo.dev/develop/development-builds/introduction/) (EAS Build or `expo prebuild`). Doesn't work in Expo Go because Expo Go can't load third-party native modules. See [Expo install](#expo) below.\n\n## Demo\n\n| iOS | Android |\n| :---: | :---: |\n| \u003cvideo src=\"https://github.com/user-attachments/assets/683227e2-3f87-4b31-b4ce-7c3a43aae9c6\" controls loop muted\u003e\u003c/video\u003e |\u003cvideo src=\"https://github.com/user-attachments/assets/6e84d929-5fe0-4198-8379-6c4a3b73b3e4\" controls loop muted\u003e\u003c/video\u003e |\n\n## Why another recorder?\n\n| | this library | [`@simform_solutions/react-native-audio-waveform`](https://github.com/SimformSolutionsPvtLtd/react-native-audio-waveform) | [`react-native-nitro-sound`](https://github.com/hyochan/react-native-nitro-sound) | [`@lodev09/expo-recorder`](https://github.com/lodev09/expo-recorder) | [`@bhojaniasgar/react-native-audio-waveform`](https://github.com/bhojaniasgar/react-native-audio-waveform) |\n| --- | --- | --- | --- | --- | --- |\n| Renders waveform | **Native, on-thread** (Swift / Kotlin) | Native (`mode=\"live\"` for recording, `mode=\"static\"` for playback) | **No built-in renderer** — exposes `currentMetering`, you draw it yourself | JS-driven via Reanimated | Native |\n| Live waveform during recording | **Yes** | Yes | Manual (from metering callback) | Yes | Yes |\n| Record + pause/resume → same file | **Yes** | Yes (`pauseRecord` / `resumeRecord`) | Yes (`pauseRecorder` / `resumeRecorder`) | Via underlying `expo-audio` | Yes (`pauseRecording` / `resumeRecording`, Android 7.0+) |\n| Preview state (in-place playback + scrub) | **Yes** | Separate `static` mode pointing at the file | No integrated preview UI | Manual (composed externally) | Separate `Waveform` component for playback |\n| WhatsApp-style 64-bucket export | **Built in** (`onComplete.samples`) | No | No | No | No |\n| Slide-to-cancel / slide-to-lock | **Native gestures** | No | No | No | No |\n| Silence detection w/ auto-stop | **Yes** | No | No | No | No |\n| Output formats | `m4a`, `aac`, `wav`, `opus` | Configurable encoder (AAC/AAC-LD/HE-AAC/…) | Configurable via `AudioSet` (AAC/AAC-LD/…) | Whatever `expo-audio` supports | m4a (default) |\n| Raw-PCM streaming hook | **Opt-in subpath** (`/pcm-stream`) | No | No (metering only) | No | No |\n| Dependencies | **None** | `react-native-gesture-handler` | `react-native-nitro-modules` | `expo-audio` + `react-native-reanimated` + `react-native-gesture-handler` | None |\n| Ecosystem | Bare RN **+ Expo** (dev client) | Bare React Native | Bare React Native | **Expo-only** | Bare React Native |\n\n## Install\n\n```sh\nnpm install react-native-waveform-recorder\n# or\nyarn add react-native-waveform-recorder\n```\n\nRequires React Native **0.85+** with the **New Architecture enabled** (Fabric + TurboModules). Bare minimums: iOS 13, Android API 24 (Android API 29 for opus output).\n\n### iOS\n\n```shell\npod install\n```\n\nAdd a microphone usage description to `Info.plist`:\n\n```xml\n\u003ckey\u003eNSMicrophoneUsageDescription\u003c/key\u003e\n\u003cstring\u003eRecord voice messages.\u003c/string\u003e\n```\n\nIf you opt into `backgroundRecording`, also add:\n\n```xml\n\u003ckey\u003eUIBackgroundModes\u003c/key\u003e\n\u003carray\u003e\n  \u003cstring\u003eaudio\u003c/string\u003e\n\u003c/array\u003e\n```\n\n### Android\n\nThe library declares `RECORD_AUDIO` for you. You still need to request the runtime permission from JS — the wrapper does this automatically on `start()` via `PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.RECORD_AUDIO)`.\n\nFor `backgroundRecording`, declare the foreground service in your `android/app/src/main/AndroidManifest.xml`:\n\n```xml\n\u003cservice\n    android:name=\"com.waveformrecorder.WaveformRecorderBackgroundService\"\n    android:foregroundServiceType=\"microphone\"\n    android:exported=\"false\" /\u003e\n```\n\n`FOREGROUND_SERVICE` and `FOREGROUND_SERVICE_MICROPHONE` are already declared by the library and merged in for you.\n\n### Expo\n\nThis library works with Expo via a **[development build](https://docs.expo.dev/develop/development-builds/introduction/)** (EAS Build or `npx expo prebuild`). It does **not** work in Expo Go — Expo Go can't load third-party native modules.\n\n**1. Install the package.**\n\n```sh\nnpx expo install react-native-waveform-recorder\n```\n\n**2. Add the config plugin to `app.json` / `app.config.js`.**\n\nThe library ships an Expo config plugin that wires up the native permissions and the Android foreground service for you — no manual `Info.plist` / `AndroidManifest.xml` edits.\n\n```json\n{\n  \"expo\": {\n    \"plugins\": [\n      [\n        \"react-native-waveform-recorder\",\n        {\n          \"microphonePermission\": \"Allow $(PRODUCT_NAME) to record voice messages.\",\n          \"backgroundRecording\": false\n        }\n      ]\n    ]\n  }\n}\n```\n\nBoth options are optional:\n\n| Option | Type | Default | What it does |\n| --- | --- | --- | --- |\n| `microphonePermission` | `string \\| false` | generic message | Sets iOS `NSMicrophoneUsageDescription`. Pass `false` to leave it untouched (e.g. if you set it elsewhere). |\n| `backgroundRecording` | `boolean` | `false` | When `true`, adds the iOS `audio` `UIBackgroundMode` **and** the Android foreground `\u003cservice\u003e` declaration so the `backgroundRecording` prop works. |\n\nIf you don't need background recording you can add the plugin with no options at all:\n\n```json\n{\n  \"expo\": {\n    \"plugins\": [\"react-native-waveform-recorder\"]\n  }\n}\n```\n\nThe library auto-merges `RECORD_AUDIO` + foreground-service permissions into the Android manifest via manifest-merger, so you don't need to list them in `android.permissions`.\n\n**3. Generate the native projects (or rebuild your dev client).**\n\n```sh\nnpx expo prebuild --clean\n# then re-build your dev client / EAS Build\neas build --profile development --platform all\n```\n\nThe library's `WaveformRecorder.podspec` uses standard new-architecture autolinking (`install_modules_dependencies(s)`), so `expo prebuild` picks it up automatically — no manual `Podfile` or `build.gradle` edits required.\n\n**SDK compatibility.** The library requires React Native 0.85+ with the New Architecture enabled. Pick an Expo SDK that ships RN 0.85 or newer (check the [Expo SDK ↔ RN compatibility table](https://docs.expo.dev/versions/latest/)). New Architecture is the default on recent Expo SDKs; if you're on an older one, enable it with `\"newArchEnabled\": true` in `app.json`.\n\n## Quick start\n\n```tsx\nimport { useRef } from 'react';\nimport { Button, View } from 'react-native';\nimport {\n  WaveformRecorderView,\n  type WaveformRecorderViewRef,\n} from 'react-native-waveform-recorder';\n\nexport function VoiceNote() {\n  const ref = useRef\u003cWaveformRecorderViewRef\u003e(null);\n  return (\n    \u003cView\u003e\n      \u003cWaveformRecorderView\n        ref={ref}\n        style={{ height: 56 }}\n        playedBarColor=\"#25D366\"\n        onComplete={(e) =\u003e {\n          console.log('saved:', e.uri, e.durationMs, e.samples.length);\n        }}\n      /\u003e\n      \u003cButton title=\"Record\" onPress={() =\u003e ref.current?.start()} /\u003e\n      \u003cButton title=\"Stop\" onPress={() =\u003e ref.current?.stop()} /\u003e\n    \u003c/View\u003e\n  );\n}\n```\n\n`onComplete.samples` is a 64-bucket `number[]` in `[0, 1]` computed natively post-recording — drop it straight into a chat-bubble waveform without re-decoding the file.\n\n## State machine\n\n```\n              start()                                   stop()\n   idle ─────────────────► recording ─────────────────► stopped ──► (onComplete)\n                              │   ▲                       ▲\n                       pause()│   │resume()               │\n                              ▼   │                       │\n                            paused ────► enterPreview ────┤\n                                            │             │\n                                            │ resume()    │\n                                            ▼             │\n                                       betweenSegments ───┘\n                                       (continue-record)\n```\n\n| state | what's happening |\n| --- | --- |\n| `idle` | view rendered, nothing recorded yet |\n| `recording` | meter + bars live, writing to disk |\n| `paused` | meter paused, file flushed but not finalized |\n| `preview` | playback-style view with scrub gesture + play button |\n| `betweenSegments` | post-preview, ready to `resume()` and append a new segment |\n| `stopped` | terminal state; segments concatenated, `onComplete` fired |\n\n`controlledState` flips the component into a controlled mode where you advance state via prop updates and commands become inert.\n\n## Props\n\n### Recording config\n\n| prop | type | default | notes |\n| --- | --- | --- | --- |\n| `outputUri` | `string` | _cache dir_ | Where to write the final file. The host's cache dir is used if omitted. |\n| `output` | `{ format, sampleRate, channels, bitrate, quality }` | `m4a / 44.1k / 1ch / 128kbps / high` | Convenience nested form. Equivalent to individual `outputFormat`, `outputSampleRate`, `outputChannels`, `outputBitrate`, `outputQuality` props. |\n| `outputFormat` | `'m4a' \\| 'aac' \\| 'wav' \\| 'opus'` | `'m4a'` | Opus requires iOS 11+ / Android API 29+; older OSes silently fall back to AAC. Container extension follows the format. |\n| `outputSampleRate` | `number` | `44100` | |\n| `outputChannels` | `1 \\| 2` | `1` | |\n| `outputBitrate` | `number` | `128000` | Ignored for `wav` (lossless). |\n| `outputQuality` | `'low' \\| 'medium' \\| 'high'` | `'high'` | |\n| `maxDurationMs` | `number` | `0` | `0` = no cap. When hit, `onMaxDurationReached` fires + recording auto-stops. |\n| `minDurationMs` | `number` | `0` | Soft hint surfaced via `onComplete.durationMs` (useful for \"press-and-hold\" UIs). |\n| `meterUpdatesPerSecond` | `number` | `30` | Native polling rate for the meter (iOS `CADisplayLink`, Android `Handler`). |\n| `samplesPerSecond` | `number` | `12` | Visual sample rate — how many bars/sec we paint on screen. |\n\n### Visual\n\n| prop | type | default |\n| --- | --- | --- |\n| `playedBarColor` / `unplayedBarColor` / `futureBarColor` | `ColorValue` | _system blue_ / `#3a3a3a` / `unplayedBarColor` |\n| `barWidth` / `barGap` / `barRadius` | `number` | `3` / `2` / `barWidth/2` |\n| `containerBackgroundColor` / `containerBorderRadius` / `showBackground` | `ColorValue` / `number` / `boolean` | `transparent` / `16` / `true` |\n| `showTime` / `timeColor` / `timeMode` | `boolean` / `ColorValue` / `'count-up' \\| 'count-down'` | `true` / `#fff` / `'count-up'` |\n| `recordingMode` | `'scroll' \\| 'morph' \\| 'centered'` | `'scroll'` | How new bars enter — feed from the right (`scroll`), grow in place (`morph`), centered around a playhead. |\n| `futureBarStyle` | `'dot' \\| 'line' \\| 'hidden'` | `'hidden'` | What to render in the un-recorded portion. Default matches WhatsApp / Slack / Messenger (nothing until the first sample arrives). Set to `'dot'` or `'line'` for an Instagram / Zalo-style placeholder. |\n| `newSampleEntry` | `'grow' \\| 'fade' \\| 'none'` | `'grow'` | Per-bar entry animation. |\n\n### Preview \u0026 continue-recording\n\n| prop | type | default | notes |\n| --- | --- | --- | --- |\n| `enablePreview` | `boolean` | `true` | Gates `enterPreview` command. |\n| `enableContinueRecording` | `boolean` | `true` | Gates `resume()` from preview (WhatsApp-style multi-segment). |\n| `showPlayButton` / `playButtonColor` | `boolean` / `ColorValue` | `true` / `playedBarColor` | Built-in play/pause button shown during preview state. |\n\n### Gestures\n\n| prop | type | default |\n| --- | --- | --- |\n| `enableSlideToCancel` / `slideToCancelThresholdDp` | `boolean` / `number` | `false` / `80` |\n| `enableSlideToLock` / `slideToLockThresholdDp` | `boolean` / `number` | `false` / `80` |\n\n### Silence detection\n\n| prop | type | default | notes |\n| --- | --- | --- | --- |\n| `silenceThresholdDb` | `number` | `-160` | Negative dBFS. `-50` is a typical \"room is quiet\" threshold. |\n| `silenceTimeoutMs` | `number` | `0` | `0` disables. |\n| `autoStopOnSilence` | `boolean` | `false` | When true, the engine calls `stop()` itself after a silence window. |\n\n### Background recording\n\n| prop | type | default | notes |\n| --- | --- | --- | --- |\n| `backgroundRecording` | `boolean` | `false` | Requires plist + manifest setup (see [Install](#install)). On iOS we emit `onError('background-capability', …)` once if `audio` is missing from `UIBackgroundModes`. |\n| `backgroundNotificationTitle` / `backgroundNotificationBody` | `string` | _generic_ | Android-only: shown in the foreground-service notification. |\n\n### Raw PCM streaming\n\n| prop | type | default | notes |\n| --- | --- | --- | --- |\n| `enablePcmStream` | `boolean` | `false` | WAV-only. Stream raw PCM via `onPcmChunk` — see the [Raw PCM streaming](#raw-pcm-streaming) section below for the full guide. |\n| `pcmChunkMs` | `number` | `200` | Target chunk cadence in ms. |\n\n### Controlled state\n\n| prop | type | default | notes |\n| --- | --- | --- | --- |\n| `controlledState` | `'auto' \\| 'idle' \\| 'recording' \\| 'paused' \\| 'preview' \\| 'stopped'` | `'auto'` | Switch to controlled mode where the host advances state via prop updates and commands become inert (they still emit `onStateChange` with the *requested* new state). |\n\n## Events\n\n| event | payload | when |\n| --- | --- | --- |\n| `onStateChange` | `{ state, durationMs }` | every state transition |\n| `onMeter` | `{ amplitude, peak, db }` | `meterUpdatesPerSecond` Hz while recording |\n| `onMaxDurationReached` | `{}` | `maxDurationMs` reached |\n| `onComplete` | `{ uri, durationMs, format, mimeType, sizeBytes, sampleRate, channels, samples (64-bucket number[]), peakAmplitude }` | after `stop()` finalises |\n| `onError` | `{ message, code }` | non-fatal warnings + fatal failures (code namespaced per origin: `session`, `start`, `pcm-stream`, …) |\n| `onPermissionDenied` | `{}` | mic permission denied at `start()` |\n| `onSeek` | `{ positionMs }` | scrub gesture or `seekPreview()` |\n| `onPlaybackTimeUpdate` | `{ positionMs, durationMs }` | preview playback tick |\n| `onSlideProgress` | `{ cancelProgress, lockProgress }` _(each 0-1)_ | live during slide gesture |\n| `onSlideCancel` / `onSlideLock` | `{}` | once when the gesture threshold is crossed |\n| `onSilenceDetected` | `{ durationMs }` | once per silence window |\n| `onPcmChunk` | `{ chunk (base64), sampleRate, channels, bytesPerSample, timestampMs }` | every `pcmChunkMs` while `enablePcmStream + outputFormat='wav'` |\n\n## Imperative commands\n\nHold a `ref` to the view and call methods directly.\n\n| command | notes |\n| --- | --- |\n| `start()` | Begins (or restarts) recording. Auto-requests mic permission. |\n| `pause()` | Pauses the active segment without finalising it. |\n| `resume()` | Resumes from `paused` (same segment) or `betweenSegments` (new segment). |\n| `stop()` | Finalises, concatenates segments, fires `onComplete`. |\n| `cancel()` | Discards all segments + returns to `idle`. Does not fire `onComplete`. |\n| `enterPreview()` / `exitPreview()` | Switch into / out of the preview state. |\n| `togglePreviewPlayback()` | Play / pause preview audio. |\n| `seekPreview(positionMs)` | Seek the preview playhead. |\n\n## Raw PCM streaming\n\nThe default bundle stays small — raw PCM lives behind an opt-in subpath so apps that don't need it never pay the cost.\n\n```tsx\nimport { WaveformRecorderView } from 'react-native-waveform-recorder';\nimport {\n  decodePcmChunk,\n  pcmToMonoFloat32,\n} from 'react-native-waveform-recorder/pcm-stream';\n\n\u003cWaveformRecorderView\n  output={{ format: 'wav', sampleRate: 16000, channels: 1 }}\n  enablePcmStream\n  pcmChunkMs={100}\n  onPcmChunk={(e) =\u003e {\n    const int16 = decodePcmChunk(e.chunk);\n    const f32 = pcmToMonoFloat32(int16, e.channels);\n    // hand `f32` to your STT / VAD pipeline of choice\n  }}\n/\u003e;\n```\n\nCaveats:\n\n- Only works with `outputFormat = 'wav'` — m4a / opus paths don't expose pre-encoded samples.\n- Payloads cross the bridge as base64, **not zero-copy buffers**. Fine up to ~1 MB/s; beyond that prefer a true JSI module like [`react-native-audio-api`](https://github.com/software-mansion/react-native-audio-api).\n\n## Recipes\n\nThe `example/` workspace ships themed, copy-pastable recipes that mirror real-world voice-message UIs. **All six chat-style recipes pair this library with [`react-native-waveform-player`](https://github.com/maitrungduc1410/react-native-waveform-player)** — `onComplete` hands the player both the recorded file URI and the 64-bucket peaks, so the playback bubble paints instantly with no decode round-trip. See `example/src/components/SentVoiceNote.tsx` for the shared bubble.\n\n| recipe | location | what it shows |\n| --- | --- | --- |\n| **WhatsApp** | `example/src/screens/WhatsAppScreen.tsx` | green pill, mic-as-send swap, swipe-up-to-lock + slide-left-to-cancel chevron, preview with delete + send. Sent voice notes become playable \"out\" bubbles in the chat thread. |\n| **Messenger** | `example/src/screens/MessengerScreen.tsx` | continue-recording flow, gradient pill, locked vs unlocked variants. Sent notes appear as playable bubbles in the conversation feed. |\n| **Instagram** | `example/src/screens/InstagramScreen.tsx` | gradient ring meter + bottom-sheet preview. Sent notes appear as right-aligned DM player bubbles. |\n| **Slack** | `example/src/screens/SlackScreen.tsx` | single-press send, no preview. Sent notes appear as `#general` voice-memo messages. |\n| **TikTok** | `example/src/screens/TikTokScreen.tsx` | hold-to-record, no preview, dark theme. Sent notes appear in a \"Recent sends\" reel. |\n| **Zalo** | `example/src/screens/ZaloScreen.tsx` | light-mode pill with explicit delete / preview / send action row. Sent notes appear as light-mode player bubbles. |\n| **Gesture playground** | `example/src/screens/GestureScreen.tsx` | live cancel/lock progress bars driven by `onSlideProgress`. |\n| **Silence demo** | `example/src/screens/SilenceScreen.tsx` | adjustable threshold + auto-stop on silence. |\n| **64-sample export** | `example/src/screens/SamplesExportScreen.tsx` | renders `onComplete.samples` three ways for chat-bubble parity. |\n| **Stress test** | `example/src/screens/StressTestScreen.tsx` | 30-minute run with tick / peak / RMS counters. |\n| **Raw PCM stream** | `example/src/screens/PcmStreamScreen.tsx` | end-to-end VAD-style consumer of `onPcmChunk`. |\n\nRun the example app:\n\n```sh\nyarn install\ncd example\nyarn ios   # or yarn android\n```\n\n## How it works\n\nSee [ARCHITECTURE.md](./ARCHITECTURE.md) for the full breakdown — file layout, state machine, threading model, memory bounds, codegen quirks, drawing pipeline, Fabric lifecycle, and segment concat strategies per format.\n\nIn short: the JS layer is intentionally thin (permission flow + ref shape + DirectEvent re-emission). Everything else — recording, drawing, metering, scrub gestures, preview playback, multi-segment concat — lives in Swift / Kotlin behind a Fabric composite view.\n\n## Lessons learned\n\nIf you're building something similar (a native-rendering Fabric component, an audio recorder, a stateful UI with async lifecycle) — or you're picking this codebase up to extend it — start with [LESSONS_LEARNED.md](./LESSONS_LEARNED.md). It's the field report of every non-obvious bug we hit (Fabric quirks like `display: 'none'` unmounting iOS views, async race conditions in preview snapshots, the WAV multi-segment memory bomb, state-machine UI desync patterns, …) along with the meta-lessons each one taught us. Cheaper to read than to re-discover.\n\n## Roadmap \u0026 known limitations\n\nThe library is feature-complete and production-ready, but a few items are deferred for future releases. PRs welcome.\n\n### Planned improvements\n\n- **`recordingMode` modes `morph` and `centered`** — currently only `'scroll'` is fully implemented. `'morph'` (grow new bars in place, no scrolling) and `'centered'` (playhead-anchored layout with bars expanding outward) are accepted as prop values but fall back to `'scroll'`. Implementing them needs new sub-pixel slot math in `WaveformBarsView` on both platforms — the scroll math doesn't translate directly because it assumes new samples always enter from the right edge.\n- **Streaming WAV multi-segment concat** — the only known long-recording memory hazard. When the user pauses + resumes a `wav`-format recording multiple times then calls `stop()` or `enterPreview()`, the current concat path buffers every segment's PCM in RAM before writing the output. Peak memory ~1× total PCM on Android, ~3× on iOS. For a 1-hour multi-segment WAV at 44.1 kHz mono 16-bit (~317 MB raw), iOS can peak ~950 MB and crash. The fix is straightforward — replace the buffer-then-write path with a chunked-stream-then-write path (~256 KB peak instead of 3× PCM) on both platforms. Continuous (no-pause) WAV recordings and any-length recordings in `m4a`/`aac`/`opus` are unaffected.\n- **CI workflow** — `lint` / `typecheck` / iOS build / Android build pipeline. Currently runs locally only.\n- **npm publish** — first publish to npm is still pending; install from the GitHub repo until then.\n- **True zero-copy PCM streaming** — current `onPcmChunk` payloads cross the bridge as base64. Fine up to ~1 MB/s. For higher throughput a JSI-backed buffer module (or delegating to [`react-native-audio-api`](https://github.com/software-mansion/react-native-audio-api)) would avoid the base64 round-trip.\n\n### Out of scope\n\nThese were intentionally rejected during planning to keep the library focused:\n\n- Streaming uploads from the recorder — handle in your own code on `onComplete`.\n- Multi-track mixing.\n- Recording effects / filters / VST-style processing — use `react-native-audio-api` if you need this.\n- JS-side custom waveform renderers — the native renderer is the whole point.\n- Old React Native architecture (paper-only) support.\n\n## Contributing\n\n- [Development workflow](CONTRIBUTING.md#development-workflow)\n- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)\n- [Code of conduct](CODE_OF_CONDUCT.md)\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaitrungduc1410%2Freact-native-waveform-recorder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaitrungduc1410%2Freact-native-waveform-recorder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaitrungduc1410%2Freact-native-waveform-recorder/lists"}