/*****************************************************
*  
*  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.httpstreaming
{
	import __AS3__.vec.Vector;
	
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.NetStatusEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.TimerEvent;
	import flash.net.NetConnection;
	import flash.net.NetStream;
	import flash.net.NetStreamPlayOptions;
	import flash.net.NetStreamPlayTransitions;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLStream;
	import flash.utils.ByteArray;
	import flash.utils.IDataInput;
	import flash.utils.Timer;
	
	import org.osmf.events.DVRStreamInfoEvent;
	import org.osmf.events.HTTPStreamingFileHandlerEvent;
	import org.osmf.events.HTTPStreamingIndexHandlerEvent;
	import org.osmf.net.NetStreamCodes;
	import org.osmf.net.httpstreaming.dvr.DVRInfo;
	import org.osmf.net.httpstreaming.flv.FLVHeader;
	import org.osmf.net.httpstreaming.flv.FLVParser;
	import org.osmf.net.httpstreaming.flv.FLVTag;
	import org.osmf.net.httpstreaming.flv.FLVTagScriptDataObject;
	import org.osmf.net.httpstreaming.flv.FLVTagVideo;
	import org.osmf.net.httpstreaming.flv.FLVTagAudio;

	[Event(name="DVRStreamInfo", type="org.osmf.events.DVRStreamInfoEvent")]
	
	CONFIG::LOGGING 
	{	
		import org.osmf.logging.Logger;
	}

	CONFIG::FLASH_10_1	
	{
		import flash.net.NetStreamAppendBytesAction;
	}
	
	[ExcludeClass]

	/**
	 * 
	 * @private
	 * 
	 * HTTPNetStream is a NetStream subclass which can accept input via the
	 * appendBytes method.  In general, the assumption is that a large media
	 * file is broken up into a number of smaller fragments.
	 * 
	 * There are two important aspects of working with an HTTPNetStream:
	 * 1) How to map a specific playback time to the media file fragment
	 * which holds the media for that time.
	 * 2) How to unmarshal the data from a media file fragment so that it can
	 * be fed to the NetStream as TCMessages. 
	 * 
	 * The former is the responsibility of HTTPStreamingIndexHandlerBase,
	 * the latter the responsibility of HTTPStreamingFileHandlerBase.
	 */	
	public class HTTPNetStream extends NetStream 
	{
		/**
		 * Constructor.
		 * 
		 * @param connection The NetConnection to use.
		 * @param indexHandler Object which exposes the index, which maps
		 * playback times to media file fragments.
		 * @param fileHandler Object which canunmarshal the data from a
		 * media file fragment so that it can be fed to the NetStream as
		 * TCMessages.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function HTTPNetStream
			( connection:NetConnection
			, indexHandler:HTTPStreamingIndexHandlerBase
			, fileHandler:HTTPStreamingFileHandlerBase
			)
		{
			super(connection);
			
			_savedBytes = new ByteArray();
			
			this.indexHandler = indexHandler;
			this.fileHandler = fileHandler;
			
			indexHandler.addEventListener(HTTPStreamingIndexHandlerEvent.NOTIFY_INDEX_READY, onIndexReady);
			indexHandler.addEventListener(HTTPStreamingIndexHandlerEvent.NOTIFY_RATES, onRates);
			indexHandler.addEventListener(HTTPStreamingIndexHandlerEvent.REQUEST_LOAD_INDEX, onRequestLoadIndexFile);
			indexHandler.addEventListener(HTTPStreamingIndexHandlerEvent.NOTIFY_ERROR, onIndexError);
			indexHandler.addEventListener(HTTPStreamingIndexHandlerEvent.NOTIFY_SEGMENT_DURATION, onSegmentDurationFromIndexHandler);
			indexHandler.addEventListener(HTTPStreamingIndexHandlerEvent.NOTIFY_SCRIPT_DATA, onScriptDataFromIndexHandler);
			
			indexHandler.addEventListener(DVRStreamInfoEvent.DVRSTREAMINFO, onDVRStreamInfo);
			
			// removed NOTIFY_TIME_BIAS
			fileHandler.addEventListener(HTTPStreamingFileHandlerEvent.NOTIFY_SEGMENT_DURATION, onSegmentDurationFromFileHandler);
			fileHandler.addEventListener(HTTPStreamingFileHandlerEvent.NOTIFY_SCRIPT_DATA, onScriptDataFromFileHandler);
			fileHandler.addEventListener(HTTPStreamingFileHandlerEvent.NOTIFY_ERROR, onErrorFromFileHandler);
			
			mainTimer = new Timer(MAIN_TIMER_INTERVAL); 
			mainTimer.addEventListener(TimerEvent.TIMER, onMainTimer);	
			mainTimer.start();
			
			this.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus);
		}
		
		/**
		 * Whether HTTPNetStream implements enhanced seek on the client side.
		 * 
		 * Enhanced seek allows for keyframe-accurate seeking.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function set enhancedSeek(value:Boolean):void
		{
			_enhancedSeekEnabled = value;
		}
		
		public function get enhancedSeek():Boolean
		{
			return _enhancedSeekEnabled;
		}
		
		/**
		 * Getters/(setters if applicable) of a bunch of properties related to the quality of service.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function get downloadRatio():Number
		{
			return _lastDownloadRatio;
		}
		
		public function set qualityLevel(value:int):void
		{
			if (_manualSwitchMode)
			{
				setQualityLevel(value);
			}
			else
			{
				throw new Error("qualityLevel cannot be set to this value at this time");
			}
		}
				
		public function get qualityLevel():int
		{
			return _qualityLevel;
		}

		public function get manualSwitchMode():Boolean
		{	
			return _manualSwitchMode;
		}
		
		public function set manualSwitchMode(value:Boolean):void
		{	
			_manualSwitchMode = value;
		}
		
		/**
		 * Initialization info for the HTTPStreamingIndexHandlerBase.
		 * 
		 * If specified, this will be passed to the index handler's
		 * initialize method when playback is initiated.  Otherwise,
		 * the argument to play (or play2) will be used.
		 **/
		public function get indexInfo():HTTPStreamingIndexInfoBase
		{
			return _indexInfo;
		}
		
		public function set indexInfo(value:HTTPStreamingIndexInfoBase):void
		{
			_indexInfo = value;
		}
		
		// new functionality
			
		public function DVRGetStreamInfo(streamName:Object):void
		{
			if (indexIsReady)
			{
				// TODO: should there be indexHandler.DVRGetStreamInfo() to re-trigger the event?
			}
			else
			{
				// TODO: should there be a guard to protect the case where indexIsReady is not yet true BUT play has already been called, so we are in an
				// "initializing but not yet ready" state? This is only needed if the caller is liable to call DVRGetStreamInfo and then, before getting the
				// event back, go ahead and call play()
				indexHandler.dvrGetStreamInfo(_indexInfo != null ? _indexInfo : streamName);
			}
		}
		
		// Overrides
		//
		
		/**
		 * The arguments to this method can mirror the arguments to the
		 * superclass's method:
		 * 1) media file
		 * 2) URL
		 * 3) name/start/len/reset
		 *		a) Subclips
		 *		b) Live
		 *		c) Resetting playlist
		 * 
		 * @inheritDoc
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		override public function play(...args):void 
		{
			if (args.length < 1)
			{
				throw new Error("HTTPStream.play() requires at least one argument");
			}
						
			// Signal to the base class that we're entering Data Generation Mode.
			super.play(null);
			
			// Playback is considered to start when we first append some bytes.
			dispatchEvent
				( new NetStatusEvent
					( NetStatusEvent.NET_STATUS
					, false
					, false
					, {code:NetStreamCodes.NETSTREAM_PLAY_START, level:"status"}
					)
				); 
			
			// Before we feed any TCMessages to the Flash Player, we must feed
			// an FLV header first.
			//
			
			var header:FLVHeader = new FLVHeader();
			var headerBytes:ByteArray = new ByteArray();
			header.write(headerBytes);
			attemptAppendBytes(headerBytes);
			
			// Initialize ourselves and the index handler.
			//
			
			setState(HTTPStreamingState.INIT);
			_initialTime = -1;
			_seekTime = -1;
			
			indexIsReady = false;
			indexHandler.initialize(_indexInfo != null ? _indexInfo : args[0]);
		
			if (args.length >= 2)
			{
				_seekTarget = Number(args[1]);
				if (_seekTarget < 0)
				{
					if (_dvrInfo != null)
					{
						_seekTarget = _dvrInfo.startTime;
					}
					else
					{
						_seekTarget = 0;	// FMS behavior, mimic -1 or -2 being passed in
					}
				}
			}
			else
			{
				// This is the start of playback, so no seek.
				_seekTarget = 0;
			}
			
			if (args.length >= 3)
			{
				_playForDuration = Number(args[2]);
			}
			else
			{
				_playForDuration = -1;
			}

			_unpublishNotifyPending = false;
		}
		
		/**
		 * @private
		 */
		override public function play2(param:NetStreamPlayOptions):void
		{
			if (param.transition == NetStreamPlayTransitions.RESET)
			{
				// XXX Need to reset playback if we're already playing.
				// Is this done via seek?
				
				// The only difference between play and play2 for the RESET
				// case is that play2 might start at a specific quality level.
				setQualityLevelForStreamName(param.streamName);
				
				play(param.streamName, param.start, param.len);
			}
			else if (param.transition == NetStreamPlayTransitions.SWITCH)
			{
				setQualityLevelForStreamName(param.streamName);
			}
			else
			{
				// Not sure which other modes we should add support for.
				super.play2(param);
			}
		} 

		/**
		 * @private
		 */
		override public function seek(offset:Number):void
		{
			// (change to override seek rather than do this based on seek notify event)
			//  can't do this unless you're already playing (for instance, you can't leave INIT to go to SEEK)! 
			// XXX need to double-check to see if there's more guards needed here
					
			if(offset < 0)
			{
				offset = 0;		// FMS rule. Seek to <0 is same as seeking to zero.
			}
			
			if (_state != HTTPStreamingState.INIT)    // can't seek before playback starts
			{
				if(_initialTime < 0)
				{
					_seekTarget = offset + 0;	// this covers the "don't know initial time" case, rare
				}
				else
				{
					_seekTarget = offset + _initialTime;
				}
				
				_seekTime = -1;		// but _initialTime stays known
				setState(HTTPStreamingState.SEEK);		
				super.seek(offset);
			}
			
			_unpublishNotifyPending = false;
		}
		
		/**
		 * @private
		 */
		override public function close():void
		{
			indexIsReady = false;
			
			switch (_state)
			{
				case HTTPStreamingState.PLAY:
				case HTTPStreamingState.PLAY_START_NEXT:
				case HTTPStreamingState.PLAY_START_SEEK:
					_urlStreamVideo.close();	// immediate abort
			}
			setState(HTTPStreamingState.HALT);
			
			mainTimer.stop();
			
			dispatchEvent
				( new NetStatusEvent
					( NetStatusEvent.NET_STATUS
					, false
					, false
					, {code:NetStreamCodes.NETSTREAM_PLAY_STOP, level:"status"}
					)
				); 

			// XXX might need to do other things here
			super.close();
		}
		
		/**
		 * @private
		 */
		override public function get time():Number
		{
			if(_seekTime >= 0 && _initialTime >= 0)
			{
				_lastValidTimeTime = (super.time + _seekTime) - _initialTime; 
					//  we remember what we say when time is valid, and just spit that back out any time we don't have valid data. This is probably the right answer.
					//  the only thing we could do better is also run a timer to ask ourselves what it is whenever it might be valid and save that, just in case the
					//  user doesn't ask... but it turns out most consumers poll this all the time in order to update playback position displays
			}
			
			return _lastValidTimeTime;
		}
				
		// Internal
		//
		
		private function setState(value:String):void
		{
			_prevState = _state;
			_state = value;
		}
		
		private function insertScriptDataTag(tag:FLVTagScriptDataObject, first:Boolean = false):void
		{
			if (!_insertScriptDataTags)
			{
				_insertScriptDataTags = new Vector.<FLVTagScriptDataObject>();
			}
			
			if (first)
			{
				_insertScriptDataTags.unshift(tag);	// push front
			}
			else
			{
				_insertScriptDataTags.push(tag);
			}
		}
		
		private function flvTagHandler(tag:FLVTag):Boolean
		{
			// this is the new common FLVTag Parser's tag handler
			var i:int;
			
			if (_insertScriptDataTags)
			{
				for (i = 0; i < _insertScriptDataTags.length; i++)
				{
					var t:FLVTagScriptDataObject;
					var bytes:ByteArray;
					
					t = _insertScriptDataTags[i];
					t.timestamp = tag.timestamp;
					
					bytes = new ByteArray();
					t.write(bytes);
					_flvParserProcessed += bytes.length;
					attemptAppendBytes(bytes);
				}
				_insertScriptDataTags = null;			
			}
				
			if (_playForDuration >= 0)
			{
				if (_initialTime >= 0)	// until we know this, we don't know where to stop, and if we're enhanced-seeking then we need that logic to be what sets this up
				{
					var currentTime:Number = (tag.timestamp / 1000.0) + _fileTimeAdjustment;
					if (currentTime > (_initialTime + _playForDuration))
					{
						setState(HTTPStreamingState.STOP);
						_flvParserDone = true;
						if (_seekTime < 0)
						{
							_seekTime = _playForDuration + _initialTime;	// FMS behavior... the time is always the final time, even if we seek to past it
									// XXX actually, FMS  actually lets exactly one frame though at that point and that's why the time gets to be what it is
									// XXX that we don't exactly mimic that is also why setting a duration of zero doesn't do what FMS does (plays exactly that one still frame)
						}
						return false;
					}
				}
			}
			
			if (_enhancedSeekTarget < 0)
			{
				if (_initialTime < 0)
				{
					_initialTime = (tag.timestamp / 1000.0) + _fileTimeAdjustment;
				}
				
				if (_seekTime < 0)
				{
					_seekTime = (tag.timestamp / 1000.0) + _fileTimeAdjustment;
				}
			}		
			else // doing enhanced seek
			{
				if (tag is FLVTagVideo)
				{	
					if (_flvParserIsSegmentStart)	
					{
						var _muteTag:FLVTagVideo = new FLVTagVideo();
						_muteTag.timestamp = tag.timestamp; // may get overwritten, ok
						_muteTag.codecID = FLVTagVideo(tag).codecID; // same as in use
						_muteTag.frameType = FLVTagVideo.FRAME_TYPE_INFO;
						_muteTag.infoPacketValue = FLVTagVideo.INFO_PACKET_SEEK_START;
						// and start saving, with this as the first...
						_enhancedSeekTags = new Vector.<FLVTagVideo>();
						_enhancedSeekTags.push(_muteTag);
						_flvParserIsSegmentStart = false;
					}	
					
					if ((tag.timestamp / 1000.0) + _fileTimeAdjustment >= _enhancedSeekTarget)
					{
						_enhancedSeekTarget = -1;
						_seekTime = (tag.timestamp  / 1000.0) + _fileTimeAdjustment;
						if(_initialTime < 0)
						{
							_initialTime = _seekTime;
						}
						
						var _unmuteTag:FLVTagVideo = new FLVTagVideo();
						_unmuteTag.timestamp = tag.timestamp;  // may get overwritten, ok
						_unmuteTag.codecID = (_enhancedSeekTags[0]).codecID;	// take the codec ID of the corresponding SEEK_START
						_unmuteTag.frameType = FLVTagVideo.FRAME_TYPE_INFO;
						_unmuteTag.infoPacketValue = FLVTagVideo.INFO_PACKET_SEEK_END;
	
						_enhancedSeekTags.push(_unmuteTag);	
						
						// twiddle and dump
					
						for (i=0; i<_enhancedSeekTags.length; i++)
						{
							var vTag:FLVTagVideo;
							
							vTag = _enhancedSeekTags[i];
							//vTag.timestamp = tag.timestamp;
							if (vTag.codecID == FLVTagVideo.CODEC_ID_AVC && vTag.avcPacketType == FLVTagVideo.AVC_PACKET_TYPE_NALU)
							{
								// for H.264 we need to move the timestamp forward but the composition time offset backwards to compensate
								var adjustment:int = tag.timestamp - vTag.timestamp; // how far we are adjusting
								var compTime:int = vTag.avcCompositionTimeOffset;
								compTime = vTag.avcCompositionTimeOffset;
								compTime -= adjustment; // do the adjustment
								vTag.avcCompositionTimeOffset = compTime;	// save adjustment
								vTag.timestamp = tag.timestamp; // and adjust the timestamp forward
							}
							else
							{
								// the simple case
								vTag.timestamp = tag.timestamp;
							}
							bytes = new ByteArray();
							vTag.write(bytes);
							_flvParserProcessed += bytes.length;
							attemptAppendBytes(bytes);
						}
						_enhancedSeekTags = null;
						
						// and append this one
						bytes = new ByteArray();
						tag.write(bytes);
						_flvParserProcessed += bytes.length;
						attemptAppendBytes(bytes);
						if (_playForDuration >= 0)
						{
							return true;	// need to continue seeing the tags, and can't shortcut because we're being dropped off mid-segment
						}
						_flvParserDone = true;
						return false;	// and end of parsing (caller must dump rest, unparsed)
						
					} // past enhanced seek target
					else
					{
						_enhancedSeekTags.push(tag);
					}
				} // is video
				else if (tag is FLVTagScriptDataObject)
				{
					// ScriptDataObject tags simply pass through with unadjusted timestamps rather than discarding or saving for later
					bytes = new ByteArray();
					tag.write(bytes);
					_flvParserProcessed += bytes.length;
					attemptAppendBytes(bytes);
				} // else tag is FLVTagAudio, which we discard, unless...			
				else if (tag is FLVTagAudio) 
				{
					var aTag:FLVTagAudio = tag as FLVTagAudio;
					if (aTag.isCodecConfiguration)	// need to pass this through? (ex. AAC AudioConfig message)
					{
						// yes, can never skip initialization...
						bytes = new ByteArray();
						tag.write(bytes);
						_flvParserProcessed += bytes.length;
						attemptAppendBytes(bytes);
					}
				}
								
				return true;
			} // enhanced seek
			
			// finally, pass this one on to appendBytes...
			
			bytes = new ByteArray();
			tag.write(bytes);
			_flvParserProcessed += bytes.length;
			attemptAppendBytes(bytes);
			
			// probably done seeing the tags, unless we are in playForDuration mode...
			if (_playForDuration >= 0)
			{
				if (_segmentDuration >= 0 && _flvParserIsSegmentStart)
				{
					// if the segmentDuration has been reported, it is possible that we might be able to shortcut
					// but we need to be careful that this is the first tag of the segment, otherwise we don't know what duration means in relation to the tag timestamp

					_flvParserIsSegmentStart = false; // also used by enhanced seek, but not generally set/cleared for everyone. be careful.
					currentTime = (tag.timestamp / 1000.0) + _fileTimeAdjustment;
					if (currentTime + _segmentDuration >= (_initialTime + _playForDuration))
					{
						// it stops somewhere in this segment, so we need to keep seeing the tags
						return true;
					}
					else
					{
						// stop is past the end of this segment, can shortcut and stop seeing tags
						_flvParserDone = true;
						return false;
					}
				}
				else
				{
					return true;	// need to continue seeing the tags because either we don't have duration, or started mid-segment so don't know what duration means
				}
			}
			// else not in playForDuration mode...
			_flvParserDone = true;
			return false;
		}
	
		/**
		 * All decision making code for MBR switching happens in this method.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		private function autoAdjustQuality(seeking:Boolean):void
		{
			if (!_manualSwitchMode)
			{
				if (seeking)
				{
					// if we are in auto-switch, then the default is that we go to lowest rate to fulfill seek... fastest video buffer fill that way, plus for some forward seek index
					//  strategies (patent-pending) we don't even have the data we need to upswitch until we get the low-rate data
					// however, some people want to be able to control this

					setQualityLevel(0);

					return; // to avoid indenting rest of function for the else case
				}
				// auto-adjust quality level aka bitrate at this point
			
				// XXX IMPORTANT NOTE: WE AREN'T YET SETTING BUFFER TIME UPWARDS IN NON-SEEK SITUATIONS. NEED TO DO THIS SO RUNNING DRY IS LESS PAINFUL.
					
				// we work in ratios so that we can cancel kbps out of all the equations
				//   the lastDownloadRatio is "playback time of last segment downloaded" / "amount of time it took to download that whole segment, from request to finished"
				//   the switchRatio[proposed] is "claimed rate of proposed quality" / "claimed rate of current quality"
				//
				// there are exactly four cases we need to deal with, and since I'm not an optimist, I'll start from the worst case:
				// 1. the lastDownloadRatio is <1 and < switchRatio[current-1]: Bandwidth is way down, Switch to lowest rate immediately (even if there's an intermediate that might work).
				// 2. the lastDownloadRatio is <1 but >= switchRatio[current-1]: We should be able to keep going if we go down one level, do it
				// 3. the lastDownloadRatio is >= 1 but < switchRatio[current+1] OR no available rate is higher than current: Steady state where we like to be. Don't touch any knobs.
				// 4. the lastDownloadRatio is >= 1 and > switchRatio[current+1]: We can go up to rate n where n is the highest n for which lastDownloadRatio is still > switchRatio[n]
				//                                                                (but see caution about high lastDownloadRatio caused by cached response)
				//
				// XXX note: we don't currently do this, but we can hold off loading for a bit if and only if we are in state 3 AND the lastDownloadRatio is significantly >= 1
				//           (in addition to holding off loading if bufferLength is growing too far)
				// note: there is a danger that lastDownloadRatio is absurdly high because it is reflecting cached data. If that is detected, then in case 4 the switch up
				//           should only be a single quality level upwards rather than seeking the top rate instantly... just in case even one level up is actually too high a rate in reality
				//
				// XXX this is also where we could look at dropped-frame history and drop a rate level as well, if necessary. not yet implemented.
				//
				// so on to the code...
				
				var proposedLevel:int;
				var switchRatio:Number;
				
				if (_lastDownloadRatio < 1.0)
				{
					// case 1 and 2
					
					// first check to see if we are even able to switch down
					if (qualityLevel > 0)
					{
						// we are
						proposedLevel = qualityLevel - 1;
						switchRatio = _qualityRates[proposedLevel] / _qualityRates[qualityLevel];
						if (_lastDownloadRatio < switchRatio)
						{
							setQualityLevel(0);	// case 1, switch to lowest
						}
						else
						{
							setQualityLevel(proposedLevel); // case 2, down by one
						}
					}
					// else, already at lowest level
				} // case 1&2
				else
				{
					// case 3 and 4
					
					// first check to see if we are able to switch up
					if (qualityLevel < _numQualityLevels - 1) 
					{
						proposedLevel = qualityLevel + 1;
						switchRatio = _qualityRates[proposedLevel] / _qualityRates[qualityLevel];
						if (_lastDownloadRatio < switchRatio)
						{
							// case 3, don't touch anything. we're where we like to be. (well, actually, we like to be at the highest level with bandwidth to spare, but not everyone has that)
						}
						else
						{
							// is the last download ratio suspiciously high (cached data), or has aggressive upswitch been turned off?
							if (_lastDownloadRatio > 100.0 || !_aggressiveUpswitch)	// XXX 100.0 s/b constant value
							{
								// keep proposed level of +1
							}
							else
							{
								// seek better proposed level
								while (++proposedLevel < _numQualityLevels)
								{
									switchRatio = _qualityRates[proposedLevel] / _qualityRates[qualityLevel];
									if (_lastDownloadRatio < switchRatio)
										break; // found one that's too high
								}
								--proposedLevel;
							}
							setQualityLevel(proposedLevel);
						}
					}
					// else already at highest level, can't up-switch
				} // case 3&4
			} // !manualSwitch
		}
		
		private function byteSource(input:IDataInput, numBytes:Number):IDataInput
		{
			if (numBytes < 0)
			{
				return null;
			}
			
			if (numBytes)
			{
			 	if (_savedBytes.bytesAvailable + input.bytesAvailable < numBytes)
			 	{
					return null;
				}
			}
			else
			{
				if (_savedBytes.bytesAvailable + input.bytesAvailable < 1)
				{
					return null;
				}
			}
			
			if (_savedBytes.bytesAvailable)
			{
				var needed:int = numBytes - _savedBytes.bytesAvailable;
				if (needed > 0)
				{
					input.readBytes(_savedBytes, _savedBytes.length, needed);
				}
				
				return _savedBytes;
			}
			
			_savedBytes.length = 0;
			
			return input;
		}
		
		private function processAndAppend(inBytes:ByteArray):uint
		{
			var bytes:ByteArray;
			var processed:uint = 0;
			
			if (!inBytes)
			{
				return 0;
			}
			
			if (_flvParser)
			{
				inBytes.position = 0;	// rewind
				_flvParserProcessed = 0;
				_flvParser.parse(inBytes, true, flvTagHandler);	// common handler for FLVTags, parser consumes everything each time just as appendBytes does when in pass-through
				processed += _flvParserProcessed;
				if(!_flvParserDone)
				{
					// the common parser has more work to do in-path
					return processed;
				}
				else
				{
					// the common parser is done, so flush whatever is left and then pass through the rest of the segment
					bytes = new ByteArray();
					_flvParser.flush(bytes);
					_flvParser = null;	// and now we're done with it
				}
			}
			else
			{
				bytes = inBytes;
			}

			// now, 'bytes' is either what came in or what we massaged above 
			
			// (ES is now part of unified parser)
			
			processed += bytes.length;
			
			if (_state != HTTPStreamingState.STOP)	// we might exit this state
			{
				attemptAppendBytes(bytes);
			}
			
			return processed;
		}
		
		private function onMainTimer(timerEvent:TimerEvent):void
		{	
			var bytes:ByteArray;
			
			CONFIG::LOGGING
			{
				if (_state != previouslyLoggedState)
				{
					logger.debug("State = " + _state);
					previouslyLoggedState = _state;
				}
			}

			switch (_state)
			{
				case HTTPStreamingState.INIT:
					// do nothing, but not an error to be here. could make timer run slower if we wanted.
					_seekAfterInit = true;
					break;
					
			
				// SEEK case
			
				case HTTPStreamingState.SEEK:
					switch (_prevState)
					{
						case HTTPStreamingState.PLAY:
						case HTTPStreamingState.PLAY_START_NEXT:
						case HTTPStreamingState.PLAY_START_SEEK:
							_urlStreamVideo.close();	// immediate abort
							break;
						default:
							// already not open
							break;
					}
					
					_dataAvailable = false;
					_savedBytes.length = 0;		// correct? XXX
					
					if (_enhancedSeekEnabled)
					{						
						_enhancedSeekTarget = _seekTarget;
						// XXX perhaps could just reuse _seekTarget?
						// XXX there is potentially an H.264 depth issue here, where we need to do a --i to pick up enough more frames to render. must revisit.
					}
					setState(HTTPStreamingState.LOAD_SEEK);
					break;
				
				
				// LOAD cases
				case HTTPStreamingState.LOAD_WAIT:
					// XXX this delay needs to shrink proportionate to the last download ratio... when we're close to or under 1, it needs to be no delay at all
					// XXX unless the bufferLength is longer (this ties into how fast switching can happen vs. timeliness of dispatch to cover jitter in loading)
					
					// XXX for now, we have a simplistic dynamic handler, in that if downloads are going poorly, we are a bit more aggressive about prefetching
					if (this._lastDownloadRatio < 2.0)	// XXX this needs to be more linear, and/or settable
					{
						if (this.bufferLength < Math.max(7.5, this.bufferTime))	// XXX need to make settable
						{
							setState(HTTPStreamingState.LOAD_NEXT);
						}
					}
					else
					{
						if (this.bufferLength < Math.max(3.75, this.bufferTime))	// XXX need to make settable
						{
							setState(HTTPStreamingState.LOAD_NEXT);
						}					
					}
					break;
				
				case HTTPStreamingState.LOAD_NEXT:
					autoAdjustQuality(false);
					if (qualityLevelHasChanged)
					{
						bytes = fileHandler.flushFileSegment(_savedBytes.bytesAvailable ? _savedBytes : null);
						processAndAppend(bytes);
						
						// XXX for testing, putting this reporting here, but it really needs to be more informative and thus generated up in the autoAdjustQuality code
						var info:Object = new Object();
						info.code = NetStreamCodes.NETSTREAM_PLAY_TRANSITION_COMPLETE;
						info.level = "status";
						
						var sdoTag:FLVTagScriptDataObject = new FLVTagScriptDataObject();
						sdoTag.objects = ["onPlayStatus", info];
						insertScriptDataTag(sdoTag);
						
						qualityLevelHasChanged = false;
					}
					setState(HTTPStreamingState.LOAD);
					break;
					
				case HTTPStreamingState.LOAD_SEEK:
					// seek always must flush per contract
					if (!_seekAfterInit)
					{
						bytes = fileHandler.flushFileSegment(_savedBytes.bytesAvailable ? _savedBytes : null);
						// processAndAppend(bytes);	// XXX this might be unneccessary as we are about to RESET
					}
					CONFIG::FLASH_10_1
					{
						appendBytesAction(NetStreamAppendBytesAction.RESET_SEEK);
					}
					
					if (!_seekAfterInit)
					{
						autoAdjustQuality(true);
					}
						
					_seekAfterInit = false;
					setState(HTTPStreamingState.LOAD);
					break;
					
				case HTTPStreamingState.LOAD:
					
					var nextRequest:HTTPStreamRequest;
			
					// XXX the double test of _prevState in here is a little weird... might want to factor differently
					
					_segmentDuration = -1;	// we now track whether or not this has been reported yet for this segment by the Index or File handler
					switch (_prevState)
					{
						case HTTPStreamingState.LOAD_SEEK:
						case HTTPStreamingState.LOAD_SEEK_RETRY_WAIT:
							nextRequest = indexHandler.getFileForTime(_seekTarget, qualityLevel);
							break;
						case HTTPStreamingState.LOAD_NEXT:
						case HTTPStreamingState.LOAD_NEXT_RETRY_WAIT:
							nextRequest = indexHandler.getNextFile(qualityLevel);
							break;
						default:
							throw new Error("in HTTPStreamState.LOAD with unknown _prevState " + _prevState);
							break;
					}
					
					if (nextRequest != null && nextRequest.urlRequest != null)
					{
						_loadComplete = false;	

						CONFIG::LOGGING
						{
							logger.debug("loading: " + 	nextRequest.urlRequest.url.toString());
						}
											
						_urlStreamVideo.load(nextRequest.urlRequest);
						
						date = new Date();
						_lastDownloadStartTime = date.getTime();
			
						switch (_prevState)
						{
							case HTTPStreamingState.LOAD_SEEK:
							case HTTPStreamingState.LOAD_SEEK_RETRY_WAIT:
								setState(HTTPStreamingState.PLAY_START_SEEK);
								break;
							case HTTPStreamingState.LOAD_NEXT:
							case HTTPStreamingState.LOAD_NEXT_RETRY_WAIT:
								setState(HTTPStreamingState.PLAY_START_NEXT);
								break;
							default:
								throw new Error("in HTTPStreamState.LOAD(2) with unknown _prevState " + _prevState);
								break;
						}
					}
					else if(nextRequest != null && nextRequest.retryAfter >= 0)
					{
						date = new Date();
						_retryAfterWaitUntil = date.getTime() + (1000.0 * nextRequest.retryAfter);
						switch (_prevState)
						{
							case HTTPStreamingState.LOAD_SEEK:
							case HTTPStreamingState.LOAD_SEEK_RETRY_WAIT:
								setState(HTTPStreamingState.LOAD_SEEK_RETRY_WAIT);
								break;
							case HTTPStreamingState.LOAD_NEXT:
							case HTTPStreamingState.LOAD_NEXT_RETRY_WAIT:
								setState(HTTPStreamingState.LOAD_NEXT_RETRY_WAIT);
								break;
							default:
								throw new Error("in HTTPStreamState.LOAD(3) with unknown _prevState " + _prevState);
								break;
						}
					}
					else
					{
						bytes = fileHandler.flushFileSegment(_savedBytes.bytesAvailable ? _savedBytes : null);
						processAndAppend(bytes);
						setState(HTTPStreamingState.STOP);
						if (nextRequest != null && nextRequest.unpublishNotify)
						{
							_unpublishNotifyPending = true;								
						}
					}
					break;
				
				case HTTPStreamingState.LOAD_SEEK_RETRY_WAIT:								
				case HTTPStreamingState.LOAD_NEXT_RETRY_WAIT:
					var date:Date = new Date();
					if (date.getTime() > _retryAfterWaitUntil)
					{
						setState(HTTPStreamingState.LOAD);			
					}
					break;					

				case HTTPStreamingState.PLAY_START_NEXT:

					fileHandler.beginProcessFile(false, 0);
					setState(HTTPStreamingState.PLAY_START_COMMON);
					break;
					
				case HTTPStreamingState.PLAY_START_SEEK:		

					fileHandler.beginProcessFile(true, _seekTarget);
					setState(HTTPStreamingState.PLAY_START_COMMON);
					break;		
				
				case HTTPStreamingState.PLAY_START_COMMON:
					
					// need to run the common FLVParser?

					if (_initialTime < 0 || _seekTime < 0 || _insertScriptDataTags || _enhancedSeekTarget >= 0 || _playForDuration >= 0)
					{
						if (_enhancedSeekTarget >= 0 || _playForDuration >= 0)
						{
							_flvParserIsSegmentStart = true;	// warning, this isn't generally set/cleared, just used by these two cooperating things
						}
						_flvParser = new FLVParser(false);
						_flvParserDone = false;
					}
					setState(HTTPStreamingState.PLAY);
					break;
							
				case HTTPStreamingState.PLAY:

					var endSegment:Boolean = false;
					
					if (_dataAvailable)
					{
						var processLimit:int = 65000*4;	// XXX needs to be settable
						var processed:int = 0;
													
						if (_enhancedSeekTarget >= 0)
						{
							processLimit = 0;	// override slow-load
						}
						
						/*
						if(_bytesAvailable < fetchBytes)
						{
							// in the past, we simply returned in this case, figuring it was cheaper to just wait for enough to accumulate
							// we might want to revisit after performance testing
							fetchBytes = _bytesAvailable;
						}
						*/
						
						var input:IDataInput = null;
						_dataAvailable = false;
						while (_state == HTTPStreamingState.PLAY && (input = byteSource(_urlStreamVideo, fileHandler.inputBytesNeeded)))
						{
							bytes = fileHandler.processFileSegment(input);
		
							// XXX need to deal with end of file issues
							processed += processAndAppend(bytes);
							
							if (processLimit > 0 && processed >= processLimit)
							{
								_dataAvailable = true;
								break;
							}
						}
						
						if(_state != HTTPStreamingState.PLAY)
							break;
						
						// XXX if the reason we bailed is that we didn't have enough bytes, then if loadComplete we need to consume the rest into our save buffer
						// OR, if we don't do cross-segment saving then we simply need to ensure that we don't return but simply fall through to a later case
						// for now, we do the latter (also see below)
						if (_loadComplete && !input)
						{
							endSegment = true;
						}
					}
					else
					{
						if (_loadComplete)
						{
							endSegment = true;
						}
					}
					
					if (endSegment)
					{
						// then save any leftovers for the next segment round. if this is a kind of filehandler that needs that, they won't suck dry in onEndSegment.
						if (_urlStreamVideo.bytesAvailable)
						{
							_urlStreamVideo.readBytes(_savedBytes);
						}
						else
						{
							_savedBytes.length = 0; // just to be sure
						}
																	
						setState(HTTPStreamingState.END_SEGMENT);
					}
					
					break;
				
				case HTTPStreamingState.END_SEGMENT:
					// give fileHandler a crack at any remaining data 

					bytes = fileHandler.endProcessFile(_savedBytes.bytesAvailable ? _savedBytes : null);
					processAndAppend(bytes);
					_lastDownloadRatio = _segmentDuration / _lastDownloadDuration;	// urlcomplete would have fired by now, otherwise we couldn't be done, and onEndSegment is the last possible chance to report duration

					if (_state != HTTPStreamingState.STOP && _state != HTTPStreamingState.HALT)
					{ 
						setState(HTTPStreamingState.LOAD_WAIT);
					}
					break;

				case HTTPStreamingState.STOP:
						var playCompleteInfo:Object = new Object();
			            playCompleteInfo.code = NetStreamCodes.NETSTREAM_PLAY_COMPLETE;
			            playCompleteInfo.level = "status";
			                                    
			            var playCompleteInfoSDOTag:FLVTagScriptDataObject = new FLVTagScriptDataObject();
			            playCompleteInfoSDOTag.objects = ["onPlayStatus", playCompleteInfo];
			
			            var tagBytes:ByteArray = new ByteArray();
			            playCompleteInfoSDOTag.write(tagBytes);
			
			   			CONFIG::FLASH_10_1
						{
							appendBytesAction(NetStreamAppendBytesAction.END_SEQUENCE);
							appendBytesAction(NetStreamAppendBytesAction.RESET_SEEK);
						}
			            
			            attemptAppendBytes(tagBytes);
			            setState(HTTPStreamingState.HALT);
				  		
			            break;
			    		
				case HTTPStreamingState.HALT:
					// do nothing. timer could run slower in this state.
					break;

				default:
					throw new Error("HTTPStream cannot run undefined _state "+_state);
					break;
			}
		}
		
		private function onURLStatus(progressEvent:ProgressEvent):void
		{
			_dataAvailable = true;
		}
		
		private function onURLComplete(event:Event):void
		{
			var date:Date = new Date;
			
			_lastDownloadDuration = (date.getTime() - _lastDownloadStartTime) / 1000.0;
			_loadComplete = true;
		}

		private function onRequestLoadIndexFile(event:HTTPStreamingIndexHandlerEvent):void
		{
			var urlLoader:URLLoader = new URLLoader(event.request);
			var requestContext:Object = event.requestContext;
			if (event.binaryData)
			{
				urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
			}
			else
			{
				urlLoader.dataFormat = URLLoaderDataFormat.TEXT;
			}
			
			urlLoader.addEventListener(Event.COMPLETE, onIndexLoadComplete);
			urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onIndexURLError);
			urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onIndexURLError);

			function onIndexLoadComplete(innerEvent:Event):void
			{
				urlLoader.removeEventListener(Event.COMPLETE, onIndexLoadComplete);
				urlLoader.removeEventListener(IOErrorEvent.IO_ERROR, onIndexURLError);
				urlLoader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onIndexURLError);

				indexHandler.processIndexData(urlLoader.data, requestContext);
			}
			
			function onIndexURLError(errorEvent:Event):void
			{
				urlLoader.removeEventListener(Event.COMPLETE, onIndexLoadComplete);
				urlLoader.removeEventListener(IOErrorEvent.IO_ERROR, onIndexURLError);
				urlLoader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onIndexURLError);

				handleURLError();
			}
		}

		private function onSegmentDurationFromFileHandler(event:HTTPStreamingFileHandlerEvent):void
		{
			_segmentDuration = event.segmentDuration;
		}
		
		private function onSegmentDurationFromIndexHandler(event:HTTPStreamingIndexHandlerEvent):void	// TOOD: unify this with the above so we don't need to duplicate
		{
			_segmentDuration = event.segmentDuration;
		}

		private function onRates(event:HTTPStreamingIndexHandlerEvent):void
		{
			_qualityRates = event.rates;
			_streamNames = event.streamNames;
			_numQualityLevels = _qualityRates.length;
		}	

		private function onIndexReady(event:HTTPStreamingIndexHandlerEvent):void
		{
			if (!indexIsReady)
			{
				if (event.live && _dvrInfo == null && !isNaN(event.offset))
				{
					_seekTarget = event.offset;
				}
				
				_urlStreamVideo = new URLStream();
			
				_urlStreamVideo.addEventListener(ProgressEvent.PROGRESS				, onURLStatus		, false, 0, true);	
				_urlStreamVideo.addEventListener(Event.COMPLETE						, onURLComplete		, false, 0, true);
				_urlStreamVideo.addEventListener(IOErrorEvent.IO_ERROR				, onVideoURLError	, false, 0, true);
				_urlStreamVideo.addEventListener(SecurityErrorEvent.SECURITY_ERROR	, onVideoURLError	, false, 0, true);
	
				setState(HTTPStreamingState.SEEK);	// was LOAD_SEEK, now want to pick up enhanced seek setup, if applicable. in the future, might want to change back?
				indexIsReady = true;
			}
		}
		
		private function onVideoURLError(event:Event):void
		{			
			handleURLError();
		}
		
		private function handleURLError():void
		{
			// We map all URL errors to Play.StreamNotFound.
			dispatchEvent
				( new NetStatusEvent
					( NetStatusEvent.NET_STATUS
					, false
					, false
					, {code:NetStreamCodes.NETSTREAM_PLAY_STREAMNOTFOUND, level:"error"}
					)
				);
		}

		private function onScriptDataFromIndexHandler(event:HTTPStreamingIndexHandlerEvent):void
		{
			onScriptData(event.scriptDataObject, event.scriptDataFirst, event.scriptDataImmediate);
		}
		
		private function onScriptDataFromFileHandler(event:HTTPStreamingFileHandlerEvent):void
		{
			onScriptData(event.scriptDataObject, event.scriptDataFirst, event.scriptDataImmediate);			// TODO: somehow figure out how to not need duplicate listeners
		}
		
		private function onErrorFromFileHandler(event:HTTPStreamingFileHandlerEvent):void
		{
			// We map file handler error to Play.NETSTREAM_PLAY_FILESTRUCTUREINVALID.
			setState(HTTPStreamingState.HALT);
			dispatchEvent
				( new NetStatusEvent
					( NetStatusEvent.NET_STATUS
					, false
					, false
					, {code:NetStreamCodes.NETSTREAM_PLAY_FILESTRUCTUREINVALID, level:"error"}
					)
				);
		}
		
		private function onScriptData(scriptDataObject:FLVTagScriptDataObject, scriptDataFirst:Boolean, scriptDataImmediate:Boolean):void
		{
			CONFIG::LOGGING
			{
				logger.debug("onScriptData called");
			}
			
			if (scriptDataImmediate)
			{
				if (client)
				{
					if (client.hasOwnProperty(scriptDataObject.objects[0]))
					{
						client[scriptDataObject.objects[0]](scriptDataObject.objects[1]);	// XXX note that we can only support a single argument for immediate dispatch
					}
				}
			}
			else
			{
				insertScriptDataTag(scriptDataObject, scriptDataFirst);
			}
		}
		
		private function onDVRStreamInfo(event:DVRStreamInfoEvent):void
		{
			_dvrInfo = event.info as DVRInfo;
			dispatchEvent(event.clone());
		}

		/**
		 * All errors from file index handler and file handler are passed to HTTPNetStream
		 * via MediaErrorEvent. 
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */		
		private function onIndexError(event:HTTPStreamingIndexHandlerEvent):void
		{
			// We map all Index errors to Play.StreamNotFound.
			dispatchEvent
				( new NetStatusEvent
					( NetStatusEvent.NET_STATUS
					, false
					, false
					, {code:NetStreamCodes.NETSTREAM_PLAY_STREAMNOTFOUND, level:"error"}
					)
				);
		}
		
		private function setQualityLevel(value:int):void
		{
			if (value >= 0 && value < _numQualityLevels)
			{
				if (value != _qualityLevel)
				{
					_qualityLevel = value;
					qualityLevelHasChanged = true;
					dispatchEvent
						( new NetStatusEvent
							( NetStatusEvent.NET_STATUS
							, false
							, false
							, {code:NetStreamCodes.NETSTREAM_PLAY_TRANSITION, level:"status", details:_streamNames[value]}
							)
						); 
				}
			}
			else
			{
				throw new Error("qualityLevel cannot be set to this value at this time");
			}
		}
		
		private function setQualityLevelForStreamName(streamName:String):void
		{
			var level:int = -1;
			
			if (_streamNames != null)
			{
				for (var i:int = 0; i < _streamNames.length; i++)
				{
					if (streamName == _streamNames[i])
					{
						level = i;
						break;
					}
				}
			}
			
			if (level != -1)
			{
				setQualityLevel(level);
			}
		}

		private function attemptAppendBytes(bytes:ByteArray):void
		{
			// Do nothing if this is not an Argo player.
			CONFIG::FLASH_10_1
			{
				super.appendBytes(bytes);
			}
		}
		
		private function onNetStatus(event:NetStatusEvent):void
		{
			if (event.info.code == NetStreamCodes.NETSTREAM_BUFFER_EMPTY && _state == HTTPStreamingState.HALT) 
			{
				finishStopProcess();
			}
		}
		
		private function finishStopProcess():void
		{
			if (_unpublishNotifyPending)
			{
				dispatchEvent
					( new NetStatusEvent
						( NetStatusEvent.NET_STATUS
						, false
						, false
						, {code:NetStreamCodes.NETSTREAM_PLAY_UNPUBLISH_NOTIFY, level:"status"}
						)
					);
					
				_unpublishNotifyPending = false; 
			}
		}

		private static const MAIN_TIMER_INTERVAL:int = 25;
		
		private var _indexInfo:HTTPStreamingIndexInfoBase = null;
		private var _numQualityLevels:int = 0;
		private var _qualityRates:Array; 	
		private var _streamNames:Array;
		private var _segmentDuration:Number = -1;
		private var _urlStreamVideo:URLStream = null;
		private var _loadComplete:Boolean = false;
		private var mainTimer:Timer;
		private var _dataAvailable:Boolean = false;
		private var _qualityLevel:int = 0;
		private var qualityLevelHasChanged:Boolean = false;
		private var _seekTarget:Number = -1;
		private var _lastDownloadStartTime:Number = -1;
		private var _lastDownloadDuration:Number;
		private var _lastDownloadRatio:Number = 0;
		private var _manualSwitchMode:Boolean = false;
		private var _aggressiveUpswitch:Boolean = true;	// XXX needs a getter and setter, or to be part of a pluggable rate-setter
		private var indexHandler:HTTPStreamingIndexHandlerBase;
		private var fileHandler:HTTPStreamingFileHandlerBase;
		private var _totalDuration:Number = -1;
		private var _enhancedSeekTarget:Number = -1;	// now in seconds, just like everything else
		private var _enhancedSeekEnabled:Boolean = false;
		private var _enhancedSeekTags:Vector.<FLVTagVideo>;
		private var _flvParserIsSegmentStart:Boolean = false;
		private var _savedBytes:ByteArray = null;
		private var _state:String = HTTPStreamingState.INIT;
		private var _prevState:String = null;
		private var _seekAfterInit:Boolean;
		private var indexIsReady:Boolean = false;
		private var _insertScriptDataTags:Vector.<FLVTagScriptDataObject> = null;
		private var _flvParser:FLVParser = null;	// this is the new common FLVTag Parser
		private var _flvParserDone:Boolean = true;	// signals that common parser has done everything and can be removed from path
		private var _flvParserProcessed:uint;
		private var _initialTime:Number = -1;	// this is the timestamp derived at start-of-play (offset or not)... what FMS would call "0"
		private var _seekTime:Number = -1;		// this is the timestamp derived at end-of-seek (enhanced or not)... what we need to add to super.time (assuming play started at zero)
		private var _fileTimeAdjustment:Number = 0;	// this is what must be added (IN SECONDS) to the timestamps that come in FLVTags from the file handler to get to the index handler timescale
													// XXX an event to set the _fileTimestampAdjustment is needed
		private var _playForDuration:Number = -1;
		private var _lastValidTimeTime:Number = 0;
		private var _retryAfterWaitUntil:Number = 0;	// millisecond timestamp (as per date.getTime) of when we retry next
		
		private var _dvrInfo:DVRInfo = null;
		private var _unpublishNotifyPending:Boolean = false;
		
		CONFIG::LOGGING
		{
			private static const logger:org.osmf.logging.Logger = org.osmf.logging.Log.getLogger("org.osmf.net.httpstreaming.HTTPNetStream");
			
			private var previouslyLoggedState:String;
		}
	}
}
