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.elements.compositeClasses 23{ 24 import org.osmf.events.LoadEvent; 25 import org.osmf.media.MediaResourceBase; 26 import org.osmf.traits.LoadState; 27 import org.osmf.traits.LoadTrait; 28 import org.osmf.traits.MediaTraitBase; 29 import org.osmf.traits.MediaTraitType; 30 31 /** 32 * Implementation of LoadTrait which can be a composite media trait. 33 * 34 * @langversion 3.0 35 * @playerversion Flash 10 36 * @playerversion AIR 1.5 37 * @productversion OSMF 1.0 38 */ 39 internal class CompositeLoadTrait extends LoadTrait 40 { 41 /** 42 * Constructor. 43 * 44 * @param traitAggregator The object which is aggregating all instances 45 * of the ILoadable trait within this composite trait. 46 * @param mode The composition mode to which this composite trait 47 * should adhere. See CompositionMode for valid values. 48 * 49 * @langversion 3.0 50 * @playerversion Flash 10 51 * @playerversion AIR 1.5 52 * @productversion OSMF 1.0 53 */ 54 public function CompositeLoadTrait(traitAggregator:TraitAggregator, mode:String) 55 { 56 super(null, null); 57 58 this.traitAggregator = traitAggregator; 59 this.mode = mode; 60 traitAggregationHelper = new TraitAggregationHelper 61 ( traitType 62 , traitAggregator 63 , processAggregatedChild 64 , processUnaggregatedChild 65 ); 66 } 67 68 override public function dispose():void 69 { 70 traitAggregationHelper.detach(); 71 traitAggregationHelper = null; 72 73 super.dispose(); 74 } 75 76 /** 77 * @private 78 */ 79 override public function get bytesLoaded():Number 80 { 81 var compositeBytesLoaded:Number; 82 83 if (mode == CompositionMode.SERIAL) 84 { 85 var emptyUnitSeen:Boolean = false; 86 traitAggregator.forEachChildTrait 87 ( 88 function (mediaTrait:MediaTraitBase):void 89 { 90 if (!emptyUnitSeen) 91 { 92 var loadTrait:LoadTrait = LoadTrait(mediaTrait); 93 94 if (!isNaN(loadTrait.bytesLoaded)) 95 { 96 // The last contributor to bytesLoaded is 97 // the first non-fully-downloaded child. 98 emptyUnitSeen = loadTrait.bytesLoaded < loadTrait.bytesTotal; 99 if (isNaN(compositeBytesLoaded)) 100 { 101 compositeBytesLoaded = 0; 102 } 103 compositeBytesLoaded += loadTrait.bytesLoaded; 104 } 105 } 106 }, 107 MediaTraitType.LOAD 108 ); 109 } 110 else // PARALLEL 111 { 112 traitAggregator.forEachChildTrait 113 ( 114 function (mediaTrait:MediaTraitBase):void 115 { 116 var loadTrait:LoadTrait = LoadTrait(mediaTrait); 117 if (!isNaN(loadTrait.bytesLoaded)) 118 { 119 if (isNaN(compositeBytesLoaded)) 120 { 121 compositeBytesLoaded = 0; 122 } 123 compositeBytesLoaded += loadTrait.bytesLoaded; 124 } 125 }, 126 MediaTraitType.LOAD 127 ); 128 } 129 return compositeBytesLoaded; 130 } 131 132 /** 133 * @private 134 */ 135 override public function get resource():MediaResourceBase 136 { 137 var value:MediaResourceBase = null; 138 139 // For serial compositions, expose the resource of the current 140 // child. For parallel compositions, no return value makes 141 // sense. 142 if (mode == CompositionMode.SERIAL) 143 { 144 if (traitAggregator.listenedChild != null) 145 { 146 value = traitAggregator.listenedChild.resource; 147 } 148 } 149 150 return value; 151 } 152 153 /** 154 * @private 155 */ 156 override public function load():void 157 { 158 if (mode == CompositionMode.PARALLEL) 159 { 160 // Call load() on all not-yet-loaded children. 161 traitAggregator.forEachChildTrait 162 ( 163 function(mediaTrait:MediaTraitBase):void 164 { 165 var loadTrait:LoadTrait = LoadTrait(mediaTrait); 166 if (loadTrait.loadState != LoadState.LOADING && 167 loadTrait.loadState != LoadState.READY) 168 { 169 loadTrait.load(); 170 } 171 } 172 , MediaTraitType.LOAD 173 ); 174 } 175 else // SERIAL 176 { 177 // Call load() on the current child only. 178 var currentLoadTrait:LoadTrait = traitOfCurrentChild; 179 if (currentLoadTrait != null && 180 currentLoadTrait.loadState != LoadState.LOADING && 181 currentLoadTrait.loadState != LoadState.READY) 182 { 183 currentLoadTrait.load(); 184 } 185 } 186 } 187 188 /** 189 * @private 190 */ 191 override public function unload():void 192 { 193 if (mode == CompositionMode.PARALLEL) 194 { 195 // Call unload() on all not-yet-unloaded children. 196 traitAggregator.forEachChildTrait 197 ( 198 function(mediaTrait:MediaTraitBase):void 199 { 200 var loadTrait:LoadTrait = LoadTrait(mediaTrait); 201 if (loadTrait.loadState == LoadState.LOADING || 202 loadTrait.loadState == LoadState.READY) 203 { 204 loadTrait.unload(); 205 } 206 } 207 , MediaTraitType.LOAD 208 ); 209 } 210 else // SERIAL 211 { 212 // Call unload() on the current child only. 213 var currentLoadTrait:LoadTrait = traitOfCurrentChild; 214 if (currentLoadTrait != null && 215 currentLoadTrait.loadState == LoadState.LOADING || 216 currentLoadTrait.loadState == LoadState.READY) 217 { 218 currentLoadTrait.unload(); 219 } 220 } 221 } 222 223 // Internals 224 // 225 226 private function processAggregatedChild(child:MediaTraitBase):void 227 { 228 child.addEventListener(LoadEvent.LOAD_STATE_CHANGE, onLoadStateChange, false, 0, true); 229 child.addEventListener(LoadEvent.BYTES_TOTAL_CHANGE, onBytesTotalChange, false, 0, true); 230 231 if (mode == CompositionMode.PARALLEL) 232 { 233 if (traitAggregator.getNumTraits(MediaTraitType.LOAD) == 1) 234 { 235 // The first added child's properties are applied to the 236 // composite trait. 237 syncToLoadState((child as LoadTrait).loadState); 238 } 239 else 240 { 241 // All subsequently added children inherit their properties 242 // from the composite trait. 243 syncToLoadState(loadState); 244 } 245 } 246 else if (child == traitOfCurrentChild) 247 { 248 // The first added child's properties are applied to the 249 // composite trait. 250 syncToLoadState((child as LoadTrait).loadState); 251 } 252 253 updateBytesTotal(); 254 } 255 256 private function processUnaggregatedChild(child:MediaTraitBase):void 257 { 258 child.removeEventListener(LoadEvent.LOAD_STATE_CHANGE, onLoadStateChange); 259 child.removeEventListener(LoadEvent.BYTES_TOTAL_CHANGE, onBytesTotalChange); 260 261 updateBytesTotal(); 262 } 263 264 private function onLoadStateChange(event:LoadEvent):void 265 { 266 // For parallel compositions and for the current child in a serial 267 // composition, changes from the child propagate to the composite 268 // trait. 269 if (mode == CompositionMode.PARALLEL || 270 event.target == traitOfCurrentChild) 271 { 272 syncToLoadState(event.loadState); 273 } 274 } 275 276 private function syncToLoadState(newLoadState:String):void 277 { 278 // If the state to apply is READY or LOADING, then we load the 279 // composition as a whole. The already-loaded parts will be 280 // ignored. 281 if (newLoadState == LoadState.LOADING || 282 newLoadState == LoadState.READY) 283 { 284 load(); 285 } 286 // If the state to apply is UNINITIALIZED or UNLOADING, then we 287 // unload the composition as a whole. The already-unloaded parts 288 // will be ignored. 289 else if (newLoadState == LoadState.UNINITIALIZED || 290 newLoadState == LoadState.UNLOADING) 291 { 292 unload(); 293 } 294 295 updateLoadState(); 296 } 297 298 private function onBytesTotalChange(event:LoadEvent):void 299 { 300 updateBytesTotal(); 301 } 302 303 private function updateLoadState():void 304 { 305 var newLoadState:String; 306 307 if (mode == CompositionMode.PARALLEL) 308 { 309 // Examine all child traits to find out the state that best 310 // represents the composite trait. This state is based on some 311 // simple rules about the precedence of states in relation to each 312 // other. 313 var loadStateInt:int = int.MAX_VALUE; 314 traitAggregator.forEachChildTrait 315 ( 316 function(mediaTrait:MediaTraitBase):void 317 { 318 // Find the state with the lowest value. 319 loadStateInt 320 = Math.min 321 ( loadStateInt 322 , getIntegerForLoadState(LoadTrait(mediaTrait).loadState) 323 ); 324 } 325 , MediaTraitType.LOAD 326 ); 327 328 // Convert the integer back to the composite state. 329 newLoadState = 330 getLoadStateForInteger(loadStateInt) 331 || LoadState.UNINITIALIZED; 332 } 333 else // SERIAL 334 { 335 var currentLoadTrait:LoadTrait = traitOfCurrentChild; 336 newLoadState = currentLoadTrait 337 ? currentLoadTrait.loadState 338 : LoadState.UNINITIALIZED; 339 } 340 341 setLoadState(newLoadState); 342 } 343 344 private function updateBytesTotal():void 345 { 346 var compositeBytesTotal:Number; 347 348 traitAggregator.forEachChildTrait 349 ( 350 function (mediaTrait:MediaTraitBase):void 351 { 352 var loadTrait:LoadTrait = LoadTrait(mediaTrait); 353 if (!isNaN(loadTrait.bytesTotal)) 354 { 355 if (isNaN(compositeBytesTotal)) 356 { 357 compositeBytesTotal = 0; 358 } 359 360 compositeBytesTotal += loadTrait.bytesTotal; 361 } 362 }, 363 MediaTraitType.LOAD 364 ); 365 366 setBytesTotal(compositeBytesTotal); 367 } 368 369 private function getIntegerForLoadState(loadState:String):int 370 { 371 if (loadState == LoadState.UNINITIALIZED) return UNINITIALIZED_INT; 372 if (loadState == LoadState.LOADING) return LOADING_INT; 373 if (loadState == LoadState.UNLOADING) return UNLOADING_INT; 374 if (loadState == LoadState.READY) return READY_INT; 375 /* loadState == LoadState.LOAD_ERROR*/ return LOAD_ERROR_INT; 376 } 377 378 private function getLoadStateForInteger(i:int):String 379 { 380 if (i == UNINITIALIZED_INT) return LoadState.UNINITIALIZED; 381 if (i == LOADING_INT) return LoadState.LOADING; 382 if (i == UNLOADING_INT) return LoadState.UNLOADING; 383 if (i == READY_INT) return LoadState.READY; 384 if (i == LOAD_ERROR_INT) return LoadState.LOAD_ERROR; 385 /* i out of range */ return null; 386 } 387 388 private function get traitOfCurrentChild():LoadTrait 389 { 390 return traitAggregator.listenedChild 391 ? traitAggregator.listenedChild.getTrait(MediaTraitType.LOAD) as LoadTrait 392 : null; 393 } 394 395 // Ordered such that the lowest one takes precedence. For example, 396 // if we have two child traits with states LOAD_ERROR and UNINITIALIZED, 397 // then the composite trait has state LOAD_ERROR. 398 private static const LOAD_ERROR_INT:int = 0; 399 private static const UNLOADING_INT:int = 1; 400 private static const LOADING_INT:int = 2; 401 private static const UNINITIALIZED_INT:int = 3; 402 private static const READY_INT:int = 4; 403 404 private var traitAggregator:TraitAggregator; 405 private var traitAggregationHelper:TraitAggregationHelper; 406 private var mode:String; 407 } 408}