/* * AbstractPlugin * Visit http://createjs.com/ for documentation, updates and examples. * * * Copyright (c) 2012 gskinner.com, inc. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ // namespace: this.createjs = this.createjs || {}; (function () { "use strict"; // constructor: /** * A default plugin class used as a base for all other plugins. * @class AbstractPlugin * @constructor * @since 0.6.0 */ var AbstractPlugin = function () { // private properties: /** * The capabilities of the plugin. * method and is used internally. * @property _capabilities * @type {Object} * @default null * @protected * @static */ this._capabilities = null; /** * Object hash indexed by the source URI of all created loaders, used to properly destroy them if sources are removed. * @type {Object} * @protected */ this._loaders = {}; /** * Object hash indexed by the source URI of each file to indicate if an audio source has begun loading, * is currently loading, or has completed loading. Can be used to store non boolean data after loading * is complete (for example arrayBuffers for web audio). * @property _audioSources * @type {Object} * @protected */ this._audioSources = {}; /** * Object hash indexed by the source URI of all created SoundInstances, updates the playbackResource if it loads after they are created, * and properly destroy them if sources are removed * @type {Object} * @protected */ this._soundInstances = {}; /** * The internal master volume value of the plugin. * @property _volume * @type {Number} * @default 1 * @protected */ this._volume = 1; /** * A reference to a loader class used by a plugin that must be set. * @type {Object} * @protected */ this._loaderClass; /** * A reference to an AbstractSoundInstance class used by a plugin that must be set. * @type {Object} * @protected; */ this._soundInstanceClass; }; var p = AbstractPlugin.prototype; // Static Properties: // NOTE THESE PROPERTIES NEED TO BE ADDED TO EACH PLUGIN /** * The capabilities of the plugin. This is generated via the _generateCapabilities method and is used internally. * @property _capabilities * @type {Object} * @default null * @private * @static */ AbstractPlugin._capabilities = null; /** * Determine if the plugin can be used in the current browser/OS. * @method isSupported * @return {Boolean} If the plugin can be initialized. * @static */ AbstractPlugin.isSupported = function () { return true; }; // public methods: /** * Pre-register a sound for preloading and setup. This is called by {{#crossLink "Sound"}}{{/crossLink}}. * Note all plugins provide a <code>Loader</code> instance, which <a href="http://preloadjs.com" target="_blank">PreloadJS</a> * can use to assist with preloading. * @method register * @param {String} loadItem An Object containing the source of the audio * Note that not every plugin will manage this value. * @return {Object} A result object, containing a "tag" for preloading purposes. */ p.register = function (loadItem) { var loader = this._loaders[loadItem.src]; if(loader && !loader.canceled) {return this._loaders[loadItem.src];} // already loading/loaded this, so don't load twice // OJR potential issue that we won't be firing loaded event, might need to trigger if this is already loaded? this._audioSources[loadItem.src] = true; this._soundInstances[loadItem.src] = []; loader = new this._loaderClass(loadItem); loader.on("complete", this._handlePreloadComplete, this); this._loaders[loadItem.src] = loader; return loader; }; // note sound calls register before calling preload /** * Internally preload a sound. * @method preload * @param {Loader} loader The sound URI to load. */ p.preload = function (loader) { loader.on("error", this._handlePreloadError, this); loader.load(); }; /** * Checks if preloading has started for a specific source. If the source is found, we can assume it is loading, * or has already finished loading. * @method isPreloadStarted * @param {String} src The sound URI to check. * @return {Boolean} */ p.isPreloadStarted = function (src) { return (this._audioSources[src] != null); }; /** * Checks if preloading has finished for a specific source. * @method isPreloadComplete * @param {String} src The sound URI to load. * @return {Boolean} */ p.isPreloadComplete = function (src) { return (!(this._audioSources[src] == null || this._audioSources[src] == true)); }; /** * Remove a sound added using {{#crossLink "WebAudioPlugin/register"}}{{/crossLink}}. Note this does not cancel a preload. * @method removeSound * @param {String} src The sound URI to unload. */ p.removeSound = function (src) { if (!this._soundInstances[src]) { return; } for (var i = this._soundInstances[src].length; i--; ) { var item = this._soundInstances[src][i]; item.destroy(); } delete(this._soundInstances[src]); delete(this._audioSources[src]); if(this._loaders[src]) { this._loaders[src].destroy(); } delete(this._loaders[src]); }; /** * Remove all sounds added using {{#crossLink "WebAudioPlugin/register"}}{{/crossLink}}. Note this does not cancel a preload. * @method removeAllSounds * @param {String} src The sound URI to unload. */ p.removeAllSounds = function () { for(var key in this._audioSources) { this.removeSound(key); } }; /** * Create a sound instance. If the sound has not been preloaded, it is internally preloaded here. * @method create * @param {String} src The sound source to use. * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. * @return {AbstractSoundInstance} A sound instance for playback and control. */ p.create = function (src, startTime, duration) { if (!this.isPreloadStarted(src)) { this.preload(this.register(src)); } var si = new this._soundInstanceClass(src, startTime, duration, this._audioSources[src]); if(this._soundInstances[src]){ this._soundInstances[src].push(si); } // Plugins that don't have a setVolume should implement a setMasterVolume/setMasterMute // So we have to check that here. si.setMasterVolume && si.setMasterVolume(createjs.Sound.volume); si.setMasterMute && si.setMasterMute(createjs.Sound.muted); return si; }; // if a plugin does not support volume and mute, it should set these to null /** * Set the master volume of the plugin, which affects all SoundInstances. * @method setVolume * @param {Number} value The volume to set, between 0 and 1. * @return {Boolean} If the plugin processes the setVolume call (true). The Sound class will affect all the * instances manually otherwise. */ p.setVolume = function (value) { this._volume = value; this._updateVolume(); return true; }; /** * Get the master volume of the plugin, which affects all SoundInstances. * @method getVolume * @return {Number} The volume level, between 0 and 1. */ p.getVolume = function () { return this._volume; }; /** * Mute all sounds via the plugin. * @method setMute * @param {Boolean} value If all sound should be muted or not. Note that plugin-level muting just looks up * the mute value of Sound {{#crossLink "Sound/muted:property"}}{{/crossLink}}, so this property is not used here. * @return {Boolean} If the mute call succeeds. */ p.setMute = function (value) { this._updateVolume(); return true; }; // plugins should overwrite this method p.toString = function () { return "[AbstractPlugin]"; }; // private methods: /** * Handles internal preload completion. * @method _handlePreloadComplete * @param event * @protected */ p._handlePreloadComplete = function (event) { var src = event.target.getItem().src, result = event.result, instances = this._soundInstances[src]; this._audioSources[src] = result; if (instances != null && instances.length > 0) { for (var i=0, l=instances.length; i<l; i++) { instances[i].playbackResource = result; // TODO consider adding play call here if playstate == playfailed // LM: SoundJS policy is not to play sounds late that previously failed, as it could play unexpectedly. } } this._soundInstances[src] = null; }; /** * Handles internal preload errors * @method _handlePreloadError * @param event * @protected */ p._handlePreloadError = function(event) { //delete(this._audioSources[src]); //LM: Not sure why this was commented out. }; /** * Set the gain value for master audio. Should not be called externally. * @method _updateVolume * @protected */ p._updateVolume = function () { // Plugin Specific code }; createjs.AbstractPlugin = AbstractPlugin; }());