1/*****************************************************
2*
3*  Copyright 2009 Adobe Systems Incorporated.  All Rights Reserved.
4*
5*****************************************************
6*  The contents of this file are subject to the Mozilla Public License
7*  Version 1.1 (the "License"); you may not use this file except in
8*  compliance with the License. You may obtain a copy of the License at
9*  http://www.mozilla.org/MPL/
10*
11*  Software distributed under the License is distributed on an "AS IS"
12*  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
13*  License for the specific language governing rights and limitations
14*  under the License.
15*
16*
17*  The Initial Developer of the Original Code is Adobe Systems Incorporated.
18*  Portions created by Adobe Systems Incorporated are Copyright (C) 2009 Adobe Systems
19*  Incorporated. All Rights Reserved.
20*
21*****************************************************/
22package org.osmf.media
23{
24	import __AS3__.vec.Vector;
25
26	import flash.events.EventDispatcher;
27	import flash.utils.Dictionary;
28
29	import org.osmf.elements.ProxyElement;
30	import org.osmf.events.MediaFactoryEvent;
31	import org.osmf.events.PluginManagerEvent;
32	import org.osmf.media.pluginClasses.PluginManager;
33	import org.osmf.utils.OSMFStrings;
34
35	/**
36	 * Dispatched when the MediaFactory has successfully loaded a plugin.
37	 *
38	 * @eventType org.osmf.events.MediaFactoryEvent.PLUGIN_LOAD
39	 *
40	 *  @langversion 3.0
41	 *  @playerversion Flash 10
42	 *  @playerversion AIR 1.5
43	 *  @productversion OSMF 1.0
44	 */
45	[Event(name="pluginLoad", type="org.osmf.events.MediaFactoryEvent")]
46
47	/**
48	 * Dispatched when the MediaFactory has failed to load a plugin due to an error.
49	 *
50	 * @eventType org.osmf.events.MediaFactoryEvent.PLUGIN_LOAD_ERROR
51	 *
52	 *  @langversion 3.0
53	 *  @playerversion Flash 10
54	 *  @playerversion AIR 1.5
55	 *  @productversion OSMF 1.0
56	 */
57	[Event(name="pluginLoadError", type="org.osmf.events.MediaFactoryEvent")]
58
59	/**
60	 * Dispatched when the MediaFactory has created a MediaElement.
61	 *
62	 * @eventType org.osmf.events.MediaFactoryEvent.MEDIA_ELEMENT_CREATE
63	 *
64	 *  @langversion 3.0
65	 *  @playerversion Flash 10
66	 *  @playerversion AIR 1.5
67	 *  @productversion OSMF 1.0
68	 */
69	[Event(name="mediaElementCreate", type="org.osmf.events.MediaFactoryEvent")]
70
71	/**
72	 * MediaFactory represents a factory class for media elements.
73	 *
74	 * <p>The factory operation takes a MediaResourceBase as input and produces a MediaElement
75	 * as output.</p>
76	 * <p>The MediaFactory maintains a list of MediaFactoryItem objects,
77	 * each of which encapsulates all the information necessary to create
78	 * a specific MediaElement. The MediaFactory relies on
79	 * the canHandleResourceFunction method of each MediaFactoryItem to find a
80	 * MediaFactoryItem object that can handle the specified MediaResourceBase.</p>
81	 *
82	 * <p>The factory interface also exposes methods for querying for specific MediaFactoryItem
83	 * objects, and for loading plugins (which hold MediaFactoryItem objects).</p>
84	 *
85	 * @see MediaFactoryItem
86	 * @see MediaResourceBase
87	 * @see MediaElement
88	 *
89	 *  @langversion 3.0
90	 *  @playerversion Flash 10
91	 *  @playerversion AIR 1.5
92	 *  @productversion OSMF 1.0
93	 *
94	 *  @includeExample MediaFactoryExample.as -noswf
95	 */
96	public class MediaFactory extends EventDispatcher
97	{
98		/**
99		 * Constructor.
100		 *
101		 *  @langversion 3.0
102		 *  @playerversion Flash 10
103		 *  @playerversion AIR 1.5
104		 *  @productversion OSMF 1.0
105		 */
106		public function MediaFactory()
107		{
108			super();
109
110			allItems = new Dictionary();
111		}
112
113		/**
114		 * Adds the specified MediaFactoryItem to the factory.
115		 * After the MediaFactoryItem has been added, for any MediaResourceBase
116		 * that this MediaFactoryItem can handle, the factory will be able to create
117		 * the corresponding media element.
118		 *
119		 * If a MediaFactoryItem with the same ID already exists in this
120		 * factory, the new MediaFactoryItem object replaces it.
121		 *
122		 * @param item The MediaFactoryItem to add.
123		 *
124		 * @throws ArgumentError If the argument is <code>null</code> or if the argument
125		 * has a <code>null</code> ID field.
126		 *
127		 *  @langversion 3.0
128		 *  @playerversion Flash 10
129		 *  @playerversion AIR 1.5
130		 *  @productversion OSMF 1.0
131		 */
132		public function addItem(item:MediaFactoryItem):void
133		{
134			if (item == null || item.id == null)
135			{
136				throw new ArgumentError(OSMFStrings.getString(OSMFStrings.INVALID_PARAM));
137			}
138
139			var items:Vector.<MediaFactoryItem> = findOrCreateItems(item.type);
140
141			// Make sure to overwrite any duplicate.
142			var existingIndex:int = getIndexOfItem(item.id, items);
143			if (existingIndex != -1)
144			{
145				items[existingIndex] = item;
146			}
147			else
148			{
149				items.push(item);
150			}
151		}
152
153		/**
154		 * Removes the specified MediaFactoryItem from the factory.
155		 *
156		 * If no such MediaFactoryItem exists in this factory, does nothing.
157		 *
158		 * @param item The MediaFactoryItem to remove.
159		 *
160		 * @throws ArgumentError If the argument is <code>null</code> or if the argument
161		 * has a <code>null</code> ID field.
162		 *
163		 *  @langversion 3.0
164		 *  @playerversion Flash 10
165		 *  @playerversion AIR 1.5
166		 *  @productversion OSMF 1.0
167		 */
168		public function removeItem(item:MediaFactoryItem):void
169		{
170			if (item == null || item.id == null)
171			{
172				throw new ArgumentError(OSMFStrings.getString(OSMFStrings.INVALID_PARAM));
173			}
174
175			var items:Vector.<MediaFactoryItem> = allItems[item.type];
176			if (items != null)
177			{
178				var existingIndex:int = items.indexOf(item);
179				if (existingIndex != -1)
180				{
181					items.splice(existingIndex, 1);
182				}
183			}
184		}
185
186		/**
187		 * The number of MediaFactoryItems managed by the factory.
188		 *
189		 *  @langversion 3.0
190		 *  @playerversion Flash 10
191		 *  @playerversion AIR 1.5
192		 *  @productversion OSMF 1.0
193		 */
194		public function get numItems():int
195		{
196			var numItems:int = 0;
197
198			for each (var type:String in MediaFactoryItemType.ALL_TYPES)
199			{
200				var items:Vector.<MediaFactoryItem> = allItems[type];
201				if (items != null)
202				{
203					numItems += items.length;
204				}
205			}
206
207			return numItems;
208		}
209
210		/**
211		 * Gets the MediaFactoryItem at the specified index.
212		 *
213		 * @param index The index in the list from which to retrieve the MediaFactoryItem.
214		 *
215		 * @return The MediaFactoryItem at that index or <code>null</code> if there is none.
216		 *
217		 *  @langversion 3.0
218		 *  @playerversion Flash 10
219		 *  @playerversion AIR 1.5
220		 *  @productversion OSMF 1.0
221		 */
222		public function getItemAt(index:int):MediaFactoryItem
223		{
224			var result:MediaFactoryItem = null;
225
226			if (index >= 0)
227			{
228				for each (var type:String in MediaFactoryItemType.ALL_TYPES)
229				{
230					var items:Vector.<MediaFactoryItem> = allItems[type];
231					if (items != null)
232					{
233						if (index < items.length)
234						{
235							result = items[index];
236							break;
237						}
238						else
239						{
240							// Not in this list, try the next.
241							index -= items.length;
242						}
243					}
244				}
245			}
246
247			return result;
248		}
249
250		/**
251		 * Returns the MediaFactoryItem with the specified ID or <code>null</code> if the
252		 * specified MediaFactoryItem does not exist in this factory.
253		 *
254		 * @param The ID of the MediaFactoryItem to retrieve.
255		 *
256		 * @return The MediaFactoryItem with the specified ID or <code>null</code> if the specified
257		 * MediaFactoryItem does not exist in this factory.
258		 *
259		 *  @langversion 3.0
260		 *  @playerversion Flash 10
261		 *  @playerversion AIR 1.5
262		 *  @productversion OSMF 1.0
263		 */
264		public function getItemById(id:String):MediaFactoryItem
265		{
266			var result:MediaFactoryItem = null;
267
268			for each (var type:String in MediaFactoryItemType.ALL_TYPES)
269			{
270				var items:Vector.<MediaFactoryItem> = allItems[type];
271				if (items != null)
272				{
273					var existingIndex:int = getIndexOfItem(id, items);
274					if (existingIndex != -1)
275					{
276						result = items[existingIndex];
277						break;
278					}
279				}
280			}
281
282			return result;
283		}
284
285		/**
286		 * Load a plugin identified by the specified resource. The MediaFactory will not
287		 * reload the plugin if it has already been loaded. Upon successful loading, the
288		 * MediaFactoryItems within the plugin's PluginInfo property will be added to
289		 * this MediaFactory, and a MediaFactoryEvent.PLUGIN_LOAD event will be dispatched.
290		 * OtherwiseIf the load fails, a MediaFactoryEvent.PLUGIN_LOAD_ERROR event will be
291		 * dispatched.
292		 *
293		 * @param resource MediaResourceBase representing the plugin.  For remote (dynamic)
294		 * plugins, use an URLResource pointing to the remote SWF to load.  For local
295		 * (static) plugins, use a PluginInfoResource.
296		 *
297		 * @throws ArgumentError If resource is null or resource is neither an URLResource
298		 * nor a PluginInfoResource.
299		 *
300		 *
301		 *  @langversion 3.0
302		 *  @playerversion Flash 10
303		 *  @playerversion AIR 1.5
304		 *  @productversion OSMF 1.0
305		 */
306		public function loadPlugin(resource:MediaResourceBase):void
307		{
308			createPluginManager();
309
310			pluginManager.loadPlugin(resource);
311		}
312
313		/**
314		 * Returns a MediaElement that can be created based on the specified
315		 * MediaResourceBase.
316		 * <p>Returns <code>null</code> if the factory cannot
317		 * find a MediaFactoryItem object
318		 * capable of creating such a MediaElement in this factory.</p>
319		 *
320		 * @param resource The MediaResourceBase for which a corresponding
321		 * MediaElement should be created.
322		 *
323		 * @return The MediaElement that was created or <code>null</code> if no such
324		 * MediaElement could be created from the resource.
325		 *
326		 *  @langversion 3.0
327		 *  @playerversion Flash 10
328		 *  @playerversion AIR 1.5
329		 *  @productversion OSMF 1.0
330		 */
331		public function createMediaElement(resource:MediaResourceBase):MediaElement
332		{
333			// Make sure we have a plugin manager before creating a MediaElement, so
334			// that it will catch the mediaElementCreate event.
335			createPluginManager();
336
337			// Note that proxies are resolved before creation callbacks are called:
338			// if a media element is proxied, then the creation callback will be invoked
339			// with the root proxy, not the wrapped media element.
340			//
341
342			// We attempt to create a MediaElement of the STANDARD type.
343			var mediaElement:MediaElement = createMediaElementByResource(resource, MediaFactoryItemType.STANDARD);
344			if (mediaElement != null)
345			{
346				var proxyElement:MediaElement =
347						createMediaElementByResource
348							( mediaElement.resource
349							, MediaFactoryItemType.PROXY
350							, mediaElement			/* element to wrap */
351							);
352
353				// If we have a corresponding ProxyElement, then instead of
354				// returning the STANDARD MediaElement, we instead return that
355				// PROXY element as the wrapper for the STANDARD element.
356				mediaElement = (proxyElement != null ? proxyElement : mediaElement);
357
358				// Inform clients of the creation.
359				dispatchEvent
360					( new MediaFactoryEvent
361						( MediaFactoryEvent.MEDIA_ELEMENT_CREATE
362						, false
363						, false
364						, null
365						, mediaElement
366						)
367					);
368			}
369
370			return mediaElement;
371		}
372
373		// Protected
374		//
375
376		/**
377		 * Returns the most appropriate MediaFactoryItem for the specified resource
378		 * out of the MediaFactoryItems in the specified list.
379		 *
380		 * This method is invoked when <code>createMediaElement</code> is invoked
381		 * with a resource that more than one MediaFactoryItem can handle.  Subclasses
382		 * can override to select the most appropriate one.
383		 *
384		 * The default behavior is to select the first item which is not "native" to
385		 * the framework, under the theory that plugins ought to take precedence over
386		 * core media types.  It makes this decision based on the presence or absence
387	 	 * of an id value which starts with "org.osmf".
388		 */
389		protected function resolveItems(resource:MediaResourceBase, items:Vector.<MediaFactoryItem>):MediaFactoryItem
390		{
391			if (resource == null || items == null)
392			{
393				return null;
394			}
395
396			var firstNativeItem:MediaFactoryItem = null;
397
398			for (var i:int = 0; i < items.length; i++)
399			{
400				var item:MediaFactoryItem = items[i] as MediaFactoryItem;
401				if (item.id.indexOf("org.osmf") == -1)
402				{
403					// Non-native, we'll take it.
404					return item;
405				}
406				else if (firstNativeItem == null)
407				{
408					firstNativeItem = item;
409				}
410			}
411
412			return firstNativeItem;
413		}
414
415		// Internals
416		//
417
418		private function findOrCreateItems(type:String):Vector.<MediaFactoryItem>
419		{
420			if (allItems[type] == null)
421			{
422				allItems[type] = new Vector.<MediaFactoryItem>();
423			}
424
425			return allItems[type] as Vector.<MediaFactoryItem>;
426		}
427
428		private function createMediaElementByResource
429			( resource:MediaResourceBase
430			, itemType:String
431			, wrappedElement:MediaElement=null
432			):MediaElement
433		{
434			var mediaElement:MediaElement = null;
435
436			var items:Vector.<MediaFactoryItem> = getItemsByResource(resource, allItems[itemType]);
437
438			if (itemType == MediaFactoryItemType.STANDARD)
439			{
440				var item:MediaFactoryItem = resolveItems(resource, items) as MediaFactoryItem;
441				if (item != null)
442				{
443					mediaElement = invokeMediaElementCreationFunction(item);
444				}
445			}
446			else if (itemType == MediaFactoryItemType.PROXY)
447			{
448				var nextElementToWrap:MediaElement = wrappedElement;
449
450				// Create our chain of proxies, starting from the bottom so
451				// that we can assign the base wrapped element.  (Note that
452				// we iterate from the end to the beginning simply to make
453				// it easier to assign the wrappedElement in our for loop.
454				// In the future, we may want to provide control for the
455				// ordering to the client through some type of resolver method.
456				for (var i:int = items.length; i > 0; i--)
457				{
458					var proxyItem:MediaFactoryItem = items[i-1] as MediaFactoryItem;
459					var proxyElement:ProxyElement = invokeMediaElementCreationFunction(proxyItem) as ProxyElement;
460					if (proxyElement != null)
461					{
462						proxyElement.proxiedElement = nextElementToWrap;
463
464						nextElementToWrap = proxyElement;
465					}
466				}
467
468				mediaElement = nextElementToWrap;
469			}
470
471			if (mediaElement != null)
472			{
473				mediaElement.resource = resource;
474			}
475
476			return mediaElement;
477		}
478
479		private static function getItemsByResource(resource:MediaResourceBase, items:Vector.<MediaFactoryItem>):Vector.<MediaFactoryItem>
480		{
481			var results:Vector.<MediaFactoryItem> = new Vector.<MediaFactoryItem>();
482
483			for each (var item:MediaFactoryItem in items)
484			{
485				if (item.canHandleResourceFunction(resource))
486				{
487					results.push(item);
488				}
489			}
490
491			return results;
492		}
493
494		private static function getIndexOfItem(id:String, items:Vector.<MediaFactoryItem>):int
495		{
496			for (var i:int = 0; i < items.length; i++)
497			{
498				var item:MediaFactoryItem = items[i] as MediaFactoryItem;
499				if (item.id == id)
500				{
501					return i;
502				}
503			}
504
505			return -1;
506		}
507
508		private function onPluginLoad(event:PluginManagerEvent):void
509		{
510			dispatchEvent(new MediaFactoryEvent(MediaFactoryEvent.PLUGIN_LOAD, false, false, event.resource));
511		}
512
513		private function onPluginLoadError(event:PluginManagerEvent):void
514		{
515			dispatchEvent(new MediaFactoryEvent(MediaFactoryEvent.PLUGIN_LOAD_ERROR, false, false, event.resource));
516		}
517
518		private function invokeMediaElementCreationFunction(item:MediaFactoryItem):MediaElement
519		{
520			var mediaElement:MediaElement = null;
521			try
522			{
523				mediaElement = item.mediaElementCreationFunction();
524			}
525			catch (error:Error)
526			{
527				// Swallow, the creation function is wrongly specified.
528				// We'll just return a null MediaElement.
529			}
530			return mediaElement;
531		}
532
533		private function createPluginManager():void
534		{
535			if (pluginManager == null)
536			{
537				pluginManager = new PluginManager(this);
538				pluginManager.addEventListener(PluginManagerEvent.PLUGIN_LOAD, onPluginLoad);
539				pluginManager.addEventListener(PluginManagerEvent.PLUGIN_LOAD_ERROR, onPluginLoadError);
540			}
541		}
542
543		private var pluginManager:PluginManager;
544
545		private var allItems:Dictionary;
546			// Keys are: String (MediaFactoryItemType)
547			// Values are: Vector.<MediaFactoryItem>
548	}
549}