Source: manager/mediaManager.js


define(['mmirf/util/deferred', 'mmirf/util/extend', 'mmirf/util/isArray', 'mmirf/resources', 'mmirf/configurationManager', 'mmirf/logger', 'mmirf/events', 'module'],
	/**
	 * The MediaManager gives access to audio in- and output functionality.
	 *
	 * Depending on its configuration, the MediaManager loads different implementation modules
	 * (<em>plugins</em>) that realize the interface-functions differently.
	 *
	 * See directory <code>mmirf/env/media</code> for available plugins.
	 *
	 * This "class" is a singleton - so that only one instance is in use.<br>
	 *
	 * @class
	 * @name MediaManager
	 * @memberOf mmir
	 * @static
	 * @hideconstructor
	 *
	 * TODO remove / change dependency on forBrowser: res.isBrowserEnv()!!!
	 */
	function(
		deferred, extend, isArray, res, configurationManager, Logger, EventEmitter, module
){
	//the next comment enables JSDoc2 to map all functions etc. to the correct class description
	/** @scope mmir.MediaManager.prototype */

	/**
	 * The instance that holds the singleton MediaManager object.
	 * @private
	 * @type MediaManager
	 * @memberOf MediaManager#
	 */
	var instance = null;

	/**
	 * The logger for the MediaManager.
	 *
	 * Exported as <code>_log</code> by the MediaManager instance.
	 *
	 * @private
	 * @memberOf MediaManager#
	 */
	var logger = Logger.create(module);//initialize with requirejs-module information

	/**
	 * HELPER get list of require plugins for an environment
	 *
	 * supported environments: <default> | 'cordova'
	 *
	 * TODO do we need to differentiate between more environments?
	 *
	 * @param  {Boolean} isCordovaEnv TRUE for cordova-environments, otherwise FALSE
	 * @return {Array<PluginEntry>} the list of required PluginEntry object for the env
	 *
	 * @private
	 * @memberOf mmir.MediaManager#
	 */
	function getRequiredPlugins(isCordovaEnv){
		return isCordovaEnv? [{mod: 'cordovaAudio', type: 'audio'}] : [{mod: 'webAudio', type: 'audio'}];
	}

	/**
	 * default configuration for env-settings "browser" and "cordova":
	 *
	 *  -> may be overwritten by settings in the configuration file.
	 *  e.g. adding the following JSON data to config/configuration.json:
	 * <pre>
	 *     "mediaManager": {
	 *     	"plugins": {
	 *     		"browser": ["webAudio",
	 *     		            "webspeechAudioInput",
	 *     		            {"mod": "audiotts", "config": "ttsMary", "type": "tts"},
	 *     		            {"mod": "webspeechAudioInput", "type": "asr",                    "ctx": "chrome"}
	 *     		],
	 *     		"cordova": ["cordovaAudio",
	 *     		            "mmir-plugin-speech-nuance",
	 *     		            "mmir-plugin-speech-nuance/ttsAndroid",
	 *     		            {"mod": "mmir-plugin-speech-android", "type": "asr",             "ctx": "native"},
	 *     		            {"mod": "mmir-plugin-speech-android/ttsAndroid", "type": "tts",  "ctx": "native"},
	 *     		            {"mod": "audiotts", "config": "ttsMary", "type": "tts",          "ctx": "web"}
	 *     		]
	 *     	}
	 *     }
	 * </pre>
	 *
	 * @private
	 * @type PlainObject
	 *
	 * @memberOf MediaManager#
	 */
	var _defaultPlugins = {
		'browser': getRequiredPlugins(false).concat([
					{mod: 'webspeechAudioInput', type: 'asr'},
					{mod: 'audiotts', config: 'ttsMary', type: 'tts'}
		]),
		'cordova': getRequiredPlugins(true).concat([
					{mod: 'asrAndroid', type: 'asr'},
					{mod: 'audiotts', config: 'ttsMary', type: 'tts'}
		])
	};

	/**
	 * Mapping for modules to default module configurations.
	 *
	 * This is mainly used for backwards compatibility, to map deprecated modules to their
	 * new/corresponding configuration.
	 *
	 * Maps a module name/file to the corresponding (new) module configuration.
	 *
	 * NOTE: The module's name/file are in lower case.
	 *
	 * TODO extract to loadable migration module
	 *
	 * @private
	 * @type PlainObject
	 *
	 * @memberOf MediaManager#
	 */
	var _pluginsConfig = {
		'marytexttospeech': {mod: 'audiotts', config: 'ttsMary', type: 'tts'},
		'html5audioinput':  {mod: 'webAudioInput', config: 'asrGoogleXhr', type: 'asr'},
		'webkitaudioinput':  {mod: 'webspeechAudioInput', type: 'asr'},
		'html5audiooutput':  {mod: 'webAudio', type: 'audio'},
		'cordovaaudiooutput':  {mod: 'cordovaAudio', type: 'audio'},
		'webaudiotexttospeech':  {mod: 'audiotts', config: 'ttsMary', type: 'tts'}
	};

	/**
	 * Mapping for modules' config:
	 *
	 * This is used for backwards compatibility, to map deprecated module config fields to their
	 * new/corresponding configuration.
	 *
	 * NOTE: The config name/file are in lower case.
	 *
	 * TODO extract to loadable migration module
	 *
	 * @private
	 * @type PlainObject
	 *
	 * @memberOf MediaManager#
	 */
	var _pluginsConfigConfig = {
		'webttsmaryimpl': 'ttsMary',
		// 'webttsnuanceimpl': 'ttsNuanceXhr',
		// 'ttsspeakJsimpl': 'ttsSpeakjs',
		// 'webasrgoogleimpl': 'asrGoogleXhr',
		// 'webasrnuanceimpl': 'asrNuanceXhr',
		// 'webasrnuancewsimpl': 'asrNuanceWs',
	};

	/**
	 * HELPER create a non-functional stub-function for a non-functional module/plugin:
	 *        the stub tries to decern the error-callback from its invocation arguments
	 *        and triggers it with a message (or logs an error )
	 *
	 * @param  {String} mod the module/plugin name
	 * @param  {String} funcName the function name (of the function that is not-functional/mocked)
	 * @param  {String} [message] OPTIONAL an additional message text for the error callback (or error logging)
	 * @param  {String} [optionsErrorCallbackName] OPTIONAL the name in the options (i.e. args-argument) for the error callback (default is 'error'); NOTE: positional argument
	 * @return {Function} function that triggeres error callback function([args], onSuccess, onError)
	 *                    where args is optional:
	 *                    if onError is undefined, and args.error is a funtion, then args.error is used as error callback
	 *                    if there is nor error callback, the error will be logged
	 *
	 * @private
	 * @function
	 *
	 * @memberOf MediaManager#
	 */
	var createNonFunctional = function(mod, funcName, message, optionsErrorCallbackName){

		return function _nonFunctional(args, onSuccess, onError){
			optionsErrorCallbackName = optionsErrorCallbackName || 'error'
			if(typeof args === 'function'){
				onError = onSuccess;
				onSuccess = args;
				args = void(0);
			}
			if(typeof onError !== 'function' && args && typeof args[optionsErrorCallbackName] === 'function'){
				onError = args[optionsErrorCallbackName];
			}
			var msg = mod + '.' + funcName + '() is disabled.' + (message? ' ' + message : '');
			if(typeof onError === 'function'){
				onError(msg);
			} else {
				logger.error(mod, funcName, msg);
			}
		}
	}

	/**
	 * Load an media-module implementation from plugin file.
	 *
	 * @param {String} fileName
	 * 			the file-name of the media-module that will be loaded.
	 * 			The file needs to be located in {@link mmir.Resources#getMediaPluginPath}.
	 * 			If fileName does not end with suffix ".js", it will be added, before
	 * 			loading the file.
	 * @param {Function} successCallback
	 * 			invoked with function(filePath, exportedFunctions, nonFunctional, pluginIndex) upon successfully loading the plugin.
	 * @param {Function} failureCallback
	 * @param {String} [execId]
	 * 			the context-ID into which the implementation of the media-module will be loaded.
	 * 			If omitted or FALSY, the default context will be used.
	 * 			NOTE: positional argument
	 * @param {any} [config]
	 * 			a configuration value that will be passed to the media-module upon its initialization
	 * 			NOTE: positional argument
	 * @param {number} pluginIndex
	 * 			the (zero-based) index of the plugin within the plugin loading list
	 *
	 * @private
	 * @function
	 *
	 * @memberOf MediaManager#
	 */
	var loadPlugin = function loadPlugin(filePath, successCallback, failureCallback, execId, config, pluginIndex){

		try {

			if((typeof WEBPACK_BUILD === 'undefined' || !WEBPACK_BUILD) && !/\.js$/i.test(filePath)){
				filePath += '.js';
			}

			var processLoaded = function(newMediaPlugin){

				/**
				 * callback handler that is invoked by the loaded media plugin after initialization
				 *
				 * @param  {PlainObject} exportedFunctions dictionary with the exported functions / fields
				 * @param  {promise | mmir.interface.DisabledPluginInfo} [nonFunctional] OPTIONAL
				 *                       OPTIONAL argument that is supplied in case the plugin is not functional:
				 *                       					if it is promise-like (i.e. then()-able), it is resolved, before continuing with its result as argument for nonFunctional
				 *                                if nonFunctional.disabled is a list, corresponding function stubs will be created in exportedFunctions
				 */
				var onInitialized = function(exportedFunctions, nonFunctional){// nonFunction: {disabled: boolean | string[] | {[func: string]: Function}, mod?: string, message?: string, errorCallbackName?: string} | promise

					if(nonFunctional){

						// if nonFunctional is promise-like: resolve promise an re-invoke with the result
						if(typeof nonFunctional.then === 'function'){
							if(typeof nonFunctional.catch === 'function'){
								nonFunctional.catch(function(err){
										logger.warn('Error resolving non-functional promise when loading MediaPlugin '+filePath+': '+err, err);
										onInitialized(exportedFunctions, true);
								});
							}
							nonFunctional.then(function(res){
								onInitialized(exportedFunctions, res);
							});
							return; ////////////////// EARLY EXIT //////////////////////
						}

						if(nonFunctional.disabled) {

							// create function-stubs for non-functional (function-name-) list:
							if(isArray(nonFunctional.disabled)){

								var nonFuncMod = nonFunctional.mod || filePath;
								var nonFuncMsg = nonFunctional.message || '';
								exportedFunctions = exportedFunctions || {};
								var list = nonFunctional.disabled, name;
								for(var i=0,size=list.length; i < size; ++i){
									name = list[i];
									exportedFunctions[name] = createNonFunctional(nonFuncMod, name, nonFuncMsg, nonFunctional.errorCallbackName);
								}

							} else if(typeof nonFunctional.disabled === 'object'){

								// -> apply function definitions from nonFunctional.disabled to exportedFunctions
								exportedFunctions = exportedFunctions || {};
								var disabledFuncs = nonFunctional.disabled;
								for(var fname in disabledFuncs){
									exportedFunctions[fname] = disabledFuncs[fname];
								}
							}
						}
					}

					if(execId){

						//create new "execution context" if necessary
						if(typeof instance.ctx[execId] === 'undefined'){

							instance.ctx[execId] = {};
						}

						//import functions and properties into execution-context:
						var func;
						for(var p in exportedFunctions){

							if(exportedFunctions.hasOwnProperty(p)){

								//only allow extension of the execution-context, no overwriting:
								if(typeof instance.ctx[execId][p] === 'undefined'){

									func = exportedFunctions[p];
									if(typeof func === 'function'){

										//need to "re-map" the execution context for the functions,
										// so that "they think" they are actually executed within the MediaManager instance

										(function(mediaManagerInstance, originalFunc, name, context, ttsFieldExists){
											//NOTE need closure to "preserve" values of for-iteration
											mediaManagerInstance.ctx[context][name] = function(){
//											logger.log('executing '+context+'.'+name+', in context '+mediaManagerInstance,mediaManagerInstance);//DEBUG
												return originalFunc.apply(mediaManagerInstance, arguments);
											};

											//add alias 'tts' for 'textToSpeech'
											if(!ttsFieldExists && name === 'textToSpeech'){
												logger.error('outdated TTS plugin '+filePath+': plugin implementation should replace textToSpeech() with tts()!');
												mediaManagerInstance.ctx[context]['tts'] = mediaManagerInstance.ctx[context]['textToSpeech'];
											}

										})(instance, func, p, execId, exportedFunctions['tts']);

									}
									else {

										//for non-functions: just attach to the new "sub-context"
										instance.ctx[execId][p] = func;
									}

								} else {

									//if there already is a function/property for this in the execution-context,
									// print out an error:

									logger.error('MediaManager', 'loadPlugin',
										'cannot load implemantion for '+p+' of plugin "'+filePath+
											'" into execution-context "'+execId+
											'": context already exists!'
									);

								}


							}//END if(exportedFunctions<own>)

						}//END for(p in exprotedFunctions)


					}//END if(execId)
					else {
						extend(instance,exportedFunctions);
						//add alias 'tts' for 'textToSpeech'
						if(typeof exportedFunctions['textToSpeech'] === 'function' && !exportedFunctions['tts']){
							logger.error('outdated TTS plugin '+filePath+': plugin implementation should replace textToSpeech() with tts()!');
							instance['tts'] = exportedFunctions['textToSpeech'];
						}
					}

					if (successCallback) successCallback(filePath, exportedFunctions, nonFunctional, pluginIndex);

				};//END: var onInitialized = function(exportedFunctions, ...

				newMediaPlugin.initialize(onInitialized, execId, config);

			};//END: var processLoaded = function(newMediaPlugin){...

			if(typeof WEBPACK_BUILD !== 'undefined' && WEBPACK_BUILD){
				var modResult;
				filePath = filePath.replace(/\.js$/i, '');
				try {
					//TODO convert file-URLs to alias/module IDs and only use __webpack_require__ (& create include list when building from configuration.json)
					modResult = require('../env/media/'+filePath);
				} catch(err){
					//load filePath "raw" as module ID:
					modResult = __webpack_require__(filePath);
				}
				processLoaded(modResult);
			} else {
				require([res.getMediaPluginPath() + filePath], processLoaded, function(_err){
					//try filePath as module ID instead:
					var moduleId = filePath.replace(/\.js$/i, '');
					if(logger.isd()) logger.debug('failed loading plugin from file '+(res.getMediaPluginPath() + filePath)+', trying module ID ' + moduleId)
					require([moduleId], processLoaded, failureCallback)
				});
			}

		} catch (e){
			logger.error('Error loading MediaPlugin '+filePath+': '+e, e);
			if (failureCallback) failureCallback();
		}

	};

	/**
	 * @constructs MediaManager
	 * @memberOf MediaManager.prototype
	 * @private
	 * @ignore
	 */
	function constructor(){

		/**
		 * event emitter / manager for media events
		 *
		 * @private
		 * @type mmir.tools.EventEmitter
		 * @memberOf MediaManager.prototype
		 */
		var listener = new EventEmitter(null);

		/**
		 * event emitter / manager of listener-observers:
		 * observers get notified if a listener for event X gets added/removed
		 *
		 * @private
		 * @type mmir.tools.EventEmitter
		 * @memberOf MediaManager.prototype
		 */
		var listenerObserver = new EventEmitter(null);

		/**
		 * exported as addListener() and on()
		 *
		 * @private
		 * @memberOf MediaManager.prototype
		 */
		var addListenerImpl = function(eventName, eventHandler){

			if(listener.on(eventName, eventHandler)){
				//notify listener-observers for this event-type
				this._notifyObservers(eventName, 'added', eventHandler);
			}
		};
		/**
		 * exported as removeListener() and off()
		 *
		 * @private
		 * @memberOf MediaManager.prototype
		 */
		var removeListenerImpl = function(eventName, eventHandler){
			if(listener.off(eventName, eventHandler)){
				//notify listener-observers for this event-type
				this._notifyObservers(eventName, 'removed', eventHandler);
				return true;
			}
			return false;
		};

		/**
		 * Default execution context for functions:
		 *
		 * if not <code>falsy</code>, then functions will be executed in this context by default.
		 *
		 * @private
		 * @type String
		 * @memberOf MediaManager.prototype
		 */
		var defaultExecId = void(0);

		/** @lends mmir.MediaManager.prototype */
		return {

			/**
			 * A logger for the MediaManager and its plugins/modules.
			 *
			 * <p>
			 * This logger MAY be used by media-plugins and / or tools and helpers
			 * related to the MediaManager.
			 *
			 * <p>
			 * This logger SHOULD NOT be used by "code" that non-related to the
			 * MediaManager
			 *
			 * @name _log
			 * @type mmir.tools.Logger
			 * @default mmir.Logger (logger instance for mmir.MediaManager)
			 * @public
			 *
			 * @memberOf mmir.MediaManager#
			 */
			_log: logger,

			/**
			 * Execution context for plugins:
			 *
			 * dictionary for non-default execution contexts (as specified via plugin configuration "ctx")
			 *
			 * @name ctx
			 * @type Object
			 * @default Object (empty context, i.e. plugins are loaded into the "root context", and no plugins loaded into the execution context)
			 * @public
			 *
			 * @memberOf mmir.MediaManager#
			 */
			ctx: {},

			/**
			 * List of loaded media plugins
			 * <pre>{mod: <module/plugin name or file or id>, type: "asr" | "tts" | "audio" | "custom", config?: any, disabled?: boolean | NonFunctionalInfo}</pre>
			 *
			 * @name plugins
			 * @type Array<PluginLoadConfig>
			 * @public
			 *
			 * @memberOf mmir.MediaManager#
			 */
			plugins: null,

			/**
			 * Wait indicator, e.g. for speech input:
			 * <p>
			 * provides 2 functions:<br>
			 *
			 * <code>preparing()</code>: if called, the implementation indicates that the "user should wait"<br>
			 * <code>ready()</code>: if called, the implementation stops indicating that the "user should wait" (i.e. that the system is ready for user input now)<br>
			 *
			 * <p>
			 * If not set (or functions are not available) will do nothing
			 *
			 * @type mmir.env.media.IWaitReadyIndicator
			 * @memberOf mmir.MediaManager#
			 *
			 * @default Object (no implementation set)
			 *
			 * @see #_preparing
			 * @see #_ready
			 *
			 * @memberOf mmir.MediaManager#
			 *
			 * @example
			 * //define custom wait/ready implementation:
			 * var impl = {
			 * 	preparing: function(str){
			 * 		console.log('Media module '+str+' is preparing...');
			 * 	},
			 * 	ready: function(str){
			 * 		console.log('Media module '+str+' is ready now!');
			 * 	}
			 * };
			 *
			 * //configure MediaManager to use custom implementation:
			 * mmir.MediaManager.waitReadyImpl = impl;
			 *
			 * //-> now plugins that call  mmir.MediaManager._preparing() and  mmir.MediaManager._ready()
			 * //   will invoke the custom implementation's functions.
			 */
			waitReadyImpl: {},

			//... these are the standard audioInput procedures, that should be implemented by a loaded module/file:

///////////////////////////// audio input API: /////////////////////////////
			/**
			 * Start speech recognition with <em>end-of-speech</em> detection:
			 *
			 * the recognizer automatically tries to detect when speech has finished and
			 * triggers the status-callback accordingly with results.
			 *
			 * @async
			 *
			 * @param {PlainObject} [options] OPTIONAL
			 * 		options for Automatic Speech Recognition:
			 * 		<pre>{
			 * 			  success: OPTIONAL Function, the status-callback (see arg statusCallback)
			 * 			, error: OPTIONAL Function, the error callback (see arg failureCallback)
			 * 			, language: OPTIONAL String, the language for recognition (if omitted, the current language setting is used)
			 * 			, intermediate: OTPIONAL Boolean, set true for receiving intermediate results (NOTE not all ASR engines may support intermediate results)
			 * 			, results: OTPIONAL Number, set how many recognition alternatives should be returned at most (NOTE not all ASR engines may support this option)
			 * 			, mode: OTPIONAL "search" | "dictation", set how many recognition alternatives should be returned at most (NOTE not all ASR engines may support this option)
			 * 			, eosPause: OTPIONAL "short" | "long", length of pause after speech for end-of-speech detection (NOTE not all ASR engines may support this option)
			 * 			, disableImprovedFeedback: OTPIONAL Boolean, disable improved feedback when using intermediate results (NOTE not all ASR engines may support this option)
			 * 		}</pre>
			 *
			 * @param {Function} [statusCallback] OPTIONAL
			 * 			callback function that is triggered when, recognition starts, text results become available, and recognition ends.
			 * 			The callback signature is:
			 * 				<pre>
			 * 				callback(
			 * 					text: String | "",
			 * 					confidence: Number | Void,
			 * 					status: "FINAL"|"INTERIM"|"INTERMEDIATE"|"RECORDING_BEGIN"|"RECORDING_DONE",
			 * 					alternatives: Array<{result: String, score: Number}> | Void,
			 * 					unstable: String | Void,
			 * 					custom: any | Void
			 * 				)
			 * 				</pre>
			 *
			 * 			Usually, for status <code>"FINAL" | "INTERIM" | "INTERMEDIATE"</code> text results are returned, where
			 * 			<pre>
			 * 			  "INTERIM": an interim result, that might still change
			 * 			  "INTERMEDIATE": a stable, intermediate result
			 * 			  "FINAL": a (stable) final result, before the recognition stops
			 * 			</pre>
			 * 			If present, the <code>unstable</code> argument provides a preview for the currently processed / recognized text.
			 *
			 * 			The <code>custom</code> argument is dependent on the ASR engine / plugin: specific implementations may return some custom results.
			 *
			 * 			<br>NOTE that when using <code>intermediate</code> mode, status-calls with <code>"INTERMEDIATE"</code> may
			 * 			     contain "final intermediate" results, too.
			 *
			 * 			<br>NOTE: if used in combination with <code>options.success</code>, this argument will supersede the options
			 *
			 * @param {Function} [failureCallback] OPTIONAL
			 * 			callback function that is triggered when an error occurred.
			 * 			The callback signature is:
			 * 				<code>callback(error)</code>
			 *
			 * 			<br>NOTE: if used in combination with <code>options.error</code>, this argument will supersede the options
			 *
			 *
			 * @memberOf mmir.MediaManager#
			 */
			recognize: function(options, statusCallback, failureCallback){

				if(typeof options === 'function'){
					failureCallback = statusCallback;
					statusCallback = options;
					options = void(0);
				}

				var funcName = 'recognize';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else if(failureCallback || (failureCallback = (options && options.error))){
					failureCallback("Audio Input: Speech Recognition is not supported.");
				}
				else {
					logger.error("Audio Input: Speech Recognition is not supported.");
				}
			},
			/**
			 * Start continuous speech recognition:
			 *
			 * The recognizer continues until {@link #stopRecord} is called.
			 *
			 * @async
			 *
			 * @param {PlainObject} [options] OPTIONAL
			 * 		options for Automatic Speech Recognition:
			 * 		<pre>{
			 * 			  success: OPTIONAL Function, the status-callback (see arg statusCallback)
			 * 			, error: OPTIONAL Function, the error callback (see arg failureCallback)
			 * 			, language: OPTIONAL String, the language for recognition (if omitted, the current language setting is used)
			 * 			, intermediate: OTPIONAL Boolean, set true for receiving intermediate results (NOTE not all ASR engines may support intermediate results)
			 * 			, results: OTPIONAL Number, set how many recognition alternatives should be returned at most (NOTE not all ASR engines may support this option)
			 * 			, mode: OTPIONAL "search" | "dictation", set how many recognition alternatives should be returned at most (NOTE not all ASR engines may support this option)
			 * 			, eosPause: OTPIONAL "short" | "long", length of pause after speech for end-of-speech detection (NOTE not all ASR engines may support this option)
			 * 			, disableImprovedFeedback: OTPIONAL Boolean, disable improved feedback when using intermediate results (NOTE not all ASR engines may support this option)
			 * 		}</pre>
			 *
			 * @param {Function} [statusCallback] OPTIONAL
			 * 			callback function that is triggered when, recognition starts, text results become available, and recognition ends.
			 * 			The callback signature is:
			 * 				<pre>
			 * 				callback(
			 * 					text: String | "",
			 * 					confidence: Number | Void,
			 * 					status: "FINAL"|"INTERIM"|"INTERMEDIATE"|"RECORDING_BEGIN"|"RECORDING_DONE",
			 * 					alternatives: Array<{result: String, score: Number}> | Void,
			 * 					unstable: String | Void,
			 * 					custom: any | Void
			 * 				)
			 * 				</pre>
			 *
			 * 			Usually, for status <code>"FINAL" | "INTERIM" | "INTERMEDIATE"</code> text results are returned, where
			 * 			<pre>
			 * 			  "INTERIM": an interim result, that might still change
			 * 			  "INTERMEDIATE": a stable, intermediate result
			 * 			  "FINAL": a (stable) final result, before the recognition stops
			 * 			</pre>
			 * 			If present, the <code>unstable</code> argument provides a preview for the currently processed / recognized text.
			 *
			 * 			The <code>custom</code> argument is dependent on the ASR engine / plugin: specific implementations may return some custom results.
			 *
			 * 			<br>NOTE that when using <code>intermediate</code> mode, status-calls with <code>"INTERMEDIATE"</code> may
			 * 			     contain "final intermediate" results, too.
			 *
			 * 			<br>NOTE: if used in combination with <code>options.success</code>, this argument will supersede the options
			 *
			 * @param {Function} [failureCallback] OPTIONAL
			 * 			callback function that is triggered when an error occurred.
			 * 			The callback signature is:
			 * 				<code>callback(error)</code>
			 *
			 * 			<br>NOTE: if used in combination with <code>options.error</code>, this argument will supersede the options
			 *
			 * @see #stopRecord
			 * @memberOf mmir.MediaManager#
			 */
			startRecord: function(options, statusCallback, failureCallback, isWithIntermediateResults){//TODO remove arg isWithIntermediateResults -> deprecated: use options instead

				if(typeof options === 'function'){
					isWithIntermediateResults = failureCallback;
					failureCallback = statusCallback;
					statusCallback = options;
					options = void(0);
				}

				var funcName = 'startRecord';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else if(failureCallback || (failureCallback = (options && options.error))){
					failureCallback("Audio Input: Speech Recognition (recording) is not supported.");
				}
				else {
					logger.error("Audio Input: Speech Recognition (recording) is not supported.");
				}
			},
			/**
			 * Stops continuous speech recognition:
			 *
			 * After {@link #startRecord} was called, invoking this function will stop the recognition
			 * process and return the result by invoking the <code>succesCallback</code>.
			 *
			 * Note, that the <code>statusCallback</code> may not return an actual text result (i.e. the last
			 * text result may have been return in the <code>statusCallback</code> of the <code>startRecord()</code> call)
			 *
			 * @async
			 *
			 * @param {PlainObject} [options] OPTIONAL
			 * 		options for stopping the Automatic Speech Recognition:
			 * 		<pre>{
			 * 			  success: OPTIONAL Function, the status-callback (see arg statusCallback)
			 * 			, error: OPTIONAL Function, the error callback (see arg failureCallback)
			 * 		}</pre>
			 *
			 *
			 * @param {Function} [statusCallback] OPTIONAL
			 * 			callback function that is triggered when, recognition starts, text results become available, and recognition ends.
			 * 			The callback signature is:
			 * 				<pre>
			 * 				callback(
			 * 					text: String | "",
			 * 					confidence: Number | Void,
			 * 					status: "FINAL"|"INTERIM"|"INTERMEDIATE"|"RECORDING_BEGIN"|"RECORDING_DONE",
			 * 					alternatives: Array<{result: String, score: Number}> | Void,
			 * 					unstable: String | Void,
			 * 					custom: any | Void
			 * 				)
			 * 				</pre>
			 *
			 * 			Usually, for status <code>"FINAL" | "INTERIM" | "INTERMEDIATE"</code> text results are returned, where
			 * 			<pre>
			 * 			  "INTERIM": an interim result, that might still change
			 * 			  "INTERMEDIATE": a stable, intermediate result
			 * 			  "FINAL": a (stable) final result, before the recognition stops
			 * 			</pre>
			 * 			If present, the <code>unstable</code> argument provides a preview for the currently processed / recognized text.
			 *
			 * 			The <code>custom</code> argument is dependent on the ASR engine / plugin: specific implementations may return some custom results.
			 *
			 * 			<br>NOTE that when using <code>intermediate</code> mode (as option in <code>startRecord()</code>),
			 * 				 status-calls with <code>"INTERMEDIATE"</code> may contain "final intermediate" results, too.
			 *
			 * @param {Function} [failureCallback] OPTIONAL
			 * 			callback function that is triggered when an error occurred.
			 * 			The callback signature is:
			 * 				<code>callback(error)</code>
			 *
			 *
			 * @see #startRecord
			 * @memberOf mmir.MediaManager#
			 */
			stopRecord: function(options, statusCallback, failureCallback){

				if(typeof options === 'function'){
					failureCallback = statusCallback;
					statusCallback = options;
					options = void(0);
				}

				var funcName = 'stopRecord';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else if(failureCallback){
					failureCallback("Audio Input: Speech Recognition (recording) is not supported.");
				}
				else {
					logger.error("Audio Input: Speech Recognition (recording) is not supported.");
				}
				},

				/**
				 * Cancel currently active speech recognition.
				 *
				 * Has no effect, if no recognition is active.
				 *
				 * @memberOf mmir.MediaManager#
				 */
			cancelRecognition: function(successCallback,failureCallback){

				var funcName = 'cancelRecognition';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else if(failureCallback){
					failureCallback("Audio Input: canceling Recognize Speech is not supported.");
				}
				else {
					logger.error("Audio Input: canceling Recognize Speech is not supported.");
				}
			},

///////////////////////////// ADDITIONAL (optional) ASR functions: /////////////////////////////
			/**
			 * get list of supported languages for ASR (may not be supported by all plugins).
			 *
			 * @memberOf mmir.MediaManager#
			 */
			getRecognitionLanguages: function(successCallback,failureCallback){

				var funcName = 'getRecognitionLanguages';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else if(failureCallback){
					failureCallback("Audio Input: retrieving list of available languages not supported.");
				}
				else {
					logger.error("Audio Input: retrieving list of available languages not supported.");
				}
			},
			/**
			 * Destroy the speech recognition instance and free up system resources.
			 *
			 * NOTE: may not be supported by all recognition implementations
			 *       (e.g. if the impl. does not block system resources etc).
			 *
			 * NOTE: If it is not supported, <code>successCallback(false)</code> is triggered.
			 *
			 * IMPORTANT: pluins that support destroyRecognition() should also support initializeRecognition().
			 *
			 * @public
			 * @memberOf mmir.MediaManager#
			 *
			 * @param  {Function} [successCallback] callback in case of success: <pre>successCallback(didDestroy: boolean)</pre>
			 * 																			in case, the plugin does not support destroyRecognition(),
			 * 																			<code>successCallback(false)</code> will be invoked
			 * @param  {Function} [failureCallback] callback that will be invoked in case of an error: <pre>failureCallback(error)</pre>
			 *
			 * @see #initializeRecognition
			 */
			destroyRecognition: function(successCallback,failureCallback){

				var funcName = 'destroyRecognition';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else {
					if(logger.isDebug()) {
						logger.debug("Audio Input: destroying the speech recognition instance is not supported.");
					}
					if(successCallback){
						setTimeout(function() { successCallback(false); }, 0);
					}
				}
			},
			/**
			 * Re-initialize the speech recognition instance:
			 * should be called after invoking <code>destroyRecognition()<code> (and its success-callback returned <code>true</code>)
			 * before continuing to use the recognition instance.
			 *
			 * NOTE: may not be supported by all recognition implementations.
			 *
			 * NOTE: If it is not supported, <code>successCallback(false)</code> is triggered.
			 *
			 * IMPORTANT: pluins that support initializeRecognition() should also support destroyRecognition().
			 *
			 * @public
			 * @memberOf mmir.MediaManager#
			 *
			 * @param  {Function} [successCallback] callback in case of success: <pre>successCallback(didDestroy: boolean)</pre>
			 * 																			in case, the plugin does not support initializeRecognition(),
			 * 																			<code>successCallback(false)</code> will be invoked
			 * @param  {Function} [failureCallback] callback that will be invoked in case of an error: <pre>failureCallback(error)</pre>
			 *
			 * @see #destroyRecognitio
			 */
			initializeRecognition: function(successCallback,failureCallback){

				var funcName = 'initializeRecognition';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else {
					if(logger.isDebug()) {
						logger.debug("Audio Input: re-initializing the speech recognition instance is not supported.");
					}
					if(successCallback){
						setTimeout(function() { successCallback(false); }, 0);
					}
				}
			},

///////////////////////////// audio output API: /////////////////////////////

			/**
			 * Play PCM audio data.
			 *
			 * @memberOf mmir.MediaManager#
			 */
			playWAV: function(blob, onPlayedCallback, failureCallback){

				var funcName = 'playWAV';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else if(failureCallback){
					failureCallback("Audio Output: play WAV audio is not supported.");
				}
				else {
					logger.error("Audio Output: play WAV audio is not supported.");
				}
			},
			/**
			 * Play audio file from the specified URL.
			 *
			 * @memberOf mmir.MediaManager#
			 */
			playURL: function(url, onPlayedCallback, failureCallback){

				var funcName = 'playURL';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else if(failureCallback){
					failureCallback("Audio Output: play audio from URL is not supported.");
				}
				else {
					logger.error("Audio Output: play audio from URL is not supported.");
				}
			},
			/**
			 * Play audio file from the specified URL or WAV data.
			 *
			 * Convenience function for {@link #playWAV} and {@link #playURL}:
			 * if first argument is a String, then <code>playURL</code> will be invoked,
			 * otherwise <code>playWAV</code>.
			 *
			 * @memberOf mmir.MediaManager#
			 */
			play: function(urlOrData, onPlayedCallback, failureCallback){
				if(typeof urlOrData === 'string'){
					return this.playURL.apply(this, arguments);
				} else {
					return this.playWAV.apply(this, arguments);
				}
			},
			/**
			 * Get an audio object for the audio file specified by URL.
			 *
			 * The audio object exports the following functions:
			 *
			 * <pre>
			 * play()
			 * stop()
			 * release()
			 * enable()
			 * disable()
			 * setVolume(number)
			 * getDuration()
			 * isPaused()
			 * isEnabled()
			 * </pre>
			 *
			 * NOTE: the audio object should only be used, after the <code>onLoadedCallback</code>
			 *       was triggered.
			 *
			 * @param {String} url
			 * @param {Function} [onPlayedCallback] OPTIONAL
			 * @param {Function} [failureCallback] OPTIONAL
			 * @param {Function} [onLoadedCallback] OPTIONAL
			 *
			 * @returns {mmir.env.media.IAudio} the audio
			 *
			 * @see {mmir.env.media.IAudio#_constructor}
			 *
			 * @memberOf mmir.MediaManager#
			 */
			getURLAsAudio: function(url, onPlayedCallback, failureCallback, onLoadedCallback){

				var funcName = 'getURLAsAudio';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else if(failureCallback){
					failureCallback("Audio Output: create audio from URL is not supported.");
				}
				else {
					logger.error("Audio Output: create audio from URL is not supported.");
				}
			},
			/**
			 * Get an audio object for the audio file specified by URL URL or by WAV data.
			 *
			 * NOTE that getWAVAsAudio may not be supported by all modules!
			 *
			 * Convenience function for {@link #getURLAsAudio} and {@link #getWAVAsAudio}:
			 * if first argument is a String, then <code>getURLAsAudio</code> will be invoked,
			 * otherwise <code>getWAVAsAudio</code> (if the module supports this function).
			 *
			 * @memberOf mmir.MediaManager#
			 */
			getAudio: function(urlOrData, onPlayedCallback, failureCallback, onLoadedCallback){
				if(typeof urlOrData === 'string'){
					return this.getURLAsAudio.apply(this, arguments);
				} else {
					return this.getWAVAsAudio.apply(this, arguments);
				}
			},
			/**
			 * Get an empty audio object. This can be used as dummy or placeholder
			 * for a "real" audio object.
			 *
			 * The audio object exports the following functions:
			 *
			 * <pre>
			 * play()
			 * stop()
			 * release()
			 * enable()
			 * disable()
			 * setVolume(number)
			 * getDuration()
			 * isPaused()
			 * isEnabled()
			 * </pre>
			 *
			 * Note:
			 *
			 * <code>enable()</code> and <code>disable()</code> will set the internal
			 * enabled-state, which can be queried via <code>isEnabled()</code>.
			 *
			 * <code>play()</code> and <code>stop()</code> will set the internal
			 * playing-state, which can be queried via <code>isPaused()</code>
			 * (note however, that this empty audio does not actually play anything.
			 *
			 * <code>setVolume()</code> sets the internal volume-value.
			 *
			 * <code>getDuration()</code> will always return <code>0</code>.
			 *
			 *
			 * @returns {mmir.env.media.IAudio} the audio
			 *
			 * @see {mmir.env.media.IAudio#_constructor}
			 * @memberOf mmir.MediaManager#
			 */
			createEmptyAudio: function(){
				return {
					_enabled: true,
					_play: false,
					_volume: 1,
					play: function(){ this._play = true; return false;},
					stop: function(){ this._play = false; return true;},
					enable: function(){ this._enabled = true; },
					disable: function(){ this._enabled = false; },
					release: function(){ this._enabled = false; },
					setVolume: function(vol){ this._volume = vol; },
					getDuration: function(){ return 0; },
					isPaused: function(){ return !this._play; },
					isEnabled: function(){ return this._enabled; }
				};
			},
///////////////////////////// text-to-speech API: /////////////////////////////

			/**
			 * Synthesizes ("read out loud") text.
			 *
			 * @param {String|Array<String>|PlainObject} [options] OPTIONAL
			 * 		if <code>String</code> or <code>Array</code> of <code>String</code>s
			 * 			  synthesizes the text of the String(s).
			 * 			  <br>For an Array: each entry is interpreted as "sentence";
			 * 				after each sentence, a short pause is inserted before synthesizing the
			 * 				the next sentence<br>
			 * 		for a <code>PlainObject</code>, the following properties should be used:
			 * 		<pre>{
			 * 			  text: String | String[], text that should be read aloud
			 * 			, pauseDuration: OPTIONAL Number, the length of the pauses between sentences (i.e. for String Arrays) in milliseconds
			 * 			, language: OPTIONAL String, the language for synthesis (if omitted, the current language setting is used)
			 * 			, voice: OPTIONAL String, the voice (language specific) for synthesis; NOTE that the specific available voices depend on the TTS engine
			 * 			, success: OPTIONAL Function, the on-playing-completed callback (see arg onPlayedCallback)
			 * 			, error: OPTIONAL Function, the error callback (see arg failureCallback)
			 * 			, ready: OPTIONAL Function, the audio-ready callback (see arg onReadyCallback)
			 * 		}</pre>
			 *
			 * @param {Function} [onPlayedCallback] OPTIONAL
			 * 			callback that is invoked when the audio of the speech synthesis finished playing:
			 * 			<pre>onPlayedCallback()</pre>
			 *
			 * 			<br>NOTE: if used in combination with <code>options.success</code>, this argument will supersede the options
			 *
			 * @param {Function} [failureCallback] OPTIONAL
			 * 			callback that is invoked in case an error occurred:
			 * 			<pre>failureCallback(error: String | Error)</pre>
			 *
			 * 			<br>NOTE: if used in combination with <code>options.error</code>, this argument will supersede the options
			 *
			 * @param {Function} [onReadyCallback] OPTIONAL
			 * 			callback that is invoked when audio becomes ready / is starting to play.
			 * 			If, after the first invocation, audio is paused due to preparing the next audio,
			 * 			then the callback will be invoked with <code>false</code>, and then with <code>true</code>
			 * 			(as first argument), when the audio becomes ready again, i.e. the callback signature is:
			 * 			<pre>onReadyCallback(isReady: Boolean, audio: IAudio)</pre>
			 *
			 * 			<br>NOTE: if used in combination with <code>options.ready</code>, this argument will supersede the options
			 *
			 * @memberOf mmir.MediaManager#
			 */
			tts: function(options, onPlayedCallback, failureCallback, onReadyCallback){

				if(typeof options === 'function'){
					onInitCallback = failureCallback;
					failureCallback = onPlayedCallback;
					onPlayedCallback = options;
					options = void(0);
				}

				var funcName = 'tts';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else if(failureCallback || (failureCallback = (options && options.error))){
					failureCallback("Audio Output: Text To Speech is not supported.");
				}
				else {
					logger.error("Audio Output: Text To Speech is not supported.");
				}
			},

			/**
			 * @deprecated use {@link #tts} instead
			 * @memberOf mmir.MediaManager#
			 */
			textToSpeech: function(parameter, onPlayedCallback, failureCallback){

				var funcName = 'textToSpeech';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else if(failureCallback || (failureCallback = (options && options.error))){
					failureCallback("Audio Output: Text To Speech is not supported.");
				}
				else {
					logger.error("Audio Output: Text To Speech is not supported.");
				}
			},
			/**
			 * Cancel current synthesis.
			 *
			 * @memberOf mmir.MediaManager#
			 */
			cancelSpeech: function(successCallback,failureCallback){

				var funcName = 'cancelSpeech';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else if(failureCallback){
					failureCallback("Audio Output: canceling Text To Speech is not supported.");
				}
				else {
					logger.error("Audio Output: canceling Text To Speech is not supported.");
				}
			},

///////////////////////////// ADDITIONAL (optional) TTS functions: /////////////////////////////
			/**
			 * get list of supported languages for TTS (may not be supported by all plugins).
			 *
			 * @memberOf mmir.MediaManager#
			 */
			getSpeechLanguages: function(successCallback,failureCallback){

				var funcName = 'getSpeechLanguages';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else if(failureCallback){
					failureCallback("Audio Output: retrieving list of available languages not supported.");
				}
				else {
					logger.error("Audio Output: retrieving list of available languages not supported.");
				}
			},
			/**
			 * get list of supported voices for TTS (may not be supported by all plugins).
			 *
			 * @param  {String | VoiceOptions} [options] OPTIONAL if String, the language code (optionally with country code)
			 * 																					for which the voices should be listed.
			 * 																				 if VoiceOptions:
			 * 																				   options.language: {String} OPTIONAL the language code
			 * 																				   options.details: {Boolean} OPTIONAL if TRUE the returned list contains
			 * 																				                    VoiceDetail objects with
			 * 																				                    {name: STRING, language: STRING, gender: "female" | "male" | "unknown"}
			 * @param  {Function} successCallback the success callback: successCallback(Array<String | VoiceDetail>)
			 * @param  {Function} failureCallback the error callback: failureCallback(err)
			 *
			 * @memberOf mmir.MediaManager#
			 */
			getVoices: function(options,successCallback,failureCallback){

				var funcName = 'getVoices';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else if(failureCallback){
					failureCallback("Audio Output: retrieving list of available voices not supported.");
				}
				else {
					logger.error("Audio Output: retrieving list of available voices not supported.");
				}
			},
			/**
			 * Destroy the speech synthesizer instance and free up system resources.
			 *
			 * NOTE: may not be supported by all synthesizer implementations
			 *       (e.g. if the impl. does not block system resources etc).
			 *
			 * NOTE: If it is not supported, <code>successCallback(false)</code> is triggered.
			 *
			 * IMPORTANT: pluins that support destroySpeech() should also support initializeSpeech().
			 *
			 * @public
			 * @memberOf mmir.MediaManager#
			 *
			 * @param  {Function} [successCallback] callback in case of success: <pre>successCallback(didDestroy: boolean)</pre>
			 * 																			in case, the plugin does not support destroySpeech(),
			 * 																			<code>successCallback(false)</code> will be invoked
			 * @param  {Function} [failureCallback] callback that will be invoked in case of an error: <pre>failureCallback(error)</pre>
			 *
			 * @see #initializeSpeech
			 */
			destroySpeech: function(successCallback,failureCallback){

				var funcName = 'destroySpeech';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else {
					if(logger.isDebug()) {
						logger.debug("Audio Output: destroying the speech synthesizer instance is not supported.");
					}
					if(successCallback){
						setTimeout(function() { successCallback(false); }, 0);
					}
				}
			},
			/**
			 * Re-initialize the speech synthesizer instance:
			 * should be called after invoking <code>destroySpeech()<code> (and its success-callback returned <code>true</code>)
			 * before continuing to use the synthesizer instance.
			 *
			 * NOTE: may not be supported by all synthesizer implementations.
			 *
			 * NOTE: If it is not supported, <code>successCallback(false)</code> is triggered.
			 *
			 * IMPORTANT: pluins that support initializeSpeech() should also support destroySpeech().
			 *
			 * @public
			 * @memberOf mmir.MediaManager#
			 *
			 * @param  {Function} [successCallback] callback in case of success: <pre>successCallback(didDestroy: boolean)</pre>
			 * 																			in case, the plugin does not support initializeSpeech(),
			 * 																			<code>successCallback(false)</code> will be invoked
			 * @param  {Function} [failureCallback] callback that will be invoked in case of an error: <pre>failureCallback(error)</pre>
			 *
			 * @see #destroySpeech
			 */
			initializeSpeech: function(successCallback,failureCallback){

				var funcName = 'initializeSpeech';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else {
					if(logger.isDebug()) {
						logger.debug("Audio Output: re-initializing the speech synthesizer instance is not supported.");
					}
					if(successCallback){
						setTimeout(function() { successCallback(false); }, 0);
					}
				}
			},
			/**
			 * Set the volume for the speech synthesis (text-to-speech).
			 *
			 * @param {Number} newValue
			 * 				TODO specify format / range
			 *
			 * @memberOf mmir.MediaManager#
			 */
			setTextToSpeechVolume: function(newValue){

				var funcName = 'setTextToSpeechVolume';
				if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
					return this.ctx[defaultExecId][funcName].apply(this, arguments);
				}
				else {
					logger.error("Audio Output: set volume for Text To Speech is not supported.");
				}
			}


///////////////////////////// MediaManager "managing" functions: /////////////////////////////
			/**
			 * Adds the handler-function for the event.
			 *
			 * This function calls {@link #_notifyObservers} for the eventName with
			 * <code>actionType "added"</code>.
			 *
			 *
			 * Event names (and firing events) are specific to the loaded media plugins.
			 *
			 * TODO list events that the default media-plugins support
			 *   * "miclevelchanged": fired by AudioInput plugins that support querying the microphone (audio input) levels
			 *
			 * A plugin can tigger / fire events using the helper {@link #_emitEvent}
			 * of the MediaManager.
			 *
			 *
			 * Media plugins may observe registration / removal of listeners
			 * via {@link #_addListenerObserver} and {@link #_removeListenerObserver}.
			 * Or get and iterate over listeners via {@link #getListeners}.
			 *
			 *
			 *
			 *
			 * @param {String} eventName
			 * @param {Function} eventHandler
			 *
			 * @function
			 * @memberOf mmir.MediaManager#
			 */
			, addListener: addListenerImpl
			/**
			 * Removes the handler-function for the event.
			 *
			 * Calls {@link #_notifyObservers} for the eventName with
			 * <code>actionType "removed"</code>, if the handler
			 * was actually removed.
			 *
			 * @param {String} eventName
			 * @param {Function} eventHandler
			 *
			 * @returns {Boolean}
			 * 		<code>true</code> if the handler function was actually
			 * 		removed, and <code>false</code> otherwise.
			 *
			 * @function
			 * @memberOf mmir.MediaManager#
			 */
			, removeListener: removeListenerImpl
			/**
			 * Add an event listener.
			 *
			 * @param {String} eventName
			 * 				the name of the event
			 * @param {Function} eventHandler
			 * 				the event handler / callback function
			 *
			 *
			 * @function
			 * @memberOf mmir.MediaManager#
			 * @see #off
			 */
			, on: addListenerImpl
			/**
			 * Add an event listener.
			 *
			 * @param {String} eventName
			 * 				the name of the event
			 * @param {Function} eventHandler
			 * 				the event handler / callback function
			 *
			 * @function
			 * @memberOf mmir.MediaManager#
			 * @see #on
			 */
			, off: removeListenerImpl
			/**
			 * Get list of registered listeners / handlers for an event.
			 *
			 * @returns {Array<Function>} of event-handlers.
			 * 				Empty, if there are no event handlers for eventName
			 *
			 * @memberOf mmir.MediaManager#
			 */
			, getListeners: function(eventName){
				return listener.get(eventName) || [];
			}
			/**
			 * Check if at least one listener / handler is  registered for the event.
			 *
			 * @returns {Boolean} <code>true</code> if at least 1 handler is registered
			 * 					  for eventName; otherwise <code>false</code>.
			 *
			 * @memberOf mmir.MediaManager#
			 */
			, hasListeners: function(eventName){
				return listener.has(eventName);
			}
			/**
			 * Helper for firing / triggering an event.
			 * This should only be used by media plugins (that handle the eventName).
			 *
			 * @param {String} eventName
			 * @param {Array} argsArray
			 * 					the list of arguments with which the event-handlers
			 * 					will be called.
			 * @protected
			 * @memberOf mmir.MediaManager#
			 * @see #on
			 *
			 * @deprecated use {@link #_emitEvent} instead!
			 */
			, _fireEvent: function(eventName, argsArray){
				logger.warn('DEPRECATED: do NOT use mediaManager._fireEvent(type, argList) anymore, instead use use mediaManager._emitEvent(type, ...args)');
				var list = argsArray;
				if(list){
					list.unshift(eventName);
				} else {
					list = [eventName];
				};
				this._emitEvent.apply(this, list);
			}
			/**
			 * Helper for firing / triggering an event.
			 * This should only be used by media plugins (that handle the eventName).
			 *
			 * @param {String} eventName
			 * @param {...any} [args] OPTIONAL
			 * 					the arguments (event data) with which the event-handlers
			 * 					will be called.
			 * @protected
			 * @memberOf mmir.MediaManager#
			 * @see #on
			 * @example
			 * // will invoke listeners for "someevent":
			 * mmir.media._emitEvent('someevent');
			 * // will invoke listeners for "otherevent" with the 2 event data arguments:
			 * mmir.media._emitEvent('otherevent', withTwo, dataParameters);
			 */
			, _emitEvent: function(eventName, args){
				listener.emit.apply(listener, arguments);
			}
			/**
			 * Helper for notifying listener-observers about changes (adding/removing listeners).
			 * This should only be used by media plugins (that handle the eventName).
			 *
			 * @param {String} eventName
			 * @param {String} actionType
			 * 					the change-type that occurred for the event/event-handler:
			 * 					one of <code>["added" | "removed"]</code>.
			 * @param {Function} eventHandler
			 * 					the event-handler function that has changed.
			 *
			 * @protected
			 * @memberOf mmir.MediaManager#
			 * @see #on
			 * @see #off
			 */
			, _notifyObservers: function(eventName, actionType, eventHandler){//actionType: one of "added" | "removed"
				listenerObserver.emit.apply(listenerObserver, arguments);
			}
			/**
			 * Add an observer for registration / removal of event-handler.
			 *
			 * The observer gets notified,when handlers are registered / removed for the event.
			 *
			 * The observer-callback function will be called with the following
			 * arguments
			 *
			 * <code>(eventName, ACTION_TYPE, eventHandler)</code>
			 * where
			 * <ul>
			 *  <li>eventName: String the name of the event that should be observed</li>
			 *  <li>ACTION_TYPE: the type of action: "added" if the handler was
			 *      registered for the event, "removed" if the the handler was removed
			 *  </li>
			 *  <li>eventHandler: the handler function that was registered or removed</li>
			 * </ul>
			 *
			 * @param {String} eventName
			 * @param {Function} observerCallback
			 *
			 * @protected
			 * @see #_removeListenerObserver
			 * @memberOf mmir.MediaManager#
			 */
			, _addListenerObserver: function(eventName, observerCallback){
				listenerObserver.on(eventName, observerCallback);
			}
			/**
			 * Remove an observer that gets notified on registration / removal of event-handler.
			 *
			 * @protected
			 * @see #_addListenerObserver
			 * @memberOf mmir.MediaManager#
			 */
			, _removeListenerObserver: function(eventName, observerCallback){
				return listenerObserver.off(eventName, observerCallback);
			}
			/**
			 * Executes function <code>funcName</code> in "sub-module" <code>ctx</code>
			 * with arguments <code>args</code>.
			 *
			 * <p>
			 * If there is no <code>funcName</code> in "sub-module" <code>ctx</code>,
			 * then <code>funcName</code> from the "main-module" (i.e. from the MediaManager
			 * instance itself) will be used.
			 *
			 * @param {String} ctx
			 * 			the execution context, i.e. "sub-module", in which to execute funcName.<br>
			 * 			If <code>falsy</code>, the "root-module" will used as execution context.
			 * @param {String} funcName
			 * 			the function name
			 * @param {Array} args
			 * 			the arguments for function "packaged" in an array
			 *
			 * @throws {ReferenceError}
			 * 			if <code>funcName</code> does not exist in the requested Execution context.<br>
			 * 			Or if <code>ctx</code> is not <code>falsy</code> but there is no valid execution
			 * 			context <code>ctx</code> in MediaManager.
			 *
			 * @memberOf mmir.MediaManager#
			 * @example
			 *
			 *  //same as mmir.MediaManager.ctx.android.textToSpeech("...", function...):
			 * 	mmir.MediaManager.perform("android", "textToSpeech", ["some text to read out loud",
			 * 		function onFinished(){ console.log("finished reading."); }
			 * 	]);
			 *
			 *  //same as mmir.MediaManager.textToSpeech("...", function...)
			 *  //... IF the defaultExecId is falsy
			 *  //    (i.e. un-changed or set to falsy value via setDefaultExec())
			 * 	mmir.MediaManager.perform(null, "textToSpeech", ["some text to read out loud",
			 * 		function onFinished(){ console.log("finished reading."); }
			 * 	]);
			 *
			 */
			, perform: function(ctx, funcName, args){

				var func;

				if(!ctx){

					if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
						func =  this.ctx[defaultExecId][funcName];
					}


				}
				else if(ctx && typeof this.ctx[ctx] !== 'undefined') {

					if(typeof this.ctx[ctx][funcName] !== 'undefined') {
						func = this.ctx[ctx][funcName];
					}

				} else {
					throw new ReferenceError('There is no context for "'+ctx+'" in MediaManager.ctx!');///////////////////////////// EARLY EXIT ////////////////////
				}


				if(!func){
					func = this[funcName];
				}


				if(typeof func === 'undefined'){
					throw new ReferenceError('There is no function '+funcName+' in MediaManager'+(ctx? ' context ' + ctx : (defaultExecId? ' default context ' + defaultExecId : '')) + '!');///////////////////////////// EARLY EXIT ////////////////////
				}

				return func.apply(this, args);
			}
			/**
			 * Returns function <code>funcName</code> from "sub-module" <code>ctx</code>.
			 *
			 * <p>
			 * If there is no <code>funcName</code> in "sub-module" <code>ctx</code>,
			 * then <code>funcName</code> from the "main-module" (i.e. from the MediaManager
			 * instance itself) will be returned.
			 *
			 * <p>
			 * NOTE that the returned functions will always execute within the context of the
			 * MediaManager instance (i.e. <code>this</code> will refer to the MediaManager instance).
			 *
			 *
			 * @param {String} ctx
			 * 			the execution context, i.e. "sub-module", in which to execute funcName.<br>
			 * 			If <code>falsy</code>, the "root-module" will used as execution context.
			 * @param {String} funcName
			 * 			the function name
			 *
			 * @throws {ReferenceError}
			 * 			if <code>funcName</code> does not exist in the requested Execution context.<br>
			 * 			Or if <code>ctx</code> is not <code>falsy</code> but there is no valid execution
			 * 			context <code>ctx</code> in MediaManager.
			 *
			 * @memberOf mmir.MediaManager#
			 * @example
			 *
			 *  //same as mmir.MediaManager.ctx.android.textToSpeech("...", function...):
			 * 	mmir.MediaManager.getFunc("android", "textToSpeech")("some text to read out loud",
			 * 		function onFinished(){ console.log("finished reading."); }
			 * 	);
			 *
			 *  //same as mmir.MediaManager.textToSpeech("...", function...):
			 *  //... IF the defaultExecId is falsy
			 *  //    (i.e. un-changed or set to falsy value via setDefaultExec())
			 * 	mmir.MediaManager.getFunc(null, "textToSpeech")("some text to read out loud",
			 * 		function onFinished(){ console.log("finished reading."); }
			 * 	);
			 *
			 */
			, getFunc: function(ctx, funcName){//this function performs worse for the "root execution" context, than perform(), since an additional wrapper function must be created

				var isRoot = false;

				if(!ctx){

					if(!defaultExecId){
						isRoot = true;
					}
					else {
						if(typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
							return this.ctx[defaultExecId][funcName];/////////// EARLY EXIT //////////////////
						}
						else {
							isRoot = true;
						}
					}
				}

				if(ctx && typeof this.ctx[ctx] !== 'undefined'){
					if(!isRoot && typeof this.ctx[ctx][funcName] !== 'undefined'){
						return this.ctx[ctx][funcName];///////////////////////////// EARLY EXIT ////////////////////
					}
				}
				else {
					throw new ReferenceError('There is no context for "'+ctx+'" in MediaManager.ctx!');///////////////////////////// EARLY EXIT ////////////////////
				}

				//-> return the implementation of the "root execution context"

				if(typeof instance[funcName] === 'undefined'){
					throw new ReferenceError('There is no function '+funcName+' in MediaManager'+(ctx? ' context ' + ctx : (defaultExecId? ' default context ' + defaultExecId : '')) + '!');///////////////////////////// EARLY EXIT ////////////////////
				}

				//need to create proxy function, in order to preserve correct execution context
				// (i.e. the MediaManager instance)
				return function() {
					return instance[funcName].apply(instance, arguments);
				};

			},
			/**
			 * Set the default execution context.
			 *
			 * If not explicitly set, or set to a <code>falsy</code> value,
			 * then the "root" execution context is the default context.
			 *
			 * @param {String} ctxId
			 * 		the new default excution context for loaded media modules
			 * 		(if <code>falsy</code> the default context will be the "root context")
			 *
			 * @throws {ReferenceError}
			 * 			if <code>ctxId</code> is no valid context
			 *
			 * @memberOf mmir.MediaManager#
			 * @example
			 *
			 * //if context "nuance" exists:
			 * mmir.MediaManager.setDefaultCtx("nuance")
			 *
			 * // -> now the following calls are equal to mmir.MediaManager.ctx.nuance.textToSpeech("some text")
			 * mmir.MediaManager.perform(null, "textToSpeech", ["some text"]);
			 * mmir.MediaManager.getFunc(null, "textToSpeech")("some text");
			 *
			 * //reset to root context:
			 * mmir.MediaManager.setDefaultCtx(false);
			 *
			 * // -> now the following call is equal to mmir.MediaManager.textToSpeech("some text") again
			 * mmir.MediaManager.perform("textToSpeech", ["some text"]);
			 *
			 */
			setDefaultCtx: function(ctxId){
				if(ctxId && typeof instance.ctx[ctxId] === 'undefined'){
					throw new ReferenceError('There is no context for "'+ctxId+'" in MediaManager.ctx!');///////////////////////////// EARLY EXIT ////////////////////
				}
				defaultExecId = ctxId;
			},
			/**
			 * This function is called by media plugin implementations (i.e. modules)
			 * to indicate that they are preparing something and that the user should
			 * wait.
			 *
			 * <p>
			 * The actual implementation for <code>_preparing(String)</code> is given by
			 * {@link #waitReadyImpl}.preparing (if not set, then calling <code>_preparing(String)</code>
			 * will have no effect.
			 *
			 * @param {String} moduleName
			 * 			the module name from which the function was invoked
			 *
			 * @function
			 * @protected
			 * @memberOf mmir.MediaManager#
			 *
			 * @see #waitReadyImpl
			 * @see #_ready
			 */
			_preparing: function(moduleName){
				if(this.waitReadyImpl && this.waitReadyImpl.preparing){
					this.waitReadyImpl.preparing(moduleName);
				}
			},
			/**
			 * This function is called by media plugin implementations (i.e. modules)
			 * to indicate that they are now ready and that the user can start interacting.
			 *
			 * <p>
			 * The actual implementation for <code>_ready(String)</code> is given by the
			 * {@link #waitReadyImpl} implementation (if not set, then calling <code>_ready(String)</code>
			 * will have no effect.
			 *
			 * @param {String} moduleName
			 * 			the module name from which the function was invoked
			 *
			 * @function
			 * @protected
			 * @memberOf mmir.MediaManager#
			 *
			 * @see #waitReadyImpl
			 * @see #_ready
			 */
			_ready: function(moduleName){
				if(this.waitReadyImpl && this.waitReadyImpl.ready){
					this.waitReadyImpl.ready(moduleName);
				}
			}

		};//END: return{...

	};//END: constructor(){...


	//has 2 default configuarions:
	// if isCordovaEnvironment TRUE: use 'cordova' config
	// if FALSEy: use 'browser' config
	//
	// NOTE: this setting/paramater is overwritten, if the configuration has a property 'mediaPlugins' set!!!
	/**
	 * HELPER for init-function:
	 * 	determines, which plugins (i.e. files) should be loaded.
	 *
	 * <p>
	 * has 2 default configuarions:<br>
	 * if isCordovaEnvironment TRUE: use 'cordova' config<br>
	 * if FALSEy: use 'browser' config
	 * <p>
	 * OR<br>
	 * loads the list for the current environment (cordova or browser) that is set in configuration.json via <br>
	 * <pre>
	 * "mediaManager": {
	 * 		"cordova": [...],
	 * 		"browser": [...]
	 * }
	 * </pre>
	 *
	 * <p>
	 * Each entry may either be a String (file name of the plugin) or an Object with
	 * properties
	 * <pre>
	 * 	mod: <file name for the module> //String
	 * 	ctx: <an ID for the module>     //String
	 * </pre>
	 *
	 * If <b>String</b>: the functions of the loaded plugin will be attached to the MediaManager instance:
	 * <code>mmir.MediaManager.thefunction()</code>
	 * <br>
	 * If <b>{mod: plugin,ctx: theContextId}</b>: the functions of the loaded plugin will be attached to the "sub-module"
	 * to the MediaManager instance <em>(NOTE the execution context of the function will remain within
	 * the MediaManager instance, i.e. <code>this</code> will still refer to the MediaManager instance)</em>:
	 * <code>mmir.MediaManager.theId.thefunction()</code>
	 *
	 * <p>
	 * If plugins are loaded with an ID, you can use
	 * <code>mmir.MediaManager.getFunc(ctxId, func)(the, arguments)</code> or
	 * <code>mmir.MediaManager.perform(ctxId, func, [the, arguments])</code>:
	 * If the "sub-module" ctxId does not have the function func (i.e. no MediaManager.ctx.ctxId.func exists), then the default function
	 * in MediaManager will be executed (i.e.  MediaManager.func(the, arguments) ).
	 *
	 *
	 * @returns {Array<String>}
	 * 				the list of plugins which should be loaded
	 *
	 * @private
	 * @memberOf mmir.MediaManager#
	 */
	function getPluginsToLoad(configurationName){//if configurationName is omitted, then it is automatically detected

		var env = configurationName;

		var dataFromConfig = configurationManager.get(['mediaManager','plugins']);

		if(!env){

			var envSetting = res.getEnv();
			if(envSetting === 'cordova'){

				//try to find config for specific cordova-env
				envSetting = res.getEnvPlatform();
				if(envSetting !== 'default'){

					//if there is a config present for the specific envSetting, then use it:
					if((dataFromConfig && dataFromConfig[envSetting]) || _defaultPlugins[envSetting]){
						//if there is a config present for the envSetting, then use it:
						env = envSetting;
					}

				}

			} else if(dataFromConfig && dataFromConfig[envSetting]){
				//if there is a non-default config present for the envSetting, then use it
				//  if there is a deault config, then the env will also be a default one
				//  -> this will be detected by default-detection-mechanism below
				env = envSetting;
			}

			//if there is no env value yet, use default criteria browser vs. cordova env:
			if(!env){
				var isCordova = res.isCordovaEnv();
				if (isCordova) {
					env = 'cordova';
				} else {
					env = 'browser';
				}
			}

			//ASSERT env is non-empty String
		}

		var pluginArray;
		if (dataFromConfig && dataFromConfig[env]){
			pluginArray = dataFromConfig[env].slice();
		} else{
			pluginArray = _defaultPlugins[env].slice();
		}

		return pluginArray;
	}

	/**
	 * HELPER remove a plugin by its mod-field from a list of plugin-entries
	 *
	 * @param  {String} pluginModule the normalized plugin "mod" field (may end with ".js")
	 * @param  {Array<PluginEntry>} pluginList a list of plugin entries, i.e. {mod: "..." ...}
	 *
	 * @private
	 * @memberOf mmir.MediaManager#
	 */
	function removePlugin(pluginModule, pluginList){
		var size = pluginList? pluginList.length : 0;
		if(size === 0){
			return;
		}
		pluginModule = pluginModule.replace(/\.js/i, '');
		for(var i=size - 1; i >= 0; --i){
			if(pluginList[i].mod === pluginModule){
				pluginList.splice(i, 1);
				break;
			}
		}
	}


	/**
	 * HELPER verify that plugin list contains at least one entry of each required plugin or plugin-type
	 *        and if not, adds required plugin entry/type(s).
	 *
	 * 				If there is at least 1 entry in plugins that has not type-field, the HELPER returns FALSE,
	 * 				indicating that required plugins need to be added separately; however requiredPlugins may have been modified
	 * 				that is, entries removed, if the corresponding "mod"-field did match a required plugin.
	 *
	 * 				Otherwise, if TRUE is returned, plugins will contain all required plugins (that is: plugin-types).
	 * 				Any required plugin that was added from requiredPlugins, or was found in plugins, will be removed from requiredPlugins
	 *
	 * @param  {Array<PluginEntry>} plugins the list of specified plugin entries to load (NOTE "mod" name may not be normalized!); may get modified by adding required plugins
	 * @param  {Array<PluginEntry>} requiredPlugins the list of required plugins (that is plugin-types); may get modified by removing plugins that are already in plugins list
	 * @returns {Boolean} TRUE if plugins already contains all required plugin-types, or if the required plugins could be successfully added
	 *
	 * @private
	 * @memberOf mmir.MediaManager#
	 */
	function verifyRequiredPlugins(plugins, requiredPlugins){
		var isVerified = true;
		var entry, mod, rq;
		for(var i=plugins.length-1; i >= 0; --i){
			entry = plugins[i];
			mod = typeof entry === 'string'? mod : entry.mod;
			if(!entry.type){
				isVerified = false;
			}
			for(var j=requiredPlugins.length-1; j >= 0; --j){
				rq = requiredPlugins[j];
				if(mod === rq.mod || rq.type === entry.type){
					plugins.splice(i, 1);
				}
			}
			if(requiredPlugins.length === 0){
				break;
			}
		}
		var len = requiredPlugins.length;
		if(isVerified && len > 0){
			for(var i=0; i < len; ++i){
				plugins.unshift(requiredPlugins[i]);
			}
		}
		return isVerified || len === 0;
	}

	/**
	 *
	 * @private
	 * @memberOf mmir.MediaManager#
	 */
	function loadAllPlugins(pluginArray, successCallback, failureCallback){

		logger.verbose('loading media plugins: ', pluginArray);

		var count = (pluginArray && pluginArray.length) || 0;
		if(count === 0){
			logger.warn('empty plugin list');
			checkCompleted();
			return;////////////////// EARLY EXIT //////////////////////
		}

		function checkCompleted(){
			if (!pluginArray || count === 0){
				if (successCallback) {
					successCallback(pluginArray);
				} else {
						logger.debug('loadAllPlugins completed');
					}
				return;
			}
		}

		function onLoaded(pluginName, _pluginInstance, nonFunctional, index){
			logger.verbose(pluginName + ' loaded!');
			if(nonFunctional && isFinite(index) && pluginArray[index]){
				pluginArray[index].disabled = nonFunctional;
			}
			--count;
			checkCompleted();
		};

		function onError(err){
			logger.error('Error loading' + (err.requireModules? ' ' + err.requireModules : '') + ': '+err, err);
			--count;
			checkCompleted();
			failureCallback && failureCallback;
		};

		var isCordova = res.isCordovaEnv();
		var requiredPlugins = getRequiredPlugins(isCordova);

		if(verifyRequiredPlugins(pluginArray, requiredPlugins)){
			requiredPlugins = null;
		}
		// pluginArray may have been modified by verifyRequiredPlugins() -> update count:
		count = pluginArray.length;

		var ctxId, config, newPluginName;
		for(var i = 0, size = count; i < size; ++i){
			newPluginName = pluginArray[i];

			if(newPluginName.mod){
				ctxId = newPluginName.ctx? newPluginName.ctx : void(0);
			config = newPluginName.config? newPluginName.config : void(0);
				newPluginName = newPluginName.mod;
			} else {
				ctxId = config = void(0);
			}

			//check if there is a "replacement" / default configuration for the requested module
			var legacyModule = newPluginName? _pluginsConfig[newPluginName.toLowerCase().replace(/\.js$/, '')] : null;
			if(legacyModule){
				ctxId  = ctxId  || legacyModule.ctxId;
				config = config || legacyModule.config;
				newPluginName = legacyModule.mod;
			}

			//check if there is a "replacement" for a requested string config-value
			config = typeof config === 'string' && config?
									_pluginsConfigConfig[config.toLowerCase().replace(/\.js$/, '')] || config
									: config;

			removePlugin(newPluginName, requiredPlugins);

			loadPlugin(newPluginName,
				onLoaded, onError,
				ctxId,
				config,
				i
			);

			if(i === size - 1 && requiredPlugins && requiredPlugins.length > 0){

				if(logger.isi()) logger.info('required plugins for '+(isCordova? 'cordova' : 'default')+' environment were not explicitly specified, now loading required plugins: '+JSON.stringify(requiredPlugins));

				for(var j=0, rlen=requiredPlugins.length; j < rlen; ++j){
					++count;
					++size;
					pluginArray.push(requiredPlugins[j]);
				}
				requiredPlugins = null;
			}
		}//END: for(...

	}


	var _stub = {

		/** @scope MediaManager.prototype */

		//TODO add for backwards compatibility?:
//		create : function(){ return this.init.apply(this, arguments); },

		/**
		 * Object containing the instance of the class {{#crossLink "audioInput"}}{{/crossLink}}
		 *
		 * If <em>listenerList</em> is provided, each listener will be registered after the instance
		 * is initialized, but before media-plugins (i.e. environment specfific implementations) are
		 * loaded.
		 * Each entry in the <em>listenerList</em> must have fields <tt>name</tt> (String) and
		 * <tt>listener</tt> (Function), where
		 * <br>
		 * name: is the name of the event
		 * <br>
		 * listener: is the listener implementation (the signature/arguments of the listener function depends
		 * 			 on the specific event for which the listener will be registered)
		 *
		 *
		 * @method init
		 * @async
		 * @param {Function} [successCallback] OPTIONAL
		 * 				 callback that gets triggered after the MediaManager instance has been initialized.
		 * @param {Function} [failureCallback] OPTIONAL
		 * 				 a failure callback that gets triggered if an error occurs during initialization.
		 * @param {Array<Object>} [listenerList] OPTIONAL
		 * 				 a list of listeners that should be registered, where each entry is an Object
		 * 				 with properties:
		 * 				 <pre>
		 * 					{
		 * 						name: String the event name,
		 * 						listener: Function the handler function
		 * 					}
		 * 				 </pre>
		 * @return {Object}
		 * 				a Deferred object that gets resolved, after the {@link mmir.MediaManager}
		 * 				has been initialized.
		 * @public
		 *
		 * @memberOf mmir.MediaManager.prototype
		 *
		 */
		init: function(successCallback, failureCallback, listenerList){

			var defer = deferred();
			var deferredSuccess = function(pluginList){
				if(instance){
					instance.plugins = pluginList;
				}
				defer.resolve();
			};
			var deferredFailure = function(){
				defer.reject();
			};

			if(successCallback || failureCallback){
				defer.then(successCallback, failureCallback);
			}

			if (instance === null) {
				extend(this, constructor());
				instance = this;

				if(listenerList){
					for(var i=0, size = listenerList.length; i < size; ++i){
						instance.addListener(listenerList[i].name, listenerList[i].listener);
					}
				}

				var pluginConfig = getPluginsToLoad();
				loadAllPlugins(pluginConfig, deferredSuccess, deferredFailure);

			}
			else if(listenerList){
				for(var i=0, size = listenerList.length; i < size; ++i){
					instance.addListener(listenerList[i].name, listenerList[i].listener);
				}
			}

			return defer;
		},
		/**
		 * loads a file. If the file implements a function initialize(f)
		 * where the function f is called with a set of functions e, then those functions in e
		 * are added to the visibility of audioInput, and will from now on be applicable by calling
		 * mmir.MediaManager.<function name>().
		 *
		 * @function
		 * @protected
		 * @memberOf mmir.MediaManager.prototype
		 *
		 * @example
		 * NOTE should only be used by plugin implementations for loading (dependent/sub-) plugins.
		 *
		 */
		loadPlugin: function(filePath, successCallback, failureCallback, execId, config){
			if (instance === null) {
				this.init().then(function(){
					loadPlugin(filePath, successCallback, failureCallback, execId, config);
				});
				return;
			}

			loadPlugin(filePath, successCallback, failureCallback, execId, config);
		}
	};

	return _stub;

});//END: define(..., function(){...