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}