Files
moeplayer/source/org/mangui/hls/loader/FragmentLoader.as
T
2020-09-17 18:40:01 +08:00

1218 lines
64 KiB
ActionScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mangui.hls.loader {
import flash.events.*;
import flash.net.*;
import flash.utils.ByteArray;
import flash.utils.Timer;
import flash.utils.getTimer;
import org.mangui.hls.HLS;
import org.mangui.hls.HLSSettings;
import org.mangui.hls.constant.HLSLoaderTypes;
import org.mangui.hls.constant.HLSTypes;
import org.mangui.hls.controller.AudioTrackController;
import org.mangui.hls.controller.LevelController;
import org.mangui.hls.demux.DemuxHelper;
import org.mangui.hls.demux.Demuxer;
import org.mangui.hls.demux.ID3Tag;
import org.mangui.hls.event.HLSError;
import org.mangui.hls.event.HLSEvent;
import org.mangui.hls.event.HLSLoadMetrics;
import org.mangui.hls.flv.FLVTag;
import org.mangui.hls.model.AudioTrack;
import org.mangui.hls.model.Fragment;
import org.mangui.hls.model.FragmentData;
import org.mangui.hls.model.Level;
import org.mangui.hls.stream.StreamBuffer;
import org.mangui.hls.utils.AES;
import com.hurlant.crypto.symmetric.DESKey;
import com.hurlant.crypto.symmetric.ECBMode;
import com.hurlant.util.Base64;
import ckaction.act.des;
CONFIG::LOGGING {
import org.mangui.hls.utils.Log;
import org.mangui.hls.utils.Hex;
}
/** Class that fetches fragments. **/
public class FragmentLoader {
/** Reference to the HLS controller. **/
private var _hls : HLS;
/** reference to auto level manager */
private var _levelController : LevelController;
/** reference to audio track controller */
private var _audioTrackController : AudioTrackController;
/** has manifest just being reloaded **/
private var _manifestJustLoaded : Boolean;
/** last loaded level. **/
private var _levelLastLoaded : int;
/** next level (-1 if not defined yet) **/
private var _levelNext : int = -1;
/** Reference to the manifest levels. **/
private var _levels : Vector.<Level>;
/** Util for loading the fragment. **/
private var _fragstreamloader : URLStream;
/** Util for loading the key. **/
private var _keystreamloader : URLStream;
/** key map **/
private var _keymap : Object;
/** Did the stream switch quality levels. **/
private var _switchLevel : Boolean;
/** Did a discontinuity occurs in the stream **/
private var _hasDiscontinuity : Boolean;
/** boolean to track whether PTS analysis is ongoing or not */
private var _ptsAnalyzing : Boolean = false;
/** Timer used to monitor/schedule fragment download. **/
private var _timer : Timer;
/** requested seek position **/
private var _seekPosition : Number;
/** first fragment loaded ? **/
private var _fragmentFirstLoaded : Boolean;
/* demux instance */
private var _demux : Demuxer;
/* stream buffer instance **/
private var _streamBuffer : StreamBuffer;
/* key error/reload */
private var _keyLoadErrorDate : Number;
private var _keyRetryTimeout : Number;
private var _keyRetryCount : int;
private var _keyLoadStatus : int;
/* fragment error/reload */
private var _fragLoadErrorDate : Number;
private var _fragRetryTimeout : Number;
private var _fragRetryCount : int;
private var _fragLoadStatus : int;
private var _fragSkipping : Boolean;
private var _fragSkipCount : int;
/** reference to previous/current fragment */
private var _fragPrevious : Fragment;
private var _fragCurrent : Fragment;
/* loading state variable */
private var _loadingState : int;
/* loading metrics */
private var _metrics : HLSLoadMetrics;
private static const LOADING_STOPPED : int = -1;
private static const LOADING_IDLE : int = 0;
private static const LOADING_IN_PROGRESS : int = 1;
private static const LOADING_WAITING_LEVEL_UPDATE : int = 2;
private static const LOADING_STALLED : int = 3;
private static const LOADING_FRAGMENT_IO_ERROR : int = 4;
private static const LOADING_KEY_IO_ERROR : int = 5;
private static const LOADING_COMPLETED : int = 6;
/** Create the loader. **/
public function FragmentLoader(hls : HLS, audioTrackController : AudioTrackController, levelController : LevelController, streamBuffer : StreamBuffer) : void {
_hls = hls;
_levelController = levelController;
_audioTrackController = audioTrackController;
_streamBuffer = streamBuffer;
_hls.addEventListener(HLSEvent.MANIFEST_LOADED, _manifestLoadedHandler);
_hls.addEventListener(HLSEvent.LEVEL_LOADED, _levelLoadedHandler);
_hls.addEventListener(HLSEvent.LEVEL_LOADING_ABORTED, _levelLoadingAbortedHandler);
_timer = new Timer(20, 0);
_timer.addEventListener(TimerEvent.TIMER, _checkLoading);
_loadingState = LOADING_STOPPED;
_manifestJustLoaded = false;
_keymap = new Object();
};
public function dispose() : void {
stop();
_timer.removeEventListener(TimerEvent.TIMER, _checkLoading);
_hls.removeEventListener(HLSEvent.MANIFEST_LOADED, _manifestLoadedHandler);
_hls.removeEventListener(HLSEvent.LEVEL_LOADED, _levelLoadedHandler);
_hls.removeEventListener(HLSEvent.LEVEL_LOADING_ABORTED, _levelLoadingAbortedHandler);
_loadingState = LOADING_STOPPED;
_keymap = new Object();
}
public function get audioExpected() : Boolean {
if (_demux) {
return _demux.audioExpected;
} else {
// always return true in case demux is not yet initialized
return true;
}
}
public function get videoExpected() : Boolean {
if (_demux) {
return _demux.videoExpected;
} else {
// always return true in case demux is not yet initialized
return true;
}
}
/** fragment loading Timer **/
private function _checkLoading(e : Event) : void {
switch(_loadingState) {
// nothing to load, stop fragment loader.
case LOADING_STOPPED:
stop();
break;
// nothing to load until level is retrieved
case LOADING_WAITING_LEVEL_UPDATE:
break;
// loading already in progress
case LOADING_IN_PROGRESS:
/* only monitor fragment loading rate if in auto mode, AND
we are not loading the first segment AND
current level is not the lowest level */
if(_hls.autoLevel && !_manifestJustLoaded && _fragCurrent.level) {
// monitor fragment load progress after half of expected fragment duration,to stabilize bitrate
var requestDelay : int = getTimer() - _metrics.loading_request_time;
var fragDuration : Number = _fragCurrent.duration;
if(requestDelay > 500*fragDuration) {
var loaded : int = _fragCurrent.data.bytesLoaded;
var expected : int = fragDuration*_levels[_fragCurrent.level].bitrate/8;
if(expected < loaded) {
expected = loaded;
}
var loadRate : int = loaded*1000/requestDelay; // byte/s
var fragLoadedDelay : Number =(expected-loaded)/loadRate;
var fragLevel0LoadedDelay : Number = fragDuration*_levels[0].bitrate/(8*loadRate); //bps/Bps
var bufferLen : Number = _hls.stream.bufferLength;
// CONFIG::LOGGING {
// Log.info("bufferLen/fragDuration/fragLoadedDelay/fragLevel0LoadedDelay:" + bufferLen.toFixed(1) + "/" + fragDuration.toFixed(1) + "/" + fragLoadedDelay.toFixed(1) + "/" + fragLevel0LoadedDelay.toFixed(1));
// }
/* if we have less than 2 frag duration in buffer and if frag loaded delay is greater than buffer len
... and also bigger than duration needed to load fragment at next level ...*/
if(bufferLen < 2*fragDuration && fragLoadedDelay > bufferLen && fragLoadedDelay > fragLevel0LoadedDelay) {
// try to abort fragment loading ...
// try to flush last fragment seamlessly
if(_streamBuffer.flushLastFragment(_fragCurrent.level,_fragCurrent.seqnum)) {
CONFIG::LOGGING {
Log.warn("_checkLoading : loading too slow, abort fragment loading");
Log.warn("fragLoadedDelay/bufferLen/fragLevel0LoadedDelay:" + fragLoadedDelay.toFixed(1) + "/" + bufferLen.toFixed(1) + "/" + fragLevel0LoadedDelay.toFixed(1));
}
//abort fragment loading
_stop_load();
// fill loadMetrics to please LevelController that will adjust bw for next fragment
// fill theoritical value, assuming bw will remain as it is
_metrics.size = expected;
_metrics.duration = 1000*fragDuration;
_metrics.loading_end_time = _metrics.parsing_end_time = _metrics.loading_request_time + 1000*expected/loadRate;
_hls.dispatchEvent(new HLSEvent(HLSEvent.FRAGMENT_LOAD_EMERGENCY_ABORTED, _metrics));
_levelNext = _levelController.getnextlevel(_fragCurrent.level, bufferLen);
// ensure that we really switch down to avoid looping here.
// _fragCurrent.level is gt 0 in that case, no need to Math.max(0,_levelNext)
_levelNext = Math.min(_levelNext, _fragCurrent.level-1);
// switch back to IDLE state to request new fragment at lowest level
_loadingState = LOADING_IDLE;
}
}
}
}
break;
// no loading in progress, try to load first/next fragment
case LOADING_IDLE:
var level : int;
// check if first fragment after seek has been already loaded
if (_fragmentFirstLoaded == false) {
// select level for first fragment load
if(_levelNext != -1) {
level = _levelNext;
} else if (_hls.autoLevel) {
if (_manifestJustLoaded) {
level = _hls.startLevel;
} else {
if(_hls.stream.bufferLength) {
// if buffer not empty, select level from heuristics
level = _levelController.getnextlevel(_hls.loadLevel, _hls.stream.bufferLength);
} else {
// if buffer empty, retrieve seek level
level = _hls.seekLevel;
}
}
} else {
level = _hls.manualLevel;
}
if (level != _hls.loadLevel) {
_demux = null;
_hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_SWITCH, level));
}
_switchLevel = true;
// check if we received playlist for choosen level. if live playlist, ensure that new playlist has been refreshed
// to avoid loading outdated fragments
if ((_levels[level].fragments.length == 0) || (_hls.type == HLSTypes.LIVE && _levelLastLoaded != level)) {
// playlist not yet received
CONFIG::LOGGING {
Log.debug("_checkLoading : playlist not received for level:" + level);
}
_loadingState = LOADING_WAITING_LEVEL_UPDATE;
_levelNext = level;
} else {
// just after seek, load first fragment
_loadingState = _loadfirstfragment(_seekPosition, level);
}
/* first fragment already loaded
* check if we need to load next fragment, do it only if buffer is NOT full
*/
} else if (HLSSettings.maxBufferLength == 0 || _hls.stream.bufferLength < HLSSettings.maxBufferLength) {
// select level for next fragment load
if(_levelNext != -1) {
level = _levelNext;
} else if (_hls.autoLevel && _levels.length > 1 ) {
// select level from heuristics (current level / last fragment duration / buffer length)
level = _levelController.getnextlevel(_hls.loadLevel, _hls.stream.bufferLength);
} else if (_hls.autoLevel && _levels.length == 1 ) {
level = 0;
} else {
level = _hls.manualLevel;
}
// notify in case level switch occurs
if (level != _hls.loadLevel) {
_switchLevel = true;
_demux = null;
_hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_SWITCH, level));
}
// check if we received playlist for choosen level. if live playlist, ensure that new playlist has been refreshed
if ((_levels[level].fragments.length == 0) || (_hls.type == HLSTypes.LIVE && _levelLastLoaded != level)) {
// playlist not yet received
CONFIG::LOGGING {
Log.debug("_checkLoading : playlist not received for level:" + level);
}
_loadingState = LOADING_WAITING_LEVEL_UPDATE;
_levelNext = level;
} else {
_loadingState = _loadnextfragment(level, _fragPrevious);
}
}
break;
case LOADING_STALLED:
/* next consecutive fragment not found:
it could happen on live playlist :
- if bandwidth available is lower than lowest quality needed bandwidth
- after long pause
*/
CONFIG::LOGGING {
Log.warn("loading stalled:stop fragment loading");
}
_hls.dispatchEvent(new HLSEvent(HLSEvent.LIVE_LOADING_STALLED));
stop();
break;
// if key loading failed
case LOADING_KEY_IO_ERROR:
// compare current date and next retry date.
if (getTimer() >= _keyLoadErrorDate) {
/* try to reload the key ...
calling _loadfragment will also reload key */
_loadfragment(_fragCurrent);
_loadingState = LOADING_IN_PROGRESS;
}
break;
// if fragment loading failed
case LOADING_FRAGMENT_IO_ERROR:
// compare current date and next retry date.
if (getTimer() >= _fragLoadErrorDate) {
/* try to reload fragment ... */
_loadfragment(_fragCurrent);
_loadingState = LOADING_IN_PROGRESS;
}
break;
case LOADING_COMPLETED:
_hls.dispatchEvent(new HLSEvent(HLSEvent.LAST_VOD_FRAGMENT_LOADED));
// stop fragment loader, as no other fragments can be loaded
stop();
break;
default:
CONFIG::LOGGING {
Log.error("invalid loading state:" + _loadingState);
}
break;
}
}
public function seek(position : Number) : void {
CONFIG::LOGGING {
Log.debug("FragmentLoader:seek(" + position.toFixed(2) + ")");
}
// reset IO Error when seeking
_fragRetryCount = _keyRetryCount = 0;
_fragRetryTimeout = _keyRetryTimeout = 1000;
_loadingState = LOADING_IDLE;
_seekPosition = position;
_fragmentFirstLoaded = false;
_fragPrevious = null;
_fragSkipping = false;
_fragSkipCount = 0;
_levelNext = -1;
_timer.start();
}
public function seekFromLastFrag(lastFrag : Fragment) : void {
CONFIG::LOGGING {
Log.info("FragmentLoader:seekFromLastFrag(level:" + lastFrag.level + ",SN:" + lastFrag.seqnum + ",PTS:" + lastFrag.data.pts_start +")");
}
// reset IO Error when seeking
_fragRetryCount = _keyRetryCount = 0;
_fragRetryTimeout = _keyRetryTimeout = 1000;
_loadingState = LOADING_IDLE;
_fragmentFirstLoaded = true;
_fragSkipping = false;
_fragSkipCount = 0;
_levelNext = -1;
_fragPrevious = lastFrag;
_timer.start();
}
/** key load completed. **/
private function _keyLoadCompleteHandler(event : Event) : void {
if (_loadingState == LOADING_IDLE)
return;
CONFIG::LOGGING {
Log.debug("key loading completed");
}
var hlsError : HLSError;
// Collect key data
if ( _keystreamloader.bytesAvailable == 16 ) {
// load complete, reset retry counter
_keyRetryCount = 0;
_keyRetryTimeout = 1000;
var keyData : ByteArray = new ByteArray();
_keystreamloader.readBytes(keyData, 0, 0);
_keymap[_fragCurrent.decrypt_url] = keyData;
// now load fragment
try {
CONFIG::LOGGING {
Log.debug("loading fragment:" + _fragCurrent.url);
}
_fragCurrent.data.bytes = null;
_hls.dispatchEvent(new HLSEvent(HLSEvent.FRAGMENT_LOADING, _fragCurrent.url));
_fragstreamloader.load(new URLRequest(_fragCurrent.url));
} catch (error : Error) {
hlsError = new HLSError(HLSError.FRAGMENT_LOADING_ERROR, _fragCurrent.url, error.message);
_hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
}
}
else if(_keystreamloader.bytesAvailable == 32){
_keyRetryCount = 0;
_keyRetryTimeout = 1000;
var keyData2 : ByteArray = new ByteArray();
_keystreamloader.readBytes(keyData2, 0, 0);
keyData2.position=0;
var esc:String=des.getVString(Base64.encodeByteArray(keyData2));
keyData=Base64.decodeToByteArray(esc);
_keymap[_fragCurrent.decrypt_url] = keyData;
// now load fragment
try {
CONFIG::LOGGING {
Log.debug("loading fragment:" + _fragCurrent.url);
}
_fragCurrent.data.bytes = null;
_hls.dispatchEvent(new HLSEvent(HLSEvent.FRAGMENT_LOADING, _fragCurrent.url));
_fragstreamloader.load(new URLRequest(_fragCurrent.url));
} catch (error : Error) {
hlsError = new HLSError(HLSError.FRAGMENT_LOADING_ERROR, _fragCurrent.url, error.message);
_hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
}
}
else {
hlsError = new HLSError(HLSError.KEY_PARSING_ERROR, _fragCurrent.decrypt_url, "invalid key size: received " + _keystreamloader.bytesAvailable + " / expected 16 bytes");
_hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
}
};
private function _keyLoadHTTPStatusHandler(event : HTTPStatusEvent) : void {
_keyLoadStatus = event.status;
}
private function _keyhandleIOError(message : String) : void {
CONFIG::LOGGING {
Log.error("I/O Error while loading key:" + message);
}
if (HLSSettings.keyLoadMaxRetry == -1 || _keyRetryCount < HLSSettings.keyLoadMaxRetry) {
_loadingState = LOADING_KEY_IO_ERROR;
_keyLoadErrorDate = getTimer() + _keyRetryTimeout;
CONFIG::LOGGING {
Log.warn("retry key load in " + _keyRetryTimeout + " ms, count=" + _keyRetryCount);
}
/* exponential increase of retry timeout, capped to keyLoadMaxRetryTimeout */
_keyRetryCount++;
_keyRetryTimeout = Math.min(HLSSettings.keyLoadMaxRetryTimeout, 2 * _keyRetryTimeout);
} else {
var hlsError : HLSError = new HLSError(HLSError.KEY_LOADING_ERROR, _fragCurrent.decrypt_url, "I/O Error :" + message);
_hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
}
}
/* in case of parsing error,
first, try to flush any tags that might have been injected in the NetStream
then switch to redundant stream if any
OR level switch down and cap level if in auto mode
OR skip fragment if allowed to
if not allowed to, report PARSING error
*/
private function _fragHandleParsingError(message : String) : void {
var hlsError : HLSError = new HLSError(HLSError.FRAGMENT_PARSING_ERROR, _fragCurrent.url, "Parsing Error :" + message);
var level : Level = _levels[_fragCurrent.level];
CONFIG::LOGGING {
Log.warn(hlsError.msg);
}
// flush any tags that might have been injected for this fragment
_streamBuffer.flushLastFragment(_fragCurrent.level,_fragCurrent.seqnum);
_hls.dispatchEvent(new HLSEvent(HLSEvent.WARNING, hlsError));
// if we have redundant streams left for that level, switch to it
if(level.redundantStreamId < level.redundantStreamsNb) {
CONFIG::LOGGING {
Log.warn("parsing error, switch to redundant stream");
}
level.redundantStreamId++;
_fragRetryCount = 0;
_fragRetryTimeout = 1000;
_loadingState = LOADING_IDLE;
// dispatch event to force redundant level loading
_hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_SWITCH, _fragCurrent.level));
} else if(_hls.autoLevel && _fragCurrent.level) {
// auto level and not on level 0, try to switch down for next fragment, and cap level to avoid coming back on this one
_levelNext = _fragCurrent.level-1;
if(_hls.autoLevelCapping == -1) {
_hls.autoLevelCapping = _levelNext;
} else {
_hls.autoLevelCapping = Math.min(_levelNext,_hls.autoLevelCapping);
}
// switch back to IDLE state to request new fragment at lowest level
_loadingState = LOADING_IDLE;
} else if(HLSSettings.fragmentLoadSkipAfterMaxRetry == true && _fragSkipCount < HLSSettings.maxSkippedFragments || HLSSettings.maxSkippedFragments < 0) {
CONFIG::LOGGING {
Log.warn("error parsing fragment, skip it and load next one");
}
var tags : Vector.<FLVTag> = tags = new Vector.<FLVTag>();
tags.push(_fragCurrent.getSkippedTag());
// send skipped FLV tag to StreamBuffer
_streamBuffer.appendTags(HLSLoaderTypes.FRAGMENT_MAIN,_fragCurrent.level,_fragCurrent.seqnum ,tags,_fragCurrent.data.pts_start_computed, _fragCurrent.data.pts_start_computed + 1000*_fragCurrent.duration, _fragCurrent.continuity, _fragCurrent.start_time);
_fragRetryCount = 0;
_fragRetryTimeout = 1000;
_fragPrevious = _fragCurrent;
_fragSkipping = true;
_fragSkipCount++;
CONFIG::LOGGING {
Log.debug("fragments skipped / max: " + _fragSkipCount + "/" + HLSSettings.maxSkippedFragments );
}
// set fragment first loaded to be true to ensure that we can skip first fragment as well
_fragmentFirstLoaded = true;
_loadingState = LOADING_IDLE;
} else {
_hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
}
}
/* in case of IO error,
retry loading fragment several times if allowed to
then switch to redundant stream if any
OR level switch down and cap level if in auto mode
OR skip fragment if allowed to
if not allowed to, report LOADING error
*/
private function _fraghandleIOError(message : String) : void {
var hlsError : HLSError = new HLSError(HLSError.FRAGMENT_LOADING_ERROR, _fragCurrent.url, "I/O Error while loading fragment:" + message);
_hls.dispatchEvent(new HLSEvent(HLSEvent.WARNING, hlsError));
CONFIG::LOGGING {
Log.warn(hlsError.msg);
}
if (HLSSettings.fragmentLoadMaxRetry == -1 || _fragRetryCount < HLSSettings.fragmentLoadMaxRetry) {
_loadingState = LOADING_FRAGMENT_IO_ERROR;
_fragLoadErrorDate = getTimer() + _fragRetryTimeout;
CONFIG::LOGGING {
Log.warn("retry fragment load in " + _fragRetryTimeout + " ms, count=" + _fragRetryCount);
}
/* exponential increase of retry timeout, capped to fragmentLoadMaxRetryTimeout */
_fragRetryCount++;
_fragRetryTimeout = Math.min(HLSSettings.fragmentLoadMaxRetryTimeout, 2 * _fragRetryTimeout);
} else {
var level : Level = _levels[_fragCurrent.level];
// if we have redundant streams left for that level, switch to it
if(level.redundantStreamId < level.redundantStreamsNb) {
CONFIG::LOGGING {
Log.warn("max load retry reached, switch to redundant stream");
}
level.redundantStreamId++;
_fragRetryCount = 0;
_fragRetryTimeout = 1000;
_loadingState = LOADING_IDLE;
// dispatch event to force redundant level loading
_hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_SWITCH, _fragCurrent.level));
} else if(_hls.autoLevel && _fragCurrent.level) {
// auto level and not on level 0, try to switch down for next fragment, and cap level to avoid coming back on this one
_levelNext = _fragCurrent.level-1;
if(_hls.autoLevelCapping == -1) {
_hls.autoLevelCapping = _levelNext;
} else {
_hls.autoLevelCapping = Math.min(_levelNext,_hls.autoLevelCapping);
}
// switch back to IDLE state to request new fragment at lowest level
_loadingState = LOADING_IDLE;
} else if(HLSSettings.fragmentLoadSkipAfterMaxRetry == true && _fragSkipCount < HLSSettings.maxSkippedFragments || HLSSettings.maxSkippedFragments < 0) {
/* check if loaded fragment is not the last one of a live playlist.
if it is the case, don't skip to next, as there is no next fragment :-)
*/
if(_hls.type == HLSTypes.LIVE && _fragCurrent.seqnum == level.end_seqnum) {
_loadingState = LOADING_FRAGMENT_IO_ERROR;
_fragLoadErrorDate = getTimer() + _fragRetryTimeout;
CONFIG::LOGGING {
Log.warn("max load retry reached on last fragment of live playlist, retrying loading this one...");
}
/* exponential increase of retry timeout, capped to fragmentLoadMaxRetryTimeout */
_fragRetryCount++;
_fragRetryTimeout = Math.min(HLSSettings.fragmentLoadMaxRetryTimeout, 2 * _fragRetryTimeout);
} else {
CONFIG::LOGGING {
Log.warn("max fragment load retry reached, skip fragment and load next one.");
}
var tags : Vector.<FLVTag> = tags = new Vector.<FLVTag>();
tags.push(_fragCurrent.getSkippedTag());
// send skipped FLV tag to StreamBuffer
_streamBuffer.appendTags(HLSLoaderTypes.FRAGMENT_MAIN,_fragCurrent.level,_fragCurrent.seqnum ,tags,_fragCurrent.data.pts_start_computed, _fragCurrent.data.pts_start_computed + 1000*_fragCurrent.duration, _fragCurrent.continuity, _fragCurrent.start_time);
_fragRetryCount = 0;
_fragRetryTimeout = 1000;
_fragPrevious = _fragCurrent;
_fragSkipping = true;
_fragSkipCount++;
CONFIG::LOGGING {
Log.debug("fragments skipped / max: " + _fragSkipCount + "/" + HLSSettings.maxSkippedFragments );
}
// set fragment first loaded to be true to ensure that we can skip first fragment as well
_fragmentFirstLoaded = true;
_loadingState = LOADING_IDLE;
}
} else {
_hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
}
}
}
private function _fragLoadHTTPStatusHandler(event : HTTPStatusEvent) : void {
_fragLoadStatus = event.status;
}
private function _fragLoadProgressHandler(event : ProgressEvent) : void {
var fragData : FragmentData = _fragCurrent.data;
if (fragData.bytes == null) {
fragData.bytes = new ByteArray();
fragData.bytesLoaded = 0;
fragData.flushTags();
_metrics.loading_begin_time = getTimer();
// decrypt data if needed
if (_fragCurrent.decrypt_url != null) {
_metrics.decryption_begin_time = getTimer();
fragData.decryptAES = new AES(_hls.stage, _keymap[_fragCurrent.decrypt_url], _fragCurrent.decrypt_iv, _fragDecryptProgressHandler, _fragDecryptCompleteHandler);
CONFIG::LOGGING {
Log.debug("init AES context");
}
} else {
fragData.decryptAES = null;
}
}
if (event.bytesLoaded > fragData.bytesLoaded
&& _fragstreamloader.bytesAvailable > 0) { // prevent EOF error race condition
var data : ByteArray = new ByteArray();
_fragstreamloader.readBytes(data);
fragData.bytesLoaded += data.length;
// CONFIG::LOGGING {
// Log.debug2("bytesLoaded/bytesTotal:" + event.bytesLoaded + "/" + event.bytesTotal);
// }
if (fragData.decryptAES != null) {
fragData.decryptAES.append(data);
} else {
_fragDecryptProgressHandler(data);
}
}
}
/** frag load completed. **/
private function _fragLoadCompleteHandler(event : Event) : void {
var fragData : FragmentData = _fragCurrent.data;
if (fragData.bytes == null) {
CONFIG::LOGGING {
Log.warn("fragment size is null, invalid it and load next one");
}
_levels[_hls.loadLevel].updateFragment(_fragCurrent.seqnum, false);
_loadingState = LOADING_IDLE;
return;
}
CONFIG::LOGGING {
Log.debug("loading completed");
}
_fragSkipping = false;
_fragSkipCount = 0;
_metrics.loading_end_time = getTimer();
_metrics.size = fragData.bytesLoaded;
var _loading_duration : uint = _metrics.loading_end_time - _metrics.loading_request_time;
CONFIG::LOGGING {
Log.debug("Loading duration/RTT/length/speed:" + _loading_duration + "/" + (_metrics.loading_begin_time - _metrics.loading_request_time) + "/" + _metrics.size + "/" + Math.round((8000 * _metrics.size / _loading_duration) / 1024) + " kb/s");
_hls.dispatchEvent(new HLSEvent(HLSEvent.LOADED_BYTES,((8000 * _metrics.size / _loading_duration) / 1024)));//niandeng 2018.12.19
}
if (fragData.decryptAES) {
fragData.decryptAES.notifycomplete();
} else {
_fragDecryptCompleteHandler();
}
}
private function _fragDecryptProgressHandler(data : ByteArray) : void {
data.position = 0;
var fragData : FragmentData = _fragCurrent.data;
if (_metrics.parsing_begin_time ==0) {
_metrics.parsing_begin_time = getTimer();
}
var bytes : ByteArray = fragData.bytes;
if (_fragCurrent.byterange_start_offset != -1) {
bytes.position = bytes.length;
bytes.writeBytes(data);
// if we have retrieved all the data, disconnect loader and notify fragment complete
if (bytes.length >= _fragCurrent.byterange_end_offset) {
if (_fragstreamloader.connected) {
_fragstreamloader.close();
_fragLoadCompleteHandler(null);
}
}
/* dont do progressive parsing of segment with byte range option */
return;
}
if (_demux == null) {
/* probe file type */
bytes.position = bytes.length;
bytes.writeBytes(data);
data = bytes;
_demux = DemuxHelper.probe(data, _levels[_hls.loadLevel], _fragParsingAudioSelectionHandler, _fragParsingProgressHandler, _fragParsingCompleteHandler, _fragParsingErrorHandler, _fragParsingVideoMetadataHandler, _fragParsingID3TagHandler, false);
}
if (_demux) {
_demux.append(data);
}
}
private function _fragDecryptCompleteHandler() : void {
if (_loadingState == LOADING_IDLE)
return;
var fragData : FragmentData = _fragCurrent.data;
if (fragData.decryptAES) {
_metrics.decryption_end_time = getTimer();
var decrypt_duration : Number = _metrics.decryption_end_time - _metrics.decryption_begin_time;
CONFIG::LOGGING {
Log.debug("Decrypted duration/length/speed:" + decrypt_duration + "/" + fragData.bytesLoaded + "/" + Math.round((8000 * fragData.bytesLoaded / decrypt_duration) / 1024) + " kb/s");
}
fragData.decryptAES = null;
}
// deal with byte range here
if (_fragCurrent.byterange_start_offset != -1) {
CONFIG::LOGGING {
Log.debug("trim byte range, start/end offset:" + _fragCurrent.byterange_start_offset + "/" + _fragCurrent.byterange_end_offset);
}
var bytes : ByteArray = new ByteArray();
fragData.bytes.position = _fragCurrent.byterange_start_offset;
fragData.bytes.readBytes(bytes, 0, _fragCurrent.byterange_end_offset - _fragCurrent.byterange_start_offset);
_demux = DemuxHelper.probe(bytes, _levels[_hls.loadLevel], _fragParsingAudioSelectionHandler, _fragParsingProgressHandler, _fragParsingCompleteHandler, _fragParsingErrorHandler, _fragParsingVideoMetadataHandler, _fragParsingID3TagHandler, false);
if (_demux) {
bytes.position = 0;
_demux.append(bytes);
}
}
if (_demux == null) {
CONFIG::LOGGING {
Log.error("unknown fragment type");
if (HLSSettings.logDebug2) {
fragData.bytes.position = 0;
var bytes2 : ByteArray = new ByteArray();
fragData.bytes.readBytes(bytes2, 0, 512);
Log.debug2("frag dump(512 bytes)");
Log.debug2(Hex.fromArray(bytes2));
}
}
// invalid fragment
_fraghandleIOError("invalid content received");
fragData.bytes = null;
return;
}
fragData.bytes = null;
_demux.notifycomplete();
}
/** stop loading fragment **/
public function stop() : void {
_stop_load();
_timer.stop();
_loadingState = LOADING_STOPPED;
}
private function _stop_load() : void {
if (_fragstreamloader && _fragstreamloader.connected) {
_fragstreamloader.close();
}
if (_keystreamloader && _keystreamloader.connected) {
_keystreamloader.close();
}
if (_demux) {
_demux.cancel();
}
if (_fragCurrent) {
var fragData : FragmentData = _fragCurrent.data;
if (fragData.decryptAES) {
fragData.decryptAES.cancel();
fragData.decryptAES = null;
}
fragData.bytes = null;
}
}
/** Catch IO and security errors. **/
private function _keyLoadErrorHandler(event : ErrorEvent) : void {
var txt : String;
var code : int;
if (event is SecurityErrorEvent) {
txt = "Cannot load key: crossdomain access denied:" + event.text;
code = HLSError.KEY_LOADING_CROSSDOMAIN_ERROR;
} else {
_keyhandleIOError("HTTP status:" + _keyLoadStatus + ",msg:" + event.text);
}
};
/** Catch IO and security errors. **/
private function _fragLoadErrorHandler(event : ErrorEvent) : void {
if (event is SecurityErrorEvent) {
var txt : String = "Cannot load fragment: crossdomain access denied:" + event.text;
var hlsError : HLSError = new HLSError(HLSError.FRAGMENT_LOADING_CROSSDOMAIN_ERROR, _fragCurrent.url, txt);
_hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
} else {
if(_fragLoadStatus == 200) {
_fragHandleParsingError("HTTP 2OO but IO error, treat as parsing error");
} else {
_fraghandleIOError("HTTP status:" + _fragLoadStatus + ",msg:" + event.text);
}
}
};
private function _loadfirstfragment(position : Number, level : int) : int {
CONFIG::LOGGING {
Log.debug("loadfirstfragment(" + position + ")");
}
var frag : Fragment = _levels[level].getFragmentBeforePosition(position);
_hasDiscontinuity = true;
CONFIG::LOGGING {
Log.debug("Loading " + frag.seqnum + " of [" + (_levels[level].start_seqnum) + "," + (_levels[level].end_seqnum) + "],level " + level);
}
_loadfragment(frag);
return LOADING_IN_PROGRESS;
}
/** Load a fragment **/
private function _loadnextfragment(level : int, frag_previous : Fragment) : int {
CONFIG::LOGGING {
Log.debug("loadnextfragment()");
}
var new_seqnum : Number;
var last_seqnum : Number = -1;
var levelObj : Level = _levels[level];
var log_prefix : String;
var frag : Fragment;
if (_switchLevel == false || frag_previous.continuity == -1) {
last_seqnum = frag_previous.seqnum;
} else {
// level switch
// trust program-time : if program-time defined in previous loaded fragment, try to find seqnum matching program-time in new level.
if (frag_previous.program_date) {
last_seqnum = levelObj.getSeqNumNearestProgramDate(frag_previous.program_date);
CONFIG::LOGGING {
Log.debug("loadnextfragment : getSeqNumNearestProgramDate(level,date,cc:" + level + "," + frag_previous.program_date + ")=" + last_seqnum);
}
}
if (last_seqnum == -1) {
// if we are here, it means that no program date info is available in the playlist. try to get last seqnum position from PTS + continuity counter
// last_pts is an approximation of last injected PTS of previous fragment
var last_pts : Number = frag_previous.data.pts_start_computed+1000*frag_previous.duration;
last_seqnum = levelObj.getSeqNumNearestPTS(last_pts, frag_previous.continuity);
CONFIG::LOGGING {
Log.debug("loadnextfragment : getSeqNumNearestPTS(level,pts,cc:" + level + "," + last_pts + "," + frag_previous.continuity + ")=" + last_seqnum);
}
last_seqnum--;
if (last_seqnum == Number.POSITIVE_INFINITY) {
/* requested PTS above max PTS of this level:
* this case could happen when loading is completed
* or when switching level at the edge of live playlist,
* in case playlist of new level is outdated
*/
if (_hls.type == HLSTypes.VOD) {
// if VOD playlist, loading is completed
return LOADING_COMPLETED;
} else {
// if live playlist, loading is pending on manifest update
return LOADING_WAITING_LEVEL_UPDATE;
}
} else if (last_seqnum < -1) {
// if we are here, it means that we have no PTS info for this continuity index, we need to do some PTS probing to find the right seqnum
/* we need to perform PTS analysis on fragments from same continuity range
get first fragment from playlist matching with criteria and load pts */
last_seqnum = levelObj.getFirstSeqNumfromContinuity(frag_previous.continuity);
CONFIG::LOGGING {
Log.debug("loadnextfragment : getFirstSeqNumfromContinuity(level,cc:" + level + "," + frag_previous.continuity + ")=" + last_seqnum);
}
if (last_seqnum == Number.NEGATIVE_INFINITY) {
// playlist not yet received
return LOADING_WAITING_LEVEL_UPDATE;
}
/* when probing PTS, take previous sequence number as reference if possible */
new_seqnum = Math.min(frag_previous.seqnum + 1, levelObj.getLastSeqNumfromContinuity(frag_previous.continuity));
new_seqnum = Math.max(new_seqnum, levelObj.getFirstSeqNumfromContinuity(frag_previous.continuity));
_ptsAnalyzing = true;
log_prefix = "analyzing PTS ";
} else {
// last seqnum found on new level, reset PTS analysis flag
_ptsAnalyzing = false;
}
}
}
if (_ptsAnalyzing == false) {
if (last_seqnum == levelObj.end_seqnum) {
// if last segment of level already loaded, return
if (_hls.type == HLSTypes.VOD) {
// if VOD playlist, loading is completed
return LOADING_COMPLETED;
} else {
// if live playlist, loading is pending on manifest update
return LOADING_WAITING_LEVEL_UPDATE;
}
} else {
// if previous segment is not the last one, increment it to get new seqnum
new_seqnum = last_seqnum + 1;
if (new_seqnum < levelObj.start_seqnum) {
// loading stalled ! report to caller
return LOADING_STALLED;
}
frag = levelObj.getFragmentfromSeqNum(new_seqnum);
if (frag == null) {
CONFIG::LOGGING {
Log.warn("error trying to load " + new_seqnum + " of [" + (levelObj.start_seqnum) + "," + (levelObj.end_seqnum) + "],level " + level);
}
return LOADING_WAITING_LEVEL_UPDATE;
}
// check whether there is a discontinuity between last segment and new segment
_hasDiscontinuity = ((frag.continuity != frag_previous.continuity) || _fragSkipping);
;
log_prefix = "Loading ";
}
}
frag = levelObj.getFragmentfromSeqNum(new_seqnum);
CONFIG::LOGGING {
Log.debug(log_prefix + new_seqnum + " of [" + (levelObj.start_seqnum) + "," + (levelObj.end_seqnum) + "],level " + level);
}
_loadfragment(frag);
return LOADING_IN_PROGRESS;
};
private function _loadfragment(frag : Fragment) : void {
// postpone URLStream init before loading first fragment
if (_fragstreamloader == null) {
var urlStreamClass : Class = _hls.URLstream as Class;
_fragstreamloader = (new urlStreamClass()) as URLStream;
_fragstreamloader.addEventListener(IOErrorEvent.IO_ERROR, _fragLoadErrorHandler);
_fragstreamloader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, _fragLoadErrorHandler);
_fragstreamloader.addEventListener(ProgressEvent.PROGRESS, _fragLoadProgressHandler);
_fragstreamloader.addEventListener(HTTPStatusEvent.HTTP_STATUS, _fragLoadHTTPStatusHandler);
_fragstreamloader.addEventListener(Event.COMPLETE, _fragLoadCompleteHandler);
_keystreamloader = (new urlStreamClass()) as URLStream;
_keystreamloader.addEventListener(IOErrorEvent.IO_ERROR, _keyLoadErrorHandler);
_keystreamloader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, _keyLoadErrorHandler);
_keystreamloader.addEventListener(HTTPStatusEvent.HTTP_STATUS, _keyLoadHTTPStatusHandler);
_keystreamloader.addEventListener(Event.COMPLETE, _keyLoadCompleteHandler);
}
if (_hasDiscontinuity || _switchLevel) {
_demux = null;
}
_metrics = new HLSLoadMetrics(HLSLoaderTypes.FRAGMENT_MAIN);
_metrics.level = frag.level;
_metrics.id = frag.seqnum;
_metrics.loading_request_time = getTimer();
_fragCurrent = frag;
frag.data.auto_level = _hls.autoLevel;
if (frag.decrypt_url != null) {
if (_keymap[frag.decrypt_url] == undefined) {
// load key
CONFIG::LOGGING {
Log.debug("loading key:" + frag.decrypt_url);
}
_keystreamloader.load(new URLRequest(frag.decrypt_url));
return;
}
}
try {
frag.data.bytes = null;
CONFIG::LOGGING {
Log.debug("loading fragment:" + frag.url);
}
_hls.dispatchEvent(new HLSEvent(HLSEvent.FRAGMENT_LOADING, frag.url));
_fragstreamloader.load(new URLRequest(frag.url));
} catch (error : Error) {
var hlsError : HLSError = new HLSError(HLSError.FRAGMENT_LOADING_ERROR, frag.url, error.message);
_hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
}
}
/** Store the manifest data. **/
private function _manifestLoadedHandler(event : HLSEvent) : void {
_levels = event.levels;
_manifestJustLoaded = true;
};
/** Store the manifest data. **/
private function _levelLoadedHandler(event : HLSEvent) : void {
_levelLastLoaded = event.loadMetrics.level;
if (_loadingState == LOADING_WAITING_LEVEL_UPDATE && _levelLastLoaded == _hls.loadLevel) {
_loadingState = LOADING_IDLE;
}
// speed up loading of new fragment
_timer.start();
};
/** Store the manifest data. **/
private function _levelLoadingAbortedHandler(event : HLSEvent) : void {
_levelNext = event.level-1;
CONFIG::LOGGING {
Log.warn("FragmentLoader:_levelLoadingAbortedHandler:switch down to:" + _levelNext);
}
_loadingState = LOADING_IDLE;
// speed up loading of new playlist/fragment
_timer.start();
};
private function _fragParsingErrorHandler(error : String) : void {
// abort any load in progress
_stop_load();
// then try to overcome parsing error
_fragHandleParsingError(error);
}
private function _fragParsingID3TagHandler(id3_tags : Vector.<ID3Tag>) : void {
_fragCurrent.data.id3_tags = id3_tags;
}
/** triggered by demux, it should return the audio track to be parsed */
private function _fragParsingAudioSelectionHandler(audioTrackList : Vector.<AudioTrack>) : AudioTrack {
return _audioTrackController.audioTrackSelectionHandler(audioTrackList);
}
/** triggered by demux, it should return video width/height */
private function _fragParsingVideoMetadataHandler(width : uint, height : uint) : void {
var fragData : FragmentData = _fragCurrent.data;
if (fragData.video_width == 0) {
CONFIG::LOGGING {
Log.debug("AVC: width/height:" + width + "/" + height);
}
fragData.video_width = width;
fragData.video_height = height;
}
}
/** triggered when demux has retrieved some tags from fragment **/
private function _fragParsingProgressHandler(tags : Vector.<FLVTag>) : void {
CONFIG::LOGGING {
Log.debug2(tags.length + " tags extracted");
}
var fragData : FragmentData = _fragCurrent.data;
fragData.appendTags(tags);
/* try to do progressive buffering here.
* only do it in case :
* first fragment is already loaded
* or if first fragment is not loaded, we can do it if
* startLevel is already defined (startLevel is already set or
* startFromLevel/startFromBitrate not set to -1
* or we only have one quality level
* in the other cases, flashls will first download one fragment at level 0 to measure dl bw, used to assess start level ...)
*/
if (( !_manifestJustLoaded ||
(_levelController.isStartLevelSet() ||
HLSSettings.startFromLevel !== -1 ||
HLSSettings.startFromBitrate !== -1 ||
_levels.length == 1))) {
/* if audio expected, PTS analysis is done on audio
* if audio not expected, PTS analysis is done on video
* the check below ensures that we can compute min/max PTS
*/
if ((_demux.audioExpected && fragData.audio_found) || (!_demux.audioExpected && fragData.video_found)) {
if (_ptsAnalyzing == true) {
var levelObj : Level = _levels[_hls.loadLevel];
_ptsAnalyzing = false;
levelObj.updateFragment(_fragCurrent.seqnum, true, fragData.pts_min, fragData.pts_min + _fragCurrent.duration * 1000);
/* in case we are probing PTS, retrieve PTS info and synchronize playlist PTS / sequence number */
CONFIG::LOGGING {
Log.debug("analyzed PTS " + _fragCurrent.seqnum + " of [" + (levelObj.start_seqnum) + "," + (levelObj.end_seqnum) + "],level " + _hls.loadLevel + " m PTS:" + fragData.pts_min);
}
/* check if fragment loaded for PTS analysis is the next one
if this is the expected one, then continue
if not, then cancel current fragment loading, next call to loadnextfragment() will load the right seqnum
*/
var next_pts:Number;
var next_seqnum:Number;
// Resolves intermittent issue that causes the player to crash due to missing previous fragment data while seeking
if (_fragPrevious && _fragPrevious.data) {
next_pts = _fragPrevious.data.pts_start_computed + 1000*_fragPrevious.duration;
next_seqnum = levelObj.getSeqNumNearestPTS(next_pts, _fragCurrent.continuity);
} else {
CONFIG::LOGGING {
Log.debug("Previous fragment data not found while analyzing PTS!");
}
}
CONFIG::LOGGING {
Log.debug("analyzed PTS : getSeqNumNearestPTS(level,pts,cc:" + _hls.loadLevel + "," + next_pts + "," + _fragCurrent.continuity + ")=" + next_seqnum);
}
// CONFIG::LOGGING {
// Log.info("seq/next:"+ _seqnum+"/"+ next_seqnum);
// }
if (next_seqnum !== _fragCurrent.seqnum) {
// stick to same level after PTS analysis
_levelNext = _hls.loadLevel;
CONFIG::LOGGING {
Log.debug("PTS analysis done on " + _fragCurrent.seqnum + ", matching seqnum is " + next_seqnum + " of [" + (levelObj.start_seqnum) + "," + (levelObj.end_seqnum) + "],cancel loading and get new one");
}
// cancel loading
_stop_load();
// clean-up tags
fragData.flushTags();
// tell that new fragment could be loaded
_loadingState = LOADING_IDLE;
return;
}
}
if (fragData.metadata_tag_injected == false) {
fragData.tags.unshift(_fragCurrent.getMetadataTag());
if (_hasDiscontinuity) {
fragData.tags.unshift(new FLVTag(FLVTag.DISCONTINUITY, fragData.dts_min, fragData.dts_min, false));
}
fragData.metadata_tag_injected = true;
}
// provide tags to StreamBuffer
_streamBuffer.appendTags(HLSLoaderTypes.FRAGMENT_MAIN,_fragCurrent.level,_fragCurrent.seqnum , fragData.tags, fragData.tag_pts_min, fragData.tag_pts_max + fragData.tag_duration, _fragCurrent.continuity, _fragCurrent.start_time + fragData.tag_pts_start_offset / 1000);
_metrics.parsing_end_time = getTimer();
_metrics.size = fragData.bytesLoaded;
_metrics.duration = fragData.tag_pts_end_offset;
_metrics.id2 = fragData.tags.length;
_hls.dispatchEvent(new HLSEvent(HLSEvent.TAGS_LOADED, _metrics));
fragData.shiftTags();
_hasDiscontinuity = false;
}
}
}
/** triggered when demux has completed fragment parsing **/
private function _fragParsingCompleteHandler() : void {
if (_loadingState == LOADING_IDLE)
return;
var hlsError : HLSError;
var fragData : FragmentData = _fragCurrent.data;
var fragLevelIdx : int = _fragCurrent.level;
if ((_demux.audioExpected && !fragData.audio_found) && (_demux.videoExpected && !fragData.video_found)) {
// handle it like a parsing error
_fragHandleParsingError("error parsing fragment, no tag found");
return;
}
// parsing complete, reset retry and skip counters
_fragRetryCount = 0;
_fragRetryTimeout = 1000;
_fragSkipCount = 0;
CONFIG::LOGGING {
if (fragData.audio_found) {
Log.debug("m/M audio PTS:" + fragData.pts_min_audio + "/" + fragData.pts_max_audio);
}
if (fragData.video_found) {
Log.debug("m/M video PTS:" + fragData.pts_min_video + "/" + fragData.pts_max_video);
if (!fragData.audio_found) {
} else {
Log.debug("Delta audio/video m/M PTS:" + (fragData.pts_min_video - fragData.pts_min_audio) + "/" + (fragData.pts_max_video - fragData.pts_max_audio));
}
}
}
// Calculate bandwidth
_metrics.parsing_end_time = getTimer();
CONFIG::LOGGING {
Log.debug("Total Process duration/length/bw:" + _metrics.processing_duration + "/" + _metrics.size + "/" + Math.round(_metrics.bandwidth / 1024) + " kb/s");
}
if (_manifestJustLoaded) {
_manifestJustLoaded = false;
if (HLSSettings.startFromLevel === -1 && HLSSettings.startFromBitrate === -1 && _levels.length > 1 && !_levelController.isStartLevelSet()) {
// check if we can directly switch to a better bitrate, in case download bandwidth is enough
var bestlevel : int = _levelController.getAutoStartBestLevel(_metrics.bandwidth,_metrics.processing_duration, 1000*_fragCurrent.duration);
if (bestlevel > fragLevelIdx) {
CONFIG::LOGGING {
Log.info("enough download bandwidth, adjust start level from 0 to " + bestlevel);
}
// dispatch event for tracking purpose
_hls.dispatchEvent(new HLSEvent(HLSEvent.FRAGMENT_LOADED, _metrics));
// let's directly jump to the accurate level to improve quality at player start
_levelNext = bestlevel;
_loadingState = LOADING_IDLE;
_switchLevel = true;
_demux = null;
_hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_SWITCH, fragLevelIdx));
// speed up loading of new playlist
_timer.start();
return;
}
}
}
try {
_switchLevel = false;
_levelNext = -1;
var fragLevel : Level = _levels[fragLevelIdx];
CONFIG::LOGGING {
Log.debug("Loaded " + _fragCurrent.seqnum + " of [" + (fragLevel.start_seqnum) + "," + (fragLevel.end_seqnum) + "],level " + fragLevelIdx + " m/M PTS:" + fragData.pts_min + "/" + fragData.pts_max);
}
if (fragData.audio_found || fragData.video_found) {
fragLevel.updateFragment(_fragCurrent.seqnum, true, fragData.pts_min, fragData.pts_max + fragData.tag_duration);
// set pts_start here, it might not be updated directly in updateFragment() if this loaded fragment has been removed from a live playlist
fragData.pts_start = fragData.pts_min;
_hls.dispatchEvent(new HLSEvent(HLSEvent.PLAYLIST_DURATION_UPDATED, fragLevel.duration));
if (fragData.tags.length) {
if (fragData.metadata_tag_injected == false) {
fragData.tags.unshift(_fragCurrent.getMetadataTag());
if (_hasDiscontinuity) {
fragData.tags.unshift(new FLVTag(FLVTag.DISCONTINUITY, fragData.dts_min, fragData.dts_min, false));
}
fragData.metadata_tag_injected = true;
}
_streamBuffer.appendTags(HLSLoaderTypes.FRAGMENT_MAIN, _fragCurrent.level,_fragCurrent.seqnum , fragData.tags, fragData.tag_pts_min, fragData.tag_pts_max + fragData.tag_duration, _fragCurrent.continuity, _fragCurrent.start_time + fragData.tag_pts_start_offset / 1000);
_metrics.duration = fragData.pts_max + fragData.tag_duration - fragData.pts_min;
_metrics.id2 = fragData.tags.length;
_hls.dispatchEvent(new HLSEvent(HLSEvent.TAGS_LOADED, _metrics));
fragData.shiftTags();
_hasDiscontinuity = false;
}
} else {
_metrics.duration = _fragCurrent.duration * 1000;
}
_loadingState = LOADING_IDLE;
_ptsAnalyzing = false;
_hls.dispatchEvent(new HLSEvent(HLSEvent.FRAGMENT_LOADED, _metrics));
_fragmentFirstLoaded = true;
_fragPrevious = _fragCurrent;
} catch (error : Error) {
hlsError = new HLSError(HLSError.OTHER_ERROR, _fragCurrent.url, error.message);
_hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
}
// speed up loading of new fragment
_timer.start();
}
}
}