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}