# Source: CrossBase/audiovisual/audio/CB_Speaker.js

    /**
     * @file Speakers management. Contains the {@link CB_Speaker} static 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}.
     */

    /**
     * Static class to manage the speaker or speakers. It will return itself if it is tried to be instantiated. It can also use [timbre.js]{@link https://mohayonao.github.io/timbre.js/}, [Band.js]{@link https://github.com/meenie/band.js/} and [jsfx]{@link https://github.com/loov/jsfx}.
     * @namespace
     *  @todo Check whether the path used to play silent sounds as a workaround (through the internal "_playBlankFilesSilently" method) is right or not (now it uses the {@link CB_scriptPath} variable).
     *  @todo getCopy and filterProperties methods (similar to the ones from {@link CB_GraphicSprites} and {@link CB_GraphicSpritesScene}).
     */
    var CB_Speaker = CB_Speakers = function() { return CB_Speaker; };
    {
    	CB_Speaker.initialized = false; //It will tells whether the object has been initialized or not.

    	CB_Speaker._silentAudioFilePlayed = false; //Defines whether a silent audio file has already been played when the user has clicked (hack for some web clients that doesn't start playing sounds unless the first sound is played when the user has clicked).
    	CB_Speaker._silentAudioFilePlayedWAAPI = false; //Defines whether a silent audio file has already been played using WAAPI when the user has clicked (hack for some web clients that doesn't start playing sounds unless the first sound is played when the user has clicked).
    	CB_Speaker._volume = CB_Configuration[CB_BASE_NAME].CB_Speaker_DEFAULT_VOLUME; //Volume of the speaker.
    	CB_Speaker._volumeBeforeMute = CB_Speaker._volume; //Stores the volume which was set before muting.
    	CB_Speaker._audioFileSpritesPool = null; //CB_AudioFileSpritesPool object.
    	CB_Speaker._timbreJSObject = null; //Stores the timbre.js object (if any).
    	CB_Speaker._bandJSObject = null; //Stores the Band.js object (if any).
    	CB_Speaker._jsfxObject = null; //Stores the jsfx object (if any).

    	//Initializes all values:
    	CB_Speaker.init = function()
    	{
    		if (CB_Speaker.initialized) { return CB_Speaker; }

    		//Sets that the object has already been initialized:
    		CB_Speaker.initialized = true;

    		//Plays the blank sound silently, if able (workaround for some web clients):
    		try { CB_Speaker._playBlankFilesSilently(); } catch(E) {}

    		//Gets and stores the timbre.js object (if any):
    		CB_Speaker._timbreJSObject = (typeof(T) !== "undefined" && T !== null) ? T : null;

    		//Gets and stores the Band.js object (if any):
    		CB_Speaker._bandJSObject = (typeof(BandJS) !== "undefined" && BandJS !== null) ? BandJS : null;

    		//Gets and stores the jsfx object (if any):
    		CB_Speaker._jsfxObject = (typeof(jsfx) !== "undefined" && jsfx !== null) ? jsfx : null;

    		return CB_Speaker;
    	}

    	//Function that plays a silent sound the first time the user clicks:
    	CB_Speaker._playBlankFilesSilently = function() //Note: hack for some web clients that doesn't start playing sounds unless the first sound is played when the user has clicked.
    	{
    		var silentAudioFiles = new Array(2);
    		silentAudioFiles["AAPI"] = new Array(3);
    		silentAudioFiles["WAAPI"] = new Array(6);

    		var destroySilentAudioFile =
    			function(API, extension, needsLoaded)
    			{
    				setTimeout
    				(
    					function()
    					{
    						if (typeof(silentAudioFiles) !== "undefined" && typeof(silentAudioFiles[API]) !== "undefined" && typeof(silentAudioFiles[API][extension]) !== "undefined" && silentAudioFiles[API][extension] !== null)
    						{
    							if (!needsLoaded || silentAudioFiles[API][extension].getStatusString() === "LOADED")
    							{
    								silentAudioFiles[API][extension].destructor(true, false, true, false);
    								silentAudioFiles[API][extension] = null;
    							}
    						}
    					},
    					100
    				);
    			};

    		var createSilentAudioFile =
    			function(filePath, API, extension, dataURI)
    			{
    				extension = extension.toLowerCase();
    				silentAudioFiles[API][extension] = null;
    				if (extension === "mp3" && CB_AudioDetector.isAudioFormatSupported("audio/mpeg;", dataURI) === "") { return; }
    				if (extension === "ogg" && CB_AudioDetector.isAudioFormatSupported("audio/ogg", dataURI) === "") { return; }
    				if (extension === "wav" && CB_AudioDetector.isAudioFormatSupported("audio/wav", dataURI) === "") { return; }

    				API = API.toUpperCase();

    				silentAudioFiles[API][extension] = new CB_AudioFile
    				(
    					filePath, //filePath
    					"CB_blank_sound_" + API, //id
    					{
    						autoLoad: true,
    						autoPlay: false,//(API === "AAPI"),
    						loop: false,
    						volume: 1
    					}, //options
    					API, //API
    					function()
    					{
    						destroySilentAudioFile(API, extension, true);
    					}, //callbackOk
    					function(error)
    					{
    						destroySilentAudioFile(API, extension);
    					} //callbackError
    				);
    			};

    		/*
    		createSilentAudioFile("data:audio/mpeg;base64, ", "WAAPI", "mp3", true);
    		createSilentAudioFile("data:audio/ogg;base64, ", "WAAPI", "ogg", true);
    		createSilentAudioFile("data:audio/wav;base64, ", "WAAPI", "wav", true);
    		*/
    		var silentAudioFilePlay =
    			function()
    			{
    				if (!CB_Speaker._silentAudioFilePlayed)
    				{
    					CB_Speaker._silentAudioFilePlayed = true;
    					/////CB_Speaker.playFileBase(CB_scriptPath + CB_Configuration[CB_BASE_NAME].SCRIPT_PATH + "audiovisual/audio/sounds/blank");

    					if (CB_AudioDetector.isAPISupported("AAPI", false))
    					{
    						createSilentAudioFile(CB_scriptPath + CB_Configuration[CB_BASE_NAME].SCRIPT_PATH + "audiovisual/audio/sounds/blank.mp3", "AAPI", "mp3");
    						createSilentAudioFile(CB_scriptPath + CB_Configuration[CB_BASE_NAME].SCRIPT_PATH + "audiovisual/audio/sounds/blank.mp3", "AAPI", "ogg");
    						createSilentAudioFile(CB_scriptPath + CB_Configuration[CB_BASE_NAME].SCRIPT_PATH + "audiovisual/audio/sounds/blank.mp3", "AAPI", "wav");
    					}
    				}
    				if (!CB_Speaker._silentAudioFilePlayedWAAPI && CB_AudioDetector.isAPISupported("WAAPI", false))
    				{
    					createSilentAudioFile("data:audio/mpeg;base64, ", "WAAPI", "mp3", true);
    					createSilentAudioFile("data:audio/ogg;base64, ", "WAAPI", "ogg", true);
    					createSilentAudioFile("data:audio/wav;base64, ", "WAAPI", "wav", true);

    					if (typeof(CB_AudioFile_API["WAAPI"].audioContext) === "undefined" || CB_AudioFile_API["WAAPI"].audioContext === null)
    					{
    						if (typeof(window.AudioContext) !== "undefined" || typeof(window.webkitAudioContext) !== "undefined")
    						{
    							CB_AudioFile_API["WAAPI"].audioContext = new (window.AudioContext || window.webkitAudioContext)();
    							//Hack (source: https://stackoverflow.com/questions/56768576/safari-audiocontext-suspended-even-with-onclick-creation/56770254#56770254):
    							try { CB_AudioFile_API["WAAPI"].audioContext.createGain(); } catch (createGainError) { CB_console("Error creating a GainNode for the AudioContext: " + createGainError); }
    							try { CB_AudioFile_API["WAAPI"].audioContext.resume(); } catch (audioContextResumeError) { CB_console("Error resuming AudioContext: " + audioContextResumeError); }
    							if (!CB_AudioFile_API["WAAPI"].audioContext) { return null; }
    						}
    					}

    					var extensions = [ "mp3", "ogg", "wav" ];
    					var allNull = true;
    					for (var x = 0; x < 3; x++)
    					{
    						if (silentAudioFiles["WAAPI"][extensions[x]] !== null)
    						{
    							allNull = false;
    							if (silentAudioFiles["WAAPI"][extensions[x]].getStatusString() === "UNCHECKED")
    							{
    								new function(x)
    								{
    									var finishedChecking =
    										function(error, checkedFine)
    										{
    											silentAudioFiles["WAAPI"][extensions[x]].destructor(true, false, true, false);
    											silentAudioFiles["WAAPI"][extensions[x]] = null;
    										};
    									silentAudioFiles["WAAPI"][extensions[x]].checkPlaying(function() { finishedChecking("", true); }, finishedChecking, false, true, false);
    								}(x);
    							}
    						}
    					}
    					//If all objects are null it means all have either played or failed:
    					if (allNull) { CB_Speaker._silentAudioFilePlayedWAAPI = true; }
    				}

    				//If not done before, initializes the jsfx library:
    				if (typeof(jsfx) === "undefined" && typeof(_jsfxInitializer) === "function")
    				{
    					//Initializes the jsfx library:
    					_jsfxInitializer(CB_this["jsfx"] = {});
    					_jsfxInitializer = null; //Prevents executing it more than once.

    					//Gets and stores the jsfx object (if any):
    					CB_Speaker._jsfxObject = (typeof(jsfx) !== "undefined" && jsfx !== null) ? jsfx : null;
    				}

    				//If not done before, initializes the timbre.js library:
    				if (typeof(T) === "undefined" && typeof(_timbreJSInitializer) === "function")
    				{
    					_timbreJSInitializer.call(CB_this);
    					_timbreJSInitializer = null; //Prevents executing it more than once.

    					//Gets and stores the timbre.js object (if any):
    					CB_Speaker._timbreJSObject = (typeof(T) !== "undefined" && T !== null) ? T : null;
    				}

    				//TODO: Think about initializing Band.js library here too (the same as with jsfx and timbre.js libraries).

    			};
    		CB_Events.add(document, "click", silentAudioFilePlay, true, true, false);
    		//CB_Events.add(document, "touchstart", silentAudioFilePlay, true, true, false);
    	}

    	//Applies the current volume to all objects:
    	CB_Speaker._applyVolume = function(volume, isMuteOrUnmute, forceSetVolumeProperty, functionToExecute, audioFiles, avoidSanitize)
    	{
    		//If there is no sprites pool object, just exits:
    		if (typeof(CB_Speaker._audioFileSpritesPool) === "undefined" || CB_Speaker._audioFileSpritesPool === null) { return; }

    		if (!avoidSanitize)
    		{
    			volume = CB_Speaker.sanitizeVolume(volume, true); //Uses CB_Speaker._volume if the value is not valid.
    		}

    		//If we want either to mute or unmute:
    		if (isMuteOrUnmute)
    		{
    			//If volume is zero, we want to mute:
    			if (volume === 0) { CB_Speaker._audioFileSpritesPool.muteAll(functionToExecute, audioFiles); }
    			//...otherwise, we want to unmute:
    			else { CB_Speaker._audioFileSpritesPool.unmuteAll(functionToExecute, audioFiles); }
    		}
    		//...otherwise, we just set the volume given:
    		else
    		{
    			CB_Speaker._audioFileSpritesPool.setVolumeAll(volume, forceSetVolumeProperty, functionToExecute, audioFiles);
    		}
    	}

    	/**
    	 * Sanitizes the volume (it does not allow values greater than 100 or lower than 0).
    	 *  @function
    	 *  @param {number} [volume=CB_Configuration.CrossBase.CB_Speaker_DEFAULT_VOLUME|CB_Speaker._volume] - The desired volume to be sanitized. If not given, it will use the current volume if the "useSpeakerVolumeIfNaN" parameter is set to true or the default volume otherwise.
    	 *  @param {boolean} [useSpeakerVolumeIfNaN=false] - If it is set to true and no valid volume is received in the "volume" parameter, it will use the current volume ({@link CB_Speaker._volume}). Otherwise, if it is set to false and no valid volume is received in the "volume" parameter, it will use the default volume which is set in the {@link CB_Configuration.CrossBase.CB_Speaker_DEFAULT_VOLUME} constant.
    	 *  @returns {number} Returns the volume sanitized (it does not allow values greater than 100 or lower than 0).
    	 */
    	CB_Speaker.sanitizeVolume = function(volume, useSpeakerVolumeIfNaN)
    	{
    		volume = parseInt(volume);
    		if (isNaN(volume)) { volume = useSpeakerVolumeIfNaN ? CB_Speaker._volume : CB_Configuration[CB_BASE_NAME].CB_Speaker_DEFAULT_VOLUME; }

    		//Sets the volume within their limits if it is beyond them:
    		if (volume > 100) { volume = 100; }
    		else if (volume < 0) { volume = 0; }

    		return volume;
    	}

    	/**
    	 * Sets the given volume.
    	 *  @function
    	 *  @param {number} [volume=CB_Speaker._volume] - The desired volume to be used (it will be sanitized internally by calling the {@link CB_Speaker.sanitizeVolume} function with the volume as the unique parameter). If not given, it will use the current volume. If the "avoidApplyingVolume" is set to false and the "isMuteOrUnmute" parameter is false, it will also be used as a parameter to call the {@link CB_AudioFileSpritesPool#setVolumeAll} method of the internal {@link CB_AudioFileSpritesPool} object defined in {@link CB_Speaker._audioFileSpritesPool} (if any).
    	 *  @param {boolean} [avoidApplyingVolume=false] - If it is set to true, the volume will not be applied (by calling the {@link CB_Speaker._applyVolume} internal function, which will also use the internal {@link CB_AudioFileSpritesPool} object defined in {@link CB_Speaker._audioFileSpritesPool}, if any) and just the {@link CB_Speaker._volume} internal property will be set.
    	 *  @param {boolean} [forceSetVolumeProperty=false] - If the "avoidApplyingVolume" is set to false and the "isMuteOrUnmute" parameter is false, it will be used as a parameter to call the {@link CB_AudioFileSpritesPool#setVolumeAll} method of the internal {@link CB_AudioFileSpritesPool} object defined in {@link CB_Speaker._audioFileSpritesPool} (if any).
    	 *  @param {function} [functionToExecute] - A callback function. If the "avoidApplyingVolume" is set to false and the "isMuteOrUnmute" parameter is false, it will be used as the "onSetVolume" parameter to call the {@link CB_AudioFileSpritesPool#setVolumeAll} method of the internal {@link CB_AudioFileSpritesPool} object defined in {@link CB_Speaker._audioFileSpritesPool} (if any). If the "avoidApplyingVolume" is set to false and the "isMuteOrUnmute" parameter is true, it will be used as the "onMute" parameter to call the {@link CB_AudioFileSpritesPool#muteAll} method or as the "onUnmute" parameter to call the {@link CB_AudioFileSpritesPool#unmuteAll} method of the internal {@link CB_AudioFileSpritesPool} object defined in {@link CB_Speaker._audioFileSpritesPool} (if any).
    	 *  @param {array} [audioFiles] - An array containing the CB_AudioFile objects that we want to affect (if not set and the "avoidApplyingVolume" is set to false, it will affect all the CB_AudioFile objects of the internal {@link CB_AudioFileSpritesPool} object defined in {@link CB_Speaker._audioFileSpritesPool}, if any). If the "avoidApplyingVolume" is set to false and the "isMuteOrUnmute" parameter is false, it will be used as the "audioFiles" parameter to call the {@link CB_AudioFileSpritesPool#setVolumeAll} method of the internal {@link CB_AudioFileSpritesPool} object defined in {@link CB_Speaker._audioFileSpritesPool} (if any). If the "avoidApplyingVolume" is set to false and the "isMuteOrUnmute" parameter is true, it will be used as the "audioFiles" parameter to call the {@link CB_AudioFileSpritesPool#muteAll} method or as the "audioFiles" parameter to call the {@link CB_AudioFileSpritesPool#unmuteAll} method of the internal {@link CB_AudioFileSpritesPool} object defined in {@link CB_Speaker._audioFileSpritesPool} (if any).
    	 *  @param {boolean} [saveForUnmute=false] - If it is set to true, the given volume will be saved to be restored later when the {@link CB_Speaker.unmute} method is called.
    	 *  @param {boolean} [isMuteOrUnmute=false] - If it is set to true and the volume given is zero, the action performed internally will be muting (by calling the {@link CB_AudioFileSpritesPool#muteAll} method of the internal {@link CB_AudioFileSpritesPool} object defined in {@link CB_Speaker._audioFileSpritesPool}, if any). Otherwise, if it is set to true and the volume given is not zero, the action performed internally will be unmuting (by calling the {@link CB_AudioFileSpritesPool#unmuteAll} method of the internal {@link CB_AudioFileSpritesPool} object defined in {@link CB_Speaker._audioFileSpritesPool}, if any). If it is set to false, the action performed will be set the volume given (by calling the {@link CB_AudioFileSpritesPool#setVolumeAll} method of the internal {@link CB_AudioFileSpritesPool} object defined in {@link CB_Speaker._audioFileSpritesPool}, if any).
    	 *  @returns {number} Returns the current volume being used after setting it.
    	 *	@todo Also affect BandJS, jsfx and timbre.js.
    	 */
    	CB_Speaker.setVolume = function(volume, avoidApplyingVolume, forceSetVolumeProperty, functionToExecute, audioFiles, saveForUnmute, isMuteOrUnmute)
    	{
    		volume = CB_Speaker.sanitizeVolume(volume);
    		if ((saveForUnmute || volume === 0) && CB_Speaker._volume > 0) { CB_Speaker._volumeBeforeMute = CB_Speaker._volume; } //Only saves it if the current volume is more than zero.
    		CB_Speaker._volume = volume;
    		if (!avoidApplyingVolume) { CB_Speaker._applyVolume(volume, isMuteOrUnmute, forceSetVolumeProperty, functionToExecute, audioFiles, true); }
    		return CB_Speaker._volume;
    	}

    	/**
    	 * Mutes the speaker. Calls the {@link CB_Speaker.setVolume} function internally, with 0 (zero) as the "volume" parmeter, null as the "forceSetVolumeProperty" parameter and true for both "saveForUnmute" and "isMuteOrUnmute" parameters.
    	 *  @function
    	 *  @param {boolean} [avoidApplyingVolume=false] - Used as a parameter to call the {@link CB_Speaker.setVolume} function internally.
    	 *  @param {function} [onMute] - Used as as the "functionToExecute" parameter to call the {@link CB_Speaker.setVolume} function internally.
    	 *  @param {array} [audioFiles] - Used as a parameter to call the {@link CB_Speaker.setVolume} function internally.
    	 *  @returns {number} Returns the result of the internal call to the {@link CB_Speaker.setVolume} function.
    	 *	@todo Also affect BandJS, jsfx and timbre.js.
    	 */
    	CB_Speaker.mute = function(avoidApplyingVolume, onMute, audioFiles)
    	{
    		return CB_Speaker.setVolume(0, avoidApplyingVolume, null, onMute, audioFiles, true, true);
    	}

    	/**
    	 * Restores the audio volume after muting it. If the current volume is 0 (zero, muted) or the "ignoreVolume" parameter is set to true, calls the {@link CB_Speaker.setVolume} function internally, with the previously-saved volume before muting it as the "volume" parmeter, null as the "forceSetVolumeProperty" parameter, false for the "saveForUnmute" parameter and true for the "isMuteOrUnmute" parameter.
    	 *  @function
    	 *  @param {boolean} [avoidApplyingVolume=false] - Used as a parameter to call the {@link CB_Speaker.setVolume} function internally, if the "ignoreVolume" parameter is set to true.
    	 *  @param {function} [onUnmute] - Used as as the "functionToExecute" parameter to call the {@link CB_Speaker.setVolume} function internally, if the "ignoreVolume" parameter is set to true.
    	 *  @param {array} [audioFiles] - Used as a parameter to call the {@link CB_Speaker.setVolume} function internally, if the "ignoreVolume" parameter is set to true.
    	 *  @param {boolean} [ignoreVolume=false] - If set to true, it will try to perform the muting action even when the current volume is not 0 (zero, muted).
    	 *  @returns {number} Returns the current volume.
    	 *	@todo Also affect BandJS, jsfx and timbre.js.
    	 */
    	CB_Speaker.unmute = function(avoidApplyingVolume, onUnmute, audioFiles, ignoreVolume)
    	{
    		if (CB_Speaker._volume === 0 || ignoreVolume)
    		{
    			CB_Speaker.setVolume(CB_Speaker._volumeBeforeMute, avoidApplyingVolume, null, onUnmute, audioFiles, false, true);
    		}
    		return CB_Speaker._volume;
    	}

    	/**
    	 * Tells the current volume.
    	 *  @function
    	 *  @param {boolean} [avoidSanitize=false] - If set to true, it will not call the {@link CB_Speaker.sanitizeVolume} function internally before returning the volume.
    	 *  @returns {number} Returns the current volume.
    	 */
    	CB_Speaker.getVolume = function(avoidSanitize)
    	{
    		if (!avoidSanitize) { CB_Speaker._volume = CB_Speaker.sanitizeVolume(CB_Speaker._volume); }
    		return CB_Speaker._volume;
    	}

    	/**
    	 * Sets the desired {@link CB_AudioFileSpritesPool} object.
    	 *  @function
    	 *  @param {CB_AudioFileSpritesPool} audioFileSpritesPool - The desired {@link CB_AudioFileSpritesPool} object to manage all audio files.
    	 *  @returns {CB_AudioFileSpritesPool|null} Returns the current {@link CB_AudioFileSpritesPool} object after setting it (if any) or null otherwise.
    	 */
    	CB_Speaker.setAudioFileSpritesPool = function(audioFileSpritesPool)
    	{
    		//if (typeof(audioFileSpritesPool) !== "undefined" && audioFileSpritesPool !== null && audioFileSpritesPool instanceof CB_AudioFileSpritesPool)
    		if (audioFileSpritesPool instanceof CB_AudioFileSpritesPool)
    		{
    			CB_Speaker._audioFileSpritesPool = audioFileSpritesPool;
    		}
    		return CB_Speaker._audioFileSpritesPool;
    	}

    	/**
    	 * Returns the current {@link CB_AudioFileSpritesPool} object (if any).
    	 *  @function
    	 *  @returns {CB_AudioFileSpritesPool|null} Returns the current {@link CB_AudioFileSpritesPool} object (if any) or null otherwise.
    	 */
    	CB_Speaker.getAudioFileSpritesPool = function()
    	{
    		return CB_Speaker._audioFileSpritesPool || null;
    	}

    	/**
    	 * Returns the [timbre.js]{@link https://mohayonao.github.io/timbre.js/} object (if any). Useful for functional processing and synthesizing audio.
    	 *  @function
    	 *  @returns {Object|null} Returns the current [timbre.js]{@link https://mohayonao.github.io/timbre.js/} object (if any) or null otherwise.
    	 *  @todo timbre.js should have into account the CB_Speaker._volume
    	 */
    	CB_Speaker.getTimbreJSObject = function()
    	{
    		return CB_Speaker._timbreJSObject || null;
    	}

    	/**
    	 * Returns a new [Band.js]{@link https://github.com/meenie/band.js/} object (if possible). Useful for managing music composition.
    	 *  @function
    	 *  @returns {Object|null} Returns a new [Band.js]{@link https://github.com/meenie/band.js/} object (if possible) or null otherwise.
    	 *  @todo Band.js should have into account the CB_Speaker._volume
    	 */
    	CB_Speaker.getBandJSObject = function()
    	{
    		return CB_Speaker._bandJSObject ? new CB_Speaker._bandJSObject() : null;
    	}

    	/**
    	 * Returns the [jsfx]{@link https://github.com/loov/jsfx} object (if any). Useful for managing sound effects generation.
    	 *  @function
    	 *  @returns {Object|null} Returns the current [jsfx]{@link https://github.com/loov/jsfx} object (if any) or null otherwise.
    	 *  @todo jsfx should have into account the CB_Speaker._volume
    	 */
    	CB_Speaker.getJsfxObject = function()
    	{
    		return CB_Speaker._jsfxObject || null;
    	}

    } //End of the static class CB_Speaker.

#### Search results