UNPKG

crossbrowdy

Version:

A Multimedia JavaScript framework to create real cross-platform and hybrid game engines, games, emulators, multimedia libraries and apps.

787 lines (682 loc) 174 kB
/** * @file Audio files cache management. Contains the {@link CB_AudioFileCache} class. * @author Joan Alba Maldonado <workindalian@gmail.com> * @license Creative Commons Attribution 4.0 International. See more at {@link https://crossbrowdy.com/about#what_is_the_crossbrowdy_copyright_and_license}. */ /** * Object whose property names are audio formats (they can include just the format as 'audio/ogg' or also the codec as for example 'audio/ogg; codecs="vorbis"') and their value is an array of strings with the URIs (audio file paths or audio data URIs) of the audio files in order of preference. The best audio format for the current client will be tried to be calculated and it will use the first working URI (audio file path or data URI). The more audio formats and URIs provided the better, as it will help to maximize the compatibility with as many clients as possible (as some audio APIs and client just support some formats, or use absolute paths instead of relative ones, etc.). Even with different formats, all provided URIs should belong to the same audio (this means same sound or same music, with same length, etc.). NOTE: Only some clients with some audio APIs will support data URIs. * @example * { * "audio/mp4" : [ "first/path/sound.m4a", "alternative/path/sound.m4a", "alternative/path/2/sound.mp4", ... ], * "audio/ogg" : [ "first/path/sound.opus", "alternative/path/sound.ogg", "alternative/path/2/sound.ogg", ... ], * "audio/mpeg" : [ "first/path/sound.mp3", "alternative/path/sound.mp3", "alternative/path/2/sound.mp3", ... ], * "audio/wav" : [ "first/path/sound.wav", "alternative/path/sound.wav", "alternative/path/2/sound.wav", ... ], * ... * } * @memberof CB_AudioFileCache * @typedef {Object} CB_AudioFileCache.URIS_OBJECT * @property {array} filePaths - Being the name of each property the audio format (it can include just the format as 'audio/ogg' or also the codec as for example 'audio/ogg; codecs="vorbis"'), the value will always be a numeric array of strings with the URIs (audio file paths or audio data URIs) of the audio files in order of preference. The best audio format for the current client will be tried to be calculated and it will use the first working URI (audio file path or data URI). The more audio formats and URIs provided the better, as it will help to maximize the compatibility with as many clients as possible (as some audio APIs and client just support some formats, or use absolute paths instead of relative ones, etc.). Even with different formats, all provided URIs should belong to the same audio (this means same sound or same music, with same length, etc.). NOTE: Only some clients with some audio APIs will support data URIs. */ /** * Object with the desired data and options for the audio files cache. * @memberof CB_AudioFileCache * @typedef {Object} CB_AudioFileCache.DATA_OBJECT * @property {CB_AudioFileCache.URIS_OBJECT} URIs - Object whose property names audio formats and their value is an array of strings with the URIs (audio file paths or audio data URIs) of the audio files in order of preference. The best audio format for the current client will be tried to be calculated and it will use the first working URI (audio file path or data URI). The more audio formats and URIs provided the better, as it will help to maximize the compatibility with as many clients as possible (as some audio APIs and client just support some formats, or use absolute paths instead of relative ones, etc.). Even with different formats, all provided URIs should belong to the same audio (this means same sound or same music, with same length, etc.). NOTE: Only some clients with some audio APIs will support data URIs. If a valid value is given, this will be added to the {@link CB_AudioFileCache#URIs} property. * @property {string} [id=""] - Desired identifier for the audio files cache. Internal usage only recommended. If a valid value is given, this will be added to the {@link CB_AudioFileCache#id} property. * @property {array} [preferredAPIs={@link CB_Configuration.CrossBase.CB_AudioFileCache_PREFERRED_AUDIO_APIS}] - Array of strings with the preferred audio API or audio APIs, in order of preference. Possible audio APIs are "WAAPI" ([HTML5 Web Audio API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API}), "SM2" ([SoundManager 2]{@link http://schillmania.com/projects/soundmanager2/}), "ACMP" ([Apache Cordova Media Plugin]{@link https://github.com/apache/cordova-plugin-media}) or "AAPI" ([HTML5 Audio API]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio}). It will try to calculate and use the best one for the current client. If a valid value is given, this will be added to the {@link CB_AudioFileCache#preferredAPIs} property. * @property {array} [preferredFormats={@link CB_Configuration.CrossBase.CB_AudioFileCache_PREFERRED_AUDIO_FORMATS}] - Array of strings with the preferred audio format or audio formats (they can include just the format as 'audio/ogg' or also the codec as for example 'audio/ogg; codecs="vorbis"'), in order of preference. It will try to calculate and use the best one for the current client. If a valid value is given, this will be added to the {@link CB_AudioFileCache#preferredFormats} property. * @property {integer} [minimumAudioFiles={@link CB_AudioFileCache.minimumAudioFiles_DEFAULT}] - Minimum {@link CB_AudioFile} objects to create internally. It must be an integer being 1 the minimum. If a valid value is given, this will be added to the {@link CB_AudioFileCache#minimumAudioFiles} property. * @property {integer|null} [maximumAudioFiles={@link CB_AudioFileCache.maximumAudioFiles_DEFAULT}] - Maximum {@link CB_AudioFile} objects that are allowed to be created internally. If it is set to null, there will not be a maximum (it will be unlimited). If an integer is provided, it must be the same number or greater than the value set in the {@link CB_AudioFileCache#minimumAudioFiles} property (also provided by the "minimumAudioFiles" of this object), allowing 1 minimum. If a valid value is given, this will be added to the {@link CB_AudioFileCache#maximumAudioFiles} property. * @property {integer} [minimumAudioFilesFree=parseInt({@link CB_AudioFileCache#minimumAudioFiles} * 0.25 + 0.5)] - New {@link CB_AudioFile} objects will be created internally when the number of free {@link CB_AudioFile} objects reaches this limit. If provided, it must be an integer being 0 (zero) the minimum. It will end using a 25% of the {@link CB_AudioFileCache#minimumAudioFiles} by default, rounded to ceil, allowing 0 (zero) minimum. If a valid value is given, this will be added to the {@link CB_AudioFileCache#minimumAudioFilesFree} property. * @property {integer} [newAudioFilesWhenNeeded=Math.min(parseInt({@link CB_AudioFileCache#minimumAudioFiles} * 0.1 + 0.5), 1)] - Number of new {@link CB_AudioFile} objects to create internally when the minimum limit of free {@link CB_AudioFile} objects ({@link CB_AudioFileCache#minimumAudioFilesFree}) is reached. If provided, it must be an integer being 0 (zero) the minimum. It will end using a 10% of the {@link CB_AudioFileCache#minimumAudioFiles} by default, rounded to ceil, allowing 1 minimum. If a valid value is given, this will be added to the {@link CB_AudioFileCache#newAudioFilesWhenNeeded} property. * @property {integer} [retries={@link CB_AudioFileCache.retries_DEFAULT}] - Number of retries to try to load a {@link CB_AudioFile} object internally before trying to load the next possible one (if any). It must be an integer being 0 the minimum. If a valid value is given, this will be added to the {@link CB_AudioFileCache#retries} property. * @property {boolean} [checkManually={@link CB_AudioFileCache.checkManually_DEFAULT}] - Tells whether the {@link CB_AudioFile} objects must be checked automatically or not (manually) by default. If a valid value is given, this will be added to the {@link CB_AudioFileCache#checkManually} property. * @property {boolean} [checkManuallyOnNeededCreated={@link CB_AudioFileCache.checkManuallyOnNeededCreated_DEFAULT}] - Tells whether the {@link CB_AudioFile} objects must be checked automatically or not (manually) when creates a new {@link CB_AudioFile} object needed. If a valid value is given, this will be added to the {@link CB_AudioFileCache#checkManuallyOnNeededCreated} property. * @property {boolean} [checkManuallyOnPlayingFailed={@link CB_AudioFileCache.checkManuallyOnPlayingFailed_DEFAULT}] - Tells whether the {@link CB_AudioFile} objects must be checked automatically or not (manually) when playing one has failed and tries to reload it. If a valid value is given, this will be added to the {@link CB_AudioFileCache#checkManuallyOnPlayingFailed} property. * @property {boolean} [checkManuallyOnCheckingFailed={@link CB_AudioFileCache.checkManuallyOnCheckingFailed_DEFAULT}] - Tells whether the {@link CB_AudioFile} objects must be checked automatically or not (manually) when checking one has failed and tries to reload it. If a valid value is given, this will be added to the {@link CB_AudioFileCache#checkManuallyOnCheckingFailed} property. * @property {function} [onLoad] - Desired function to be called once the cache has been loaded. The first and unique parameter will be an integer with the {@link CB_AudioFile} objects that still need to be checked, if any, being "this" the current {@link CB_AudioFileCache} object. If a valid value is given, this will be added to the {@link CB_AudioFileCache#onLoad} property. * @property {function} [onError] - Desired function to be called when any kind of error happens. The first and unique parameter will be a string with the error description (if it could be determined), being "this" the current {@link CB_AudioFileCache} object. If a valid value is given, this will be added to the {@link CB_AudioFileCache#onError} property. * @property {boolean} [disableAutoLoad=false] - If set to true, it will not create automatically the {@link CB_AudioFile} objects by calling the {@link CB_AudioFileCache#createAudioFiles} method internally. Internal usage only recommended. */ /** * The constructor is recommended to be called through a user-driven event (as onClick, onTouch, etc.), as some clients may need this at least the first time in order to be able to play the audio. * @class * @classdesc Class to manage a cache with multiple {@link CB_AudioFile} objects (they should be the same sound although they can be in different formats). This is not only useful for performance purposes but also for being able to play the same sound simultaneously and multiple times in different audio APIs and clients. * @param {CB_AudioFileCache.DATA_OBJECT} [dataObject] - Object with the desired data and options for the audio files cache. * @returns {CB_AudioFileCache} Returns a new {@link CB_AudioFileCache} object. * @todo Do not allow to create one object with an "id" which has already been used (unless the value is undefined, null...). * @todo Method getCopy and static method filterProperties (similar to the ones from {@link CB_GraphicSprites} and {@link CB_GraphicSpritesScene}). */ var CB_AudioFileCache = function(dataObject) { //Creates an instance of this object and returns it in the case that it is being called from an unexpected context: if (this === window || !(this instanceof CB_AudioFileCache)) { return new CB_AudioFileCache(dataObject); } //Static properties and constants: /** * Keeps the default volume. If the {@link CB_Configuration.CrossBase.CB_AudioFile_AudioFileCache_USE_SPEAKER_VOLUME_AS_DEFAULT} property is true, this will keep the result of calling the {@link CB_Speaker.getVolume} function. Otherwise, it will use the value of the {@link CB_Configuration.CrossBase.CB_Speaker_DEFAULT_VOLUME} variable. * @constant * @type {number} * @default CB_Configuration.CrossBase.CB_AudioFile_AudioFileCache_USE_SPEAKER_VOLUME_AS_DEFAULT ? CB_Speaker.getVolume() : CB_Configuration.CrossBase.CB_Speaker_DEFAULT_VOLUME */ CB_AudioFileCache.prototype.DEFAULT_VOLUME = CB_Configuration[CB_BASE_NAME].CB_AudioFile_AudioFileCache_USE_SPEAKER_VOLUME_AS_DEFAULT ? CB_Speaker.getVolume() : CB_Configuration[CB_BASE_NAME].CB_Speaker_DEFAULT_VOLUME; //Properties and variables: /** * Tells whether the cache is unloaded ({@link CB_AudioFileCache.UNLOADED}), loading ({@link CB_AudioFileCache.LOADING}), unchecked ({@link CB_AudioFileCache.UNCHECKED}), checking ({@link CB_AudioFileCache.CHECKING}), loaded ({@link CB_AudioFileCache.LOADED}), failed ({@link CB_AudioFileCache.FAILED}) or aborted ({@link CB_AudioFileCache.ABORTED}). * @var CB_AudioFileCache#status * @readonly * @type {integer} * @default {@link CB_AudioFileCache.UNLOADED} */ this.status = CB_AudioFileCache.UNLOADED; /** * Stores the identifier for the audio files cache. * @var * @readonly * @type {string} * @default */ this.id = ""; /** * Stores an array of strings with the preferred audio API or audio APIs, in order of preference. Possible audio APIs are "WAAPI" ([HTML5 Web Audio API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API}), "SM2" ([SoundManager 2]{@link http://schillmania.com/projects/soundmanager2/}), "ACMP" ([Apache Cordova Media Plugin]{@link https://github.com/apache/cordova-plugin-media}) or "AAPI" ([HTML5 Audio API]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio}). Internal usage only recommended. * @var * @readonly * @type {array} * @default CB_Configuration.CrossBase.CB_AudioFileCache_PREFERRED_AUDIO_APIS */ this.preferredAPIs = CB_Configuration[CB_BASE_NAME].CB_AudioFileCache_PREFERRED_AUDIO_APIS; /** * Stores an array of strings with the preferred audio format or audio formats (they can include just the format as 'audio/ogg' or also the codec as for example 'audio/ogg; codecs="vorbis"'), in order of preference. Internal usage only recommended. * @var * @readonly * @type {array} * @default CB_Configuration.CrossBase.CB_AudioFileCache_PREFERRED_AUDIO_FORMATS */ this.preferredFormats = CB_Configuration[CB_BASE_NAME].CB_AudioFileCache_PREFERRED_AUDIO_FORMATS; /** * Object whose property names audio formats and their value is an array of strings with the URIs (audio file paths or audio data URIs) of the audio files in order of preference. The more audio formats and URIs provided the better, as it will help to maximize the compatibility with as many clients as possible (as some audio APIs and client just support some formats, or use absolute paths instead of relative ones, etc.). Even with different formats, all provided URIs should belong to the same audio (this means same sound or same music, with same length, etc.). NOTE: Only some clients with some audio APIs will support data URIs. Internal usage only recommended. * @var * @readonly * @type {CB_AudioFileCache.URIS_OBJECT} */ this.URIs = {}; /** * Minimum {@link CB_AudioFile} objects to create internally. It must be an integer being 1 the minimum. Internal usage only recommended. * @var * @readonly * @type {integer} * @default CB_AudioFileCache.minimumAudioFiles_DEFAULT */ this.minimumAudioFiles = CB_AudioFileCache.minimumAudioFiles_DEFAULT; /** * Maximum {@link CB_AudioFile} objects that are to be created internally. If it is set to null, there will not be a maximum (it will be unlimited). If an integer is provided, it must be the same number or greater than the value set in the {@link CB_AudioFileCache#minimumAudioFiles} property, allowing 1 minimum. Internal usage only recommended. * @var * @readonly * @type {integer|null} * @default CB_AudioFileCache.maximumAudioFiles_DEFAULT */ this.maximumAudioFiles = CB_AudioFileCache.maximumAudioFiles_DEFAULT; /** * New {@link CB_AudioFile} objects will be created internally when the number of free {@link CB_AudioFile} objects reaches this limit. It must be an integer being 0 (zero) the minimum. Internal usage only recommended. * @var * @readonly * @type {integer} * @default parseInt({@link CB_AudioFileCache#minimumAudioFiles} * 0.25 + 0.5) */ this.minimumAudioFilesFree = CB_AudioFileCache._minimumAudioFilesFree_FIRST_VALUE; /** * Number of new {@link CB_AudioFile} objects to create internally when the minimum limit of free {@link CB_AudioFile} objects ({@link CB_AudioFileCache#minimumAudioFilesFree}) is reached. It must be an integer being 0 (zero) the minimum. Internal usage only recommended. * @var * @readonly * @type {integer} * @default Math.min(parseInt({@link CB_AudioFileCache#minimumAudioFiles} * 0.1 + 0.5), 1) */ this.newAudioFilesWhenNeeded = CB_AudioFileCache._newAudioFilesWhenNeeded_FIRST_VALUE; /** * Number of retries to try to load a {@link CB_AudioFile} object internally before trying to load the next possible one internally (if any). It must be an integer being 0 the minimum. Internal usage only recommended. * @var * @readonly * @type {integer} * @default CB_AudioFileCache.retries_DEFAULT */ this.retries = CB_AudioFileCache.retries_DEFAULT; /** * Tells whether the {@link CB_AudioFile} objects must be checked automatically or not (manually). Internal usage only recommended. * @var * @readonly * @type {boolean} * @default CB_AudioFileCache.checkManually_DEFAULT */ this.checkManually = CB_AudioFileCache.checkManually_DEFAULT; /** * Tells whether the {@link CB_AudioFile} objects must be checked automatically or not (manually) when creates a new {@link CB_AudioFile} object needed. Internal usage only recommended. * @var * @readonly * @type {boolean} * @default CB_AudioFileCache.checkManuallyOnNeededCreated_DEFAULT */ this.checkManuallyOnNeededCreated = CB_AudioFileCache.checkManuallyOnNeededCreated_DEFAULT; /** * Tells whether the {@link CB_AudioFile} objects must be checked automatically or not (manually) when playing one has failed and tries to reload it. Internal usage only recommended. * @var * @readonly * @type {boolean} * @default CB_AudioFileCache.checkManuallyOnPlayingFailed_DEFAULT */ this.checkManuallyOnPlayingFailed = CB_AudioFileCache.checkManuallyOnPlayingFailed_DEFAULT; /** * Tells whether the {@link CB_AudioFile} objects must be checked automatically or not (manually) when checking one has failed and tries to reload it. Internal usage only recommended. * @var * @readonly * @type {boolean} * @default CB_AudioFileCache.checkManuallyOnCheckingFailed_DEFAULT */ this.checkManuallyOnCheckingFailed = CB_AudioFileCache.checkManuallyOnCheckingFailed_DEFAULT; /** * Desired function to be called once the cache has been loaded. The first and unique parameter will be an integer with the {@link CB_AudioFile} objects that still need to be checked, if any, being "this" the current {@link CB_AudioFileCache} object. Internal usage only recommended. * @var * @readonly * @type {function} * @default */ this.onLoad = null; /** * Desired function to be called when any kind of error happens. The first and unique parameter will be a string with the error description (if it could be determined), being "this" the current {@link CB_AudioFileCache} object. Internal usage only recommended. * @var * @readonly * @type {function} * @default */ this.onError = null; /** * Numeric array containing all the {@link CB_AudioFile} objects created internally. Internal usage only recommended. * @var * @readonly * @type {array} * @default */ this.audioFiles = []; /** * Total number of {@link CB_AudioFile} objects created internally (optimization purposes, to avoid using {@link CB_AudioFileCache#audioFiles}.length). Internal usage only recommended. * @var * @readonly * @type {integer} * @default */ this.audioFilesCreated = 0; /** * Stack that stores the indexes (belonged to the {@link CB_AudioFileCache#audioFiles} array) of the free {@link CB_AudioFile} objects. Internal usage only recommended. * @var * @readonly * @type {array} * @default */ this.audioFilesFree = []; /** * Pointer for the {@link CB_AudioFileCache#audioFilesFree} stack (for optimization purposes). Internal usage only recommended. * @var * @readonly * @type {integer} * @default */ this.audioFilesFreePointer = -1; /** * Object with sound instance identifiers (integers created by the {@link CB_AudioFileCache#play} method) which are going to play (this way we can cancel the sound before it starts playing). Each property name is the identifier of the sound instance and the value will be an object with "cancelled" (boolean, to know whether the sound instance was cancelled or not) and "object" (containing the {@link CB_AudioFile} object used) properties. Internal usage only recommended. * @var * @readonly * @type {Object} * @default */ this.soundInstancesQueued = {}; /** * Stores the minimum duration found among all the {@link CB_AudioFile} objects. Internal usage only recommended. * @var * @readonly * @type {number} * @default 0 */ this.duration = 0; /** * Stores the maximum duration found among all the {@link CB_AudioFile} objects. Internal usage only recommended. * @var * @readonly * @type {number} * @default 0 */ this.durationMaximum = 0; //Internal properties: this._URIsListLast = undefined; this._lastSuccededIndexes = {}; //Stores last indexes that were used when an object is created successfully, ordered by URIs and APIs (for optimization purposes). this._checkCacheLoadedTimeout = null; this._checkCacheLoadedTimeoutMs = 500; this._onLoadCalled = false; //Tells whether the onLoad has been called already or not. this._existingObjectIds = []; this._clearAudioFilesTimeout = null; this._createNewAudioFilesIfNeededTimeout = null; ///////this._callRecursivelyIfNotTooLateCalled = false; this._checkingPlaying = false; this._settingAPI = false; //Calls the constructor of the object when creates an instance: return this._init(dataObject); } //Static properties and constants: /////CB_AudioFileCache.MAX_VOLUME = CB_Configuration[CB_BASE_NAME].CB_AudioFile_AudioFileCache_USE_SPEAKER_VOLUME_AS_MAXIMUM; CB_AudioFileCache._soundInstanceIdUnique = 0; /** * Status value for audio file cache which is unloaded. Can be used to compare the value returned by the {@link CB_AudioFileCache#getStatus} method. Recommended for internal usage only. * @constant * @type {integer} * @default 0 */ CB_AudioFileCache.UNLOADED = 0; /** * Status value for an audio file cache which is loading. Can be used to compare the value returned by the {@link CB_AudioFileCache#getStatus} method. Recommended for internal usage only. * @constant * @type {integer} * @default */ CB_AudioFileCache.LOADING = 1; /** * Status value for an audio file cache which has not been checked yet. Can be used to compare the value returned by the {@link CB_AudioFileCache#getStatus} method. Recommended for internal usage only. * @constant * @type {integer} * @default */ CB_AudioFileCache.UNCHECKED = 2; /** * Status value for an audio file cache which is being checked currently. Can be used to compare the value returned by the {@link CB_AudioFileCache#getStatus} method. Recommended for internal usage only. * @constant * @type {integer} * @default */ CB_AudioFileCache.CHECKING = 3; /** * Status value for an audio file cache which has been loaded. Can be used to compare the value returned by the {@link CB_AudioFileCache#getStatus} method. Recommended for internal usage only. * @constant * @type {integer} * @default */ CB_AudioFileCache.LOADED = 4; /** * Status value for an audio file cache which failed to be loaded or failed for any other reason. Can be used to compare the value returned by the {@link CB_AudioFileCache#getStatus} method. Recommended for internal usage only. * @constant * @type {integer} * @default */ CB_AudioFileCache.FAILED = 5; /** * Status value for an audio file cache which has been aborted. This will happen when the audio file cache has been destroyed with the {@link CB_AudioFileCache#destructor} method. Can be used to compare the value returned by the {@link CB_AudioFileCache#getStatus} method. Recommended for internal usage only. * @constant * @type {integer} * @default */ CB_AudioFileCache.ABORTED = 6; /** * Default value for the {@link CB_AudioFileCache#minimumAudioFiles} property. * @constant * @type {integer} * @default */ CB_AudioFileCache.minimumAudioFiles_DEFAULT = 2; /** * Default value for the {@link CB_AudioFileCache#maximumAudioFiles} property. * @constant * @type {integer|null} * @default null */ CB_AudioFileCache.maximumAudioFiles_DEFAULT = null; /** * Default value for the {@link CB_AudioFileCache#retries} property. * @constant * @type {integer} * @default */ CB_AudioFileCache.retries_DEFAULT = 1; /** * Default value for the {@link CB_AudioFileCache#checkManually} property. * @constant * @type {boolean} * @default false */ CB_AudioFileCache.checkManually_DEFAULT = false; /** * Default value for the {@link CB_AudioFileCache#checkManuallyOnNeededCreated} property. * @constant * @type {boolean} * @default false */ CB_AudioFileCache.checkManuallyOnNeededCreated_DEFAULT = false; /** * Default value for the {@link CB_AudioFileCache#checkManuallyOnPlayingFailed} property. * @constant * @type {boolean} * @default false */ CB_AudioFileCache.checkManuallyOnPlayingFailed_DEFAULT = false; /** * Default value for the {@link CB_AudioFileCache#checkManuallyOnCheckingFailed} property. * @constant * @type {boolean} * @default false */ CB_AudioFileCache.checkManuallyOnCheckingFailed_DEFAULT = false; CB_AudioFileCache._minimumAudioFilesFree_FIRST_VALUE = 1; //First value for the {@link CB_AudioFileCache#minimumAudioFilesFree} property, although it will end using a 25% of the {@link CB_AudioFileCache#minimumAudioFiles} by default, rounded to ceil, allowing 0 (zero) minimum. CB_AudioFileCache._newAudioFilesWhenNeeded_FIRST_VALUE = 1; //First value for the {@link CB_AudioFileCache#newAudioFilesWhenNeeded} property, although it will end using a 10% of the {@link CB_AudioFileCache#minimumAudioFiles} by default, rounded to ceil, allowing 1 minimum. //Constructor: CB_AudioFileCache.prototype._init = function(dataObject) { /* FORMAT: dataObject = { [id : String,] [preferredAPIs : Array<String>,] [preferredFormats : Array<String>,] URIs : Object, [minimumAudioFiles : Integer,] [maximumAudioFiles : Integer,] [minimumAudioFilesFree : Integer,] [newAudioFilesWhenNeeded : Integer,] [retries : Integer,] [checkManually : Boolean,] [checkManuallyOnNeededCreated : Boolean,] [checkManuallyOnPlayingFailed : Boolean,] [checkManuallyOnCheckingFailed : Boolean,] [disableAutoLoad : Boolean,] [onLoad : Function,] [onError : Function] }; */ //Tries to load the data (if any): this.load(dataObject); //Returns the object: return this; } /** * Destroys the audio file cache object, including all the internal {@link CB_AudioFile} objects, and frees memory. By default, unless the "preventAbortedStatus" is set to true, sets the current status of the audio file cache object as ABORTED ({@link CB_AudioFileCache.ABORTED} value). * @function * @param {boolean} [stopSounds=false] - Used as the "stopSound" parameter when calling internally the {@link CB_AudioFile#destructor} method for all the {@link CB_AudioFile} objects. * @param {boolean} [preventAbortedStatus=false] - If set to true (not recommended), it will not assign the status of "ABORTED" (it will not assign the value of {@link CB_AudioFileCache.ABORTED} to the {@link CB_AudioFileCache#status} property). */ CB_AudioFileCache.prototype.destructor = function(stopSounds, preventAbortedStatus) { clearTimeout(this._checkCacheLoadedTimeout); clearTimeout(this._clearAudioFilesTimeout); clearTimeout(this._createNewAudioFilesIfNeededTimeout); this.cancelSoundInstances(true, true); //Destroys all sounds: this.destroyAll(stopSounds); //Resets properties to their default value: this.preferredAPIs = CB_Configuration[CB_BASE_NAME].CB_AudioFileCache_PREFERRED_AUDIO_APIS; this.preferredFormats = CB_Configuration[CB_BASE_NAME].CB_AudioFileCache_PREFERRED_AUDIO_FORMATS; this.URIs = {}; this.minimumAudioFiles = CB_AudioFileCache.minimumAudioFiles_DEFAULT; this.maximumAudioFiles = CB_AudioFileCache.maximumAudioFiles_DEFAULT; this.minimumAudioFilesFree = CB_AudioFileCache._minimumAudioFilesFree_FIRST_VALUE; this.newAudioFilesWhenNeeded = CB_AudioFileCache._newAudioFilesWhenNeeded_FIRST_VALUE; this.retries = CB_AudioFileCache.retries_DEFAULT; this.checkManually = CB_AudioFileCache.checkManually_DEFAULT; this.checkManuallyOnNeededCreated = CB_AudioFileCache.checkManuallyOnNeededCreated_DEFAULT; this.checkManuallyOnPlayingFailed = CB_AudioFileCache.checkManuallyOnPlayingFailed_DEFAULT; this.checkManuallyOnCheckingFailed = CB_AudioFileCache.checkManuallyOnCheckingFailed_DEFAULT; this.onLoad = null; this.onError = null; this.audioFiles = []; this.audioFilesCreated = 0; this.soundInstancesQueued = {}; this.duration = 0; this.durationMaximum = 0; //Resets the audioFilesFree stack and its pointer: this.audioFilesFree = []; this.audioFilesFreePointer = -1; //Sets the status as ABORTED: if (!preventAbortedStatus) { this.status = CB_AudioFileCache.ABORTED; } } /** * Loads the audio file cache with the desired data given. This method is called by the constructor automatically. Recommended to be called through a user-driven event (as onClick, onTouch, etc.), as some clients may need this at least the first time in order to be able to play the audio. * @function * @param {CB_AudioFileCache.DATA_OBJECT} dataObject - Object with the desired data and options for the audio files cache. * @returns {CB_AudioFileCache|null} If a "dataObject" is given, it returns the current {@link CB_AudioFileCache} object. Otherwise, it returns null. */ CB_AudioFileCache.prototype.load = function(dataObject) { if (typeof(dataObject) === "undefined" || dataObject === null) { return null; } this.status = CB_AudioFileCache.LOADING; //The cache is loading. //Destroys all previous data (if any): this.destructor(true, true); //Also stops all sounds. //Sanitizes the given data: dataObject.id = CB_trim(dataObject.id); dataObject.minimumAudioFiles = parseInt(CB_trim(dataObject.minimumAudioFiles)); if (dataObject.maximumAudioFiles !== null) { dataObject.maximumAudioFiles = parseInt(CB_trim(dataObject.maximumAudioFiles)); } dataObject.minimumAudioFilesFree = parseInt(CB_trim(dataObject.minimumAudioFilesFree)) dataObject.newAudioFilesWhenNeeded = parseInt(CB_trim(dataObject.newAudioFilesWhenNeeded)) dataObject.retries = parseInt(CB_trim(dataObject.retries)); //Sets the new data: if (dataObject.id !== "") { this.id = dataObject.id; } if (CB_isArray(dataObject.preferredAPIs) && dataObject.preferredAPIs.length > 0 && CB_trim(dataObject.preferredAPIs.join("")) !== "") { this.preferredAPIs = dataObject.preferredAPIs; } if (CB_isArray(dataObject.preferredFormats) && dataObject.preferredFormats.length > 0 && CB_trim(dataObject.preferredFormats.join("")) !== "") { this.preferredFormats = dataObject.preferredFormats; } if (typeof(dataObject.URIs) !== "undefined") { this.URIs = dataObject.URIs; } if (dataObject.minimumAudioFiles !== "" && !isNaN(dataObject.minimumAudioFiles) && dataObject.minimumAudioFiles >= 1) { this.minimumAudioFiles = dataObject.minimumAudioFiles; } if (dataObject.maximumAudioFiles === null || dataObject.maximumAudioFiles !== "" && !isNaN(dataObject.maximumAudioFiles) && dataObject.maximumAudioFiles >= this.minimumAudioFiles) { this.maximumAudioFiles = dataObject.maximumAudioFiles; } else { this.maximumAudioFiles = CB_AudioFileCache.maximumAudioFiles_DEFAULT; } if (dataObject.minimumAudioFilesFree !== "" && !isNaN(dataObject.minimumAudioFilesFree) && dataObject.minimumAudioFilesFree >= 0) { this.minimumAudioFilesFree = dataObject.minimumAudioFilesFree; } else { //Uses a limit of 25% of the minimum by default: this.minimumAudioFilesFree = parseInt(this.minimumAudioFiles * 0.25 + 0.5); //Ceil round. } if (dataObject.newAudioFilesWhenNeeded !== "" && !isNaN(dataObject.newAudioFilesWhenNeeded) && dataObject.newAudioFilesWhenNeeded >= 0) { this.newAudioFilesWhenNeeded = dataObject.newAudioFilesWhenNeeded; } else { //Creates a 10% of the minimum by default: this.newAudioFilesWhenNeeded = parseInt(this.minimumAudioFiles * 0.1 + 0.5); //Ceil round. if (this.newAudioFilesWhenNeeded < 1) { this.newAudioFilesWhenNeeded = 1; } } if (dataObject.retries !== "" && !isNaN(dataObject.retries) && dataObject.retries >= 0) { this.retries = dataObject.retries; } if (typeof(dataObject.checkManually) !== "undefined" && dataObject.checkManually !== null) { this.checkManually = dataObject.checkManually; } if (typeof(dataObject.checkManuallyOnNeededCreated) !== "undefined" && dataObject.checkManuallyOnNeededCreated !== null) { this.checkManuallyOnNeededCreated = dataObject.checkManuallyOnNeededCreated; } if (typeof(dataObject.checkManuallyOnPlayingFailed) !== "undefined" && dataObject.checkManuallyOnPlayingFailed !== null) { this.checkManuallyOnPlayingFailed = dataObject.checkManuallyOnPlayingFailed; } if (typeof(dataObject.checkManuallyOnCheckingFailed) !== "undefined" && dataObject.checkManuallyOnCheckingFailed !== null) { this.checkManuallyOnCheckingFailed = dataObject.checkManuallyOnCheckingFailed; } if (typeof(dataObject.onLoad) === "function") { this.onLoad = dataObject.onLoad; } if (typeof(dataObject.onError) === "function") { this.onError = dataObject.onError; } //If we want, loads the needed objects (if any): var disableAutoLoad = false; if (typeof(dataObject.disableAutoLoad) !== "undefined" && dataObject.disableAutoLoad !== null) { disableAutoLoad = dataObject.disableAutoLoad; } if (!disableAutoLoad) { this.createAudioFiles(this.minimumAudioFiles); } //Creates the minimum number of objects desired. return this; } /** * Creates the desired number of internal {@link CB_AudioFile} objects (inside the {@link CB_AudioFileCache#audioFiles} property). This method is already called by the {@link CB_AudioFileCache#load} method automatically (unless the "disableAutoLoad" property has been set to true in the "dataObject" given). Recommended to be called through a user-driven event (as onClick, onTouch, etc.), as some clients may need this at least the first time in order to be able to play the audio. * @function * @param {integer} minimumAudioFiles - Minimum {@link CB_AudioFile} objects to create internally. It must be an integer being 1 the minimum. If a valid value is given, this will be added to the {@link CB_AudioFileCache#minimumAudioFiles} property. * @param {boolean} [setAsLoaded=false] - If the {@link CB_AudioFile} objects already created internally (before calling this method) does not reach the number given in the "minimumAudioFiles", this parameter will be ignored. Otherwise, if set to true, it will set the {@link CB_AudioFileCache.status} property as "LOADED" (the value of the {@link CB_AudioFileCache#LOADED} property) after reaching the desired number. If set to false, the {@link CB_AudioFileCache.status} property will be set as "LOADED" {@link CB_AudioFileCache#LOADED} property) if the {@link CB_AudioFileCache#checkManually} property is set to true or set as "UNCHECKED" if the {@link CB_AudioFileCache#checkManually} property is set to false. Internal usage only recommended. * @returns {integer} Returns the number of {@link CB_AudioFile} objects which are intended to be created (they could fail). */ CB_AudioFileCache.prototype.createAudioFiles = function(minimumAudioFiles, setAsLoaded) { this.status = CB_AudioFileCache.LOADING; //The cache is loading. if (typeof(minimumAudioFiles) === "undefined" || minimumAudioFiles === null || isNaN(minimumAudioFiles) || minimumAudioFiles < 1) { minimumAudioFiles = this.minimumAudioFiles; } //If there is a maximum of files set: if (typeof(this.maximumAudioFiles) !== "undefined" && this.maximumAudioFiles !== null && !isNaN(this.maximumAudioFiles) && this.maximumAudioFiles >= 1) { //If the minimum of files we want is bigger than the maximum, throws an error and exits: if (minimumAudioFiles > this.maximumAudioFiles) { this.errorFunction("Cannot create " + minimumAudioFiles + " audio files. Maximum is " + this.maximumAudioFiles + "."); return 0; } } //Sets as the minimum objects to create the number given: this.minimumAudioFiles = minimumAudioFiles; //Clears the array of the AudioFiles: this.clearAudioFiles(); //Creates the objects if they do not exist already: this.audioFilesCreated = 0; var audioFilesCreated = 0; var audioFilesCreating = 0; var audioFile; var that = this; for (var x = 0; x < minimumAudioFiles; x++) { //If an object is needed: if (typeof(this.audioFiles[x]) === "undefined" || this.audioFiles[x] === null) { //this.audioFiles[x] = this.createAudioFile(); //If loads correctly, it will increase the audioFilesCreated property. //Creates a new object: /////setTimeout //Uses a delay to prevent Firefox error ("Media resource [URI] could not be decoded") since AAPI and SM2 call play() method (and many calls to play() method would fail). /////( ////////// function() /////{ audioFile = that.createAudioFile(null, null, null, null, null, null, true); //If loads correctly, it will increase the audioFilesCreated property. audioFilesCreating++; //////}, /////////x * 10 + 1 //////); //If no object has been created, throws an error (cache status will be FAILED): //////////if (typeof(audioFile) === "undefined" || audioFile === null) { ////////////this.errorFunction("Tried to create the audio object #" + x + " but is undefined or null."); ////////////return; //Exits the function. } } else { audioFilesCreated++; } //If the cache has already failed or is aborted, just exits: if (this.status === CB_AudioFileCache.FAILED || this.status === CB_AudioFileCache.ABORTED) { return audioFilesCreating; } } //If the files are already created, the cache has finished loading: //if (audioFilesCreated >= minimumAudioFiles) { this.status = CB_AudioFileCache.LOADED; } if (audioFilesCreated >= minimumAudioFiles) { this.status = this.checkManually && !setAsLoaded ? CB_AudioFileCache.UNCHECKED : CB_AudioFileCache.LOADED; } //Stores the number of files already created: this.audioFilesCreated += audioFilesCreated; //It is an addition because some objects could have been created asynchronously. return audioFilesCreating; } /** * Creates one internal {@link CB_AudioFile} object (inside the {@link CB_AudioFileCache#audioFiles} property). This method is already called by the {@link CB_AudioFileCache#createAudioFiles} method and other methods automatically. Recommended to be called through a user-driven event (as onClick, onTouch, etc.), as some clients may need this at least the first time in order to be able to play the audio. Internal usage only recommended. * @function * @param {CB_AudioFileCache.URIS_OBJECT} [URIs={@link CB_AudioFileCache#URIs}] - Object whose property names audio formats and their value is an array of strings with the URIs (audio file paths or audio data URIs) of the audio files in order of preference. It will try to calculate and use the best audio format for the current client and use the first working URI (audio file path or data URI). The more audio formats and URIs provided the better, as it will help to maximize the compatibility with as many clients as possible (as some audio APIs and client just support some formats, or use absolute paths instead of relative ones, etc.). Even with different formats, all provided URIs should belong to the same audio (this means same sound or same music, with same length, etc.). NOTE: Only some clients with some audio APIs will support data URIs. * @param {array} [preferredAPIs={@link CB_AudioFileCache#preferredAPIs}] - Array of strings with the preferred audio API or audio APIs, in order of preference. Possible audio APIs are "WAAPI" ([HTML5 Web Audio API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API}), "SM2" ([SoundManager 2]{@link http://schillmania.com/projects/soundmanager2/}), "ACMP" ([Apache Cordova Media Plugin]{@link https://github.com/apache/cordova-plugin-media}) or "AAPI" ([HTML5 Audio API]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio}). It will try to calculate and use the best one for the current client. * @param {array} [preferredFormats={@link CB_AudioFileCache#preferredFormats}] - Array of strings with the preferred audio format or audio formats (they can include just the format as 'audio/ogg' or also the codec as for example 'audio/ogg; codecs="vorbis"'), in order of preference. It will try to calculate and use the best one for the current client. * @param {CB_AudioFile} [audioObject] - A {@link CB_AudioFile} object that we want to reuse instead of creating a new one (for performance purposes). * @param {function} [callbackOk] - Function with no parameters that will be called once the {@link CB_AudioFile} object is created and loaded successfully (or after it has been checked successfully, depending on the desired option), being "this" the {@link CB_AudioFileCache} object itself. * @param {function} [callbackError] - Function called when any error is produced during creation, loading or checking process, etc. The unique parameter will be a string describing the error (if it was possible to be determined), being "this" the {@link CB_AudioFileCache} object itself. * @param {boolean} [storeURIsList=false] - If set to true, it will store internally the valid supported "URIs" from the given ones (needed by the {@link CB_AudioFileCache#setAudioAPIAll} method, for example). Internal usage only recommended. * @param {boolean} [checkAutomatically=false] - If set to true (not recommended), it will call the {@link CB_AudioFile#checkPlaying} method automatically. Otherwise, it will perform according to the value set at the {@link CB_AudioFileCache#checkManually} property. Internal usage only recommended. * @returns {CB_AudioFile|null} If it fails, it returns null. Otherwise, returns the {@link CB_AudioFile} that has been created or reused. */ CB_AudioFileCache.prototype.createAudioFile = function(URIs, preferredAPIs, preferredFormats, audioObject, callbackOk, callbackError, storeURIsList, checkAutomatically) { //If the cache has already failed or is aborted, just exits: if (this.status === CB_AudioFileCache.FAILED || this.status === CB_AudioFileCache.ABORTED) { return null; } this.status = CB_AudioFileCache.LOADING; //The cache is loading. //If not given, uses default parameters: if (typeof(URIs) === "undefined" || URIs === null) { URIs = this.URIs; } if (!CB_isArray(preferredAPIs) || preferredAPIs.length === 0 || CB_trim(preferredAPIs.join("")) === "") { preferredAPIs = this.preferredAPIs; } if (!CB_isArray(preferredFormats) || preferredFormats.length === 0 || CB_trim(preferredFormats.join("")) === "") { preferredFormats = this.preferredFormats; } //Filters the audio APIs to just use the supported ones: preferredAPIs = CB_AudioDetector.getSupportedAPIs(preferredAPIs); //If preferredAPIs is empty, throws the error and exits: if (preferredAPIs.length === 0) { this.errorFunction("No API supported from the provided ones."); return null; } //Filters the audio formats to just use the supported ones (also orders them with the "probably" ones first): var preferredFormatsSupported = CB_AudioDetector.getSupportedAudioFormats(preferredFormats, ["probably", "maybe"]); if (preferredFormatsSupported.length > 0) { preferredFormats = preferredFormatsSupported; } //Only uses the filtered ones if there is at least one. else { this.errorFunction("No format supported from the provided ones."); return null; } //Filters the URIs given to just use the ones whose format is supported: var URIsList = []; var preferredFormatsLength = preferredFormats.length; var y, URIsListCurrentLength, isDataURI; for (var x = 0; x < preferredFormatsLength; x++) { //If the support format has URIs associated: if (typeof(URIs[preferredFormats[x]]) !== "undefined" && CB_isArray(URIs[preferredFormats[x]])) { URIsListCurrentLength = URIs[preferredFormats[x]].length; for (y = 0; y < URIsListCurrentLength; y++) { //Only stores it if this kind of URI (data URI or normal one) is supported by the format: if (!CB_isString(URIs[preferredFormats[x]][y])) { continue; } isDataURI = (URIs[preferredFormats[x]][y].substring(0, 5).toLowerCase() === "data:"); if (CB_AudioDetector.isAudioFormatSupported(preferredFormats[x], isDataURI) !== "") { //Stores the current URI: URIsList[URIsList.length] = URIs[preferredFormats[x]][y++]; } } } } //If there are not URIs supported, throws the error and exits: if (URIsList.length === 0) { this.errorFunction("No URI supported from the provided ones."); return null; } else if (storeURIsList) { this._URIsListLast = URIsList; } //Returns the object created: return this._createAudioFileObjectRecursively(URIsList, preferredAPIs, audioObject, callbackOk, callbackError, null, null, null, null, null, checkAutomatically); } //Function that creates an audio file object trying given URIs and given APIs (internal usage only): CB_AudioFileCache.prototype._createAudioFileObjectRecursively = function(URIsList, preferredAPIs, audioObject, callbackOk, callbackError, URIsListIndex, stopAtURIsListIndex, preferredAPIsIndex, stopAtPreferredAPIsIndex, retryNumber, checkAutomatically) { this.status = CB_AudioFileCache.LOADING; //The cache is loading. //If there are not URIs supported, throws the error and exits: if (!CB_isArray(URIsList) || URIsList.length === 0) { this.errorFunction("The URIs provided are not in an array or its length is 0."); return null; } //If not given, uses default parameters: if (typeof(URIsListIndex) === "undefined" || URIsListIndex === null || isNaN(URIsListIndex)) { URIsListIndex = 0; } if (typeof(preferredAPIsIndex) === "undefined" || preferredAPIsIndex === null || isNaN(preferredAPIsIndex)) { preferredAPIsIndex = 0; } if (typeof(retryNumber) === "undefined" || retryNumber === null || isNaN(retryNumber)) { retryNumber = 0; } //If it does not exist yet, creates the last succeeded indexes for the given URIs and the given preferred APIs: if (typeof(this._lastSuccededIndexes[URIsList]) === "undefined" || this._lastSuccededIndexes[URIsList] === null) { this._lastSuccededIndexes[URIsList] = {}; } if (typeof(this._lastSuccededIndexes[URIsList][preferredAPIs]) === "undefined" || this._lastSuccededIndexes[URIsList][preferredAPIs] === null) { this._lastSuccededIndexes[URIsList][preferredAPIs] = { "URIsListIndex" : 0, "preferredAPIsIndex" : 0 }; //The first time, starts at the beginning. } //If this is not a recursive call (stopAtURIsListIndex will still not be created): if (typeof(stopAtURIsListIndex) === "undefined" || stopAtURIsListIndex === null) { //We continue from the last succeeded API and last succeeded URI (optimization purposes): URIsListIndex = this._lastSuccededIndexes[URIsList][preferredAPIs]["URIsListIndex"]; preferredAPIsIndex = this._lastSuccededIndexes[URIsList][preferredAPIs]["preferredAPIsIndex"]; //Calculates when we should stop trying to create the object (the last API and last URI we should try): stopAtPreferredAPIsIndex = preferredAPIsIndex; stopAtURIsListIndex = URIsListIndex - 1; if (stopAtURIsListIndex < 0) { stopAtURIsListIndex = URIsList.length - 1; stopAtPreferredAPIsIndex--; if (stopAtPreferredAPIsIndex < 0) { stopAtPreferredAPIsIndex = preferredAPIs.length - 1; } } } var that = this; //Function to call when the object is created successfully: var callbackOkFunction = function() { //Stores the API index and the URI index to use the next time (for optimization purposes): that._lastSuccededIndexes[URIsList][preferredAPIs]["URIsListIndex"] = URIsListIndex; that._lastSuccededIndexes[URIsList][preferredAPIs]["preferredAPIsIndex"] = preferredAPIsIndex; if (typeof(callbackOk) === "function") { callbackOk.call(that); } }; //Function to call when the object has failed (has not been created): var callbackErrorFunction = function(error) { //If the cache has already failed or is aborted, just exits: if (that.status === CB_AudioFileCache.FAILED || that.status === CB_AudioFileCache.ABORTED) { return; } ///////if (allAttemptsFailed) { return; } //If we have already tried all, throws an error (the status of the cache will be set to FAILED): if (preferredAPIsIndex === stopAtPreferredAPIsIndex && URIsListIndex === st