Home
Softono
h

hyochan

Professional software vendor delivering innovative solutions on the Softono platform. Specialized in both open-source and proprietary software development.

Total Products
2

Software by hyochan

flutter_calendar_carousel
Open Source

flutter_calendar_carousel

# flutter_calendar_carousel [![Pub Version](https://img.shields.io/pub/v/flutter_calendar_carousel.svg?style=flat-square)](https://pub.dartlang.org/packages/flutter_calendar_carousel) [![Flutter CI](https://github.com/hyochan/flutter_calendar_carousel/actions/workflows/ci.yml/badge.svg)](https://github.com/hyochan/flutter_calendar_carousel/actions/workflows/ci.yml) [![Coverage Status](https://codecov.io/gh/hyochan/flutter_calendar_carousel/branch/master/graph/badge.svg?token=KTrSs3fGsS)](https://codecov.io/gh/hyochan/flutter_calendar_carousel) ![License](https://img.shields.io/badge/license-MIT-blue.svg) Calendar widget for flutter that is swipeable horizontally. This widget can help you build your own calendar widget highly customizable. Now you can even add your icon for each event. ## Notice This widget is compatible with flutter V3 from version `2.4.+`. ## New Feature [2.0.3] - Multiple days selection using `addRange` method [#285](https://github.com/hyochan/flutter_calendar_carousel/pull/285) - Check out great feature `customDayBuilder` work done by [maxgmer](https://github.com/maxgmer) :tada:. #### Rectangular style ![image](https://raw.githubusercontent.com/hyochan/flutter_calendar_carousel/master/doc/calendar1.gif) #### Circular style ![image](https://raw.githubusercontent.com/hyochan/flutter_calendar_carousel/master/doc/calendar2.gif) #### No border ![image](https://raw.githubusercontent.com/hyochan/flutter_calendar_carousel/master/doc/calendar3.gif) #### Marked Dates ![image](https://raw.githubusercontent.com/hyochan/flutter_calendar_carousel/master/doc/calendar4.gif) #### Custom Icon Events ![image](https://raw.githubusercontent.com/icemanbsi/flutter_calendar_carousel/master/doc/calendar5.gif) ## Getting Started For help getting started with Flutter, view our online [documentation](https://flutter.io/). ## Props | props | types | defaultValues | | :----------------------------- | :------------------------: | :---------------------------------------------------------------------------------------------------------: | | viewPortFraction | `double` | 1.0 | | prevDaysTextStyle | `TextStyle` | | | daysTextStyle | `TextStyle` | | | nextDaysTextStyle | `TextStyle` | | | prevMonthDayBorderColor | `Color` | Colors.transparent | | thisMonthDayBorderColor | `Color` | Colors.transparent | | nextMonthDayBorderColor | `Color` | Colors.transparent | | dayPadding | `double` | 2.0 | | height | `double` | double.infinity | | width | `double` | double.infinity | | todayTextStyle | `TextStyle` | `fontSize: 14.0, color: Colors.white` | | dayButtonColor | `Color` | Colors.red | | todayBorderColor | `Color` | Colors.red | | todayButtonColor | `Colors` | Colors.red | | selectedDateTime | `DateTime` | | | selectedDayTextStyle | `TextStyle` | `fontSize: 14.0, color: Colors.white` | | selectedDayBorderColor | `Color` | Colors.green | | selectedDayButtonColor | `Color` | Colors.green | | daysHaveCircularBorder | `bool` | | | onDayPressed | `Func` | | | weekdayTextStyle | `TextStyle` | `fontSize: 14.0, color: Colors.deepOrange` | | iconColor | `Color` | Colors.blueAccent | | headerTextStyle | `TextStyle` | `fontSize: 20.0, color: Colors.blue` | | headerText | `Text` | `Text('${DateFormat.yMMM().format(this._dates[1])}'`) | | weekendTextStyle | `TextStyle` | `fontSize: 14.0, color: Colors.pinkAccent` | | markedDatesMap | `Events` | `null` | | markedDateWidget | `Widget` | `Positioned(child: Container(color: Colors.blueAccent, height: 4.0, width: 4.0), bottom: 4.0, left: 18.0);` | | markedDateShowIcon | `bool` | false | | markedDateIconBorderColor | `Color` | | | markedDateIconMaxShown | `int` | 2 | | markedDateIconMargin | `double` | 5.0 | | markedDateIconBuilder | `MarkedDateIconBuilder<T>` | | | markedDateIconOffset | `double` | 5.0 | | markedDateCustomShapeBorder | `ShapeBorder` | null | | markedDateCustomTextStyle | `TextStyle` | null | | markedDateMoreCustomDecoration | `Decoration` | | | markedDateMoreCustomTextStyle | `TextStyle` | | | headerMargin | `EdgetInsets` | `const EdgeInsets.symmetric(vertical: 16.0)` | | headerTitleTouchable | `bool` | `false` | | onHeaderTitlePressed | `Function` | `() => _selectDateFromPicker()` | | showHeader | `bool` | | | showHeaderButton | `bool` | | | childAspectRatio | `double` | `1.0` | | weekDayMargin | `EdgeInsets` | `const EdgeInsets.only(bottom: 4.0)` | | weekFormat | `bool` | `false` | | locale | `String` | `en` | | firstDayOfWeek | `int` | `null` | | onCalendarChanged | `Function(DateTime)` | | | minSelectedDate | `DateTime` | | | maxSelectedDate | `DateTime` | | | inactiveDaysTextStyle | `TextStyle` | | | inactiveWeekendTextStyle | `TextStyle` | | | weekDayFormat | `WeekdayFormat` | `short` | | staticSixWeekFormat | `bool` | `false` | | showOnlyCurrentMonthDate | `bool` | `false` | | dayCrossAxisAlignment | `CrossAxisAlignment` | `CrossAxisAlignment.center` | | dayMainAxisAlignment | `MainAxisAlignment` | `CrossAlignment.center` | | showIconBehindDayText | `bool` | `false` | | pageScrollPhysics | `ScrollPhysics` | `ScrollPhysics` | With `CalendarCarousel<YourEventClass>` and `EventList<YourEventClass>` you can specifiy a custom Event class. ## Install Add `flutter_calendar_carousel` as a dependency in pubspec.yaml For help on adding as a dependency, view the [documentation](https://flutter.io/using-packages/). ## Usage ```dart import 'package:flutter_calendar_carousel/flutter_calendar_carousel.dart' show CalendarCarousel; Widget widget() { return Container( margin: EdgeInsets.symmetric(horizontal: 16.0), child: CalendarCarousel<Event>( onDayPressed: (DateTime date, List<Event> events) { this.setState(() => _currentDate = date); }, weekendTextStyle: TextStyle( color: Colors.red, ), thisMonthDayBorderColor: Colors.grey, // weekDays: null, /// for pass null when you do not want to render weekDays // headerText: Container( /// Example for rendering custom header // child: Text('Custom Header'), // ), customDayBuilder: ( /// you can provide your own build function to make custom day containers bool isSelectable, int index, bool isSelectedDay, bool isToday, bool isPrevMonthDay, TextStyle textStyle, bool isNextMonthDay, bool isThisMonthDay, DateTime day, ) { /// If you return null, [CalendarCarousel] will build container for current [day] with default function. /// This way you can build custom containers for specific days only, leaving rest as default. // Example: every 15th of month, we have a flight, we can place an icon in the container like that: if (day.day == 15) { return Center( child: Icon(Icons.local_airport), ); } else { return null; } }, weekFormat: false, markedDatesMap: _markedDateMap, height: 420.0, selectedDateTime: _currentDate, daysHaveCircularBorder: false, /// null for not rendering any border, true for circular border, false for rectangular border ), ); } ``` ### TODO - [x] Render weekdays. - [x] Customizable headerWidget. - [x] Set weekdays visibility. - [x] Customizable textStyles for days in weekend. - [x] Marked Dates. - [x] Multiple Marked Dates. - [x] Customizable weekend days. - [x] Week Calendar. - [x] Carousel Week Calendar. - [ ] Multiple days selections. - [x] Widget test. ## Help Maintenance I've been maintaining quite many repos these days and burning out slowly. If you could help me cheer up, buying me a cup of coffee will make my life really happy and get much energy out of it. <br/> <a href="https://www.buymeacoffee.com/dooboolab" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/purple_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a> [![Paypal](https://www.paypalobjects.com/webstatic/mktg/Logo/pp-logo-100px.png)](https://paypal.me/dooboolab)

Web Components & Widgets
861 Github Stars
react-native-nitro-sound
Open Source

react-native-nitro-sound

# react-native-nitro-sound [![yarn Version](http://img.shields.io/npm/v/react-native-nitro-sound.svg?style=flat-square)](https://npmjs.org/package/react-native-nitro-sound) [![Monthly Downloads](http://img.shields.io/npm/dm/react-native-nitro-sound.svg?style=flat-square)](https://npmjs.org/package/react-native-nitro-sound) [![Weekly Downloads](http://img.shields.io/npm/dw/react-native-nitro-sound.svg?style=flat-square)](https://npmjs.org/package/react-native-nitro-sound) [![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) [![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) [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg?style=flat-square)](https://www.typescriptlang.org/) [![MIT License](http://img.shields.io/npm/l/react-native-nitro-sound.svg?style=flat-square)](https://npmjs.org/package/react-native-nitro-sound) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) [![Platform - iOS](https://img.shields.io/badge/platform-iOS-blue.svg?style=flat-square&logo=apple&logoColor=white)](https://developer.apple.com/ios/) [![Platform - Android](https://img.shields.io/badge/platform-Android-green.svg?style=flat-square&logo=android&logoColor=white)](https://developer.android.com/) [![Platform - Web](https://img.shields.io/badge/platform-Web-orange.svg?style=flat-square&logo=googlechrome&logoColor=white)](https://reactnative.dev/docs/react-native-web) [![GreatFrontEnd](greatfrontend-js.gif)](https://www.greatfrontend.com?fpr=hyo73&fp_sid=rnsound) > πŸ€– **This repository is now AI-native maintained.** Hyo is juggling a lot of open-source repos with very limited time (currently focused on [hyodotdev/openiap](https://github.com/hyodotdev/openiap)), so day-to-day triage, bug fixes, and dependency bumps here are handled by Claude on his behalf. Commits and pull requests labeled `πŸ€– Autogenerated by Claude` are produced through the `.claude/commands/` skills in this repo and the `.github/workflows/ai-dispatch.yml` workflow. If you see something off with an AI-generated change, please comment on the issue or PR β€” Hyo still reviews direction, and humans are always welcome to send PRs. > > ➑️ To nudge the AI maintainer on an issue from the GitHub mobile app, just comment `/ai resolve-issue` on the issue. Supported commands: `/ai resolve-issue`, `/ai upgrade-deps`, `/ai compile-knowledge`, `/ai triage-all`. > ℹ️ **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). <img src="https://github.com/user-attachments/assets/81ce7b7b-0b7d-413b-8a26-505372349ecb" width="70%" alt="Logo" /> ## Legacy Package (react-native-audio-recorder-player) [![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) [![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) [![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) ## React Native Nitro Sound πŸš€ **React Native Nitro Sound** is a high-performance audio recording and playback library built with [NitroModules](https://github.com/mrousavy/nitro), offering: - **Zero Bridge Overhead**: Direct native module access for maximum performance - **Full Type Safety**: TypeScript definitions generated from native specs - **Synchronous Methods**: Where appropriate, for better developer experience - **Event Listeners**: Native callbacks with type-safe event payloads - **Cross-Platform Code Generation**: Automatic code generation for iOS (Swift) and Android (Kotlin) - **Background Processing**: Recording operations now run in background threads to prevent UI blocking, requiring loading state management - **Web Platform Support**: Full support for web browsers using Web Audio API and MediaRecorder API ### Requirements - React Native: >= 0.79 (0.82 recommended) - iOS: Deployment Target >= 13.0 - Note: With RN 0.81+, build using Xcode >= 16.1 (toolchain requirement; iOS runtime minimum remains 13.0) - Android: minSdk >= 24 (JDK 17 recommended; compileSdk 36 recommended) - Note: RN 0.82+ requires Gradle 9.0+ - New Architecture: optional (Nitro works on both old and new arch; RN 0.82+ is New Architecture only) - Expo SDK >= 50 (for Expo users) ## πŸŽ‰ React Native Nitro Sound - Reborn from React Native Audio Recorder Player **React Native Nitro Sound** is the reborn version of [React Native Audio Recorder Player](https://www.npmjs.com/package/react-native-audio-recorder-player)! For 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**. As 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. Starting 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! πŸ™ Your continued support and interest mean the world to us! --- This 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. > πŸ”΄ **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. ## Help Maintenance This 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! <a href="https://www.buymeacoffee.com/hyochan" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/purple_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a> [![Paypal](https://www.paypalobjects.com/webstatic/mktg/Logo/pp-logo-100px.png)](https://paypal.me/dooboolab) ## Preview [Web Demo](https://hyochan.github.io/react-native-nitro-sound/) <img src="https://github.com/user-attachments/assets/02545c15-f41d-4186-be79-2a13fd67ccb2" width=800 alt="Screenshot"/> <img src="https://github.com/user-attachments/assets/6d8a5a80-cd31-450f-b410-9b8f42ae8d03" width=800 alt="Screenshot2"/> ## Documentation & Resources - πŸ”§ [NitroModules Documentation](https://github.com/mrousavy/nitro) - Learn about the underlying technology - πŸ“ [Version 3 Release Note](https://medium.com/dooboolab/react-native-audio-player-and-recorder-v3-7697e25cd07) - πŸ“° [Original Blog Post](https://medium.com/@dooboolab/react-native-audio-recorder-and-player-4aa5f26a666) ## Migration from react-native-audio-recorder-player If 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: ```diff - import AudioRecorderPlayer from 'react-native-audio-recorder-player'; + import Sound from 'react-native-nitro-sound'; ``` ## Getting started 1. **Install packages**: ```sh yarn add react-native-nitro-sound react-native-nitro-modules ``` Or using npm: ```sh npm install react-native-nitro-sound react-native-nitro-modules ``` 2. **Align React Native dependencies (recommended)**: ```sh npx @rnx-kit/align-deps --requirements [email protected] --write ``` ## Post Installation After installing the packages, follow these steps: 1. **iOS Setup**: ```sh npx pod-install ``` - If resolution fails, try `npx pod-install --repo-update`. - RN 0.81+ requires Xcode >= 16.1 to build. 2. **Android Setup**: No additional steps required. The module uses autolinking. 3. **Web Setup**: For React Native Web, install the additional dependency: ```sh yarn add react-native-web ``` Then configure your webpack to include the web-specific implementation: ```js // webpack.config.js module.exports = { resolve: { alias: { 'react-native': 'react-native-web', }, }, }; ``` > **Note**: The `nitrogen` command is already run during the library's build process. You don't need to run it in your application. ## Platform-specific Configuration ### iOS Configuration 1. **Microphone Permission**: Add to your `Info.plist`: ```xml <key>NSMicrophoneUsageDescription</key> <string>Give $(PRODUCT_NAME) permission to use your microphone. Your record wont be shared without your permission.</string> ``` 2. **Minimum iOS Version**: Ensure your minimum deployment target is iOS 13.0 or higher in your `Podfile`: ```ruby platform :ios, '13.0' ``` ### Android Configuration On _Android_ you need to add permissions to `AndroidManifest.xml`: ```xml <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> ``` Also, android above `Marshmallow` needs runtime permission to record audio. Below are two approaches: **Minimal Approach (Recommended for Android 13+):** ```ts if (Platform.OS === 'android') { try { const granted = await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, { title: 'Audio Recording Permission', message: 'This app needs access to your microphone to record audio.', buttonNeutral: 'Ask Me Later', buttonNegative: 'Cancel', buttonPositive: 'OK', } ); if (granted === PermissionsAndroid.RESULTS.GRANTED) { console.log('Recording permission granted'); } else { console.log('Recording permission denied'); return; } } catch (err) { console.warn(err); return; } } ``` **Full Permissions Approach (For older Android versions):** ```ts if (Platform.OS === 'android') { try { const grants = await PermissionsAndroid.requestMultiple([ PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, ]); if ( grants['android.permission.WRITE_EXTERNAL_STORAGE'] === PermissionsAndroid.RESULTS.GRANTED && grants['android.permission.READ_EXTERNAL_STORAGE'] === PermissionsAndroid.RESULTS.GRANTED && grants['android.permission.RECORD_AUDIO'] === PermissionsAndroid.RESULTS.GRANTED ) { console.log('All permissions granted'); } else { console.log('All required permissions not granted'); return; } } catch (err) { console.warn(err); return; } } ``` ## Methods | Method | Param | Return | Description | | :------------------------ | :----------------------------------------------: | :---------------: | :---------------------------------------------------- | | mmss | `number` seconds | `string` | Convert seconds to `minute:second` string | | mmssss | `number` seconds | `string` | Convert seconds to `minute:second:millisecond` string | | setSubscriptionDuration | `number` duration | `void` | Set callback interval in ms (default 500ms) | | startRecorder | `string?` uri, `AudioSet?` audioSet, | `Promise<string>` | Start recording with optional path and audio settings | | | `boolean?` meteringEnabled | | | | pauseRecorder | | `Promise<string>` | Pause recording | | resumeRecorder | | `Promise<string>` | Resume recording | | stopRecorder | | `Promise<string>` | Stop recording and return file path | | startPlayer | `string?` uri, `Record<string, string>?` headers | `Promise<string>` | Start playback with optional URI and HTTP headers | | stopPlayer | | `Promise<string>` | Stop playback | | pausePlayer | | `Promise<string>` | Pause playback | | resumePlayer | | `Promise<string>` | Resume playback | | seekToPlayer | `number` milliseconds | `Promise<string>` | Seek to position in milliseconds | | setVolume | `number` value | `Promise<string>` | Set volume (0.0 - 1.0) | | setPlaybackSpeed | `number` speed | `Promise<string>` | Set playback speed (0.5 - 2.0) | | addRecordBackListener | `Function` callback | `void` | Add recording progress listener | | removeRecordBackListener | | `void` | Remove recording progress listener | | addPlayBackListener | `Function` callback | `void` | Add playback progress listener | | removePlayBackListener | | `void` | Remove playback progress listener | | addPlaybackEndListener | `Function` callback | `void` | Add playback completion listener | | removePlaybackEndListener | | `void` | Remove playback completion listener | ## Usage ### Basic Usage ```typescript import Sound, { AudioEncoderAndroidType, AudioSourceAndroidType, AVEncoderAudioQualityIOSType, AVEncodingOption, RecordBackType, PlayBackType, } from 'react-native-nitro-sound'; // Sound is a singleton instance, use directly // Recording const onStartRecord = async () => { // Set up recording progress listener Sound.addRecordBackListener((e: RecordBackType) => { console.log('Recording progress:', e.currentPosition, e.currentMetering); setRecordSecs(e.currentPosition); setRecordTime(Sound.mmssss(Math.floor(e.currentPosition))); }); const result = await Sound.startRecorder(); console.log('Recording started:', result); }; const onStopRecord = async () => { const result = await Sound.stopRecorder(); Sound.removeRecordBackListener(); console.log('Recording stopped:', result); }; // Pause/Resume Recording const onPauseRecord = async () => { await Sound.pauseRecorder(); console.log('Recording paused'); }; const onResumeRecord = async () => { await Sound.resumeRecorder(); console.log('Recording resumed'); }; // Playback const onStartPlay = async () => { // Set up playback progress listener Sound.addPlayBackListener((e: PlayBackType) => { console.log('Playback progress:', e.currentPosition, e.duration); setCurrentPosition(e.currentPosition); setTotalDuration(e.duration); setPlayTime(Sound.mmssss(Math.floor(e.currentPosition))); setDuration(Sound.mmssss(Math.floor(e.duration))); }); // Set up playback end listener Sound.addPlaybackEndListener((e: PlaybackEndType) => { console.log('Playback completed:', e); // Handle playback completion setIsPlaying(false); setCurrentPosition(0); }); const result = await Sound.startPlayer(); console.log('Playback started:', result); }; const onPausePlay = async () => { await Sound.pausePlayer(); }; const onStopPlay = async () => { Sound.stopPlayer(); Sound.removePlayBackListener(); Sound.removePlaybackEndListener(); }; // Seeking const seekTo = async (milliseconds: number) => { await Sound.seekToPlayer(milliseconds); }; // Volume control const setVolume = async (volume: number) => { await Sound.setVolume(volume); // 0.0 - 1.0 }; // Speed control const setSpeed = async (speed: number) => { await Sound.setPlaybackSpeed(speed); // 0.5 - 2.0 }; ``` ### Modern API: Multiple Instances ```tsx import { createSound } from 'react-native-nitro-sound'; // Create independent instances (recorder/player per instance) const soundA = createSound(); const soundB = createSound(); await soundA.startPlayer('https://example.com/a.mp3'); await soundB.startPlayer('https://example.com/b.mp3'); // Control them independently await soundA.pausePlayer(); await soundB.setVolume(0.5); // Clean up when done soundA.dispose(); soundB.dispose(); ``` ### React Hook API ```tsx import { useSound } from 'react-native-nitro-sound'; export function Player() { const { sound, state, startPlayer, pausePlayer, resumePlayer, stopPlayer, seekToPlayer, mmssss, } = useSound({ subscriptionDuration: 0.05, // 50ms updates }); return ( <View> <Text> {mmssss(Math.floor(state.currentPosition))} /{' '} {mmssss(Math.floor(state.duration))} </Text> <Button title="Play" onPress={() => startPlayer('https://example.com/audio.mp3')} /> <Button title="Pause" onPress={pausePlayer} /> <Button title="Resume" onPress={resumePlayer} /> <Button title="Stop" onPress={stopPlayer} /> <Button title="Seek 10s" onPress={() => seekToPlayer(10_000)} /> </View> ); } ``` > Note: The default export remains a singleton for backward compatibility. Prefer `createSound()` and `useSound()` for new code and multiple instances. ### Audio Configuration The 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. #### iOS Configuration ```typescript const audioSet: AudioSet = { // iOS-specific settings AVSampleRateKeyIOS: 44100, AVFormatIDKeyIOS: AVEncodingOption.aac, AVEncoderAudioQualityKeyIOS: AVEncoderAudioQualityIOSType.high, AVNumberOfChannelsKeyIOS: 2, AVModeIOS: 'measurement', // Available options: 'gameChatAudio', 'measurement', 'moviePlayback', 'spokenAudio', 'videoChat', 'videoRecording', 'voiceChat', 'voicePrompt' }; ``` #### Android Configuration ```typescript const audioSet: AudioSet = { // Android-specific settings AudioEncoderAndroid: AudioEncoderAndroidType.AAC, AudioSourceAndroid: AudioSourceAndroidType.MIC, // Common audio settings (apply on Android as well) // Tip: prefer these for consistent quality AudioSamplingRate: 44100, AudioEncodingBitRate: 128000, AudioChannels: 1, }; ``` #### Cross-Platform Configuration For consistent settings across platforms, use common properties that work on both iOS and Android: ```typescript const audioSet: AudioSet = { // Common settings automatically applied to the appropriate platform AudioSamplingRate: 44100, AudioEncodingBitRate: 128000, AudioChannels: 1, }; ``` ```typescript const meteringEnabled = true; // Enable audio metering const uri = await Sound.startRecorder( undefined, // Use default path audioSet, meteringEnabled ); ``` > Note: Legacy Android-specific keys like `AudioSamplingRateAndroid`, > `AudioEncodingBitRateAndroid`, and `AudioChannelsAndroid` are no longer > supported. Use the common keys `AudioSamplingRate`, `AudioEncodingBitRate`, > and `AudioChannels`. ### Android Defaults via AudioQuality On Android, when specific numeric values are not provided, the library applies sensible defaults based on `AudioQuality`. - `low`: `22050 Hz`, `64 kbps`, `mono` - `medium`: `44100 Hz`, `128 kbps`, `mono` - `high`: `48000 Hz`, `192 kbps`, `stereo` (default when `AudioQuality` is omitted) Notes: - If `AudioQuality` is not provided, the recorder defaults to `high`. - You can still override any of `AudioSamplingRate`, `AudioEncodingBitRate`, or `AudioChannels` explicitly; explicit values take precedence over `AudioQuality` defaults. ## Default Path - Default path for android uri is `{cacheDir}/sound.mp4`. - Default path for ios uri is `{cacheDir}/sound.m4a`. - Default path for web: Files are stored as Blob URLs in memory. > **Tip**: Store the file path returned by `startRecorder()` immediately for later use in playback or file management. ## Web Platform Support ### Features - Audio recording using MediaRecorder API - Audio playback using Web Audio API - Support for common audio formats (depends on browser) - Real-time playback progress updates - Volume and speed control ### Limitations - Recording format is browser-dependent (typically webm/opus) - Some audio configuration options are not supported - File paths are Blob URLs instead of file system paths - Metering during recording is not currently supported ### Browser Compatibility - Chrome/Edge: Full support - Firefox: Full support - Safari: Limited recording format support (may require polyfills) ## Component-Based Implementation For better code organization, consider separating recording and playback into separate components: ### Important: Loading States > **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. ### AudioRecorder Component with Loading States ```typescript import React, { useState } from 'react'; import { View, Button, Text, ActivityIndicator } from 'react-native'; import Sound from 'react-native-nitro-sound'; export const AudioRecorder = ({ onRecordingComplete }) => { const [isRecording, setIsRecording] = useState(false); const [isLoading, setIsLoading] = useState(false); const [recordTime, setRecordTime] = useState('00:00:00'); const onStartRecord = async () => { setIsLoading(true); try { const result = await Sound.startRecorder(); Sound.addRecordBackListener((e) => { setRecordTime(Sound.mmssss(Math.floor(e.currentPosition))); }); setIsRecording(true); } catch (error) { console.error('Failed to start recording:', error); } finally { setIsLoading(false); } }; const onStopRecord = async () => { setIsLoading(true); try { const result = await Sound.stopRecorder(); Sound.removeRecordBackListener(); setIsRecording(false); onRecordingComplete?.(result); } catch (error) { console.error('Failed to stop recording:', error); } finally { setIsLoading(false); } }; return ( <View> <Text>{recordTime}</Text> <Button title={isRecording ? 'Stop Recording' : 'Start Recording'} onPress={isRecording ? onStopRecord : onStartRecord} disabled={isLoading} /> {isLoading && <ActivityIndicator />} </View> ); }; ``` ### AudioPlayer Component with Loading States ```typescript import React, { useState } from 'react'; import { View, Button, Text, ActivityIndicator } from 'react-native'; import Sound from 'react-native-nitro-sound'; export const AudioPlayer = ({ audioPath }) => { const [isPlaying, setIsPlaying] = useState(false); const [isLoading, setIsLoading] = useState(false); const [playTime, setPlayTime] = useState('00:00:00'); const [duration, setDuration] = useState('00:00:00'); const onStartPlay = async () => { setIsLoading(true); try { const msg = await Sound.startPlayer(audioPath); Sound.addPlayBackListener((e) => { setPlayTime(Sound.mmssss(Math.floor(e.currentPosition))); setDuration(Sound.mmssss(Math.floor(e.duration))); }); // Use the proper playback end listener Sound.addPlaybackEndListener((e) => { console.log('Playback completed', e); setIsPlaying(false); setPlayTime('00:00:00'); }); setIsPlaying(true); } catch (error) { console.error('Failed to start playback:', error); } finally { setIsLoading(false); } }; const onStopPlay = async () => { setIsLoading(true); try { await Sound.stopPlayer(); Sound.removePlayBackListener(); Sound.removePlaybackEndListener(); setIsPlaying(false); setPlayTime('00:00:00'); setDuration('00:00:00'); } catch (error) { console.error('Failed to stop playback:', error); } finally { setIsLoading(false); } }; return ( <View> <Text>{playTime} / {duration}</Text> <Button title={isPlaying ? 'Stop' : 'Play'} onPress={isPlaying ? onStopPlay : onStartPlay} disabled={!audioPath || isLoading} /> {isLoading && <ActivityIndicator />} </View> ); }; ``` ## Example App ### Running the Example Because this repo uses a Yarn workspace, run everything from the repository root. 1. Install dependencies and build the library: ```sh yarn yarn prepare ``` 2. Start the development server: ```sh yarn start ``` 3. Run on your platform: ```sh # iOS # First time on a new machine, you may need to install pods: (cd example/ios && pod install) yarn example ios # Android yarn example android ``` ## Troubleshooting ### iOS Recording Error: "Unknown std::runtime_error" If you encounter this error when trying to record on iOS: 1. **Ensure microphone permissions are properly configured** in your `Info.plist`: ```xml <key>NSMicrophoneUsageDescription</key> <string>Your app needs microphone access to record audio</string> ``` 2. **Clean and rebuild your iOS project**: ```sh cd ios rm -rf build Pods pod install cd .. yarn ios ``` 3. **Make sure you're testing on a real device** if using the simulator doesn't work. Some audio features require real hardware. 4. **Verify the Nitro modules are properly linked** by checking that the `[NitroModules] πŸ”₯ Sound is boosted by nitro!` message appears during `pod install`. ### Common Issues - **"nitrogen" command not found**: This command is only needed when developing the library itself, not when using it in your app. - **Module not found errors**: Make sure to run `pod install` after installing the package. - **Android build issues**: Ensure your `minSdkVersion` is 24 or higher in `android/build.gradle`. - If you see `:react-native:generateCodegenSchemaFromJavaScript` failing, this comes from RN's Gradle plugin (not Nitro). Ensure RN >= 0.79 (0.81 recommended) and JDK 17, then align and clean: ```sh npx @rnx-kit/align-deps --requirements [email protected] --write rm -rf node_modules android/.gradle yarn cd android && ./gradlew clean assembleDebug ``` ### Clean Build Instructions If you're experiencing build issues or runtime errors after updating the library: #### iOS Clean Build ```sh cd ios rm -rf ~/Library/Caches/CocoaPods rm -rf Pods rm -rf ~/Library/Developer/Xcode/DerivedData/* pod cache clean --all pod install cd .. ``` Then in Xcode: 1. Product β†’ Clean Build Folder (β‡§βŒ˜K) 2. Product β†’ Build (⌘B) #### Android Clean Build ```sh cd android ./gradlew clean rm -rf ~/.gradle/caches/ cd .. ``` Then rebuild: ```sh yarn android # or npx react-native run-android ``` #### Both Platforms You can also try resetting Metro cache: ```sh npx react-native start --reset-cache ``` ## Contributing See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow. ## License MIT

Mobile Development Audio Editing & DAW
950 Github Stars