/*****************************************************
*
* Copyright 2009 Adobe Systems Incorporated. All Rights Reserved.
*
*****************************************************
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
*
* The Initial Developer of the Original Code is Adobe Systems Incorporated.
* Portions created by Adobe Systems Incorporated are Copyright (C) 2009 Adobe Systems
* Incorporated. All Rights Reserved.
*
*****************************************************/
package org.osmf.elements.compositeClasses
{
import org.osmf.elements.SerialElement;
import org.osmf.events.TimeEvent;
import org.osmf.media.MediaElement;
import org.osmf.traits.MediaTraitBase;
import org.osmf.traits.MediaTraitType;
import org.osmf.traits.PlayTrait;
import org.osmf.traits.TimeTrait;
/**
* Implementation of TimeTrait which can be a composite media trait.
*
* For parallel media elements, the composite trait represents a timeline
* that encapsulates the timeline of all children. Its duration is the
* maximum of the durations of all children. Its currentTime is kept in sync
* for all children (with the obvious caveat that a child's currentTime will
* never be greater than its duration).
*
* For serial elements, the composite trait represents a timeline that
* encapsulates the timeline of all children. Its duration is the sum of
* the durations of all children. Its currentTime is the sum of the currentTimes
* of the first N fully complete children, plus the currentTime of the next
* child.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
internal class CompositeTimeTrait extends TimeTrait implements IReusable
{
/**
* Constructor.
*
* @param traitAggregator The object which is aggregating all instances
* of the TimeTrait within this composite trait.
* @param mode The composition mode to which this composite trait
* should adhere. See CompositionMode for valid values.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
public function CompositeTimeTrait(traitAggregator:TraitAggregator, mode:String, owner:MediaElement)
{
super();
this.mode = mode;
this.traitAggregator = traitAggregator;
this.owner = owner;
traitAggregationHelper = new TraitAggregationHelper
( traitType
, traitAggregator
, processAggregatedChild
, processUnaggregatedChild
);
}
/**
* @private
*/
public function attach():void
{
traitAggregationHelper.attach();
}
/**
* @private
*/
public function detach():void
{
traitAggregationHelper.detach();
}
override public function dispose():void
{
}
/**
* @private
*/
override public function get currentTime():Number
{
updateCurrentTime();
return super.currentTime;
}
override protected function signalComplete():void
{
// The base class can cause this method to get called, sometimes
// inappropriately for serial elements, so we need to verify that
// it's truly complete.
if ( mode == CompositionMode.PARALLEL
|| isSerialComplete()
)
{
super.signalComplete()
}
}
private function isSerialComplete():Boolean
{
// A serial element is only complete if it's truly at the end.
//
var childTimeTrait:TimeTrait = traitAggregator.listenedChild.getTrait(MediaTraitType.TIME) as TimeTrait;
// Conditions in which it's not at the end:
// 1) If currentTime == duration because we don't yet have the duration
// for the next child.
// 2) If the next child doesn't have a duration yet (FM-303).
var result:Boolean =
traitAggregator.getChildIndex(traitAggregator.listenedChild) == traitAggregator.numChildren - 1
&& childTimeTrait.duration > 0
&& !isNaN(childTimeTrait.duration);
// 3) If the last child is itself a SerialElement, then we need to
// recursively apply the same rule checks, in case it appears to be
// complete but really isn't (FM-707).
if ( result
&& childTimeTrait is CompositeTimeTrait
&& traitAggregator.listenedChild is SerialElement
)
{
result = CompositeTimeTrait(childTimeTrait).isSerialComplete();
}
return result;
}
// Internal
//
/**
* @private
*/
private function processAggregatedChild(child:MediaTraitBase):void
{
child.addEventListener(TimeEvent.DURATION_CHANGE, onDurationChanged, false, 0, true);
child.addEventListener(TimeEvent.COMPLETE, onComplete, false, 0, true);
updateDuration();
updateCurrentTime();
}
/**
* @private
*/
private function processUnaggregatedChild(child:MediaTraitBase):void
{
child.removeEventListener(TimeEvent.DURATION_CHANGE, onDurationChanged);
child.removeEventListener(TimeEvent.COMPLETE, onComplete);
updateDuration();
updateCurrentTime();
}
private function onDurationChanged(event:TimeEvent):void
{
updateDuration();
}
private function onComplete(event:TimeEvent):void
{
var timeTrait:TimeTrait = event.target as TimeTrait;
if (mode == CompositionMode.PARALLEL)
{
// If every child has reached their duration, then we should
// dispatch the complete event.
var allHaveReachedDuration:Boolean = true;
traitAggregator.forEachChildTrait
(
function(mediaTrait:MediaTraitBase):void
{
var iterTimeTrait:TimeTrait = TimeTrait(mediaTrait);
// Assume that the child that fired the event has
// finished.
if (iterTimeTrait != timeTrait &&
iterTimeTrait.currentTime < iterTimeTrait.duration)
{
allHaveReachedDuration = false;
}
}
, MediaTraitType.TIME
);
if (allHaveReachedDuration)
{
dispatchEvent(new TimeEvent(TimeEvent.COMPLETE));
}
}
else // SERIAL
{
if (timeTrait == traitOfCurrentChild)
{
// If the composite element has the PlayTrait and the
// current child has another sibling ahead of it, then
// the next sibling with a PlayTrait should be played.
var playTrait:PlayTrait = owner.getTrait(MediaTraitType.PLAY) as PlayTrait;
if (playTrait != null)
{
// Note that we don't check whether to dispatch the
// complete event until we determine that
// there's no more playable children -- otherwise we'd
// almost certainly dispatch it when it shouldn't be
// dispatched since subsequent children are likely to
// lack the temporal trait until they're loaded.
SerialElementTransitionManager.playNextPlayableChild
( traitAggregator
, checkDispatchCompleteEvent
);
}
else
{
checkDispatchCompleteEvent();
}
}
}
}
private function checkDispatchCompleteEvent():void
{
// If the current child is the last temporal child, then we should
// dispatch the complete event.
var nextChild:MediaElement =
traitAggregator.getNextChildWithTrait
( traitAggregator.listenedChild
, MediaTraitType.TIME
);
if (nextChild == null)
{
super.signalComplete();
}
}
private function updateDuration():void
{
var newDuration:Number = 0;
var hasChildWithDuration:Boolean = false;
traitAggregator.forEachChildTrait
(
function(mediaTrait:MediaTraitBase):void
{
var childDuration:Number = TimeTrait(mediaTrait).duration;
if (!isNaN(childDuration))
{
hasChildWithDuration = true;
if (mode == CompositionMode.PARALLEL)
{
// The duration is the max of all child durations.
newDuration = Math.max(newDuration, childDuration);
}
else // SERIAL
{
// The duration is the sum of all child durations.
newDuration += childDuration;
}
}
}
, MediaTraitType.TIME
);
setDuration(hasChildWithDuration ? newDuration : NaN);
}
private function updateCurrentTime():void
{
var newCurrentTime:Number = 0;
var hasChildWithCurrentTime:Boolean = false;
var serialCurrentTimeCalculated:Boolean = false;
traitAggregator.forEachChildTrait
(
function(mediaTrait:MediaTraitBase):void
{
var childCurrentTime:Number = TimeTrait(mediaTrait).currentTime;
if (isNaN(childCurrentTime))
{
childCurrentTime = 0;
}
else
{
hasChildWithCurrentTime = true;
}
if (mode == CompositionMode.PARALLEL)
{
// The currentTime is the max of all child currentTimes.
newCurrentTime = Math.max(newCurrentTime, childCurrentTime);
}
else // SERIAL
{
// The currentTime is the sum of all durations up to the
// current child, plus the currentTime of the current
// child.
if (!serialCurrentTimeCalculated)
{
if (mediaTrait == traitOfCurrentChild)
{
newCurrentTime += childCurrentTime;
serialCurrentTimeCalculated = true;
}
else
{
var duration:Number = TimeTrait(mediaTrait).duration;
if (!isNaN(duration))
{
newCurrentTime += duration;
}
}
}
}
}
, MediaTraitType.TIME
);
setCurrentTime(hasChildWithCurrentTime ? newCurrentTime : NaN);
}
private function get traitOfCurrentChild():TimeTrait
{
return traitAggregator.listenedChild
? traitAggregator.listenedChild.getTrait(MediaTraitType.TIME) as TimeTrait
: null;
}
private var traitAggregator:TraitAggregator;
private var traitAggregationHelper:TraitAggregationHelper;
private var mode:String;
private var owner:MediaElement;
}
}