├── .github
├── ISSUE_TEMPLATE
│ └── bug_report.md
└── workflows
│ └── npm-publish.yml
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── RNSoundPlayer.podspec
├── android
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── johnsonsu
│ └── rnsoundplayer
│ ├── RNSoundPlayerModule.java
│ └── RNSoundPlayerPackage.java
├── index.d.ts
├── index.js
├── ios
├── RNSoundPlayer.h
├── RNSoundPlayer.m
└── RNSoundPlayer.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ └── contents.xcworkspacedata
└── package.json
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Platform (please complete the following information):**
24 | - OS: [e.g. iOS or Android]
25 |
26 | **Additional context**
27 | Add any other context about the problem here.
28 |
--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
3 |
4 | name: Publish NPM Package
5 |
6 | on:
7 | workflow_dispatch:
8 | release:
9 | types: [created]
10 |
11 | jobs:
12 | publish-npm:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v3
16 | - uses: actions/setup-node@v3
17 | with:
18 | node-version: 16
19 | registry-url: https://registry.npmjs.org/
20 | - run: npm publish
21 | env:
22 | NODE_AUTH_TOKEN: ${{secrets.NPM_ACCESS_TOKEN}}
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.DS_Store
2 | .AppleDouble
3 | .LSOverride
4 |
5 | # Icon must end with two \r
6 | Icon
7 |
8 |
9 | # Thumbnails
10 | ._*
11 |
12 | # Files that might appear in the root of a volume
13 | .DocumentRevisions-V100
14 | .fseventsd
15 | .Spotlight-V100
16 | .TemporaryItems
17 | .Trashes
18 | .VolumeIcon.icns
19 | .com.apple.timemachine.donotpresent
20 |
21 | # Directories potentially created on remote AFP share
22 | .AppleDB
23 | .AppleDesktop
24 | Network Trash Folder
25 | Temporary Items
26 | .apdisk
27 |
28 | # Xcode
29 | #
30 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
31 |
32 | ## Build generated
33 | build/
34 | DerivedData/
35 |
36 | ## Various settings
37 | *.pbxuser
38 | !default.pbxuser
39 | *.mode1v3
40 | !default.mode1v3
41 | *.mode2v3
42 | !default.mode2v3
43 | *.perspectivev3
44 | !default.perspectivev3
45 | xcuserdata/
46 |
47 | ## Other
48 | *.moved-aside
49 | *.xccheckout
50 | *.xcscmblueprint
51 |
52 | ## Obj-C/Swift specific
53 | *.hmap
54 | *.ipa
55 | *.dSYM.zip
56 | *.dSYM
57 |
58 | # CocoaPods
59 | #
60 | # We recommend against adding the Pods directory to your .gitignore. However
61 | # you should judge for yourself, the pros and cons are mentioned at:
62 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
63 | #
64 | # Pods/
65 |
66 | # Carthage
67 | #
68 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
69 | # Carthage/Checkouts
70 |
71 | Carthage/Build
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
76 | # screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "semi": false,
4 | "singleQuote": true
5 | }
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Johnson Su
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-sound-player
2 |
3 | Play audio files, stream audio from URL, using ReactNative.
4 |
5 | ## Installation
6 |
7 | ### 1. `yarn` or `npm`
8 |
9 | ```
10 | // yarn
11 | yarn add react-native-sound-player
12 | // or npm
13 | npm install --save react-native-sound-player
14 | ```
15 |
16 | ### 2. Link
17 |
18 | For RN >= 0.60 you can skip this step.
19 |
20 | ```
21 | react-native link react-native-sound-player
22 | ```
23 |
24 | ## Usage
25 |
26 | ### Play sound with file name and type
27 |
28 | 1. Add sound files to iOS/Android.
29 |
30 | - On iOS, drag and drop sound file into project in Xcode. Remember to check **"Copy items if needed"** option and **"Add to targets"**.
31 | - On Android, put sound files in `{project_root}/android/app/src/main/res/raw/`. Just create the folder if it doesn't exist.
32 | - When using playAsset() you only need to copy the file to the projects root directory or a subfolder like assets
33 |
34 | 2. Import the library and call the `playSoundFile(fileName, fileType)` function:
35 |
36 | ```javascript
37 | import SoundPlayer from "react-native-sound-player";
38 |
39 | try {
40 | // play the file tone.mp3
41 | SoundPlayer.playSoundFile("tone", "mp3");
42 | // or play from url
43 | SoundPlayer.playUrl("https://example.com/music.mp3");
44 | // or play file from folder
45 | SoundPlayer.playAsset(require("./assets/tone.mp3"));
46 | } catch (e) {
47 | console.log(`cannot play the sound file`, e);
48 | }
49 | ```
50 |
51 | > Please note that the device can still go to sleep (screen goes off) while audio is playing.
52 | > When this happens, the audio will stop playing.
53 | > To prevent this, you can use something like [react-native-keep-awake](https://github.com/corbt/react-native-keep-awake).
54 | > Or alternatively, for iOS, you can add a Background Mode of `Audio, AirPlay, and Picture in Picture` in XCode. To do this, select your application from Targets, then click on `Signing & Capabilities` and add `Background Modes`. once the options for it appear on your `Signing & Capabilities` page select the checkbox with `Audio, AirPlay, and Picture in Picture`. This will allow the application to continue playing audio when the app is in the background and even when the device is locked.
55 |
56 | ## Functions
57 |
58 | ### `playSoundFile(fileName: string, fileType: string)`
59 |
60 | Play the sound file named `fileName` with file type `fileType`.
61 |
62 | ### `playSoundFileWithDelay(fileName: string, fileType: string, delay: number)` - iOS Only
63 |
64 | Play the sound file named `fileName` with file type `fileType` after a a delay of `delay` in _seconds_ from the current device time.
65 |
66 | ### `loadSoundFile(fileName: string, fileType: string)`
67 |
68 | Load the sound file named `fileName` with file type `fileType`, without playing it.
69 | This is useful when you want to play a large file, which can be slow to mount,
70 | and have precise control on when the sound is played. This can also be used in
71 | combination with `getInfo()` to get audio file `duration` without playing it.
72 | You should subscribe to the `onFinishedLoading` event to get notified when the
73 | file is loaded.
74 |
75 | ### `playUrl(url: string)`
76 |
77 | Play the audio from url. Supported formats are:
78 |
79 | - [AVPlayer (iOS)](https://stackoverflow.com/questions/21879981/avfoundation-avplayer-supported-formats-no-vob-or-mpg-containers)
80 | - [MediaPlayer (Android)](https://developer.android.com/guide/topics/media/media-formats)
81 |
82 | ### `loadUrl(url: string)`
83 |
84 | Load the audio from the given `url` without playing it. You can then play the audio
85 | by calling `play()`. This might be useful when you find the delay between calling
86 | `playUrl()` and the sound actually starts playing is too much.
87 |
88 | ### `playAsset(asset: number)`
89 |
90 | Play the audio from an asset, to get the asset number use `require('./assets/tone.mp3')`.
91 |
92 | Supported formats see `playUrl()` function.
93 |
94 | ### `loadAsset(asset: number)`
95 |
96 | Load the audio from an asset like above but without playing it. You can then play the audio by calling `play()`. This might be useful when you find the delay between calling `playAsset()` and the sound actually starts playing is too much.
97 |
98 | ### `addEventListener(callback: (object: ResultObject) => SubscriptionObject)`
99 |
100 | Subscribe to any event. Returns a subscription object. Subscriptions created by this function cannot be removed by calling `unmount()`. You **NEED** to call `yourSubscriptionObject.remove()` when you no longer need this event listener or whenever your component unmounts.
101 |
102 | Supported events are:
103 |
104 | 1. `FinishedLoading`
105 | 2. `FinishedPlaying`
106 | 3. `FinishedLoadingURL`
107 | 4. `FinishedLoadingFile`
108 |
109 | ```javascript
110 | // Example
111 | ...
112 | // Create instance variable(s) to store your subscriptions in your class
113 | _onFinishedPlayingSubscription = null
114 | _onFinishedLoadingSubscription = null
115 | _onFinishedLoadingFileSubscription = null
116 | _onFinishedLoadingURLSubscription = null
117 |
118 | // Subscribe to event(s) you want when component mounted
119 | componentDidMount() {
120 | _onFinishedPlayingSubscription = SoundPlayer.addEventListener('FinishedPlaying', ({ success }) => {
121 | console.log('finished playing', success)
122 | })
123 | _onFinishedLoadingSubscription = SoundPlayer.addEventListener('FinishedLoading', ({ success }) => {
124 | console.log('finished loading', success)
125 | })
126 | _onFinishedLoadingFileSubscription = SoundPlayer.addEventListener('FinishedLoadingFile', ({ success, name, type }) => {
127 | console.log('finished loading file', success, name, type)
128 | })
129 | _onFinishedLoadingURLSubscription = SoundPlayer.addEventListener('FinishedLoadingURL', ({ success, url }) => {
130 | console.log('finished loading url', success, url)
131 | })
132 | }
133 |
134 | // Remove all the subscriptions when component will unmount
135 | componentWillUnmount() {
136 | _onFinishedPlayingSubscription.remove()
137 | _onFinishedLoadingSubscription.remove()
138 | _onFinishedLoadingURLSubscription.remove()
139 | _onFinishedLoadingFileSubscription.remove()
140 | }
141 | ...
142 | ```
143 |
144 | ### `onFinishedPlaying(callback: (success: boolean) => any)`
145 |
146 | Subscribe to the "finished playing" event. The `callback` function is called whenever a file is finished playing. **This function will be deprecated soon, please use `addEventListener` above**.
147 |
148 | ### `onFinishedLoading(callback: (success: boolean) => any)`
149 |
150 | Subscribe to the "finished loading" event. The `callback` function is called whenever a file is finished loading, i.e. the file is ready to be `play()`, `resume()`, `getInfo()`, etc. **This function will be deprecated soon, please use `addEventListener` above**.
151 |
152 | ### `unmount()`
153 |
154 | Unsubscribe the "finished playing" and "finished loading" event. **This function will be deprecated soon, please use `addEventListener` and remove your own listener by calling `yourSubscriptionObject.remove()`**.
155 |
156 | ### `play()`
157 |
158 | Play the loaded sound file. This function is the same as `resume()`.
159 |
160 | ### `pause()`
161 |
162 | Pause the currently playing file.
163 |
164 | ### `resume()`
165 |
166 | Resume from pause and continue playing the same file. This function is the same as `play()`.
167 |
168 | ### `stop()`
169 |
170 | Stop playing, call `playSound(fileName: string, fileType: string)` to start playing again.
171 |
172 | ### `seek(seconds: number)`
173 |
174 | Seek to `seconds` of the currently playing file.
175 |
176 | ### `setSpeaker(on: boolean)`
177 |
178 | Overwrite default audio output to speaker, which forces `playUrl()` function to play from speaker.
179 |
180 | ### `setMixAudio(on: boolean)`
181 |
182 | Only available on iOS. If you set this option, your audio will be mixed with audio playing in background apps, such as the Music app.
183 |
184 | ### `setVolume(volume: number)`
185 |
186 | Set the volume of the current player. This does not change the volume of the device.
187 |
188 | ### `setNumberOfLoops(loops: number)`
189 |
190 | **iOS**: Set the number of loops. A negative value will loop indefinitely until the `stop()` command is called.
191 |
192 | **Android**: 0 will play the sound once. Any other number will loop indefinitely until the `stop()` command is called.
193 |
194 | ### `getInfo() => Promise<{currentTime: number, duration: number}>`
195 |
196 | Get the `currentTime` and `duration` of the currently mounted audio media. This function returns a promise which resolves to an Object containing `currentTime` and `duration` properties.
197 |
198 | ```javascript
199 | // Example
200 | ...
201 | playSong() {
202 | try {
203 | SoundPlayer.playSoundFile('engagementParty', 'm4a')
204 | } catch (e) {
205 | alert('Cannot play the file')
206 | console.log('cannot play the song file', e)
207 | }
208 | }
209 |
210 | async getInfo() { // You need the keyword `async`
211 | try {
212 | const info = await SoundPlayer.getInfo() // Also, you need to await this because it is async
213 | console.log('getInfo', info) // {duration: 12.416, currentTime: 7.691}
214 | } catch (e) {
215 | console.log('There is no song playing', e)
216 | }
217 | }
218 |
219 | onPressPlayButton() {
220 | this.playSong()
221 | this.getInfo()
222 | }
223 |
224 | ...
225 | ```
226 |
--------------------------------------------------------------------------------
/RNSoundPlayer.podspec:
--------------------------------------------------------------------------------
1 | require 'json'
2 | package = JSON.parse(File.read(File.join(__dir__, "package.json")))
3 |
4 | Pod::Spec.new do |s|
5 |
6 | s.name = "RNSoundPlayer"
7 | s.version = package['version']
8 | s.summary = package["description"]
9 | s.homepage = "https://github.com/johnsonsu/react-native-sound-player"
10 | s.license = package["license"]
11 | s.author = { "Johnson Su" => "johnsonsu@johnsonsu.com" }
12 | s.platforms = { :ios => "9.0", :tvos => "9.0" }
13 | s.source = { :git => "https://github.com/johnsonsu/react-native-sound-player.git", :tag => s.version }
14 | s.source_files = 'ios/**/*.{h,m}'
15 | s.preserve_paths = "package.json", "LICENSE"
16 | s.dependency 'React-Core'
17 |
18 | end
19 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | apply plugin: 'com.android.library'
3 |
4 | def DEFAULT_COMPILE_SDK_VERSION = 31
5 | def DEFAULT_BUILD_TOOLS_VERSION = '31.0.3'
6 | def DEFAULT_MIN_SDK_VERSION = 16
7 | def DEFAULT_TARGET_SDK_VERSION = 31
8 |
9 | def safeExtGet(prop, fallback) {
10 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
11 | }
12 |
13 | android {
14 | compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION)
15 | buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION)
16 |
17 | defaultConfig {
18 | minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION)
19 | targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION)
20 | versionCode 1
21 | versionName "1.0"
22 | ndk {
23 | abiFilters "armeabi-v7a", "x86"
24 | }
25 | }
26 | lintOptions {
27 | warning 'InvalidPackage'
28 | }
29 | }
30 |
31 | dependencies {
32 | implementation 'com.facebook.react:react-native:+'
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/android/src/main/java/com/johnsonsu/rnsoundplayer/RNSoundPlayerModule.java:
--------------------------------------------------------------------------------
1 | package com.johnsonsu.rnsoundplayer;
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.OnPreparedListener;
8 | import android.net.Uri;
9 |
10 | import java.io.File;
11 |
12 | import java.io.IOException;
13 | import javax.annotation.Nullable;
14 |
15 | import com.facebook.react.bridge.ReactApplicationContext;
16 | import com.facebook.react.bridge.ReactContextBaseJavaModule;
17 | import com.facebook.react.bridge.ReactMethod;
18 | import com.facebook.react.bridge.Callback;
19 | import com.facebook.react.bridge.ReactContext;
20 | import com.facebook.react.modules.core.DeviceEventManagerModule;
21 | import com.facebook.react.bridge.WritableMap;
22 | import com.facebook.react.bridge.Arguments;
23 | import com.facebook.react.bridge.Promise;
24 | import com.facebook.react.bridge.LifecycleEventListener;
25 |
26 |
27 | public class RNSoundPlayerModule extends ReactContextBaseJavaModule implements LifecycleEventListener {
28 |
29 | public final static String EVENT_SETUP_ERROR = "OnSetupError";
30 | public final static String EVENT_FINISHED_PLAYING = "FinishedPlaying";
31 | public final static String EVENT_FINISHED_LOADING = "FinishedLoading";
32 | public final static String EVENT_FINISHED_LOADING_FILE = "FinishedLoadingFile";
33 | public final static String EVENT_FINISHED_LOADING_URL = "FinishedLoadingURL";
34 |
35 | private final ReactApplicationContext reactContext;
36 | private MediaPlayer mediaPlayer;
37 | private float volume;
38 | private AudioManager audioManager;
39 |
40 | public RNSoundPlayerModule(ReactApplicationContext reactContext) {
41 | super(reactContext);
42 | this.reactContext = reactContext;
43 | this.volume = 1.0f;
44 | this.audioManager = (AudioManager) this.reactContext.getSystemService(Context.AUDIO_SERVICE);
45 | reactContext.addLifecycleEventListener(this);
46 | }
47 |
48 | @Override
49 | public String getName() {
50 | return "RNSoundPlayer";
51 | }
52 |
53 | @ReactMethod
54 | public void setSpeaker(Boolean on) {
55 | audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
56 | audioManager.setSpeakerphoneOn(on);
57 | }
58 |
59 | @Override
60 | public void onHostResume() {
61 | }
62 |
63 | @Override
64 | public void onHostPause() {
65 | }
66 |
67 | @Override
68 | public void onHostDestroy() {
69 |
70 | this.stop();
71 | if (mediaPlayer != null) {
72 | mediaPlayer.release();
73 | mediaPlayer = null;
74 | }
75 | }
76 |
77 | @ReactMethod
78 | public void playSoundFile(String name, String type) throws IOException {
79 | mountSoundFile(name, type);
80 | this.resume();
81 | }
82 |
83 | @ReactMethod
84 | public void loadSoundFile(String name, String type) throws IOException {
85 | mountSoundFile(name, type);
86 | }
87 |
88 | @ReactMethod
89 | public void playUrl(String url) throws IOException {
90 | prepareUrl(url);
91 | this.resume();
92 | }
93 |
94 | @ReactMethod
95 | public void loadUrl(String url) throws IOException {
96 | prepareUrl(url);
97 | }
98 |
99 | @ReactMethod
100 | public void pause() throws IllegalStateException {
101 | if (this.mediaPlayer != null) {
102 | this.mediaPlayer.pause();
103 | }
104 | }
105 |
106 | @ReactMethod
107 | public void resume() throws IOException, IllegalStateException {
108 | if (this.mediaPlayer != null) {
109 | this.setVolume(this.volume);
110 | this.mediaPlayer.start();
111 | }
112 | }
113 |
114 | @ReactMethod
115 | public void stop() throws IllegalStateException {
116 | if (this.mediaPlayer != null) {
117 | this.mediaPlayer.stop();
118 | }
119 | }
120 |
121 | @ReactMethod
122 | public void seek(float seconds) throws IllegalStateException {
123 | if (this.mediaPlayer != null) {
124 | this.mediaPlayer.seekTo((int) seconds * 1000);
125 | }
126 | }
127 |
128 | @ReactMethod
129 | public void setVolume(float volume) throws IOException {
130 | this.volume = volume;
131 | if (this.mediaPlayer != null) {
132 | this.mediaPlayer.setVolume(volume, volume);
133 | }
134 | }
135 |
136 | @ReactMethod
137 | public void setNumberOfLoops(int noOfLooping){
138 | // The expected boolean value
139 | Boolean looping;
140 | if (noOfLooping == 0) {
141 | looping = false;
142 | }
143 | else {
144 | looping = true;
145 | }
146 |
147 | if (this.mediaPlayer != null) {
148 | this.mediaPlayer.setLooping(looping);
149 | }
150 | }
151 |
152 | @ReactMethod
153 | public void getInfo(
154 | Promise promise) {
155 | if (this.mediaPlayer == null) {
156 | promise.resolve(null);
157 | return;
158 | }
159 | WritableMap map = Arguments.createMap();
160 | map.putDouble("currentTime", this.mediaPlayer.getCurrentPosition() / 1000.0);
161 | map.putDouble("duration", this.mediaPlayer.getDuration() / 1000.0);
162 | promise.resolve(map);
163 | }
164 |
165 | @ReactMethod
166 | public void addListener(String eventName) {
167 | // Set up any upstream listeners or background tasks as necessary
168 | }
169 |
170 | @ReactMethod
171 | public void removeListeners(Integer count) {
172 | // Remove upstream listeners, stop unnecessary background tasks
173 | }
174 |
175 | private void sendEvent(ReactApplicationContext reactContext,
176 | String eventName,
177 | @Nullable WritableMap params) {
178 | reactContext
179 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
180 | .emit(eventName, params);
181 | }
182 |
183 | private void mountSoundFile(String name, String type) throws IOException {
184 | try {
185 | Uri uri;
186 | int soundResID = getReactApplicationContext().getResources().getIdentifier(name, "raw", getReactApplicationContext().getPackageName());
187 |
188 | if (soundResID > 0) {
189 | uri = Uri.parse("android.resource://" + getReactApplicationContext().getPackageName() + "/raw/" + name);
190 | } else {
191 | uri = this.getUriFromFile(name, type);
192 | }
193 |
194 | if (this.mediaPlayer == null) {
195 | this.mediaPlayer = initializeMediaPlayer(uri);
196 | } else {
197 | this.mediaPlayer.reset();
198 | this.mediaPlayer.setDataSource(getCurrentActivity(), uri);
199 | this.mediaPlayer.prepare();
200 | }
201 | sendMountFileSuccessEvents(name, type);
202 | } catch (IOException e) {
203 | sendErrorEvent(e);
204 | }
205 | }
206 |
207 | private Uri getUriFromFile(String name, String type) {
208 | String folder = getReactApplicationContext().getFilesDir().getAbsolutePath();
209 | String file = (!type.isEmpty()) ? name + "." + type : name;
210 |
211 | // http://blog.weston-fl.com/android-mediaplayer-prepare-throws-status0x1-error1-2147483648
212 | // this helps avoid a common error state when mounting the file
213 | File ref = new File(folder + "/" + file);
214 |
215 | if (ref.exists()) {
216 | ref.setReadable(true, false);
217 | }
218 |
219 | return Uri.parse("file://" + folder + "/" + file);
220 | }
221 |
222 | private void prepareUrl(final String url) throws IOException {
223 | try {
224 | if (this.mediaPlayer == null) {
225 | Uri uri = Uri.parse(url);
226 | this.mediaPlayer = initializeMediaPlayer(uri);
227 | this.mediaPlayer.setOnPreparedListener(
228 | new OnPreparedListener() {
229 | @Override
230 | public void onPrepared(MediaPlayer mediaPlayer) {
231 | WritableMap onFinishedLoadingURLParams = Arguments.createMap();
232 | onFinishedLoadingURLParams.putBoolean("success", true);
233 | onFinishedLoadingURLParams.putString("url", url);
234 | sendEvent(getReactApplicationContext(), EVENT_FINISHED_LOADING_URL, onFinishedLoadingURLParams);
235 | }
236 | }
237 | );
238 | } else {
239 | Uri uri = Uri.parse(url);
240 | this.mediaPlayer.reset();
241 | this.mediaPlayer.setDataSource(getCurrentActivity(), uri);
242 | this.mediaPlayer.prepare();
243 | }
244 | WritableMap params = Arguments.createMap();
245 | params.putBoolean("success", true);
246 | sendEvent(getReactApplicationContext(), EVENT_FINISHED_LOADING, params);
247 | } catch (IOException e) {
248 | WritableMap errorParams = Arguments.createMap();
249 | errorParams.putString("error", e.getMessage());
250 | sendEvent(getReactApplicationContext(), EVENT_SETUP_ERROR, errorParams);
251 | }
252 | }
253 |
254 | private MediaPlayer initializeMediaPlayer(Uri uri) throws IOException {
255 | MediaPlayer mediaPlayer = MediaPlayer.create(getCurrentActivity(), uri);
256 |
257 | if (mediaPlayer == null) {
258 | throw new IOException("Failed to initialize MediaPlayer for URI: " + uri.toString());
259 | }
260 |
261 | mediaPlayer.setOnCompletionListener(
262 | new OnCompletionListener() {
263 | @Override
264 | public void onCompletion(MediaPlayer arg0) {
265 | WritableMap params = Arguments.createMap();
266 | params.putBoolean("success", true);
267 | sendEvent(getReactApplicationContext(), EVENT_FINISHED_PLAYING, params);
268 | }
269 | }
270 | );
271 |
272 | return mediaPlayer;
273 | }
274 |
275 | private void sendMountFileSuccessEvents(String name, String type) {
276 | WritableMap params = Arguments.createMap();
277 | params.putBoolean("success", true);
278 | sendEvent(reactContext, EVENT_FINISHED_LOADING, params);
279 |
280 | WritableMap onFinishedLoadingFileParams = Arguments.createMap();
281 | onFinishedLoadingFileParams.putBoolean("success", true);
282 | onFinishedLoadingFileParams.putString("name", name);
283 | onFinishedLoadingFileParams.putString("type", type);
284 | sendEvent(reactContext, EVENT_FINISHED_LOADING_FILE, onFinishedLoadingFileParams);
285 | }
286 |
287 |
288 | private void sendErrorEvent(IOException e) {
289 | WritableMap errorParams = Arguments.createMap();
290 | errorParams.putString("error", e.getMessage());
291 | sendEvent(reactContext, EVENT_SETUP_ERROR, errorParams);
292 | }
293 | }
294 |
--------------------------------------------------------------------------------
/android/src/main/java/com/johnsonsu/rnsoundplayer/RNSoundPlayerPackage.java:
--------------------------------------------------------------------------------
1 | package com.johnsonsu.rnsoundplayer;
2 |
3 | import java.util.Arrays;
4 | import java.util.Collections;
5 | import java.util.List;
6 |
7 | import com.facebook.react.ReactPackage;
8 | import com.facebook.react.bridge.NativeModule;
9 | import com.facebook.react.bridge.ReactApplicationContext;
10 | import com.facebook.react.uimanager.ViewManager;
11 | import com.facebook.react.bridge.JavaScriptModule;
12 |
13 | public class RNSoundPlayerPackage implements ReactPackage {
14 | @Override
15 | public List createNativeModules(ReactApplicationContext reactContext) {
16 | return Arrays.asList(new RNSoundPlayerModule(reactContext));
17 | }
18 |
19 | @Override
20 | public List createViewManagers(ReactApplicationContext reactContext) {
21 | return Collections.emptyList();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "react-native-sound-player" {
2 | import { EmitterSubscription } from "react-native";
3 |
4 | export type SoundPlayerEvent =
5 | | "OnSetupError"
6 | | "FinishedLoading"
7 | | "FinishedPlaying"
8 | | "FinishedLoadingURL"
9 | | "FinishedLoadingFile";
10 |
11 | export type SoundPlayerEventData = {
12 | success?: boolean;
13 | url?: string;
14 | name?: string;
15 | type?: string;
16 | };
17 |
18 | interface SoundPlayerType {
19 | playSoundFile: (name: string, type: string) => void;
20 | playSoundFileWithDelay: (name: string, type: string, delay: number) => void;
21 | loadSoundFile: (name: string, type: string) => void;
22 | playUrl: (url: string) => void;
23 | loadUrl: (url: string) => void;
24 | playAsset: (asset: number) => void;
25 | loadAsset: (asset: number) => void;
26 | /** @deprecated please use addEventListener*/
27 | onFinishedPlaying: (callback: (success: boolean) => unknown) => void;
28 | /** @deprecated please use addEventListener*/
29 | onFinishedLoading: (callback: (success: boolean) => unknown) => void;
30 | /** Subscribe to any event. Returns a subscription object. Subscriptions created by this function cannot be removed by calling unmount(). You NEED to call yourSubscriptionObject.remove() when you no longer need this event listener or whenever your component unmounts. */
31 | addEventListener: (
32 | eventName: SoundPlayerEvent,
33 | callback: (data: SoundPlayerEventData) => void
34 | ) => EmitterSubscription;
35 | /** Play the loaded sound file. This function is the same as `resume`. */
36 | play: () => void;
37 | /** Pause the currently playing file. */
38 | pause: () => void;
39 | /** Resume from pause and continue playing the same file. This function is the same as `play`. */
40 | resume: () => void;
41 | /** Stop playing, call `playSound` to start playing again. */
42 | stop: () => void;
43 | /** Seek to seconds of the currently playing file. */
44 | seek: (seconds: number) => void;
45 | /** Set the volume of the current player. This does not change the volume of the device. */
46 | setVolume: (volume: number) => void;
47 | /** Only available on iOS. Overwrite default audio output to speaker, which forces playUrl() function to play from speaker. */
48 | setSpeaker: (on: boolean) => void;
49 | /** Only available on iOS. If you set this option, your audio will be mixed with audio playing in background apps, such as the Music app. */
50 | setMixAudio: (on: boolean) => void;
51 | /** 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. */
52 | setNumberOfLoops: (loops: number) => void;
53 | /** Get the currentTime and duration of the currently mounted audio media. This function returns a promise which resolves to an Object containing currentTime and duration properties. */
54 | getInfo: () => Promise<{ currentTime: number; duration: number }>;
55 | /** @deprecated Please use addEventListener and remove your own listener by calling yourSubscriptionObject.remove(). */
56 | unmount: () => void;
57 | }
58 |
59 | const SoundPlayer: SoundPlayerType;
60 |
61 | export default SoundPlayer;
62 | }
63 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @flow
3 | */
4 | "use strict";
5 |
6 | import { NativeModules, NativeEventEmitter, Platform } from "react-native";
7 | import resolveAsset from 'react-native/Libraries/Image/resolveAssetSource';
8 | const { RNSoundPlayer } = NativeModules;
9 |
10 | const _soundPlayerEmitter = new NativeEventEmitter(RNSoundPlayer);
11 | let _finishedPlayingListener = null;
12 | let _finishedLoadingListener = null;
13 |
14 | export default {
15 | playSoundFile: (name: string, type: string) => {
16 | RNSoundPlayer.playSoundFile(name, type);
17 | },
18 |
19 | playSoundFileWithDelay: (name: string, type: string, delay: number) => {
20 | RNSoundPlayer.playSoundFileWithDelay(name, type, delay);
21 | },
22 |
23 | loadSoundFile: (name: string, type: string) => {
24 | RNSoundPlayer.loadSoundFile(name, type);
25 | },
26 |
27 | setNumberOfLoops: (loops: number) => {
28 | RNSoundPlayer.setNumberOfLoops(loops);
29 | },
30 |
31 | playUrl: (url: string) => {
32 | RNSoundPlayer.playUrl(url);
33 | },
34 |
35 | loadUrl: (url: string) => {
36 | RNSoundPlayer.loadUrl(url);
37 | },
38 |
39 | playAsset: async (asset: number) => {
40 | if (!(__DEV__) && Platform.OS === "android") {
41 | RNSoundPlayer.playSoundFile(resolveAsset(asset).uri, '');
42 | } else {
43 | RNSoundPlayer.playUrl(resolveAsset(asset).uri);
44 | }
45 | },
46 |
47 | loadAsset: (asset: number) => {
48 | if (!(__DEV__) && Platform.OS === "android") {
49 | RNSoundPlayer.loadSoundFile(resolveAsset(asset).uri, '');
50 | } else {
51 | RNSoundPlayer.loadUrl(resolveAsset(asset).uri);
52 | }
53 | },
54 |
55 | onFinishedPlaying: (callback: (success: boolean) => any) => {
56 | if (_finishedPlayingListener) {
57 | _finishedPlayingListener.remove();
58 | _finishedPlayingListener = undefined;
59 | }
60 |
61 | _finishedPlayingListener = _soundPlayerEmitter.addListener(
62 | "FinishedPlaying",
63 | callback
64 | );
65 | },
66 |
67 | onFinishedLoading: (callback: (success: boolean) => any) => {
68 | if (_finishedLoadingListener) {
69 | _finishedLoadingListener.remove();
70 | _finishedLoadingListener = undefined;
71 | }
72 |
73 | _finishedLoadingListener = _soundPlayerEmitter.addListener(
74 | "FinishedLoading",
75 | callback
76 | );
77 | },
78 |
79 | addEventListener: (
80 | eventName:
81 | | "OnSetupError"
82 | | "FinishedLoading"
83 | | "FinishedPlaying"
84 | | "FinishedLoadingURL"
85 | | "FinishedLoadingFile",
86 | callback: Function
87 | ) => _soundPlayerEmitter.addListener(eventName, callback),
88 |
89 | play: () => {
90 | // play and resume has the exact same implementation natively
91 | RNSoundPlayer.resume();
92 | },
93 |
94 | pause: () => {
95 | RNSoundPlayer.pause();
96 | },
97 |
98 | resume: () => {
99 | RNSoundPlayer.resume();
100 | },
101 |
102 | stop: () => {
103 | RNSoundPlayer.stop();
104 | },
105 |
106 | seek: (seconds: number) => {
107 | RNSoundPlayer.seek(seconds);
108 | },
109 |
110 | setVolume: (volume: number) => {
111 | RNSoundPlayer.setVolume(volume);
112 | },
113 |
114 | setSpeaker: (on: boolean) => {
115 | RNSoundPlayer.setSpeaker(on);
116 | },
117 |
118 | setMixAudio: (on: boolean) => {
119 | if (Platform.OS === "android") {
120 | console.log("setMixAudio is not implemented on Android");
121 | } else {
122 | RNSoundPlayer.setMixAudio(on);
123 | }
124 | },
125 |
126 | getInfo: async () => RNSoundPlayer.getInfo(),
127 |
128 | unmount: () => {
129 | if (_finishedPlayingListener) {
130 | _finishedPlayingListener.remove();
131 | _finishedPlayingListener = undefined;
132 | }
133 |
134 | if (_finishedLoadingListener) {
135 | _finishedLoadingListener.remove();
136 | _finishedLoadingListener = undefined;
137 | }
138 | },
139 | };
140 |
--------------------------------------------------------------------------------
/ios/RNSoundPlayer.h:
--------------------------------------------------------------------------------
1 | //
2 | // RNSoundPlayer
3 | //
4 | // Created by Johnson Su on 2018-07-10.
5 | //
6 |
7 | #import
8 | #import
9 | #import
10 |
11 | @interface RNSoundPlayer : RCTEventEmitter
12 | @property (nonatomic, strong) AVAudioPlayer *player;
13 | @property (nonatomic, strong) AVPlayer *avPlayer;
14 | @property (nonatomic) int loopCount;
15 | @end
16 |
--------------------------------------------------------------------------------
/ios/RNSoundPlayer.m:
--------------------------------------------------------------------------------
1 | //
2 | // RNSoundPlayer
3 | //
4 | // Created by Johnson Su on 2018-07-10.
5 | //
6 |
7 | #import "RNSoundPlayer.h"
8 | #import
9 |
10 | @implementation RNSoundPlayer
11 | {
12 | bool hasListeners;
13 | }
14 |
15 | static NSString *const EVENT_SETUP_ERROR = @"OnSetupError";
16 | static NSString *const EVENT_FINISHED_LOADING = @"FinishedLoading";
17 | static NSString *const EVENT_FINISHED_LOADING_FILE = @"FinishedLoadingFile";
18 | static NSString *const EVENT_FINISHED_LOADING_URL = @"FinishedLoadingURL";
19 | static NSString *const EVENT_FINISHED_PLAYING = @"FinishedPlaying";
20 |
21 | RCT_EXPORT_MODULE();
22 |
23 | @synthesize bridge = _bridge;
24 |
25 | + (BOOL)requiresMainQueueSetup {
26 | return YES;
27 | }
28 |
29 | - (instancetype)init {
30 | self = [super init];
31 | if (self) {
32 | self.loopCount = 0;
33 | [[NSNotificationCenter defaultCenter] addObserver:self
34 | selector:@selector(itemDidFinishPlaying:)
35 | name:AVPlayerItemDidPlayToEndTimeNotification
36 | object:nil];
37 | }
38 | return self;
39 | }
40 |
41 | - (void)dealloc {
42 | [[NSNotificationCenter defaultCenter] removeObserver:self];
43 | }
44 |
45 | - (NSArray *)supportedEvents {
46 | return @[EVENT_FINISHED_PLAYING, EVENT_FINISHED_LOADING, EVENT_FINISHED_LOADING_URL, EVENT_FINISHED_LOADING_FILE, EVENT_SETUP_ERROR];
47 | }
48 |
49 | -(void)startObserving {
50 | hasListeners = YES;
51 | }
52 |
53 | -(void)stopObserving {
54 | hasListeners = NO;
55 | }
56 |
57 | RCT_EXPORT_METHOD(playUrl:(NSString *)url) {
58 | [self prepareUrl:url];
59 | if (self.avPlayer) {
60 | [self.avPlayer play];
61 | }
62 | }
63 |
64 | RCT_EXPORT_METHOD(loadUrl:(NSString *)url) {
65 | [self prepareUrl:url];
66 | }
67 |
68 | RCT_EXPORT_METHOD(playSoundFile:(NSString *)name ofType:(NSString *)type) {
69 | [self mountSoundFile:name ofType:type];
70 | if (self.player) {
71 | [self.player play];
72 | }
73 | }
74 |
75 | RCT_EXPORT_METHOD(playSoundFileWithDelay:(NSString *)name ofType:(NSString *)type delay:(double)delay) {
76 | [self mountSoundFile:name ofType:type];
77 | if (self.player) {
78 | [self.player playAtTime:(self.player.deviceCurrentTime + delay)];
79 | }
80 | }
81 |
82 | RCT_EXPORT_METHOD(loadSoundFile:(NSString *)name ofType:(NSString *)type) {
83 | [self mountSoundFile:name ofType:type];
84 | }
85 |
86 | RCT_EXPORT_METHOD(pause) {
87 | if (self.player != nil) {
88 | [self.player pause];
89 | }
90 | if (self.avPlayer != nil) {
91 | [self.avPlayer pause];
92 | }
93 | }
94 |
95 | RCT_EXPORT_METHOD(resume) {
96 | if (self.player != nil) {
97 | [self.player play];
98 | }
99 | if (self.avPlayer != nil) {
100 | [self.avPlayer play];
101 | }
102 | }
103 |
104 | RCT_EXPORT_METHOD(stop) {
105 | if (self.player != nil) {
106 | [self.player stop];
107 | }
108 | if (self.avPlayer != nil) {
109 | [self.avPlayer pause];
110 | [self.avPlayer seekToTime:kCMTimeZero];
111 | }
112 | }
113 |
114 | RCT_EXPORT_METHOD(seek:(float)seconds) {
115 | if (self.player != nil) {
116 | self.player.currentTime = seconds;
117 | }
118 | if (self.avPlayer != nil) {
119 | [self.avPlayer seekToTime:CMTimeMakeWithSeconds(seconds, NSEC_PER_SEC)];
120 | }
121 | }
122 |
123 | #if !TARGET_OS_TV
124 | RCT_EXPORT_METHOD(setSpeaker:(BOOL)on) {
125 | AVAudioSession *session = [AVAudioSession sharedInstance];
126 | NSError *error = nil;
127 | if (on) {
128 | [session setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
129 | [session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error];
130 | } else {
131 | [session setCategory:AVAudioSessionCategoryPlayback error:&error];
132 | [session overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:&error];
133 | }
134 | [session setActive:YES error:&error];
135 | if (error) {
136 | [self sendErrorEvent:error];
137 | }
138 | }
139 | #endif
140 |
141 | RCT_EXPORT_METHOD(setMixAudio:(BOOL)on) {
142 | AVAudioSession *session = [AVAudioSession sharedInstance];
143 | NSError *error = nil;
144 | if (on) {
145 | [session setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&error];
146 | } else {
147 | [session setCategory:AVAudioSessionCategoryPlayback withOptions:0 error:&error];
148 | }
149 | [session setActive:YES error:&error];
150 | if (error) {
151 | [self sendErrorEvent:error];
152 | }
153 | }
154 |
155 | RCT_EXPORT_METHOD(setVolume:(float)volume) {
156 | if (self.player != nil) {
157 | [self.player setVolume:volume];
158 | }
159 | if (self.avPlayer != nil) {
160 | [self.avPlayer setVolume:volume];
161 | }
162 | }
163 |
164 | RCT_EXPORT_METHOD(setNumberOfLoops:(NSInteger)loopCount) {
165 | self.loopCount = loopCount;
166 | if (self.player != nil) {
167 | [self.player setNumberOfLoops:loopCount];
168 | }
169 | }
170 |
171 | RCT_REMAP_METHOD(getInfo,
172 | getInfoWithResolver:(RCTPromiseResolveBlock)resolve
173 | rejecter:(RCTPromiseRejectBlock)reject) {
174 | if (self.player != nil) {
175 | NSDictionary *data = @{
176 | @"currentTime": [NSNumber numberWithDouble:[self.player currentTime]],
177 | @"duration": [NSNumber numberWithDouble:[self.player duration]]
178 | };
179 | resolve(data);
180 | } else if (self.avPlayer != nil) {
181 | CMTime currentTime = [[self.avPlayer currentItem] currentTime];
182 | CMTime duration = [[[self.avPlayer currentItem] asset] duration];
183 | NSDictionary *data = @{
184 | @"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(currentTime)],
185 | @"duration": [NSNumber numberWithFloat:CMTimeGetSeconds(duration)]
186 | };
187 | resolve(data);
188 | } else {
189 | resolve(nil);
190 | }
191 | }
192 |
193 | - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
194 | if (hasListeners) {
195 | [self sendEventWithName:EVENT_FINISHED_PLAYING body:@{@"success": [NSNumber numberWithBool:flag]}];
196 | }
197 | }
198 |
199 | - (void)itemDidFinishPlaying:(NSNotification *)notification {
200 | if (hasListeners) {
201 | [self sendEventWithName:EVENT_FINISHED_PLAYING body:@{@"success": [NSNumber numberWithBool:YES]}];
202 | }
203 | }
204 |
205 | - (void)mountSoundFile:(NSString *)name ofType:(NSString *)type {
206 | if (self.avPlayer) {
207 | self.avPlayer = nil;
208 | }
209 |
210 | NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:name ofType:type];
211 |
212 | if (soundFilePath == nil) {
213 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
214 | NSString *documentsDirectory = [paths objectAtIndex:0];
215 | soundFilePath = [NSString stringWithFormat:@"%@.%@", [documentsDirectory stringByAppendingPathComponent:name], type];
216 | }
217 |
218 | NSURL *soundFileURL = [NSURL fileURLWithPath:soundFilePath];
219 | NSError *error = nil;
220 | self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error:&error];
221 | if (error) {
222 | [self sendErrorEvent:error];
223 | return;
224 | }
225 | [self.player setDelegate:self];
226 | [self.player setNumberOfLoops:self.loopCount];
227 | [self.player prepareToPlay];
228 | [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];
229 | if (error) {
230 | [self sendErrorEvent:error];
231 | return;
232 | }
233 | if (hasListeners) {
234 | [self sendEventWithName:EVENT_FINISHED_LOADING body:@{@"success": [NSNumber numberWithBool:YES]}];
235 | [self sendEventWithName:EVENT_FINISHED_LOADING_FILE body:@{@"success": [NSNumber numberWithBool:YES], @"name": name, @"type": type}];
236 | }
237 | }
238 |
239 | - (void)prepareUrl:(NSString *)url {
240 | if (self.player) {
241 | self.player = nil;
242 | }
243 | NSURL *soundURL = [NSURL URLWithString:url];
244 | self.avPlayer = [[AVPlayer alloc] initWithURL:soundURL];
245 | [self.avPlayer.currentItem addObserver:self forKeyPath:@"status" options:0 context:nil];
246 | }
247 |
248 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
249 | if (object == self.avPlayer.currentItem && [keyPath isEqualToString:@"status"] && hasListeners) {
250 | if (self.avPlayer.currentItem.status == AVPlayerItemStatusReadyToPlay) {
251 | [self sendEventWithName:EVENT_FINISHED_LOADING body:@{@"success": [NSNumber numberWithBool:YES]}];
252 | NSURL *url = [(AVURLAsset *)self.avPlayer.currentItem.asset URL];
253 | [self sendEventWithName:EVENT_FINISHED_LOADING_URL body:@{@"success": [NSNumber numberWithBool:YES], @"url": [url absoluteString]}];
254 | } else if (self.avPlayer.currentItem.status == AVPlayerItemStatusFailed) {
255 | [self sendErrorEvent:self.avPlayer.currentItem.error];
256 | }
257 | }
258 | }
259 |
260 | - (void)sendErrorEvent:(NSError *)error {
261 | if (hasListeners) {
262 | [self sendEventWithName:EVENT_SETUP_ERROR body:@{@"error": [error localizedDescription]}];
263 | }
264 | }
265 |
266 | @end
267 |
--------------------------------------------------------------------------------
/ios/RNSoundPlayer.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 13BE3DEE1AC21097009241FE /* RNSoundPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* RNSoundPlayer.m */; };
11 | /* End PBXBuildFile section */
12 |
13 | /* Begin PBXCopyFilesBuildPhase section */
14 | 58B511D91A9E6C8500147676 /* CopyFiles */ = {
15 | isa = PBXCopyFilesBuildPhase;
16 | buildActionMask = 2147483647;
17 | dstPath = "include/$(PRODUCT_NAME)";
18 | dstSubfolderSpec = 16;
19 | files = (
20 | );
21 | runOnlyForDeploymentPostprocessing = 0;
22 | };
23 | /* End PBXCopyFilesBuildPhase section */
24 |
25 | /* Begin PBXFileReference section */
26 | 134814201AA4EA6300B7C361 /* libRNSoundPlayer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNSoundPlayer.a; sourceTree = BUILT_PRODUCTS_DIR; };
27 | 13BE3DEC1AC21097009241FE /* RNSoundPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSoundPlayer.h; sourceTree = ""; };
28 | 13BE3DED1AC21097009241FE /* RNSoundPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSoundPlayer.m; sourceTree = ""; };
29 | /* End PBXFileReference section */
30 |
31 | /* Begin PBXFrameworksBuildPhase section */
32 | 58B511D81A9E6C8500147676 /* Frameworks */ = {
33 | isa = PBXFrameworksBuildPhase;
34 | buildActionMask = 2147483647;
35 | files = (
36 | );
37 | runOnlyForDeploymentPostprocessing = 0;
38 | };
39 | /* End PBXFrameworksBuildPhase section */
40 |
41 | /* Begin PBXGroup section */
42 | 134814211AA4EA7D00B7C361 /* Products */ = {
43 | isa = PBXGroup;
44 | children = (
45 | 134814201AA4EA6300B7C361 /* libRNSoundPlayer.a */,
46 | );
47 | name = Products;
48 | sourceTree = "";
49 | };
50 | 58B511D21A9E6C8500147676 = {
51 | isa = PBXGroup;
52 | children = (
53 | 13BE3DEC1AC21097009241FE /* RNSoundPlayer.h */,
54 | 13BE3DED1AC21097009241FE /* RNSoundPlayer.m */,
55 | 134814211AA4EA7D00B7C361 /* Products */,
56 | );
57 | sourceTree = "";
58 | };
59 | /* End PBXGroup section */
60 |
61 | /* Begin PBXNativeTarget section */
62 | 58B511DA1A9E6C8500147676 /* RNSoundPlayer */ = {
63 | isa = PBXNativeTarget;
64 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNSoundPlayer" */;
65 | buildPhases = (
66 | 58B511D71A9E6C8500147676 /* Sources */,
67 | 58B511D81A9E6C8500147676 /* Frameworks */,
68 | 58B511D91A9E6C8500147676 /* CopyFiles */,
69 | );
70 | buildRules = (
71 | );
72 | dependencies = (
73 | );
74 | name = RNSoundPlayer;
75 | productName = RCTDataManager;
76 | productReference = 134814201AA4EA6300B7C361 /* libRNSoundPlayer.a */;
77 | productType = "com.apple.product-type.library.static";
78 | };
79 | /* End PBXNativeTarget section */
80 |
81 | /* Begin PBXProject section */
82 | 58B511D31A9E6C8500147676 /* Project object */ = {
83 | isa = PBXProject;
84 | attributes = {
85 | LastUpgradeCheck = 0610;
86 | ORGANIZATIONNAME = Facebook;
87 | TargetAttributes = {
88 | 58B511DA1A9E6C8500147676 = {
89 | CreatedOnToolsVersion = 6.1.1;
90 | };
91 | };
92 | };
93 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNSoundPlayer" */;
94 | compatibilityVersion = "Xcode 3.2";
95 | developmentRegion = English;
96 | hasScannedForEncodings = 0;
97 | knownRegions = (
98 | en,
99 | );
100 | mainGroup = 58B511D21A9E6C8500147676;
101 | productRefGroup = 58B511D21A9E6C8500147676;
102 | projectDirPath = "";
103 | projectRoot = "";
104 | targets = (
105 | 58B511DA1A9E6C8500147676 /* RNSoundPlayer */,
106 | );
107 | };
108 | /* End PBXProject section */
109 |
110 | /* Begin PBXSourcesBuildPhase section */
111 | 58B511D71A9E6C8500147676 /* Sources */ = {
112 | isa = PBXSourcesBuildPhase;
113 | buildActionMask = 2147483647;
114 | files = (
115 | 13BE3DEE1AC21097009241FE /* RNSoundPlayer.m in Sources */,
116 | );
117 | runOnlyForDeploymentPostprocessing = 0;
118 | };
119 | /* End PBXSourcesBuildPhase section */
120 |
121 | /* Begin XCBuildConfiguration section */
122 | 58B511ED1A9E6C8500147676 /* Debug */ = {
123 | isa = XCBuildConfiguration;
124 | buildSettings = {
125 | ALWAYS_SEARCH_USER_PATHS = NO;
126 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
127 | CLANG_CXX_LIBRARY = "libc++";
128 | CLANG_ENABLE_MODULES = YES;
129 | CLANG_ENABLE_OBJC_ARC = YES;
130 | CLANG_WARN_BOOL_CONVERSION = YES;
131 | CLANG_WARN_CONSTANT_CONVERSION = YES;
132 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
133 | CLANG_WARN_EMPTY_BODY = YES;
134 | CLANG_WARN_ENUM_CONVERSION = YES;
135 | CLANG_WARN_INT_CONVERSION = YES;
136 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
137 | CLANG_WARN_UNREACHABLE_CODE = YES;
138 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
139 | COPY_PHASE_STRIP = NO;
140 | ENABLE_STRICT_OBJC_MSGSEND = YES;
141 | GCC_C_LANGUAGE_STANDARD = gnu99;
142 | GCC_DYNAMIC_NO_PIC = NO;
143 | GCC_OPTIMIZATION_LEVEL = 0;
144 | GCC_PREPROCESSOR_DEFINITIONS = (
145 | "DEBUG=1",
146 | "$(inherited)",
147 | );
148 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
149 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
150 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
151 | GCC_WARN_UNDECLARED_SELECTOR = YES;
152 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
153 | GCC_WARN_UNUSED_FUNCTION = YES;
154 | GCC_WARN_UNUSED_VARIABLE = YES;
155 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
156 | MTL_ENABLE_DEBUG_INFO = YES;
157 | ONLY_ACTIVE_ARCH = YES;
158 | SDKROOT = iphoneos;
159 | };
160 | name = Debug;
161 | };
162 | 58B511EE1A9E6C8500147676 /* Release */ = {
163 | isa = XCBuildConfiguration;
164 | buildSettings = {
165 | ALWAYS_SEARCH_USER_PATHS = NO;
166 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
167 | CLANG_CXX_LIBRARY = "libc++";
168 | CLANG_ENABLE_MODULES = YES;
169 | CLANG_ENABLE_OBJC_ARC = YES;
170 | CLANG_WARN_BOOL_CONVERSION = YES;
171 | CLANG_WARN_CONSTANT_CONVERSION = YES;
172 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
173 | CLANG_WARN_EMPTY_BODY = YES;
174 | CLANG_WARN_ENUM_CONVERSION = YES;
175 | CLANG_WARN_INT_CONVERSION = YES;
176 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
177 | CLANG_WARN_UNREACHABLE_CODE = YES;
178 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
179 | COPY_PHASE_STRIP = YES;
180 | ENABLE_NS_ASSERTIONS = NO;
181 | ENABLE_STRICT_OBJC_MSGSEND = YES;
182 | GCC_C_LANGUAGE_STANDARD = gnu99;
183 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
184 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
185 | GCC_WARN_UNDECLARED_SELECTOR = YES;
186 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
187 | GCC_WARN_UNUSED_FUNCTION = YES;
188 | GCC_WARN_UNUSED_VARIABLE = YES;
189 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
190 | MTL_ENABLE_DEBUG_INFO = NO;
191 | SDKROOT = iphoneos;
192 | VALIDATE_PRODUCT = YES;
193 | };
194 | name = Release;
195 | };
196 | 58B511F01A9E6C8500147676 /* Debug */ = {
197 | isa = XCBuildConfiguration;
198 | buildSettings = {
199 | HEADER_SEARCH_PATHS = (
200 | "$(inherited)",
201 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
202 | "$(SRCROOT)/../../React/**",
203 | "$(SRCROOT)/../../node_modules/react-native/React/**",
204 | );
205 | LIBRARY_SEARCH_PATHS = "$(inherited)";
206 | OTHER_LDFLAGS = "-ObjC";
207 | PRODUCT_NAME = RNSoundPlayer;
208 | SKIP_INSTALL = YES;
209 | };
210 | name = Debug;
211 | };
212 | 58B511F11A9E6C8500147676 /* Release */ = {
213 | isa = XCBuildConfiguration;
214 | buildSettings = {
215 | HEADER_SEARCH_PATHS = (
216 | "$(inherited)",
217 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
218 | "$(SRCROOT)/../../React/**",
219 | );
220 | LIBRARY_SEARCH_PATHS = "$(inherited)";
221 | OTHER_LDFLAGS = "-ObjC";
222 | PRODUCT_NAME = RNSoundPlayer;
223 | SKIP_INSTALL = YES;
224 | };
225 | name = Release;
226 | };
227 | /* End XCBuildConfiguration section */
228 |
229 | /* Begin XCConfigurationList section */
230 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNSoundPlayer" */ = {
231 | isa = XCConfigurationList;
232 | buildConfigurations = (
233 | 58B511ED1A9E6C8500147676 /* Debug */,
234 | 58B511EE1A9E6C8500147676 /* Release */,
235 | );
236 | defaultConfigurationIsVisible = 0;
237 | defaultConfigurationName = Release;
238 | };
239 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNSoundPlayer" */ = {
240 | isa = XCConfigurationList;
241 | buildConfigurations = (
242 | 58B511F01A9E6C8500147676 /* Debug */,
243 | 58B511F11A9E6C8500147676 /* Release */,
244 | );
245 | defaultConfigurationIsVisible = 0;
246 | defaultConfigurationName = Release;
247 | };
248 | /* End XCConfigurationList section */
249 | };
250 | rootObject = 58B511D31A9E6C8500147676 /* Project object */;
251 | }
252 |
--------------------------------------------------------------------------------
/ios/RNSoundPlayer.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-sound-player",
3 | "version": "0.14.5",
4 | "description": "Play or stream audio files in ReactNative on iOS/Android",
5 | "main": "index.js",
6 | "types": "index.d.ts",
7 | "keywords": [
8 | "reactnative",
9 | "react-native",
10 | "sound",
11 | "player",
12 | "audio",
13 | "streaming"
14 | ],
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/johnsonsu/react-native-sound-player.git"
18 | },
19 | "author": {
20 | "name": "Johnson Su",
21 | "email": "johnsonsu@johnsonsu.com"
22 | },
23 | "prettier": {
24 | "trailingComma": "es5",
25 | "tabWidth": 2,
26 | "semi": true,
27 | "singleQuote": false
28 | },
29 | "license": "MIT"
30 | }
31 |
--------------------------------------------------------------------------------