/*****************************************************
*
* 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 __AS3__.vec.Vector;
import org.osmf.events.SeekEvent;
import org.osmf.media.MediaElement;
import org.osmf.traits.MediaTraitType;
import org.osmf.traits.PlayState;
import org.osmf.traits.PlayTrait;
import org.osmf.traits.SeekTrait;
import org.osmf.traits.TimeTrait;
/**
* Implementation of SeekTrait which can be a composite media trait.
*
* In the serial case, the seek may:
* <ul> seek within the current child
* <ul> seek out of the current child and seek backward
* <ul> seek out of the current child and seek forward
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
internal class SerialSeekTrait extends CompositeSeekTrait
{
public function SerialSeekTrait(traitAggregator:TraitAggregator, owner:MediaElement)
{
super(traitAggregator, CompositionMode.SERIAL, owner);
this.owner = owner;
}
/**
* @private
*/
override protected function doSeek(seekOp:CompositeSeekOperationInfo):void
{
var serialSeek:SerialSeekOperationInfo = seekOp as SerialSeekOperationInfo;
var childSeekTrait:SeekTrait = serialSeek.fromChild.getTrait(MediaTraitType.SEEK) as SeekTrait;
if (serialSeek.fromChild == serialSeek.toChild)
{
readyToExitSeekingState = true;
// This is the case where the seek is within the current child.
childSeekTrait.seek(serialSeek.toChildTime);
}
else
{
// Now, the composite is going to do a seek that crosses multiple children.
// The order of operations is as follows:
// 1) Seek any required in-between children to the appropriate time.
// 2) Seek the targeted child to the appropriate time.
// 3) Seek the previous current child to the appropriate time.
// 4) Set the new current child to be the targeted child.
//
// The reason for this order is to ensure that we seek all non-current
// children *first*, and the current child *last*, so that we trigger
// events last.
//
// Note that we prevent ourselves from exiting the seeking state until
// we've completed all but the last operation.
crossChildrenSeeking = true;
allCrossChildrenSeeksInitiated = false;
readyToExitSeekingState = false;
// Remember the current state so that we can reapply it after the seek.
var previousPlayState:String = getCompositePlayState();
// Since the playhead seeks out of the child, the child needs to be stopped.
var childPlayTrait:PlayTrait = serialSeek.fromChild.getTrait(MediaTraitType.PLAY) as PlayTrait;
if (childPlayTrait != null)
{
childPlayTrait.stop();
}
var childSeekTargetTime:Number;
// Do the seek out of the current child first.
if (serialSeek.seekForward)
{
// Seeking forward means to move the playhead to the end.
var childTimeTrait:TimeTrait = serialSeek.fromChild.getTrait(MediaTraitType.TIME) as TimeTrait;
childSeekTargetTime = childTimeTrait.duration;
}
else
{
// Place all in-between children at the start.
for each (var inBetweenChild:SerialElementSegment in serialSeek.inBetweenChildren)
{
if (inBetweenChild.mediaElement.hasTrait(MediaTraitType.SEEK))
{
var inBetweenSeekTrait:SeekTrait = inBetweenChild.mediaElement.getTrait(MediaTraitType.SEEK) as SeekTrait;
if (inBetweenSeekTrait.canSeekTo(0))
{
inBetweenSeekTrait.seek(0);
}
}
}
// Seeking backward means to move the playhead to the beginning.
childSeekTargetTime = 0;
}
// Now the composite does the seek into another child, where the destiny of the seek is.
var nextSeekTrait:SeekTrait = serialSeek.toChild.getTrait(MediaTraitType.SEEK) as SeekTrait;
nextSeekTrait.seek(serialSeek.toChildTime);
// For forward seeks, set the current child before seeking the old
// current child. This ensures that a seek to the end doesn't trigger
// playback of the next child.
if (serialSeek.seekForward)
{
// Next, update our current child to the target child.
traitAggregator.listenedChild = serialSeek.toChild;
// We're not ready to exit the seeking state until we initiate
// the last operation. On a seek forward, the last operation
// is the last seek (see below).
readyToExitSeekingState = true;
}
// Now, seek the previously current child to the appropriate time.
allCrossChildrenSeeksInitiated = true;
childSeekTrait.seek(childSeekTargetTime);
if (serialSeek.seekForward == false)
{
// We're not ready to exit the seeking state until we initiate
// the last operation. On a seek back, the last operation
// is the setting of the new current child (see below).
readyToExitSeekingState = true;
traitAggregator.listenedChild = serialSeek.toChild;
}
if (!isNaN(exitSeekingStateTime))
{
setSeeking(false, exitSeekingStateTime);
exitSeekingStateTime = NaN;
}
var playTrait:PlayTrait = owner.getTrait(MediaTraitType.PLAY) as PlayTrait;
if (playTrait != null)
{
if (previousPlayState == PlayState.PLAYING)
{
playTrait.play();
}
else if (previousPlayState == PlayState.PAUSED && playTrait.canPause)
{
playTrait.pause();
}
}
}
}
/**
* @private
*/
override protected function prepareSeekOperationInfo(time:Number):CompositeSeekOperationInfo
{
var serialSegments:Vector.<SerialElementSegment> = prepareSerialSegments();
var seekToOp:SerialSeekOperationInfo;
var curChildIndex:int = traitAggregator.getChildIndex(traitAggregator.listenedChild);
if (curChildIndex < serialSegments.length)
{
var curChildSeekOperation:SerialElementSegment = serialSegments[curChildIndex];
// Check whether this is a seek within the current child
if (curChildSeekOperation.relativeStart <= time && time <= curChildSeekOperation.relativeEnd)
{
seekToOp = new SerialSeekOperationInfo();
seekToOp.canSeekTo = true;
seekToOp.fromChild = curChildSeekOperation.mediaElement;
seekToOp.toChild = curChildSeekOperation.mediaElement;
// Convert from the composite playhead position to child playhead position
seekToOp.toChildTime = time - curChildSeekOperation.relativeStart;
}
else if (time < curChildSeekOperation.relativeStart)
{
seekToOp = canSeekBackward(time, serialSegments, curChildIndex);
}
else
{
seekToOp = canSeekForward(time, serialSegments, curChildIndex);
}
}
else
{
// Seeking is not possible within the current child.
seekToOp = new SerialSeekOperationInfo();
seekToOp.canSeekTo = false;
}
return seekToOp;
}
/**
* @private
*/
override protected function checkSeeking():Boolean
{
// Serail composite seekable is only in seeking state when the current child is in
// seeking state.
return (traitOfCurrentChild != null) ? traitOfCurrentChild.seeking : false;
}
// Internals
//
private function prepareSerialSegments():Vector.<SerialElementSegment>
{
var serialSegments:Vector.<SerialElementSegment> = new Vector.<SerialElementSegment>();
var currentTime:Number = 0;
for (var i:int = 0; i < traitAggregator.numChildren; i++)
{
var mediaElement:MediaElement = traitAggregator.getChildAt(i);
var timeTrait:TimeTrait = mediaElement.getTrait(MediaTraitType.TIME) as TimeTrait;
// If a child has no duration, it will be skipped.
if (timeTrait != null && !isNaN(timeTrait.duration))
{
serialSegments.push
( new SerialElementSegment
( mediaElement
, currentTime
, currentTime + timeTrait.duration
, mediaElement.hasTrait(MediaTraitType.SEEK) == false
)
);
currentTime += timeTrait.duration;
}
else
{
// Push an empty segment so that we maintain one segment
// for each child.
serialSegments.push
( new SerialElementSegment
( mediaElement
, currentTime
, currentTime
, mediaElement.hasTrait(MediaTraitType.SEEK) == false
)
);
}
}
return serialSegments;
}
override protected function onSeekingChanged(event:SeekEvent):void
{
// If the composite is in the middle of a seek that crosses multiple children,
// the composite trait will expect to receive SeekingChangeEvents from its children.
// Once all children finish seeking, then the seek is considered complete.
if (crossChildrenSeeking)
{
// When executing a cross-child seek, we must wait until all of the seeks
// are initiated before checking to see if we should signal that the entire
// seek is complete, or else we'll end up with a false positive.
if (allCrossChildrenSeeksInitiated)
{
var child:MediaElement;
while (child = traitAggregator.getNextChildWithTrait(child, MediaTraitType.SEEK))
{
if ((child.getTrait(MediaTraitType.SEEK) as SeekTrait).seeking)
{
return; //return if any children haven't completed the seek.
}
}
crossChildrenSeeking = false;
if (readyToExitSeekingState)
{
// The child is exiting the seeking state, so we just
// update the composite seeking state.
setSeeking(false, timeTrait != null ? timeTrait.currentTime : 0);
}
else
{
// Save the time which we'll pass to the setSeeking method.
exitSeekingStateTime = timeTrait != null ? timeTrait.currentTime : 0;
}
}
}
else
{
var newSeekingState:Boolean = checkSeeking();
if (newSeekingState != seeking)
{
// At this point, we know that the change of the child seeking state will cause the
// composite seeking state to change.
if (newSeekingState)
{
// Do nothing.
}
else
{
// The child is exiting the seeking state, so we just
// update the composite seeking state.
setSeeking(false, event.time);
}
}
}
}
private function canSeekBackward
( time:Number
, serialSegments:Vector.<SerialElementSegment>
, curChildIndex:int):SerialSeekOperationInfo
{
var seekToOp:SerialSeekOperationInfo = new SerialSeekOperationInfo();
seekToOp.seekForward = false;
seekToOp.canSeekTo = false;
// If the current child is the first child of the serial element, and the
// composite trait still needs to seek to the left of the current child,
// it is definitely unseekable.
var index:int = curChildIndex - 1;
if (index < 0)
{
return seekToOp;
}
seekToOp.fromChild = serialSegments[curChildIndex].mediaElement;
var curSegment:SerialElementSegment = serialSegments[index];
while (curSegment != null)
{
var childSeekTrait:SeekTrait = curSegment.mediaElement.getTrait(MediaTraitType.SEEK) as SeekTrait;
if (time >= curSegment.relativeStart)
{
if (childSeekTrait == null)
{
curSegment = serialSegments[index+1];
seekToOp.toChild = curSegment.mediaElement;
seekToOp.toChildTime = 0;
}
else
{
seekToOp.toChild = curSegment.mediaElement;
seekToOp.toChildTime = time - curSegment.relativeStart;
}
seekToOp.canSeekTo = true;
break;
}
index--;
if (index < 0)
{
break;
}
else
{
seekToOp.inBetweenChildren.push(curSegment);
curSegment = serialSegments[index];
}
}
return seekToOp;
}
private function canSeekForward
( time:Number
, serialSegments:Vector.<SerialElementSegment>
, curChildIndex:int):SerialSeekOperationInfo
{
var seekToOp:SerialSeekOperationInfo = new SerialSeekOperationInfo();
seekToOp.seekForward = true;
seekToOp.canSeekTo = false;
// If the current child is the last child of the serial element, and the
// composite trait still needs to seek to the right of the current child,
// it is definitely unseekable.
var index:int = curChildIndex + 1;
if (index >= serialSegments.length)
{
return seekToOp;
}
seekToOp.fromChild = serialSegments[curChildIndex].mediaElement;
var curSegment:SerialElementSegment = serialSegments[index];
while (curSegment != null)
{
if (time <= curSegment.relativeEnd)
{
var childSeekTrait:SeekTrait = curSegment.mediaElement.getTrait(MediaTraitType.SEEK) as SeekTrait;
if (childSeekTrait == null)
{
// Not seekable.
break;
}
else
{
seekToOp.toChild = curSegment.mediaElement;
seekToOp.toChildTime = time - curSegment.relativeStart;
}
seekToOp.canSeekTo = true;
break;
}
index++;
if (index >= serialSegments.length || curSegment.unseekable)
{
// Not seekable.
break;
}
else
{
seekToOp.inBetweenChildren.push(curSegment);
curSegment = serialSegments[index];
}
}
return seekToOp;
}
private function get traitOfCurrentChild():SeekTrait
{
return traitAggregator.listenedChild
? traitAggregator.listenedChild.getTrait(MediaTraitType.SEEK) as SeekTrait
: null;
}
private function getCompositePlayState():String
{
var playTrait:PlayTrait = owner.getTrait(MediaTraitType.PLAY) as PlayTrait;
return (playTrait == null) ? null : playTrait.playState;
}
private var owner:MediaElement;
private var crossChildrenSeeking:Boolean;
private var allCrossChildrenSeeksInitiated:Boolean;
private var readyToExitSeekingState:Boolean = true;
private var exitSeekingStateTime:Number;
}
}