{"id":30933469,"url":"https://github.com/hyochan/react-native-nitro-sound","last_synced_at":"2026-01-12T05:45:43.294Z","repository":{"id":38386537,"uuid":"129062069","full_name":"hyochan/react-native-nitro-sound","owner":"hyochan","description":"react-native native module for audio recorder and player with nitromodule!","archived":false,"fork":false,"pushed_at":"2025-11-28T12:41:05.000Z","size":15912,"stargazers_count":888,"open_issues_count":159,"forks_count":248,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-11-29T13:28:25.547Z","etag":null,"topics":["audio-player","audio-recorder","java","kotlin","nitromodule","objective-c","react-native","swift","turbomodule","typescript"],"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/hyochan.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","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},"funding":{"buy_me_a_coffee":"hyochan"}},"created_at":"2018-04-11T08:37:46.000Z","updated_at":"2025-11-27T17:20:03.000Z","dependencies_parsed_at":"2024-06-20T14:16:33.792Z","dependency_job_id":"09ba8474-d49c-455a-9d71-7844d0c62a07","html_url":"https://github.com/hyochan/react-native-nitro-sound","commit_stats":{"total_commits":419,"total_committers":52,"mean_commits":8.057692307692308,"dds":"0.49880668257756566","last_synced_commit":"8d866e7ddfc720f2125136de249253dfa5a44550"},"previous_names":["dooboolab/react-native-audio-recorder-player","hyochan/react-native-nitro-sound"],"tags_count":76,"template":false,"template_full_name":null,"purl":"pkg:github/hyochan/react-native-nitro-sound","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyochan%2Freact-native-nitro-sound","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyochan%2Freact-native-nitro-sound/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyochan%2Freact-native-nitro-sound/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyochan%2Freact-native-nitro-sound/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hyochan","download_url":"https://codeload.github.com/hyochan/react-native-nitro-sound/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyochan%2Freact-native-nitro-sound/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28335229,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T00:36:25.062Z","status":"online","status_checked_at":"2026-01-12T02:00:08.677Z","response_time":98,"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-player","audio-recorder","java","kotlin","nitromodule","objective-c","react-native","swift","turbomodule","typescript"],"created_at":"2025-09-10T14:03:11.998Z","updated_at":"2026-01-12T05:45:43.288Z","avatar_url":"https://github.com/hyochan.png","language":"TypeScript","funding_links":["https://buymeacoffee.com/hyochan","https://www.buymeacoffee.com/hyochan","https://paypal.me/dooboolab"],"categories":["TypeScript"],"sub_categories":[],"readme":"# react-native-nitro-sound\n\n[![yarn Version](http://img.shields.io/npm/v/react-native-nitro-sound.svg?style=flat-square)](https://npmjs.org/package/react-native-nitro-sound)\n[![Monthly Downloads](http://img.shields.io/npm/dm/react-native-nitro-sound.svg?style=flat-square)](https://npmjs.org/package/react-native-nitro-sound)\n[![Weekly Downloads](http://img.shields.io/npm/dw/react-native-nitro-sound.svg?style=flat-square)](https://npmjs.org/package/react-native-nitro-sound)\n[![CI](https://github.com/hyochan/react-native-nitro-sound/actions/workflows/ci.yml/badge.svg)](https://github.com/hyochan/react-native-nitro-sound/actions/workflows/ci.yml)\n[![publish-package](https://github.com/hyochan/react-native-nitro-sound/actions/workflows/publish-package.yml/badge.svg)](https://github.com/hyochan/react-native-nitro-sound/actions/workflows/publish-package.yml)\n[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg?style=flat-square)](https://www.typescriptlang.org/)\n[![MIT License](http://img.shields.io/npm/l/react-native-nitro-sound.svg?style=flat-square)](https://npmjs.org/package/react-native-nitro-sound)\n[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)\n\n[![Platform - iOS](https://img.shields.io/badge/platform-iOS-blue.svg?style=flat-square\u0026logo=apple\u0026logoColor=white)](https://developer.apple.com/ios/)\n[![Platform - Android](https://img.shields.io/badge/platform-Android-green.svg?style=flat-square\u0026logo=android\u0026logoColor=white)](https://developer.android.com/)\n[![Platform - Web](https://img.shields.io/badge/platform-Web-orange.svg?style=flat-square\u0026logo=googlechrome\u0026logoColor=white)](https://reactnative.dev/docs/react-native-web)\n\n[![GreatFrontEnd](greatfrontend-js.gif)](https://www.greatfrontend.com?fpr=hyo73\u0026fp_sid=rnsound)\n\n\u003e ℹ️ **Swift 6 build warning**: If Xcode shows `function type mismatch … has_value` errors (see [#718](https://github.com/hyochan/react-native-nitro-sound/issues/718)), upgrade to Xcode 16.4 or newer. The workaround and cleanup steps are documented in the [FAQ](docs/FAQ.md#swift-6-compile-error-function-type-mismatch--has_value-718).\n\n\u003cimg src=\"https://github.com/user-attachments/assets/81ce7b7b-0b7d-413b-8a26-505372349ecb\" width=\"70%\" alt=\"Logo\" /\u003e\n\n## Legacy Package (react-native-audio-recorder-player)\n\n[![yarn Version](http://img.shields.io/npm/v/react-native-audio-recorder-player.svg?style=flat-square)](https://npmjs.org/package/react-native-audio-recorder-player)\n[![Downloads](http://img.shields.io/npm/dm/react-native-audio-recorder-player.svg?style=flat-square)](https://npmjs.org/package/react-native-audio-recorder-player)\n[![Weekly Downloads](http://img.shields.io/npm/dw/react-native-audio-recorder-player.svg?style=flat-square)](https://npmjs.org/package/react-native-audio-recorder-player)\n\n## React Native Nitro Sound 🚀\n\n**React Native Nitro Sound** is a high-performance audio recording and playback library built with [NitroModules](https://github.com/mrousavy/nitro), offering:\n\n- **Zero Bridge Overhead**: Direct native module access for maximum performance\n- **Full Type Safety**: TypeScript definitions generated from native specs\n- **Synchronous Methods**: Where appropriate, for better developer experience\n- **Event Listeners**: Native callbacks with type-safe event payloads\n- **Cross-Platform Code Generation**: Automatic code generation for iOS (Swift) and Android (Kotlin)\n- **Background Processing**: Recording operations now run in background threads to prevent UI blocking, requiring loading state management\n- **Web Platform Support**: Full support for web browsers using Web Audio API and MediaRecorder API\n\n### Requirements\n\n- React Native: \u003e= 0.79 (0.82 recommended)\n- iOS: Deployment Target \u003e= 13.0\n  - Note: With RN 0.81+, build using Xcode \u003e= 16.1 (toolchain requirement; iOS runtime minimum remains 13.0)\n- Android: minSdk \u003e= 24 (JDK 17 recommended; compileSdk 36 recommended)\n  - Note: RN 0.82+ requires Gradle 9.0+\n- New Architecture: optional (Nitro works on both old and new arch; RN 0.82+ is New Architecture only)\n- Expo SDK \u003e= 50 (for Expo users)\n\n## 🎉 React Native Nitro Sound - Reborn from React Native Audio Recorder Player\n\n**React Native Nitro Sound** is the reborn version of [React Native Audio Recorder Player](https://www.npmjs.com/package/react-native-audio-recorder-player)!\n\nFor those unfamiliar, this was a beloved library with **40k+ weekly downloads** and **180k+ monthly downloads**. Now, this library embarks on a new journey with [NitroModules](https://github.com/mrousavy/nitro), starting fresh as **react-native-nitro-sound**.\n\nAs the creator of [Flutter Sound](https://pub.dev/packages/flutter_sound), the name 'Sound' feels much more familiar and close to my heart. This rebranding is not just a name change, but a commitment to actively maintain and evolve this library with even greater dedication.\n\nStarting fresh with **version 1.0.0**, we're delivering a more powerful and stable audio solution. Special thanks to [@mrousavy](https://github.com/mrousavy) for creating this amazing technology! 🙏\n\nYour continued support and interest mean the world to us!\n\n---\n\nThis is a high-performance React Native module for audio recording and playback, now powered by [NitroModules](https://github.com/mrousavy/nitro) for direct native module access without bridge overhead. The library provides simple recorder and player functionalities for iOS, Android, and Web platforms with full TypeScript support and type safety.\n\n\u003e 🔴 **Critical**: Recording operations now run in background threads. **You MUST implement loading states** to handle the async delays, or your UI may appear unresponsive. See [Component Examples](#component-based-implementation) for proper implementation.\n\n## Help Maintenance\n\nThis is one of those projects that brings me joy to work on. If you find it useful, consider buying me a coffee ☕️ — your support keeps me motivated!\n\n\u003ca href=\"https://www.buymeacoffee.com/hyochan\" target=\"_blank\"\u003e\u003cimg src=\"https://www.buymeacoffee.com/assets/img/custom_images/purple_img.png\" alt=\"Buy Me A Coffee\" style=\"height: auto !important;width: auto !important;\" \u003e\u003c/a\u003e\n[![Paypal](https://www.paypalobjects.com/webstatic/mktg/Logo/pp-logo-100px.png)](https://paypal.me/dooboolab)\n\n## Preview\n\n[Web Demo](https://hyochan.github.io/react-native-nitro-sound/)\n\n\u003cimg src=\"https://github.com/user-attachments/assets/02545c15-f41d-4186-be79-2a13fd67ccb2\" width=800 alt=\"Screenshot\"/\u003e\n\n\u003cimg src=\"https://github.com/user-attachments/assets/6d8a5a80-cd31-450f-b410-9b8f42ae8d03\" width=800 alt=\"Screenshot2\"/\u003e\n\n## Documentation \u0026 Resources\n\n- 🔧 [NitroModules Documentation](https://github.com/mrousavy/nitro) - Learn about the underlying technology\n- 📝 [Version 3 Release Note](https://medium.com/dooboolab/react-native-audio-player-and-recorder-v3-7697e25cd07)\n- 📰 [Original Blog Post](https://medium.com/@dooboolab/react-native-audio-recorder-and-player-4aa5f26a666)\n\n## Migration from react-native-audio-recorder-player\n\nIf you're migrating from `react-native-audio-recorder-player` (version 3.x or earlier), the API remains largely the same. Simply update your package name:\n\n```diff\n- import AudioRecorderPlayer from 'react-native-audio-recorder-player';\n+ import Sound from 'react-native-nitro-sound';\n```\n\n## Getting started\n\n1. **Install packages**:\n\n   ```sh\n   yarn add react-native-nitro-sound react-native-nitro-modules\n   ```\n\n   Or using npm:\n\n   ```sh\n   npm install react-native-nitro-sound react-native-nitro-modules\n   ```\n\n2. **Align React Native dependencies (recommended)**:\n\n   ```sh\n   npx @rnx-kit/align-deps --requirements react-native@0.81 --write\n   ```\n\n## Post Installation\n\nAfter installing the packages, follow these steps:\n\n1. **iOS Setup**:\n\n   ```sh\n   npx pod-install\n   ```\n\n   - If resolution fails, try `npx pod-install --repo-update`.\n   - RN 0.81+ requires Xcode \u003e= 16.1 to build.\n\n2. **Android Setup**:\n   No additional steps required. The module uses autolinking.\n\n3. **Web Setup**:\n   For React Native Web, install the additional dependency:\n\n   ```sh\n   yarn add react-native-web\n   ```\n\n   Then configure your webpack to include the web-specific implementation:\n\n   ```js\n   // webpack.config.js\n   module.exports = {\n     resolve: {\n       alias: {\n         'react-native': 'react-native-web',\n       },\n     },\n   };\n   ```\n\n\u003e **Note**: The `nitrogen` command is already run during the library's build process. You don't need to run it in your application.\n\n## Platform-specific Configuration\n\n### iOS Configuration\n\n1. **Microphone Permission**: Add to your `Info.plist`:\n\n   ```xml\n   \u003ckey\u003eNSMicrophoneUsageDescription\u003c/key\u003e\n   \u003cstring\u003eGive $(PRODUCT_NAME) permission to use your microphone. Your record wont be shared without your permission.\u003c/string\u003e\n   ```\n\n2. **Minimum iOS Version**: Ensure your minimum deployment target is iOS 13.0 or higher in your `Podfile`:\n\n   ```ruby\n   platform :ios, '13.0'\n   ```\n\n### Android Configuration\n\nOn _Android_ you need to add permissions to `AndroidManifest.xml`:\n\n```xml\n\u003cuses-permission android:name=\"android.permission.INTERNET\" /\u003e\n\u003cuses-permission android:name=\"android.permission.RECORD_AUDIO\" /\u003e\n\u003cuses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" /\u003e\n\u003cuses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" /\u003e\n```\n\nAlso, android above `Marshmallow` needs runtime permission to record audio. Below are two approaches:\n\n**Minimal Approach (Recommended for Android 13+):**\n\n```ts\nif (Platform.OS === 'android') {\n  try {\n    const granted = await PermissionsAndroid.request(\n      PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,\n      {\n        title: 'Audio Recording Permission',\n        message: 'This app needs access to your microphone to record audio.',\n        buttonNeutral: 'Ask Me Later',\n        buttonNegative: 'Cancel',\n        buttonPositive: 'OK',\n      }\n    );\n\n    if (granted === PermissionsAndroid.RESULTS.GRANTED) {\n      console.log('Recording permission granted');\n    } else {\n      console.log('Recording permission denied');\n      return;\n    }\n  } catch (err) {\n    console.warn(err);\n    return;\n  }\n}\n```\n\n**Full Permissions Approach (For older Android versions):**\n\n```ts\nif (Platform.OS === 'android') {\n  try {\n    const grants = await PermissionsAndroid.requestMultiple([\n      PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,\n      PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,\n      PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,\n    ]);\n\n    if (\n      grants['android.permission.WRITE_EXTERNAL_STORAGE'] ===\n        PermissionsAndroid.RESULTS.GRANTED \u0026\u0026\n      grants['android.permission.READ_EXTERNAL_STORAGE'] ===\n        PermissionsAndroid.RESULTS.GRANTED \u0026\u0026\n      grants['android.permission.RECORD_AUDIO'] ===\n        PermissionsAndroid.RESULTS.GRANTED\n    ) {\n      console.log('All permissions granted');\n    } else {\n      console.log('All required permissions not granted');\n      return;\n    }\n  } catch (err) {\n    console.warn(err);\n    return;\n  }\n}\n```\n\n## Methods\n\n| Method                    |                      Param                       |      Return       | Description                                           |\n| :------------------------ | :----------------------------------------------: | :---------------: | :---------------------------------------------------- |\n| mmss                      |                 `number` seconds                 |     `string`      | Convert seconds to `minute:second` string             |\n| mmssss                    |                 `number` seconds                 |     `string`      | Convert seconds to `minute:second:millisecond` string |\n| setSubscriptionDuration   |                `number` duration                 |      `void`       | Set callback interval in ms (default 500ms)           |\n| startRecorder             |       `string?` uri, `AudioSet?` audioSet,       | `Promise\u003cstring\u003e` | Start recording with optional path and audio settings |\n|                           |            `boolean?` meteringEnabled            |                   |                                                       |\n| pauseRecorder             |                                                  | `Promise\u003cstring\u003e` | Pause recording                                       |\n| resumeRecorder            |                                                  | `Promise\u003cstring\u003e` | Resume recording                                      |\n| stopRecorder              |                                                  | `Promise\u003cstring\u003e` | Stop recording and return file path                   |\n| startPlayer               | `string?` uri, `Record\u003cstring, string\u003e?` headers | `Promise\u003cstring\u003e` | Start playback with optional URI and HTTP headers     |\n| stopPlayer                |                                                  | `Promise\u003cstring\u003e` | Stop playback                                         |\n| pausePlayer               |                                                  | `Promise\u003cstring\u003e` | Pause playback                                        |\n| resumePlayer              |                                                  | `Promise\u003cstring\u003e` | Resume playback                                       |\n| seekToPlayer              |              `number` milliseconds               | `Promise\u003cstring\u003e` | Seek to position in milliseconds                      |\n| setVolume                 |                  `number` value                  | `Promise\u003cstring\u003e` | Set volume (0.0 - 1.0)                                |\n| setPlaybackSpeed          |                  `number` speed                  | `Promise\u003cstring\u003e` | Set playback speed (0.5 - 2.0)                        |\n| addRecordBackListener     |               `Function` callback                |      `void`       | Add recording progress listener                       |\n| removeRecordBackListener  |                                                  |      `void`       | Remove recording progress listener                    |\n| addPlayBackListener       |               `Function` callback                |      `void`       | Add playback progress listener                        |\n| removePlayBackListener    |                                                  |      `void`       | Remove playback progress listener                     |\n| addPlaybackEndListener    |               `Function` callback                |      `void`       | Add playback completion listener                      |\n| removePlaybackEndListener |                                                  |      `void`       | Remove playback completion listener                   |\n\n## Usage\n\n### Basic Usage\n\n```typescript\nimport Sound, {\n  AudioEncoderAndroidType,\n  AudioSourceAndroidType,\n  AVEncoderAudioQualityIOSType,\n  AVEncodingOption,\n  RecordBackType,\n  PlayBackType,\n} from 'react-native-nitro-sound';\n\n// Sound is a singleton instance, use directly\n\n// Recording\nconst onStartRecord = async () =\u003e {\n  // Set up recording progress listener\n  Sound.addRecordBackListener((e: RecordBackType) =\u003e {\n    console.log('Recording progress:', e.currentPosition, e.currentMetering);\n    setRecordSecs(e.currentPosition);\n    setRecordTime(Sound.mmssss(Math.floor(e.currentPosition)));\n  });\n\n  const result = await Sound.startRecorder();\n  console.log('Recording started:', result);\n};\n\nconst onStopRecord = async () =\u003e {\n  const result = await Sound.stopRecorder();\n  Sound.removeRecordBackListener();\n  console.log('Recording stopped:', result);\n};\n\n// Pause/Resume Recording\nconst onPauseRecord = async () =\u003e {\n  await Sound.pauseRecorder();\n  console.log('Recording paused');\n};\n\nconst onResumeRecord = async () =\u003e {\n  await Sound.resumeRecorder();\n  console.log('Recording resumed');\n};\n\n// Playback\nconst onStartPlay = async () =\u003e {\n  // Set up playback progress listener\n  Sound.addPlayBackListener((e: PlayBackType) =\u003e {\n    console.log('Playback progress:', e.currentPosition, e.duration);\n    setCurrentPosition(e.currentPosition);\n    setTotalDuration(e.duration);\n    setPlayTime(Sound.mmssss(Math.floor(e.currentPosition)));\n    setDuration(Sound.mmssss(Math.floor(e.duration)));\n  });\n\n  // Set up playback end listener\n  Sound.addPlaybackEndListener((e: PlaybackEndType) =\u003e {\n    console.log('Playback completed:', e);\n    // Handle playback completion\n    setIsPlaying(false);\n    setCurrentPosition(0);\n  });\n\n  const result = await Sound.startPlayer();\n  console.log('Playback started:', result);\n};\n\nconst onPausePlay = async () =\u003e {\n  await Sound.pausePlayer();\n};\n\nconst onStopPlay = async () =\u003e {\n  Sound.stopPlayer();\n  Sound.removePlayBackListener();\n  Sound.removePlaybackEndListener();\n};\n\n// Seeking\nconst seekTo = async (milliseconds: number) =\u003e {\n  await Sound.seekToPlayer(milliseconds);\n};\n\n// Volume control\nconst setVolume = async (volume: number) =\u003e {\n  await Sound.setVolume(volume); // 0.0 - 1.0\n};\n\n// Speed control\nconst setSpeed = async (speed: number) =\u003e {\n  await Sound.setPlaybackSpeed(speed); // 0.5 - 2.0\n};\n```\n\n### Modern API: Multiple Instances\n\n```tsx\nimport { createSound } from 'react-native-nitro-sound';\n\n// Create independent instances (recorder/player per instance)\nconst soundA = createSound();\nconst soundB = createSound();\n\nawait soundA.startPlayer('https://example.com/a.mp3');\nawait soundB.startPlayer('https://example.com/b.mp3');\n\n// Control them independently\nawait soundA.pausePlayer();\nawait soundB.setVolume(0.5);\n\n// Clean up when done\nsoundA.dispose();\nsoundB.dispose();\n```\n\n### React Hook API\n\n```tsx\nimport { useSound } from 'react-native-nitro-sound';\n\nexport function Player() {\n  const {\n    sound,\n    state,\n    startPlayer,\n    pausePlayer,\n    resumePlayer,\n    stopPlayer,\n    seekToPlayer,\n    mmssss,\n  } = useSound({\n    subscriptionDuration: 0.05, // 50ms updates\n  });\n\n  return (\n    \u003cView\u003e\n      \u003cText\u003e\n        {mmssss(Math.floor(state.currentPosition))} /{' '}\n        {mmssss(Math.floor(state.duration))}\n      \u003c/Text\u003e\n      \u003cButton\n        title=\"Play\"\n        onPress={() =\u003e startPlayer('https://example.com/audio.mp3')}\n      /\u003e\n      \u003cButton title=\"Pause\" onPress={pausePlayer} /\u003e\n      \u003cButton title=\"Resume\" onPress={resumePlayer} /\u003e\n      \u003cButton title=\"Stop\" onPress={stopPlayer} /\u003e\n      \u003cButton title=\"Seek 10s\" onPress={() =\u003e seekToPlayer(10_000)} /\u003e\n    \u003c/View\u003e\n  );\n}\n```\n\n\u003e Note: The default export remains a singleton for backward compatibility. Prefer `createSound()` and `useSound()` for new code and multiple instances.\n\n### Audio Configuration\n\nThe library automatically detects the platform and applies the appropriate settings. Use platform-specific properties (with `IOS` or `Android` suffixes) for fine-grained control, or use common properties for cross-platform consistency.\n\n#### iOS Configuration\n\n```typescript\nconst audioSet: AudioSet = {\n  // iOS-specific settings\n  AVSampleRateKeyIOS: 44100,\n  AVFormatIDKeyIOS: AVEncodingOption.aac,\n  AVEncoderAudioQualityKeyIOS: AVEncoderAudioQualityIOSType.high,\n  AVNumberOfChannelsKeyIOS: 2,\n  AVModeIOS: 'measurement', // Available options: 'gameChatAudio', 'measurement', 'moviePlayback', 'spokenAudio', 'videoChat', 'videoRecording', 'voiceChat', 'voicePrompt'\n};\n```\n\n#### Android Configuration\n\n```typescript\nconst audioSet: AudioSet = {\n  // Android-specific settings\n  AudioEncoderAndroid: AudioEncoderAndroidType.AAC,\n  AudioSourceAndroid: AudioSourceAndroidType.MIC,\n  // Common audio settings (apply on Android as well)\n  // Tip: prefer these for consistent quality\n  AudioSamplingRate: 44100,\n  AudioEncodingBitRate: 128000,\n  AudioChannels: 1,\n};\n```\n\n#### Cross-Platform Configuration\n\nFor consistent settings across platforms, use common properties that work on both iOS and Android:\n\n```typescript\nconst audioSet: AudioSet = {\n  // Common settings automatically applied to the appropriate platform\n  AudioSamplingRate: 44100,\n  AudioEncodingBitRate: 128000,\n  AudioChannels: 1,\n};\n```\n\n```typescript\nconst meteringEnabled = true; // Enable audio metering\n\nconst uri = await Sound.startRecorder(\n  undefined, // Use default path\n  audioSet,\n  meteringEnabled\n);\n```\n\n\u003e Note: Legacy Android-specific keys like `AudioSamplingRateAndroid`,\n\u003e `AudioEncodingBitRateAndroid`, and `AudioChannelsAndroid` are no longer\n\u003e supported. Use the common keys `AudioSamplingRate`, `AudioEncodingBitRate`,\n\u003e and `AudioChannels`.\n\n### Android Defaults via AudioQuality\n\nOn Android, when specific numeric values are not provided, the library applies sensible defaults based on `AudioQuality`.\n\n- `low`: `22050 Hz`, `64 kbps`, `mono`\n- `medium`: `44100 Hz`, `128 kbps`, `mono`\n- `high`: `48000 Hz`, `192 kbps`, `stereo` (default when `AudioQuality` is omitted)\n\nNotes:\n\n- If `AudioQuality` is not provided, the recorder defaults to `high`.\n- You can still override any of `AudioSamplingRate`, `AudioEncodingBitRate`, or `AudioChannels` explicitly; explicit values take precedence over `AudioQuality` defaults.\n\n## Default Path\n\n- Default path for android uri is `{cacheDir}/sound.mp4`.\n- Default path for ios uri is `{cacheDir}/sound.m4a`.\n- Default path for web: Files are stored as Blob URLs in memory.\n\n\u003e **Tip**: Store the file path returned by `startRecorder()` immediately for later use in playback or file management.\n\n## Web Platform Support\n\n### Features\n\n- Audio recording using MediaRecorder API\n- Audio playback using Web Audio API\n- Support for common audio formats (depends on browser)\n- Real-time playback progress updates\n- Volume and speed control\n\n### Limitations\n\n- Recording format is browser-dependent (typically webm/opus)\n- Some audio configuration options are not supported\n- File paths are Blob URLs instead of file system paths\n- Metering during recording is not currently supported\n\n### Browser Compatibility\n\n- Chrome/Edge: Full support\n- Firefox: Full support\n- Safari: Limited recording format support (may require polyfills)\n\n## Component-Based Implementation\n\nFor better code organization, consider separating recording and playback into separate components:\n\n### Important: Loading States\n\n\u003e **Note**: Starting from version 4.x, recording operations (start/stop) are processed in the background to prevent UI blocking. This means there's a slight delay between calling the method and the actual operation completing. **We strongly recommend implementing loading states** to provide better user experience.\n\n### AudioRecorder Component with Loading States\n\n```typescript\nimport React, { useState } from 'react';\nimport { View, Button, Text, ActivityIndicator } from 'react-native';\nimport Sound from 'react-native-nitro-sound';\n\nexport const AudioRecorder = ({ onRecordingComplete }) =\u003e {\n  const [isRecording, setIsRecording] = useState(false);\n  const [isLoading, setIsLoading] = useState(false);\n  const [recordTime, setRecordTime] = useState('00:00:00');\n\n  const onStartRecord = async () =\u003e {\n    setIsLoading(true);\n    try {\n      const result = await Sound.startRecorder();\n      Sound.addRecordBackListener((e) =\u003e {\n        setRecordTime(Sound.mmssss(Math.floor(e.currentPosition)));\n      });\n      setIsRecording(true);\n    } catch (error) {\n      console.error('Failed to start recording:', error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  const onStopRecord = async () =\u003e {\n    setIsLoading(true);\n    try {\n      const result = await Sound.stopRecorder();\n      Sound.removeRecordBackListener();\n      setIsRecording(false);\n      onRecordingComplete?.(result);\n    } catch (error) {\n      console.error('Failed to stop recording:', error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    \u003cView\u003e\n      \u003cText\u003e{recordTime}\u003c/Text\u003e\n      \u003cButton\n        title={isRecording ? 'Stop Recording' : 'Start Recording'}\n        onPress={isRecording ? onStopRecord : onStartRecord}\n        disabled={isLoading}\n      /\u003e\n      {isLoading \u0026\u0026 \u003cActivityIndicator /\u003e}\n    \u003c/View\u003e\n  );\n};\n```\n\n### AudioPlayer Component with Loading States\n\n```typescript\nimport React, { useState } from 'react';\nimport { View, Button, Text, ActivityIndicator } from 'react-native';\nimport Sound from 'react-native-nitro-sound';\n\nexport const AudioPlayer = ({ audioPath }) =\u003e {\n  const [isPlaying, setIsPlaying] = useState(false);\n  const [isLoading, setIsLoading] = useState(false);\n  const [playTime, setPlayTime] = useState('00:00:00');\n  const [duration, setDuration] = useState('00:00:00');\n\n  const onStartPlay = async () =\u003e {\n    setIsLoading(true);\n    try {\n      const msg = await Sound.startPlayer(audioPath);\n      Sound.addPlayBackListener((e) =\u003e {\n        setPlayTime(Sound.mmssss(Math.floor(e.currentPosition)));\n        setDuration(Sound.mmssss(Math.floor(e.duration)));\n      });\n\n      // Use the proper playback end listener\n      Sound.addPlaybackEndListener((e) =\u003e {\n        console.log('Playback completed', e);\n        setIsPlaying(false);\n        setPlayTime('00:00:00');\n      });\n\n      setIsPlaying(true);\n    } catch (error) {\n      console.error('Failed to start playback:', error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  const onStopPlay = async () =\u003e {\n    setIsLoading(true);\n    try {\n      await Sound.stopPlayer();\n      Sound.removePlayBackListener();\n      Sound.removePlaybackEndListener();\n      setIsPlaying(false);\n      setPlayTime('00:00:00');\n      setDuration('00:00:00');\n    } catch (error) {\n      console.error('Failed to stop playback:', error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    \u003cView\u003e\n      \u003cText\u003e{playTime} / {duration}\u003c/Text\u003e\n      \u003cButton\n        title={isPlaying ? 'Stop' : 'Play'}\n        onPress={isPlaying ? onStopPlay : onStartPlay}\n        disabled={!audioPath || isLoading}\n      /\u003e\n      {isLoading \u0026\u0026 \u003cActivityIndicator /\u003e}\n    \u003c/View\u003e\n  );\n};\n```\n\n## Example App\n\n### Running the Example\n\nBecause this repo uses a Yarn workspace, run everything from the repository root.\n\n1. Install dependencies and build the library:\n\n   ```sh\n   yarn\n   yarn prepare\n   ```\n\n2. Start the development server:\n\n   ```sh\n   yarn start\n   ```\n\n3. Run on your platform:\n\n   ```sh\n   # iOS\n   # First time on a new machine, you may need to install pods:\n   (cd example/ios \u0026\u0026 pod install)\n   yarn example ios\n\n   # Android\n   yarn example android\n   ```\n\n## Troubleshooting\n\n### iOS Recording Error: \"Unknown std::runtime_error\"\n\nIf you encounter this error when trying to record on iOS:\n\n1. **Ensure microphone permissions are properly configured** in your `Info.plist`:\n\n   ```xml\n   \u003ckey\u003eNSMicrophoneUsageDescription\u003c/key\u003e\n   \u003cstring\u003eYour app needs microphone access to record audio\u003c/string\u003e\n   ```\n\n2. **Clean and rebuild your iOS project**:\n\n   ```sh\n   cd ios\n   rm -rf build Pods\n   pod install\n   cd ..\n   yarn ios\n   ```\n\n3. **Make sure you're testing on a real device** if using the simulator doesn't work. Some audio features require real hardware.\n\n4. **Verify the Nitro modules are properly linked** by checking that the `[NitroModules] 🔥 Sound is boosted by nitro!` message appears during `pod install`.\n\n### Common Issues\n\n- **\"nitrogen\" command not found**: This command is only needed when developing the library itself, not when using it in your app.\n- **Module not found errors**: Make sure to run `pod install` after installing the package.\n- **Android build issues**: Ensure your `minSdkVersion` is 24 or higher in `android/build.gradle`.\n  - If you see `:react-native:generateCodegenSchemaFromJavaScript` failing, this comes from RN's Gradle plugin (not Nitro). Ensure RN \u003e= 0.79 (0.81 recommended) and JDK 17, then align and clean:\n\n    ```sh\n    npx @rnx-kit/align-deps --requirements react-native@0.81 --write\n    rm -rf node_modules android/.gradle\n    yarn\n    cd android \u0026\u0026 ./gradlew clean assembleDebug\n    ```\n\n### Clean Build Instructions\n\nIf you're experiencing build issues or runtime errors after updating the library:\n\n#### iOS Clean Build\n\n```sh\ncd ios\nrm -rf ~/Library/Caches/CocoaPods\nrm -rf Pods\nrm -rf ~/Library/Developer/Xcode/DerivedData/*\npod cache clean --all\npod install\ncd ..\n```\n\nThen in Xcode:\n\n1. Product → Clean Build Folder (⇧⌘K)\n2. Product → Build (⌘B)\n\n#### Android Clean Build\n\n```sh\ncd android\n./gradlew clean\nrm -rf ~/.gradle/caches/\ncd ..\n```\n\nThen rebuild:\n\n```sh\nyarn android\n# or\nnpx react-native run-android\n```\n\n#### Both Platforms\n\nYou can also try resetting Metro cache:\n\n```sh\nnpx react-native start --reset-cache\n```\n\n## Contributing\n\nSee the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyochan%2Freact-native-nitro-sound","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhyochan%2Freact-native-nitro-sound","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyochan%2Freact-native-nitro-sound/lists"}