App-MHFS
view release on metacpan or search on metacpan
share/public_html/static/hls.js view on Meta::CPAN
function task_loop__possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function task_loop__inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.cre...
/**
* Sub-class specialization of EventHandler base class.
*
* TaskLoop allows to schedule a task function being called (optionnaly repeatedly) on the main loop,
* scheduled asynchroneously, avoiding recursive calls in the same tick.
*
* The task itself is implemented in `doTick`. It can be requested and called for single execution
* using the `tick` method.
*
* It will be assured that the task execution method (`tick`) only gets called once per main loop "tick",
* no matter how often it gets requested for execution. Execution in further ticks will be scheduled accordingly.
*
* If further execution requests have already been scheduled on the next tick, it can be checked with `hasNextTick`,
* and cancelled with `clearNextTick`.
*
* The task can be scheduled as an interval repeatedly with a period as parameter (see `setInterval`, `clearInterval`).
*
* Sub-classes need to implement the `doTick` method which will effectively have the task execution routine.
*
* Further explanations:
*
* The baseclass has a `tick` method that will schedule the doTick call. It may be called synchroneously
* only for a stack-depth of one. On re-entrant calls, sub-sequent calls are scheduled for next main loop ticks.
*
* When the task execution (`tick` method) is called in re-entrant way this is detected and
* we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
* task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
*/
var TaskLoop = function (_EventHandler) {
task_loop__inherits(TaskLoop, _EventHandler);
function TaskLoop(hls) {
task_loop__classCallCheck(this, TaskLoop);
for (var _len = arguments.length, events = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
events[_key - 1] = arguments[_key];
}
var _this = task_loop__possibleConstructorReturn(this, _EventHandler.call.apply(_EventHandler, [this, hls].concat(events)));
_this._tickInterval = null;
_this._tickTimer = null;
_this._tickCallCount = 0;
_this._boundTick = _this.tick.bind(_this);
return _this;
}
/**
* @override
*/
TaskLoop.prototype.onHandlerDestroying = function onHandlerDestroying() {
// clear all timers before unregistering from event bus
this.clearNextTick();
this.clearInterval();
};
/**
* @returns {boolean}
*/
TaskLoop.prototype.hasInterval = function hasInterval() {
return !!this._tickInterval;
};
/**
* @returns {boolean}
*/
TaskLoop.prototype.hasNextTick = function hasNextTick() {
return !!this._tickTimer;
};
/**
* @param {number} millis Interval time (ms)
* @returns {boolean} True when interval has been scheduled, false when already scheduled (no effect)
*/
TaskLoop.prototype.setInterval = function (_setInterval) {
function setInterval(_x) {
return _setInterval.apply(this, arguments);
}
setInterval.toString = function () {
return _setInterval.toString();
};
return setInterval;
}(function (millis) {
if (!this._tickInterval) {
this._tickInterval = setInterval(this._boundTick, millis);
return true;
}
return false;
});
/**
* @returns {boolean} True when interval was cleared, false when none was set (no effect)
*/
TaskLoop.prototype.clearInterval = function (_clearInterval) {
function clearInterval() {
return _clearInterval.apply(this, arguments);
}
clearInterval.toString = function () {
return _clearInterval.toString();
};
return clearInterval;
}(function () {
if (this._tickInterval) {
clearInterval(this._tickInterval);
this._tickInterval = null;
return true;
}
return false;
});
/**
* @returns {boolean} True when timeout was cleared, false when none was set (no effect)
*/
TaskLoop.prototype.clearNextTick = function clearNextTick() {
if (this._tickTimer) {
clearTimeout(this._tickTimer);
this._tickTimer = null;
return true;
}
return false;
};
/**
* Will call the subclass doTick implementation in this main loop tick
* or in the next one (via setTimeout(,0)) in case it has already been called
* in this tick (in case this is a re-entrant call).
*/
TaskLoop.prototype.tick = function tick() {
this._tickCallCount++;
if (this._tickCallCount === 1) {
this.doTick();
// re-entrant call to tick from previous doTick call stack
// -> schedule a call on the next main loop iteration to process this task processing request
if (this._tickCallCount > 1) {
// make sure only one timer exists at any time at max
this.clearNextTick();
this._tickTimer = setTimeout(this._boundTick, 0);
}
this._tickCallCount = 0;
}
};
/**
* For subclass to implement task logic
* @abstract
*/
TaskLoop.prototype.doTick = function doTick() {};
return TaskLoop;
}(event_handler);
/* harmony default export */ var task_loop = (TaskLoop);
// CONCATENATED MODULE: ./src/controller/fragment-finders.js
/**
* Calculates the PDT of the next load position.
* bufferEnd in this function is usually the position of the playhead.
* @param {number} [start = 0] - The PTS of the first fragment within the level
* @param {number} [bufferEnd = 0] - The end of the contiguous buffered range the playhead is currently within
* @param {*} levelDetails - An object containing the parsed and computed properties of the currently playing level
* @returns {number} nextPdt - The computed PDT
*/
function calculateNextPDT() {
var start = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
var bufferEnd = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var levelDetails = arguments[2];
var pdt = 0;
if (levelDetails.programDateTime) {
var parsedDateInt = Date.parse(levelDetails.programDateTime);
if (!isNaN(parsedDateInt)) {
pdt = bufferEnd * 1000 + parsedDateInt - 1000 * start;
}
}
return pdt;
}
/**
* Finds the first fragment whose endPDT value exceeds the given PDT.
* @param {Array<Fragment>} fragments - The array of candidate fragments
* @param {number|null} [PDTValue = null] - The PDT value which must be exceeded
* @returns {*|null} fragment - The best matching fragment
*/
function findFragmentByPDT(fragments) {
var PDTValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
if (!Array.isArray(fragments) || !fragments.length || PDTValue === null) {
return null;
}
// if less than start
var firstSegment = fragments[0];
share/public_html/static/hls.js view on Meta::CPAN
StreamController.prototype.onFragLoadEmergencyAborted = function onFragLoadEmergencyAborted() {
this.state = State.IDLE;
// if loadedmetadata is not set, it means that we are emergency switch down on first frag
// in that case, reset startFragRequested flag
if (!this.loadedmetadata) {
this.startFragRequested = false;
this.nextLoadPosition = this.startPosition;
}
this.tick();
};
StreamController.prototype.onBufferFlushed = function onBufferFlushed() {
/* after successful buffer flushing, filter flushed fragments from bufferedFrags
use mediaBuffered instead of media (so that we will check against video.buffered ranges in case of alt audio track)
*/
var media = this.mediaBuffer ? this.mediaBuffer : this.media;
if (media) {
// filter fragments potentially evicted from buffer. this is to avoid memleak on live streams
this.fragmentTracker.detectEvictedFragments(loader_fragment.ElementaryStreamTypes.VIDEO, media.buffered);
}
// move to IDLE once flush complete. this should trigger new fragment loading
this.state = State.IDLE;
// reset reference to frag
this.fragPrevious = null;
};
StreamController.prototype.swapAudioCodec = function swapAudioCodec() {
this.audioCodecSwap = !this.audioCodecSwap;
};
StreamController.prototype.computeLivePosition = function computeLivePosition(sliding, levelDetails) {
var targetLatency = this.config.liveSyncDuration !== undefined ? this.config.liveSyncDuration : this.config.liveSyncDurationCount * levelDetails.targetduration;
return sliding + Math.max(0, levelDetails.totalduration - targetLatency);
};
/**
* Detects and attempts to fix known buffer stalling issues.
* @param bufferInfo - The properties of the current buffer.
* @param stalledDuration - The amount of time Hls.js has been stalling for.
* @private
*/
StreamController.prototype._tryFixBufferStall = function _tryFixBufferStall(bufferInfo, stalledDuration) {
var config = this.config,
media = this.media;
var currentTime = media.currentTime;
var jumpThreshold = 0.5; // tolerance needed as some browsers stalls playback before reaching buffered range end
var partial = this.fragmentTracker.getPartialFragment(currentTime);
if (partial) {
// Try to skip over the buffer hole caused by a partial fragment
// This method isn't limited by the size of the gap between buffered ranges
this._trySkipBufferHole(partial);
}
if (bufferInfo.len > jumpThreshold && stalledDuration > config.highBufferWatchdogPeriod * 1000) {
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
// We only try to jump the hole if it's under the configured size
// Reset stalled so to rearm watchdog timer
this.stalled = null;
this._tryNudgeBuffer();
}
};
/**
* Triggers a BUFFER_STALLED_ERROR event, but only once per stall period.
* @param bufferLen - The playhead distance from the end of the current buffer segment.
* @private
*/
StreamController.prototype._reportStall = function _reportStall(bufferLen) {
var hls = this.hls,
media = this.media,
stallReported = this.stallReported;
if (!stallReported) {
// Report stalled error once
this.stallReported = true;
logger["b" /* logger */].warn('Playback stalling at @' + media.currentTime + ' due to low buffer');
hls.trigger(events["a" /* default */].ERROR, {
type: errors["b" /* ErrorTypes */].MEDIA_ERROR,
details: errors["a" /* ErrorDetails */].BUFFER_STALLED_ERROR,
fatal: false,
buffer: bufferLen
});
}
};
/**
* Attempts to fix buffer stalls by jumping over known gaps caused by partial fragments
* @param partial - The partial fragment found at the current time (where playback is stalling).
* @private
*/
StreamController.prototype._trySkipBufferHole = function _trySkipBufferHole(partial) {
var hls = this.hls,
media = this.media;
var currentTime = media.currentTime;
var lastEndTime = 0;
// Check if currentTime is between unbuffered regions of partial fragments
for (var i = 0; i < media.buffered.length; i++) {
var startTime = media.buffered.start(i);
if (currentTime >= lastEndTime && currentTime < startTime) {
media.currentTime = Math.max(startTime, media.currentTime + 0.1);
logger["b" /* logger */].warn('skipping hole, adjusting currentTime from ' + currentTime + ' to ' + media.currentTime);
this.stalled = null;
hls.trigger(events["a" /* default */].ERROR, {
type: errors["b" /* ErrorTypes */].MEDIA_ERROR,
details: errors["a" /* ErrorDetails */].BUFFER_SEEK_OVER_HOLE,
fatal: false,
reason: 'fragment loaded with buffer holes, seeking from ' + currentTime + ' to ' + media.currentTime,
frag: partial
});
return;
}
lastEndTime = media.buffered.end(i);
share/public_html/static/hls.js view on Meta::CPAN
}, {
key: 'nextLevel',
get: function get() {
var frag = this.nextBufferedFrag;
if (frag) {
return frag.level;
} else {
return -1;
}
}
}, {
key: 'liveSyncPosition',
get: function get() {
return this._liveSyncPosition;
},
set: function set(value) {
this._liveSyncPosition = value;
}
}]);
return StreamController;
}(task_loop);
/* harmony default export */ var stream_controller = (stream_controller_StreamController);
// CONCATENATED MODULE: ./src/controller/level-controller.js
var level_controller__typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.p...
var level_controller__createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; i...
function level_controller__classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function level_controller__possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : ...
function level_controller__inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Obj...
/*
* Level Controller
*/
var level_controller__window = window,
level_controller_performance = level_controller__window.performance;
var level_controller_LevelController = function (_EventHandler) {
level_controller__inherits(LevelController, _EventHandler);
function LevelController(hls) {
level_controller__classCallCheck(this, LevelController);
var _this = level_controller__possibleConstructorReturn(this, _EventHandler.call(this, hls, events["a" /* default */].MANIFEST_LOADED, events["a" /* default */].LEVEL_LOADED, events["a" /* default */].AUDIO_TRACK_SWITCHED, events["a" /* default *...
_this.canload = false;
_this.currentLevelIndex = null;
_this.manualLevelIndex = -1;
_this.timer = null;
return _this;
}
LevelController.prototype.onHandlerDestroying = function onHandlerDestroying() {
this.clearTimer();
this.manualLevelIndex = -1;
};
LevelController.prototype.clearTimer = function clearTimer() {
if (this.timer !== null) {
clearTimeout(this.timer);
this.timer = null;
}
};
LevelController.prototype.startLoad = function startLoad() {
var levels = this._levels;
this.canload = true;
this.levelRetryCount = 0;
// clean up live level details to force reload them, and reset load errors
if (levels) {
levels.forEach(function (level) {
level.loadError = 0;
var levelDetails = level.details;
if (levelDetails && levelDetails.live) {
level.details = undefined;
}
});
}
// speed up live playlist refresh if timer exists
if (this.timer !== null) {
this.loadLevel();
}
};
LevelController.prototype.stopLoad = function stopLoad() {
this.canload = false;
};
LevelController.prototype.onManifestLoaded = function onManifestLoaded(data) {
var levels = [];
var bitrateStart = void 0;
var levelSet = {};
var levelFromSet = null;
var videoCodecFound = false;
var audioCodecFound = false;
var chromeOrFirefox = /chrome|firefox/.test(navigator.userAgent.toLowerCase());
var audioTracks = [];
// regroup redundant levels together
data.levels.forEach(function (level) {
level.loadError = 0;
level.fragmentError = false;
videoCodecFound = videoCodecFound || !!level.videoCodec;
audioCodecFound = audioCodecFound || !!level.audioCodec || !!(level.attrs && level.attrs.AUDIO);
// erase audio codec info if browser does not support mp4a.40.34.
// demuxer will autodetect codec and fallback to mpeg/audio
if (chromeOrFirefox && level.audioCodec && level.audioCodec.indexOf('mp4a.40.34') !== -1) {
level.audioCodec = undefined;
}
levelFromSet = levelSet[level.bitrate]; // FIXME: we would also have to match the resolution here
if (!levelFromSet) {
level.url = [level.url];
level.urlId = 0;
levelSet[level.bitrate] = level;
levels.push(level);
} else {
levelFromSet.url.push(level.url);
}
if (level.attrs && level.attrs.AUDIO) {
addGroupId(levelFromSet || level, 'audio', level.attrs.AUDIO);
}
if (level.attrs && level.attrs.SUBTITLES) {
addGroupId(levelFromSet || level, 'text', level.attrs.SUBTITLES);
}
});
// remove audio-only level if we also have levels with audio+video codecs signalled
if (videoCodecFound && audioCodecFound) {
levels = levels.filter(function (_ref) {
var videoCodec = _ref.videoCodec;
return !!videoCodec;
});
}
// only keep levels with supported audio/video codecs
levels = levels.filter(function (_ref2) {
var audioCodec = _ref2.audioCodec,
videoCodec = _ref2.videoCodec;
return (!audioCodec || isCodecSupportedInMp4(audioCodec)) && (!videoCodec || isCodecSupportedInMp4(videoCodec));
});
if (data.audioTracks) {
audioTracks = data.audioTracks.filter(function (track) {
return !track.audioCodec || isCodecSupportedInMp4(track.audioCodec, 'audio');
});
}
if (levels.length > 0) {
// start bitrate is the first bitrate of the manifest
bitrateStart = levels[0].bitrate;
// sort level on bitrate
levels.sort(function (a, b) {
return a.bitrate - b.bitrate;
});
this._levels = levels;
// find index of first level in sorted levels
for (var i = 0; i < levels.length; i++) {
if (levels[i].bitrate === bitrateStart) {
this._firstLevel = i;
logger["b" /* logger */].log('manifest loaded,' + levels.length + ' level(s) found, first bitrate:' + bitrateStart);
break;
}
}
this.hls.trigger(events["a" /* default */].MANIFEST_PARSED, {
levels: levels,
audioTracks: audioTracks,
firstLevel: this._firstLevel,
stats: data.stats,
audio: audioCodecFound,
video: videoCodecFound,
altAudio: audioTracks.length > 0 && videoCodecFound
});
} else {
this.hls.trigger(events["a" /* default */].ERROR, {
type: errors["b" /* ErrorTypes */].MEDIA_ERROR,
details: errors["a" /* ErrorDetails */].MANIFEST_INCOMPATIBLE_CODECS_ERROR,
fatal: true,
url: this.hls.url,
reason: 'no level with compatible codecs found in manifest'
});
}
};
LevelController.prototype.setLevelInternal = function setLevelInternal(newLevel) {
var levels = this._levels;
var hls = this.hls;
// check if level idx is valid
if (newLevel >= 0 && newLevel < levels.length) {
// stopping live reloading timer if any
this.clearTimer();
if (this.currentLevelIndex !== newLevel) {
logger["b" /* logger */].log('switching to level ' + newLevel);
this.currentLevelIndex = newLevel;
var levelProperties = levels[newLevel];
levelProperties.level = newLevel;
hls.trigger(events["a" /* default */].LEVEL_SWITCHING, levelProperties);
}
var level = levels[newLevel];
var levelDetails = level.details;
// check if we need to load playlist for this level
if (!levelDetails || levelDetails.live) {
// level not retrieved yet, or live playlist we need to (re)load it
var urlId = level.urlId;
hls.trigger(events["a" /* default */].LEVEL_LOADING, { url: level.url[urlId], level: newLevel, id: urlId });
}
} else {
// invalid level id given, trigger error
hls.trigger(events["a" /* default */].ERROR, {
type: errors["b" /* ErrorTypes */].OTHER_ERROR,
details: errors["a" /* ErrorDetails */].LEVEL_SWITCH_ERROR,
level: newLevel,
fatal: false,
reason: 'invalid level idx'
});
}
};
LevelController.prototype.onError = function onError(data) {
if (data.fatal) {
if (data.type === errors["b" /* ErrorTypes */].NETWORK_ERROR) {
this.clearTimer();
}
return;
}
var levelError = false,
fragmentError = false;
var levelIndex = void 0;
// try to recover not fatal errors
switch (data.details) {
case errors["a" /* ErrorDetails */].FRAG_LOAD_ERROR:
case errors["a" /* ErrorDetails */].FRAG_LOAD_TIMEOUT:
case errors["a" /* ErrorDetails */].KEY_LOAD_ERROR:
case errors["a" /* ErrorDetails */].KEY_LOAD_TIMEOUT:
levelIndex = data.frag.level;
fragmentError = true;
break;
case errors["a" /* ErrorDetails */].LEVEL_LOAD_ERROR:
case errors["a" /* ErrorDetails */].LEVEL_LOAD_TIMEOUT:
levelIndex = data.context.level;
levelError = true;
break;
case errors["a" /* ErrorDetails */].REMUX_ALLOC_ERROR:
levelIndex = data.level;
levelError = true;
break;
}
if (levelIndex !== undefined) {
this.recoverLevel(data, levelIndex, levelError, fragmentError);
}
};
/**
* Switch to a redundant stream if any available.
* If redundant stream is not available, emergency switch down if ABR mode is enabled.
*
* @param {Object} errorEvent
* @param {Number} levelIndex current level index
* @param {Boolean} levelError
* @param {Boolean} fragmentError
*/
// FIXME Find a better abstraction where fragment/level retry management is well decoupled
LevelController.prototype.recoverLevel = function recoverLevel(errorEvent, levelIndex, levelError, fragmentError) {
var _this2 = this;
var config = this.hls.config;
var errorDetails = errorEvent.details;
var level = this._levels[levelIndex];
var redundantLevels = void 0,
delay = void 0,
nextLevel = void 0;
level.loadError++;
level.fragmentError = fragmentError;
if (levelError) {
if (this.levelRetryCount + 1 <= config.levelLoadingMaxRetry) {
// exponential backoff capped to max retry timeout
delay = Math.min(Math.pow(2, this.levelRetryCount) * config.levelLoadingRetryDelay, config.levelLoadingMaxRetryTimeout);
// Schedule level reload
this.timer = setTimeout(function () {
return _this2.loadLevel();
}, delay);
// boolean used to inform stream controller not to switch back to IDLE on non fatal error
errorEvent.levelRetry = true;
this.levelRetryCount++;
logger["b" /* logger */].warn('level controller, ' + errorDetails + ', retry in ' + delay + ' ms, current retry count is ' + this.levelRetryCount);
} else {
logger["b" /* logger */].error('level controller, cannot recover from ' + errorDetails + ' error');
this.currentLevelIndex = null;
// stopping live reloading timer if any
this.clearTimer();
// switch error to fatal
errorEvent.fatal = true;
return;
}
}
// Try any redundant streams if available for both errors: level and fragment
// If level.loadError reaches redundantLevels it means that we tried them all, no hope => let's switch down
if (levelError || fragmentError) {
redundantLevels = level.url.length;
if (redundantLevels > 1 && level.loadError < redundantLevels) {
level.urlId = (level.urlId + 1) % redundantLevels;
level.details = undefined;
logger["b" /* logger */].warn('level controller, ' + errorDetails + ' for level ' + levelIndex + ': switching to redundant URL-id ' + level.urlId);
// console.log('Current audio track group ID:', this.hls.audioTracks[this.hls.audioTrack].groupId);
// console.log('New video quality level audio group id:', level.attrs.AUDIO);
} else {
// Search for available level
if (this.manualLevelIndex === -1) {
// When lowest level has been reached, let's start hunt from the top
nextLevel = levelIndex === 0 ? this._levels.length - 1 : levelIndex - 1;
logger["b" /* logger */].warn('level controller, ' + errorDetails + ': switch to ' + nextLevel);
this.hls.nextAutoLevel = this.currentLevelIndex = nextLevel;
} else if (fragmentError) {
// Allow fragment retry as long as configuration allows.
// reset this._level so that another call to set level() will trigger again a frag load
logger["b" /* logger */].warn('level controller, ' + errorDetails + ': reload a fragment');
this.currentLevelIndex = null;
}
}
}
};
// reset errors on the successful load of a fragment
LevelController.prototype.onFragLoaded = function onFragLoaded(_ref3) {
var frag = _ref3.frag;
if (frag !== undefined && frag.type === 'main') {
var level = this._levels[frag.level];
if (level !== undefined) {
level.fragmentError = false;
level.loadError = 0;
this.levelRetryCount = 0;
}
}
};
LevelController.prototype.onLevelLoaded = function onLevelLoaded(data) {
var _this3 = this;
var levelId = data.level;
// only process level loaded events matching with expected level
if (levelId !== this.currentLevelIndex) {
return;
}
var curLevel = this._levels[levelId];
// reset level load error counter on successful level loaded only if there is no issues with fragments
if (!curLevel.fragmentError) {
curLevel.loadError = 0;
this.levelRetryCount = 0;
}
var newDetails = data.details;
// if current playlist is a live playlist, arm a timer to reload it
if (newDetails.live) {
var targetdurationMs = 1000 * (newDetails.averagetargetduration ? newDetails.averagetargetduration : newDetails.targetduration);
var reloadInterval = targetdurationMs,
curDetails = curLevel.details;
if (curDetails && newDetails.endSN === curDetails.endSN) {
// follow HLS Spec, If the client reloads a Playlist file and finds that it has not
// changed then it MUST wait for a period of one-half the target
// duration before retrying.
reloadInterval /= 2;
logger["b" /* logger */].log('same live playlist, reload twice faster');
}
// decrement reloadInterval with level loading delay
reloadInterval -= level_controller_performance.now() - data.stats.trequest;
// in any case, don't reload more than half of target duration
reloadInterval = Math.max(targetdurationMs / 2, Math.round(reloadInterval));
logger["b" /* logger */].log('live playlist, reload in ' + Math.round(reloadInterval) + ' ms');
this.timer = setTimeout(function () {
return _this3.loadLevel();
}, reloadInterval);
} else {
this.clearTimer();
}
};
LevelController.prototype.onAudioTrackSwitched = function onAudioTrackSwitched(data) {
var audioGroupId = this.hls.audioTracks[data.id].groupId;
var currentLevel = this.hls.levels[this.currentLevelIndex];
if (!currentLevel) {
return;
}
if (currentLevel.audioGroupIds) {
var urlId = currentLevel.audioGroupIds.findIndex(function (groupId) {
return groupId === audioGroupId;
});
if (urlId !== currentLevel.urlId) {
currentLevel.urlId = urlId;
this.startLoad();
}
}
};
LevelController.prototype.loadLevel = function loadLevel() {
logger["b" /* logger */].debug('call to loadLevel');
if (this.currentLevelIndex !== null && this.canload) {
var levelObject = this._levels[this.currentLevelIndex];
if ((typeof levelObject === 'undefined' ? 'undefined' : level_controller__typeof(levelObject)) === 'object' && levelObject.url.length > 0) {
var level = this.currentLevelIndex;
var id = levelObject.urlId;
var url = levelObject.url[id];
logger["b" /* logger */].log('Attempt loading level index ' + level + ' with URL-id ' + id);
// console.log('Current audio track group ID:', this.hls.audioTracks[this.hls.audioTrack].groupId);
// console.log('New video quality level audio group id:', levelObject.attrs.AUDIO, level);
this.hls.trigger(events["a" /* default */].LEVEL_LOADING, { url: url, level: level, id: id });
}
}
};
level_controller__createClass(LevelController, [{
key: 'levels',
get: function get() {
return this._levels;
}
}, {
key: 'level',
get: function get() {
return this.currentLevelIndex;
},
set: function set(newLevel) {
var levels = this._levels;
if (levels) {
share/public_html/static/hls.js view on Meta::CPAN
EwmaBandWidthEstimator.prototype.canEstimate = function canEstimate() {
var fast = this.fast_;
return fast && fast.getTotalWeight() >= this.minWeight_;
};
EwmaBandWidthEstimator.prototype.getEstimate = function getEstimate() {
if (this.canEstimate()) {
// console.log('slow estimate:'+ Math.round(this.slow_.getEstimate()));
// console.log('fast estimate:'+ Math.round(this.fast_.getEstimate()));
// Take the minimum of these two estimates. This should have the effect of
// adapting down quickly, but up more slowly.
return Math.min(this.fast_.getEstimate(), this.slow_.getEstimate());
} else {
return this.defaultEstimate_;
}
};
EwmaBandWidthEstimator.prototype.destroy = function destroy() {};
return EwmaBandWidthEstimator;
}();
/* harmony default export */ var ewma_bandwidth_estimator = (ewma_bandwidth_estimator_EwmaBandWidthEstimator);
// CONCATENATED MODULE: ./src/controller/abr-controller.js
var abr_controller__createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ...
function abr_controller__classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function abr_controller__possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : se...
function abr_controller__inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Objec...
/*
* simple ABR Controller
* - compute next level based on last fragment bw heuristics
* - implement an abandon rules triggered if we have less than 2 frag buffered and if computed bw shows that we risk buffer stalling
*/
var abr_controller__window = window,
abr_controller_performance = abr_controller__window.performance;
var abr_controller_AbrController = function (_EventHandler) {
abr_controller__inherits(AbrController, _EventHandler);
function AbrController(hls) {
abr_controller__classCallCheck(this, AbrController);
var _this = abr_controller__possibleConstructorReturn(this, _EventHandler.call(this, hls, events["a" /* default */].FRAG_LOADING, events["a" /* default */].FRAG_LOADED, events["a" /* default */].FRAG_BUFFERED, events["a" /* default */].ERROR));
_this.lastLoadedFragLevel = 0;
_this._nextAutoLevel = -1;
_this.hls = hls;
_this.timer = null;
_this._bwEstimator = null;
_this.onCheck = _this._abandonRulesCheck.bind(_this);
return _this;
}
AbrController.prototype.destroy = function destroy() {
this.clearTimer();
event_handler.prototype.destroy.call(this);
};
AbrController.prototype.onFragLoading = function onFragLoading(data) {
var frag = data.frag;
if (frag.type === 'main') {
if (!this.timer) {
this.fragCurrent = frag;
this.timer = setInterval(this.onCheck, 100);
}
// lazy init of BwEstimator, rationale is that we use different params for Live/VoD
// so we need to wait for stream manifest / playlist type to instantiate it.
if (!this._bwEstimator) {
var hls = this.hls;
var config = hls.config;
var level = frag.level;
var isLive = hls.levels[level].details.live;
var ewmaFast = void 0,
ewmaSlow = void 0;
if (isLive) {
ewmaFast = config.abrEwmaFastLive;
ewmaSlow = config.abrEwmaSlowLive;
} else {
ewmaFast = config.abrEwmaFastVoD;
ewmaSlow = config.abrEwmaSlowVoD;
}
this._bwEstimator = new ewma_bandwidth_estimator(hls, ewmaSlow, ewmaFast, config.abrEwmaDefaultEstimate);
}
}
};
AbrController.prototype._abandonRulesCheck = function _abandonRulesCheck() {
/*
monitor fragment retrieval time...
we compute expected time of arrival of the complete fragment.
we compare it to expected time of buffer starvation
*/
var hls = this.hls;
var video = hls.media;
var frag = this.fragCurrent;
if (!frag) {
return;
}
var loader = frag.loader;
var minAutoLevel = hls.minAutoLevel;
// if loader has been destroyed or loading has been aborted, stop timer and return
if (!loader || loader.stats && loader.stats.aborted) {
logger["b" /* logger */].warn('frag loader destroy or aborted, disarm abandonRules');
this.clearTimer();
// reset forced auto level value so that next level will be selected
this._nextAutoLevel = -1;
return;
}
var stats = loader.stats;
/* only monitor frag retrieval time if
(video not paused OR first fragment being loaded(ready state === HAVE_NOTHING = 0)) AND autoswitching enabled AND not lowest level (=> means that we have several levels) */
if (video && stats && (!video.paused && video.playbackRate !== 0 || !video.readyState) && frag.autoLevel && frag.level) {
var requestDelay = abr_controller_performance.now() - stats.trequest,
playbackRate = Math.abs(video.playbackRate);
// monitor fragment load progress after half of expected fragment duration,to stabilize bitrate
if (requestDelay > 500 * frag.duration / playbackRate) {
var levels = hls.levels,
loadRate = Math.max(1, stats.bw ? stats.bw / 8 : stats.loaded * 1000 / requestDelay),
// byte/s; at least 1 byte/s to avoid division by zero
// compute expected fragment length using frag duration and level bitrate. also ensure that expected len is gte than already loaded size
level = levels[frag.level],
levelBitrate = level.realBitrate ? Math.max(level.realBitrate, level.bitrate) : level.bitrate,
expectedLen = stats.total ? stats.total : Math.max(stats.loaded, Math.round(frag.duration * levelBitrate / 8)),
pos = video.currentTime,
fragLoadedDelay = (expectedLen - stats.loaded) / loadRate,
bufferStarvationDelay = (BufferHelper.bufferInfo(video, pos, hls.config.maxBufferHole).end - pos) / playbackRate;
// consider emergency switch down only if we have less than 2 frag buffered AND
// time to finish loading current fragment is bigger than buffer starvation delay
// ie if we risk buffer starvation if bw does not increase quickly
if (bufferStarvationDelay < 2 * frag.duration / playbackRate && fragLoadedDelay > bufferStarvationDelay) {
var fragLevelNextLoadedDelay = void 0,
nextLoadLevel = void 0;
// lets iterate through lower level and try to find the biggest one that could avoid rebuffering
// we start from current level - 1 and we step down , until we find a matching level
for (nextLoadLevel = frag.level - 1; nextLoadLevel > minAutoLevel; nextLoadLevel--) {
// compute time to load next fragment at lower level
// 0.8 : consider only 80% of current bw to be conservative
// 8 = bits per byte (bps/Bps)
var levelNextBitrate = levels[nextLoadLevel].realBitrate ? Math.max(levels[nextLoadLevel].realBitrate, levels[nextLoadLevel].bitrate) : levels[nextLoadLevel].bitrate;
fragLevelNextLoadedDelay = frag.duration * levelNextBitrate / (8 * 0.8 * loadRate);
if (fragLevelNextLoadedDelay < bufferStarvationDelay) {
// we found a lower level that be rebuffering free with current estimated bw !
break;
}
}
// only emergency switch down if it takes less time to load new fragment at lowest level instead
// of finishing loading current one ...
if (fragLevelNextLoadedDelay < fragLoadedDelay) {
logger["b" /* logger */].warn('loading too slow, abort fragment loading and switch to level ' + nextLoadLevel + ':fragLoadedDelay[' + nextLoadLevel + ']<fragLoadedDelay[' + (frag.level - 1) + '];bufferStarvationDelay:' + fragLevelNextLoad...
// force next load level in auto mode
hls.nextLoadLevel = nextLoadLevel;
// update bw estimate for this fragment before cancelling load (this will help reducing the bw)
this._bwEstimator.sample(requestDelay, stats.loaded);
// abort fragment loading
loader.abort();
// stop abandon rules timer
this.clearTimer();
hls.trigger(events["a" /* default */].FRAG_LOAD_EMERGENCY_ABORTED, { frag: frag, stats: stats });
}
}
}
}
};
AbrController.prototype.onFragLoaded = function onFragLoaded(data) {
var frag = data.frag;
if (frag.type === 'main' && !isNaN(frag.sn)) {
// stop monitoring bw once frag loaded
this.clearTimer();
// store level id after successful fragment load
this.lastLoadedFragLevel = frag.level;
// reset forced auto level value so that next level will be selected
this._nextAutoLevel = -1;
// compute level average bitrate
if (this.hls.config.abrMaxWithRealBitrate) {
var level = this.hls.levels[frag.level];
var loadedBytes = (level.loaded ? level.loaded.bytes : 0) + data.stats.loaded;
var loadedDuration = (level.loaded ? level.loaded.duration : 0) + data.frag.duration;
level.loaded = { bytes: loadedBytes, duration: loadedDuration };
level.realBitrate = Math.round(8 * loadedBytes / loadedDuration);
}
// if fragment has been loaded to perform a bitrate test,
if (data.frag.bitrateTest) {
var stats = data.stats;
stats.tparsed = stats.tbuffered = stats.tload;
this.onFragBuffered(data);
}
}
};
AbrController.prototype.onFragBuffered = function onFragBuffered(data) {
var stats = data.stats;
var frag = data.frag;
// only update stats on first frag buffering
// if same frag is loaded multiple times, it might be in browser cache, and loaded quickly
// and leading to wrong bw estimation
// on bitrate test, also only update stats once (if tload = tbuffered == on FRAG_LOADED)
if (stats.aborted !== true && frag.type === 'main' && !isNaN(frag.sn) && (!frag.bitrateTest || stats.tload === stats.tbuffered)) {
// use tparsed-trequest instead of tbuffered-trequest to compute fragLoadingProcessing; rationale is that buffer appending only happens once media is attached
// in case we use config.startFragPrefetch while media is not attached yet, fragment might be parsed while media not attached yet, but it will only be buffered on media attached
// as a consequence it could happen really late in the process. meaning that appending duration might appears huge ... leading to underestimated throughput estimation
var fragLoadingProcessingMs = stats.tparsed - stats.trequest;
logger["b" /* logger */].log('latency/loading/parsing/append/kbps:' + Math.round(stats.tfirst - stats.trequest) + '/' + Math.round(stats.tload - stats.tfirst) + '/' + Math.round(stats.tparsed - stats.tload) + '/' + Math.round(stats.tbuffered - ...
this._bwEstimator.sample(fragLoadingProcessingMs, stats.loaded);
stats.bwEstimate = this._bwEstimator.getEstimate();
// if fragment has been loaded to perform a bitrate test, (hls.startLevel = -1), store bitrate test delay duration
if (frag.bitrateTest) {
this.bitrateTestDelay = fragLoadingProcessingMs / 1000;
} else {
this.bitrateTestDelay = 0;
}
}
};
AbrController.prototype.onError = function onError(data) {
// stop timer in case of frag loading error
switch (data.details) {
case errors["a" /* ErrorDetails */].FRAG_LOAD_ERROR:
case errors["a" /* ErrorDetails */].FRAG_LOAD_TIMEOUT:
this.clearTimer();
break;
default:
break;
}
};
AbrController.prototype.clearTimer = function clearTimer() {
clearInterval(this.timer);
this.timer = null;
};
// return next auto level
AbrController.prototype._findBestLevel = function _findBestLevel(currentLevel, currentFragDuration, currentBw, minAutoLevel, maxAutoLevel, maxFetchDuration, bwFactor, bwUpFactor, levels) {
for (var i = maxAutoLevel; i >= minAutoLevel; i--) {
var levelInfo = levels[i],
levelDetails = levelInfo.details,
avgDuration = levelDetails ? levelDetails.totalduration / levelDetails.fragments.length : currentFragDuration,
live = levelDetails ? levelDetails.live : false,
adjustedbw = void 0;
// follow algorithm captured from stagefright :
// https://android.googlesource.com/platform/frameworks/av/+/master/media/libstagefright/httplive/LiveSession.cpp
// Pick the highest bandwidth stream below or equal to estimated bandwidth.
// consider only 80% of the available bandwidth, but if we are switching up,
// be even more conservative (70%) to avoid overestimating and immediately
// switching back.
if (i <= currentLevel) {
adjustedbw = bwFactor * currentBw;
} else {
adjustedbw = bwUpFactor * currentBw;
}
var bitrate = levels[i].realBitrate ? Math.max(levels[i].realBitrate, levels[i].bitrate) : levels[i].bitrate,
fetchDuration = bitrate * avgDuration / adjustedbw;
logger["b" /* logger */].trace('level/adjustedbw/bitrate/avgDuration/maxFetchDuration/fetchDuration: ' + i + '/' + Math.round(adjustedbw) + '/' + bitrate + '/' + avgDuration + '/' + maxFetchDuration + '/' + fetchDuration);
// if adjusted bw is greater than level bitrate AND
if (adjustedbw > bitrate && (
// fragment fetchDuration unknown OR live stream OR fragment fetchDuration less than max allowed fetch duration, then this level matches
// we don't account for max Fetch Duration for live streams, this is to avoid switching down when near the edge of live sliding window ...
// special case to support startLevel = -1 (bitrateTest) on live streams : in that case we should not exit loop so that _findBestLevel will return -1
!fetchDuration || live && !this.bitrateTestDelay || fetchDuration < maxFetchDuration)) {
// as we are looping from highest to lowest, this will return the best achievable quality level
return i;
}
}
// not enough time budget even with quality level 0 ... rebuffering might happen
return -1;
};
abr_controller__createClass(AbrController, [{
key: 'nextAutoLevel',
get: function get() {
var forcedAutoLevel = this._nextAutoLevel;
var bwEstimator = this._bwEstimator;
// in case next auto level has been forced, and bw not available or not reliable, return forced value
if (forcedAutoLevel !== -1 && (!bwEstimator || !bwEstimator.canEstimate())) {
return forcedAutoLevel;
}
// compute next level using ABR logic
var nextABRAutoLevel = this._nextABRAutoLevel;
// if forced auto level has been defined, use it to cap ABR computed quality level
if (forcedAutoLevel !== -1) {
nextABRAutoLevel = Math.min(forcedAutoLevel, nextABRAutoLevel);
}
return nextABRAutoLevel;
share/public_html/static/hls.js view on Meta::CPAN
if (Math.min(flushEnd, bufEnd) - flushStart > 0.5) {
this.flushBufferCounter++;
logger["b" /* logger */].log('flush ' + type + ' [' + flushStart + ',' + flushEnd + '], of [' + bufStart + ',' + bufEnd + '], pos:' + this.media.currentTime);
sb.remove(flushStart, flushEnd);
return false;
}
}
} catch (e) {
logger["b" /* logger */].warn('exception while accessing sourcebuffer, it might have been removed from MediaSource');
}
} else {
// logger.log('abort ' + type + ' append in progress');
// this will abort any appending in progress
// sb.abort();
logger["b" /* logger */].warn('cannot flush, sb updating in progress');
return false;
}
}
} else {
logger["b" /* logger */].warn('abort flushing too many retries');
}
logger["b" /* logger */].log('buffer flushed');
}
// everything flushed !
return true;
};
return BufferController;
}(event_handler);
/* harmony default export */ var buffer_controller = (buffer_controller_BufferController);
// CONCATENATED MODULE: ./src/controller/cap-level-controller.js
var cap_level_controller__createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = tru...
function cap_level_controller__classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function cap_level_controller__possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? cal...
function cap_level_controller__inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype =...
/*
* cap stream level to media size dimension controller
*/
var cap_level_controller_CapLevelController = function (_EventHandler) {
cap_level_controller__inherits(CapLevelController, _EventHandler);
function CapLevelController(hls) {
cap_level_controller__classCallCheck(this, CapLevelController);
var _this = cap_level_controller__possibleConstructorReturn(this, _EventHandler.call(this, hls, events["a" /* default */].FPS_DROP_LEVEL_CAPPING, events["a" /* default */].MEDIA_ATTACHING, events["a" /* default */].MANIFEST_PARSED, events["a" /* ...
_this.autoLevelCapping = Number.POSITIVE_INFINITY;
_this.firstLevel = null;
_this.levels = [];
_this.media = null;
_this.restrictedLevels = [];
_this.timer = null;
return _this;
}
CapLevelController.prototype.destroy = function destroy() {
if (this.hls.config.capLevelToPlayerSize) {
this.media = null;
this._stopCapping();
}
};
CapLevelController.prototype.onFpsDropLevelCapping = function onFpsDropLevelCapping(data) {
// Don't add a restricted level more than once
if (CapLevelController.isLevelAllowed(data.droppedLevel, this.restrictedLevels)) {
this.restrictedLevels.push(data.droppedLevel);
}
};
CapLevelController.prototype.onMediaAttaching = function onMediaAttaching(data) {
this.media = data.media instanceof window.HTMLVideoElement ? data.media : null;
};
CapLevelController.prototype.onManifestParsed = function onManifestParsed(data) {
var hls = this.hls;
this.restrictedLevels = [];
this.levels = data.levels;
this.firstLevel = data.firstLevel;
if (hls.config.capLevelToPlayerSize && (data.video || data.levels.length && data.altAudio)) {
// Start capping immediately if the manifest has signaled video codecs
this._startCapping();
}
};
// Only activate capping when playing a video stream; otherwise, multi-bitrate audio-only streams will be restricted
// to the first level
CapLevelController.prototype.onBufferCodecs = function onBufferCodecs(data) {
var hls = this.hls;
if (hls.config.capLevelToPlayerSize && data.video) {
// If the manifest did not signal a video codec capping has been deferred until we're certain video is present
this._startCapping();
}
};
CapLevelController.prototype.onLevelsUpdated = function onLevelsUpdated(data) {
this.levels = data.levels;
};
CapLevelController.prototype.detectPlayerSize = function detectPlayerSize() {
if (this.media) {
var levelsLength = this.levels ? this.levels.length : 0;
if (levelsLength) {
var hls = this.hls;
hls.autoLevelCapping = this.getMaxLevel(levelsLength - 1);
if (hls.autoLevelCapping > this.autoLevelCapping) {
// if auto level capping has a higher value for the previous one, flush the buffer using nextLevelSwitch
// usually happen when the user go to the fullscreen mode.
hls.streamController.nextLevelSwitch();
}
this.autoLevelCapping = hls.autoLevelCapping;
}
}
};
/*
* returns level should be the one with the dimensions equal or greater than the media (player) dimensions (so the video will be downscaled)
*/
CapLevelController.prototype.getMaxLevel = function getMaxLevel(capLevelIndex) {
var _this2 = this;
if (!this.levels) {
return -1;
}
var validLevels = this.levels.filter(function (level, index) {
return CapLevelController.isLevelAllowed(index, _this2.restrictedLevels) && index <= capLevelIndex;
});
return CapLevelController.getMaxLevelByMediaSize(validLevels, this.mediaWidth, this.mediaHeight);
};
CapLevelController.prototype._startCapping = function _startCapping() {
if (this.timer) {
// Don't reset capping if started twice; this can happen if the manifest signals a video codec
return;
}
this.autoLevelCapping = Number.POSITIVE_INFINITY;
this.hls.firstLevel = this.getMaxLevel(this.firstLevel);
clearInterval(this.timer);
this.timer = setInterval(this.detectPlayerSize.bind(this), 1000);
this.detectPlayerSize();
};
CapLevelController.prototype._stopCapping = function _stopCapping() {
this.restrictedLevels = [];
this.firstLevel = null;
this.autoLevelCapping = Number.POSITIVE_INFINITY;
if (this.timer) {
this.timer = clearInterval(this.timer);
this.timer = null;
}
};
CapLevelController.isLevelAllowed = function isLevelAllowed(level) {
var restrictedLevels = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
return restrictedLevels.indexOf(level) === -1;
};
CapLevelController.getMaxLevelByMediaSize = function getMaxLevelByMediaSize(levels, width, height) {
if (!levels || levels && !levels.length) {
return -1;
}
// Levels can have the same dimensions but differing bandwidths - since levels are ordered, we can look to the next
// to determine whether we've chosen the greatest bandwidth for the media's dimensions
var atGreatestBandiwdth = function atGreatestBandiwdth(curLevel, nextLevel) {
if (!nextLevel) {
return true;
}
return curLevel.width !== nextLevel.width || curLevel.height !== nextLevel.height;
};
// If we run through the loop without breaking, the media's dimensions are greater than every level, so default to
// the max level
var maxLevelIndex = levels.length - 1;
for (var i = 0; i < levels.length; i += 1) {
var level = levels[i];
if ((level.width >= width || level.height >= height) && atGreatestBandiwdth(level, levels[i + 1])) {
maxLevelIndex = i;
break;
}
}
return maxLevelIndex;
};
cap_level_controller__createClass(CapLevelController, [{
key: 'mediaWidth',
get: function get() {
var width = void 0;
var media = this.media;
if (media) {
width = media.width || media.clientWidth || media.offsetWidth;
width *= CapLevelController.contentScaleFactor;
}
return width;
}
}, {
key: 'mediaHeight',
get: function get() {
var height = void 0;
var media = this.media;
if (media) {
height = media.height || media.clientHeight || media.offsetHeight;
height *= CapLevelController.contentScaleFactor;
}
return height;
}
}], [{
key: 'contentScaleFactor',
get: function get() {
var pixelRatio = 1;
try {
pixelRatio = window.devicePixelRatio;
} catch (e) {}
return pixelRatio;
}
}]);
return CapLevelController;
}(event_handler);
/* harmony default export */ var cap_level_controller = (cap_level_controller_CapLevelController);
// CONCATENATED MODULE: ./src/controller/fps-controller.js
function fps_controller__classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function fps_controller__possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : se...
function fps_controller__inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Objec...
/*
* FPS Controller
*/
var fps_controller__window = window,
fps_controller_performance = fps_controller__window.performance;
var fps_controller_FPSController = function (_EventHandler) {
fps_controller__inherits(FPSController, _EventHandler);
function FPSController(hls) {
fps_controller__classCallCheck(this, FPSController);
return fps_controller__possibleConstructorReturn(this, _EventHandler.call(this, hls, events["a" /* default */].MEDIA_ATTACHING));
}
FPSController.prototype.destroy = function destroy() {
if (this.timer) {
clearInterval(this.timer);
}
this.isVideoPlaybackQualityAvailable = false;
};
FPSController.prototype.onMediaAttaching = function onMediaAttaching(data) {
var config = this.hls.config;
if (config.capLevelOnFPSDrop) {
var video = this.video = data.media instanceof window.HTMLVideoElement ? data.media : null;
if (typeof video.getVideoPlaybackQuality === 'function') {
this.isVideoPlaybackQualityAvailable = true;
}
clearInterval(this.timer);
this.timer = setInterval(this.checkFPSInterval.bind(this), config.fpsDroppedMonitoringPeriod);
}
};
FPSController.prototype.checkFPS = function checkFPS(video, decodedFrames, droppedFrames) {
var currentTime = fps_controller_performance.now();
if (decodedFrames) {
if (this.lastTime) {
var currentPeriod = currentTime - this.lastTime,
currentDropped = droppedFrames - this.lastDroppedFrames,
currentDecoded = decodedFrames - this.lastDecodedFrames,
droppedFPS = 1000 * currentDropped / currentPeriod,
hls = this.hls;
hls.trigger(events["a" /* default */].FPS_DROP, { currentDropped: currentDropped, currentDecoded: currentDecoded, totalDroppedFrames: droppedFrames });
if (droppedFPS > 0) {
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
var currentLevel = hls.currentLevel;
logger["b" /* logger */].warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
currentLevel = currentLevel - 1;
hls.trigger(events["a" /* default */].FPS_DROP_LEVEL_CAPPING, { level: currentLevel, droppedLevel: hls.currentLevel });
hls.autoLevelCapping = currentLevel;
hls.streamController.nextLevelSwitch();
}
}
}
}
this.lastTime = currentTime;
this.lastDroppedFrames = droppedFrames;
this.lastDecodedFrames = decodedFrames;
}
};
FPSController.prototype.checkFPSInterval = function checkFPSInterval() {
var video = this.video;
if (video) {
if (this.isVideoPlaybackQualityAvailable) {
var videoPlaybackQuality = video.getVideoPlaybackQuality();
this.checkFPS(video, videoPlaybackQuality.totalVideoFrames, videoPlaybackQuality.droppedVideoFrames);
} else {
this.checkFPS(video, video.webkitDecodedFrameCount, video.webkitDroppedFrameCount);
}
}
};
return FPSController;
}(event_handler);
/* harmony default export */ var fps_controller = (fps_controller_FPSController);
// CONCATENATED MODULE: ./src/utils/xhr-loader.js
function xhr_loader__classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* XHR based logger
*/
var xhr_loader__window = window,
xhr_loader_performance = xhr_loader__window.performance,
share/public_html/static/hls.js view on Meta::CPAN
* (we grab this on manifest-parsed and new level-loaded)
* @member {string}
*/
_this.audioGroupId = null;
return _this;
}
/**
* Reset audio tracks on new manifest loading.
*/
AudioTrackController.prototype.onManifestLoading = function onManifestLoading() {
this.tracks = [];
this.trackId = -1;
};
/**
* Store tracks data from manifest parsed data.
*
* Trigger AUDIO_TRACKS_UPDATED event.
*
* @param {*} data
*/
AudioTrackController.prototype.onManifestParsed = function onManifestParsed(data) {
var tracks = this.tracks = data.audioTracks || [];
this.hls.trigger(events["a" /* default */].AUDIO_TRACKS_UPDATED, { audioTracks: tracks });
};
/**
* Store track details of loaded track in our data-model.
*
* Set-up metadata update interval task for live-mode streams.
*
* @param {} data
*/
AudioTrackController.prototype.onAudioTrackLoaded = function onAudioTrackLoaded(data) {
if (data.id >= this.tracks.length) {
logger["b" /* logger */].warn('Invalid audio track id:', data.id);
return;
}
logger["b" /* logger */].log('audioTrack ' + data.id + ' loaded');
this.tracks[data.id].details = data.details;
// check if current playlist is a live playlist
// and if we have already our reload interval setup
if (data.details.live && !this.hasInterval()) {
// if live playlist we will have to reload it periodically
// set reload period to playlist target duration
var updatePeriodMs = data.details.targetduration * 1000;
this.setInterval(updatePeriodMs);
}
if (!data.details.live && this.hasInterval()) {
// playlist is not live and timer is scheduled: cancel it
this.clearInterval();
}
};
/**
* Update the internal group ID to any audio-track we may have set manually
* or because of a failure-handling fallback.
*
* Quality-levels should update to that group ID in this case.
*
* @param {*} data
*/
AudioTrackController.prototype.onAudioTrackSwitched = function onAudioTrackSwitched(data) {
var audioGroupId = this.tracks[data.id].groupId;
if (audioGroupId && this.audioGroupId !== audioGroupId) {
this.audioGroupId = audioGroupId;
}
};
/**
* When a level gets loaded, if it has redundant audioGroupIds (in the same ordinality as it's redundant URLs)
* we are setting our audio-group ID internally to the one set, if it is different from the group ID currently set.
*
* If group-ID got update, we re-select the appropriate audio-track with this group-ID matching the currently
* selected one (based on NAME property).
*
* @param {*} data
*/
AudioTrackController.prototype.onLevelLoaded = function onLevelLoaded(data) {
// FIXME: crashes because currentLevel is undefined
// const levelInfo = this.hls.levels[this.hls.currentLevel];
var levelInfo = this.hls.levels[data.level];
if (!levelInfo.audioGroupIds) {
return;
}
var audioGroupId = levelInfo.audioGroupIds[levelInfo.urlId];
if (this.audioGroupId !== audioGroupId) {
this.audioGroupId = audioGroupId;
this._selectInitialAudioTrack();
}
};
/**
* Handle network errors loading audio track manifests
* and also pausing on any netwok errors.
*
* @param {ErrorEventData} data
*/
AudioTrackController.prototype.onError = function onError(data) {
// Only handle network errors
if (data.type !== errors["b" /* ErrorTypes */].NETWORK_ERROR) {
share/public_html/static/hls.js view on Meta::CPAN
}
if (!trackFound) {
logger["b" /* logger */].error('No track found for running audio group-ID: ' + this.audioGroupId);
this.hls.trigger(events["a" /* default */].ERROR, {
type: errors["b" /* ErrorTypes */].MEDIA_ERROR,
details: errors["a" /* ErrorDetails */].AUDIO_TRACK_LOAD_ERROR,
fatal: true
});
}
};
/**
* @private
* @param {AudioTrack} audioTrack
* @returns {boolean}
*/
AudioTrackController.prototype._needsTrackLoading = function _needsTrackLoading(audioTrack) {
var details = audioTrack.details;
if (!details) {
return true;
} else if (details.live) {
return true;
}
};
/**
* @private
* @param {AudioTrack} audioTrack
*/
AudioTrackController.prototype._loadTrackDetailsIfNeeded = function _loadTrackDetailsIfNeeded(audioTrack) {
if (this._needsTrackLoading(audioTrack)) {
var url = audioTrack.url,
id = audioTrack.id;
// track not retrieved yet, or live playlist we need to (re)load it
logger["b" /* logger */].log('loading audio-track playlist for id: ' + id);
this.hls.trigger(events["a" /* default */].AUDIO_TRACK_LOADING, { url: url, id: id });
}
};
/**
* @private
* @param {number} newId
*/
AudioTrackController.prototype._updateTrack = function _updateTrack(newId) {
// check if level idx is valid
if (newId < 0 || newId >= this.tracks.length) {
return;
}
// stopping live reloading timer if any
this.clearInterval();
this.trackId = newId;
logger["b" /* logger */].log('trying to update audio-track ' + newId);
var audioTrack = this.tracks[newId];
this._loadTrackDetailsIfNeeded(audioTrack);
};
/**
* @private
*/
AudioTrackController.prototype._handleLoadError = function _handleLoadError() {
// First, let's black list current track id
this.trackIdBlacklist[this.trackId] = true;
// Let's try to fall back on a functional audio-track with the same group ID
var previousId = this.trackId;
var _tracks$previousId = this.tracks[previousId],
name = _tracks$previousId.name,
language = _tracks$previousId.language,
groupId = _tracks$previousId.groupId;
logger["b" /* logger */].warn('Loading failed on audio track id: ' + previousId + ', group-id: ' + groupId + ', name/language: "' + name + '" / "' + language + '"');
// Find a non-blacklisted track ID with the same NAME
// At least a track that is not blacklisted, thus on another group-ID.
var newId = previousId;
for (var i = 0; i < this.tracks.length; i++) {
if (this.trackIdBlacklist[i]) {
continue;
}
var newTrack = this.tracks[i];
if (newTrack.name === name) {
newId = i;
break;
}
}
if (newId === previousId) {
logger["b" /* logger */].warn('No fallback audio-track found for name/language: "' + name + '" / "' + language + '"');
return;
}
logger["b" /* logger */].log('Attempting audio-track fallback id:', newId, 'group-id:', this.tracks[newId].groupId);
this.audioTrack = newId;
};
audio_track_controller__createClass(AudioTrackController, [{
key: 'audioTracks',
get: function get() {
return this.tracks;
}
/**
* @type {number} Index into audio-tracks list of currently selected track.
*/
}, {
key: 'audioTrack',
get: function get() {
return this.trackId;
}
/**
* Select current track by index
*/
,
set: function set(newId) {
// noop on same audio track id as already set
if (this.trackId === newId && this.tracks[this.trackId].details) {
logger["b" /* logger */].debug('Same id as current audio-track passed, and track details available -> no-op');
return;
}
// check if level idx is valid
if (newId < 0 || newId >= this.tracks.length) {
logger["b" /* logger */].warn('Invalid id passed to audio-track controller');
return;
}
var audioTrack = this.tracks[newId];
logger["b" /* logger */].log('Now switching to audio-track index ' + newId);
// stopping live reloading timer if any
this.clearInterval();
this.trackId = newId;
var url = audioTrack.url,
type = audioTrack.type,
id = audioTrack.id;
this.hls.trigger(events["a" /* default */].AUDIO_TRACK_SWITCHING, { id: id, type: type, url: url });
this._loadTrackDetailsIfNeeded(audioTrack);
}
}]);
return AudioTrackController;
}(task_loop);
/* harmony default export */ var audio_track_controller = (audio_track_controller_AudioTrackController);
// CONCATENATED MODULE: ./src/controller/audio-stream-controller.js
var audio_stream_controller__createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = ...
function audio_stream_controller__classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function audio_stream_controller__possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? ...
function audio_stream_controller__inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototyp...
/*
* Audio Stream Controller
*/
var audio_stream_controller__window = window,
audio_stream_controller_performance = audio_stream_controller__window.performance;
var audio_stream_controller_State = {
STOPPED: 'STOPPED',
STARTING: 'STARTING',
IDLE: 'IDLE',
PAUSED: 'PAUSED',
KEY_LOADING: 'KEY_LOADING',
FRAG_LOADING: 'FRAG_LOADING',
FRAG_LOADING_WAITING_RETRY: 'FRAG_LOADING_WAITING_RETRY',
WAITING_TRACK: 'WAITING_TRACK',
PARSING: 'PARSING',
PARSED: 'PARSED',
BUFFER_FLUSHING: 'BUFFER_FLUSHING',
ENDED: 'ENDED',
ERROR: 'ERROR',
share/public_html/static/hls.js view on Meta::CPAN
this.startLoad(config.startPosition);
}
};
AudioStreamController.prototype.onMediaDetaching = function onMediaDetaching() {
var media = this.media;
if (media && media.ended) {
logger["b" /* logger */].log('MSE detaching and video ended, reset startPosition');
this.startPosition = this.lastCurrentTime = 0;
}
// remove video listeners
if (media) {
media.removeEventListener('seeking', this.onvseeking);
media.removeEventListener('ended', this.onvended);
this.onvseeking = this.onvseeked = this.onvended = null;
}
this.media = this.mediaBuffer = this.videoBuffer = null;
this.loadedmetadata = false;
this.stopLoad();
};
AudioStreamController.prototype.onMediaSeeking = function onMediaSeeking() {
if (this.state === audio_stream_controller_State.ENDED) {
// switch to IDLE state to check for potential new fragment
this.state = audio_stream_controller_State.IDLE;
}
if (this.media) {
this.lastCurrentTime = this.media.currentTime;
}
// tick to speed up processing
this.tick();
};
AudioStreamController.prototype.onMediaEnded = function onMediaEnded() {
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
this.startPosition = this.lastCurrentTime = 0;
};
AudioStreamController.prototype.onAudioTracksUpdated = function onAudioTracksUpdated(data) {
logger["b" /* logger */].log('audio tracks updated');
this.tracks = data.audioTracks;
};
AudioStreamController.prototype.onAudioTrackSwitching = function onAudioTrackSwitching(data) {
// if any URL found on new audio track, it is an alternate audio track
var altAudio = !!data.url;
this.trackId = data.id;
this.fragCurrent = null;
this.state = audio_stream_controller_State.PAUSED;
this.waitingFragment = null;
// destroy useless demuxer when switching audio to main
if (!altAudio) {
if (this.demuxer) {
this.demuxer.destroy();
this.demuxer = null;
}
} else {
// switching to audio track, start timer if not already started
this.setInterval(100);
}
// should we switch tracks ?
if (altAudio) {
this.audioSwitch = true;
// main audio track are handled by stream-controller, just do something if switching to alt audio track
this.state = audio_stream_controller_State.IDLE;
}
this.tick();
};
AudioStreamController.prototype.onAudioTrackLoaded = function onAudioTrackLoaded(data) {
var newDetails = data.details,
trackId = data.id,
track = this.tracks[trackId],
duration = newDetails.totalduration,
sliding = 0;
logger["b" /* logger */].log('track ' + trackId + ' loaded [' + newDetails.startSN + ',' + newDetails.endSN + '],duration:' + duration);
if (newDetails.live) {
var curDetails = track.details;
if (curDetails && newDetails.fragments.length > 0) {
// we already have details for that level, merge them
mergeDetails(curDetails, newDetails);
sliding = newDetails.fragments[0].start;
// TODO
// this.liveSyncPosition = this.computeLivePosition(sliding, curDetails);
if (newDetails.PTSKnown) {
logger["b" /* logger */].log('live audio playlist sliding:' + sliding.toFixed(3));
} else {
logger["b" /* logger */].log('live audio playlist - outdated PTS, unknown sliding');
}
} else {
newDetails.PTSKnown = false;
logger["b" /* logger */].log('live audio playlist - first load, unknown sliding');
}
} else {
newDetails.PTSKnown = false;
}
track.details = newDetails;
// compute start position
if (!this.startFragRequested) {
// compute start position if set to -1. use it straight away if value is defined
if (this.startPosition === -1) {
// first, check if start time offset has been set in playlist, if yes, use this value
var startTimeOffset = newDetails.startTimeOffset;
if (!isNaN(startTimeOffset)) {
logger["b" /* logger */].log('start time offset found in playlist, adjust startPosition to ' + startTimeOffset);
this.startPosition = startTimeOffset;
} else {
this.startPosition = 0;
}
}
this.nextLoadPosition = this.startPosition;
}
// only switch batck to IDLE state if we were waiting for track to start downloading a new fragment
if (this.state === audio_stream_controller_State.WAITING_TRACK) {
share/public_html/static/hls.js view on Meta::CPAN
SubtitleTrackController.prototype.onManifestLoading = function onManifestLoading() {
this.tracks = [];
this.trackId = -1;
};
// Fired whenever a new manifest is loaded.
SubtitleTrackController.prototype.onManifestLoaded = function onManifestLoaded(data) {
var _this3 = this;
var tracks = data.subtitles || [];
this.tracks = tracks;
this.trackId = -1;
this.hls.trigger(events["a" /* default */].SUBTITLE_TRACKS_UPDATED, { subtitleTracks: tracks });
// loop through available subtitle tracks and autoselect default if needed
// TODO: improve selection logic to handle forced, etc
tracks.forEach(function (track) {
if (track.default) {
// setting this.subtitleTrack will trigger internal logic
// if media has not been attached yet, it will fail
// we keep a reference to the default track id
// and we'll set subtitleTrack when onMediaAttached is triggered
if (_this3.media) {
_this3.subtitleTrack = track.id;
} else {
_this3.queuedDefaultTrack = track.id;
}
}
});
};
// Trigger subtitle track playlist reload.
SubtitleTrackController.prototype.onTick = function onTick() {
var trackId = this.trackId;
var subtitleTrack = this.tracks[trackId];
if (!subtitleTrack) {
return;
}
var details = subtitleTrack.details;
// check if we need to load playlist for this subtitle Track
if (!details || details.live) {
// track not retrieved yet, or live playlist we need to (re)load it
logger["b" /* logger */].log('(re)loading playlist for subtitle track ' + trackId);
this.hls.trigger(events["a" /* default */].SUBTITLE_TRACK_LOADING, { url: subtitleTrack.url, id: trackId });
}
};
SubtitleTrackController.prototype.onSubtitleTrackLoaded = function onSubtitleTrackLoaded(data) {
var _this4 = this;
if (data.id < this.tracks.length) {
logger["b" /* logger */].log('subtitle track ' + data.id + ' loaded');
this.tracks[data.id].details = data.details;
// check if current playlist is a live playlist
if (data.details.live && !this.timer) {
// if live playlist we will have to reload it periodically
// set reload period to playlist target duration
this.timer = setInterval(function () {
_this4.onTick();
}, 1000 * data.details.targetduration, this);
}
if (!data.details.live && this.timer) {
// playlist is not live and timer is armed : stopping it
this._stopTimer();
}
}
};
/** get alternate subtitle tracks list from playlist **/
/**
* This method is responsible for validating the subtitle index and periodically reloading if live.
* Dispatches the SUBTITLE_TRACK_SWITCH event, which instructs the subtitle-stream-controller to load the selected track.
* @param newId - The id of the subtitle track to activate.
*/
SubtitleTrackController.prototype.setSubtitleTrackInternal = function setSubtitleTrackInternal(newId) {
var hls = this.hls,
tracks = this.tracks;
if (typeof newId !== 'number' || newId < -1 || newId >= tracks.length) {
return;
}
this._stopTimer();
this.trackId = newId;
logger["b" /* logger */].log('switching to subtitle track ' + newId);
hls.trigger(events["a" /* default */].SUBTITLE_TRACK_SWITCH, { id: newId });
if (newId === -1) {
return;
}
// check if we need to load playlist for this subtitle Track
var subtitleTrack = tracks[newId];
var details = subtitleTrack.details;
if (!details || details.live) {
// track not retrieved yet, or live playlist we need to (re)load it
logger["b" /* logger */].log('(re)loading playlist for subtitle track ' + newId);
hls.trigger(events["a" /* default */].SUBTITLE_TRACK_LOADING, { url: subtitleTrack.url, id: newId });
}
};
SubtitleTrackController.prototype._stopTimer = function _stopTimer() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
};
/**
* Disables the old subtitleTrack and sets current mode on the next subtitleTrack.
* This operates on the DOM textTracks.
* A value of -1 will disable all subtitle tracks.
* @param newId - The id of the next track to enable
* @private
*/
SubtitleTrackController.prototype._toggleTrackModes = function _toggleTrackModes(newId) {
var media = this.media,
subtitleDisplay = this.subtitleDisplay,
trackId = this.trackId;
if (!media) {
return;
}
var textTracks = filterSubtitleTracks(media.textTracks);
if (newId === -1) {
[].slice.call(textTracks).forEach(function (track) {
track.mode = 'disabled';
});
} else {
var oldTrack = textTracks[trackId];
if (oldTrack) {
oldTrack.mode = 'disabled';
}
}
var nextTrack = textTracks[newId];
if (nextTrack) {
nextTrack.mode = subtitleDisplay ? 'showing' : 'hidden';
}
};
subtitle_track_controller__createClass(SubtitleTrackController, [{
key: 'subtitleTracks',
get: function get() {
return this.tracks;
}
/** get index of the selected subtitle track (index in subtitle track lists) **/
}, {
key: 'subtitleTrack',
get: function get() {
return this.trackId;
}
/** select a subtitle track, based on its index in subtitle track lists**/
,
set: function set(subtitleTrackId) {
if (this.trackId !== subtitleTrackId) {
this._toggleTrackModes(subtitleTrackId);
this.setSubtitleTrackInternal(subtitleTrackId);
}
( run in 1.526 second using v1.01-cache-2.11-cpan-bbb979687b5 )