├── .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 | [][npm]
4 | [][npm]
5 | [][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 |
--------------------------------------------------------------------------------