/**
* @module cloudkid
*/
(function() {
"use strict";
var OS = cloudkid.OS,
MediaLoader = cloudkid.MediaLoader,
LoadTask = cloudkid.LoadTask,
Task = cloudkid.Task,
TaskManager = cloudkid.TaskManager;
/**
* Acts as a wrapper for SoundJS as well as adding lots of other functionality
* for managing sounds.
*
* @class Sound
*/
var Sound = function()
{
this._sounds = {};
this._fades = [];
this._contexts = {};
this._pool = [];
this._update = this._update.bind(this);
this._markLoaded = this._markLoaded.bind(this);
/**
* A copy of _playAfterLoad bound to this Sound instance.
* @property {function} _playAfterLoadBound
* @private
*/
this._playAfterLoadBound = this._playAfterLoad.bind(this);
};
var p = Sound.prototype = {};
var _instance = null;
/**
* Dictionary of sound objects, containing configuration info and playback objects.
* @property {Object} _sounds
* @private
*/
p._sounds = null;
/**
* Array of SoundInst objects that are being faded in or out.
* @property {Array} _fades
* @private
*/
p._fades = null;
/**
* Array of SoundInst objects waiting to be used.
* @property {Array} _pool
* @private
*/
p._pool = null;
/**
* The extension of the supported sound type that will be used.
* @property {string} supportedSound
* @public
*/
p.supportedSound = null;
/**
* Dictionary of SoundContexts.
* @property {Object} _contexts
* @private
*/
p._contexts = null;
//sound states
var UNLOADED = 0;
var LOADING = 1;
var LOADED = 2;
var UPDATE_ALIAS = "CKSOUND";
/**
* A constant for telling Sound not to handle a sound with play(), but to
* return what SoundJS returns directly.
* @property {String} UNHANDLED
* @public
* @static
*/
Sound.UNHANDLED = "unhandled";
/**
* Initializes the Sound singleton. If using createjs.FlashPlugin, you will be responsible for setting
* createjs.FlashPlugin.BASE_PATH.
* @method init
* @static
* @param {Array} pluginOrder The SoundJS plugins to pass to createjs.Sound.registerPlugins().
* @param {Array} filetypeOrder The order in which file types are preferred, where "ogg" becomes a ".ogg"
* extension on all sound file urls.
* @param {Function} completeCallback A function to call when initialization is complete.
*/
Sound.init = function(pluginOrder, filetypeOrder, completeCallback)
{
createjs.Sound.registerPlugins(pluginOrder);
//If on iOS, then we need to add a touch listener to unmute sounds.
//playback pretty much has to be createjs.WebAudioPlugin for iOS
if(createjs.Sound.BrowserDetect.isIOS)
{
document.addEventListener("touchstart", _playEmpty);
}
_instance = new Sound();
//make sure the capabilities are ready (looking at you, Cordova plugin)
if(createjs.Sound.getCapabilities())
{
_instance._initComplete(filetypeOrder, completeCallback);
}
else if(createjs.Sound.activePlugin)
{
if(DEBUG)
{
Debug.log("SoundJS Plugin " + createjs.Sound.activePlugin + " was not ready, waiting until it is");
}
//if the sound plugin is not ready, then just wait until it is
cloudkid.OS.instance.addUpdateCallback("SoundInit", function()
{
if(createjs.Sound.getCapabilities())
{
cloudkid.OS.instance.removeUpdateCallback("SoundInit");
_instance._initComplete(filetypeOrder, completeCallback);
}
});
}
else
Debug.error("Unable to initialize SoundJS with a plugin!");
return _instance;
};
function _playEmpty()
{
document.removeEventListener("touchstart", _playEmpty);
createjs.WebAudioPlugin.playEmptySound();
}
p._initComplete = function(filetypeOrder, callback)
{
if(createjs.FlashPlugin && createjs.Sound.activePlugin instanceof createjs.FlashPlugin)
_instance.supportedSound = ".mp3";
else
{
for(var i = 0; i < filetypeOrder.length; ++i)
{
var type = filetypeOrder[i];
if(createjs.Sound.getCapability(type))
{
_instance.supportedSound = "." + type;
break;
}
}
}
if(callback)
callback();
};
/**
* The singleton instance of Sound.
* @property {Sound} instance
* @public
* @static
*/
Object.defineProperty(Sound, "instance",
{
get: function() { return _instance; }
});
/**
* Loads a config object. This should not be called until after Sound.init() is complete.
* @method loadConfig
* @public
* @param {Object} config The config to load.
* @param {String} defaultContext The optional sound context to load sounds into unless
* otherwise specified. Sounds do not require a context.
*/
p.loadConfig = function(config, defaultContext)
{
if(!config)
{
Debug.warn("Warning - cloudkid.Sound was told to load a null config");
return;
}
var list = config.soundManifest;
var path = config.path;
defaultContext = defaultContext || config.context;
for(var i = 0, len = list.length; i < len; ++i)
{
var s = list[i];
var temp = this._sounds[s.id] = {
id: s.id,
src: path + s.src + this.supportedSound,
volume: s.volume ? s.volume : 1,
state: UNLOADED,
playing: [],
waitingToPlay: [],
context: s.context || defaultContext,
playAfterLoad: false,
preloadCallback: null,
data:s//save data for potential use by SoundJS plugins
};
if(temp.context)
{
if(!this._contexts[temp.context])
this._contexts[temp.context] = new SoundContext(temp.context);
this._contexts[temp.context].sounds.push(temp);
}
}
};
/**
* If a sound exists in the list of recognized sounds.
* @method exists
* @public
* @param {String} alias The alias of the sound to look for.
* @return {bool} true if the sound exists, false otherwise.
*/
p.exists = function(alias)
{
return !!this._sounds[alias];
};
/**
* If a sound is unloaded.
* @method isUnloaded
* @public
* @param {String} alias The alias of the sound to look for.
* @return {bool} true if the sound is unloaded, false if it is loaded, loading or does not exist.
*/
p.isUnloaded = function(alias)
{
return this._sounds[alias] ? this._sounds[alias].state == UNLOADED : false;
};
/**
* If a sound is loaded.
* @method isLoaded
* @public
* @param {String} alias The alias of the sound to look for.
* @return {bool} true if the sound is loaded, false if it is not loaded or does not exist.
*/
p.isLoaded = function(alias)
{
return this._sounds[alias] ? this._sounds[alias].state == LOADED : false;
};
/**
* If a sound is in the process of being loaded
* @method isLoading
* @public
* @param {String} alias The alias of the sound to look for.
* @return {bool} true if the sound is currently loading, false if it is loaded, unloaded, or does not exist.
*/
p.isLoading = function(alias)
{
return this._sounds[alias] ? this._sounds[alias].state == LOADING : false;
};
/**
* If a sound is playing.
* @method isPlaying
* @public
* @param {String} alias The alias of the sound to look for.
* @return {bool} true if the sound is currently playing or loading with an intent to play, false if it is not playing or does not exist.
*/
p.isPlaying = function(alias)
{
var sound = this._sounds[alias];
return sound ? sound.playing.length + sound.waitingToPlay.length > 0 : false;
};
/**
* Fades a sound from 0 to a specified volume.
* @method fadeIn
* @public
* @param {String|SoundInst} aliasOrInst The alias of the sound to fade the last played instance of, or an instance returned from play().
* @param {Number} duration The duration in milliseconds to fade for. The default is 500ms.
* @param {Number} targetVol The volume to fade to. The default is the sound's default volume.
* @param {Number} startVol The volume to start from. The default is 0.
*/
p.fadeIn = function(aliasOrInst, duration, targetVol, startVol)
{
var sound, inst;
if(typeof(aliasOrInst) == "string")
{
sound = this._sounds[aliasOrInst];
if(!sound) return;
if(sound.playing.length)
inst = sound.playing[sound.playing.length - 1];//fade the last played instance
}
else
{
inst = aliasOrInst;
sound = this._sounds[inst.alias];
}
if(!inst || !inst._channel) return;
inst._fTime = 0;
inst._fDur = duration > 0 ? duration : 500;
var v = startVol > 0 ? startVol : 0;
inst._channel.setVolume(v);
inst.curVol = inst._fStart = v;
inst._fEnd = targetVol || sound.volume;
if(this._fades.indexOf(inst) == -1)
{
this._fades.push(inst);
if(this._fades.length == 1)
OS.instance.addUpdateCallback(UPDATE_ALIAS, this._update);
}
};
/**
* Fades a sound from the current volume to a specified volume. A sound that ends at 0 volume
* is stopped after the fade.
* @method fadeOut
* @public
* @param {String|SoundInst} aliasOrInst The alias of the sound to fade the last played instance of, or an instance returned from play().
* @param {Number} duration The duration in milliseconds to fade for. The default is 500ms.
* @param {Number} targetVol The volume to fade to. The default is 0.
* @param {Number} startVol The volume to fade from. The default is the current volume.
*/
p.fadeOut = function(aliasOrInst, duration, targetVol, startVol)
{
var sound, inst;
if(typeof(aliasOrInst) == "string")
{
sound = this._sounds[aliasOrInst];
if(!sound) return;
if(sound.playing.length)
inst = sound.playing[sound.playing.length - 1];//fade the last played instance
}
else
{
inst = aliasOrInst;
//sound = this._sounds[inst.alias];
}
if(!inst || !inst._channel) return;
inst._fTime = 0;
inst._fDur = duration > 0 ? duration : 500;
if(startVol > 0)
{
inst._channel.setVolume(startVol);
inst._fStart = startVol;
}
else
inst._fStart = inst._channel.getVolume();
inst.curVol = inst._fStart;
inst._fEnd = targetVol || 0;
if(this._fades.indexOf(inst) == -1)
{
this._fades.push(inst);
if(this._fades.length == 1)
OS.instance.addUpdateCallback(UPDATE_ALIAS, this._update);
}
};
/**
* The update call, used for fading sounds. This is bound to the instance of Sound
* @method _update
* @private
* @param {int} elapsed The time elapsed since the previous frame, in milliseconds.
*/
p._update = function(elapsed)
{
var fades = this._fades;
var trim = 0;
for(var i = fades.length - 1; i >= 0; --i)
{
var inst = fades[i];
if(inst.paused) continue;
var time = inst._fTime += elapsed;
if(time >= inst._fDur)
{
if(inst._fEnd === 0)
{
var sound = this._sounds[inst.alias];
sound.playing = sound.playing.splice(sound.playing.indexOf(inst), 1);
this._stopInst(inst);
}
else
{
inst.curVol = inst._fEnd;
inst.updateVolume();
}
++trim;
var swapIndex = fades.length - trim;
if(i != swapIndex)//don't bother swapping if it is already last
{
fades[i] = fades[swapIndex];
}
}
else
{
var lerp = time / inst._fDur;
var vol;
if(inst._fEnd > inst._fStart)
vol = inst._fStart + (inst._fEnd - inst._fStart) * lerp;
else
vol = inst._fEnd + (inst._fStart - inst._fEnd) * lerp;
inst.curVol = vol;
inst.updateVolume();
}
}
fades.length = fades.length - trim;
if(fades.length === 0)
OS.instance.removeUpdateCallback(UPDATE_ALIAS);
};
/**
* Plays a sound.
* @method play
* @public
* @param {String} alias The alias of the sound to play.
* @param {function} completeCallback An optional function to call when the sound is finished.
Passing cloudkid.Sound.UNHANDLED results in cloudkid.Sound not handling the sound
and merely returning what SoundJS returns from its play() call.
* @param {function} startCallback An optional function to call when the sound starts playback.
If the sound is loaded, this is called immediately, if not, it calls when the
sound is finished loading.
* @param {bool} interrupt If the sound should interrupt previous sounds (SoundJS parameter). Default is false.
* @param {Number} delay The delay to play the sound at in milliseconds(SoundJS parameter). Default is 0.
* @param {Number} offset The offset into the sound to play in milliseconds(SoundJS parameter). Default is 0.
* @param {int} loop How many times the sound should loop. Use -1 (or true) for infinite loops (SoundJS parameter).
Default is no looping.
* @param {Number} volume The volume to play the sound at (0 to 1). Omit to use the default for the sound.
* @param {Number} pan The panning to start the sound at (-1 to 1). Default is centered (0).
* @return {SoundInst} An internal SoundInst object that can be used for fading in/out as well as
pausing and getting the sound's current position.
*/
p.play = function (alias, completeCallback, startCallback, interrupt, delay, offset, loop, volume, pan)
{
if(loop === true)//Replace with correct infinite looping.
loop = -1;
//UNHANDLED is really for legacy code, like the StateManager and Cutscene libraries that are using the sound instance directly to synch animations
if(completeCallback == Sound.UNHANDLED)//let calling code manage the SoundInstance - this is only allowed if the sound is already loaded
{
return createjs.Sound.play(alias, interrupt, delay, offset, loop, volume, pan);
}
var sound = this._sounds[alias];
if(!sound)
{
Debug.error("cloudkid.Sound: sound " + alias + " not found!");
if(completeCallback)
completeCallback();
return;
}
var state = sound.state;
var inst, arr;
volume = (typeof(volume) == "number" && volume > 0) ? volume : sound.volume;
if(state == LOADED)
{
var channel = createjs.Sound.play(alias, interrupt, delay, offset, loop, volume, pan);
//have Sound manage the playback of the sound
if(!channel || channel.playState == createjs.Sound.PLAY_FAILED)
{
if(completeCallback)
completeCallback();
return null;
}
else
{
inst = this._getSoundInst(channel, sound.id);
if(channel.handleExtraData)
channel.handleExtraData(sound.data);
inst.curVol = volume;
sound.playing.push(inst);
inst._endCallback = completeCallback;
inst.updateVolume();
inst.length = channel.getDuration();
inst._channel.addEventListener("complete", inst._endFunc);
if(startCallback)
setTimeout(startCallback, 0);
return inst;
}
}
else if(state == UNLOADED)
{
sound.state = LOADING;
sound.playAfterLoad = true;
inst = this._getSoundInst(null, sound.id);
inst.curVol = volume;
sound.waitingToPlay.push(inst);
inst._endCallback = completeCallback;
inst._startFunc = startCallback;
if(inst._startParams)
{
arr = inst._startParams;
arr[0] = interrupt;
arr[1] = delay;
arr[2] = offset;
arr[3] = loop;
arr[4] = pan;
}
else
inst._startParams = [interrupt, delay, offset, loop, pan];
MediaLoader.instance.load(
sound.src, //url to load
this._playAfterLoadBound,//complete callback
null,//progress callback
0,//priority
sound//the sound object (contains properties for PreloadJS/SoundJS)
);
return inst;
}
else if(state == LOADING)
{
//tell the sound to play after loading
sound.playAfterLoad = true;
inst = this._getSoundInst(null, sound.id);
inst.curVol = volume;
sound.waitingToPlay.push(inst);
inst._endCallback = completeCallback;
inst._startFunc = startCallback;
if(inst._startParams)
{
arr = inst._startParams;
arr[0] = interrupt;
arr[1] = delay;
arr[2] = offset;
arr[3] = loop;
arr[4] = pan;
}
else
inst._startParams = [interrupt, delay, offset, loop, pan];
return inst;
}
};
/**
* Gets a SoundInst, from the pool if available or maks a new one if not.
* @method _getSoundInst
* @private
* @param {createjs.SoundInstance} channel A createjs SoundInstance to initialize the object with.
* @param {String} id The alias of the sound that is going to be used.
* @return {SoundInst} The SoundInst that is ready to use.
*/
p._getSoundInst = function(channel, id)
{
var rtn;
if(this._pool.length)
rtn = this._pool.pop();
else
{
rtn = new SoundInst();
rtn._endFunc = this._onSoundComplete.bind(this, rtn);
}
rtn._channel = channel;
rtn.alias = id;
rtn.length = channel ? channel.getDuration() : 0;//set or reset this
rtn.isValid = true;
return rtn;
};
/**
* Plays a sound after it finishes loading.
* @method _playAfterload
* @private
* @param {String} alias The sound to play.
*/
p._playAfterLoad = function(result)
{
var alias = typeof result == "string" ? result : result.id;
var sound = this._sounds[alias];
sound.state = LOADED;
//If the sound was stopped before it finished loading, then don't play anything
if(!sound.playAfterLoad) return;
//Go through the list of sound instances that are waiting to start and start them
var waiting = sound.waitingToPlay;
for(var i = 0; i < waiting.length; ++i)
{
var inst = waiting[i];
var startParams = inst._startParams;
var volume = inst.curVol;
var channel = createjs.Sound.play(alias, startParams[0], startParams[1], startParams[2], startParams[3], volume, startParams[4]);
if(!channel || channel.playState == createjs.Sound.PLAY_FAILED)
{
if(inst._endCallback)
inst._endCallback();
this._poolInst(inst);
}
else
{
sound.playing.push(inst);
inst._channel = channel;
if(channel.handleExtraData)
channel.handleExtraData(sound.data);
inst.length = channel.getDuration();
inst.updateVolume();
channel.addEventListener("complete", inst._endFunc);
if(inst._startFunc)
inst._startFunc();
if(inst.paused)//if the sound got paused while loading, then pause it
channel.pause();
}
}
waiting.length = 0;
};
/**
* The callback used for when a sound instance is complete.
* @method _onSoundComplete
* @private
* @param {SoundInst} inst The SoundInst that is complete.s
*/
p._onSoundComplete = function(inst)
{
inst._channel.removeEventListener("complete", inst._endFunc);
var sound = this._sounds[inst.alias];
sound.playing.splice(sound.playing.indexOf(inst), 1);
var callback = inst._endCallback;
this._poolInst(inst);
if(callback)
callback();
};
/**
* Stops all playing or loading instances of a given sound.
* @method stop
* @public
* @param {String} alias The alias of the sound to stop.
*/
p.stop = function(alias)
{
var s = this._sounds[alias];
if(!s) return;
if(s.playing.length)
this._stopSound(s);
else if(s.state == LOADING)
{
s.playAfterLoad = false;
var waiting = s.waitingToPlay;
for(var i = 0; i < waiting.length; ++i)
{
var inst = waiting[i];
/*if(inst._endCallback)
inst._endCallback();*/
this._poolInst(inst);
}
waiting.length = 0;
}
};
/**
* Stops all playing SoundInsts for a sound.
* @method _stopSound
* @private
* @param {Object} s The sound (from the _sounds dictionary) to stop.
*/
p._stopSound = function(s)
{
var arr = s.playing;
for(var i = arr.length -1; i >= 0; --i)
{
this._stopInst(arr[i]);
}
arr.length = 0;
};
/**
* Stops and repools a specific SoundInst.
* @method _stopInst
* @private
* @param {SoundInst} inst The SoundInst to stop.
*/
p._stopInst = function(inst)
{
inst._channel.removeEventListener("complete", inst._endFunc);
inst._channel.stop();
this._poolInst(inst);
};
/**
* Stops all sounds in a given context.
* @method stopContext
* @public
* @param {String} context The name of the context to stop.
*/
p.stopContext = function(context)
{
context = this._contexts[context];
if(context)
{
var arr = context.sounds;
for(var i = arr.length - 1; i >= 0; --i)
{
var s = arr[i];
if(s.playing.length)
this._stopSound(s);
else if(s.state == LOADING)
s.playAfterLoad = false;
}
}
};
/**
* Pauses a specific sound.
* @method pauseSound
* @public
* @param {String} alias The alias of the sound to pause.
* Internally, this can also be the object from the _sounds dictionary directly.
*/
p.pauseSound = function(alias)
{
var sound;
if(typeof alias == "string")
sound = this._sounds[alias];
else
sound = alias;
var arr = sound.playing;
for(var i = arr.length - 1; i >= 0; --i)
arr[i].pause();
};
/**
* Unpauses a specific sound.
* @method unpauseSound
* @public
* @param {String} alias The alias of the sound to pause.
* Internally, this can also be the object from the _sounds dictionary directly.
*/
p.unpauseSound = function(alias)
{
var sound;
if(typeof alias == "string")
sound = this._sounds[alias];
else
sound = alias;
var arr = sound.playing;
for(var i = arr.length - 1; i >= 0; --i)
{
arr[i].unpause();
}
};
/**
* Pauses all sounds.
* @method pauseAll
* @public
*/
p.pauseAll = function()
{
var arr = this._sounds;
for(var i in arr)
this.pauseSound(arr[i]);
};
/**
* Unpauses all sounds.
* @method unpauseAll
* @public
*/
p.unpauseAll = function()
{
var arr = this._sounds;
for(var i in arr)
this.unpauseSound(arr[i]);
};
/**
* Sets mute status of all sounds in a context
* @method setContextMute
* @public
* @param {String} context The name of the context to modify.
* @param {bool} muted If the context should be muted.
*/
p.setContextMute = function(context, muted)
{
context = this._contexts[context];
if(context)
{
context.muted = muted;
var volume = context.volume;
var arr = context.sounds;
for(var i = arr.length - 1; i >= 0; --i)
{
var s = arr[i];
if(s.playing.length)
{
var playing = s.playing;
for(var j = playing.length - 1; j >= 0; --j)
{
playing[j].updateVolume(muted ? 0 : volume);
}
}
}
}
};
/**
* Sets volume of a context. Individual sound volumes are multiplied by this value.
* @method setContextVolume
* @public
* @param {String} context The name of the context to modify.
* @param {Number} volume The volume for the context (0 to 1).
*/
p.setContextVolume = function(context, volume)
{
context = this._contexts[context];
if(context)
{
var muted = context.muted;
context.volume = volume;
var arr = context.sounds;
for(var i = arr.length - 1; i >= 0; --i)
{
var s = arr[i];
if(s.playing.length)
{
var playing = s.playing;
for(var j = playing.length - 1; j >= 0; --j)
{
playing[j].updateVolume(muted ? 0 : volume);
}
}
}
}
};
/**
* Preloads a specific sound.
* @method preloadSound
* @public
* @param {String} alias The alias of the sound to load.
* @param {function} callback The function to call when the sound is finished loading.
*/
p.preloadSound = function(alias, callback)
{
var sound = this._sounds[alias];
if(!sound)
{
Debug.error("Sound does not exist: " + alias + " - can't preload!");
return;
}
if(sound.state != UNLOADED) return;
sound.state = LOADING;
sound.preloadCallback = callback || null;
MediaLoader.instance.load(
sound.src, //url to load
this._markLoaded,//complete callback
null,//progress callback
0,//priority
sound//the sound object (contains properties for PreloadJS/SoundJS)
);
};
/**
* Preloads a list of sounds.
* @method preload
* @public
* @param {Array} list An array of sound aliases to load.
* @param {function} callback The function to call when all sounds have been loaded.
*/
p.preload = function(list, callback)
{
if(!list || list.length === 0)
{
if(callback)
callback();
return;
}
var tasks = [];
for(var i = 0, len = list.length; i < len; ++i)
{
var sound = this._sounds[list[i]];
if(sound)
{
if(sound.state == UNLOADED)
{
sound.state = LOADING;
//sound is passed last so that SoundJS gets the sound ID
tasks.push(new LoadTask(sound.id, sound.src, this._markLoaded, null, 0, sound));
}
}
else
{
Debug.error("cloudkid.Sound was asked to preload " + list[i] + " but it is not a registered sound!");
}
}
if(tasks.length > 0)
{
TaskManager.process(tasks, function()
{
if(callback)
callback();
});
}
else if(callback)
{
callback();
}
};
/**
* Marks a sound as loaded. If it needs to play after the load, then it is played.
* @method _markLoaded
* @private
* @param {String} alias The alias of the sound to mark.
* @param {function} callback A function to call to show that the sound is loaded.
*/
p._markLoaded = function(result)
{
var alias = result.id;
var sound = this._sounds[alias];
if(sound)
{
sound.state = LOADED;
if(sound.playAfterLoad)
this._playAfterLoad(alias);
}
var callback = sound.preloadCallback;
if(callback)
{
sound.preloadCallback = null;
callback();
}
};
/**
* Creates a Task for the CloudKid Task library for preloading a list of sounds.
* This function will not work if the Task library was not loaded before the Sound library.
* @method createPreloadTask
* @public
* @param {String} id The id of the task.
* @param {Array} list An array of sound aliases to load.
* @param {function} callback The function to call when the task is complete.
* @return {cloudkid.Task} A task to load up all of the sounds in the list.
*/
p.createPreloadTask = function(id, list, callback)
{
if(!SoundListTask) return null;
return new SoundListTask(id, list, callback);
};
/**
* Unloads a list of sounds to reclaim memory if possible.
* If the sounds are playing, they are stopped.
* @method unload
* @public
* @param {Array} list An array of sound aliases to unload.
*/
p.unload = function(list)
{
if(!list) return;
for(var i = 0, len = list.length; i < len; ++i)
{
var sound = this._sounds[list[i]];
if(sound)
{
this._stopSound(sound);
sound.state = UNLOADED;
}
createjs.Sound.removeSound(list[i]);
}
};
/**
* Places a SoundInst back in the pool for reuse.
* @method _poolinst
* @private
* @param {SoundInst} inst The instance to repool.
*/
p._poolInst = function(inst)
{
inst._endCallback = null;
inst.alias = null;
inst._channel = null;
inst._startFunc = null;
inst.curVol = 0;
inst.paused = false;
inst.isValid = false;
this._pool.push(inst);
};
/**
* Destroys cloudkid.Sound. This does not unload loaded sounds, destroy SoundJS to do that.
* @method destroy
* @public
*/
p.destroy = function()
{
_instance = null;
this._volumes = null;
this._fades = null;
this._contexts = null;
this._pool = null;
};
/**
* A playing instance of a sound (or promise to play as soon as it loads). These can only
* be created through cloudkid.Sound.instance.play().
* @class SoundInst
*/
var SoundInst = function()
{
/**
* SoundJS SoundInstance, essentially a sound channel.
* @property {createjs.SoundInstance} _channel
* @private
*/
this._channel = null;
/**
* Internal callback function for when the sound ends.
* @property {function} _endFunc
* @private
*/
this._endFunc = null;
/**
* User's callback function for when the sound ends.
* @property {function} _endCallback
* @private
*/
this._endCallback = null;
/**
* User's callback function for when the sound starts. This is only used if the sound wasn't loaded before play() was called.
* @property {function} _startFunc
* @private
*/
this._startFunc = null;
/**
* An array of relevant parameters passed to play(). This is only used if the sound wasn't loaded before play() was called.
* @property {Array} _startParams
* @private
*/
this._startParams = null;
/**
* The alias for the sound that this instance was created from.
* @property {String} alias
* @public
* @readOnly
*/
this.alias = null;
/**
* The current time in milliseconds for the fade that this sound instance is performing.
* @property {Number} _fTime
* @private
*/
this._fTime = 0;
/**
* The duration in milliseconds for the fade that this sound instance is performing.
* @property {Number} _fDur
* @private
*/
this._fDur = 0;
/**
* The starting volume for the fade that this sound instance is performing.
* @property {Number} _fEnd
* @private
*/
this._fStart = 0;
/**
* The ending volume for the fade that this sound instance is performing.
* @property {Number} _fEnd
* @private
*/
this._fEnd = 0;
/**
* The current sound volume (0 to 1). This is multiplied by the sound context's volume.
* Setting this won't take effect until updateVolume() is called.
* @property {Number} curVol
* @public
*/
this.curVol = 0;
/**
* The length of the sound in milliseconds. This is 0 if it hasn't finished loading.
* @property {Number} length
* @public
*/
this.length = 0;
/**
* If the sound is currently paused. Setting this has no effect - use pause() and unpause().
* @property {bool} paused
* @public
* @readOnly
*/
this.paused = false;
/**
* An active SoundInst should always be valid. This is primarily for compatability with cloudkid.Audio.
* @property {bool} isValid
* @public
* @readOnly
*/
this.isValid = true;
};
/**
* The position of the sound playhead in milliseconds, or 0 if it hasn't started playing yet.
* @property {Number} position
* @public
*/
Object.defineProperty(SoundInst.prototype, "position",
{
get: function(){ return this._channel ? this._channel.getPosition() : 0;}
});
/**
* Stops this SoundInst.
* @method stop
* @public
*/
SoundInst.prototype.stop = function()
{
var s = Sound.instance;
var sound = s._sounds[this.alias];
sound.playing.splice(sound.playing.indexOf(this), 1);
Sound.instance._stopInst(this);
};
/**
* Updates the volume of this SoundInst.
* @method updateVolume
* @public
* @param {Number} contextVol The volume of the sound context that the sound belongs to. If omitted, the volume is automatically collected.
*/
SoundInst.prototype.updateVolume = function(contextVol)
{
if(!this._channel) return;
if(contextVol === undefined)
{
var s = Sound.instance;
var sound = s._sounds[this.alias];
if(sound.context)
{
var context = s._contexts[sound.context];
contextVol = context.muted ? 0 : context.volume;
}
else
contextVol = 1;
}
this._channel.setVolume(contextVol * this.curVol);
};
/**
* Pauses this SoundInst.
* @method pause
* @public
*/
SoundInst.prototype.pause = function()
{
if(this.paused) return;
this.paused = true;
if(!this._channel) return;
this._channel.pause();
};
/**
* Unpauses this SoundInst.
* @method unpause
* @public
*/
SoundInst.prototype.unpause = function()
{
if(!this.paused) return;
this.paused = false;
if(!this._channel) return;
this._channel.resume();
};
//As use of the Task library isn't required, creating this class is optional
var SoundListTask;
if(Task)
{
/**
* A task for loading a list of sounds. These can only
* be created through Sound.instance.createPreloadTask().
* This class is not created if the Task library is not loaded before the Sound library.
* @class SoundListTask
* @extends {cloudkid.Task}
*/
SoundListTask = function(id, list, callback)
{
this.initialize(id, callback);
this.list = list;
};
SoundListTask.prototype = Object.create(Task.prototype);
SoundListTask.s = Task.prototype;
SoundListTask.prototype.start = function(callback)
{
_instance.preload(this.list, callback);
};
SoundListTask.prototype.destroy = function()
{
SoundListTask.s.destroy.apply(this);
this.list = null;
};
}
/**
* A private class that represents a sound context.
* @class SoundContext
* @constructor
* @param {String} id The name of the sound context.
*/
var SoundContext = function(id)
{
/**
* The name of the sound context.
* @property {String} id
* @public
*/
this.id = id;
/**
* The current volume to apply to all sounds in the context (0 to 1).
* @property {Number} volume
* @public
*/
this.volume = 1;
/**
* If all sounds in the sound context are muted or not.
* @property {bool} muted
* @public
*/
this.muted = false;
/**
* The sound objects in this context, from Sound.instance._sounds;
* @property {Array} sounds
* @public
*/
this.sounds = [];
};
SoundContext.prototype = {};
namespace('cloudkid').Sound = Sound;
}());