Skip to content

Commit

Permalink
Add support for volume, pan, loops, and playback time
Browse files Browse the repository at this point in the history
  • Loading branch information
zmxv committed Dec 3, 2015
1 parent 3b19a5d commit d0a6d29
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 19 deletions.
63 changes: 57 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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()`.
40 changes: 39 additions & 1 deletion RNSound/RNSound.m
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ -(NSDictionary *)constantsToExport {
AVAudioPlayer* player = [self playerForKey:key];
if (player) {
[player stop];
[player setCurrentTime:0.0];
player.currentTime = 0;
}
}

Expand All @@ -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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
73 changes: 62 additions & 11 deletions sound.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,88 @@ 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);
});
}

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) {
Expand Down

0 comments on commit d0a6d29

Please sign in to comment.