1 /*
  2  * 	Copyright (C) 2012-2013 DFKI GmbH
  3  * 	Deutsches Forschungszentrum fuer Kuenstliche Intelligenz
  4  * 	German Research Center for Artificial Intelligence
  5  * 	http://www.dfki.de
  6  * 
  7  * 	Permission is hereby granted, free of charge, to any person obtaining a 
  8  * 	copy of this software and associated documentation files (the 
  9  * 	"Software"), to deal in the Software without restriction, including 
 10  * 	without limitation the rights to use, copy, modify, merge, publish, 
 11  * 	distribute, sublicense, and/or sell copies of the Software, and to 
 12  * 	permit persons to whom the Software is furnished to do so, subject to 
 13  * 	the following conditions:
 14  * 
 15  * 	The above copyright notice and this permission notice shall be included 
 16  * 	in all copies or substantial portions of the Software.
 17  * 
 18  * 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 19  * 	OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 20  * 	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 21  * 	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
 22  * 	CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
 23  * 	TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 24  * 	SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 25  */
 26 
 27 
 28 
 29 define(['jquery', 'constants', 'commonUtils', 'configurationManager', 'dictionary', 'logger', 'module'],
 30 	/**
 31 	 * The MediaManager gives access to audio in- and output functionality.
 32 	 * 
 33 	 * Depending on its configuration, the MediaManager loads different implementation modules
 34 	 * (<em>plugins</em>) that realize the interface-functions differently.
 35 	 * 
 36 	 * See directory <code>mmirf/env/media</code> for available plugins.
 37 	 * 
 38 	 * This "class" is a singleton - so that only one instance is in use.<br>
 39 	 * 
 40 	 * @class
 41 	 * @name MediaManager
 42 	 * @memberOf mmir
 43 	 * @static
 44 	 * 
 45 	 * @requires jQuery.extend
 46 	 * @requires jQuery.Deferred
 47 	 * 
 48 	 * TODO remove / change dependency on forBrowser: constants.isBrowserEnv()!!!
 49 	 */
 50 	function(
 51 		jQuery, constants, commonUtils, configurationManager, Dictionary, Logger, module
 52 ){
 53 	//the next comment enables JSDoc2 to map all functions etc. to the correct class description
 54 	/** @scope mmir.MediaManager.prototype */
 55 
 56 	/**
 57 	 * The instance that holds the singleton MediaManager object.
 58 	 * @private
 59 	 * @type MediaManager
 60 	 * @memberOf MediaManager#
 61 	 */
 62     var instance = null;
 63     
 64     /**
 65      * default configuration for env-settings "browser" and "cordova":
 66      * 
 67      *  -> may be overwritten by settings in the configuration file.
 68      *  e.g. adding the following JSON data to config/configuration.json:
 69      * <pre>
 70 	 *     "mediaManager": {
 71 	 *     	"plugins": {
 72 	 *     		"browser": ["html5AudioOutput.js",
 73 	 *     		            "html5AudioInput.js",
 74 	 *     		            "maryTextToSpeech.js",
 75 	 *     		            {"mod": "webkitAudioInput.js",    "ctx": "chrome"}
 76 	 *     		],
 77 	 *     		"cordova": ["cordovaAudioOutput.js",
 78 	 *     		            "nuanceAudioInput.js",
 79 	 *     		            "nuanceTextToSpeech.js",
 80 	 *     		            {"mod": "androidAudioInput.js",   "ctx": "native"},
 81 	 *     		            {"mod": "androidTextToSpeech.js", "ctx": "native"},
 82 	 *     		            {"mod": "maryTextToSpeech.js",    "ctx": "web"}
 83 	 *     		]
 84 	 *     	}
 85 	 *     }
 86 	 * </pre>
 87 	 * 
 88 	 * @private
 89 	 * @type PlainObject
 90 	 * 
 91 	 * @memberOf MediaManager#
 92 	 */
 93     var _defaultPlugins = {
 94     		'browser': ['waitReadyIndicator.js',
 95     		            'html5AudioOutput.js',
 96     		            'html5AudioInput.js',
 97     		            'maryTextToSpeech.js'
 98     		],
 99     		'cordova': ['waitReadyIndicator.js',
100     		            'cordovaAudioOutput.js',
101     		            'androidAudioInput.js',
102     		            'maryTextToSpeech.js'
103     		]
104     };
105     
106     /**
107      * Load an media-module implementation from plugin file.
108      * 
109      * @param {String} filePath
110      * @param {Function} successCallback
111      * @param {Function} failureCallback
112      * @param {String} [execId]
113      * 
114      * @private
115 	 * @function
116 	 * 
117 	 * @memberOf MediaManager#
118 	 */
119     var loadPlugin = function loadPlugin(filePath, successCallback, failureCallback, execId){
120     	try {
121     		
122     		commonUtils.loadScript(constants.getMediaPluginPath() + filePath, function(){
123     			
124 	    		if (typeof newMediaPlugin !== 'undefined' && newMediaPlugin){
125 	    			
126 	    			newMediaPlugin.initialize(function(exportedFunctions){
127 	    				
128 	    				if(execId){
129 	    					
130 	    					//create new "execution context" if necessary
131 	    					if(typeof instance.ctx[execId] === 'undefined'){
132 	    						
133 	    						instance.ctx[execId] = {};
134 	    						
135 	    					}
136 	    					
137 	    					//import functions and properties into execution-context:
138     						var func;
139 	    					for(var p in exportedFunctions){
140 	    						
141 	    						if(exportedFunctions.hasOwnProperty(p)){
142 	    							
143 	    							//only allow extension of the execution-context, no overwriting:
144 	    							if(typeof instance.ctx[execId][p] === 'undefined'){
145 	    								
146 	    								func = exportedFunctions[p];
147 		    							if(typeof func === 'function'){
148 		    								
149 		    								//need to "re-map" the execution context for the functions,
150 		    								// so that "they think" they are actually executed within the MediaManager instance
151 			    							
152 		    								(function(mediaManagerInstance, originalFunc, name, context){
153 		    									//NOTE need closure to "preserve" values of for-iteration
154 		    									mediaManagerInstance.ctx[context][name] = function(){
155 //					    								console.log('executing '+context+'.'+name+', in context '+mediaManagerInstance,mediaManagerInstance);//DEBUG
156 				    								return originalFunc.apply(mediaManagerInstance, arguments);
157 				    							};
158 		    								})(instance, func, p, execId);
159 		    								
160 		    							}
161 		    							else {
162 		    								//for non-functions: just attach to the new "sub-context"
163 			    							instance.ctx[execId][p] = func;
164 		    							}
165 		    							
166 	    							} else {
167 	    								
168 	    								//if there already is a function/property for this in the execution-context,
169 	    								// print out an error:
170 	    								
171 	    	    						logger.error('MediaManager', 'loadPlugin', 
172 	    	    							'cannot load implemantion for '+p+' of plugin "'+filePath+
173 	    	    								'" into execution-context "'+execId+
174 	    	    								'": context already exists!'
175 	    	    						);
176 	    	    						
177 	    	    					}
178 	    							
179 	    							
180 	    						}//END if(exportedFunctions<own>)
181 	    						
182 	    					}//END for(p in exprotedFunctions)
183 		    					
184 	    					
185 	    				}//END if(execId)
186 	    				else {
187 	    					jQuery.extend(true,instance,exportedFunctions);
188 	    					newMediaPlugin = null;
189 	    				}
190 	    				
191 						if (successCallback) successCallback();
192 						
193 	    			}, instance, execId);
194 	    			
195 	    			//"delete" global var for media plugin after loading
196 	    			// TODO remove when/if other loading mechanism is established
197 //	    			newMediaPlugin = void(0);
198 	    			delete newMediaPlugin;
199 	    			
200 	    		}
201 	    		else {
202 	        		console.error('Error loading MediaPlugin '+filePath + ' - no newMediaPlugin set!');
203 	    			if (failureCallback) failureCallback();
204 	    		}
205 			});
206     		
207 
208     		//DISABLED @russa: currently disabled, since debugging eval'ed code is problematic
209     		//                 NOTE support for code-naming feature (see below) is currently somewhat broken in FireFox (e.g. location in error-stack is not done correctly) 
210 //        	//NOTE: this new loading-mechanism avoids global VARIABLES by
211 //    		//	* loading the script as text
212 //    		//	* evaluating the script-text (i.e. executing the JavaScript) within an local context
213 //    		//  * uses code-naming feature for eval'ed code: //@ sourceURL=...
214 //    		//i.e. eval(..) is used ...
215 //    		var targetPath = constants.getMediaPluginPath()+filePath;
216 //    		$.ajax({
217 //                async: true,
218 //                dataType: "text",
219 //                url: targetPath,
220 //                success: function(data){
221 //                	
222 //                	//add "dummy-export-code" to script-text 
223 //                	// -> for "retrieving" the media-plugin implementation as return value from eval(..)
224 //            		var LOAD_MODULE_TEMPLATE_POSTFIX = 'var dummy = newMediaPlugin; dummy';
225 //            		//use eval code naming feature...
226 //            		var codeId = ' sourceURL=' + constants.getMediaPluginPath()+filePath + '\n';
227 //            		//... for WebKit:
228 //            		var CODE_ID_EXPR1 = '//@';
229 //            		// ... and for FireFox:
230 //            		var CODE_ID_EXPR2 = '//#';
231 //            		
232 //                	var newMediaPlugin = eval(data 
233 //                			+ CODE_ID_EXPR1 + codeId 
234 //                			+ CODE_ID_EXPR2 + codeId 
235 //                			+ LOAD_MODULE_TEMPLATE_POSTFIX
236 //                	);
237 //                	
238 //                	if (typeof newMediaPlugin !== 'undefined' && newMediaPlugin){
239 //    	    			newMediaPlugin.initialize(function(exportedFunctions){
240 //    	    					jQuery.extend(true,instance,exportedFunctions);
241 //    	    					newMediaPlugin = null;
242 //    							if (successCallback) successCallback();
243 //    	    			}, instance);
244 //    	    		}
245 //    	    		else {
246 //    	        		console.error('Error loading MediaPlugin '+filePath + ' - no newMediaPlugin set!');
247 //    	    			if (failureCallback) failureCallback();
248 //    	    		}
249 //                }
250 //            }).fail(function(jqxhr, settings, err){
251 //                // print out an error message
252 //				var errMsg = err && err.stack? err.stack : err;
253 //                console.error("[" + settings + "] " + JSON.stringify(jqxhr) + " -- " + partial.path + ": "+errMsg); //failure
254 //            });
255     	}catch (e){
256     		console.error('Error loading MediaPlugin '+filePath+': '+e);
257     		if (failureCallback) failureCallback();
258     	}
259 	
260     };
261     
262 //    /**
263 //     * "Register" a media-module implementation to the MediaManager.
264 //     * 
265 //     * @param {MediaPlugin} newMediaPlugin
266 //     * 				The new media-plugin which must have the
267 //     * 				function <code>initialize</code>:
268 //     * 				The initializer function will be called with 3 arguments:
269 //     * 				(callbackFuntion(mediaPlugin: Object), instance: MediaManager, execId: String)
270 //     * 
271 //     * 				the first argument (this callback-function from the MediaManager)
272 //     * 				should be invoked by the media-plugin when it has it finished 
273 //     * 				initializing in its <code>initializeFunc</code>.
274 //     * 				The callback must be invoked with on argument:
275 //     * 				(mediaPlugin: Object)
276 //     * 				where mediaPlugin is an object with all the functions and properties,
277 //     * 				that the media-plugin exports to the MediaManager.
278 //     * 
279 //     * @private
280 //	 * @function
281 //	 * 
282 //	 * @memberOf MediaManager#
283 //	 */
284 //    function registerMediaPlugin(newMediaPlugin, successCallback, failureCallback, execId){
285 //    	TODO move code from loadPlugin here:
286 //    	* export this as MediaManager.registerPlugin
287 //    	* media-plugins should call registerPlugin on MediaManager (instead of creating object newMediaPlugin)
288 //    	* open problem: how can success-callback for MediaManager-initialization be handled this way? (should be called after all plugins have themselves initialized)
289 //    }
290     
291     /**
292      * @constructs MediaManager
293      * @memberOf MediaManager.prototype
294      * @private
295      * @ignore
296      */
297     function constructor(){
298     	
299     	/**
300     	 * map of listeners: 
301     	 * 		event(String) -> listener(Function)
302     	 * 
303 		 * @private
304     	 * @memberOf MediaManager.prototype
305     	 */
306     	var listener = new Dictionary();
307     	
308     	/**
309     	 * map of listener-observers:
310     	 *  observers get notified if a listener for event X gets added/removed
311     	 * 
312 		 * @private
313     	 * @memberOf MediaManager.prototype
314     	 */
315     	var listenerObserver = new Dictionary();
316     	
317 		/** 
318 		 * exported as addListener() and on()
319 		 * 
320 		 * @private
321 		 * @memberOf MediaManager.prototype
322 		 */
323     	var addListenerImpl = function(eventName, eventHandler){
324 			var list = listener.get(eventName);
325 			if(!list){
326 				list = [eventHandler];
327 				listener.put(eventName, list);
328 			}
329 			else {
330 				list.push(eventHandler);
331 			}
332 
333 			//notify listener-observers for this event-type
334 			this._notifyObservers(eventName, 'added', eventHandler);
335 		};
336 		/**
337 		 * exported as removeListener() and off()
338 		 *  
339 		 * @private
340 		 * @memberOf MediaManager.prototype
341 		 */
342     	var removeListenerImpl = function(eventName, eventHandler){
343 			var isRemoved = false;
344 			var list = listener.get(eventName);
345 			if(list){
346 				var size = list.length;
347 				for(var i = size - 1; i >= 0; --i){
348 					if(list[i] ===  eventHandler){
349 						
350 						//move all handlers after i by 1 index-position ahead:
351 						for(var j = size - 1; j > i; --j){
352 							list[j-1] = list[j];
353 						}
354 						//remove last array-element
355 						list.splice(size-1, 1);
356 						
357 						//notify listener-observers for this event-type
358 						this._notifyObservers(eventName, 'removed', eventHandler);
359 						
360 						isRemoved = true;
361 						break;
362 					}
363 				}
364 			}
365 			return isRemoved;
366 		};
367 		
368 		
369 		/**
370 		 * The logger for the MediaManager.
371 		 * 
372 		 * Exported as <code>_log</code> by the MediaManager instance.
373 		 * 
374 		 * @private
375 		 * @memberOf MediaManager.prototype
376 		 */
377 		var logger = Logger.create(module);//initialize with requirejs-module information
378 		
379 
380 		/**
381 		 * Default execution context for functions:
382 		 * 
383 		 * if not <code>falsy</code>, then functions will be executed in this context by default.
384 		 * 
385 		 * @private
386 		 * @type String
387 		 * @memberOf MediaManager.prototype
388 		 */
389 		var defaultExecId = void(0);
390     	
391     	/** @lends mmir.MediaManager.prototype */
392     	return {
393     			
394     			/**
395     			 * A logger for the MediaManager and its plugins/modules.
396     			 * 
397     			 * <p>
398     			 * This logger MAY be used by media-plugins and / or tools and helpers
399     			 * related to the MediaManager.
400     			 * 
401     			 * <p>
402     			 * This logger SHOULD NOT be used by "code" that non-related to the
403     			 * MediaManager 
404     			 * 
405 				 * @name _log
406 				 * @type mmir.Logger
407 				 * @default mmir.Logger (logger instance for mmir.MediaManager)
408 				 * @public
409 				 * 
410 				 * @memberOf mmir.MediaManager#
411     			 */
412     			_log: logger,
413     			
414     			/**
415     			 * Execution context for plugins
416     			 * 
417     			 * TODO add doc
418     			 * 
419 				 * @name ctx
420 				 * @type mmir.Logger
421 				 * @default Object (empty context, i.e. plugins are loaded into the "root context", and no plugins loaded into the execution context)
422 				 * @public
423 				 * 
424 				 * @memberOf mmir.MediaManager#
425     			 */
426     			ctx: {},
427     			
428     			/**
429     			 * Wait indicator, e.g. for speech input:
430     			 * <p>
431     			 * provides 2 functions:<br>
432     			 * 
433     			 * <code>preparing()</code>: if called, the implementation indicates that the "user should wait"<br>
434     			 * <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>
435     			 * 
436     			 * <p>
437     			 * If not set (or functions are not available) will do nothing
438     			 * 
439     			 * @type mmir.env.media.IWaitReadyIndicator
440     			 * @memberOf mmir.MediaManager#
441     			 * 
442     			 * @default Object (no implementation set)
443     			 * 
444     			 * @see #_preparing
445     			 * @see #_ready
446     			 * 
447     			 * @example
448     			 * //define custom wait/ready implementation:
449     			 * var impl = {
450     			 * 	preparing: function(str){
451     			 * 		console.log('Media module '+str+' is preparing...');
452     			 * 	},
453     			 * 	ready: function(str){
454     			 * 		console.log('Media module '+str+' is ready now!');
455     			 * 	}
456     			 * };
457     			 * 
458     			 * //configure MediaManager to use custom implementation:
459     			 * mmir.MediaManager.waitReadyImpl = impl;
460     			 * 
461     			 * //-> now plugins that call  mmir.MediaManager._preparing() and  mmir.MediaManager._ready()
462     			 * //   will invoke the custom implementation's functions.
463     			 */
464     			waitReadyImpl: {},
465     		
466     			//TODO add API documentation
467     		
468     			//... these are the standard audioInput procedures, that should be implemented by a loaded file
469     		
470 ///////////////////////////// audio input API: /////////////////////////////
471 	    		/**
472 	    		 * Start speech recognition with <em>end-of-speech</em> detection:
473 	    		 * 
474 	    		 * the recognizer automatically tries to detect when speech has finished and then
475 	    		 * triggers the callback with the result.
476 	    		 * 
477 	    		 * @async
478 	    		 * 
479 	    		 * @param {Function} [successCallBack] OPTIONAL
480 	    		 * 			callback function that is triggered when a text result is available.
481 	    		 * 			The callback signature is:
482 	    		 * 				<code>callback(textResult)</code>
483 	    		 * @param {Function} [failureCallBack] OPTIONAL
484 	    		 * 			callback function that is triggered when an error occurred.
485 	    		 * 			The callback signature is:
486 	    		 * 				<code>callback(error)</code> 
487 	    		 */
488     			recognize: function(successCallBack, failureCallBack){
489     				if(failureCallBack){
490     					failureCallBack("Audio Input: Speech Recognition is not supported.");
491     				}
492     				else {
493     					console.error("Audio Input: Speech Recognition is not supported.");
494     				}
495     			},
496     			/**
497 	    		 * Start continuous speech recognition:
498 	    		 * 
499 	    		 * The recognizer continues until {@link #stopRecord} is called.
500 	    		 * 
501 	    		 * <p>
502 	    		 * If <code>isWithIntermediateResults</code> is used, the recognizer may
503 	    		 * invoke the callback with intermediate recognition results.
504 	    		 * 
505 	    		 * TODO specify whether stopRecord should return the "gathered" intermediate results, or just the last one
506 	    		 * 
507 	    		 * NOTE that not all implementation may support this feature.
508 	    		 * 
509 	    		 * @async
510 	    		 * 
511 	    		 * @param {Function} [successCallBack] OPTIONAL
512 	    		 * 			callback function that is triggered when a text result is available.
513 	    		 * 			The callback signature is:
514 	    		 * 				<code>callback(textResult)</code>
515 	    		 * @param {Function} [failureCallBack] OPTIONAL
516 	    		 * 			callback function that is triggered when an error occurred.
517 	    		 * 			The callback signature is:
518 	    		 * 				<code>callback(error)</code>
519 	    		 * @param {Boolean} [isWithIntermediateResults] OPTIONAL
520 	    		 * 			if <code>true</code>, the recognizer will return intermediate results
521 	    		 * 			by invoking the successCallback
522 	    		 * 
523 	    		 * @see #stopRecord
524 	    		 */
525     			startRecord: function(successCallBack,failureCallBack, isWithIntermediateResults){
526     				if(failureCallBack){
527     					failureCallBack("Audio Input: Speech Recognition (recording) is not supported.");
528     				}
529     				else {
530     					console.error("Audio Input: Speech Recognition (recording) is not supported.");
531     				}
532     			},
533     			/**
534 	    		 * Stops continuous speech recognition:
535 	    		 * 
536 	    		 * After {@link #startRecord} was called, invoking this function will stop the recognition
537 	    		 * process and return the result by invoking the <code>succesCallback</code>.
538 	    		 * 
539 	    		 * TODO specify whether stopRecord should return the "gathered" intermediate results, or just the last one
540 	    		 * 
541 	    		 * @async
542 	    		 * 
543 	    		 * @param {Function} [successCallBack] OPTIONAL
544 	    		 * 			callback function that is triggered when a text result is available.
545 	    		 * 			The callback signature is:
546 	    		 * 				<code>callback(textResult)</code>
547 	    		 * @param {Function} [failureCallBack] OPTIONAL
548 	    		 * 			callback function that is triggered when an error occurred.
549 	    		 * 			The callback signature is:
550 	    		 * 				<code>callback(error)</code>
551 	    		 * 
552 	    		 * @see #startRecord
553 	    		 */
554     			stopRecord: function(successCallBack,failureCallBack){
555     				if(failureCallBack){
556     					failureCallBack("Audio Input: Speech Recognition (recording) is not supported.");
557     				}
558     				else {
559     					console.error("Audio Input: Speech Recognition (recording) is not supported.");
560     				}
561     	   		},
562 
563     	   		/**
564     	   		 * Cancel currently active speech recognition.
565     	   		 * 
566     	   		 * Has no effect, if no recognition is active.
567     	   		 */
568     			cancelRecognition: function(successCallBack,failureCallBack){
569     	   			if(failureCallBack){
570     					failureCallBack("Audio Output: canceling Recognize Speech is not supported.");
571     				}
572     				else {
573     					console.error("Audio Output: canceling Recognize Speech is not supported.");
574     				}
575     			},
576 ///////////////////////////// audio output API: /////////////////////////////
577     	   		
578     			/**
579     			 * Play PCM audio data.
580     			 */
581     	   		playWAV: function(blob, onPlayedCallback, failureCallBack){
582     	   			if(failureCallBack){
583     					failureCallBack("Audio Output: play WAV audio is not supported.");
584     				}
585     				else {
586     					console.error("Audio Output: play WAV audio is not supported.");
587     				}
588     			},
589     			/**
590     			 * Play audio file from the specified URL.
591     			 */
592     			playURL: function(url, onPlayedCallback, failureCallBack){
593     	   			if(failureCallBack){
594     					failureCallBack("Audio Output: play audio from URL is not supported.");
595     				}
596     				else {
597     					console.error("Audio Output: play audio from URL is not supported.");
598     				}
599     			},
600     			/**
601     			 * Get an audio object for the audio file specified by URL.
602     			 * 
603     			 * The audio object exports the following functions:
604     			 * 
605     			 * <pre>
606     			 * play()
607     			 * stop()
608     			 * release()
609     			 * enable()
610     			 * disable()
611     			 * setVolume(number)
612     			 * getDuration()
613     			 * isPaused()
614     			 * isEnabled()
615     			 * </pre>
616     			 * 
617     			 * NOTE: the audio object should only be used, after the <code>onLoadedCallback</code>
618     			 *       was triggered.
619     			 * 
620     			 * @param {String} url
621     			 * @param {Function} [onPlayedCallback] OPTIONAL
622     			 * @param {Function} [failureCallBack] OPTIONAL
623     			 * @param {Function} [onLoadedCallBack] OPTIONAL
624     			 * 
625     			 * @returns {mmir.env.media.IAudio} the audio
626     			 * 
627     			 * @see {mmir.env.media.IAudio#_constructor}
628     			 */
629     			getURLAsAudio: function(url, onPlayedCallback, failureCallBack, onLoadedCallBack){
630     	   			if(failureCallBack){
631     					failureCallBack("Audio Output: create audio from URL is not supported.");
632     				}
633     				else {
634     					console.error("Audio Output: create audio from URL is not supported.");
635     				}
636     			},
637     			/**
638     			 * Get an empty audio object. This can be used as dummy or placeholder
639     			 * for a "real" audio object.
640     			 * 
641     			 * The audio object exports the following functions:
642     			 * 
643     			 * <pre>
644     			 * play()
645     			 * stop()
646     			 * release()
647     			 * enable()
648     			 * disable()
649     			 * setVolume(number)
650     			 * getDuration()
651     			 * isPaused()
652     			 * isEnabled()
653     			 * </pre>
654     			 * 
655     			 * Note:
656     			 * 
657     			 * <code>enable()</code> and <code>disable()</code> will set the internal
658     			 * enabled-state, which can be queried via <code>isEnabled()</code>.
659     			 * 
660     			 * <code>play()</code> and <code>stop()</code> will set the internal 
661     			 * playing-state, which can be queried via <code>isPaused()</code>
662     			 * (note however, that this empty audio does not actually play anything.
663     			 * 
664     			 * <code>setVolume()</code> sets the internal volume-value.
665     			 * 
666     			 * <code>getDuration()</code> will always return <code>0</code>.
667     			 * 
668     			 * 
669     			 * @returns {mmir.env.media.IAudio} the audio
670     			 * 
671     			 * @see {mmir.env.media.IAudio#_constructor}
672 				 */
673 				createEmptyAudio: function(){
674 					return {
675 						_enabled: true,
676 						_play: false,
677 						_volume: 1,
678 						play: function(){ this._play = true; },
679 						stop: function(){ this._play = true; },
680 						enable: function(){ this._enabled = true; },
681 						disable: function(){ this._enabled = false; },
682 						release: function(){ this._enabled = false; },
683 						setVolume: function(vol){ this._volume = vol; },
684 						getDuration: function(){ return 0; },
685 						isPaused: function(){ return !this._play; },
686 						isEnabled: function(){ return this._enabled; }
687 					};
688 				},
689 ///////////////////////////// text-to-speech API: /////////////////////////////
690     			
691     			/**
692     			 * Synthesizes ("read out loud") text.
693     			 * 
694     			 * @param {String|Array<String>|PlainObject} parameter
695     			 * 		if <code>String</code> or <code>Array</code> of <code>String</code>s
696     			 * 			  synthesizes the text of the String, for an Array: each entry is interpreted as "sentence";
697     			 * 				after each sentence, a short pause is inserted before synthesizing the
698     			 * 				the next sentence<br>
699     			 * 		for a <code>PlainObject</code>, the following properties should be used:
700     			 * 		<pre>{
701     			 * 			  text: string OR string Array, text that should be read aloud
702     			 * 			, pauseLength: OPTIONAL Length of the pauses between sentences in milliseconds
703     			 * 			, forceSingleSentence: OPTIONAL boolean, if true, a string Array will be turned into a single string
704     			 * 			, split: OPTIONAL boolean, if true and the text is a single string, it will be split using a splitter function
705     			 * 			, splitter: OPTIONAL function, replaces the default splitter-function. It takes a simple string as input and gives a string Array as output
706     			 * 		}</pre>
707     			 */
708     			textToSpeech: function(parameter, onPlayedCallback, failureCallBack){
709     	   			if(failureCallBack){
710     					failureCallBack("Audio Output: Text To Speech is not supported.");
711     				}
712     				else {
713     					console.error("Audio Output: Text To Speech is not supported.");
714     				}
715     			},
716     			/**
717     			 * Cancel current synthesis.
718     			 */
719     			cancelSpeech: function(successCallBack,failureCallBack){
720     	   			if(failureCallBack){
721     					failureCallBack("Audio Output: canceling Text To Speech is not supported.");
722     				}
723     				else {
724     					console.error("Audio Output: canceling Text To Speech is not supported.");
725     				}
726     			},
727     			
728 ///////////////////////////// ADDITIONAL (optional) functions: ///////////////////////////// 
729     			/**
730     			 * Set the volume for the speech synthesis (text-to-speech).
731     			 * 
732     			 * @param {Number} newValue
733     			 * 				TODO specify format / range
734     			 */
735     			setTextToSpeechVolume: function(newValue){
736     				console.error("Audio Output: set volume for Text To Speech is not supported.");
737 				}
738     			
739 
740 ///////////////////////////// MediaManager "managing" functions: ///////////////////////////// 
741     			/**
742     	    	 * Adds the handler-function for the event.
743     	    	 * 
744     	    	 * This function calls {@link #_notifyObservers} for the eventName with
745     	    	 * <code>actionType "added"</code>.
746     	    	 * 
747     	    	 * 
748     	    	 * Event names (and firing events) are specific to the loaded media plugins.
749     	    	 * 
750     	    	 * TODO list events that the default media-plugins support
751     	    	 *   * "miclevelchanged": fired by AudioInput plugins that support querying the microphone (audio input) levels
752     	    	 * 
753     	    	 * A plugin can tigger / fire events using the helper {@link #_fireEvent}
754     	    	 * of the MediaManager.
755     	    	 * 
756     	    	 * 
757     	    	 * Media plugins may observe registration / removal of listeners
758     	    	 * via {@link #_addListenerObserver} and {@link #_removeListenerObserver}.
759     	    	 * Or get and iterate over listeners via {@link #getListeners}.
760     	    	 * 
761     	    	 * 
762     	    	 * 
763     	    	 * 
764     	    	 * @param {String} eventName
765     	    	 * @param {Function} eventHandler
766     	    	 * 
767     	    	 * @function
768     	    	 */
769     			, addListener: addListenerImpl
770     			/**
771     	    	 * Removes the handler-function for the event.
772     	    	 * 
773     	    	 * Calls {@link #_notifyObservers} for the eventName with
774     	    	 * <code>actionType "removed"</code>, if the handler
775     	    	 * was actually removed.
776     	    	 * 
777     	    	 * @param {String} eventName
778     	    	 * @param {Function} eventHandler
779     	    	 * 
780     	    	 * @returns {Boolean}
781     	    	 * 		<code>true</code> if the handler function was actually 
782     	    	 * 		removed, and <code>false</code> otherwise.
783     	    	 * 
784     	    	 * @function
785     	    	 */
786     			, removeListener: removeListenerImpl
787     			/** 
788     			 * @function
789     			 * @see #addListener
790     			 */
791     			, on:addListenerImpl
792     			/** 
793     			 * @function
794     			 * @see #removeListener
795     			 */
796     			, off: removeListenerImpl
797     			/**
798     			 * Get list of registered listeners / handlers for an event.
799     			 * 
800     			 * @returns {Array<Function>} of event-handlers. 
801     			 * 				Empty, if there are no event handlers for eventName
802     			 */
803     			, getListeners: function(eventName){
804     				var list = listener.get(eventName);
805     				if(list && list.length){
806     					//return copy of listener-list
807     					return list.slice(0,list.length);
808     				}
809     				return [];
810     			}
811     			/**
812     			 * Check if at least one listener / handler is  registered for the event.
813     			 * 
814     			 * @returns {Boolean} <code>true</code> if at least 1 handler is registered 
815     			 * 					  for eventName; otherwise <code>false</code>.
816     			 */
817     			, hasListeners: function(eventName){
818     				var list = listener.get(eventName);
819     				return list && list.length > 0;
820     			}
821     			/**
822     			 * Helper for firing / triggering an event.
823     			 * This should only be used by media plugins (that handle the eventName).
824     			 * 
825     	    	 * @param {String} eventName
826     	    	 * @param {Array} argsArray
827     	    	 * 					the list of arguments with which the event-handlers
828     	    	 * 					will be called.
829     	    	 * @protected
830     			 */
831     			, _fireEvent: function(eventName, argsArray){
832     				var list = listener.get(eventName);
833     				if(list && list.length){
834     					for(var i=0, size = list.length; i < size; ++i){
835     						list[i].apply(this, argsArray);
836     					}
837     				}
838     			}
839     			/** 
840     			 * Helper for notifying listener-observers about changes (adding/removing listeners).
841     			 * This should only be used by media plugins (that handle the eventName).
842     			 * 
843     			 * @param {String} eventName
844     			 * @param {String} actionType
845     			 * 					the change-type that occurred for the event/event-handler:
846     			 * 					one of <code>["added" | "removed"]</code>.
847     	    	 * @param {Function} eventHandler
848     	    	 * 					the event-handler function that has changed.
849     	    	 * 
850     			 * @protected
851     			 */
852     			, _notifyObservers: function(eventName, actionType, eventHandler){//actionType: one of "added" | "removed"
853     				var list = listenerObserver.get(eventName);
854     				if(list && list.length){
855     					for(var i=0, size = list.length; i < size; ++i){
856     						list[i](actionType,eventHandler);
857     					}
858     				}
859     			}
860     			/**
861     			 * Add an observer for registration / removal of event-handler.
862     			 * 
863     			 * The observer gets notified,when handlers are registered / removed for the event.
864     			 * 
865     			 * The observer-callback function will be called with the following
866     			 * arguments
867     			 * 
868     			 * <code>(eventName, ACTION_TYPE, eventHandler)</code>
869     			 * where
870     			 * <ul>
871     			 *  <li>eventName: String the name of the event that should be observed</li>
872     			 *  <li>ACTION_TYPE: the type of action: "added" if the handler was 
873     			 *      registered for the event, "removed" if the the handler was removed
874     			 *  </li>
875     			 *  <li>eventHandler: the handler function that was registered or removed</li>
876     			 * </ul> 
877     			 * 
878     	    	 * @param {String} eventName
879     	    	 * @param {Function} observerCallback
880     			 */
881     			, _addListenerObserver: function(eventName, observerCallback){
882     				var list = listenerObserver.get(eventName);
883     				if(!list){
884     					list = [observerCallback];
885     					listenerObserver.put(eventName, list);
886     				}
887     				else {
888     					list.push(observerCallback);
889     				}
890     			}
891     			
892     			, _removeListenerObserver: function(eventName, observerCallback){
893     				var isRemoved = false;
894     				var list = listenerObserver.get(eventName);
895     				if(list){
896     					var size = list.length;
897     					for(var i = size - 1; i >= 0; --i){
898     						if(list[i] ===  observerCallback){
899     							
900     							//move all handlers after i by 1 index-position ahead:
901     							for(var j = size - 1; j > i; --j){
902     								list[j-1] = list[j];
903     							}
904     							//remove last array-element
905     							list.splice(size-1, 1);
906     							
907     							isRemoved = true;
908     							break;
909     						}
910     					}
911     				}
912     				return isRemoved;
913     			}
914     			/**
915     			 * Executes function <code>funcName</code> in "sub-module" <code>ctx</code>
916     			 * with arguments <code>args</code>.
917     			 * 
918     			 * <p>
919     			 * If there is no <code>funcName</code> in "sub-module" <code>ctx</code>,
920     			 * then <code>funcName</code> from the "main-module" (i.e. from the MediaManager
921     			 * instance itself) will be used.
922     			 * 
923     			 * @param {String} ctx
924     			 * 			the execution context, i.e. "sub-module", in which to execute funcName.<br>
925     			 * 			If <code>falsy</code>, the "root-module" will used as execution context.
926     			 * @param {String} funcName
927     			 * 			the function name
928     			 * @param {Array} args
929     			 * 			the arguments for function "packaged" in an array
930     			 * 
931     			 * @throws {ReferenceError}
932     			 * 			if <code>funcName</code> does not exist in the requested Execution context.<br>
933     			 * 			Or if <code>ctx</code> is not <code>falsy</code> but there is no valid execution
934     			 * 			context <code>ctx</code> in MediaManager.
935     			 * 
936     			 * @example
937     			 * 
938     			 *  //same as mmir.MediaManager.ctx.android.textToSpeech("...", function...):
939     			 * 	mmir.MediaManager.perform("android", "textToSpeech", ["some text to read out loud",
940     			 * 		function onFinished(){ console.log("finished reading."); }
941     			 * 	]);
942     			 * 
943     			 *  //same as mmir.MediaManager.textToSpeech("...", function...)
944     			 *  //... IF the defaultExecId is falsy 
945     			 *  //    (i.e. un-changed or set to falsy value via setDefaultExec())
946     			 * 	mmir.MediaManager.perform(null, "textToSpeech", ["some text to read out loud",
947     			 * 		function onFinished(){ console.log("finished reading."); }
948     			 * 	]);
949     			 * 
950     			 */
951     			, perform: function(ctx, funcName, args){
952     				
953     				var func;
954     				
955     				if(!ctx){
956     					
957     					if(defaultExecId && typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
958     						func =  this.ctx[defaultExecId][funcName];
959     					}
960     					
961         				
962     				}
963     				else if(ctx && typeof this.ctx[ctx] !== 'undefined') {
964 
965         				if(typeof this.ctx[ctx][funcName] !== 'undefined') {
966         					func = this.ctx[ctx][funcName];
967         				}
968         				
969     				} else {
970     					throw new ReferenceError('There is no context for "'+ctx+'" in MediaManager.ctx!');///////////////////////////// EARLY EXIT ////////////////////
971     				}
972     				
973     				
974     				if(!func){
975 						func = this[funcName];
976     				}
977     				
978     				
979     				if(typeof func === 'undefined'){
980     					throw new ReferenceError('There is no function '+funcName+' in MediaManager'+(ctx? ' context ' + ctx : (defaultExecId? ' default context ' + defaultExecId : '')) + '!');///////////////////////////// EARLY EXIT ////////////////////
981     				}
982     				
983     				return func.apply(this, args);
984     			}
985     			/**
986     			 * Returns function <code>funcName</code> from "sub-module" <code>ctx</code>.
987     			 * 
988     			 * <p>
989     			 * If there is no <code>funcName</code> in "sub-module" <code>ctx</code>,
990     			 * then <code>funcName</code> from the "main-module" (i.e. from the MediaManager
991     			 * instance itself) will be returned.
992     			 * 
993     			 * <p>
994     			 * NOTE that the returned functions will always execute within the context of the
995     			 * MediaManager instance (i.e. <code>this</code> will refer to the MediaManager instance).
996     			 * 
997     			 * 
998     			 * @param {String} ctx
999     			 * 			the execution context, i.e. "sub-module", in which to execute funcName.<br>
1000     			 * 			If <code>falsy</code>, the "root-module" will used as execution context.
1001     			 * @param {String} funcName
1002     			 * 			the function name
1003     			 * 
1004     			 * @throws {ReferenceError}
1005     			 * 			if <code>funcName</code> does not exist in the requested Execution context.<br>
1006     			 * 			Or if <code>ctx</code> is not <code>falsy</code> but there is no valid execution
1007     			 * 			context <code>ctx</code> in MediaManager.
1008     			 * 
1009     			 * @example
1010     			 * 
1011     			 *  //same as mmir.MediaManager.ctx.android.textToSpeech("...", function...):
1012     			 * 	mmir.MediaManager.getFunc("android", "textToSpeech")("some text to read out loud",
1013     			 * 		function onFinished(){ console.log("finished reading."); }
1014     			 * 	);
1015     			 * 
1016     			 *  //same as mmir.MediaManager.textToSpeech("...", function...):
1017     			 *  //... IF the defaultExecId is falsy 
1018     			 *  //    (i.e. un-changed or set to falsy value via setDefaultExec())
1019     			 * 	mmir.MediaManager.getFunc(null, "textToSpeech")("some text to read out loud",
1020     			 * 		function onFinished(){ console.log("finished reading."); }
1021     			 * 	);
1022     			 * 
1023     			 */
1024     			, getFunc: function(ctx, funcName){//this function performs worse for the "root execution" context, than perform(), since an additional wrapper function must be created
1025     				
1026     				var isRoot = false;
1027     				
1028     				if(!ctx){
1029     					
1030     					if(!defaultExecId){
1031     						isRoot = true;
1032     					}
1033     					else {
1034     						if(typeof this.ctx[defaultExecId][funcName] !== 'undefined'){
1035     							return this.ctx[defaultExecId][funcName];/////////// EARLY EXIT //////////////////
1036     						}
1037     						else {
1038         						isRoot = true;
1039     						}
1040     					}
1041     				}
1042     				
1043     				if(ctx && typeof this.ctx[ctx] !== 'undefined'){
1044 	    				if(!isRoot && typeof this.ctx[ctx][funcName] !== 'undefined'){
1045 	    					return this.ctx[ctx][funcName];///////////////////////////// EARLY EXIT ////////////////////
1046 	    				}
1047     				}
1048     				else {
1049         				throw new ReferenceError('There is no context for "'+ctx+'" in MediaManager.ctx!');///////////////////////////// EARLY EXIT ////////////////////
1050     				}
1051     				
1052     				//-> return the implementation of the "root execution context"
1053     				
1054     				if(typeof instance[funcName] === 'undefined'){
1055     					throw new ReferenceError('There is no function '+funcName+' in MediaManager'+(ctx? ' context ' + ctx : (defaultExecId? ' default context ' + defaultExecId : '')) + '!');///////////////////////////// EARLY EXIT ////////////////////
1056     				}
1057     				
1058 					//need to create proxy function, in order to preserve correct execution context
1059 					// (i.e. the MediaManager instance)
1060 					return function() {
1061 						return instance[funcName].apply(instance, arguments);
1062 					};
1063     				
1064     			},
1065     			/**
1066     			 * Set the default execution context.
1067     			 * 
1068     			 * If not explicitly set, or set to a <code>falsy</code> value,
1069     			 * then the "root" execution context is the default context.
1070     			 * 
1071     			 * @param {String} ctxId
1072     			 * 		the new default excution context for loaded media modules
1073     			 * 		(if <code>falsy</code> the default context will be the "root context")
1074     			 * 
1075     			 * @throws {ReferenceError}
1076     			 * 			if <code>ctxId</code> is no valid context
1077     			 * 
1078     			 * @example
1079     			 * 
1080     			 * //if context "nuance" exists:
1081     			 * mmir.MediaManager.setDefaultCtx("nuance")
1082     			 * 
1083     			 * // -> now the following calls are equal to mmir.MediaManager.ctx.nuance.textToSpeech("some text")
1084     			 * mmir.MediaManager.perform(null, "textToSpeech", ["some text"]);
1085     			 * mmir.MediaManager.getFunc(null, "textToSpeech")("some text");
1086     			 * 
1087     			 * //reset to root context:
1088     			 * mmir.MediaManager.setDefaultCtx("nuance");
1089     			 * 
1090     			 * // -> now the following call is equal to mmir.MediaManager.textToSpeech("some text") again
1091     			 * mmir.MediaManager.perform("textToSpeech", ["some text"]);
1092     			 * 
1093     			 */
1094     			setDefaultCtx: function(ctxId){
1095     				if(ctxId && typeof instance.ctx[ctxId] === 'undefined'){
1096     					throw new ReferenceError('There is no context for "'+ctxId+'" in MediaManager.ctx!');///////////////////////////// EARLY EXIT ////////////////////
1097     				}
1098     				defaultExecId = ctxId;
1099     			},
1100     			/**
1101     	    	 * This function is called by media plugin implementations (i.e. modules)
1102     	    	 * to indicate that they are preparing something and that the user should
1103     	    	 * wait.
1104     	    	 * 
1105     	    	 * <p>
1106     	    	 * The actual implementation for <code>_preparing(String)</code> is given by
1107     	    	 * {@link #waitReadyImpl}.preparing (if not set, then calling <code>_preparing(String)</code>
1108     	    	 * will have no effect.
1109     	    	 * 
1110     	    	 * @param {String} moduleName
1111     	    	 * 			the module name from which the function was invoked
1112     	    	 * 
1113     	    	 * @function
1114     	    	 * @protected
1115     	    	 * 
1116     	    	 * @see #waitReadyImpl
1117     	    	 * @see #_ready
1118     	    	 */
1119     			_preparing: function(moduleName){
1120     				if(this.waitReadyImpl && this.waitReadyImpl.preparing){
1121     					this.waitReadyImpl.preparing(moduleName);
1122     				}
1123     			},
1124     			/**
1125     	    	 * This function is called by media plugin implementations (i.e. modules)
1126     	    	 * to indicate that they are now ready and that the user can start interacting.
1127     	    	 * 
1128     	    	 * <p>
1129     	    	 * The actual implementation for <code>_ready(String)</code> is given by the
1130     	    	 * {@link #waitReadyImpl} implementation (if not set, then calling <code>_ready(String)</code>
1131     	    	 * will have no effect.
1132     	    	 * 
1133     	    	 * @param {String} moduleName
1134     	    	 * 			the module name from which the function was invoked
1135     	    	 * 
1136     	    	 * @function
1137     	    	 * @protected
1138     	    	 * 
1139     	    	 * @see #waitReadyImpl
1140     	    	 * @see #_ready
1141     	    	 */
1142     			_ready: function(moduleName){
1143     				if(this.waitReadyImpl && this.waitReadyImpl.ready){
1144     					this.waitReadyImpl.ready(moduleName);
1145     				}
1146     			}
1147     			
1148     	};//END: return{...
1149     	
1150     };//END: constructor(){...
1151     
1152     
1153     //has 2 default configuarions:
1154     // if isCordovaEnvironment TRUE: use 'cordova' config
1155     // if FALSEy: use 'browser' config
1156     //
1157     // NOTE: this setting/paramater is overwritten, if the configuration has a property 'mediaPlugins' set!!!
1158     /**
1159      * HELPER for init-function:
1160      * 	determines, which plugins (i.e. files) should be loaded.
1161      * 
1162      * <p>
1163      * has 2 default configuarions:<br>
1164      * if isCordovaEnvironment TRUE: use 'cordova' config<br>
1165      * if FALSEy: use 'browser' config
1166      * <p>
1167      * OR<br>
1168      * loads the list for the current environment (cordova or browser) that is set in configuration.json via <br>
1169      * <pre>
1170      * "mediaManager": {
1171      * 		"cordova": [...],
1172      * 		"browser": [...]
1173      * } 
1174      * </pre>
1175      * 
1176      * <p>
1177      * Each entry may either be a String (file name of the plugin) or an Object with
1178      * properties
1179      * <pre>
1180      * 	mod: <file name for the module> //String
1181      * 	ctx: <an ID for the module>     //String
1182      * </pre>
1183      * 
1184      * If <b>String</b>: the functions of the loaded plugin will be attached to the MediaManager instance:
1185      * <code>mmir.MediaManager.thefunction()</code>
1186      * <br>
1187      * If <b>{mod: plugin,ctx: theContextId}</b>: the functions of the loaded plugin will be attached to the "sub-module"
1188      * to the MediaManager instance <em>(NOTE the execution context of the function will remain within 
1189      * the MediaManager instance, i.e. <code>this</code> will still refer to the MediaManager instance)</em>:
1190      * <code>mmir.MediaManager.theId.thefunction()</code>
1191      * 
1192      * <p>
1193      * If plugins are loaded with an ID, you can use 
1194      * <code>mmir.MediaManager.getFunc(ctxId, func)(the, arguments)</code> or
1195      * <code>mmir.MediaManager.perform(ctxId, func, [the, arguments])</code>:
1196      * If the "sub-module" ctxId does not have the function func (i.e. no MediaManager.ctx.ctxId.func exists), then the default function
1197      * in MediaManager will be executed (i.e.  MediaManager.func(the, arguments) ).
1198      * 
1199      * 
1200      * @returns {Array<String>}
1201      * 				the list of plugins which should be loaded
1202      * 
1203 	 * @private
1204 	 * @memberOf mmir.MediaManager#
1205      */
1206     function getPluginsToLoad(configurationName){//if configurationName is omitted, then it is automatically detected
1207     	
1208     	var env = configurationName;
1209     	var pluginArray = [];
1210 
1211     	var dataFromConfig = configurationManager.get('mediaManager.plugins', true);
1212     	
1213     	if(!env){
1214     		
1215     		var envSetting = constants.getEnv();
1216     		if(envSetting === 'cordova'){
1217     			
1218     			//try to find config for specific cordova-env
1219     			envSetting = constants.getEnvPlatform();
1220     			if(envSetting !== 'default'){
1221     				
1222     				//if there is a config present for the specific envSetting, then use it:
1223     				if((dataFromConfig && dataFromConfig[envSetting]) || _defaultPlugins[envSetting]){
1224     	    			//if there is a config present for the envSetting, then use it:
1225     					env = envSetting;
1226     				}
1227     				
1228     			}
1229     			
1230     		} else if(dataFromConfig && dataFromConfig[envSetting]){
1231     			//if there is a non-default config present for the envSetting, then use it
1232     			//  if there is a deault config, then the env will also be a default one 
1233     			//  -> this will be detected by default-detection-mechanism below
1234 				env = envSetting;
1235 			}
1236     		
1237     		//if there is no env value yet, use default criteria browser vs. cordova env:
1238     		if(!env){
1239     			
1240 	    		var isCordovaEnvironment = ! constants.isBrowserEnv();
1241 	        	if (isCordovaEnvironment) {
1242 	        		env = 'cordova';
1243 	        	} else {
1244 	        		env = 'browser';
1245 	        	}
1246     		}
1247     		
1248     		//ASSERT env is non-empty String
1249     	}
1250         
1251     	if (dataFromConfig && dataFromConfig[env]){
1252     		pluginArray = pluginArray.concat(dataFromConfig[env]);
1253     	} else{
1254     		pluginArray = pluginArray.concat(_defaultPlugins[env]);
1255     	}
1256     	
1257     	return pluginArray;
1258     }
1259     /**
1260      * 
1261 	 * @private
1262 	 * @memberOf mmir.MediaManager#
1263      */
1264     function loadAllPlugins(pluginArray, successCallback,failureCallback){
1265     	
1266     	if (pluginArray == null || pluginArray.length<1){
1267     		if (successCallback) {
1268     			successCallback();
1269     		}
1270     		return;
1271     	}
1272     	
1273     	var ctxId;
1274     	var newPluginName = pluginArray.pop();
1275     	if(newPluginName.ctx && newPluginName.mod){
1276     		ctxId = newPluginName.ctx;
1277     		newPluginName = newPluginName.mod;
1278     	}
1279     	
1280     	loadPlugin(newPluginName, function (){
1281     		console.log(newPluginName+' loaded!');
1282     		loadAllPlugins(pluginArray,successCallback, failureCallback);},
1283     		failureCallback,
1284     		ctxId
1285     	);
1286     }
1287     	
1288     
1289     var _stub = {
1290     	
1291     	/** @scope MediaManager.prototype */
1292     	
1293     	//TODO add for backwards compatibility?:
1294 //    	create : function(){ return this.init.apply(this, arguments); },
1295     	
1296         /**
1297          * Object containing the instance of the class {{#crossLink "audioInput"}}{{/crossLink}} 
1298          * 
1299          * If <em>listenerList</em> is provided, each listener will be registered after the instance
1300          * is initialized, but before media-plugins (i.e. environment specfific implementations) are
1301          * loaded.
1302          * Each entry in the <em>listenerList</em> must have fields <tt>name</tt> (String) and
1303          * <tt>listener</tt> (Function), where
1304          * <br>
1305          * name: is the name of the event
1306          * <br>
1307          * listener: is the listener implementation (the signature/arguments of the listener function depends
1308          * 			 on the specific event for which the listener will be registered)
1309          *  
1310          * 
1311          * @method init
1312          * @param {Function} [successCallback] OPTIONAL
1313          * 				 callback that gets triggered after the MediaManager instance has been initialized.
1314          * @param {Function} [failureCallback] OPTIONAL
1315          * 				 a failure callback that gets triggered if an error occurs during initialization.
1316          * @param {Array<Object>} [listenerList] OPTIONAL
1317          * 				 a list of listeners that should be registered, where each entry is an Object
1318          * 				 with properties:
1319          * 				 <pre>
1320          * 					{
1321          * 						name: String the event name,
1322          * 						listener: Function the handler function
1323          * 					}
1324          * 				 </pre>
1325          * @return {Object}
1326          * 				an Deferred object that gets resolved, after the {@link mmir.MediaManager}
1327          * 				has been initialized.
1328          * @public
1329          * 
1330          * @memberOf mmir.MediaManager.prototype
1331          * 
1332          */
1333         init: function(successCallback, failureCallback, listenerList){
1334         	
1335         	var defer = jQuery.Deferred();
1336         	var deferredSuccess = function(){
1337     			defer.resolve();
1338     		};
1339         	var deferredFailure = function(){
1340     			defer.reject();
1341     		};
1342         	
1343     		
1344         	if(successCallback){
1345         		defer.done(successCallback);
1346         	}
1347         	
1348         	if(deferredFailure){
1349         		defer.fail(failureCallback);
1350         	}
1351         	
1352         	
1353             if (instance === null) {
1354             	jQuery.extend(true,this,constructor());
1355                 instance = this;
1356                 
1357                 if(listenerList){
1358                 	for(var i=0, size = listenerList.length; i < size; ++i){
1359                 		instance.addListener(listenerList[i].name, listenerList[i].listener);
1360                 	}
1361                 }
1362                 
1363             	var pluginConfig = getPluginsToLoad();
1364                 loadAllPlugins(pluginConfig,deferredSuccess, deferredFailure);
1365 
1366             }
1367             else if(listenerList){
1368             	for(var i=0, size = listenerList.length; i < size; ++i){
1369             		instance.addListener(listenerList[i].name, listenerList[i].listener);
1370             	}
1371             }
1372             
1373             return defer.promise(this);
1374         },
1375         /**
1376          * Same as {@link #init}.
1377          * 
1378          * @deprecated access MediaManger directly via <code>mmir.MediaManager.someFunction</code> - <em>&tl;internal: for initialization use <code>init()</code> instead></em>
1379          * 
1380          * @function
1381          * @public
1382          * @memberOf mmir.MediaManager.prototype
1383          */
1384         getInstance: function(){
1385             return this.init(null, null);
1386         },
1387         /**
1388          * loads a file. If the file implements a function initialize(f)
1389          * where the function f is called with a set of functions e, then those functions in e 
1390          * are added to the visibility of audioInput, and will from now on be applicable by calling
1391          * mmir.MediaManager.<function name>().
1392          * 
1393          * @deprecated do not use.
1394          * @function
1395          * @protected
1396          * @memberOf mmir.MediaManager.prototype
1397          * 
1398          */
1399     	loadFile: function(filePath,successCallback, failureCallback, execId){
1400     		if (instance=== null) {
1401     			this.init();
1402     		}
1403     		
1404     		loadPlugin(filePath,sucessCallback, failureCallback, execId);
1405 			
1406     	}
1407     };
1408     
1409     return _stub;
1410     
1411 });//END: define(..., function(){...
1412