diff --git a/ISound.js b/ISound.js index 3dfd2ac..895af1f 100644 --- a/ISound.js +++ b/ISound.js @@ -15,14 +15,13 @@ function ISound() { // the following properties are public but should NOT be assigned directly. // instead, use the setter functions: setId, setVolume, setPan, setLoop, setPitch. - this.id = 0; + this.id = null; this.volume = 1.0; this.pan = 0.0; this.loop = false; this.pitch = 0.0; // private properties - this._src = ''; this._loaded = false; this._loading = false; this._unloading = false; @@ -38,9 +37,7 @@ ISound.prototype.init = function () { /* virtual function */ }; //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ISound.prototype.setId = function (value) { - var audioPath = this.audioManager.settings.audioPath; this.id = value; - this._src = audioPath + value + '.mp3'; this._loaded = false; }; @@ -67,11 +64,9 @@ ISound.prototype.setPitch = function (pitch) { //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ /** Load sound. Abstract method to be overwritten * @private - * - * @param {String} filePath - audio file to be loaded */ -ISound.prototype._load = function (filePath) { - console.log('ISound load call: ' + filePath); +ISound.prototype._load = function () { + console.log('ISound load call: ' + this.id); return this._finalizeLoad(null); }; @@ -88,8 +83,7 @@ ISound.prototype.load = function (cb) { if (this._loading) return; this._loading = true; - var filePath = this.audioManager.settings.assetSeverUrl + this._src; - return this._load(filePath, this); + return this._load(); }; //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ @@ -137,8 +131,7 @@ ISound.prototype.unload = function () { this.audioManager.usedMemory -= this.usedMemory; this.setVolume(1.0); this.setPan(0.0); - this.id = 0; - this._src = ''; + this.id = null; this._loaded = false; this.usedMemory = 0; @@ -176,7 +169,7 @@ ISound.prototype.play = function (vol, pan, pitch) { /** Play sound. Abstract method to be overwritten */ ISound.prototype._play = function () { this.playing = true; - console.log('ISound play call: "' + this._src + '"'); + console.log('ISound play call: "' + this.id + '"'); }; //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ diff --git a/README.md b/README.md index b0e3757..3c5bdd5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ Play sounds using Web Audio, fallback on HTML5 Audio. `AudioManager` is specifically designed to works for games that have a big quantity of audio assets. Loading and unloading is made easy and transparent. -If available, WizAsset is used for downloading files to disc. # API @@ -151,3 +150,41 @@ audioManager.settings.crossFade = true; // default is false ```javascript audioManager.release(); ``` + +### Custom loader function +`audioManager.settings.getFileUri` can be overriden if you want more control on how to +resolve files uri from sound id. The function have one on the following synchronous or +asynchronous profile: + +```javascript +function getFileUriSync(audioPath, id) { + var uri; + // ... + return uri; +} +``` + +```javascript +function getFileUriAsync(audioPath, id, callback) { + var uri; + var error = null; + // ... + return callback(error, uri); +} +``` + +For instance, here's how you can add [wizAssets](https://github.com/Wizcorp/phonegap-plugin-wizAssets) +to cache files on disc in a PhoneGap application: +```javascript +if (window.wizAssets) { + audioManager.settings.getFileUri = function wizAssetsGetUri(audioPath, id, cb) { + window.wizAssets.downloadFile(audioPath + id + '.mp3', 'audio/' + id + '.mp3', + function onSuccess(uri) { + cb(null, uri) + }, + cb // onFail callback + ); + } +} +``` + diff --git a/Sound.js b/Sound.js index 2702d92..1189bfc 100644 --- a/Sound.js +++ b/Sound.js @@ -39,15 +39,13 @@ Sound.prototype.setLoop = function (value) { //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ /** Load sound * @private - * - * @param {String} filePath - audio file to be loaded */ -Sound.prototype._load = function (filePath) { +Sound.prototype._load = function () { var self = this; - function loadFail() { + function loadFail(error) { // TODO: keep track that loading has failed to not retry to loading it - self._finalizeLoad('Sound could not be loaded.'); + self._finalizeLoad(error); } function onAudioLoaded() { @@ -58,10 +56,10 @@ Sound.prototype._load = function (filePath) { self._finalizeLoad(null); } - function onAudioError() { + function onAudioError(error) { this.removeEventListener('canplaythrough', onAudioLoaded); this.removeEventListener('error', onAudioError); - loadFail(); + loadFail(error); } function loadAudio(uri) { @@ -72,10 +70,24 @@ Sound.prototype._load = function (filePath) { self._audio.load(); } - if (window.wizAssets) { - window.wizAssets.downloadFile(filePath, this._src, loadAudio, loadFail); + var getFileUri = this.audioManager.settings.getFileUri; + var audioPath = this.audioManager.settings.audioPath; + + if (getFileUri.length > 2) { + // asynchronous + getFileUri(audioPath, this.id, function onUri(error, uri) { + if (error) return loadFail(error); + loadAudio(uri); + }); } else { - loadAudio(filePath); + // synchronous + try { + var uri = getFileUri(audioPath, this.id); + if (!uri) return loadFail('emptyUri'); + loadAudio(uri); + } catch (error) { + loadFail(error); + } } }; diff --git a/SoundBuffered.js b/SoundBuffered.js index 02a6a2e..8d50533 100644 --- a/SoundBuffered.js +++ b/SoundBuffered.js @@ -156,17 +156,15 @@ SoundBuffered.prototype._setPlaybackRate = function (portamento) { //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ /** Load sound * @private - * - * @param {String} filePath - audio file to be loaded */ -SoundBuffered.prototype._load = function (filePath) { +SoundBuffered.prototype._load = function () { var self = this; this._createAudioNodes(); - function loadFail() { + function loadFail(error) { // TODO: keep track that loading has failed so we don't retry to load it ? - self._finalizeLoad('Sound could not be loaded.'); + self._finalizeLoad(error); } function onAudioLoaded(buffer) { @@ -183,7 +181,7 @@ SoundBuffered.prototype._load = function (filePath) { xobj.onreadystatechange = function onXhrStateChange() { if (~~xobj.readyState !== 4) return; if (~~xobj.status !== 200 && ~~xobj.status !== 0) { - return loadFail(); + return loadFail('xhrError:' + xobj.status); } if (self.audioContext) { self.audioContext.decodeAudioData(xobj.response, onAudioLoaded, loadFail); @@ -192,14 +190,29 @@ SoundBuffered.prototype._load = function (filePath) { self._finalizeLoad(null); } }; + xobj.open('GET', uri, true); xobj.send(); } - if (window.wizAssets) { - window.wizAssets.downloadFile(filePath, this._src, loadAudio, loadFail); + var getFileUri = this.audioManager.settings.getFileUri; + var audioPath = this.audioManager.settings.audioPath; + + if (getFileUri.length > 2) { + // asynchronous + getFileUri(audioPath, this.id, function onUri(error, uri) { + if (error) return loadFail(error); + loadAudio(uri); + }); } else { - loadAudio(filePath); + // synchronous + try { + var uri = getFileUri(audioPath, this.id); + if (!uri) return loadFail('emptyUri'); + loadAudio(uri); + } catch (error) { + loadFail(error); + } } }; @@ -233,8 +246,8 @@ SoundBuffered.prototype._play = function (pitch) { } // prevent a looped sound to play twice + // TODO: add a flag to allow force restart if (this.loop && this.playing) { - // TODO: restart sound from beginning // update pitch if needed if ((pitch || pitch === 0) && pitch !== this._playPitch) { this._playPitch = pitch; @@ -243,15 +256,19 @@ SoundBuffered.prototype._play = function (pitch) { return; } - // if sound is still fading out, we stop and clear it before restarting it + this.playing = true; + this.gain.setTargetAtTime(this.volume, this.audioContext.currentTime, this.fade); + + // if sound is still fading out, clear all onStop callback if (this._fadeTimeout) { this._onStopCallback = null; - this._stopAndClear(); + this.stopping = false; + this.source.onended = null; + window.clearTimeout(this._fadeTimeout); + this._fadeTimeout = null; + return; } - this.playing = true; - this.gain.setTargetAtTime(this.volume, this.audioContext.currentTime, this.fade); - var sourceNode = this.source = this.audioContext.createBufferSource(); sourceNode.connect(this.sourceConnector); diff --git a/component.json b/component.json index 220060d..94fb5ba 100644 --- a/component.json +++ b/component.json @@ -1,7 +1,7 @@ { "name": "AudioManager", "repo": "Wizcorp/AudioManager", - "version": "0.1.5", + "version": "0.1.6", "description": "Play sounds using Web Audio, fallback to HTML5 Audio", "dependencies": { "Wizcorp/util": "0.1.0" diff --git a/index.js b/index.js index 2684e68..7564b87 100644 --- a/index.js +++ b/index.js @@ -44,13 +44,13 @@ function AudioManager(channels) { // settings this.settings = { audioPath: '', // path to audio assets folder - assetSeverUrl: '', // asset server url maxSoundGroup: 500, maxUsedMemory: 300, // seconds defaultFade: 2, // seconds maxPlayLatency: 1000, // milliseconds fadeOutRatio: 0.4, - crossFading: false + crossFading: false, + getFileUri: function getFileUri(audioPath, id) { return audioPath + id + '.mp3'; } }; // create channels @@ -268,8 +268,8 @@ AudioManager.prototype.playLoopSound = function (channelId, soundId, volume, pan var defaultFade = this.settings.defaultFade; var crossFading = this.settings.crossFading; var channel = this.channels[channelId]; - var currentSoundId = channel.loopId; var currentSound = channel.loopSound; + var currentSoundId = currentSound && currentSound.id; volume = Math.max(0, Math.min(1, volume || 1)); @@ -280,7 +280,7 @@ AudioManager.prototype.playLoopSound = function (channelId, soundId, volume, pan if (channel.muted) return; // if requested sound is already playing, update volume, pan and pitch - if (soundId === currentSoundId && currentSound && currentSound.playing) { + if (soundId === currentSoundId && currentSound && (currentSound.playing || currentSound.stopping)) { currentSound.play(volume * channel.volume, pan, pitch); if (channel.nextLoop) { channel.nextLoop.cancelOnLoadCallbacks(); diff --git a/package.json b/package.json index 5fa238d..ea711e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "audio-manager", - "version": "0.1.5", + "version": "0.1.6", "description": "Play sounds using Web Audio, fallback to HTML5 Audio", "main": "index.js", "scripts": {