├── .circleci └── config.yml ├── .clang-format ├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── 1_Get_help.md │ ├── 2_Bug_report.md │ └── 3_Feature_request.md ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── RNSound.podspec ├── RNSound └── RNSound.m ├── android ├── build.gradle ├── gradle.properties └── src │ ├── main │ ├── AndroidManifest.xml │ ├── AndroidManifestNew.xml │ └── java │ │ └── com │ │ └── zmxv │ │ └── RNSound │ │ ├── Sound.kt │ │ ├── SoundModule.kt │ │ └── SoundPackage.kt │ ├── newarch │ └── SoundSpec.kt │ └── oldarch │ └── SoundSpec.kt ├── example ├── .bundle │ └── config ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── .watchmanconfig ├── App.tsx ├── Gemfile ├── Gemfile.lock ├── README.md ├── SoundPlayer.tsx ├── __tests__ │ └── App.test.tsx ├── android │ ├── app │ │ ├── build.gradle │ │ ├── debug.keystore │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ ├── MainActivity.kt │ │ │ │ └── MainApplication.kt │ │ │ └── res │ │ │ ├── drawable │ │ │ └── rn_edit_text_material.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── app.json ├── assets │ ├── back.png │ ├── favicon.png │ ├── icon.png │ ├── left_foward.png │ ├── logo.jpg │ ├── menu.png │ ├── pause.png │ ├── play.png │ ├── right_foward.png │ └── splash.png ├── babel.config.js ├── index.js ├── ios │ ├── .xcode.env │ ├── Podfile │ ├── Podfile.lock │ ├── example.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── example.xcscheme │ ├── example.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── example │ │ ├── AppDelegate.h │ │ ├── AppDelegate.mm │ │ ├── Images.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── LaunchScreen.storyboard │ │ ├── PrivacyInfo.xcprivacy │ │ └── main.m │ └── exampleTests │ │ ├── Info.plist │ │ └── exampleTests.m ├── jest.config.js ├── metro.config.js ├── package.json ├── tsconfig.json └── yarn.lock ├── ios ├── RNSound.h └── RNSound.mm ├── package.json ├── sound.js ├── src ├── NativeSoundAndroid.ts ├── NativeSoundIOS.ts ├── index.d.ts └── index.ts └── windows └── RNSoundModule ├── .gitignore ├── RNSoundModule.sln └── RNSoundModule ├── Properties ├── AssemblyInfo.cs └── RNSoundModule.rd.xml ├── RNSound.cs ├── RNSoundModule.csproj ├── RNSoundPackage.cs └── project.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | clang-format: 5 | docker: 6 | - image: circleci/node:latest 7 | steps: 8 | - checkout 9 | - run: 10 | name: Install container dependencies 11 | # https://apt.llvm.org/ 12 | command: | 13 | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - 14 | sudo apt-get install software-properties-common # Get add-apt-repository 15 | sudo add-apt-repository "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-8 main" 16 | sudo apt-get update -y 17 | sudo apt-get install -y clang-format-8 18 | sudo ln -s /usr/bin/clang-format-8 /usr/bin/clang-format 19 | clang-format --version 20 | - run: 21 | name: Install npm dependencies 22 | command: yarn 23 | - run: 24 | name: Check clang-format 25 | command: yarn clang-format:check 26 | 27 | 28 | workflows: 29 | version: 2 30 | build-test-lint: 31 | jobs: 32 | - clang-format 33 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | IndentWidth: 4 3 | ObjCSpaceAfterProperty: true 4 | 5 | # Looks like this option is coming, but isn't ready yet. 6 | # https://reviews.llvm.org/rL356613 7 | #IndentPPDirectives: BeforeHash 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1_Get_help.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ❓Get help 3 | about: Get help using react-native-sound in your project 4 | labels: 'question' 5 | --- 6 | 7 | :beetle: **Description** 8 | 9 | 10 | 11 | :beetle: **What have you tried?** 12 | 13 | 14 | 15 | :beetle: **Please post your code:** 16 | 17 | ```js 18 | // Please post your code 19 | ``` 20 | 21 | :bulb: **Possible solution** 22 | 23 | 24 | 25 | **Is your issue with...** 26 | 27 | - [ ] iOS 28 | - [ ] Android 29 | - [ ] Windows 30 | 31 | **Are you using...** 32 | 33 | - [ ] React Native CLI (e.g. `react-native run-android`) 34 | - [ ] Expo 35 | - [ ] Other: (please specify) 36 | 37 | **Which versions are you using?** 38 | 39 | - React Native Sound: 40 | - React Native: 41 | - iOS: 42 | - Android: 43 | - Windows: 44 | 45 | **Does the problem occur on...** 46 | 47 | - [ ] Simulator 48 | - [ ] Device 49 | 50 | **If your problem is happening on a device, which device?** 51 | 52 | - Device: 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2_Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | about: Report errors and problems 4 | labels: 'question' 5 | --- 6 | 7 | :beetle: **Description** 8 | 9 | 10 | 11 | :beetle: **What is the observed behavior?** 12 | 13 | 14 | 15 | :beetle: **What is the expected behavior?** 16 | 17 | 18 | 19 | :beetle: **Please post your code:** 20 | 21 | ```js 22 | // Please post your code 23 | ``` 24 | 25 | :bulb: **Does the problem have a test case?** 26 | 27 | 29 | 30 | :bulb: **Possible solution** 31 | 32 | 33 | 34 | :bulb: **Is there a workaround?** 35 | 36 | 37 | 38 | :bulb: **If the bug is confirmed, would you be willing to create a pull request?** 39 | 40 | 41 | 42 | **Is your issue with...** 43 | 44 | - [ ] iOS 45 | - [ ] Android 46 | - [ ] Windows 47 | 48 | **Are you using...** 49 | 50 | - [ ] React Native CLI (e.g. `react-native run-android`) 51 | - [ ] Expo 52 | - [ ] Other: (please specify) 53 | 54 | **Which versions are you using?** 55 | 56 | - React Native Sound: 57 | - React Native: 58 | - iOS: 59 | - Android: 60 | - Windows: 61 | 62 | **Does the problem occur on...** 63 | 64 | - [ ] Simulator 65 | - [ ] Device 66 | 67 | **If your problem is happening on a device, which device?** 68 | 69 | - Device: 70 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3_Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💡 Feature request 3 | about: Ideas for new features or improvements 4 | --- 5 | 6 | :clipboard: **Description** 7 | 8 | 9 | 10 | :microphone: **Motivation** 11 | 12 | 17 | 18 | :bulb: **Alternatives** 19 | 20 | 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # OSX 4 | # 5 | .DS_Store 6 | 7 | # Xcode 8 | # 9 | build/ 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | *.xccheckout 20 | *.moved-aside 21 | DerivedData 22 | *.hmap 23 | *.ipa 24 | *.xcuserstate 25 | # Jetbrains 26 | .idea 27 | 28 | # Logs 29 | logs 30 | *.log 31 | npm-debug.log* 32 | yarn-debug.log* 33 | yarn-error.log* 34 | 35 | # Runtime data 36 | pids 37 | *.pid 38 | *.seed 39 | *.pid.lock 40 | 41 | # dotenv environment variables file 42 | .env 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.11.0 4 | 5 | New features: 6 | 7 | - Add support for `Sound.setCategory('Alarm')` on Android 8 | - Update Visual Studio path definition to support Windows UWP in CI. 9 | 10 | Bug fixes: 11 | 12 | - Use incrementing keys instead of a filename hash to allow multiple Sound 13 | instances for the same filename. 14 | - Update Podfile reference to fix build under React Native 0.60. 15 | - Fix getSystemVolume callback on Android for parity with iOS. 16 | - Prevent a crash under iOS 8. 17 | 18 | Other improvements: 19 | 20 | - Documentation improvements. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Zhen Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-sound 2 | 3 | [![](https://img.shields.io/npm/v/react-native-sound.svg?style=flat-square)][npm] 4 | [![](https://img.shields.io/npm/l/react-native-sound.svg?style=flat-square)][npm] 5 | [![](https://img.shields.io/npm/dm/react-native-sound.svg?style=flat-square)][npm] 6 | 7 | [npm]: https://www.npmjs.com/package/react-native-sound 8 | 9 | React Native module for playing sound clips on iOS, Android, and Windows. 10 | 11 | Be warned, this software is alpha quality and may have bugs. Test on your own 12 | and use at your own risk! 13 | 14 | ## Feature matrix 15 | 16 | React-native-sound does not support streaming. See [#353][] for more info. 17 | Of course, we would welcome a PR if someone wants to take this on. 18 | 19 | In iOS, the library uses [AVAudioPlayer][], not [AVPlayer][]. 20 | 21 | [#353]: https://github.com/zmxv/react-native-sound/issues/353 22 | [AVAudioPlayer]: https://developer.apple.com/documentation/avfoundation/avaudioplayer 23 | [AVPlayer]: https://developer.apple.com/documentation/avfoundation/avplayer 24 | 25 | Feature | iOS | Android | Windows 26 | ---|---|---|--- 27 | Load sound from the app bundle | ✓ | ✓ | ✓ 28 | Load sound from other directories | ✓ | ✓ | ✓ 29 | Load sound from the network | ✓ | ✓ | 30 | Play sound | ✓ | ✓ | ✓ 31 | Playback completion callback | ✓ | ✓ | ✓ 32 | Pause | ✓ | ✓ | ✓ 33 | Resume | ✓ | ✓ | ✓ 34 | Stop | ✓ | ✓ | ✓ 35 | Reset | | ✓ | 36 | Release resource | ✓ | ✓ | ✓ 37 | Get duration | ✓ | ✓ | ✓ 38 | Get number of channels | ✓ | | 39 | Get/set volume | ✓ | ✓ | ✓ 40 | Get system volume | ✓ | ✓ | 41 | Set system volume | | ✓ | 42 | Get/set pan | ✓ | | 43 | Get/set loops | ✓ | ✓ | ✓ 44 | Get/set exact loop count | ✓ | | 45 | Get/set current time | ✓ | ✓ | ✓ 46 | Set speed | ✓ | ✓ | 47 | 48 | ## Installation 49 | 50 | First install the npm package from your app directory: 51 | 52 | ```javascript 53 | npm install react-native-sound --save 54 | ``` 55 | Note: If your react-native version is >= 0.60 then linking is done automatically. 56 | 57 | If your react-native version is < 0.60 then link it using: 58 | 59 | ```javascript 60 | react-native link react-native-sound 61 | ``` 62 | 63 | **If you encounter this error** 64 | 65 | ``` 66 | undefined is not an object (evaluating 'RNSound.IsAndroid') 67 | ``` 68 | 69 | you may additionally need to fully clear your build caches for Android. You 70 | can do this using 71 | 72 | ```bash 73 | cd android 74 | ./gradlew cleanBuildCache 75 | ``` 76 | 77 | After clearing your build cache, you should execute a new `react-native` build. 78 | 79 | If you still experience issues, **know that this is the most common build issue.** See [#592][] and the several 80 | issues linked from it for possible resolution. A pull request with improved 81 | documentation on this would be welcome! 82 | 83 | [#592]: https://github.com/zmxv/react-native-sound/issues/592 84 | 85 | ### Manual Installation Notes 86 | 87 | Please see the Wiki for these details https://github.com/zmxv/react-native-sound/wiki/Installation 88 | 89 | 90 | ## Help with React-Native-Sound 91 | 92 | * For react-native-sound developers [![][gitter badge]](https://gitter.im/react-native-sound/developers) 93 | * For help using react-native-sound [![][gitter badge]](https://gitter.im/react-native-sound/Help) 94 | 95 | [gitter badge]: https://img.shields.io/gitter/room/react-native-sound/developers.svg?format=flat-square 96 | 97 | ## Demo project 98 | 99 | https://github.com/zmxv/react-native-sound-demo 100 | 101 | ## Player 102 | 103 | 104 | 105 | https://github.com/benevbright/react-native-sound-playerview 106 | 107 | ## Basic usage 108 | 109 | First you'll need to add audio files to your project. 110 | 111 | - Android: Save your sound clip files under the directory `android/app/src/main/res/raw`. Note that files in this directory must be lowercase and underscored (e.g. my_file_name.mp3) and that subdirectories are not supported by Android. 112 | - iOS: Open Xcode and add your sound files to the project (Right-click the project and select `Add Files to [PROJECTNAME]`) 113 | 114 | ```js 115 | // Import the react-native-sound module 116 | var Sound = require('react-native-sound'); 117 | 118 | // Enable playback in silence mode 119 | Sound.setCategory('Playback'); 120 | 121 | // Load the sound file 'whoosh.mp3' from the app bundle 122 | // See notes below about preloading sounds within initialization code below. 123 | var whoosh = new Sound('whoosh.mp3', Sound.MAIN_BUNDLE, (error) => { 124 | if (error) { 125 | console.log('failed to load the sound', error); 126 | return; 127 | } 128 | // loaded successfully 129 | console.log('duration in seconds: ' + whoosh.getDuration() + 'number of channels: ' + whoosh.getNumberOfChannels()); 130 | 131 | // Play the sound with an onEnd callback 132 | whoosh.play((success) => { 133 | if (success) { 134 | console.log('successfully finished playing'); 135 | } else { 136 | console.log('playback failed due to audio decoding errors'); 137 | } 138 | }); 139 | }); 140 | 141 | // Reduce the volume by half 142 | whoosh.setVolume(0.5); 143 | 144 | // Position the sound to the full right in a stereo field 145 | whoosh.setPan(1); 146 | 147 | // Loop indefinitely until stop() is called 148 | whoosh.setNumberOfLoops(-1); 149 | 150 | // Get properties of the player instance 151 | console.log('volume: ' + whoosh.getVolume()); 152 | console.log('pan: ' + whoosh.getPan()); 153 | console.log('loops: ' + whoosh.getNumberOfLoops()); 154 | 155 | // Seek to a specific point in seconds 156 | whoosh.setCurrentTime(2.5); 157 | 158 | // Get the current playback point in seconds 159 | whoosh.getCurrentTime((seconds) => console.log('at ' + seconds)); 160 | 161 | // Pause the sound 162 | whoosh.pause(); 163 | 164 | // Stop the sound and rewind to the beginning 165 | whoosh.stop(() => { 166 | // Note: If you want to play a sound after stopping and rewinding it, 167 | // it is important to call play() in a callback. 168 | whoosh.play(); 169 | }); 170 | 171 | // Release the audio player resource 172 | whoosh.release(); 173 | ``` 174 | 175 | ## Notes 176 | 177 | - To minimize playback delay, you may want to preload a sound file without calling `play()` (e.g. `var s = new Sound(...);`) during app initialization. This also helps avoid a race condition where `play()` may be called before loading of the sound is complete, which results in no sound but no error because loading is still being processed. 178 | - You can play multiple sound files at the same time. Under the hood, this module uses `AVAudioSessionCategoryAmbient` to mix sounds on iOS. 179 | - You may reuse a `Sound` instance for multiple playbacks. 180 | - On iOS, the module wraps `AVAudioPlayer` that supports aac, aiff, mp3, wav etc. The full list of supported formats can be found at https://developer.apple.com/library/content/documentation/MusicAudio/Conceptual/CoreAudioOverview/SupportedAudioFormatsMacOSX/SupportedAudioFormatsMacOSX.html 181 | - On Android, the module wraps `android.media.MediaPlayer`. The full list of supported formats can be found at https://developer.android.com/guide/topics/media/media-formats.html 182 | - On Android, the absolute path can start with '/sdcard/'. So, if you want to access a sound called "my_sound.mp3" on Downloads folder, the absolute path will be: '/sdcard/Downloads/my_sound.mp3'. 183 | - You may chain non-getter calls, for example, `sound.setVolume(.5).setPan(.5).play()`. 184 | 185 | ## Audio on React Native 186 | 187 | - [The State of Audio Libraries in React Native (Oct. 2018)][medium] 188 | - [react-native-audio-toolkit][] 189 | - [react-native-video][] (also plays audio) 190 | - [Expo Audio SDK][] 191 | - [#media on awesome-react-native][#media] 192 | 193 | [medium]: https://medium.com/@emmettharper/the-state-of-audio-libraries-in-react-native-7e542f57b3b4 194 | [react-native-audio-toolkit]: https://github.com/react-native-community/react-native-audio-toolkit 195 | [react-native-video]: https://github.com/react-native-community/react-native-video 196 | [expo audio sdk]: https://docs.expo.io/versions/latest/sdk/audio/ 197 | [#media]: http://www.awesome-react-native.com/#media 198 | 199 | ## Contributing 200 | 201 | Pull requests welcome with bug fixes, documentation improvements, and 202 | enhancements. 203 | 204 | When making big changes, please open an issue first to discuss. 205 | 206 | ## License 207 | 208 | This project is licensed under the MIT License. 209 | -------------------------------------------------------------------------------- /RNSound.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | 4 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 5 | folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' 6 | 7 | Pod::Spec.new do |s| 8 | s.name = "RNSound" 9 | s.version = package['version'] 10 | s.summary = package['description'] 11 | s.license = package['license'] 12 | 13 | s.authors = package['author'] 14 | s.homepage = package['homepage'] 15 | s.platform = :ios, "9.0" 16 | 17 | s.source = { :git => "https://github.com/zmxv/react-native-sound.git" } 18 | s.source_files = "ios/**/*.{h,m,mm,cpp}" 19 | 20 | # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0. 21 | # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79. 22 | if respond_to?(:install_modules_dependencies, true) 23 | install_modules_dependencies(s) 24 | else 25 | s.dependency "React-Core" 26 | 27 | # Don't install the dependencies when we run `pod install` in the old architecture. 28 | if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then 29 | s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" 30 | s.pod_target_xcconfig = { 31 | "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", 32 | "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", 33 | "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" 34 | } 35 | s.dependency "React-Codegen" 36 | s.dependency "RCT-Folly" 37 | s.dependency "RCTRequired" 38 | s.dependency "RCTTypeSafety" 39 | s.dependency "ReactCommon/turbomodule/core" 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /RNSound/RNSound.m: -------------------------------------------------------------------------------- 1 | #import "RNSound.h" 2 | 3 | #if __has_include("RCTUtils.h") 4 | #import "RCTUtils.h" 5 | #else 6 | #import 7 | #endif 8 | 9 | @implementation RNSound { 10 | NSMutableDictionary *_playerPool; 11 | NSMutableDictionary *_callbackPool; 12 | } 13 | 14 | @synthesize _key = _key; 15 | 16 | - (void)audioSessionChangeObserver:(NSNotification *)notification { 17 | NSDictionary *userInfo = notification.userInfo; 18 | AVAudioSessionRouteChangeReason audioSessionRouteChangeReason = 19 | [userInfo[@"AVAudioSessionRouteChangeReasonKey"] longValue]; 20 | AVAudioSessionInterruptionType audioSessionInterruptionType = 21 | [userInfo[@"AVAudioSessionInterruptionTypeKey"] longValue]; 22 | AVAudioPlayer *player = [self playerForKey:self._key]; 23 | if (audioSessionInterruptionType == AVAudioSessionInterruptionTypeEnded) { 24 | if (player) { 25 | [player play]; 26 | [self setOnPlay:YES forPlayerKey:self._key]; 27 | } 28 | } 29 | else if (audioSessionRouteChangeReason == 30 | AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { 31 | if (player) { 32 | [player pause]; 33 | [self setOnPlay:NO forPlayerKey:self._key]; 34 | } 35 | } 36 | else if (audioSessionInterruptionType == AVAudioSessionInterruptionTypeBegan) { 37 | if (player) { 38 | [player pause]; 39 | [self setOnPlay:NO forPlayerKey:self._key]; 40 | } 41 | } 42 | } 43 | 44 | - (NSMutableDictionary *)playerPool { 45 | if (!_playerPool) { 46 | _playerPool = [NSMutableDictionary new]; 47 | } 48 | return _playerPool; 49 | } 50 | 51 | - (NSMutableDictionary *)callbackPool { 52 | if (!_callbackPool) { 53 | _callbackPool = [NSMutableDictionary new]; 54 | } 55 | return _callbackPool; 56 | } 57 | 58 | - (AVAudioPlayer *)playerForKey:(nonnull NSNumber *)key { 59 | return [[self playerPool] objectForKey:key]; 60 | } 61 | 62 | - (NSNumber *)keyForPlayer:(nonnull AVAudioPlayer *)player { 63 | return [[[self playerPool] allKeysForObject:player] firstObject]; 64 | } 65 | 66 | - (RCTResponseSenderBlock)callbackForKey:(nonnull NSNumber *)key { 67 | return [[self callbackPool] objectForKey:key]; 68 | } 69 | 70 | - (NSString *)getDirectory:(int)directory { 71 | return [NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, 72 | YES) firstObject]; 73 | } 74 | 75 | - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player 76 | successfully:(BOOL)flag { 77 | @synchronized(self) { 78 | NSNumber *key = [self keyForPlayer:player]; 79 | if (key == nil) 80 | return; 81 | 82 | [self setOnPlay:NO forPlayerKey:key]; 83 | RCTResponseSenderBlock callback = [self callbackForKey:key]; 84 | if (callback) { 85 | callback( 86 | [NSArray arrayWithObjects:[NSNumber numberWithBool:flag], nil]); 87 | [[self callbackPool] removeObjectForKey:key]; 88 | } 89 | } 90 | } 91 | 92 | RCT_EXPORT_MODULE(); 93 | 94 | - (NSArray *)supportedEvents { 95 | return [NSArray arrayWithObjects:@"onPlayChange", nil]; 96 | } 97 | 98 | - (NSDictionary *)constantsToExport { 99 | return [NSDictionary 100 | dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO], @"IsAndroid", 101 | [[NSBundle mainBundle] bundlePath], 102 | @"MainBundlePath", 103 | [self getDirectory:NSDocumentDirectory], 104 | @"NSDocumentDirectory", 105 | [self getDirectory:NSLibraryDirectory], 106 | @"NSLibraryDirectory", 107 | [self getDirectory:NSCachesDirectory], 108 | @"NSCachesDirectory", nil]; 109 | } 110 | 111 | RCT_EXPORT_METHOD(enable : (BOOL)enabled) { 112 | AVAudioSession *session = [AVAudioSession sharedInstance]; 113 | [session setCategory:AVAudioSessionCategoryAmbient error:nil]; 114 | [session setActive:enabled error:nil]; 115 | } 116 | 117 | RCT_EXPORT_METHOD(setActive : (BOOL)active) { 118 | AVAudioSession *session = [AVAudioSession sharedInstance]; 119 | [session setActive:active error:nil]; 120 | } 121 | 122 | RCT_EXPORT_METHOD(setMode : (NSString *)modeName) { 123 | AVAudioSession *session = [AVAudioSession sharedInstance]; 124 | NSString *mode = nil; 125 | 126 | if ([modeName isEqual:@"Default"]) { 127 | mode = AVAudioSessionModeDefault; 128 | } else if ([modeName isEqual:@"VoiceChat"]) { 129 | mode = AVAudioSessionModeVoiceChat; 130 | } else if ([modeName isEqual:@"VideoChat"]) { 131 | mode = AVAudioSessionModeVideoChat; 132 | } else if ([modeName isEqual:@"GameChat"]) { 133 | mode = AVAudioSessionModeGameChat; 134 | } else if ([modeName isEqual:@"VideoRecording"]) { 135 | mode = AVAudioSessionModeVideoRecording; 136 | } else if ([modeName isEqual:@"Measurement"]) { 137 | mode = AVAudioSessionModeMeasurement; 138 | } else if ([modeName isEqual:@"MoviePlayback"]) { 139 | mode = AVAudioSessionModeMoviePlayback; 140 | } else if ([modeName isEqual:@"SpokenAudio"]) { 141 | mode = AVAudioSessionModeSpokenAudio; 142 | } 143 | 144 | if (mode) { 145 | [session setMode:mode error:nil]; 146 | } 147 | } 148 | 149 | RCT_EXPORT_METHOD(setCategory 150 | : (NSString *)categoryName mixWithOthers 151 | : (BOOL)mixWithOthers) { 152 | AVAudioSession *session = [AVAudioSession sharedInstance]; 153 | NSString *category = nil; 154 | 155 | if ([categoryName isEqual:@"Ambient"]) { 156 | category = AVAudioSessionCategoryAmbient; 157 | } else if ([categoryName isEqual:@"SoloAmbient"]) { 158 | category = AVAudioSessionCategorySoloAmbient; 159 | } else if ([categoryName isEqual:@"Playback"]) { 160 | category = AVAudioSessionCategoryPlayback; 161 | } else if ([categoryName isEqual:@"Record"]) { 162 | category = AVAudioSessionCategoryRecord; 163 | } else if ([categoryName isEqual:@"PlayAndRecord"]) { 164 | category = AVAudioSessionCategoryPlayAndRecord; 165 | } 166 | #if TARGET_OS_IOS 167 | else if ([categoryName isEqual:@"AudioProcessing"]) { 168 | category = AVAudioSessionCategoryAudioProcessing; 169 | } 170 | #endif 171 | else if ([categoryName isEqual:@"MultiRoute"]) { 172 | category = AVAudioSessionCategoryMultiRoute; 173 | } 174 | 175 | if (category) { 176 | if (mixWithOthers) { 177 | [session setCategory:category 178 | withOptions:AVAudioSessionCategoryOptionMixWithOthers | 179 | AVAudioSessionCategoryOptionAllowBluetooth 180 | error:nil]; 181 | } else { 182 | [session setCategory:category error:nil]; 183 | } 184 | } 185 | } 186 | 187 | RCT_EXPORT_METHOD(enableInSilenceMode : (BOOL)enabled) { 188 | AVAudioSession *session = [AVAudioSession sharedInstance]; 189 | [session setCategory:AVAudioSessionCategoryPlayback error:nil]; 190 | [session setActive:enabled error:nil]; 191 | } 192 | 193 | RCT_EXPORT_METHOD(prepare 194 | : (NSString *)fileName withKey 195 | : (nonnull NSNumber *)key withOptions 196 | : (NSDictionary *)options withCallback 197 | : (RCTResponseSenderBlock)callback) { 198 | NSError *error; 199 | NSURL *fileNameUrl; 200 | AVAudioPlayer *player; 201 | 202 | if ([fileName hasPrefix:@"http"]) { 203 | fileNameUrl = [NSURL URLWithString:fileName]; 204 | NSData *data = [NSData dataWithContentsOfURL:fileNameUrl]; 205 | player = [[AVAudioPlayer alloc] initWithData:data error:&error]; 206 | } else if ([fileName hasPrefix:@"ipod-library://"]) { 207 | fileNameUrl = [NSURL URLWithString:fileName]; 208 | player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileNameUrl 209 | error:&error]; 210 | } else { 211 | fileNameUrl = [NSURL URLWithString:fileName]; 212 | player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileNameUrl 213 | error:&error]; 214 | } 215 | 216 | if (player) { 217 | @synchronized(self) { 218 | player.delegate = self; 219 | player.enableRate = YES; 220 | [player prepareToPlay]; 221 | [[self playerPool] setObject:player forKey:key]; 222 | callback([NSArray 223 | arrayWithObjects:[NSNull null], 224 | [NSDictionary 225 | dictionaryWithObjectsAndKeys: 226 | [NSNumber 227 | numberWithDouble:player.duration], 228 | @"duration", 229 | [NSNumber numberWithUnsignedInteger: 230 | player.numberOfChannels], 231 | @"numberOfChannels", nil], 232 | nil]); 233 | } 234 | } else { 235 | callback([NSArray arrayWithObjects:RCTJSErrorFromNSError(error), nil]); 236 | } 237 | } 238 | 239 | RCT_EXPORT_METHOD(play 240 | : (nonnull NSNumber *)key withCallback 241 | : (RCTResponseSenderBlock)callback) { 242 | [[AVAudioSession sharedInstance] setActive:YES error:nil]; 243 | [[NSNotificationCenter defaultCenter] 244 | addObserver:self 245 | selector:@selector(audioSessionChangeObserver:) 246 | name:AVAudioSessionRouteChangeNotification 247 | object:[AVAudioSession sharedInstance]]; 248 | [[NSNotificationCenter defaultCenter] 249 | addObserver:self 250 | selector:@selector(audioSessionChangeObserver:) 251 | name:AVAudioSessionInterruptionNotification 252 | object:[AVAudioSession sharedInstance]]; 253 | self._key = key; 254 | AVAudioPlayer *player = [self playerForKey:key]; 255 | if (player) { 256 | [[self callbackPool] setObject:[callback copy] forKey:key]; 257 | [player play]; 258 | [self setOnPlay:YES forPlayerKey:key]; 259 | } 260 | } 261 | 262 | RCT_EXPORT_METHOD(pause 263 | : (nonnull NSNumber *)key withCallback 264 | : (RCTResponseSenderBlock)callback) { 265 | AVAudioPlayer *player = [self playerForKey:key]; 266 | if (player) { 267 | [player pause]; 268 | callback([NSArray array]); 269 | } 270 | } 271 | 272 | RCT_EXPORT_METHOD(stop 273 | : (nonnull NSNumber *)key withCallback 274 | : (RCTResponseSenderBlock)callback) { 275 | AVAudioPlayer *player = [self playerForKey:key]; 276 | if (player) { 277 | [player stop]; 278 | player.currentTime = 0; 279 | callback([NSArray array]); 280 | } 281 | } 282 | 283 | RCT_EXPORT_METHOD(release : (nonnull NSNumber *)key) { 284 | @synchronized(self) { 285 | AVAudioPlayer *player = [self playerForKey:key]; 286 | if (player) { 287 | [player stop]; 288 | [[self callbackPool] removeObjectForKey:key]; 289 | [[self playerPool] removeObjectForKey:key]; 290 | NSNotificationCenter *notificationCenter = 291 | [NSNotificationCenter defaultCenter]; 292 | [notificationCenter removeObserver:self]; 293 | } 294 | } 295 | } 296 | 297 | RCT_EXPORT_METHOD(setVolume 298 | : (nonnull NSNumber *)key withValue 299 | : (nonnull NSNumber *)value) { 300 | AVAudioPlayer *player = [self playerForKey:key]; 301 | if (player) { 302 | player.volume = [value floatValue]; 303 | } 304 | } 305 | 306 | RCT_EXPORT_METHOD(getSystemVolume : (RCTResponseSenderBlock)callback) { 307 | AVAudioSession *session = [AVAudioSession sharedInstance]; 308 | callback(@[ @(session.outputVolume) ]); 309 | } 310 | 311 | RCT_EXPORT_METHOD(setPan 312 | : (nonnull NSNumber *)key withValue 313 | : (nonnull NSNumber *)value) { 314 | AVAudioPlayer *player = [self playerForKey:key]; 315 | if (player) { 316 | player.pan = [value floatValue]; 317 | } 318 | } 319 | 320 | RCT_EXPORT_METHOD(setNumberOfLoops 321 | : (nonnull NSNumber *)key withValue 322 | : (nonnull NSNumber *)value) { 323 | AVAudioPlayer *player = [self playerForKey:key]; 324 | if (player) { 325 | player.numberOfLoops = [value intValue]; 326 | } 327 | } 328 | 329 | RCT_EXPORT_METHOD(setSpeed 330 | : (nonnull NSNumber *)key withValue 331 | : (nonnull NSNumber *)value) { 332 | AVAudioPlayer *player = [self playerForKey:key]; 333 | if (player) { 334 | player.rate = [value floatValue]; 335 | } 336 | } 337 | 338 | RCT_EXPORT_METHOD(setCurrentTime 339 | : (nonnull NSNumber *)key withValue 340 | : (nonnull NSNumber *)value) { 341 | AVAudioPlayer *player = [self playerForKey:key]; 342 | if (player) { 343 | player.currentTime = [value doubleValue]; 344 | } 345 | } 346 | 347 | RCT_EXPORT_METHOD(getCurrentTime 348 | : (nonnull NSNumber *)key withCallback 349 | : (RCTResponseSenderBlock)callback) { 350 | AVAudioPlayer *player = [self playerForKey:key]; 351 | if (player) { 352 | callback([NSArray 353 | arrayWithObjects:[NSNumber numberWithDouble:player.currentTime], 354 | [NSNumber numberWithBool:player.isPlaying], nil]); 355 | } else { 356 | callback([NSArray arrayWithObjects:[NSNumber numberWithInteger:-1], 357 | [NSNumber numberWithBool:NO], nil]); 358 | } 359 | } 360 | 361 | RCT_EXPORT_METHOD(setSpeakerPhone : (BOOL)on) { 362 | AVAudioSession *session = [AVAudioSession sharedInstance]; 363 | if (on) { 364 | [session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker 365 | error:nil]; 366 | } else { 367 | [session overrideOutputAudioPort:AVAudioSessionPortOverrideNone 368 | error:nil]; 369 | } 370 | [session setActive:true error:nil]; 371 | } 372 | 373 | + (BOOL)requiresMainQueueSetup { 374 | return YES; 375 | } 376 | - (void)setOnPlay:(BOOL)isPlaying forPlayerKey:(nonnull NSNumber *)playerKey { 377 | [self 378 | sendEventWithName:@"onPlayChange" 379 | body:[NSDictionary 380 | dictionaryWithObjectsAndKeys: 381 | [NSNumber 382 | numberWithBool:isPlaying ? YES : NO], 383 | @"isPlaying", playerKey, @"playerKey", nil]]; 384 | } 385 | @end 386 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | // Buildscript is evaluated before everything else so we can't use getExtOrDefault 3 | def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["Sound_kotlinVersion"] 4 | 5 | repositories { 6 | google() 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | classpath "com.android.tools.build:gradle:7.2.1" 12 | // noinspection DifferentKotlinGradleVersion 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | def reactNativeArchitectures() { 18 | def value = rootProject.getProperties().get("reactNativeArchitectures") 19 | return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] 20 | } 21 | 22 | def isNewArchitectureEnabled() { 23 | return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" 24 | } 25 | 26 | apply plugin: "com.android.library" 27 | apply plugin: "kotlin-android" 28 | 29 | if (isNewArchitectureEnabled()) { 30 | apply plugin: "com.facebook.react" 31 | } 32 | 33 | def getExtOrDefault(name) { 34 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["Sound_" + name] 35 | } 36 | 37 | def getExtOrIntegerDefault(name) { 38 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Sound_" + name]).toInteger() 39 | } 40 | 41 | def supportsNamespace() { 42 | def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.') 43 | def major = parsed[0].toInteger() 44 | def minor = parsed[1].toInteger() 45 | 46 | // Namespace support was added in 7.3.0 47 | return (major == 7 && minor >= 3) || major >= 8 48 | } 49 | 50 | android { 51 | if (supportsNamespace()) { 52 | namespace "com.zmxv.RNSound" 53 | 54 | sourceSets { 55 | main { 56 | manifest.srcFile "src/main/AndroidManifestNew.xml" 57 | } 58 | } 59 | } 60 | 61 | compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") 62 | 63 | defaultConfig { 64 | minSdkVersion getExtOrIntegerDefault("minSdkVersion") 65 | targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") 66 | buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() 67 | 68 | } 69 | 70 | buildFeatures { 71 | buildConfig true 72 | } 73 | 74 | buildTypes { 75 | release { 76 | minifyEnabled false 77 | } 78 | } 79 | 80 | lintOptions { 81 | disable "GradleCompatible" 82 | } 83 | 84 | compileOptions { 85 | sourceCompatibility JavaVersion.VERSION_1_8 86 | targetCompatibility JavaVersion.VERSION_1_8 87 | } 88 | 89 | sourceSets { 90 | main { 91 | if (isNewArchitectureEnabled()) { 92 | java.srcDirs += [ 93 | "src/newarch", 94 | // Codegen specs 95 | "generated/java", 96 | "generated/jni" 97 | ] 98 | } else { 99 | java.srcDirs += ["src/oldarch"] 100 | } 101 | } 102 | } 103 | } 104 | 105 | repositories { 106 | mavenCentral() 107 | google() 108 | } 109 | 110 | def kotlin_version = getExtOrDefault("kotlinVersion") 111 | 112 | dependencies { 113 | // For < 0.71, this will be from the local maven repo 114 | // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin 115 | //noinspection GradleDynamicVersion 116 | implementation "com.facebook.react:react-native:+" 117 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 118 | } 119 | 120 | if (isNewArchitectureEnabled()) { 121 | react { 122 | jsRootDir = file("../src/") 123 | libraryName = "RNSound" 124 | codegenJavaPackageName = "com.zmxv.RNSound" 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | Sound_kotlinVersion=1.7.0 2 | Sound_minSdkVersion=21 3 | Sound_targetSdkVersion=31 4 | Sound_compileSdkVersion=31 5 | Sound_ndkversion=21.4.7075529 6 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifestNew.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /android/src/main/java/com/zmxv/RNSound/Sound.kt: -------------------------------------------------------------------------------- 1 | package com.zmxv.RNSound; 2 | 3 | import android.content.Context 4 | import android.media.AudioManager 5 | import android.media.MediaPlayer 6 | import android.media.MediaPlayer.OnCompletionListener 7 | import android.media.MediaPlayer.OnErrorListener 8 | import android.media.MediaPlayer.OnPreparedListener 9 | 10 | import android.util.Log 11 | import com.facebook.react.bridge.Arguments 12 | import com.facebook.react.bridge.BridgeReactContext.RCTDeviceEventEmitter 13 | import com.facebook.react.bridge.Callback 14 | import com.facebook.react.bridge.ReactApplicationContext 15 | 16 | import com.facebook.react.bridge.ReadableMap 17 | import java.io.File 18 | import java.io.IOException 19 | 20 | 21 | open class Sound internal constructor(context:ReactApplicationContext):AudioManager.OnAudioFocusChangeListener { 22 | private var playerPool: MutableMap = mutableMapOf() 23 | private val reactContext: ReactApplicationContext = context 24 | var category: String? = null 25 | private var mixWithOthers: Boolean = true 26 | private var focusedPlayerKey: Double? = null 27 | private var wasPlayingBeforeFocusChange: Boolean = false 28 | 29 | fun setOnPlay(isPlaying: Boolean, playerKey: Double) { 30 | val params = Arguments.createMap() 31 | params.putBoolean("isPlaying", isPlaying) 32 | params.putDouble("playerKey", playerKey) 33 | reactContext.getJSModule(RCTDeviceEventEmitter::class.java) 34 | ?.emit("onPlayChange", params) 35 | } 36 | 37 | fun prepare(fileName: String, key: Double?, options: ReadableMap, callback: Callback) { 38 | val player = createMediaPlayer(fileName) 39 | if (options.hasKey("speed")) { 40 | player!!.playbackParams = player.playbackParams.setSpeed(options.getDouble("speed").toFloat()) 41 | } 42 | if (player == null) { 43 | val e = Arguments.createMap() 44 | e.putInt("code", -1) 45 | e.putString("message", "resource not found") 46 | callback.invoke(e, null) 47 | return 48 | } 49 | if (key != null) { 50 | playerPool[key] = player 51 | } 52 | 53 | val module: Sound = this 54 | 55 | if (module.category != null) { 56 | var category: Int? = null 57 | when (module.category) { 58 | "Playback" -> category = AudioManager.STREAM_MUSIC 59 | "Ambient" -> category = AudioManager.STREAM_NOTIFICATION 60 | "System" -> category = AudioManager.STREAM_SYSTEM 61 | "Voice" -> category = AudioManager.STREAM_VOICE_CALL 62 | "Ring" -> category = AudioManager.STREAM_RING 63 | "Alarm" -> category = AudioManager.STREAM_ALARM 64 | 65 | } 66 | if (category != null) { 67 | player.setAudioStreamType(category) 68 | } 69 | } 70 | 71 | player.setOnPreparedListener(object : OnPreparedListener { 72 | var callbackWasCalled: Boolean = false 73 | 74 | @Synchronized 75 | override fun onPrepared(mp: MediaPlayer) { 76 | if (callbackWasCalled) return 77 | callbackWasCalled = true 78 | 79 | val props = Arguments.createMap() 80 | props.putDouble("duration", mp.duration * .001) 81 | try { 82 | callback.invoke(null, props) 83 | } catch (runtimeException: RuntimeException) { 84 | // The callback was already invoked 85 | 86 | } 87 | } 88 | }) 89 | 90 | player.setOnErrorListener(object : OnErrorListener { 91 | var callbackWasCalled: Boolean = false 92 | 93 | @Synchronized 94 | override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean { 95 | if (callbackWasCalled) return true 96 | callbackWasCalled = true 97 | try { 98 | val props = Arguments.createMap() 99 | props.putInt("what", what) 100 | props.putInt("extra", extra) 101 | callback.invoke(props, null) 102 | } catch (runtimeException: RuntimeException) { 103 | // The callback was already invoked 104 | 105 | } 106 | return true 107 | } 108 | }) 109 | 110 | try { 111 | if (options.hasKey("loadSync") && options.getBoolean("loadSync")) { 112 | player.prepare() 113 | } else { 114 | player.prepareAsync() 115 | } 116 | } catch (ignored: Exception) { 117 | // When loading files from a file, we useMediaPlayer.create, which actually 118 | // prepares the audio for us already. So we catch and ignore this error 119 | 120 | } 121 | } 122 | 123 | private fun createMediaPlayer(fileName: String): MediaPlayer? { 124 | val res = 125 | reactContext.resources.getIdentifier(fileName, "raw", reactContext.packageName) 126 | val mediaPlayer = MediaPlayer() 127 | if (res != 0) { 128 | try { 129 | val afd = reactContext.resources.openRawResourceFd(res) 130 | mediaPlayer.setDataSource(afd.fileDescriptor, afd.startOffset, afd.length) 131 | afd.close() 132 | } catch (e: IOException) { 133 | return null 134 | } 135 | return mediaPlayer 136 | } 137 | 138 | if (fileName.startsWith("http://") || fileName.startsWith("https://")) { 139 | mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC) 140 | try { 141 | mediaPlayer.setDataSource(fileName) 142 | } catch (e: IOException) { 143 | return null 144 | } 145 | return mediaPlayer 146 | } 147 | 148 | if (fileName.startsWith("asset:/")) { 149 | try { 150 | val descriptor = 151 | reactContext.assets.openFd(fileName.replace("asset:/", "")) 152 | mediaPlayer.setDataSource( 153 | descriptor.fileDescriptor, 154 | descriptor.startOffset, 155 | descriptor.length 156 | ) 157 | descriptor.close() 158 | return mediaPlayer 159 | } catch (e: IOException) { 160 | return null 161 | } 162 | } 163 | 164 | if (fileName.startsWith("file:/")) { 165 | try { 166 | mediaPlayer.setDataSource(fileName) 167 | } catch (e: IOException) { 168 | return null 169 | } 170 | return mediaPlayer 171 | } 172 | 173 | val file = File(fileName) 174 | if (file.exists()) { 175 | mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC) 176 | try { 177 | mediaPlayer.setDataSource(fileName) 178 | } catch (e: IOException) { 179 | return null 180 | } 181 | return mediaPlayer 182 | } 183 | 184 | return null 185 | } 186 | 187 | 188 | fun play(key: Double?, callback: Callback?) { 189 | val player = playerPool[key] 190 | if (player == null) { 191 | if (key != null) { 192 | setOnPlay(false, key) 193 | } 194 | callback?.invoke(false) 195 | return 196 | } 197 | if (player.isPlaying) { 198 | return 199 | } 200 | 201 | // Request audio focus in Android system 202 | if (!this.mixWithOthers) { 203 | val audioManager = reactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager 204 | 205 | audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) 206 | 207 | this.focusedPlayerKey = key 208 | } 209 | 210 | player.setOnCompletionListener(object : OnCompletionListener { 211 | var callbackWasCalled: Boolean = false 212 | 213 | @Synchronized 214 | override fun onCompletion(mp: MediaPlayer) { 215 | if (!mp.isLooping) { 216 | if (key != null) { 217 | setOnPlay(false, key) 218 | } 219 | if (callbackWasCalled) return 220 | callbackWasCalled = true 221 | try { 222 | callback?.invoke(true) 223 | } catch (e: Exception) { 224 | //Catches the exception: java.lang.RuntimeException·Illegal callback invocation from native module 225 | } 226 | } 227 | } 228 | }) 229 | player.setOnErrorListener(object : OnErrorListener { 230 | var callbackWasCalled: Boolean = false 231 | 232 | @Synchronized 233 | override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean { 234 | if (key != null) { 235 | setOnPlay(false, key) 236 | } 237 | if (callbackWasCalled) return true 238 | callbackWasCalled = true 239 | try { 240 | callback?.invoke(true) 241 | } catch (e: Exception) { 242 | //Catches the exception: java.lang.RuntimeException·Illegal callback invocation from native module 243 | } 244 | return true 245 | } 246 | }) 247 | player.start() 248 | if (key != null) { 249 | setOnPlay(true, key) 250 | } 251 | } 252 | 253 | 254 | fun pause(key: Double?, callback: Callback?) { 255 | val player = playerPool[key] 256 | if (player != null && player.isPlaying) { 257 | player.pause() 258 | } 259 | 260 | callback?.invoke() 261 | } 262 | 263 | 264 | fun stop(key: Double, callback: Callback) { 265 | val player = playerPool[key] 266 | if (player != null && player.isPlaying) { 267 | player.pause() 268 | player.seekTo(0) 269 | } 270 | 271 | // Release audio focus in Android system 272 | if (!this.mixWithOthers && key === this.focusedPlayerKey) { 273 | val audioManager = reactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager 274 | audioManager.abandonAudioFocus(this) 275 | } 276 | 277 | callback.invoke() 278 | } 279 | 280 | 281 | fun reset(key: Double?) { 282 | val player = playerPool[key] 283 | player?.reset() 284 | } 285 | 286 | 287 | fun release(key: Double) { 288 | val player = playerPool[key] 289 | if (player != null) { 290 | player.reset() 291 | player.release() 292 | playerPool.remove(key) 293 | 294 | 295 | // Release audio focus in Android system 296 | if (!this.mixWithOthers && key === this.focusedPlayerKey) { 297 | val audioManager = reactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager 298 | audioManager.abandonAudioFocus(this) 299 | } 300 | } 301 | } 302 | 303 | 304 | fun setVolume(key: Double?, left: Float?, right: Float?) { 305 | val player = playerPool[key] 306 | player?.setVolume(left!!, right!!) 307 | } 308 | 309 | 310 | fun getSystemVolume(callback: Callback) { 311 | try { 312 | val audioManager = reactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager 313 | 314 | callback.invoke( 315 | audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) 316 | .toFloat() / audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) 317 | ) 318 | } catch (error: Exception) { 319 | val e = Arguments.createMap() 320 | e.putInt("code", -1) 321 | e.putString("message", error.message) 322 | callback.invoke(e) 323 | } 324 | } 325 | 326 | 327 | fun setSystemVolume(value: Float) { 328 | val audioManager = reactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager 329 | 330 | val volume = Math.round(audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) * value) 331 | audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0) 332 | } 333 | 334 | 335 | fun setLooping(key: Double?, looping: Boolean?) { 336 | val player = playerPool[key] 337 | if (player != null) { 338 | player.isLooping = looping!! 339 | } 340 | } 341 | 342 | 343 | fun setSpeed(key: Double?, speed: Float?) { 344 | val player = playerPool[key] 345 | if (player != null) { 346 | player.playbackParams = player.playbackParams.setSpeed(speed!!) 347 | } 348 | } 349 | 350 | 351 | fun setPitch(key: Double?, pitch: Float?) { 352 | val player = playerPool[key] 353 | if (player != null) { 354 | player.playbackParams = player.playbackParams.setPitch(pitch!!) 355 | } 356 | } 357 | 358 | 359 | fun setCurrentTime(key: Double?, sec: Float) { 360 | val player = playerPool[key] 361 | player?.seekTo(Math.round(sec * 1000)) 362 | } 363 | 364 | 365 | fun getCurrentTime(key: Double?, callback: Callback) { 366 | val player = playerPool[key] 367 | if (player == null) { 368 | callback.invoke(-1, false) 369 | return 370 | } 371 | callback.invoke(player.currentPosition * .001, player.isPlaying) 372 | } 373 | 374 | 375 | //turn speaker on 376 | fun setSpeakerphoneOn(key: Double?, speaker: Boolean) { 377 | val player = playerPool[key] 378 | if (player != null) { 379 | val audioManager = reactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager 380 | if (speaker) { 381 | audioManager.mode = AudioManager.MODE_IN_COMMUNICATION 382 | } else { 383 | audioManager.mode = AudioManager.MODE_NORMAL 384 | } 385 | audioManager.isSpeakerphoneOn = speaker 386 | } 387 | } 388 | 389 | 390 | fun setCategory(category: String?, mixWithOthers: Boolean?) { 391 | this.category = category 392 | this.mixWithOthers = mixWithOthers!! 393 | } 394 | 395 | 396 | 397 | override fun onAudioFocusChange(focusChange: Int) { 398 | if (!this.mixWithOthers) { 399 | val player = playerPool[focusedPlayerKey] 400 | 401 | if (player != null) { 402 | if (focusChange <= 0) { 403 | this.wasPlayingBeforeFocusChange = player.isPlaying 404 | 405 | if (this.wasPlayingBeforeFocusChange) { 406 | this.pause(this.focusedPlayerKey, null) 407 | } 408 | } else { 409 | if (this.wasPlayingBeforeFocusChange) { 410 | this.play(this.focusedPlayerKey, null) 411 | this.wasPlayingBeforeFocusChange = false 412 | } 413 | } 414 | } 415 | } 416 | } 417 | } 418 | -------------------------------------------------------------------------------- /android/src/main/java/com/zmxv/RNSound/SoundModule.kt: -------------------------------------------------------------------------------- 1 | package com.zmxv.RNSound; 2 | 3 | 4 | import com.facebook.react.bridge.Callback 5 | import com.facebook.react.bridge.ReactApplicationContext 6 | import com.facebook.react.bridge.ReadableMap 7 | import com.facebook.react.bridge.ReactMethod 8 | 9 | class SoundModule internal constructor(context: ReactApplicationContext) : 10 | SoundSpec(context) { 11 | private var module = Sound(context) 12 | 13 | @ReactMethod 14 | override fun prepare(fileName: String, key: Double, options: ReadableMap, callback: Callback) { 15 | module.prepare(fileName,key,options,callback) 16 | } 17 | @ReactMethod 18 | override fun setCategory(value: String, mixWithOthers: Boolean) { 19 | module.setCategory(value,mixWithOthers) 20 | } 21 | @ReactMethod 22 | override fun play(key: Double, callback: Callback) { 23 | module.play(key,callback) 24 | } 25 | @ReactMethod 26 | override fun pause(key: Double, callback: Callback) { 27 | module.pause(key,callback) 28 | } 29 | @ReactMethod 30 | override fun stop(key: Double, callback: Callback) { 31 | module.stop(key,callback) 32 | } 33 | @ReactMethod 34 | override fun reset(key: Double) { 35 | module.reset(key) 36 | } 37 | @ReactMethod 38 | override fun release(key: Double) { 39 | module.release(key) 40 | } 41 | @ReactMethod 42 | override fun setVolume(key: Double, left: Double, right: Double) { 43 | module.setVolume(key,left.toFloat(),right.toFloat()) 44 | } 45 | @ReactMethod 46 | override fun getSystemVolume(callback: Callback) { 47 | module.getSystemVolume(callback) 48 | } 49 | 50 | 51 | @ReactMethod 52 | override fun setLooping(key: Double, looping: Boolean) { 53 | module.setLooping(key,looping) 54 | } 55 | 56 | @ReactMethod 57 | override fun setSpeed(key: Double, speed: Double) { 58 | module.setSpeed(key,speed.toFloat()) 59 | } 60 | @ReactMethod 61 | override fun setPitch(key: Double, pitch: Double) { 62 | module.setPitch(key,pitch.toFloat()) 63 | } 64 | @ReactMethod 65 | override fun setCurrentTime(key: Double, sec: Double) { 66 | module.setCurrentTime(key,sec.toFloat()) 67 | } 68 | @ReactMethod 69 | override fun getCurrentTime(key: Double, callback: Callback) { 70 | module.getCurrentTime(key,callback) 71 | } 72 | @ReactMethod 73 | override fun setSpeakerphoneOn(key: Double, speaker: Boolean) { 74 | module.setSpeakerphoneOn(key,speaker) 75 | } 76 | @ReactMethod 77 | override fun setSystemVolume(value: Double) { 78 | module.setSystemVolume(value.toFloat()) 79 | } 80 | 81 | @ReactMethod 82 | override fun addListener(eventName: String){ 83 | // Keep: Needed for RN built in Event Emitter Calls 84 | } 85 | 86 | @ReactMethod 87 | override fun removeListeners(count: Double){ 88 | // Keep: Needed for RN built in Event Emitter Calls 89 | } 90 | 91 | override fun getName(): String { 92 | return NAME 93 | } 94 | 95 | 96 | 97 | 98 | 99 | companion object { 100 | const val NAME = "RNSound" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /android/src/main/java/com/zmxv/RNSound/SoundPackage.kt: -------------------------------------------------------------------------------- 1 | package com.zmxv.RNSound; 2 | 3 | import com.facebook.react.TurboReactPackage 4 | import com.facebook.react.bridge.ReactApplicationContext 5 | import com.facebook.react.bridge.NativeModule 6 | import com.facebook.react.module.model.ReactModuleInfoProvider 7 | import com.facebook.react.module.model.ReactModuleInfo 8 | import java.util.HashMap 9 | 10 | class SoundPackage : TurboReactPackage() { 11 | override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { 12 | return if (name == SoundModule.NAME) { 13 | SoundModule(reactContext) 14 | } else { 15 | null 16 | } 17 | } 18 | 19 | override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { 20 | return ReactModuleInfoProvider { 21 | val moduleInfos: MutableMap = HashMap() 22 | val isTurboModule: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 23 | val canOverrideExistingModule=false 24 | val needsEagerInit=false 25 | val isCxxModule=false 26 | moduleInfos[SoundModule.NAME] = ReactModuleInfo( 27 | SoundModule.NAME, 28 | SoundModule.NAME, 29 | canOverrideExistingModule, 30 | needsEagerInit, 31 | isCxxModule, 32 | isTurboModule 33 | ) 34 | moduleInfos 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /android/src/newarch/SoundSpec.kt: -------------------------------------------------------------------------------- 1 | package com.zmxv.RNSound; 2 | 3 | 4 | import com.facebook.react.bridge.Callback 5 | import com.facebook.react.bridge.ReactApplicationContext 6 | import com.facebook.react.bridge.ReadableMap 7 | 8 | open class SoundSpec internal constructor(context: ReactApplicationContext) : 9 | NativeSoundAndroidSpec(context) { 10 | private var module = Sound(context) 11 | 12 | override fun prepare(fileName: String, key: Double, options: ReadableMap, callback: Callback) { 13 | module.prepare(fileName,key,options,callback) 14 | } 15 | 16 | 17 | override fun setCategory(value: String, mixWithOthers: Boolean) { 18 | module.setCategory(value,mixWithOthers) 19 | } 20 | 21 | 22 | override fun play(key: Double, callback: Callback) { 23 | module.play(key,callback) 24 | } 25 | 26 | override fun pause(key: Double, callback: Callback) { 27 | module.pause(key,callback) 28 | } 29 | 30 | override fun stop(key: Double, callback: Callback) { 31 | module.stop(key,callback) 32 | } 33 | 34 | override fun reset(key: Double) { 35 | module.reset(key) 36 | } 37 | 38 | override fun release(key: Double) { 39 | module.release(key) 40 | } 41 | 42 | override fun setVolume(key: Double, left: Double, right: Double) { 43 | module.setVolume(key,left.toFloat(),right.toFloat()) 44 | } 45 | 46 | override fun getSystemVolume(callback: Callback) { 47 | module.getSystemVolume(callback) 48 | } 49 | 50 | 51 | 52 | override fun setLooping(key: Double, looping: Boolean) { 53 | module.setLooping(key,looping) 54 | } 55 | override fun setSpeed(key: Double, speed: Double) { 56 | module.setSpeed(key,speed.toFloat()) 57 | } 58 | 59 | override fun setPitch(key: Double, pitch: Double) { 60 | module.setPitch(key,pitch.toFloat()) 61 | } 62 | 63 | override fun setCurrentTime(key: Double, sec: Double) { 64 | module.setCurrentTime(key,sec.toFloat()) 65 | } 66 | 67 | override fun getCurrentTime(key: Double, callback: Callback) { 68 | module.getCurrentTime(key,callback) 69 | } 70 | 71 | override fun setSpeakerphoneOn(key: Double, speaker: Boolean) { 72 | module.setSpeakerphoneOn(key,speaker) 73 | } 74 | 75 | override fun setSystemVolume(value: Double) { 76 | module.setSystemVolume(value.toFloat()) 77 | } 78 | 79 | override fun addListener(eventName: String){ 80 | // Keep: Needed for RN built in Event Emitter Calls 81 | } 82 | 83 | override fun removeListeners(count: Double){ 84 | // Keep: Needed for RN built in Event Emitter Calls 85 | } 86 | 87 | 88 | override fun getName(): String { 89 | return NAME 90 | } 91 | 92 | 93 | 94 | 95 | 96 | companion object { 97 | const val NAME = "RNSound" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /android/src/oldarch/SoundSpec.kt: -------------------------------------------------------------------------------- 1 | package com.zmxv.RNSound; 2 | 3 | import com.facebook.react.bridge.Callback 4 | import com.facebook.react.bridge.ReactApplicationContext 5 | import com.facebook.react.bridge.ReactContextBaseJavaModule 6 | import com.facebook.react.bridge.ReadableMap 7 | 8 | abstract class SoundSpec internal constructor(context: ReactApplicationContext) : 9 | ReactContextBaseJavaModule(context) { 10 | 11 | abstract fun prepare(fileName: String, key: Double, options: ReadableMap, callback: Callback) 12 | abstract fun play(key: Double, callback: Callback) 13 | abstract fun pause(key: Double, callback: Callback) 14 | abstract fun stop(key: Double, callback: Callback) 15 | abstract fun reset(key: Double) 16 | abstract fun release(key: Double) 17 | abstract fun setVolume(key: Double, left: Double, right: Double) 18 | abstract fun getSystemVolume(callback: Callback) 19 | abstract fun setSystemVolume(value: Double) 20 | abstract fun setLooping(key: Double, looping: Boolean) 21 | abstract fun setSpeed(key: Double, speed: Double) 22 | abstract fun setPitch(key: Double, pitch: Double) 23 | abstract fun setCurrentTime(key: Double, sec: Double) 24 | abstract fun getCurrentTime(key: Double, callback: Callback) 25 | abstract fun setSpeakerphoneOn(key: Double, speaker: Boolean) 26 | abstract fun setCategory(value: String, mixWithOthers: Boolean) 27 | abstract fun addListener(eventName: String) 28 | abstract fun removeListeners(count: Double) 29 | 30 | } 31 | -------------------------------------------------------------------------------- /example/.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /example/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@react-native', 4 | }; 5 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | **/.xcode.env.local 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | *.hprof 33 | .cxx/ 34 | *.keystore 35 | !debug.keystore 36 | 37 | # node.js 38 | # 39 | node_modules/ 40 | npm-debug.log 41 | yarn-error.log 42 | 43 | # fastlane 44 | # 45 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 46 | # screenshots whenever they are needed. 47 | # For more information about the recommended setup visit: 48 | # https://docs.fastlane.tools/best-practices/source-control/ 49 | 50 | **/fastlane/report.xml 51 | **/fastlane/Preview.html 52 | **/fastlane/screenshots 53 | **/fastlane/test_output 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | 58 | # Ruby / CocoaPods 59 | **/Pods/ 60 | /vendor/bundle/ 61 | 62 | # Temporary files created by Metro to check the health of the file watcher 63 | .metro-health-check* 64 | 65 | # testing 66 | /coverage 67 | 68 | # Yarn 69 | .yarn/* 70 | !.yarn/patches 71 | !.yarn/plugins 72 | !.yarn/releases 73 | !.yarn/sdks 74 | !.yarn/versions 75 | -------------------------------------------------------------------------------- /example/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSameLine: true, 4 | bracketSpacing: false, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /example/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SoundPlayer from './SoundPlayer'; 3 | 4 | const App = () => { 5 | return ; 6 | }; 7 | 8 | export default App; 9 | -------------------------------------------------------------------------------- /example/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby ">= 2.6.10" 5 | 6 | # Exclude problematic versions of cocoapods and activesupport that causes build failures. 7 | gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' 8 | gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' 9 | gem 'xcodeproj', '< 1.26.0' 10 | -------------------------------------------------------------------------------- /example/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.7) 5 | base64 6 | nkf 7 | rexml 8 | activesupport (7.2.2) 9 | base64 10 | benchmark (>= 0.3) 11 | bigdecimal 12 | concurrent-ruby (~> 1.0, >= 1.3.1) 13 | connection_pool (>= 2.2.5) 14 | drb 15 | i18n (>= 1.6, < 2) 16 | logger (>= 1.4.2) 17 | minitest (>= 5.1) 18 | securerandom (>= 0.3) 19 | tzinfo (~> 2.0, >= 2.0.5) 20 | addressable (2.8.7) 21 | public_suffix (>= 2.0.2, < 7.0) 22 | algoliasearch (1.27.5) 23 | httpclient (~> 2.8, >= 2.8.3) 24 | json (>= 1.5.1) 25 | atomos (0.1.3) 26 | base64 (0.2.0) 27 | benchmark (0.4.0) 28 | bigdecimal (3.1.8) 29 | claide (1.1.0) 30 | cocoapods (1.15.2) 31 | addressable (~> 2.8) 32 | claide (>= 1.0.2, < 2.0) 33 | cocoapods-core (= 1.15.2) 34 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 35 | cocoapods-downloader (>= 2.1, < 3.0) 36 | cocoapods-plugins (>= 1.0.0, < 2.0) 37 | cocoapods-search (>= 1.0.0, < 2.0) 38 | cocoapods-trunk (>= 1.6.0, < 2.0) 39 | cocoapods-try (>= 1.1.0, < 2.0) 40 | colored2 (~> 3.1) 41 | escape (~> 0.0.4) 42 | fourflusher (>= 2.3.0, < 3.0) 43 | gh_inspector (~> 1.0) 44 | molinillo (~> 0.8.0) 45 | nap (~> 1.0) 46 | ruby-macho (>= 2.3.0, < 3.0) 47 | xcodeproj (>= 1.23.0, < 2.0) 48 | cocoapods-core (1.15.2) 49 | activesupport (>= 5.0, < 8) 50 | addressable (~> 2.8) 51 | algoliasearch (~> 1.0) 52 | concurrent-ruby (~> 1.1) 53 | fuzzy_match (~> 2.0.4) 54 | nap (~> 1.0) 55 | netrc (~> 0.11) 56 | public_suffix (~> 4.0) 57 | typhoeus (~> 1.0) 58 | cocoapods-deintegrate (1.0.5) 59 | cocoapods-downloader (2.1) 60 | cocoapods-plugins (1.0.0) 61 | nap 62 | cocoapods-search (1.0.1) 63 | cocoapods-trunk (1.6.0) 64 | nap (>= 0.8, < 2.0) 65 | netrc (~> 0.11) 66 | cocoapods-try (1.2.0) 67 | colored2 (3.1.2) 68 | concurrent-ruby (1.3.4) 69 | connection_pool (2.4.1) 70 | drb (2.2.1) 71 | escape (0.0.4) 72 | ethon (0.16.0) 73 | ffi (>= 1.15.0) 74 | ffi (1.17.0) 75 | fourflusher (2.3.1) 76 | fuzzy_match (2.0.4) 77 | gh_inspector (1.1.3) 78 | httpclient (2.8.3) 79 | i18n (1.14.6) 80 | concurrent-ruby (~> 1.0) 81 | json (2.8.2) 82 | logger (1.6.1) 83 | minitest (5.25.1) 84 | molinillo (0.8.0) 85 | nanaimo (0.3.0) 86 | nap (1.1.0) 87 | netrc (0.11.0) 88 | nkf (0.2.0) 89 | public_suffix (4.0.7) 90 | rexml (3.3.9) 91 | ruby-macho (2.5.1) 92 | securerandom (0.3.2) 93 | typhoeus (1.4.1) 94 | ethon (>= 0.9.0) 95 | tzinfo (2.0.6) 96 | concurrent-ruby (~> 1.0) 97 | xcodeproj (1.25.1) 98 | CFPropertyList (>= 2.3.3, < 4.0) 99 | atomos (~> 0.1.3) 100 | claide (>= 1.0.2, < 2.0) 101 | colored2 (~> 3.1) 102 | nanaimo (~> 0.3.0) 103 | rexml (>= 3.3.6, < 4.0) 104 | 105 | PLATFORMS 106 | ruby 107 | 108 | DEPENDENCIES 109 | activesupport (>= 6.1.7.5, != 7.1.0) 110 | cocoapods (>= 1.13, != 1.15.1, != 1.15.0) 111 | xcodeproj (< 1.26.0) 112 | 113 | RUBY VERSION 114 | ruby 3.3.6p108 115 | 116 | BUNDLED WITH 117 | 2.5.23 118 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli). 2 | 3 | # Getting Started 4 | 5 | >**Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding. 6 | 7 | ## Step 1: Start the Metro Server 8 | 9 | First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native. 10 | 11 | To start Metro, run the following command from the _root_ of your React Native project: 12 | 13 | ```bash 14 | # using npm 15 | npm start 16 | 17 | # OR using Yarn 18 | yarn start 19 | ``` 20 | 21 | ## Step 2: Start your Application 22 | 23 | Let Metro Bundler run in its _own_ terminal. Open a _new_ terminal from the _root_ of your React Native project. Run the following command to start your _Android_ or _iOS_ app: 24 | 25 | ### For Android 26 | 27 | ```bash 28 | # using npm 29 | npm run android 30 | 31 | # OR using Yarn 32 | yarn android 33 | ``` 34 | 35 | ### For iOS 36 | 37 | ```bash 38 | # using npm 39 | npm run ios 40 | 41 | # OR using Yarn 42 | yarn ios 43 | ``` 44 | 45 | If everything is set up _correctly_, you should see your new app running in your _Android Emulator_ or _iOS Simulator_ shortly provided you have set up your emulator/simulator correctly. 46 | 47 | This is one way to run your app — you can also run it directly from within Android Studio and Xcode respectively. 48 | 49 | ## Step 3: Modifying your App 50 | 51 | Now that you have successfully run the app, let's modify it. 52 | 53 | 1. Open `App.tsx` in your text editor of choice and edit some lines. 54 | 2. For **Android**: Press the R key twice or select **"Reload"** from the **Developer Menu** (Ctrl + M (on Window and Linux) or Cmd ⌘ + M (on macOS)) to see your changes! 55 | 56 | For **iOS**: Hit Cmd ⌘ + R in your iOS Simulator to reload the app and see your changes! 57 | 58 | ## Congratulations! :tada: 59 | 60 | You've successfully run and modified your React Native App. :partying_face: 61 | 62 | ### Now what? 63 | 64 | - If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps). 65 | - If you're curious to learn more about React Native, check out the [Introduction to React Native](https://reactnative.dev/docs/getting-started). 66 | 67 | # Troubleshooting 68 | 69 | If you can't get this to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. 70 | 71 | # Learn More 72 | 73 | To learn more about React Native, take a look at the following resources: 74 | 75 | - [React Native Website](https://reactnative.dev) - learn more about React Native. 76 | - [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment. 77 | - [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**. 78 | - [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts. 79 | - [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native. 80 | -------------------------------------------------------------------------------- /example/SoundPlayer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { 4 | Text, 5 | View, 6 | StyleSheet, 7 | SafeAreaView, 8 | Image, 9 | type ImageStyle, 10 | StatusBar, 11 | Alert, 12 | TouchableOpacity, 13 | ActivityIndicator, 14 | } from 'react-native'; 15 | 16 | import Slider from '@react-native-community/slider'; 17 | import Sound from 'react-native-sound'; 18 | 19 | const getImageStyle = (size: number, tinColor: string = '#000') => { 20 | return { 21 | width: size, 22 | height: size, 23 | tinColor, 24 | } as ImageStyle; 25 | }; 26 | Sound.setCategory('Playback'); 27 | const secondsToMMSS = (seconds: number) => 28 | new Date(seconds * 1000).toISOString().substring(14, 19); 29 | 30 | export default () => { 31 | const [currentTime, setCurrentTime] = React.useState(0); 32 | const [duration, setDuration] = React.useState(0); 33 | const [isPlaying, setIsPlaying] = React.useState(false); 34 | const sound = React.useRef(); 35 | const timerRef = React.useRef(); 36 | const [isLoading, setIsLoading] = React.useState(true); 37 | 38 | React.useEffect(() => { 39 | sound.current = new Sound( 40 | 'https://cdn.pixabay.com/download/audio/2024/10/27/audio_694158870e.mp3?filename=abnormal-for-you-255737.mp3', 41 | Sound.MAIN_BUNDLE, 42 | (error, props) => { 43 | setIsLoading(false); 44 | if (error) { 45 | Alert.alert('Error', 'failed to load the sound' + error); 46 | return; 47 | } 48 | if (props.duration) { 49 | setDuration(props.duration); 50 | } 51 | }, 52 | ); 53 | }, []); 54 | 55 | const stopListening = () => { 56 | if (timerRef.current) { 57 | clearInterval(timerRef.current); 58 | } 59 | }; 60 | 61 | const startListening = () => { 62 | stopListening(); 63 | timerRef.current = setInterval(() => { 64 | if (sound.current) { 65 | sound.current.getCurrentTime(setCurrentTime); 66 | } 67 | }, 1000); 68 | }; 69 | 70 | const onPressPlayPause = () => { 71 | if (sound.current) { 72 | if (isPlaying) { 73 | sound.current.pause(); 74 | setIsPlaying(false); 75 | stopListening(); 76 | } else { 77 | sound.current.play(); 78 | setIsPlaying(true); 79 | startListening(); 80 | } 81 | } 82 | }; 83 | 84 | const onPressBackward = () => { 85 | if (sound.current) { 86 | sound.current.getCurrentTime(sec => { 87 | sound.current?.setCurrentTime(sec - 10); 88 | }); 89 | } 90 | }; 91 | 92 | const onPressFastFoward = () => { 93 | if (sound.current) { 94 | sound.current.getCurrentTime(sec => { 95 | sound.current?.setCurrentTime(sec + 10); 96 | }); 97 | } 98 | }; 99 | 100 | return ( 101 | 102 | 103 | Now Playing 104 | 105 | 106 | 110 | {isLoading && ( 111 | 116 | )} 117 | 118 | 119 | #02 - Practice 120 | 121 | Digital Marketing - By Setup Cast 122 | 123 | 124 | 125 | 126 | {secondsToMMSS(currentTime)} 127 | { 131 | sound.current?.setCurrentTime(value); 132 | }} 133 | maximumValue={duration} 134 | minimumTrackTintColor="#e75480" 135 | maximumTrackTintColor="#d3d3d3" 136 | thumbTintColor="#e75480" 137 | value={currentTime} 138 | /> 139 | {secondsToMMSS(duration)} 140 | 141 | 142 | 143 | 144 | 148 | 149 | 150 | 158 | 159 | 160 | 164 | 165 | 166 | 167 | ); 168 | }; 169 | 170 | const styles = StyleSheet.create({ 171 | container: { 172 | flex: 1, 173 | backgroundColor: 'white', 174 | justifyContent: 'space-evenly', 175 | }, 176 | 177 | now_playing_text: { 178 | fontSize: 19, 179 | alignSelf: 'center', 180 | marginVertical: 15, 181 | }, 182 | 183 | image_view: { 184 | alignSelf: 'center', 185 | borderRadius: 10, 186 | }, 187 | name_of_song_View: { 188 | alignSelf: 'center', 189 | width: '100%', 190 | alignItems: 'center', 191 | justifyContent: 'center', 192 | marginVertical: 20, 193 | rowGap: 20, 194 | }, 195 | name_of_song_Text1: { 196 | fontSize: 19, 197 | fontWeight: '500', 198 | }, 199 | name_of_song_Text2: { 200 | color: '#808080', 201 | }, 202 | slider_view: { 203 | width: '100%', 204 | alignItems: 'center', 205 | flexDirection: 'row', 206 | justifyContent: 'space-between', 207 | paddingHorizontal: 15, 208 | marginVertical: 20, 209 | }, 210 | slider_style: { 211 | flex: 1, 212 | }, 213 | slider_time: { 214 | fontSize: 15, 215 | color: '#808080', 216 | }, 217 | functions_view: { 218 | flexDirection: 'row', 219 | width: '100%', 220 | alignItems: 'center', 221 | justifyContent: 'space-evenly', 222 | }, 223 | }); 224 | -------------------------------------------------------------------------------- /example/__tests__/App.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import 'react-native'; 6 | import React from 'react'; 7 | import App from '../App'; 8 | 9 | // Note: import explicitly to use the types shipped with jest. 10 | import {it} from '@jest/globals'; 11 | 12 | // Note: test renderer must be required after react-native. 13 | import renderer from 'react-test-renderer'; 14 | 15 | it('renders correctly', () => { 16 | renderer.create(); 17 | }); 18 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | apply plugin: "org.jetbrains.kotlin.android" 3 | apply plugin: "com.facebook.react" 4 | 5 | /** 6 | * This is the configuration block to customize your React Native Android app. 7 | * By default you don't need to apply any configuration, just uncomment the lines you need. 8 | */ 9 | react { 10 | /* Folders */ 11 | // The root of your project, i.e. where "package.json" lives. Default is '../..' 12 | // root = file("../../") 13 | // The folder where the react-native NPM package is. Default is ../../node_modules/react-native 14 | // reactNativeDir = file("../../node_modules/react-native") 15 | // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen 16 | // codegenDir = file("../../node_modules/@react-native/codegen") 17 | // The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js 18 | // cliFile = file("../../node_modules/react-native/cli.js") 19 | 20 | /* Variants */ 21 | // The list of variants to that are debuggable. For those we're going to 22 | // skip the bundling of the JS bundle and the assets. By default is just 'debug'. 23 | // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. 24 | // debuggableVariants = ["liteDebug", "prodDebug"] 25 | 26 | /* Bundling */ 27 | // A list containing the node command and its flags. Default is just 'node'. 28 | // nodeExecutableAndArgs = ["node"] 29 | // 30 | // The command to run when bundling. By default is 'bundle' 31 | // bundleCommand = "ram-bundle" 32 | // 33 | // The path to the CLI configuration file. Default is empty. 34 | // bundleConfig = file(../rn-cli.config.js) 35 | // 36 | // The name of the generated asset file containing your JS bundle 37 | // bundleAssetName = "MyApplication.android.bundle" 38 | // 39 | // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' 40 | // entryFile = file("../js/MyApplication.android.js") 41 | // 42 | // A list of extra flags to pass to the 'bundle' commands. 43 | // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle 44 | // extraPackagerArgs = [] 45 | 46 | /* Hermes Commands */ 47 | // The hermes compiler command to run. By default it is 'hermesc' 48 | // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" 49 | // 50 | // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" 51 | // hermesFlags = ["-O", "-output-source-map"] 52 | 53 | /* Autolinking */ 54 | autolinkLibrariesWithApp() 55 | } 56 | 57 | /** 58 | * Set this to true to Run Proguard on Release builds to minify the Java bytecode. 59 | */ 60 | def enableProguardInReleaseBuilds = false 61 | 62 | /** 63 | * The preferred build flavor of JavaScriptCore (JSC) 64 | * 65 | * For example, to use the international variant, you can use: 66 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 67 | * 68 | * The international variant includes ICU i18n library and necessary data 69 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 70 | * give correct results when using with locales other than en-US. Note that 71 | * this variant is about 6MiB larger per architecture than default. 72 | */ 73 | def jscFlavor = 'org.webkit:android-jsc:+' 74 | 75 | android { 76 | ndkVersion rootProject.ext.ndkVersion 77 | buildToolsVersion rootProject.ext.buildToolsVersion 78 | compileSdk rootProject.ext.compileSdkVersion 79 | 80 | namespace "com.example" 81 | defaultConfig { 82 | applicationId "com.example" 83 | minSdkVersion rootProject.ext.minSdkVersion 84 | targetSdkVersion rootProject.ext.targetSdkVersion 85 | versionCode 1 86 | versionName "1.0" 87 | } 88 | signingConfigs { 89 | debug { 90 | storeFile file('debug.keystore') 91 | storePassword 'android' 92 | keyAlias 'androiddebugkey' 93 | keyPassword 'android' 94 | } 95 | } 96 | buildTypes { 97 | debug { 98 | signingConfig signingConfigs.debug 99 | } 100 | release { 101 | // Caution! In production, you need to generate your own keystore file. 102 | // see https://reactnative.dev/docs/signed-apk-android. 103 | signingConfig signingConfigs.debug 104 | minifyEnabled enableProguardInReleaseBuilds 105 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 106 | } 107 | } 108 | } 109 | 110 | dependencies { 111 | // The version of react-native is set by the React Native Gradle Plugin 112 | implementation("com.facebook.react:react-android") 113 | 114 | if (hermesEnabled.toBoolean()) { 115 | implementation("com.facebook.react:hermes-android") 116 | } else { 117 | implementation jscFlavor 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /example/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/android/app/debug.keystore -------------------------------------------------------------------------------- /example/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example 2 | 3 | import com.facebook.react.ReactActivity 4 | import com.facebook.react.ReactActivityDelegate 5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled 6 | import com.facebook.react.defaults.DefaultReactActivityDelegate 7 | 8 | class MainActivity : ReactActivity() { 9 | 10 | /** 11 | * Returns the name of the main component registered from JavaScript. This is used to schedule 12 | * rendering of the component. 13 | */ 14 | override fun getMainComponentName(): String = "example" 15 | 16 | /** 17 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] 18 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] 19 | */ 20 | override fun createReactActivityDelegate(): ReactActivityDelegate = 21 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) 22 | } 23 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.example 2 | 3 | import android.app.Application 4 | import com.facebook.react.PackageList 5 | import com.facebook.react.ReactApplication 6 | import com.facebook.react.ReactHost 7 | import com.facebook.react.ReactNativeHost 8 | import com.facebook.react.ReactPackage 9 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load 10 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost 11 | import com.facebook.react.defaults.DefaultReactNativeHost 12 | import com.facebook.react.soloader.OpenSourceMergedSoMapping 13 | import com.facebook.soloader.SoLoader 14 | 15 | class MainApplication : Application(), ReactApplication { 16 | 17 | override val reactNativeHost: ReactNativeHost = 18 | object : DefaultReactNativeHost(this) { 19 | override fun getPackages(): List = 20 | PackageList(this).packages.apply { 21 | // Packages that cannot be autolinked yet can be added manually here, for example: 22 | // add(MyReactNativePackage()) 23 | } 24 | 25 | override fun getJSMainModuleName(): String = "index" 26 | 27 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG 28 | 29 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 30 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED 31 | } 32 | 33 | override val reactHost: ReactHost 34 | get() = getDefaultReactHost(applicationContext, reactNativeHost) 35 | 36 | override fun onCreate() { 37 | super.onCreate() 38 | SoLoader.init(this, OpenSourceMergedSoMapping) 39 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 40 | // If you opted-in for the New Architecture, we load the native entry point for this app. 41 | load() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | 23 | 24 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | example 3 | 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | buildToolsVersion = "35.0.0" 4 | minSdkVersion = 24 5 | compileSdkVersion = 35 6 | targetSdkVersion = 34 7 | ndkVersion = "26.1.10909125" 8 | kotlinVersion = "1.9.24" 9 | } 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | dependencies { 15 | classpath("com.android.tools.build:gradle") 16 | classpath("com.facebook.react:react-native-gradle-plugin") 17 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") 18 | } 19 | } 20 | 21 | apply plugin: "com.facebook.react.rootproject" 22 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | 25 | # Use this property to specify which architecture you want to build. 26 | # You can also override it from the CLI using 27 | # ./gradlew -PreactNativeArchitectures=x86_64 28 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 29 | 30 | # Use this property to enable support to the new architecture. 31 | # This will allow you to use TurboModules and the Fabric render in 32 | # your application. You should enable this flag either if you want 33 | # to write custom TurboModules/Fabric components OR use libraries that 34 | # are providing them. 35 | newArchEnabled=true 36 | 37 | # Use this property to enable or disable the Hermes JS engine. 38 | # If set to false, you will be using JSC instead. 39 | hermesEnabled=true 40 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /example/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s 90 | ' "$PWD" ) || exit 91 | 92 | # Use the maximum available, or set MAX_FD != -1 to use that value. 93 | MAX_FD=maximum 94 | 95 | warn () { 96 | echo "$*" 97 | } >&2 98 | 99 | die () { 100 | echo 101 | echo "$*" 102 | echo 103 | exit 1 104 | } >&2 105 | 106 | # OS specific support (must be 'true' or 'false'). 107 | cygwin=false 108 | msys=false 109 | darwin=false 110 | nonstop=false 111 | case "$( uname )" in #( 112 | CYGWIN* ) cygwin=true ;; #( 113 | Darwin* ) darwin=true ;; #( 114 | MSYS* | MINGW* ) msys=true ;; #( 115 | NONSTOP* ) nonstop=true ;; 116 | esac 117 | 118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 119 | 120 | 121 | # Determine the Java command to use to start the JVM. 122 | if [ -n "$JAVA_HOME" ] ; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD=$JAVA_HOME/jre/sh/java 126 | else 127 | JAVACMD=$JAVA_HOME/bin/java 128 | fi 129 | if [ ! -x "$JAVACMD" ] ; then 130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 131 | 132 | Please set the JAVA_HOME variable in your environment to match the 133 | location of your Java installation." 134 | fi 135 | else 136 | JAVACMD=java 137 | if ! command -v java >/dev/null 2>&1 138 | then 139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 140 | 141 | Please set the JAVA_HOME variable in your environment to match the 142 | location of your Java installation." 143 | fi 144 | fi 145 | 146 | # Increase the maximum file descriptors if we can. 147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 148 | case $MAX_FD in #( 149 | max*) 150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 151 | # shellcheck disable=SC2039,SC3045 152 | MAX_FD=$( ulimit -H -n ) || 153 | warn "Could not query maximum file descriptor limit" 154 | esac 155 | case $MAX_FD in #( 156 | '' | soft) :;; #( 157 | *) 158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 159 | # shellcheck disable=SC2039,SC3045 160 | ulimit -n "$MAX_FD" || 161 | warn "Could not set maximum file descriptor limit to $MAX_FD" 162 | esac 163 | fi 164 | 165 | # Collect all arguments for the java command, stacking in reverse order: 166 | # * args from the command line 167 | # * the main class name 168 | # * -classpath 169 | # * -D...appname settings 170 | # * --module-path (only if needed) 171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 172 | 173 | # For Cygwin or MSYS, switch paths to Windows format before running java 174 | if "$cygwin" || "$msys" ; then 175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 177 | 178 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 179 | 180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 181 | for arg do 182 | if 183 | case $arg in #( 184 | -*) false ;; # don't mess with options #( 185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 186 | [ -e "$t" ] ;; #( 187 | *) false ;; 188 | esac 189 | then 190 | arg=$( cygpath --path --ignore --mixed "$arg" ) 191 | fi 192 | # Roll the args list around exactly as many times as the number of 193 | # args, so each arg winds up back in the position where it started, but 194 | # possibly modified. 195 | # 196 | # NB: a `for` loop captures its iteration list before it begins, so 197 | # changing the positional parameters here affects neither the number of 198 | # iterations, nor the values presented in `arg`. 199 | shift # remove old arg 200 | set -- "$@" "$arg" # push replacement arg 201 | done 202 | fi 203 | 204 | 205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 207 | 208 | # Collect all arguments for the java command: 209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 210 | # and any embedded shellness will be escaped. 211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 212 | # treated as '${Hostname}' itself on the command line. 213 | 214 | set -- \ 215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 216 | -classpath "$CLASSPATH" \ 217 | org.gradle.wrapper.GradleWrapperMain \ 218 | "$@" 219 | 220 | # Stop when "xargs" is not available. 221 | if ! command -v xargs >/dev/null 2>&1 222 | then 223 | die "xargs is not available" 224 | fi 225 | 226 | # Use "xargs" to parse quoted args. 227 | # 228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 229 | # 230 | # In Bash we could simply go: 231 | # 232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 233 | # set -- "${ARGS[@]}" "$@" 234 | # 235 | # but POSIX shell has neither arrays nor command substitution, so instead we 236 | # post-process each arg (as a line of input to sed) to backslash-escape any 237 | # character that might be a shell metacharacter, then use eval to reverse 238 | # that process (while maintaining the separation between arguments), and wrap 239 | # the whole thing up as a single "set" statement. 240 | # 241 | # This will of course break if any of these variables contains a newline or 242 | # an unmatched quote. 243 | # 244 | 245 | eval "set -- $( 246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 247 | xargs -n1 | 248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 249 | tr '\n' ' ' 250 | )" '"$@"' 251 | 252 | exec "$JAVACMD" "$@" 253 | -------------------------------------------------------------------------------- /example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } 2 | plugins { id("com.facebook.react.settings") } 3 | extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } 4 | rootProject.name = 'example' 5 | include ':app' 6 | includeBuild('../node_modules/@react-native/gradle-plugin') 7 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "displayName": "example" 4 | } 5 | -------------------------------------------------------------------------------- /example/assets/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/assets/back.png -------------------------------------------------------------------------------- /example/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/assets/favicon.png -------------------------------------------------------------------------------- /example/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/assets/icon.png -------------------------------------------------------------------------------- /example/assets/left_foward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/assets/left_foward.png -------------------------------------------------------------------------------- /example/assets/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/assets/logo.jpg -------------------------------------------------------------------------------- /example/assets/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/assets/menu.png -------------------------------------------------------------------------------- /example/assets/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/assets/pause.png -------------------------------------------------------------------------------- /example/assets/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/assets/play.png -------------------------------------------------------------------------------- /example/assets/right_foward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/assets/right_foward.png -------------------------------------------------------------------------------- /example/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmxv/react-native-sound/bc92152667a628120374c1324fc9e44f697c3192/example/assets/splash.png -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:@react-native/babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import {AppRegistry} from 'react-native'; 6 | import App from './App'; 7 | import {name as appName} from './app.json'; 8 | 9 | AppRegistry.registerComponent(appName, () => App); 10 | -------------------------------------------------------------------------------- /example/ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Resolve react_native_pods.rb with node to allow for hoisting 2 | require Pod::Executable.execute_command('node', ['-p', 3 | 'require.resolve( 4 | "react-native/scripts/react_native_pods.rb", 5 | {paths: [process.argv[1]]}, 6 | )', __dir__]).strip 7 | 8 | platform :ios, min_ios_version_supported 9 | prepare_react_native_project! 10 | 11 | linkage = ENV['USE_FRAMEWORKS'] 12 | if linkage != nil 13 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green 14 | use_frameworks! :linkage => linkage.to_sym 15 | end 16 | 17 | target 'example' do 18 | config = use_native_modules! 19 | 20 | use_react_native!( 21 | :path => config[:reactNativePath], 22 | # An absolute path to your application root. 23 | :app_path => "#{Pod::Config.instance.installation_root}/.." 24 | ) 25 | 26 | target 'exampleTests' do 27 | inherit! :complete 28 | # Pods for testing 29 | end 30 | 31 | post_install do |installer| 32 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 33 | react_native_post_install( 34 | installer, 35 | config[:reactNativePath], 36 | :mac_catalyst_enabled => false, 37 | # :ccache_enabled => true 38 | ) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /example/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /example/ios/example.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/example/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : RCTAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/example/AppDelegate.mm: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | 5 | @implementation AppDelegate 6 | 7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 8 | { 9 | self.moduleName = @"example"; 10 | // You can add your custom initial props in the dictionary below. 11 | // They will be passed down to the ViewController used by React Native. 12 | self.initialProps = @{}; 13 | 14 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 15 | } 16 | 17 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 18 | { 19 | return [self bundleURL]; 20 | } 21 | 22 | - (NSURL *)bundleURL 23 | { 24 | #if DEBUG 25 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; 26 | #else 27 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 28 | #endif 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /example/ios/example/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "scale" : "1x", 46 | "size" : "1024x1024" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/ios/example/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(CURRENT_PROJECT_VERSION) 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | 30 | NSAllowsArbitraryLoads 31 | 32 | NSAllowsLocalNetworking 33 | 34 | 35 | NSLocationWhenInUseUsageDescription 36 | 37 | UILaunchStoryboardName 38 | LaunchScreen 39 | UIRequiredDeviceCapabilities 40 | 41 | arm64 42 | 43 | UISupportedInterfaceOrientations 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | UIViewControllerBasedStatusBarAppearance 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /example/ios/example/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/ios/example/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryFileTimestamp 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | C617.1 13 | 14 | 15 | 16 | NSPrivacyAccessedAPIType 17 | NSPrivacyAccessedAPICategoryUserDefaults 18 | NSPrivacyAccessedAPITypeReasons 19 | 20 | CA92.1 21 | 22 | 23 | 24 | NSPrivacyAccessedAPIType 25 | NSPrivacyAccessedAPICategorySystemBootTime 26 | NSPrivacyAccessedAPITypeReasons 27 | 28 | 35F9.1 29 | 30 | 31 | 32 | NSPrivacyCollectedDataTypes 33 | 34 | NSPrivacyTracking 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/example/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | @autoreleasepool { 8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/ios/exampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /example/ios/exampleTests/exampleTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | #import 6 | 7 | #define TIMEOUT_SECONDS 600 8 | #define TEXT_TO_LOOK_FOR @"Welcome to React" 9 | 10 | @interface exampleTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation exampleTests 15 | 16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test 17 | { 18 | if (test(view)) { 19 | return YES; 20 | } 21 | for (UIView *subview in [view subviews]) { 22 | if ([self findSubviewInView:subview matching:test]) { 23 | return YES; 24 | } 25 | } 26 | return NO; 27 | } 28 | 29 | - (void)testRendersWelcomeScreen 30 | { 31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 33 | BOOL foundElement = NO; 34 | 35 | __block NSString *redboxError = nil; 36 | #ifdef DEBUG 37 | RCTSetLogFunction( 38 | ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 39 | if (level >= RCTLogLevelError) { 40 | redboxError = message; 41 | } 42 | }); 43 | #endif 44 | 45 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 46 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 47 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 48 | 49 | foundElement = [self findSubviewInView:vc.view 50 | matching:^BOOL(UIView *view) { 51 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 52 | return YES; 53 | } 54 | return NO; 55 | }]; 56 | } 57 | 58 | #ifdef DEBUG 59 | RCTSetLogFunction(RCTDefaultLogFunction); 60 | #endif 61 | 62 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 63 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /example/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'react-native', 3 | }; 4 | -------------------------------------------------------------------------------- /example/metro.config.js: -------------------------------------------------------------------------------- 1 | const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); 2 | 3 | /** 4 | * Metro configuration 5 | * https://reactnative.dev/docs/metro 6 | * 7 | * @type {import('metro-config').MetroConfig} 8 | */ 9 | const config = {}; 10 | 11 | module.exports = mergeConfig(getDefaultConfig(__dirname), config); 12 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "lint": "eslint .", 9 | "start": "react-native start", 10 | "test": "jest" 11 | }, 12 | "dependencies": { 13 | "@react-native-community/slider": "^4.5.5", 14 | "react": "18.3.1", 15 | "react-native": "0.76.3", 16 | "react-native-sound": "../" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.25.2", 20 | "@babel/preset-env": "^7.25.3", 21 | "@babel/runtime": "^7.25.0", 22 | "@react-native-community/cli": "15.0.1", 23 | "@react-native-community/cli-platform-android": "15.0.1", 24 | "@react-native-community/cli-platform-ios": "15.0.1", 25 | "@react-native/babel-preset": "0.76.3", 26 | "@react-native/eslint-config": "0.76.3", 27 | "@react-native/metro-config": "0.76.3", 28 | "@react-native/typescript-config": "0.76.3", 29 | "@types/react": "^18.2.6", 30 | "@types/react-test-renderer": "^18.0.0", 31 | "babel-jest": "^29.6.3", 32 | "eslint": "^8.19.0", 33 | "jest": "^29.6.3", 34 | "prettier": "2.8.8", 35 | "react-test-renderer": "18.3.1", 36 | "typescript": "5.0.4" 37 | }, 38 | "engines": { 39 | "node": ">=18" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@react-native/typescript-config/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /ios/RNSound.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #ifdef RCT_NEW_ARCH_ENABLED 5 | #import 6 | 7 | @interface RNSound : RCTEventEmitter 8 | #else 9 | #import 10 | @interface RNSound : RCTEventEmitter 11 | #endif 12 | 13 | @property (nonatomic, assign) double _key; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ios/RNSound.mm: -------------------------------------------------------------------------------- 1 | #import "RNSound.h" 2 | 3 | #if __has_include("RCTUtils.h") 4 | #import "RCTUtils.h" 5 | #else 6 | #import 7 | #endif 8 | 9 | @implementation RNSound { 10 | NSMutableDictionary *_playerPool; 11 | NSMutableDictionary *_callbackPool; 12 | double _key; // Add this line to declare _key 13 | } 14 | 15 | RCT_EXPORT_MODULE() 16 | @synthesize _key = _key; 17 | 18 | - (NSArray *)supportedEvents { 19 | return @[ 20 | @"onPlayChange" 21 | ]; 22 | } 23 | 24 | #pragma mark - Audio Session Management 25 | 26 | - (void)audioSessionChangeObserver:(NSNotification *)notification { 27 | NSDictionary *userInfo = notification.userInfo; 28 | 29 | 30 | AVAudioSessionRouteChangeReason routeChangeReason = 31 | (AVAudioSessionRouteChangeReason)[userInfo[@"AVAudioSessionRouteChangeReasonKey"] longValue]; 32 | AVAudioSessionInterruptionType interruptionType = 33 | (AVAudioSessionInterruptionType)[userInfo[@"AVAudioSessionInterruptionTypeKey"] longValue]; 34 | 35 | AVAudioPlayer *player = [self playerForKey:self._key]; 36 | 37 | if (interruptionType == AVAudioSessionInterruptionTypeEnded && player) { 38 | [player play]; 39 | [self setOnPlay:YES forPlayerKey:self._key]; 40 | } else if (routeChangeReason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable && player) { 41 | [player pause]; 42 | [self setOnPlay:NO forPlayerKey:self._key]; 43 | } else if (interruptionType == AVAudioSessionInterruptionTypeBegan && player) { 44 | [player pause]; 45 | [self setOnPlay:NO forPlayerKey:self._key]; 46 | } 47 | } 48 | 49 | #pragma mark - Player and Callback Management 50 | 51 | - (NSMutableDictionary *)playerPool { 52 | if (!_playerPool) { 53 | _playerPool = [NSMutableDictionary new]; 54 | } 55 | return _playerPool; 56 | } 57 | 58 | - (NSMutableDictionary *)callbackPool { 59 | if (!_callbackPool) { 60 | _callbackPool = [NSMutableDictionary new]; 61 | } 62 | return _callbackPool; 63 | } 64 | 65 | - (AVAudioPlayer *)playerForKey:(double)key { 66 | NSNumber *keyNumber = @(key); 67 | return [[self playerPool] objectForKey:keyNumber]; 68 | } 69 | 70 | - (NSNumber *)keyForPlayer:(AVAudioPlayer *)player { 71 | return [[[self playerPool] allKeysForObject:player] firstObject]; 72 | } 73 | 74 | - (RCTResponseSenderBlock)callbackForKey:(NSNumber *)key { 75 | return [[self callbackPool] objectForKey:key]; 76 | } 77 | 78 | - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player 79 | successfully:(BOOL)flag { 80 | @synchronized(self) { 81 | NSNumber *key = [self keyForPlayer:player]; 82 | if (key == nil) 83 | return; 84 | [self setOnPlay:NO forPlayerKey:self._key]; 85 | RCTResponseSenderBlock callback = [self callbackForKey:key]; 86 | if (callback) { 87 | callback( 88 | [NSArray arrayWithObjects:[NSNumber numberWithBool:flag], nil]); 89 | [[self callbackPool] removeObjectForKey:key]; 90 | } 91 | } 92 | } 93 | 94 | #pragma mark - File and Directory Access 95 | 96 | - (NSString *)getDirectory:(NSSearchPathDirectory)directory { 97 | return [NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES) firstObject]; 98 | } 99 | 100 | - (NSDictionary *)constantsToExport { 101 | return @{ 102 | @"IsAndroid": @NO, 103 | @"MainBundlePath": [[NSBundle mainBundle] bundlePath], 104 | @"NSDocumentDirectory": [self getDirectory:NSDocumentDirectory], 105 | @"NSLibraryDirectory": [self getDirectory:NSLibraryDirectory], 106 | @"NSCachesDirectory": [self getDirectory:NSCachesDirectory] 107 | }; 108 | } 109 | 110 | #pragma mark - Audio Session Configuration 111 | 112 | RCT_EXPORT_METHOD(enable : (BOOL)enabled) { 113 | AVAudioSession *session = [AVAudioSession sharedInstance]; 114 | [session setCategory:AVAudioSessionCategoryAmbient error:nil]; 115 | [session setActive:enabled error:nil]; 116 | } 117 | 118 | RCT_EXPORT_METHOD(setActive:(BOOL)active) { 119 | AVAudioSession *session = [AVAudioSession sharedInstance]; 120 | [session setActive:active error:nil]; 121 | } 122 | 123 | RCT_EXPORT_METHOD(setMode:(NSString *)modeName) { 124 | AVAudioSession *session = [AVAudioSession sharedInstance]; 125 | NSString *mode = [self modeForName:modeName]; 126 | if (mode) { 127 | [session setMode:mode error:nil]; 128 | } 129 | } 130 | 131 | - (NSString *)modeForName:(NSString *)modeName { 132 | NSDictionary *modes = @{ 133 | @"Default": AVAudioSessionModeDefault, 134 | @"VoiceChat": AVAudioSessionModeVoiceChat, 135 | @"VideoChat": AVAudioSessionModeVideoChat, 136 | @"GameChat": AVAudioSessionModeGameChat, 137 | @"VideoRecording": AVAudioSessionModeVideoRecording, 138 | @"Measurement": AVAudioSessionModeMeasurement, 139 | @"MoviePlayback": AVAudioSessionModeMoviePlayback, 140 | @"SpokenAudio": AVAudioSessionModeSpokenAudio 141 | }; 142 | return modes[modeName]; 143 | } 144 | 145 | RCT_EXPORT_METHOD(setCategory:(NSString *)categoryName mixWithOthers:(NSNumber *)mixWithOthers) { 146 | AVAudioSession *session = [AVAudioSession sharedInstance]; 147 | NSString *category = [self categoryForName:categoryName]; 148 | 149 | if (category) { 150 | if (mixWithOthers.boolValue) { 151 | [session setCategory:category 152 | withOptions:AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryOptionAllowBluetooth 153 | error:nil]; 154 | } else { 155 | [session setCategory:category error:nil]; 156 | } 157 | } 158 | } 159 | 160 | - (NSString *)categoryForName:(NSString *)categoryName { 161 | NSDictionary *categories = @{ 162 | @"Ambient": AVAudioSessionCategoryAmbient, 163 | @"SoloAmbient": AVAudioSessionCategorySoloAmbient, 164 | @"Playback": AVAudioSessionCategoryPlayback, 165 | @"Record": AVAudioSessionCategoryRecord, 166 | @"PlayAndRecord": AVAudioSessionCategoryPlayAndRecord, 167 | @"MultiRoute": AVAudioSessionCategoryMultiRoute 168 | }; 169 | return categories[categoryName]; 170 | } 171 | 172 | RCT_EXPORT_METHOD(enableInSilenceMode:(BOOL)enabled) { 173 | AVAudioSession *session = [AVAudioSession sharedInstance]; 174 | [session setCategory:AVAudioSessionCategoryPlayback error:nil]; 175 | [session setActive:enabled error:nil]; 176 | } 177 | 178 | #pragma mark - Audio Control Methods 179 | 180 | RCT_EXPORT_METHOD(prepare:(NSString *)fileName key:(double)key options:(JS::NativeSoundIOS::SoundOptionTypes &)options callback:(RCTResponseSenderBlock)callback ) { 181 | NSError *error; 182 | NSURL *fileNameUrl; 183 | AVAudioPlayer *player; 184 | 185 | if ([fileName hasPrefix:@"http"]) { 186 | fileNameUrl = [NSURL URLWithString:fileName]; 187 | NSData *data = [NSData dataWithContentsOfURL:fileNameUrl]; 188 | player = [[AVAudioPlayer alloc] initWithData:data error:&error]; 189 | } else if ([fileName hasPrefix:@"ipod-library://"]) { 190 | fileNameUrl = [NSURL URLWithString:fileName]; 191 | player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileNameUrl 192 | error:&error]; 193 | } else { 194 | fileNameUrl = [NSURL URLWithString:fileName]; 195 | player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileNameUrl error:&error]; 196 | } 197 | 198 | if (player) { 199 | @synchronized(self) { 200 | player.delegate = self; 201 | player.enableRate = YES; 202 | [player prepareToPlay]; 203 | NSNumber *myNumber = @(key); 204 | [[self playerPool] setObject:player forKey:myNumber]; 205 | callback(@[ 206 | [NSNull null], 207 | @{ 208 | @"duration": @(player.duration), 209 | @"numberOfChannels": @(player.numberOfChannels) 210 | } 211 | ]); 212 | } 213 | } else { 214 | callback(@[RCTJSErrorFromNSError(error)]); 215 | } 216 | } 217 | 218 | RCT_EXPORT_METHOD(play:(double)key callback:(RCTResponseSenderBlock)callback) { 219 | [[AVAudioSession sharedInstance] setActive:YES error:nil]; 220 | 221 | [[NSNotificationCenter defaultCenter] 222 | addObserver:self 223 | selector:@selector(audioSessionChangeObserver:) 224 | name:AVAudioSessionRouteChangeNotification 225 | object:[AVAudioSession sharedInstance]]; 226 | 227 | [[NSNotificationCenter defaultCenter] 228 | addObserver:self 229 | selector:@selector(audioSessionChangeObserver:) 230 | name:AVAudioSessionInterruptionNotification 231 | object:[AVAudioSession sharedInstance]]; 232 | 233 | self._key = key; 234 | AVAudioPlayer *player = [self playerForKey:key]; 235 | 236 | if (player) { 237 | NSNumber *myNumber = @(key); 238 | [[self callbackPool] setObject:[callback copy] forKey:myNumber]; 239 | [player play]; 240 | [self setOnPlay:YES forPlayerKey:key]; 241 | } 242 | } 243 | 244 | RCT_EXPORT_METHOD(pause:(double)key callback:(RCTResponseSenderBlock)callback) { 245 | AVAudioPlayer *player = [self playerForKey:key]; 246 | if (player) { 247 | [player pause]; 248 | callback(@[]); 249 | } 250 | } 251 | 252 | RCT_EXPORT_METHOD(stop:(double)key callback:(RCTResponseSenderBlock)callback) { 253 | AVAudioPlayer *player = [self playerForKey:key]; 254 | if (player) { 255 | [player stop]; 256 | player.currentTime = 0; 257 | callback(@[]); 258 | } 259 | } 260 | 261 | RCT_EXPORT_METHOD(release:(double)key) { 262 | @synchronized(self) { 263 | AVAudioPlayer *player = [self playerForKey:key]; 264 | if (player) { 265 | NSNumber *myNumber = @(key); 266 | [player stop]; 267 | [[self callbackPool] removeObjectForKey:myNumber]; 268 | [[self playerPool] removeObjectForKey:myNumber]; 269 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 270 | } 271 | } 272 | } 273 | 274 | RCT_EXPORT_METHOD(setNumberOfLoops:(double)key loops:(double)loops) { 275 | 276 | AVAudioPlayer *player = [self playerForKey:key]; 277 | if (player) { 278 | NSNumber *myValue = @(loops); 279 | player.numberOfLoops = [myValue intValue]; 280 | } 281 | } 282 | 283 | RCT_EXPORT_METHOD(setVolume:(double)key left:(double)left right:(double)right) { 284 | AVAudioPlayer *player = [self playerForKey:key]; 285 | if (player) { 286 | player.volume = (float)left; 287 | } 288 | } 289 | 290 | RCT_EXPORT_METHOD(getSystemVolume:(RCTResponseSenderBlock)callback) { 291 | AVAudioSession *session = [AVAudioSession sharedInstance]; 292 | callback(@[@(session.outputVolume)]); 293 | } 294 | 295 | RCT_EXPORT_METHOD(getCurrentTime:(double)key callback:(RCTResponseSenderBlock)callback) { 296 | AVAudioPlayer *player = [self playerForKey:key]; 297 | if (player) { 298 | callback([NSArray 299 | arrayWithObjects:[NSNumber numberWithDouble:player.currentTime], 300 | [NSNumber numberWithBool:player.isPlaying], nil]); 301 | } else { 302 | callback([NSArray arrayWithObjects:[NSNumber numberWithInteger:-1], 303 | [NSNumber numberWithBool:NO], nil]); 304 | } 305 | } 306 | 307 | #pragma mark - Playback Controls 308 | 309 | 310 | - (void)setCurrentTime:(double)key currentTime:(double)currentTime { 311 | AVAudioPlayer *player = [self playerForKey:key]; 312 | if (player) { 313 | player.currentTime = currentTime; 314 | } 315 | } 316 | 317 | - (void)setPan:(double)key pan:(double)pan { 318 | AVAudioPlayer *player = [self playerForKey:key]; 319 | if (player) { 320 | player.pan = (float)pan; 321 | } 322 | } 323 | 324 | - (void)setSpeakerPhone:(double)key isSpeaker:(BOOL)isSpeaker { 325 | AVAudioSession *session = [AVAudioSession sharedInstance]; 326 | 327 | if (isSpeaker) { 328 | [session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil]; 329 | } else { 330 | [session overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:nil]; 331 | } 332 | 333 | [session setActive:YES error:nil]; 334 | } 335 | 336 | - (void)setSpeed:(double)key speed:(double)speed { 337 | AVAudioPlayer *player = [self playerForKey:key]; 338 | if (player) { 339 | player.rate = (float)speed; 340 | } 341 | } 342 | 343 | #pragma mark - Event Handling 344 | 345 | - (NSDictionary *)getDirectories { 346 | return [self constantsToExport]; 347 | } 348 | 349 | - (void)setOnPlay:(BOOL)isPlaying forPlayerKey:(double)playerKey { 350 | [self sendEventWithName:@"onPlayChange" 351 | body:@{ 352 | @"isPlaying": @(isPlaying), 353 | @"playerKey": @(playerKey) 354 | }]; 355 | } 356 | 357 | #pragma mark - Turbo Module 358 | 359 | #ifdef RCT_NEW_ARCH_ENABLED 360 | - (std::shared_ptr)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params { 361 | return std::make_shared(params); 362 | } 363 | #endif 364 | 365 | + (BOOL)requiresMainQueueSetup { 366 | return YES; 367 | } 368 | 369 | @end 370 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-sound", 3 | "version": "0.11.2", 4 | "description": "React Native module for playing sound clips on iOS, Android, and Windows", 5 | "main": "src/index.ts", 6 | "typings": "src/index.d.ts", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/zmxv/react-native-sound.git" 10 | }, 11 | "homepage": "https://github.com/zmxv/react-native-sound", 12 | "keywords": [ 13 | "react-native", 14 | "sound", 15 | "audio", 16 | "ios", 17 | "android", 18 | "windows" 19 | ], 20 | "author": "Zhen Wang (http://blog.zmxv.com)", 21 | "license": "MIT", 22 | "peerDependencies": { 23 | "react": "*", 24 | "react-native": "*" 25 | }, 26 | "files": [ 27 | "src", 28 | "android", 29 | "ios", 30 | "windows", 31 | "RNSound.podspec" 32 | ], 33 | "codegenConfig": { 34 | "name": "RNSoundSpec", 35 | "type": "all", 36 | "jsSrcsDir": "src", 37 | "outputDir": { 38 | "ios": "ios/generated", 39 | "android": "android/generated" 40 | }, 41 | "android": { 42 | "javaPackageName": "com.zmxv.RNSound" 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sound.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ReactNative = require('react-native'); 4 | var RNSound = ReactNative.NativeModules.RNSound; 5 | var IsAndroid = RNSound.IsAndroid; 6 | var IsWindows = RNSound.IsWindows; 7 | var resolveAssetSourceModule = require("react-native/Libraries/Image/resolveAssetSource"); 8 | var resolveAssetSource = resolveAssetSourceModule && resolveAssetSourceModule.__esModule 9 | ? resolveAssetSourceModule.default 10 | : resolveAssetSourceModule; 11 | var eventEmitter = new ReactNative.NativeEventEmitter(RNSound); 12 | 13 | var nextKey = 0; 14 | 15 | function isRelativePath(path) { 16 | return !/^(\/|http(s?)|asset|file)/.test(path); 17 | } 18 | 19 | function calculateRelativeVolume(volume, pan) { 20 | // calculates a lower volume relative to the pan value 21 | const relativeVolume = (volume * (1 - Math.abs(pan))); 22 | return Number(relativeVolume.toFixed(1)); 23 | } 24 | 25 | function setAndroidVolumes(sound) { 26 | // calculates the volumes for left and right channels 27 | if (sound._pan) { 28 | const relativeVolume = calculateRelativeVolume(sound._volume, sound._pan); 29 | if (sound._pan < 0) { 30 | // left is louder 31 | RNSound.setVolume(sound._key, sound._volume, relativeVolume); 32 | } else { 33 | // right is louder 34 | RNSound.setVolume(sound._key, relativeVolume, sound._volume); 35 | } 36 | } else { 37 | // no panning, same volume on both channels 38 | RNSound.setVolume(sound._key, sound._volume, sound._volume); 39 | } 40 | } 41 | 42 | function Sound(filename, basePath, onError, options) { 43 | var asset = resolveAssetSource(filename); 44 | if (asset) { 45 | this._filename = asset.uri; 46 | onError = basePath; 47 | } else { 48 | this._filename = basePath ? basePath + '/' + filename : filename; 49 | 50 | if (IsAndroid && !basePath && isRelativePath(filename)) { 51 | this._filename = filename.toLowerCase().replace(/\.[^.]+$/, ''); 52 | } 53 | } 54 | 55 | this.registerOnPlay = function() { 56 | if (this.onPlaySubscription != null) { 57 | console.warn('On Play change event listener is already registered'); 58 | return; 59 | } 60 | 61 | if (!IsWindows) { 62 | this.onPlaySubscription = eventEmitter.addListener( 63 | 'onPlayChange', 64 | (param) => { 65 | const { isPlaying, playerKey } = param; 66 | if (playerKey === this._key) { 67 | if (isPlaying) { 68 | this._playing = true; 69 | } 70 | else { 71 | this._playing = false; 72 | } 73 | } 74 | }, 75 | ); 76 | } 77 | } 78 | 79 | this._loaded = false; 80 | this._key = nextKey++; 81 | this._playing = false; 82 | this._duration = -1; 83 | this._numberOfChannels = -1; 84 | this._volume = 1; 85 | this._pan = 0; 86 | this._numberOfLoops = 0; 87 | this._speed = 1; 88 | this._pitch = 1; 89 | RNSound.prepare(this._filename, this._key, options || {}, (error, props) => { 90 | if (props) { 91 | if (typeof props.duration === 'number') { 92 | this._duration = props.duration; 93 | } 94 | if (typeof props.numberOfChannels === 'number') { 95 | this._numberOfChannels = props.numberOfChannels; 96 | } 97 | } 98 | if (error === null) { 99 | this._loaded = true; 100 | this.registerOnPlay(); 101 | } 102 | onError && onError(error, props); 103 | }); 104 | } 105 | 106 | Sound.prototype.isLoaded = function() { 107 | return this._loaded; 108 | }; 109 | 110 | Sound.prototype.play = function(onEnd) { 111 | if (this._loaded) { 112 | RNSound.play(this._key, (successfully) => onEnd && onEnd(successfully)); 113 | } else { 114 | onEnd && onEnd(false); 115 | } 116 | return this; 117 | }; 118 | 119 | Sound.prototype.pause = function(callback) { 120 | if (this._loaded) { 121 | RNSound.pause(this._key, () => { 122 | this._playing = false; 123 | callback && callback(); 124 | }); 125 | } 126 | return this; 127 | }; 128 | 129 | Sound.prototype.stop = function(callback) { 130 | if (this._loaded) { 131 | RNSound.stop(this._key, () => { 132 | this._playing = false; 133 | callback && callback(); 134 | }); 135 | } 136 | return this; 137 | }; 138 | 139 | Sound.prototype.reset = function() { 140 | if (this._loaded && IsAndroid) { 141 | RNSound.reset(this._key); 142 | this._playing = false; 143 | } 144 | return this; 145 | }; 146 | 147 | Sound.prototype.release = function() { 148 | if (this._loaded) { 149 | RNSound.release(this._key); 150 | this._loaded = false; 151 | if (!IsWindows) { 152 | if (this.onPlaySubscription != null) { 153 | this.onPlaySubscription.remove(); 154 | this.onPlaySubscription = null; 155 | } 156 | } 157 | } 158 | return this; 159 | }; 160 | 161 | Sound.prototype.getFilename = function() { 162 | return this._filename; 163 | }; 164 | 165 | Sound.prototype.getDuration = function() { 166 | return this._duration; 167 | }; 168 | 169 | Sound.prototype.getNumberOfChannels = function() { 170 | return this._numberOfChannels; 171 | }; 172 | 173 | Sound.prototype.getVolume = function() { 174 | return this._volume; 175 | }; 176 | 177 | Sound.prototype.getSpeed = function() { 178 | return this._speed; 179 | }; 180 | 181 | Sound.prototype.getPitch = function() { 182 | return this._pitch; 183 | }; 184 | 185 | Sound.prototype.setVolume = function(value) { 186 | this._volume = value; 187 | if (this._loaded) { 188 | if (IsAndroid) { 189 | setAndroidVolumes(this) 190 | } else { 191 | RNSound.setVolume(this._key, value); 192 | } 193 | } 194 | return this; 195 | }; 196 | 197 | Sound.prototype.setPan = function(value) { 198 | this._pan = value; 199 | if (this._loaded) { 200 | if (IsWindows) { 201 | throw new Error('#setPan not supported on windows'); 202 | } else if (IsAndroid) { 203 | setAndroidVolumes(this) 204 | } else { 205 | RNSound.setPan(this._key, value); 206 | } 207 | } 208 | return this; 209 | }; 210 | 211 | Sound.prototype.getSystemVolume = function(callback) { 212 | if(!IsWindows) { 213 | RNSound.getSystemVolume(callback); 214 | } 215 | return this; 216 | }; 217 | 218 | Sound.prototype.setSystemVolume = function(value) { 219 | if (IsAndroid) { 220 | RNSound.setSystemVolume(value); 221 | } 222 | return this; 223 | }; 224 | 225 | Sound.prototype.getPan = function() { 226 | return this._pan; 227 | }; 228 | 229 | Sound.prototype.getNumberOfLoops = function() { 230 | return this._numberOfLoops; 231 | }; 232 | 233 | Sound.prototype.setNumberOfLoops = function(value) { 234 | this._numberOfLoops = value; 235 | if (this._loaded) { 236 | if (IsAndroid || IsWindows) { 237 | RNSound.setLooping(this._key, !!value); 238 | } else { 239 | RNSound.setNumberOfLoops(this._key, value); 240 | } 241 | } 242 | return this; 243 | }; 244 | 245 | Sound.prototype.setSpeed = function(value) { 246 | this._speed = value; 247 | if (this._loaded) { 248 | if (!IsWindows) { 249 | RNSound.setSpeed(this._key, value); 250 | } 251 | } 252 | return this; 253 | }; 254 | 255 | Sound.prototype.setPitch = function(value) { 256 | this._pitch = value; 257 | if (this._loaded) { 258 | if (IsAndroid) { 259 | RNSound.setPitch(this._key, value); 260 | } 261 | } 262 | return this; 263 | }; 264 | 265 | Sound.prototype.getCurrentTime = function(callback) { 266 | if (this._loaded) { 267 | RNSound.getCurrentTime(this._key, callback); 268 | } 269 | }; 270 | 271 | Sound.prototype.setCurrentTime = function(value) { 272 | if (this._loaded) { 273 | RNSound.setCurrentTime(this._key, value); 274 | } 275 | return this; 276 | }; 277 | 278 | // android only 279 | Sound.prototype.setSpeakerphoneOn = function(value) { 280 | if (IsAndroid) { 281 | RNSound.setSpeakerphoneOn(this._key, value); 282 | } 283 | }; 284 | 285 | // ios only 286 | 287 | // This is deprecated. Call the static one instead. 288 | 289 | Sound.prototype.setCategory = function(value) { 290 | Sound.setCategory(value, false); 291 | } 292 | 293 | Sound.prototype.isPlaying = function() { 294 | return this._playing; 295 | } 296 | 297 | Sound.enable = function(enabled) { 298 | RNSound.enable(enabled); 299 | }; 300 | 301 | Sound.enableInSilenceMode = function(enabled) { 302 | if (!IsAndroid && !IsWindows) { 303 | RNSound.enableInSilenceMode(enabled); 304 | } 305 | }; 306 | 307 | Sound.setActive = function(value) { 308 | if (!IsAndroid && !IsWindows) { 309 | RNSound.setActive(value); 310 | } 311 | }; 312 | 313 | Sound.setCategory = function(value, mixWithOthers = false) { 314 | if (!IsWindows) { 315 | RNSound.setCategory(value, mixWithOthers); 316 | } 317 | }; 318 | 319 | Sound.setMode = function(value) { 320 | if (!IsAndroid && !IsWindows) { 321 | RNSound.setMode(value); 322 | } 323 | }; 324 | 325 | Sound.setSpeakerPhone = function(value) { 326 | if (!IsAndroid && !IsWindows) { 327 | RNSound.setSpeakerPhone(value) 328 | } 329 | } 330 | 331 | Sound.MAIN_BUNDLE = RNSound.MainBundlePath; 332 | Sound.DOCUMENT = RNSound.NSDocumentDirectory; 333 | Sound.LIBRARY = RNSound.NSLibraryDirectory; 334 | Sound.CACHES = RNSound.NSCachesDirectory; 335 | 336 | module.exports = Sound; 337 | -------------------------------------------------------------------------------- /src/NativeSoundAndroid.ts: -------------------------------------------------------------------------------- 1 | import type { TurboModule } from "react-native"; 2 | import { TurboModuleRegistry } from "react-native"; 3 | interface SoundOptionTypes { 4 | speed: number; 5 | loadSync: boolean; 6 | } 7 | export interface Spec extends TurboModule { 8 | setVolume: (key: number, left: number, right: number) => void; 9 | setSpeed: (key: number, speed: number) => void; 10 | setSystemVolume: (value: number) => void; 11 | getSystemVolume: (callback: (volume: number) => void) => void; 12 | getCurrentTime: ( 13 | key: number, 14 | callback: (currentTime: number) => void 15 | ) => void; 16 | setCurrentTime: (key: number, currentTime: number) => void; 17 | play: (key: number, callback: (success: boolean) => void) => void; 18 | pause: (key: number, callback: () => void) => void; 19 | stop: (key: number, callback: () => void) => void; 20 | reset: (key: number) => void; 21 | release: (key: number) => void; 22 | prepare: ( 23 | fileName: string, 24 | key: number, 25 | options: SoundOptionTypes, 26 | callback: () => void 27 | ) => void; 28 | setCategory: (value: string, mixWithOthers: boolean) => void; 29 | setLooping: (key: number, isLooping: boolean) => void; 30 | setSpeakerphoneOn: (key: number, value: boolean) => void; 31 | setPitch: (key: number, pitch: number) => void; 32 | addListener: (eventName: string) => void; 33 | removeListeners: (count: number) => void; 34 | } 35 | 36 | export default TurboModuleRegistry.getEnforcing("RNSound"); 37 | -------------------------------------------------------------------------------- /src/NativeSoundIOS.ts: -------------------------------------------------------------------------------- 1 | import type { TurboModule } from "react-native"; 2 | import { TurboModuleRegistry } from "react-native"; 3 | interface SoundOptionTypes { 4 | speed: number; 5 | loadSync: boolean; 6 | } 7 | export interface Spec extends TurboModule { 8 | readonly getDirectories: () => { 9 | MainBundlePath: string; 10 | NSDocumentDirectory: string; 11 | NSLibraryDirectory: string; 12 | NSCachesDirectory: string; 13 | }; 14 | setVolume: (key: number, left: number, right: number) => void; 15 | setMode: (value: string) => void; 16 | setSpeed: (key: number, speed: number) => void; 17 | setPan: (key: number, pan: number) => void; 18 | getSystemVolume: (callback: (volume: number) => void) => void; 19 | getCurrentTime: ( 20 | key: number, 21 | callback: (currentTime: number) => void 22 | ) => void; 23 | setCurrentTime: (key: number, currentTime: number) => void; 24 | play: (key: number, callback: (success: boolean) => void) => void; 25 | pause: (key: number, callback: () => void) => void; 26 | stop: (key: number, callback: () => void) => void; 27 | release: (key: number) => void; 28 | prepare: ( 29 | fileName: string, 30 | key: number, 31 | options: SoundOptionTypes, 32 | callback: () => void 33 | ) => void; 34 | enableInSilenceMode: (enabled: boolean) => void; 35 | enable: (enabled: boolean) => void; 36 | setActive: (value: boolean) => void; 37 | setCategory: (value: string, mixWithOthers?: boolean) => void; 38 | setSpeakerPhone: (key: number, isSpeaker: boolean) => void; 39 | setNumberOfLoops: (key: number, loops: number) => void; 40 | addListener: (eventName: string) => void; 41 | removeListeners: (count: number) => void; 42 | } 43 | 44 | export default TurboModuleRegistry.getEnforcing("RNSound") as Spec; 45 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for react-native-sound 2 | // Project: https://github.com/zmxv/react-native-sound 3 | // Definitions by: Kyle Roach 4 | // TypeScript Version: 2.3.2 5 | 6 | type AVAudioSessionCategory = 7 | | "Ambient" 8 | | "SoloAmbient" 9 | | "Playback" 10 | | "Record" 11 | | "PlayAndRecord" 12 | | "AudioProcessing" 13 | | "MultiRoute" 14 | | "Alarm"; 15 | 16 | type AVAudioSessionMode = 17 | | "Default" 18 | | "VoiceChat" 19 | | "VideoChat" 20 | | "GameChat" 21 | | "VideoRecording" 22 | | "Measurement" 23 | | "MoviePlayback" 24 | | "SpokenAudio"; 25 | 26 | type FilenameType = string; 27 | 28 | type FileType = any; 29 | 30 | type BasePathType = string; 31 | 32 | type CallbackType = ( 33 | error: any, 34 | props: { 35 | duration?: number; 36 | numberOfChannels?: number; 37 | } 38 | ) => void; 39 | 40 | declare class Sound { 41 | static MAIN_BUNDLE: string; 42 | static DOCUMENT: string; 43 | static LIBRARY: string; 44 | static CACHES: string; 45 | 46 | /** 47 | * Sets AVAudioSession as active, which is recommended on iOS to achieve seamless background playback. 48 | * Use this method to deactivate the AVAudioSession when playback is finished in order for other apps 49 | * to regain access to the audio stack. 50 | * 51 | * @param category AVAudioSession category 52 | * @param mixWithOthers Can be set to true to force mixing with other audio sessions. 53 | */ 54 | static setActive(active: boolean): void; 55 | 56 | /** 57 | * Sets AVAudioSession category, which allows playing sound in background, 58 | * stop sound playback when phone is locked, etc. 59 | * Parameter options: "Ambient", "SoloAmbient", "Playback", "Record", "PlayAndRecord", "AudioProcessing", "MultiRoute". 60 | * 61 | * @param category AVAudioSession category 62 | * @param mixWithOthers Can be set to true to force mixing with other audio sessions. 63 | */ 64 | static setCategory( 65 | category: AVAudioSessionCategory, 66 | mixWithOthers?: boolean 67 | ): void; 68 | 69 | /** 70 | * Sets AVAudioSession mode, which works in conjunction with the category to determine audio mixing behavior. 71 | * Parameter options: "Default", "VoiceChat", "VideoChat", "GameChat", "VideoRecording", "Measurement", "MoviePlayback", "SpokenAudio". 72 | * 73 | * @param mode AVAudioSession mode 74 | * @param mixWithOthers Can be set to true to force mixing with other audio sessions. 75 | */ 76 | static setMode(mode: AVAudioSessionMode): void; 77 | 78 | /** 79 | * Activates or deactivates the audio session with the ambient category, based on the provided boolean value.(iOS only) 80 | * @param enabled 81 | */ 82 | static enable(enabled: boolean): void; 83 | 84 | /** 85 | * @param filenameOrFile Either absolute or relative path to the sound file or the `require` call. 86 | * @param basePathOrCallback Optional base path of the file. Omit this or pass '' if filename is an absolute path; you may use one of the predefined directories: Sound.MAIN_BUNDLE, Sound.DOCUMENT, Sound.LIBRARY, Sound.CACHES. If you are using `require` to define filepath, then set the callback function as the second argument. 87 | * @param callback Optional callback function called when load ends in either success or error. In the event of success, error is undefined. 88 | */ 89 | constructor( 90 | filenameOrFile: FilenameType | FileType, 91 | basePathOrCallback?: BasePathType | CallbackType, 92 | callback?: CallbackType 93 | ); 94 | 95 | /** 96 | * Return true if the sound has been loaded. 97 | */ 98 | isLoaded(): boolean; 99 | 100 | /** 101 | * Plays the loaded file 102 | * @param onEnd - Optional callback function that gets called when the playback finishes successfully or an audio decoding error interrupts it 103 | */ 104 | play(onEnd?: (success: boolean) => void): this; 105 | 106 | /** 107 | * Pause the sound 108 | * @param cb - Optional callback function that gets called when the sound has been paused. 109 | */ 110 | pause(cb?: () => void): this; 111 | 112 | /** 113 | * Stop playback and set the seek position to 0. 114 | * @param cb - Optional callback function that gets called when the sound has been stopped. 115 | */ 116 | stop(cb?: () => void): this; 117 | 118 | /** 119 | * Reset the audio player to its uninitialized state (android only) 120 | */ 121 | reset(): this; 122 | 123 | /** 124 | * Release the audio player resource associated with the instance. 125 | */ 126 | release(): this; 127 | 128 | /** 129 | * Return the number of channels 130 | * (1 for mono and 2 for stereo sound), or -1 before the sound gets loaded. 131 | */ 132 | getNumberOfChannels(): number; 133 | 134 | /** 135 | * Return the time of audio (second) 136 | */ 137 | getDuration(): number; 138 | 139 | /** 140 | * Return the volume of the audio player (not the system-wide volume), 141 | * Ranges from 0.0 (silence) through 1.0 (full volume, the default) 142 | */ 143 | getVolume(): number; 144 | 145 | /** 146 | * Set the volume 147 | * @param value - ranging from 0.0 (silence) through 1.0 (full volume) 148 | */ 149 | setVolume(value: number): this; 150 | 151 | /** 152 | * Return the stereo pan position of the audio player (not the system-wide pan) 153 | * Ranges from -1.0 (full left) through 1.0 (full right). The default value is 0.0 (center) 154 | */ 155 | getPan(): number; 156 | 157 | /** 158 | * Set the pan value 159 | * @param value - ranging from -1.0 (full left) through 1.0 (full right). 160 | */ 161 | setPan(value: number): this; 162 | 163 | /** 164 | * Return the loop count of the audio player. 165 | * The default is 0 which means to play the sound once. 166 | * On iOS a positive number specifies the number of times to return to the start and play again, a negative number indicates an indefinite loop. 167 | * On Android any non-zero value indicates an indefinite loop. 168 | */ 169 | getNumberOfLoops(): number; 170 | 171 | /** 172 | * Set the loop count 173 | * @param value - iOS: 0 means to play the sound once, a positive number specifies the number of times to return to the start and play again, a negative number indicates an indefinite loop. Android: 0 means to play the sound once, other numbers indicate an indefinite loop. 174 | */ 175 | setNumberOfLoops(value: number): this; 176 | 177 | /** 178 | * Callback will receive the current playback position in seconds and whether the sound is being played. 179 | * @param cb 180 | */ 181 | getCurrentTime(cb?: (seconds: number, isPlaying: boolean) => void): void; 182 | 183 | /** 184 | * Seek to a particular playback point in seconds. 185 | * @param value 186 | */ 187 | setCurrentTime(value: number): this; 188 | 189 | /** 190 | * Return the speed of the audio player 191 | */ 192 | getSpeed(): number; 193 | 194 | /** 195 | * Speed of the audio playback. 196 | * @param value 197 | */ 198 | setSpeed(value: number): this; 199 | 200 | /** 201 | * Return the pitch of the audio player 202 | */ 203 | getPitch(): number; 204 | 205 | /** 206 | * Pitch of the audio playback (Android Only). 207 | * @param value 208 | */ 209 | setPitch(value: number): void; 210 | 211 | /** 212 | * Whether to enable playback in silence mode (iOS only) 213 | * @deprecated - Use the static method Sound.setCategory('Playback') instead which has the same effect. 214 | * @param enabled 215 | */ 216 | enableInSilenceMode(enabled: boolean): void; 217 | 218 | /** 219 | * Sets AVAudioSession category 220 | * @deprecated 221 | * @param value 222 | */ 223 | setCategory(value: AVAudioSessionCategory): void; 224 | 225 | /** 226 | * Turn speaker phone on (android only) 227 | * @platform android 228 | * @param value 229 | */ 230 | setSpeakerphoneOn(value: boolean): void; 231 | 232 | /** 233 | * Whether the player is playing or not. 234 | */ 235 | isPlaying(): boolean; 236 | } 237 | 238 | export = Sound; 239 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | EmitterSubscription, 3 | NativeEventEmitter, 4 | NativeModules, 5 | Platform, 6 | } from "react-native"; 7 | 8 | const IsAndroid = Platform.OS === "android"; 9 | 10 | const IsWindows = Platform.OS === "windows"; 11 | 12 | const LINKING_ERROR = 13 | `The package 'react-native-sound' doesn't seem to be linked. Make sure: \n\n` + 14 | Platform.select({ ios: "- You have run 'pod install'\n", default: "" }) + 15 | "- You rebuilt the app after installing the package\n" + 16 | "- You are not using Expo Go\n"; 17 | 18 | // @ts-expect-error 19 | const SoundModule = global.__turboModuleProxy 20 | ? IsAndroid 21 | ? require("./NativeSoundAndroid").default 22 | : require("./NativeSoundIOS").default 23 | : NativeModules.RNSound; 24 | 25 | const RNSound = 26 | SoundModule || 27 | new Proxy( 28 | {}, 29 | { 30 | get() { 31 | throw new Error(LINKING_ERROR); 32 | }, 33 | } 34 | ); 35 | 36 | interface SoundProps { 37 | duration?: number; 38 | numberOfChannels?: number; 39 | } 40 | 41 | interface SoundOptionTypes { 42 | speed: number; 43 | loadSync: boolean; 44 | } 45 | 46 | const isRelativePath = (path: string) => { 47 | return !/^(\/|http(s?)|asset|file)/.test(path); 48 | }; 49 | 50 | let nextKey = 0; 51 | 52 | const eventEmitter = new NativeEventEmitter(RNSound); 53 | 54 | class Sound { 55 | private _filename: string; 56 | private _key: number; 57 | private _loaded: boolean; 58 | private _playing: boolean; 59 | private _duration: number; 60 | private _numberOfChannels: number; 61 | private _volume: number; 62 | private _pan: number; 63 | private _numberOfLoops: number; 64 | private _speed: number; 65 | private _pitch: number; 66 | private onPlaySubscription: EmitterSubscription; 67 | 68 | constructor( 69 | filename: string, 70 | basePath?: string | ((error: string, props: SoundProps) => void), 71 | onError?: (error: string, props: SoundProps) => void, 72 | options?: SoundOptionTypes 73 | ) { 74 | if (filename.startsWith("http")) { 75 | this._filename = filename; 76 | } else { 77 | this._filename = basePath ? `${basePath}/${filename}` : filename; 78 | 79 | if (IsAndroid && !basePath && isRelativePath(filename)) { 80 | this._filename = filename.toLowerCase().replace(/\.[^.]+$/, ""); 81 | } 82 | } 83 | 84 | if (typeof basePath === "function") { 85 | onError = basePath; 86 | } 87 | 88 | this._key = nextKey++; 89 | this._loaded = false; 90 | this._playing = false; 91 | this._duration = -1; 92 | this._numberOfChannels = -1; 93 | this._volume = 1; 94 | this._pan = 0; 95 | this._numberOfLoops = 0; 96 | this._speed = 1; 97 | this._pitch = 1; 98 | 99 | RNSound.prepare( 100 | this._filename, 101 | this._key, 102 | options ?? {}, 103 | (error: string, props: SoundProps) => { 104 | if (props) { 105 | if (typeof props.duration === "number") { 106 | this._duration = props.duration; 107 | } 108 | if (typeof props.numberOfChannels === "number") { 109 | this._numberOfChannels = props.numberOfChannels; 110 | } 111 | } 112 | if (error === null) { 113 | this._loaded = true; 114 | this.registerOnPlay(); 115 | } 116 | onError && onError(error, props); 117 | } 118 | ); 119 | } 120 | 121 | private registerOnPlay() { 122 | if (this.onPlaySubscription) { 123 | return; 124 | } 125 | 126 | if (!IsWindows) { 127 | this.onPlaySubscription = eventEmitter.addListener( 128 | "onPlayChange", 129 | (param: { isPlaying: boolean; playerKey: number }) => { 130 | const { isPlaying, playerKey } = param; 131 | if (playerKey === this._key) { 132 | this._playing = isPlaying; 133 | } 134 | } 135 | ); 136 | } 137 | } 138 | 139 | private calculateRelativeVolume(volume: number, pan: number): number { 140 | const relativeVolume = volume * (1 - Math.abs(pan)); 141 | return Number(relativeVolume.toFixed(1)); 142 | } 143 | 144 | private setAndroidVolumes() { 145 | if (this._pan) { 146 | const relativeVolume = this.calculateRelativeVolume( 147 | this._volume, 148 | this._pan 149 | ); 150 | if (this._pan < 0) { 151 | RNSound.setVolume(this._key, this._volume, relativeVolume); 152 | } else { 153 | RNSound.setVolume(this._key, relativeVolume, this._volume); 154 | } 155 | } else { 156 | RNSound.setVolume(this._key, this._volume, this._volume); 157 | } 158 | } 159 | 160 | public isLoaded(): boolean { 161 | return this._loaded; 162 | } 163 | 164 | public play(onEnd?: (successfully: boolean) => void): Sound { 165 | if (this._loaded) { 166 | RNSound.play( 167 | this._key, 168 | (successfully: boolean) => onEnd && onEnd(successfully) 169 | ); 170 | } else { 171 | onEnd && onEnd(false); 172 | } 173 | return this; 174 | } 175 | 176 | public pause(callback?: () => void): Sound { 177 | if (this._loaded) { 178 | RNSound.pause(this._key, () => { 179 | this._playing = false; 180 | callback && callback(); 181 | }); 182 | } 183 | return this; 184 | } 185 | 186 | public stop(callback?: () => void): Sound { 187 | if (this._loaded) { 188 | RNSound.stop(this._key, () => { 189 | this._playing = false; 190 | callback && callback(); 191 | }); 192 | } 193 | return this; 194 | } 195 | 196 | public reset(): Sound { 197 | if (this._loaded && IsAndroid) { 198 | RNSound.reset(this._key); 199 | this._playing = false; 200 | } 201 | return this; 202 | } 203 | 204 | public release(): Sound { 205 | if (this._loaded) { 206 | RNSound.release(this._key); 207 | this._loaded = false; 208 | if (!IsWindows && this.onPlaySubscription) { 209 | this.onPlaySubscription.remove(); 210 | } 211 | } 212 | return this; 213 | } 214 | 215 | public getFilename(): string { 216 | return this._filename; 217 | } 218 | 219 | public getDuration(): number { 220 | return this._duration; 221 | } 222 | 223 | public getNumberOfChannels(): number { 224 | return this._numberOfChannels; 225 | } 226 | 227 | public getVolume(): number { 228 | return this._volume; 229 | } 230 | 231 | public getSpeed(): number { 232 | return this._speed; 233 | } 234 | 235 | public getPitch(): number { 236 | return this._pitch; 237 | } 238 | 239 | public setVolume(value: number): Sound { 240 | this._volume = value; 241 | if (this._loaded) { 242 | if (IsAndroid) { 243 | this.setAndroidVolumes(); 244 | } else { 245 | RNSound.setVolume(this._key, value); 246 | } 247 | } 248 | return this; 249 | } 250 | 251 | public setPan(value: number): Sound { 252 | this._pan = value; 253 | if (this._loaded) { 254 | if (IsWindows) { 255 | throw new Error("#setPan not supported on windows"); 256 | } else if (IsAndroid) { 257 | this.setAndroidVolumes(); 258 | } else { 259 | RNSound.setPan(this._key, value); 260 | } 261 | } 262 | return this; 263 | } 264 | 265 | public getSystemVolume(callback: (volume: number) => void): Sound { 266 | if (!IsWindows) { 267 | RNSound.getSystemVolume(callback); 268 | } 269 | return this; 270 | } 271 | 272 | public setSystemVolume(value: number): Sound { 273 | if (IsAndroid) { 274 | RNSound.setSystemVolume(value); 275 | } 276 | return this; 277 | } 278 | 279 | public getPan(): number { 280 | return this._pan; 281 | } 282 | 283 | public getNumberOfLoops(): number { 284 | return this._numberOfLoops; 285 | } 286 | 287 | public setNumberOfLoops(value: number): Sound { 288 | this._numberOfLoops = value; 289 | if (this._loaded) { 290 | if (IsAndroid || IsWindows) { 291 | RNSound.setLooping(this._key, !!value); 292 | } else { 293 | RNSound.setNumberOfLoops(this._key, value); 294 | } 295 | } 296 | return this; 297 | } 298 | 299 | public setSpeed(value: number): Sound { 300 | this._speed = value; 301 | if (this._loaded && !IsWindows) { 302 | RNSound.setSpeed(this._key, value); 303 | } 304 | return this; 305 | } 306 | 307 | public setPitch(value: number): Sound { 308 | this._pitch = value; 309 | if (this._loaded && IsAndroid) { 310 | RNSound.setPitch(this._key, value); 311 | } 312 | return this; 313 | } 314 | 315 | public getCurrentTime(callback: (time: number) => void): void { 316 | if (this._loaded) { 317 | RNSound.getCurrentTime(this._key, callback); 318 | } 319 | } 320 | 321 | public setCurrentTime(value: number): Sound { 322 | if (this._loaded) { 323 | RNSound.setCurrentTime(this._key, value); 324 | } 325 | return this; 326 | } 327 | 328 | // android only 329 | public setSpeakerphoneOn(value: boolean): void { 330 | if (IsAndroid) { 331 | RNSound.setSpeakerphoneOn(this._key, value); 332 | } 333 | } 334 | 335 | // ios only 336 | public static setCategory( 337 | value: string, 338 | mixWithOthers: boolean = false 339 | ): void { 340 | if (!IsWindows) { 341 | RNSound.setCategory(value, mixWithOthers); 342 | } 343 | } 344 | 345 | public isPlaying(): boolean { 346 | return this._playing; 347 | } 348 | 349 | public static enableInSilenceMode(enabled: boolean): void { 350 | if (!IsAndroid && !IsWindows) { 351 | RNSound.enableInSilenceMode(enabled); 352 | } 353 | } 354 | 355 | public static setActive(value: boolean): void { 356 | if (!IsAndroid && !IsWindows) { 357 | RNSound.setActive(value); 358 | } 359 | } 360 | 361 | public static setMode(value: string): void { 362 | if (!IsAndroid && !IsWindows) { 363 | RNSound.setMode(value); 364 | } 365 | } 366 | 367 | public static enable(enable: boolean): void { 368 | if (!IsAndroid && !IsWindows) { 369 | RNSound.enable(enable); 370 | } 371 | } 372 | 373 | public static setSpeakerPhone(value: boolean): void { 374 | if (!IsAndroid && !IsWindows) { 375 | RNSound.setSpeakerPhone(value); 376 | } 377 | } 378 | 379 | public static MAIN_BUNDLE = IsAndroid 380 | ? "" 381 | : RNSound.getDirectories().MainBundlePath; 382 | public static DOCUMENT = IsAndroid 383 | ? "" 384 | : RNSound.getDirectories().NSDocumentDirectory; 385 | public static LIBRARY = IsAndroid 386 | ? "" 387 | : RNSound.getDirectories().NSLibraryDirectory; 388 | public static CACHES = IsAndroid 389 | ? "" 390 | : RNSound.getDirectories().NSCachesDirectory; 391 | } 392 | export default Sound; 393 | -------------------------------------------------------------------------------- /windows/RNSoundModule/.gitignore: -------------------------------------------------------------------------------- 1 | *AppPackages* 2 | *BundleArtifacts* 3 | *ReactAssets* 4 | 5 | #OS junk files 6 | [Tt]humbs.db 7 | *.DS_Store 8 | 9 | #Visual Studio files 10 | *.[Oo]bj 11 | *.user 12 | *.aps 13 | *.pch 14 | *.vspscc 15 | *.vssscc 16 | *_i.c 17 | *_p.c 18 | *.ncb 19 | *.suo 20 | *.tlb 21 | *.tlh 22 | *.bak 23 | *.[Cc]ache 24 | *.ilk 25 | *.log 26 | *.lib 27 | *.sbr 28 | *.sdf 29 | *.opensdf 30 | *.opendb 31 | *.unsuccessfulbuild 32 | ipch/ 33 | [Oo]bj/ 34 | [Bb]in 35 | [Dd]ebug*/ 36 | [Rr]elease*/ 37 | Ankh.NoLoad 38 | 39 | # Visual C++ cache files 40 | ipch/ 41 | *.aps 42 | *.ncb 43 | *.opendb 44 | *.opensdf 45 | *.sdf 46 | *.cachefile 47 | *.VC.db 48 | *.VC.VC.opendb 49 | 50 | #MonoDevelop 51 | *.pidb 52 | *.userprefs 53 | 54 | #Tooling 55 | _ReSharper*/ 56 | *.resharper 57 | [Tt]est[Rr]esult* 58 | *.sass-cache 59 | 60 | #Project files 61 | [Bb]uild/ 62 | 63 | #Subversion files 64 | .svn 65 | 66 | # Office Temp Files 67 | ~$* 68 | 69 | # vim Temp Files 70 | *~ 71 | 72 | #NuGet 73 | packages/ 74 | *.nupkg 75 | 76 | #ncrunch 77 | *ncrunch* 78 | *crunch*.local.xml 79 | 80 | # visual studio database projects 81 | *.dbmdl 82 | 83 | #Test files 84 | *.testsettings 85 | 86 | #Other files 87 | *.DotSettings 88 | .vs/ 89 | *project.lock.json 90 | -------------------------------------------------------------------------------- /windows/RNSoundModule/RNSoundModule.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RNSoundModule", "RNSoundModule\RNSoundModule.csproj", "{7B8A9860-75EC-434D-AC70-C6135600C0C7}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactNative", "..\..\node_modules\react-native-windows\ReactWindows\ReactNative\ReactNative.csproj", "{C7673AD5-E3AA-468C-A5FD-FA38154E205C}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ChakraBridge", "..\..\node_modules\react-native-windows\ReactWindows\ChakraBridge\ChakraBridge.vcxproj", "{4B72C796-16D5-4E3A-81C0-3E36F531E578}" 11 | EndProject 12 | Global 13 | GlobalSection(SharedMSBuildProjectFiles) = preSolution 14 | ..\..\node_modules\react-native-windows\ReactWindows\ReactNative.Shared\ReactNative.Shared.projitems*{c7673ad5-e3aa-468c-a5fd-fa38154e205c}*SharedItemsImports = 4 15 | EndGlobalSection 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Debug|ARM = Debug|ARM 19 | Debug|x64 = Debug|x64 20 | Debug|x86 = Debug|x86 21 | Release|Any CPU = Release|Any CPU 22 | Release|ARM = Release|ARM 23 | Release|x64 = Release|x64 24 | Release|x86 = Release|x86 25 | EndGlobalSection 26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 27 | {7B8A9860-75EC-434D-AC70-C6135600C0C7}.Debug|Any CPU.ActiveCfg = Debug|ARM 28 | {7B8A9860-75EC-434D-AC70-C6135600C0C7}.Debug|Any CPU.Build.0 = Debug|ARM 29 | {7B8A9860-75EC-434D-AC70-C6135600C0C7}.Debug|ARM.ActiveCfg = Debug|ARM 30 | {7B8A9860-75EC-434D-AC70-C6135600C0C7}.Debug|ARM.Build.0 = Debug|ARM 31 | {7B8A9860-75EC-434D-AC70-C6135600C0C7}.Debug|x64.ActiveCfg = Debug|x64 32 | {7B8A9860-75EC-434D-AC70-C6135600C0C7}.Debug|x64.Build.0 = Debug|x64 33 | {7B8A9860-75EC-434D-AC70-C6135600C0C7}.Debug|x86.ActiveCfg = Debug|x86 34 | {7B8A9860-75EC-434D-AC70-C6135600C0C7}.Debug|x86.Build.0 = Debug|x86 35 | {7B8A9860-75EC-434D-AC70-C6135600C0C7}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {7B8A9860-75EC-434D-AC70-C6135600C0C7}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {7B8A9860-75EC-434D-AC70-C6135600C0C7}.Release|ARM.ActiveCfg = Release|ARM 38 | {7B8A9860-75EC-434D-AC70-C6135600C0C7}.Release|ARM.Build.0 = Release|ARM 39 | {7B8A9860-75EC-434D-AC70-C6135600C0C7}.Release|x64.ActiveCfg = Release|x64 40 | {7B8A9860-75EC-434D-AC70-C6135600C0C7}.Release|x64.Build.0 = Release|x64 41 | {7B8A9860-75EC-434D-AC70-C6135600C0C7}.Release|x86.ActiveCfg = Release|x86 42 | {7B8A9860-75EC-434D-AC70-C6135600C0C7}.Release|x86.Build.0 = Release|x86 43 | {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|Any CPU.ActiveCfg = Debug|ARM 44 | {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|ARM.ActiveCfg = Debug|ARM 45 | {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|ARM.Build.0 = Debug|ARM 46 | {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|x64.ActiveCfg = Debug|x64 47 | {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|x64.Build.0 = Debug|x64 48 | {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|x86.ActiveCfg = Debug|x86 49 | {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|x86.Build.0 = Debug|x86 50 | {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|Any CPU.ActiveCfg = Release|x86 51 | {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|ARM.ActiveCfg = Release|ARM 52 | {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|ARM.Build.0 = Release|ARM 53 | {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|x64.ActiveCfg = Release|x64 54 | {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|x64.Build.0 = Release|x64 55 | {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|x86.ActiveCfg = Release|x86 56 | {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|x86.Build.0 = Release|x86 57 | {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|Any CPU.ActiveCfg = Debug|ARM 58 | {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|ARM.ActiveCfg = Debug|ARM 59 | {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|ARM.Build.0 = Debug|ARM 60 | {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|x64.ActiveCfg = Debug|x64 61 | {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|x64.Build.0 = Debug|x64 62 | {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|x86.ActiveCfg = Debug|Win32 63 | {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|x86.Build.0 = Debug|Win32 64 | {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|Any CPU.ActiveCfg = Release|Win32 65 | {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|ARM.ActiveCfg = Release|ARM 66 | {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|ARM.Build.0 = Release|ARM 67 | {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|x64.ActiveCfg = Release|x64 68 | {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|x64.Build.0 = Release|x64 69 | {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|x86.ActiveCfg = Release|Win32 70 | {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|x86.Build.0 = Release|Win32 71 | EndGlobalSection 72 | GlobalSection(SolutionProperties) = preSolution 73 | HideSolutionNode = FALSE 74 | EndGlobalSection 75 | EndGlobal 76 | -------------------------------------------------------------------------------- /windows/RNSoundModule/RNSoundModule/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("RNSoundModule")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("RNSoundModule")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Version information for an assembly consists of the following four values: 18 | // 19 | // Major Version 20 | // Minor Version 21 | // Build Number 22 | // Revision 23 | // 24 | // You can specify all the values or you can default the Build and Revision Numbers 25 | // by using the '*' as shown below: 26 | // [assembly: AssemblyVersion("1.0.*")] 27 | [assembly: AssemblyVersion("1.0.0.0")] 28 | [assembly: AssemblyFileVersion("1.0.0.0")] 29 | [assembly: ComVisible(false)] -------------------------------------------------------------------------------- /windows/RNSoundModule/RNSoundModule/Properties/RNSoundModule.rd.xml: -------------------------------------------------------------------------------- 1 | 2 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /windows/RNSoundModule/RNSoundModule/RNSound.cs: -------------------------------------------------------------------------------- 1 | using ReactNative.Bridge; 2 | using ReactNative.Collections; 3 | using System; 4 | using System.Collections.Generic; 5 | using Windows.ApplicationModel.Core; 6 | using Windows.UI.Core; 7 | using Windows.UI.Popups; 8 | using Windows.Media.Capture; 9 | using Windows.Media.MediaProperties; 10 | using Windows.Storage; 11 | using Windows.Storage.Streams; 12 | using Windows.UI.Xaml.Controls; 13 | using System.Threading.Tasks; 14 | using System.Diagnostics; 15 | using System.IO; 16 | using ReactNative.Modules.Core; 17 | using System.Threading; 18 | using Newtonsoft.Json.Linq; 19 | using Windows.UI.Xaml; 20 | using System.Windows; 21 | using Windows.UI.Xaml.Media; 22 | using Windows.Media.Playback; 23 | using Windows.Media.Core; 24 | using System.Text; 25 | 26 | namespace RNSoundModule 27 | { 28 | public class RNSound : ReactContextNativeModuleBase 29 | { 30 | private const String IsWindows = "IsWindows"; 31 | private ReactContext context; 32 | 33 | Dictionary playerPool = new Dictionary(); 34 | static Object NULL = null; 35 | 36 | 37 | 38 | public RNSound(ReactContext reactContext) 39 | : base(reactContext) 40 | { 41 | context = reactContext; 42 | } 43 | public override string Name 44 | { 45 | get 46 | { 47 | return "RNSound"; 48 | } 49 | } 50 | public override void Initialize() 51 | { 52 | } 53 | 54 | public override IReadOnlyDictionary Constants 55 | { 56 | get 57 | { 58 | return new Dictionary 59 | { 60 | { IsWindows, true }, 61 | }; 62 | } 63 | } 64 | 65 | 66 | [ReactMethod] 67 | public async void prepare(String fileName, int key, JObject options, ICallback callback) 68 | { 69 | bool enableSMTCIntegration = true; 70 | JToken smtcOptionToken = options["enableSMTCIntegration"]; 71 | if (smtcOptionToken != null) 72 | { 73 | enableSMTCIntegration = smtcOptionToken.Value(); 74 | } 75 | 76 | await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => 77 | { 78 | MediaPlayer player = await createMediaPlayer(fileName, enableSMTCIntegration); 79 | player.MediaOpened += 80 | delegate 81 | { 82 | JObject props = new JObject(); 83 | props.Add("duration", player.PlaybackSession.NaturalDuration.TotalMilliseconds * .001); 84 | callback.Invoke(NULL, props); 85 | }; 86 | player.AutoPlay = false; 87 | if (player == null) 88 | { 89 | JObject e = new JObject(); 90 | 91 | e.Add("code", -1); 92 | e.Add("message", "resource not found"); 93 | callback.Invoke(e); 94 | return; 95 | } 96 | 97 | this.playerPool.Add(key, player); 98 | }); 99 | } 100 | 101 | protected async Task createMediaPlayer(String fileName, bool enableSMTCIntegration) 102 | { 103 | MediaPlayer player = new MediaPlayer(); 104 | player.CommandManager.IsEnabled = enableSMTCIntegration; 105 | 106 | StorageFile file = null; 107 | await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, 108 | async () => 109 | { 110 | StorageFolder LocalFolder = ApplicationData.Current.LocalFolder; 111 | StorageFolder InstallationFolder = Windows.ApplicationModel.Package.Current.InstalledLocation; 112 | 113 | try 114 | { 115 | file = await InstallationFolder.GetFileAsync(@"Assets\" + fileName); 116 | } 117 | catch (Exception) { } 118 | 119 | if (file == null) 120 | { 121 | try 122 | { 123 | file = await LocalFolder.GetFileAsync(fileName); 124 | } 125 | catch (Exception) { } 126 | 127 | } 128 | 129 | if (file != null) 130 | { 131 | var stream = await file.OpenAsync(FileAccessMode.Read); 132 | 133 | var mediaSource = MediaSource.CreateFromStorageFile(file); 134 | player.Source = mediaSource; 135 | } 136 | }).AsTask(); 137 | 138 | return player; 139 | } 140 | 141 | [ReactMethod] 142 | public void play(int key, ICallback callback) 143 | { 144 | Boolean callbackWasCalled = false; 145 | MediaPlayer player = null; 146 | Debug.WriteLine("play()"); 147 | 148 | if (!playerPool.TryGetValue(key, out player)) 149 | { 150 | Debug.WriteLine("Player is null"); 151 | callback.Invoke(false); 152 | return; 153 | } 154 | 155 | if (player == null) 156 | { 157 | Debug.WriteLine("Player is null"); 158 | callback.Invoke(false); 159 | return; 160 | } 161 | if (player.PlaybackSession.PlaybackState == MediaPlaybackState.Playing) 162 | { 163 | Debug.WriteLine("Already playing..."); 164 | return; 165 | } 166 | 167 | player.MediaEnded += 168 | delegate 169 | { 170 | if (callbackWasCalled) return; 171 | callbackWasCalled = true; 172 | Debug.WriteLine("Media Ended"); 173 | callback.Invoke(true); 174 | }; 175 | 176 | player.Play(); 177 | } 178 | 179 | [ReactMethod] 180 | public void pause(int key, ICallback callback) 181 | { 182 | MediaPlayer player = null; 183 | 184 | if (!playerPool.TryGetValue(key, out player)) 185 | { 186 | return; 187 | } 188 | if (player != null && player.PlaybackSession.PlaybackState == MediaPlaybackState.Playing) 189 | { 190 | player.Pause(); 191 | } 192 | callback.Invoke(); 193 | } 194 | 195 | [ReactMethod] 196 | public void stop(int key, ICallback callback) 197 | { 198 | MediaPlayer player = null; 199 | 200 | if (!playerPool.TryGetValue(key, out player)) 201 | { 202 | return; 203 | } 204 | 205 | player.Pause(); 206 | player.PlaybackSession.Position = new TimeSpan(0); 207 | callback.Invoke(); 208 | } 209 | 210 | [ReactMethod] 211 | public void release(int key) 212 | { 213 | MediaPlayer player = null; 214 | 215 | if (!playerPool.TryGetValue(key, out player)) 216 | { 217 | return; 218 | } 219 | 220 | player.Source = null; 221 | playerPool.Remove(key); 222 | } 223 | 224 | [ReactMethod] 225 | public void setVolume(int key, float volume) 226 | { 227 | MediaPlayer player = null; 228 | 229 | if (!playerPool.TryGetValue(key, out player)) 230 | { 231 | return; 232 | } 233 | 234 | player.Volume = volume; 235 | 236 | 237 | } 238 | 239 | [ReactMethod] 240 | public void setLooping(int key, bool looping) 241 | { 242 | MediaPlayer player = null; 243 | 244 | if (!playerPool.TryGetValue(key, out player)) 245 | { 246 | return; 247 | } 248 | 249 | player.IsLoopingEnabled = looping; 250 | 251 | } 252 | 253 | [ReactMethod] 254 | public void setCurrentTime(int key, float seconds) 255 | { 256 | MediaPlayer player = null; 257 | 258 | if (!playerPool.TryGetValue(key, out player)) 259 | { 260 | return; 261 | } 262 | 263 | player.PlaybackSession.Position = new TimeSpan(0, 0, 0, 0, (int)seconds * 1000); 264 | 265 | } 266 | 267 | [ReactMethod] 268 | public void getCurrentTime(int key, ICallback callback) 269 | { 270 | MediaPlayer player = null; 271 | 272 | if (!playerPool.TryGetValue(key, out player)) 273 | { 274 | return; 275 | } 276 | callback.Invoke(player.PlaybackSession.Position.Milliseconds * .001, player.PlaybackSession.PlaybackState == MediaPlaybackState.Playing); 277 | } 278 | 279 | [ReactMethod] 280 | public void enable(bool enabled) 281 | { 282 | // no op 283 | } 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /windows/RNSoundModule/RNSoundModule/RNSoundModule.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {7B8A9860-75EC-434D-AC70-C6135600C0C7} 8 | Library 9 | Properties 10 | RNSoundModule 11 | RNSoundModule 12 | en-US 13 | UAP 14 | 10.0.14393.0 15 | 10.0.10586.0 16 | 14 17 | 512 18 | {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 19 | ..\..\node_modules 20 | 21 | 22 | ..\..\..\.. 23 | 24 | 25 | AnyCPU 26 | true 27 | full 28 | false 29 | bin\Debug\ 30 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP 31 | prompt 32 | 4 33 | 34 | 35 | AnyCPU 36 | pdbonly 37 | true 38 | bin\Release\ 39 | TRACE;NETFX_CORE;WINDOWS_UWP 40 | prompt 41 | 4 42 | 43 | 44 | x86 45 | true 46 | bin\x86\Debug\ 47 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP 48 | ;2008 49 | full 50 | x86 51 | false 52 | prompt 53 | 54 | 55 | x86 56 | bin\x86\Release\ 57 | TRACE;NETFX_CORE;WINDOWS_UWP 58 | true 59 | ;2008 60 | pdbonly 61 | x86 62 | false 63 | prompt 64 | 65 | 66 | ARM 67 | true 68 | bin\ARM\Debug\ 69 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP 70 | ;2008 71 | full 72 | ARM 73 | false 74 | prompt 75 | 76 | 77 | ARM 78 | bin\ARM\Release\ 79 | TRACE;NETFX_CORE;WINDOWS_UWP 80 | true 81 | ;2008 82 | pdbonly 83 | ARM 84 | false 85 | prompt 86 | 87 | 88 | x64 89 | true 90 | bin\x64\Debug\ 91 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP 92 | ;2008 93 | full 94 | x64 95 | false 96 | prompt 97 | 98 | 99 | x64 100 | bin\x64\Release\ 101 | TRACE;NETFX_CORE;WINDOWS_UWP 102 | true 103 | ;2008 104 | pdbonly 105 | x64 106 | false 107 | prompt 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | {c7673ad5-e3aa-468c-a5fd-fa38154e205c} 122 | ReactNative 123 | 124 | 125 | 126 | 14.0 127 | 128 | 129 | 136 | -------------------------------------------------------------------------------- /windows/RNSoundModule/RNSoundModule/RNSoundPackage.cs: -------------------------------------------------------------------------------- 1 | using ReactNative.Bridge; 2 | using ReactNative.Modules.Core; 3 | using ReactNative.UIManager; 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace RNSoundModule 8 | { 9 | public class RNSoundPackage: IReactPackage 10 | { 11 | public IReadOnlyList CreateNativeModules(ReactContext reactContext) 12 | { 13 | return new List 14 | { 15 | new RNSound(reactContext) 16 | }; 17 | } 18 | 19 | public IReadOnlyList CreateJavaScriptModulesConfig() 20 | { 21 | return new List(0); 22 | } 23 | 24 | public IReadOnlyList CreateViewManagers( 25 | ReactContext reactContext) 26 | { 27 | return new List(0); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /windows/RNSoundModule/RNSoundModule/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.NETCore.UniversalWindowsPlatform": "6.0.8" 4 | }, 5 | "frameworks": { 6 | "uap10.0": {} 7 | }, 8 | "runtimes": { 9 | "win10-arm": {}, 10 | "win10-arm-aot": {}, 11 | "win10-x86": {}, 12 | "win10-x86-aot": {}, 13 | "win10-x64": {}, 14 | "win10-x64-aot": {} 15 | } 16 | } 17 | --------------------------------------------------------------------------------