Commit b2a05cb0 authored by Daniel Rossi's avatar Daniel Rossi
Browse files

- #136 when we are streaming live and not in dvr mode set the duration to zero...

- #136 when we are streaming live and not in dvr mode set the duration to zero in the overridden index handler instead of the metadata callback. dvr mode will have metadata with a duration.
parent f88c8445
......@@ -5,6 +5,7 @@ Version history:
- Now uses OSMF 2.0
- #70 fixes for live streams
- #70 fixes for buffer start value.
- #136 when we are streaming live and not in dvr mode set the duration to zero in the index handler instead of the metadata callback.
3.2.10
------
......
......@@ -172,11 +172,17 @@ package org.flowplayer.httpstreaming {
}
}
override protected function onMetaData(event:ClipEvent):void {
/*override protected function onMetaData(event:ClipEvent):void {
log.debug("in NetStreamControllingStremProvider.onMetaData: " + event.target);
//#70 remove clip duration for live streams and when not dvr recording
if (clip.live && !isDvr) clip.metaData.duration = null;
if (clip.live && !isDvr) {
clip.metaData.duration = 0;
clip.duration = 0;
clip.durationFromMetadata = 0;
}
if (! clip.startDispatched) {
clip.dispatch(ClipEventType.START, pauseAfterStart);
......@@ -187,7 +193,7 @@ package org.flowplayer.httpstreaming {
pauseToFrame();
}
switching = false;
}
} */
override public function get allowRandomSeek():Boolean {
return true;
......
/*****************************************************
*
* 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 flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.events.TimerEvent;
import flash.net.URLRequest;
import flash.utils.ByteArray;
import flash.utils.IDataInput;
import flash.utils.Timer;
import org.osmf.elements.f4mClasses.BootstrapInfo;
import org.osmf.events.DVRStreamInfoEvent;
import org.osmf.events.HTTPStreamingEvent;
import org.osmf.events.HTTPStreamingEventReason;
import org.osmf.events.HTTPStreamingFileHandlerEvent;
import org.osmf.events.HTTPStreamingIndexHandlerEvent;
import org.osmf.net.dvr.DVRUtils;
import org.osmf.net.httpstreaming.HTTPStreamDownloader;
import org.osmf.net.httpstreaming.HTTPStreamRequest;
import org.osmf.net.httpstreaming.HTTPStreamRequestKind;
import org.osmf.net.httpstreaming.HTTPStreamingFileHandlerBase;
import org.osmf.net.httpstreaming.HTTPStreamingIndexHandlerBase;
import org.osmf.net.httpstreaming.HTTPStreamingUtils;
import org.osmf.net.httpstreaming.dvr.DVRInfo;
import org.osmf.net.httpstreaming.flv.FLVTagScriptDataMode;
import org.osmf.net.httpstreaming.flv.FLVTagScriptDataObject;
import org.osmf.utils.OSMFSettings;
import org.osmf.utils.URL;
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
{
/*
AdobePatentID="2390US01"
*/
/**
* Default Constructor.
*
* @param fileHandler The associated file handler object which is responsable for processing the actual data.
* We need this object as it may process bootstrap information found into the stream.
* @param fragmentsThreshold The default threshold for fragments.
*/
public function HTTPStreamingF4FIndexHandler(fileHandler:HTTPStreamingFileHandlerBase, fragmentsThreshold:uint = DEFAULT_FRAGMENTS_THRESHOLD)
{
super();
// listen for any bootstrap box information dispatched by file handler
fileHandler.addEventListener(HTTPStreamingFileHandlerEvent.NOTIFY_BOOTSTRAP_BOX, onBootstrapBox);
_bestEffortF4FHandler.addEventListener(HTTPStreamingFileHandlerEvent.NOTIFY_BOOTSTRAP_BOX, onBestEffortF4FHandlerNotifyBootstrapBox);
}
/**
* @private
*/
override public function dvrGetStreamInfo(indexInfo:Object):void
{
_invokedFromDvrGetStreamInfo = true;
playInProgress = false;
initialize(indexInfo);
}
/**
* Initializes the index handler.
*
* @param indexInfor The index information.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.6
*/
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)
{
CONFIG::LOGGING
{
logger.error("indexInfo object wrong or contains insufficient information!");
}
dispatchEvent(new HTTPStreamingEvent(HTTPStreamingEvent.INDEX_ERROR));
return;
}
_indexUpdating = false;
_pendingIndexLoads = 0;
_pendingIndexUpdates = 0;
_pendingIndexUrls = new Object();
playInProgress = false;
_pureLiveOffset = NaN;
_serverBaseURL = _f4fIndexInfo.serverBaseURL;
_streamInfos = _f4fIndexInfo.streamInfos;
var bootstrapBox:AdobeBootstrapBox;
var streamCount:int = _streamInfos.length;
_streamQualityRates = [];
_streamNames = [];
_bootstrapBoxesURLs = new Vector.<String>(streamCount);
_bootstrapBoxes = new Vector.<AdobeBootstrapBox>(streamCount);
for (var quality:int = 0; quality < streamCount; quality++)
{
var streamInfo:HTTPStreamingF4FStreamInfo = _streamInfos[quality];
if (streamInfo != null)
{
_streamQualityRates[quality] = streamInfo.bitrate;
_streamNames[quality] = streamInfo.streamName;
var bootstrap:BootstrapInfo = streamInfo.bootstrapInfo;
if (bootstrap == null || (bootstrap.url == null && bootstrap.data == null))
{
CONFIG::LOGGING
{
logger.error("Bootstrap(" + quality + ") null or contains inadequate information!");
}
dispatchEvent(new HTTPStreamingEvent(HTTPStreamingEvent.INDEX_ERROR));
return;
}
if (bootstrap.data != null)
{
bootstrapBox = processBootstrapData(bootstrap.data, quality);
if (bootstrapBox == null)
{
CONFIG::LOGGING
{
logger.error("BootstrapBox(" + quality + ") is null, potentially from bad bootstrap data!");
}
dispatchEvent(new HTTPStreamingEvent(HTTPStreamingEvent.INDEX_ERROR));
return;
}
_bootstrapBoxes[quality] = bootstrapBox;
}
else
{
_bootstrapBoxesURLs[quality] = HTTPStreamingUtils.normalizeURL(bootstrap.url);
_pendingIndexLoads++;
dispatchIndexLoadRequest(quality);
}
}
}
if (_pendingIndexLoads == 0)
{
notifyRatesReady();
notifyIndexReady(0);
}
}
/**
* @inheritDoc
*/
override public function dispose():void
{
destroyBootstrapUpdateTimer();
_pendingIndexLoads = 0;
_pendingIndexUpdates = 0;
_pendingIndexUrls = new Object();
_bestEffortNeedsToFireFragmentDuration = false;
_bestEffortEnabled = true;
if (_bestEffortNotifyBootstrapBoxInfo != null && _bestEffortNotifyBootstrapBoxInfo.hasOwnProperty("downloader"))
{
var downloader:HTTPStreamDownloader = _bestEffortNotifyBootstrapBoxInfo.downloader as HTTPStreamDownloader;
if (downloader != null)
{
downloader.close(true);
}
}
}
/**
* Called when the index file has been loaded and is ready to be processed.
*
* @param data The data from the loaded index file.
* @param indexContext An arbitrary context object which describes the loaded
* index file. Useful for index handlers which load multiple index files
* (and thus need to know which one to process).
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
override public function processIndexData(data:*, indexContext:Object):void
{
var quality:int = indexContext as int;
var bootstrapBox:AdobeBootstrapBox = processBootstrapData(data, quality);
if (bootstrapBox == null)
{
CONFIG::LOGGING
{
logger.error("BootstrapBox(" + quality + ") is null when attempting to process index data during a bootstrap update.");
}
dispatchEvent(new HTTPStreamingEvent(HTTPStreamingEvent.INDEX_ERROR));
return;
}
if (!_indexUpdating)
{
// we are processing an index initialization
_pendingIndexLoads--;
CONFIG::LOGGING
{
logger.debug("Pending index loads: " + _pendingIndexLoads);
}
}
else
{
// we are processing an index update
_pendingIndexUpdates--;
CONFIG::LOGGING
{
logger.debug("Pending index updates: " + _pendingIndexUpdates);
}
var requestedUrl:String = _bootstrapBoxesURLs[quality];
if (requestedUrl != null && _pendingIndexUrls.hasOwnProperty(requestedUrl))
{
_pendingIndexUrls[requestedUrl].active = false;
}
if (_pendingIndexUpdates == 0)
{
_indexUpdating = false;
// FM-924, onMetadata is called twice on http streaming live/dvr content
// It is really unnecessary to call onMetadata multiple times. The change of
// media length is fixed for VOD, and is informed by the call dispatchDVRStreamInfo
// for DVR. For "pure live", it does not really matter. Whenever MBR switching
// happens, onMetadata will be called by invoking checkMetadata method.
//
//notifyTotalDuration(bootstrapBox.totalDuration / bootstrapBox.timeScale, indexContext as int);
}
}
CONFIG::LOGGING
{
logger.debug("BootstrapBox(" + quality + ") loaded successfully." +
"[version:" + bootstrapBox.bootstrapVersion +
", fragments from frt:" + bootstrapBox.totalFragments +
", fragments from srt:" + bootstrapBox.segmentRunTables[0].totalFragments + "]"
);
}
updateBootstrapBox(quality, bootstrapBox, true /* sourceIsIndex */);
if (_pendingIndexLoads == 0 && !_indexUpdating)
{
notifyRatesReady();
notifyIndexReady(quality);
}
}
/**
* Returns the HTTPStreamRequest which encapsulates the file for the given
* playback time and quality. If no such file exists for the specified time
* or quality, then this method should return null.
*
* @param time The time for which to retrieve a request object.
* @param quality The quality of the requested stream.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
override public function getFileForTime(time:Number, quality:int):HTTPStreamRequest
{
if ( quality < 0
|| quality >= _streamInfos.length
|| time < 0)
{
CONFIG::LOGGING
{
logger.warn("Invalid parameters for getFileForTime(time=" + time + ", quality=" + quality + ").");
}
return new HTTPStreamRequest(HTTPStreamRequestKind.DONE);
}
// best effort fetch accounting. initially assume seeks are not best effort.
_bestEffortState = BEST_EFFORT_STATE_OFF;
var bootstrapBox:AdobeBootstrapBox = _bootstrapBoxes[quality];
if (bootstrapBox == null)
return new HTTPStreamRequest(HTTPStreamRequestKind.DONE);
if (!playInProgress && isStopped(bootstrapBox))
{
destroyBootstrapUpdateTimer();
return new HTTPStreamRequest(HTTPStreamRequestKind.DONE);
}
updateMetadata(quality);
var streamRequest:HTTPStreamRequest;
var desiredTime:Number = time * bootstrapBox.timeScale;
// we should know the segment and fragment containing the desired time
if(_bestEffortEnabled)
{
streamRequest = getFirstRequestForBestEffortSeek(desiredTime, quality, bootstrapBox);
}
else
{
streamRequest = getSeekRequestForNormalFetch(desiredTime, quality, bootstrapBox);
}
CONFIG::LOGGING
{
logger.debug("The url for ( time=" + time + ", quality=" + quality + ") = " + streamRequest.toString());
}
return streamRequest;
}
/**
* @private
*
* helper for getFileForTime, called when best effort fetch is disabled.
*
* @return the action to take, expressed as an HTTPStreamRequest
**/
private function getSeekRequestForNormalFetch(
desiredTime:Number,
quality:int,
bootstrapBox:AdobeBootstrapBox):HTTPStreamRequest
{
var streamRequest:HTTPStreamRequest = null;
var refreshNeeded:Boolean = false;
var currentTime:Number = bootstrapBox.currentMediaTime;
var contentComplete:Boolean = bootstrapBox.contentComplete();
var frt:AdobeFragmentRunTable = getFragmentRunTable(bootstrapBox);
CONFIG::LOGGING
{
if (contentComplete)
{
logger.debug("Bootstrap reports that content is complete. If this is a live stream, then the publisher stopped it.");
}
}
if (desiredTime <= currentTime)
{
if(frt != null)
{
_currentFAI = frt.findFragmentIdByTime(desiredTime, currentTime, contentComplete ? false : bootstrapBox.live);
}
if (_currentFAI == null || fragmentOverflow(bootstrapBox, _currentFAI.fragId))
{
// we're beyond the end of the bootstrap
if (!bootstrapBox.live || contentComplete)
{
// we're either:
// - vod, in which case we should stop playback
// - live/DVR, and there's a null term, meaning the content is done
return new HTTPStreamRequest(HTTPStreamRequestKind.DONE);
}
else
{
// we're in live and we reached the end of content, but we're not done
return initiateLivenessFailure(quality);
}
}
else
{
// normal case: we found the fragment we were looking for. initiate a download request
return initiateNormalDownload(bootstrapBox, quality);
}
}
else if(bootstrapBox.live)
{
// we are trying to seek beyond the "live" point in the live scenario
return initiateBootstrapRefresh(quality);
}
else
{
// we are trying to seek beyond the "live" point in the vod scenario
return new HTTPStreamRequest(HTTPStreamRequestKind.DONE);
}
}
/**
* @private
*/
override public function getNextFile(quality:int):HTTPStreamRequest
{
if (quality < 0 || quality >= _streamInfos.length)
{
CONFIG::LOGGING
{
logger.warn("Invalid parameters for getNextFile(quality=" + quality + ").");
}
return new HTTPStreamRequest(HTTPStreamRequestKind.DONE);
}
var bootstrapBox:AdobeBootstrapBox = _bootstrapBoxes[quality];
if (bootstrapBox == null)
return new HTTPStreamRequest(HTTPStreamRequestKind.DONE);
if (!playInProgress && isStopped(bootstrapBox))
{
destroyBootstrapUpdateTimer();
return new HTTPStreamRequest(HTTPStreamRequestKind.DONE);
}
updateMetadata(quality);
var streamRequest:HTTPStreamRequest = null;
if(_bestEffortEnabled)
{
// best effort case
if(_bestEffortState == BEST_EFFORT_STATE_OFF ||
_bestEffortState == BEST_EFFORT_STATE_PLAY)
{
streamRequest = getNextRequestForBestEffortPlay(quality, bootstrapBox);
}
else
{
streamRequest = getNextRequestForBestEffortSeek(quality, bootstrapBox);
}
}
else
{
streamRequest = getNextRequestForNormalPlay(quality, bootstrapBox);
}
CONFIG::LOGGING
{
logger.debug("Next url for (quality=" + quality + ") = " + streamRequest.toString());
}
return streamRequest;
}
/**
* @private
*
* helper for getNextFile, called when best effort fetch is disabled.
*
* @return the action to take, expressed as an HTTPStreamRequest
**/
private function getNextRequestForNormalPlay(
quality:int,
bootstrapBox:AdobeBootstrapBox):HTTPStreamRequest
{
var streamRequest:HTTPStreamRequest;
var currentTime:Number = bootstrapBox.currentMediaTime;
var contentComplete:Boolean = bootstrapBox.contentComplete();
CONFIG::LOGGING
{
if (contentComplete)
{
logger.debug("Bootstrap reports that content is complete. If this is a live stream, then the publisher stopped it.");
}
}
var oldCurrentFAI:FragmentAccessInformation = _currentFAI;
var frt:AdobeFragmentRunTable = getFragmentRunTable(bootstrapBox);
if (oldCurrentFAI == null)
{
_currentFAI = null;
}
if(frt != null)
{
_currentFAI = frt.validateFragment(
oldCurrentFAI.fragId + 1, // fragId
currentTime, // totalDuration
contentComplete ? false : bootstrapBox.live);
}
if (_currentFAI == null || fragmentOverflow(bootstrapBox, _currentFAI.fragId))
{
// we're beyond the end of the bootstrap
_currentFAI = oldCurrentFAI;
if (!bootstrapBox.live || contentComplete)
{
// we're either:
// - vod, in which case we should stop playback
// - live/DVR, and there's a null term, meaning the content is done
return new HTTPStreamRequest(HTTPStreamRequestKind.DONE);