/*****************************************************
*  
*  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.f4f
{
	import __AS3__.vec.Vector;
	
	import flash.net.URLRequest;
	import flash.utils.ByteArray;
	
	import org.osmf.elements.f4mClasses.BootstrapInfo;
	import org.osmf.events.DVRStreamInfoEvent;
	import org.osmf.events.HTTPStreamingFileHandlerEvent;
	import org.osmf.events.HTTPStreamingIndexHandlerEvent;
	import org.osmf.net.dvr.DVRUtils;
	import org.osmf.net.httpstreaming.HTTPStreamRequest;
	import org.osmf.net.httpstreaming.HTTPStreamingFileHandlerBase;
	import org.osmf.net.httpstreaming.HTTPStreamingIndexHandlerBase;
	import org.osmf.net.httpstreaming.flv.FLVTagScriptDataObject;

	CONFIG::LOGGING 
	{	
		import org.osmf.logging.Logger;
	}

	[ExcludeClass]
	
	/**
	 * @private
	 * 
	 * The actual implementation of HTTPStreamingFileIndexHandlerBase.  It
	 * handles the indexing scheme of an F4V file.
	 */	
	public class HTTPStreamingF4FIndexHandler extends HTTPStreamingIndexHandlerBase
	{
		/**
		 * Constructor.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function HTTPStreamingF4FIndexHandler(fileHandler:HTTPStreamingFileHandlerBase, fragmentsThreshold:uint = DEFAULT_FRAGMENTS_THRESHOLD)
		{
			super();
			
			currentQuality = -1;
			currentFAI = null;
			fragmentRunTablesUpdating = false;		
			this.fileHandler = fileHandler;	
			this.fragmentsThreshold = fragmentsThreshold;
			dvrGetStreamInfoCall = false;
			
			fileHandler.addEventListener(HTTPStreamingFileHandlerEvent.NOTIFY_BOOTSTRAP_BOX, onNewBootstrapBox);
		}
		
		/**
		 * @private
		 */
		public function get bootstrapInfo():AdobeBootstrapBox
		{
			return currentQuality < 0? null : bootstrapBoxes[currentQuality];
		}
		
		/**
		 * @private
		 */
		override public function dvrGetStreamInfo(indexInfo:Object):void
		{
			dvrGetStreamInfoCall = true;
			playInProgress = false;
			initialize(indexInfo);
		} 
		
		/**
		 * @private
		 */
		override public function initialize(indexInfo:Object):void
		{
			// Make sure we have an info object of the expected type.
			f4fIndexInfo = indexInfo as HTTPStreamingF4FIndexInfo;
			if (f4fIndexInfo == null || f4fIndexInfo.streamInfos == null || f4fIndexInfo.streamInfos.length <= 0)
			{
				dispatchEvent(new HTTPStreamingIndexHandlerEvent(HTTPStreamingIndexHandlerEvent.NOTIFY_ERROR));
				return;					
			}
			
			bootstrapBoxes = new Vector.<AdobeBootstrapBox>(f4fIndexInfo.streamInfos.length);
			fragmentRunTablesUpdating= false;
			playInProgress = false;
			pendingIndexLoads = 0;
			pureLiveOffset = NaN;
			
			serverBaseURL = f4fIndexInfo.serverBaseURL;			
			streamInfos = f4fIndexInfo.streamInfos;
			
			var bootstrapBox:AdobeBootstrapBox;
			for (var i:int = 0; i < streamInfos.length; i++)
			{
				var bootstrap:BootstrapInfo = streamInfos[i].bootstrapInfo;
				if (bootstrap == null || (bootstrap.url == null && bootstrap.data == null))
				{
					dispatchEvent(new HTTPStreamingIndexHandlerEvent(HTTPStreamingIndexHandlerEvent.NOTIFY_ERROR));
					return;					
				}
				if (bootstrap.data != null)
				{
					bootstrapBox = processBootstrapData(bootstrap.data, null);
					if (bootstrapBox == null)
					{
						dispatchEvent(new HTTPStreamingIndexHandlerEvent(HTTPStreamingIndexHandlerEvent.NOTIFY_ERROR));
						return;					
					}
					bootstrapBoxes[i] = bootstrapBox;
				}
				else
				{
					pendingIndexLoads++;
					dispatchEvent
						(	new HTTPStreamingIndexHandlerEvent
								( HTTPStreamingIndexHandlerEvent.REQUEST_LOAD_INDEX 
								, false
								, false
								, false
								, NaN
								, null
								, null
								, new URLRequest(bootstrap.url)
								, i
								, true
								)
						);
				}
			}
			
			if (pendingIndexLoads == 0)
			{
				dispatchEvent
					( new HTTPStreamingIndexHandlerEvent
						( HTTPStreamingIndexHandlerEvent.NOTIFY_RATES
						, false
						, false
						, false
						, NaN
						, getStreamNames(streamInfos)
						, getQualityRates(streamInfos)
						)
					);

				notifyIndexReady(0);
			}
		}
		
		/**
		 * @private
		 */
		override public function processIndexData(data:*, indexContext:Object):void
		{
			var index:int = indexContext as int;
			var bootstrapBox:AdobeBootstrapBox = processBootstrapData(data, index);

			CONFIG::LOGGING
			{			
				logger.debug("processIndexData: " + index + " pendingIndexUpdates: " + pendingIndexUpdates);
				logger.debug("abst version: " + bootstrapBox.bootstrapVersion + 
					" fragments calculated: " + bootstrapBox.totalFragments +
					" fragments from segment run table: " + bootstrapBox.segmentRunTables[0].totalFragments);
			}
			
			if (bootstrapBox == null)
			{
				dispatchEvent(new HTTPStreamingIndexHandlerEvent(HTTPStreamingIndexHandlerEvent.NOTIFY_ERROR));
				return;					
			}

			if (!fragmentRunTablesUpdating) 
			{
				pendingIndexLoads--;
			}
			else
			{
				pendingIndexUpdates--;
				if (pendingIndexUpdates == 0)
				{
					fragmentRunTablesUpdating = false;
					notifyTotalDuration(bootstrapBox.totalDuration / bootstrapBox.timeScale, indexContext as int);
					dispatchDVRStreamInfo(bootstrapBox);
				}
			}
			
			if (bootstrapBoxes[index] == null || 
				bootstrapBoxes[index].bootstrapVersion < bootstrapBox.bootstrapVersion ||
				bootstrapBoxes[index].currentMediaTime < bootstrapBox.currentMediaTime)
			{
				delay = 0.05; 		
				bootstrapBoxes[index] = bootstrapBox;
			}

			if (pendingIndexLoads == 0 && !fragmentRunTablesUpdating)
			{
				dispatchEvent
					( new HTTPStreamingIndexHandlerEvent
						( HTTPStreamingIndexHandlerEvent.NOTIFY_RATES
						, false
						, false
						, false
						, NaN
						, getStreamNames(streamInfos)
						, getQualityRates(streamInfos)
						)
					);

				notifyIndexReady(index);
			}
		}	
		
		/**
		 * @private
		 */
		override public function getFileForTime(time:Number, quality:int):HTTPStreamRequest
		{
			var abst:AdobeBootstrapBox = bootstrapBoxes[quality];
			var streamRequest:HTTPStreamRequest = null;

			if (!playInProgress && stopPlaying(abst))
			{
				return null;
			}
							
			checkMetadata(quality, abst);
			
			var frt:AdobeFragmentRunTable = getFragmentRunTable(abst);
			if (	time >= 0
				&&	time * abst.timeScale <= abst.currentMediaTime
				&& 	quality >= 0
				&&  quality < streamInfos.length
			   )
			{
				currentFAI = frt.findFragmentIdByTime(
					time * abst.timeScale, abst.currentMediaTime, abst.contentComplete()? false : abst.live);
				if (currentFAI == null || fragmentOverflow(abst, currentFAI.fragId))
				{
					if (abst.contentComplete())
					{
						if (abst.live) // live/DVR playback stops
						{
							return new HTTPStreamRequest(null, quality, -1, -1, true);
						}
						else
						{
							return null;
						}
					}
					else
					{
						adjustDelay();
						refreshBootstrapInfo(quality);
						return new HTTPStreamRequest(null, quality, 0, delay);
					}
				}
				
				playInProgress = true;
				var fdp:FragmentDurationPair = frt.fragmentDurationPairs[0];
				var segId:uint = abst.findSegmentId(currentFAI.fragId - fdp.firstFragment + 1);
				var requestUrl:String = "";
				if ((streamInfos[quality].streamName as String).indexOf("http") != 0)
				{
					requestUrl = serverBaseURL + "/" + streamInfos[quality].streamName + "Seg" + segId + "-Frag" + currentFAI.fragId;
				}
				else
				{
					requestUrl = streamInfos[quality].streamName + "Seg" + segId + "-Frag" + currentFAI.fragId;
				}
				
				CONFIG::LOGGING
				{
					logger.debug("getFileForTime URL = " + requestUrl);
				}

				streamRequest = new HTTPStreamRequest(requestUrl);
				checkQuality(quality);
				notifyFragmentDuration(currentFAI.fragDuration / abst.timeScale);
			}
			
			CONFIG::LOGGING
			{
				if (streamRequest == null)
				{
					logger.debug("getFileForTime No URL for time=" + time + " and quality=" + quality);
				}
			}
			
			return streamRequest;
		}
		
		/**
		 * @private
		 */
		override public function getNextFile(quality:int):HTTPStreamRequest
		{
			var abst:AdobeBootstrapBox = bootstrapBoxes[quality];
			var streamRequest:HTTPStreamRequest = null;
/*
			if (quality != this.currentQuality)
			{
				var curAbst:AdobeBootstrapBox = bootstrapBoxes[currentQuality];
				if (currentFAI == null || (currentFAI.fragmentEndTime*abst.timeScale/curAbst.timeScale > abst.currentMediaTime))
				{
					adjustDelay();
					refreshBootstrapInfo(quality);					
					return new HTTPStreamRequest(null, quality, 0, delay);
				}

				return getFileForTime(currentFAI.fragmentEndTime / curAbst.timeScale, quality); 
			}
*/			
			if (!playInProgress && stopPlaying(abst))
			{
				return null;
			}

			checkMetadata(quality, abst);

			if (quality >= 0 && quality < streamInfos.length)
			{
				var frt:AdobeFragmentRunTable = getFragmentRunTable(abst);
				var oldCurrentFAI:FragmentAccessInformation = currentFAI;
				currentFAI = frt.validateFragment(currentFAI.fragId + 1, abst.currentMediaTime, abst.contentComplete()? false : abst.live);
				if (currentFAI == null || fragmentOverflow(abst, currentFAI.fragId))
				{
					if (!abst.live || abst.contentComplete())
					{
						if (abst.live) // live/DVR playback stops
						{
							return new HTTPStreamRequest(null, quality, -1, -1, true);
						}
						else
						{
							return null;
						}
					}
					else
					{
						adjustDelay();
						currentFAI = oldCurrentFAI;
						refreshBootstrapInfo(quality);
						return new HTTPStreamRequest(null, quality, 0, delay);
					}
				}

				playInProgress = true;
				var fdp:FragmentDurationPair = frt.fragmentDurationPairs[0];
				var segId:uint = abst.findSegmentId(currentFAI.fragId - fdp.firstFragment + 1);
				var requestUrl:String = "";
				if ((streamInfos[quality].streamName as String).indexOf("http") != 0)
				{
					requestUrl = serverBaseURL + "/" + streamInfos[quality].streamName + "Seg" + segId + "-Frag" + currentFAI.fragId;
				}
				else
				{
					requestUrl = streamInfos[quality].streamName + "Seg" + segId + "-Frag" + currentFAI.fragId;
				}
			
				streamRequest = new HTTPStreamRequest(requestUrl);
				checkQuality(quality);
				notifyFragmentDuration(currentFAI.fragDuration / abst.timeScale);
					
				CONFIG::LOGGING
				{
					logger.debug("getNextFile URL = " + requestUrl);
				}				
			}
			
			CONFIG::LOGGING
			{
				if (streamRequest == null)
				{
					logger.debug("getNextFile No URL for quality=" + quality);
				}
			}
			
			return streamRequest;
		}
		
		/**
		 * @private
		 * 
		 * Given timeBias, calculates the corresponding segment duration.
		 */
		internal function calculateSegmentDuration(abst:AdobeBootstrapBox, timeBias:Number):Number
		{
			var fragmentDurationPairs:Vector.<FragmentDurationPair> = (abst.fragmentRunTables)[0].fragmentDurationPairs;
			var fragmentId:uint = currentFAI.fragId;
			
			var index:int =  fragmentDurationPairs.length - 1;
			while (index >= 0)
			{
				var fragmentDurationPair:FragmentDurationPair = fragmentDurationPairs[index];
				if (fragmentDurationPair.firstFragment <= fragmentId)
				{
					var duration:Number = fragmentDurationPair.duration;
					var durationAccrued:Number = fragmentDurationPair.durationAccrued;
					durationAccrued += (fragmentId - fragmentDurationPair.firstFragment) * fragmentDurationPair.duration;
					if (timeBias > 0)
					{
						duration -= (timeBias - durationAccrued);
					}
					
					return duration;
				}
				else
				{
					index--;
				}
			}
			
			return 0;
		}

		// Internal
		//
		
		/**
		 * When there is an MBR switching and the switched-to fragment is DRM protected,
		 * we need to append the additionalHeader that contains the DRM metadata to the Flash Player
		 * for that fragment before any additional TCMessage can be appended to FP.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		private function checkQuality(quality:int):void
		{
			if (currentQuality != quality)
			{
				var prevAdditionalHeader:ByteArray = (currentQuality < 0)? null : streamInfos[currentQuality].additionalHeader;
				currentQuality = quality;
				var newAdditionalHeader:ByteArray = streamInfos[currentQuality].additionalHeader;
				
				// Strictly speaking, the != comparison of additional header is not enough. 
				// Ideally, we need to do bytewise comparison, however there might be a performance
				// hit considering the size of the additional header.
				if (newAdditionalHeader != prevAdditionalHeader && newAdditionalHeader != null)
				{
					var flvTag:FLVTagScriptDataObject = new FLVTagScriptDataObject();
					flvTag.data = newAdditionalHeader;
					dispatchEvent
						( new HTTPStreamingIndexHandlerEvent
							( HTTPStreamingIndexHandlerEvent.NOTIFY_SCRIPT_DATA
							, false
							, false
							, false
							, NaN
							, null
							, null
							, null
							, null
							, true
							, 0
							, flvTag
							, true
							, false
							)
						);
				}
			}
		}
		
		private function checkMetadata(quality:int, abst:AdobeBootstrapBox):void
		{
			if (currentQuality != quality)
			{
				notifyTotalDuration(abst.totalDuration / abst.timeScale, quality);
			}
		}

		private function refreshBootstrapInfo(quality:uint):void
		{
			pendingIndexUpdates++;
			fragmentRunTablesUpdating = true;
			CONFIG::LOGGING
			{
				logger.debug("refresh frt: " + (streamInfos[quality] as HTTPStreamingF4FStreamInfo).bootstrapInfo.url);
			}
			dispatchEvent
				(	new HTTPStreamingIndexHandlerEvent
						( HTTPStreamingIndexHandlerEvent.REQUEST_LOAD_INDEX 
						, false
						, false
						, false
						, NaN
						, null
						, null
						, new URLRequest((streamInfos[quality] as HTTPStreamingF4FStreamInfo).bootstrapInfo.url)
						, quality
						, true
						)
				);				
		}
		
		private function processBootstrapData(data:*, indexContext:Object):AdobeBootstrapBox
		{
			var bootstrapBox:AdobeBootstrapBox = null;
			
			var parser:BoxParser = new BoxParser();
			data.position = 0;
			parser.init(data);
			try
			{
				var boxes:Vector.<Box> = parser.getBoxes();
			}
			catch (e:Error)
			{
				boxes = null;
			}
			
			if (boxes == null || boxes.length < 1)
			{
				return null;
			}
			
			bootstrapBox = boxes[0] as AdobeBootstrapBox;
			if (bootstrapBox == null)
			{
				return null;
			}
			
			if (serverBaseURL == null || serverBaseURL.length <= 0)
			{
				if (bootstrapBox.serverBaseURLs == null || bootstrapBox.serverBaseURLs.length <= 0)
				{
					// If serverBaseURL is not set from the external, we need to pick 
					// a server base URL from the bootstrap box. For now, we just
					// pick the first one. It is an error if the server base URL is null 
					// under this circumstance.
					return null;
				}
			}
			
			return bootstrapBox;
		}	

		private function getQualityRates(streamInfos:Vector.<HTTPStreamingF4FStreamInfo>):Array
		{
			var rates:Array = [];
			
			if (streamInfos.length >= 1)
			{
				for (var i:int = 0; i < streamInfos.length; i++)
				{
					var streamInfo:HTTPStreamingF4FStreamInfo = streamInfos[i] as HTTPStreamingF4FStreamInfo;
					rates.push(streamInfo.bitrate);
				}
			}
			
			return rates;
		}
		
		private function getStreamNames(streamInfos:Vector.<HTTPStreamingF4FStreamInfo>):Array
		{
			var streamNames:Array = [];
			
			if (streamInfos.length >= 1)
			{
				for (var i:int = 0; i < streamInfos.length; i++)
				{
					var streamInfo:HTTPStreamingF4FStreamInfo = streamInfos[i] as HTTPStreamingF4FStreamInfo;
					streamNames.push(streamInfo.streamName);
				}
			}
			
			return streamNames;
		}

		private function getFragmentRunTable(abst:AdobeBootstrapBox):AdobeFragmentRunTable
		{
			// For now, we assume that there is only one fragment run table.
			return abst.fragmentRunTables[0];
		}
		
		private function notifyTotalDuration(duration:Number, quality:int):void
		{
			var sdo:FLVTagScriptDataObject = new FLVTagScriptDataObject();
			var metaInfo:Object = this.f4fIndexInfo.streamInfos[quality].streamMetadata;
			if (metaInfo == null)
			{
				metaInfo = new Object();
			}
			metaInfo.duration = duration;

			sdo.objects = ["onMetaData", metaInfo];
			dispatchEvent
				( new HTTPStreamingIndexHandlerEvent
					( HTTPStreamingIndexHandlerEvent.NOTIFY_SCRIPT_DATA
					, false
					, false
					, false
					, NaN
					, null
					, null
					, null
					, null
					, false
					, 0
					, sdo
					, false
					, true
					)
				);
		}
		
		private function notifyFragmentDuration(duration:Number):void
		{
			dispatchEvent
				(	new HTTPStreamingIndexHandlerEvent
						( HTTPStreamingIndexHandlerEvent.NOTIFY_SEGMENT_DURATION 
						, false
						, false
						, false
						, NaN
						, null
						, null
						, null
						, null
						, true
						, duration
						)
				);				
		}
		
		private function notifyIndexReady(quality:int):void
		{
			var abst:AdobeBootstrapBox = bootstrapBoxes[quality];
			var frt:AdobeFragmentRunTable = getFragmentRunTable(abst);

			dispatchDVRStreamInfo(abst);

			if (!dvrGetStreamInfoCall)
			{
				if (abst.live && f4fIndexInfo.dvrInfo == null && isNaN(pureLiveOffset))
				{
					pureLiveOffset = (abst.currentMediaTime - offsetFromCurrent * abst.timeScale) > 0? abst.currentMediaTime / abst.timeScale - offsetFromCurrent : NaN
				}

				dispatchEvent
					( new HTTPStreamingIndexHandlerEvent
						( HTTPStreamingIndexHandlerEvent.NOTIFY_INDEX_READY
						, false
						, false
						, abst.live
						, pureLiveOffset 
						)
					);
			}
			dvrGetStreamInfoCall = false;
		}

		private function stopPlaying(abst:AdobeBootstrapBox):Boolean
		{
			var frt:AdobeFragmentRunTable = getFragmentRunTable(abst);
			if ((f4fIndexInfo.dvrInfo == null && abst.live && frt.tableComplete()) ||
				(f4fIndexInfo.dvrInfo != null && f4fIndexInfo.dvrInfo.offline))
			{
				return true;
			}
			
			return false;
		}
		
		private function onNewBootstrapBox(event:HTTPStreamingFileHandlerEvent):void
		{
			var abst:AdobeBootstrapBox = bootstrapBoxes[currentQuality];
			if (abst.bootstrapVersion < event.bootstrapBox.bootstrapVersion || 
				abst.currentMediaTime < event.bootstrapBox.currentMediaTime)
			{
				bootstrapBoxes[currentQuality] = event.bootstrapBox;
				dispatchDVRStreamInfo(event.bootstrapBox);
			}			
		}
		
		private function dispatchDVRStreamInfo(abst:AdobeBootstrapBox):void
		{
			var frt:AdobeFragmentRunTable = getFragmentRunTable(abst);
			
			if (f4fIndexInfo.dvrInfo != null)
			{
				f4fIndexInfo.dvrInfo.isRecording = !frt.tableComplete();
				if (isNaN(f4fIndexInfo.dvrInfo.startTime))
				{
					f4fIndexInfo.dvrInfo.startTime = 
						frt.tableComplete()? 0 : DVRUtils.calculateOffset(
							((f4fIndexInfo.dvrInfo.beginOffset < 0) || isNaN(f4fIndexInfo.dvrInfo.beginOffset))? 0 : f4fIndexInfo.dvrInfo.beginOffset, 
							((f4fIndexInfo.dvrInfo.endOffset < 0) || isNaN(f4fIndexInfo.dvrInfo.endOffset))? 0 : f4fIndexInfo.dvrInfo.endOffset, 
							abst.totalDuration/abst.timeScale);
					f4fIndexInfo.dvrInfo.startTime += (frt.fragmentDurationPairs)[0].durationAccrued/abst.timeScale;
							
					if (f4fIndexInfo.dvrInfo.startTime > abst.currentMediaTime)
					{
						f4fIndexInfo.dvrInfo.startTime = abst.currentMediaTime;
					}
				}
				
				f4fIndexInfo.dvrInfo.curLength = abst.currentMediaTime/abst.timeScale - f4fIndexInfo.dvrInfo.startTime;

				dispatchEvent(new DVRStreamInfoEvent(DVRStreamInfoEvent.DVRSTREAMINFO, false, false, f4fIndexInfo.dvrInfo)); 								
			}	
		}
		
		private function fragmentOverflow(abst:AdobeBootstrapBox, fragId:uint):Boolean
		{
			var fragmentRunTable:AdobeFragmentRunTable = abst.fragmentRunTables[0];
			var fdp:FragmentDurationPair = fragmentRunTable.fragmentDurationPairs[0];
			var segmentRunTable:AdobeSegmentRunTable = abst.segmentRunTables[0];
			return ((segmentRunTable == null) || ((segmentRunTable.totalFragments + fdp.firstFragment - 1) < fragId));
		}
		
		private function adjustDelay():void
		{
			if (delay < 1.0)
			{
				delay = delay * 2.0;
				if (delay > 1.0)
				{
					delay = 1.0;
				} 
			}
		}
		
		private var pendingIndexLoads:int;
		private var pendingIndexUpdates:int;
		private var bootstrapBoxes:Vector.<AdobeBootstrapBox>;
		private var serverBaseURL:String;
		private var streamInfos:Vector.<HTTPStreamingF4FStreamInfo>;
		private var currentQuality:int;
		private var currentFAI:FragmentAccessInformation;
		private var fragmentsThreshold:uint;
		private var fragmentRunTablesUpdating:Boolean;
		private var f4fIndexInfo:HTTPStreamingF4FIndexInfo;
		private var fileHandler:HTTPStreamingFileHandlerBase;
		private var dvrGetStreamInfoCall:Boolean;
		private var playInProgress:Boolean;
		
		private var offsetFromCurrent:Number = 5;
		private var delay:Number = 0.05;
		private var pureLiveOffset:Number = NaN;
		
		public static const DEFAULT_FRAGMENTS_THRESHOLD:uint = 5;
		
		CONFIG::LOGGING
		{
			private static const logger:org.osmf.logging.Logger = org.osmf.logging.Log.getLogger("org.osmf.net.httpstreaming.HTTPStreamF4FIndexHandler");
		}
	}
}