diff --git a/README.md b/README.md index 568db996..4ba92325 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ var whoosh = new Sound('whoosh.mp3', Sound.MAIN_BUNDLE, (error) => { } }); -// Play the sound +// Play the sound with an onEnd callback whoosh.play((success) => { if (success) { console.log('successfully finished playing'); @@ -46,10 +46,30 @@ whoosh.play((success) => { } }); +// Reduce the volume by half +whoosh.setVolume(0.5); + +// Position the sound to the full right in a stereo field +whoosh.setPan(1); + +// Loop indefinitely until stop() is called +whoosh.setNumberOfLoops(-1); + +// Get properties of the player instance +console.log('volume: ' + whoosh.getVolume()); +console.log('pan: ' + whoosh.getPan()); +console.log('loops: ' + whoosh.getNumberOfLoops()); + +// Seek to a specific point in seconds +whoosh.setCurrentTime(2.5); + +// Get the current playback point in seconds +whoosh.getCurrentTime((seconds) => console.log('at ' + seconds)); + // Pause the sound whoosh.pause(); -// Stop the sound +// Stop the sound and rewind to the beginning whoosh.stop(); // Release the audio player resource @@ -62,7 +82,7 @@ whoosh.release(); `basePath` {?string} Optional base path of the file. Omit this or pass `''` if `filename` is an absolute path. Otherwise, you may use one of the predefined directories: `Sound.MAIN_BUNDLE`, `Sound.DOCUMENT`, `Sound.LIBRARY`, `Sound.CACHES`. -`onError` {?function(error, props)} Optional callback function. If the file is successfully loaded, the first parameter `error` is `null`, and `props` contains an object with two properties: `duration` (in seconds) and `numberOfChannels` (1 for mono and 2 for stereo sound), both of which can also be accessed from the `Sound` instance object. If an initialization error is encountered (e.g. file not found), `error` will be an object containing `code`, `description`, and the stack trace. +`onError` {?function(error, props)} Optional callback function. If the file is successfully loaded, the first parameter `error` is `null`, and `props` contains an object with two properties: `duration` (in seconds) and `numberOfChannels` (`1` for mono and `2` for stereo sound), both of which can also be accessed from the `Sound` instance object. If an initialization error is encountered (e.g. file not found), `error` will be an object containing `code`, `description`, and the stack trace. ### `play(onEnd)` `onEnd` {?function(successfully)} Optinoal callback function that gets called when the playback finishes successfully or an audio decoding error interrupts it. @@ -76,11 +96,42 @@ Stop the playback. ### `release()` Release the audio player resource associated with the instance. +### `getDuration()` +Return the duration in seconds, or `-1` before the sound gets loaded. + +### `getNumberOfChannels()` +Return the number of channels (`1` for mono and `2` for stereo sound), or `-1` before the sound gets loaded. + +### `getVolume()` +Return the volume of the audio player (not the system-wide volume), ranging from `0.0` (silence) through `1.0` (full volume, the default). + +### `setVolume(value)` +`value` {number} Set the volume, ranging from `0.0` (silence) through `1.0` (full volume). + +### `getPan()` +Return the stereo pan position of the audio player (not the system-wide pan), ranging from `-1.0` (full left) through `1.0` (full right). The default value is `0.0` (center). + +### `setPan(value)` +`value` {number} Set the pan, ranging from `-1.0` (full left) through `1.0` (full right). + +### `getNumberOfLoops()` +Return the loop count of the audio player. The default is `0` which 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. + +### `setNumberOfLoops(value)` +`value` {number} Set the loop count. `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. + +### `getCurrentTime(callback)` +`callback` {function(seconds)} Callback will receive the current playback position in seconds. + +### `setCurrentTime(value)` +`value` {number} Seek to a particular playback point in seconds. + ### `Sound.enable(enabled)` `enabled` {boolean} Enable or disable sound for the entire app. Sound is enabled by default. ## Notes -- To minimize playback delay, you may want to preload a sound file (e.g. `var s = new Sound(...);`) during app initialization. -- You can play multiple sound files at the same time. Under the hood, this module uses AVAudioSessionCategoryAmbient to mix sounds. +- To minimize playback delay, you may want to preload a sound file without calling `play()` (e.g. `var s = new Sound(...);`) during app initialization. +- You can play multiple sound files at the same time. Under the hood, this module uses `AVAudioSessionCategoryAmbient` to mix sounds. - You may reuse a `Sound` instance for multiple playbacks. -- The module wraps AVAudioPlayer which supports aac, aiff, mp3, wav etc. The full list of supported formats can be found at https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/MultimediaPG/UsingAudio/UsingAudio.html +- The module wraps `AVAudioPlayer` which supports aac, aiff, mp3, wav etc. The full list of supported formats can be found at https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/MultimediaPG/UsingAudio/UsingAudio.html +- You may chain non-getter calls, for example, `sound.setVolume(.5).setPan(.5).play()`. \ No newline at end of file diff --git a/RNSound/RNSound.m b/RNSound/RNSound.m index ca6e92a1..6a0a7236 100644 --- a/RNSound/RNSound.m +++ b/RNSound/RNSound.m @@ -98,7 +98,7 @@ -(NSDictionary *)constantsToExport { AVAudioPlayer* player = [self playerForKey:key]; if (player) { [player stop]; - [player setCurrentTime:0.0]; + player.currentTime = 0; } } @@ -111,4 +111,42 @@ -(NSDictionary *)constantsToExport { } } +RCT_EXPORT_METHOD(setVolume:(nonnull NSNumber*)key withValue:(nonnull NSNumber*)value) { + AVAudioPlayer* player = [self playerForKey:key]; + if (player) { + player.volume = [value floatValue]; + } +} + +RCT_EXPORT_METHOD(setPan:(nonnull NSNumber*)key withValue:(nonnull NSNumber*)value) { + AVAudioPlayer* player = [self playerForKey:key]; + if (player) { + player.pan = [value floatValue]; + } +} + +RCT_EXPORT_METHOD(setNumberOfLoops:(nonnull NSNumber*)key withValue:(nonnull NSNumber*)value) { + AVAudioPlayer* player = [self playerForKey:key]; + if (player) { + player.numberOfLoops = [value intValue]; + } +} + +RCT_EXPORT_METHOD(setCurrentTime:(nonnull NSNumber*)key withValue:(nonnull NSNumber*)value) { + AVAudioPlayer* player = [self playerForKey:key]; + if (player) { + player.currentTime = [value doubleValue]; + } +} + +RCT_EXPORT_METHOD(getCurrentTime:(nonnull NSNumber*)key + withCallback:(RCTResponseSenderBlock)callback) { + AVAudioPlayer* player = [self playerForKey:key]; + if (player) { + callback(@[@(player.currentTime)]); + } else { + callback(@[@(-1)]); + } +} + @end diff --git a/package.json b/package.json index 0acfe184..deb459d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-sound", - "version": "0.4.1", + "version": "0.5.0", "description": "React Native module for playing sound clips", "main": "sound.js", "repository": { diff --git a/sound.js b/sound.js index 8a548e70..ec59a555 100644 --- a/sound.js +++ b/sound.js @@ -5,17 +5,20 @@ var RNSound = require('react-native').NativeModules.RNSound; var nextKey = 0; function Sound(filename, basePath, onError) { - this.filename = basePath ? basePath + '/' + filename : filename; - this.key = nextKey++; - this.duration = -1; - this.numberOfChannels = -1; - RNSound.prepare(this.filename, this.key, (error, props) => { + this._filename = basePath ? basePath + '/' + filename : filename; + this._key = nextKey++; + this._duration = -1; + this._numberOfChannels = -1; + this._volume = 1; + this._pan = 0; + this._numberOfLoops = 0; + RNSound.prepare(this._filename, this._key, (error, props) => { if (props) { if (typeof props.duration === 'number') { - this.duration = props.duration; + this._duration = props.duration; } if (typeof props.numberOfChannels === 'number') { - this.numberOfChannels = props.numberOfChannels; + this._numberOfChannels = props.numberOfChannels; } } onError && onError(error); @@ -23,19 +26,67 @@ function Sound(filename, basePath, onError) { } Sound.prototype.play = function(onEnd) { - RNSound.play(this.key, (successfully) => onEnd && onEnd(successfully)); + RNSound.play(this._key, (successfully) => onEnd && onEnd(successfully)); + return this; }; Sound.prototype.pause = function() { - RNSound.pause(this.key); + RNSound.pause(this._key); + return this; }; Sound.prototype.stop = function() { - RNSound.stop(this.key); + RNSound.stop(this._key); + return this; }; Sound.prototype.release = function() { - RNSound.release(this.key); + RNSound.release(this._key); + return this; +}; + +Sound.prototype.getDuration = function() { + return this._duration; +}; + +Sound.prototype.getNumberOfChannels = function() { + return this._numberOfChannels; +}; + +Sound.prototype.getVolume = function() { + return this._volume; +}; + +Sound.prototype.setVolume = function(value) { + RNSound.setVolume(this._key, this._volume = value); + return this; +}; + +Sound.prototype.getPan = function() { + return this._pan; +}; + +Sound.prototype.setPan = function(value) { + RNSound.setPan(this._key, this._pan = value); + return this; +}; + +Sound.prototype.getNumberOfLoops = function() { + return this._numberOfLoops; +}; + +Sound.prototype.setNumberOfLoops = function(value) { + RNSound.setNumberOfLoops(this._key, this._numberOfLoops = value); + return this; +}; + +Sound.prototype.getCurrentTime = function(callback) { + RNSound.getCurrentTime(this._key, callback); +}; + +Sound.prototype.setCurrentTime = function(value) { + RNSound.setCurrentTime(this._key, value); + return this; }; Sound.enable = function(enabled) {