/*****************************************************
*
* 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.events.PlayEvent;
import org.osmf.traits.MediaTraitBase;
import org.osmf.traits.MediaTraitType;
import org.osmf.traits.PlayState;
import org.osmf.traits.PlayTrait;
internal class CompositePlayTrait extends PlayTrait implements IReusable
{
/**
* Constructor.
*
* @param traitAggregator The object which is aggregating all instances
* of the PlayTrait 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 CompositePlayTrait(traitAggregator:TraitAggregator, mode:String)
{
super();
this.traitAggregator = traitAggregator;
this.mode = mode;
traitAggregationHelper = new TraitAggregationHelper
( traitType
, traitAggregator
, processAggregatedChild
, processUnaggregatedChild
);
}
/**
* @private
*/
public function attach():void
{
traitAggregationHelper.attach();
}
/**
* @private
*/
public function detach():void
{
traitAggregationHelper.detach();
}
/**
* @private
*/
override protected function playStateChangeStart(newPlayState:String):void
{
if (newPlayState != playState && !playStateIsChanging)
{
// Prevent this from being reentrant.
playStateIsChanging = true;
if (mode == CompositionMode.PARALLEL)
{
// Invoke the appropriate method on all children.
if (newPlayState == PlayState.PLAYING)
{
traitAggregator.invokeOnEachChildTrait("play", [], MediaTraitType.PLAY);
}
else if (newPlayState == PlayState.PAUSED)
{
traitAggregator.invokeOnEachChildTrait("pause", [], MediaTraitType.PLAY);
}
else // STOPPED
{
traitAggregator.invokeOnEachChildTrait("stop", [], MediaTraitType.PLAY);
}
}
else // SERIAL
{
// We want to set the playState on the current child. But doing
// so could trigger events which would affect the state of this
// trait, which might cause it to get out of sync (since this call
// is generally followed by the set to the actual playState var).
// It is the responsibility of this trait to ensure that it's own
// state doesn't get out of sync. It does this by remembering
// the operation to invoke, and invoking it at the next "safe" time
// (which is basically when the postProcess method gets called).
deferredPlayTraitToSet = traitOfCurrentChild;
deferredPlayStateToSet = newPlayState;
}
playStateIsChanging = false;
}
}
/**
* @private
*/
override protected function playStateChangeEnd():void
{
// Never dispatch the event while we're in the middle of
// processing.
if (playStateIsChanging == false)
{
super.playStateChangeEnd();
}
// If we have a deferred operation to complete, do so now.
if (deferredPlayTraitToSet != null)
{
setPlayState(deferredPlayTraitToSet, deferredPlayStateToSet);
}
deferredPlayTraitToSet = null;
deferredPlayStateToSet = null;
}
// Internals
//
private function processAggregatedChild(child:MediaTraitBase):void
{
child.addEventListener(PlayEvent.PLAY_STATE_CHANGE, onPlayStateChange, false, 0, true);
child.addEventListener(PlayEvent.CAN_PAUSE_CHANGE, onCanPauseChange, false, 0, true);
var playTrait:PlayTrait = child as PlayTrait;
if (mode == CompositionMode.PARALLEL)
{
if (traitAggregator.getNumTraits(MediaTraitType.PLAY) == 1)
{
// The first added child's properties are applied to the
// composite trait.
setPlayState(this, playTrait.playState);
}
else
{
// All subsequently added children inherit their properties
// from the composite trait.
setPlayState(playTrait, this.playState);
}
updateCanPauseState();
}
else if (child == traitOfCurrentChild)
{
// The first added child's properties are applied to the
// composite trait.
setPlayState(this, playTrait.playState);
updateCanPauseState();
}
}
private function processUnaggregatedChild(child:MediaTraitBase):void
{
child.removeEventListener(PlayEvent.PLAY_STATE_CHANGE, onPlayStateChange);
child.removeEventListener(PlayEvent.CAN_PAUSE_CHANGE, onCanPauseChange);
updateCanPauseState();
}
private function onPlayStateChange(event:PlayEvent):void
{
var playTrait:PlayTrait = event.target as PlayTrait;
if (mode == CompositionMode.PARALLEL)
{
// The composition should reflect what its children do.
var computedPlayState:String = playTrait.playState;
// If this child reached the STOPPED state by virtue of
// reaching the end, then we only want to set the composite
// trait's state to STOPPED if all other children are
// similarly STOPPED.
if (computedPlayState == PlayState.STOPPED)
{
traitAggregator.forEachChildTrait
(
function(mediaTrait:PlayTrait):void
{
if (mediaTrait.playState != PlayState.STOPPED)
{
computedPlayState = mediaTrait.playState;
}
}
, MediaTraitType.PLAY
);
}
setPlayState(this, computedPlayState);
}
else if (playTrait == traitOfCurrentChild)
{
// The composition should reflect what its children do.
setPlayState(this, playTrait.playState);
// Typically, the CompositeTimeTrait will handle transitioning
// from one child to the next based on the receipt of the
// complete event. However, if the current child
// doesn't have the TimeTrait, then it obviously can't do so.
// So we check here for that case.
if (playTrait.playState == PlayState.STOPPED &&
traitAggregator.listenedChild.hasTrait(MediaTraitType.TIME) == false)
{
// If the current child has another sibling ahead of it,
// then the next sibling with a PlayTrait should be played.
SerialElementTransitionManager.playNextPlayableChild
( traitAggregator
, null
);
}
}
}
private function onCanPauseChange(event:PlayEvent):void
{
updateCanPauseState();
}
private function setPlayState(playTrait:PlayTrait, value:String):void
{
if (value != playTrait.playState)
{
if (value == PlayState.PLAYING)
{
playTrait.play();
}
else if (value == PlayState.PAUSED)
{
// If we can't pause, leave it alone. The composition may
// get out of sync, but that's acceptable.
if (playTrait.canPause)
{
playTrait.pause();
}
}
else // STOPPED
{
playTrait.stop();
}
}
}
private function updateCanPauseState():void
{
if (mode == CompositionMode.PARALLEL)
{
// The composition can be paused if at least one child can be paused.
var newCanPause:Boolean = false;
traitAggregator.forEachChildTrait
(
function(mediaTrait:MediaTraitBase):void
{
newCanPause ||= PlayTrait(mediaTrait).canPause;
}
, MediaTraitType.PLAY
);
if (canPause != newCanPause)
{
setCanPause(newCanPause);
// If the composite trait changes from pausable to unpausable
// while it's paused, we need to get the composite trait's state
// in sync with the state of its children (whether that be playing
// or stopped).
if (newCanPause == false && playState == PlayState.PAUSED)
{
var newPlayState:String;
traitAggregator.forEachChildTrait
(
function(mediaTrait:MediaTraitBase):void
{
newPlayState ||= PlayTrait(mediaTrait).playState;
}
, MediaTraitType.PLAY
);
if (newPlayState != null)
{
setPlayState(this, newPlayState);
}
}
}
}
else
{
setCanPause(traitOfCurrentChild.canPause);
}
}
private function get traitOfCurrentChild():PlayTrait
{
return traitAggregator.listenedChild
? traitAggregator.listenedChild.getTrait(MediaTraitType.PLAY) as PlayTrait
: null;
}
private var mode:String;
private var traitAggregator:TraitAggregator;
private var traitAggregationHelper:TraitAggregationHelper;
private var playStateIsChanging:Boolean;
private var deferredPlayTraitToSet:PlayTrait;
private var deferredPlayStateToSet:String;
}
}