/***************************************************** * * 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.net { import flash.events.NetStatusEvent; import flash.events.TimerEvent; import flash.net.NetStream; import flash.utils.Timer; import org.osmf.traits.LoadTrait; import org.osmf.traits.SeekTrait; import org.osmf.traits.TimeTrait; [ExcludeClass] /** * @private * * The NetStreamSeekTrait class extends SeekTrait for NetStream-based seeking. * * @see flash.net.NetStream */ public class NetStreamSeekTrait extends SeekTrait { /** * Constructor. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion OSMF 1.0 */ public function NetStreamSeekTrait(temporal:TimeTrait, loadTrait:LoadTrait, netStream:NetStream) { super(temporal); this.netStream = netStream; this.loadTrait = loadTrait; NetClient(netStream.client).addHandler(NetStreamCodes.ON_META_DATA, onMetaData); netStream.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus); seekBugTimer = new Timer(10, 100); seekBugTimer.addEventListener(TimerEvent.TIMER, onSeekBugTimer, false, 0, true); seekBugTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onSeekBugTimerDone, false, 0, true); } /** * @private **/ override public function canSeekTo(time:Number):Boolean { var result:Boolean = super.canSeekTo(time); if ( result && !isNaN(loadTrait.bytesTotal) && loadTrait.bytesTotal > 0 ) { // Check the progress of the download, as you can't seek past // the last downloaded byte. var maxSeekTime:Number = timeTrait.duration * Number(loadTrait.bytesLoaded / loadTrait.bytesTotal); result = time <= maxSeekTime; } return result; } /** * @private * Communicates a seeking change to the media through the NetStream. * @param newSeeking New seeking value. * @param time Time to seek to, in seconds. */ override protected function seekingChangeStart(newSeeking:Boolean, time:Number):void { if (newSeeking) { // Note that we set previousTime based on the NetStream's time // and audioDelay, rather than pulling it from the TimeTrait. // This is because the TimeTrait sometimes includes a duration // offset (to compensate for the onMetaData-based duration // differing from the actual duration), which throws off the // calculation in the timer handler below. See bug FM-708. suppressSeekNotifyEvent = false; previousTime = netStream.time - audioDelay; expectedTime = time; netStream.seek(time + audioDelay); if (previousTime == expectedTime) { // Manually start the seekBugTimer, because NetStream // sometimes doesn't trigger an event when seeking to // its current position (FM-227), causing the seek to // never get closed. seekBugTimer.start(); // Note that this event gets triggered "sometimes". // If it does, then we want to suppress it (until the // next seek operation, hence the clearing of it // earlier in this method). suppressSeekNotifyEvent = true; } } } private function onMetaData(value:Object):void { audioDelay = value.hasOwnProperty("audiodelay") ? value.audiodelay : 0; } private function onNetStatus(event:NetStatusEvent):void { switch (event.info.code) { case NetStreamCodes.NETSTREAM_SEEK_NOTIFY: // There's a bug (FP-1705) where NetStream.time doesn't get // updated until *after* the NetStream.Seek.Notify event is // received. We don't want to processSeekCompletion until // NetStream's state is consistent, so we use a Timer to // delay the processing until the NetStream.time property // is up-to-date. // Note that we don't start the Timer if we've been // instructed to suppress the event. runSeekBugTimer(); break; case NetStreamCodes.NETSTREAM_SEEK_INVALIDTIME: case NetStreamCodes.NETSTREAM_SEEK_FAILED: setSeeking(false, previousTime); break; case NetStreamCodes.NETSTREAM_PLAY_START: case NetStreamCodes.NETSTREAM_PLAY_RESET: case NetStreamCodes.NETSTREAM_PAUSE_NOTIFY: case NetStreamCodes.NETSTREAM_PLAY_STOP: case NetStreamCodes.NETSTREAM_UNPAUSE_NOTIFY: // On play being invoked while seeking is in progress, the // seek will not complete (FM-475). Do so manually: if (seeking && seekBugTimer.running == false) { // Use the same timer mechanism as on the regular // NETSTREAM_SEEK_NOTIFY event: runSeekBugTimer(); } break; } } private function runSeekBugTimer():void { if (suppressSeekNotifyEvent == false) { seekBugTimer.start(); } else { suppressSeekNotifyEvent = false; } } private function onSeekBugTimer(event:TimerEvent):void { // As soon as our NetStream.time value has changed from the value // it had when the seek was initiated, we assume the seek has // completed. Note that the playhead may not actually be at the // seeked-to position, since it will land on the nearest keyframe // (which might be seconds away). Addresses bug FM-258. The // inclusion of audioDelay in the comparison is for bug FM-708. // The second condition is to cover the case where the seek time // is the same as the current time (bug FM-227). if ( previousTime != netStream.time - audioDelay || previousTime == expectedTime ) { onSeekBugTimerDone(null); } } private function onSeekBugTimerDone(event:TimerEvent):void { seekBugTimer.reset(); setSeeking(false, expectedTime); } private var loadTrait:LoadTrait; private var audioDelay:Number = 0; private var seekBugTimer:Timer; private var netStream:NetStream; private var expectedTime:Number; private var previousTime:Number; private var suppressSeekNotifyEvent:Boolean = false; } }