update components
This commit is contained in:
parent
1bd4890f44
commit
e453a5b9f7
18 changed files with 387 additions and 128 deletions
|
@ -9,18 +9,19 @@ import EventHandler from '../event-handler';
|
|||
import BufferHelper from '../helper/buffer-helper';
|
||||
import {ErrorDetails} from '../errors';
|
||||
import {logger} from '../utils/logger';
|
||||
import EwmaBandWidthEstimator from './ewma-bandwidth-estimator';
|
||||
|
||||
class AbrController extends EventHandler {
|
||||
|
||||
constructor(hls) {
|
||||
super(hls, Event.FRAG_LOADING,
|
||||
Event.FRAG_LOAD_PROGRESS,
|
||||
Event.FRAG_LOADED,
|
||||
Event.ERROR);
|
||||
this.lastLoadedFragLevel = 0;
|
||||
this._autoLevelCapping = -1;
|
||||
this._nextAutoLevel = -1;
|
||||
this.hls = hls;
|
||||
this.bwEstimator = new EwmaBandWidthEstimator(hls);
|
||||
this.onCheck = this.abandonRulesCheck.bind(this);
|
||||
}
|
||||
|
||||
|
@ -36,18 +37,6 @@ class AbrController extends EventHandler {
|
|||
this.fragCurrent = data.frag;
|
||||
}
|
||||
|
||||
onFragLoadProgress(data) {
|
||||
var stats = data.stats;
|
||||
// only update stats if first frag loading
|
||||
// if same frag is loaded multiple times, it might be in browser cache, and loaded quickly
|
||||
// and leading to wrong bw estimation
|
||||
if (stats.aborted === undefined && data.frag.loadCounter === 1) {
|
||||
this.lastfetchduration = (performance.now() - stats.trequest) / 1000;
|
||||
this.lastbw = (stats.loaded * 8) / this.lastfetchduration;
|
||||
//console.log(`fetchDuration:${this.lastfetchduration},bw:${(this.lastbw/1000).toFixed(0)}/${stats.aborted}`);
|
||||
}
|
||||
}
|
||||
|
||||
abandonRulesCheck() {
|
||||
/*
|
||||
monitor fragment retrieval time...
|
||||
|
@ -100,6 +89,8 @@ class AbrController extends EventHandler {
|
|||
nextLoadLevel = Math.max(0,nextLoadLevel);
|
||||
// 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,frag.loaded);
|
||||
// abort fragment loading ...
|
||||
logger.warn(`loading too slow, abort fragment loading and switch to level ${nextLoadLevel}`);
|
||||
//abort fragment loading
|
||||
|
@ -113,6 +104,14 @@ class AbrController extends EventHandler {
|
|||
}
|
||||
|
||||
onFragLoaded(data) {
|
||||
var stats = data.stats;
|
||||
// only update stats on first frag loading
|
||||
// if same frag is loaded multiple times, it might be in browser cache, and loaded quickly
|
||||
// and leading to wrong bw estimation
|
||||
if (stats.aborted === undefined && data.frag.loadCounter === 1) {
|
||||
this.bwEstimator.sample(performance.now() - stats.trequest,stats.loaded);
|
||||
}
|
||||
|
||||
// stop monitoring bw once frag loaded
|
||||
this.clearTimer();
|
||||
// store level id after successful fragment load
|
||||
|
@ -151,9 +150,9 @@ class AbrController extends EventHandler {
|
|||
}
|
||||
|
||||
get nextAutoLevel() {
|
||||
var lastbw = this.lastbw, hls = this.hls,adjustedbw, i, maxAutoLevel;
|
||||
if (this._autoLevelCapping === -1 && hls.levels && hls.levels.length) {
|
||||
maxAutoLevel = hls.levels.length - 1;
|
||||
var hls = this.hls, i, maxAutoLevel, levels = hls.levels, config = hls.config;
|
||||
if (this._autoLevelCapping === -1 && levels && levels.length) {
|
||||
maxAutoLevel = levels.length - 1;
|
||||
} else {
|
||||
maxAutoLevel = this._autoLevelCapping;
|
||||
}
|
||||
|
@ -163,6 +162,7 @@ class AbrController extends EventHandler {
|
|||
return Math.min(this._nextAutoLevel,maxAutoLevel);
|
||||
}
|
||||
|
||||
let avgbw = this.bwEstimator.getEstimate(),adjustedbw;
|
||||
// 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.
|
||||
|
@ -171,11 +171,11 @@ class AbrController extends EventHandler {
|
|||
// be even more conservative (70%) to avoid overestimating and immediately
|
||||
// switching back.
|
||||
if (i <= this.lastLoadedFragLevel) {
|
||||
adjustedbw = 0.8 * lastbw;
|
||||
adjustedbw = config.abrBandWidthFactor * avgbw;
|
||||
} else {
|
||||
adjustedbw = 0.7 * lastbw;
|
||||
adjustedbw = config.abrBandWidthUpFactor * avgbw;
|
||||
}
|
||||
if (adjustedbw < hls.levels[i].bitrate) {
|
||||
if (adjustedbw < levels[i].bitrate) {
|
||||
return Math.max(0, i - 1);
|
||||
}
|
||||
}
|
||||
|
|
49
dashboard-ui/bower_components/hls.js/src/controller/ewma-bandwidth-estimator.js
vendored
Normal file
49
dashboard-ui/bower_components/hls.js/src/controller/ewma-bandwidth-estimator.js
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* EWMA Bandwidth Estimator
|
||||
* - heavily inspired from shaka-player
|
||||
* Tracks bandwidth samples and estimates available bandwidth.
|
||||
* Based on the minimum of two exponentially-weighted moving averages with
|
||||
* different half-lives.
|
||||
*/
|
||||
|
||||
import EWMA from '../utils/ewma';
|
||||
|
||||
|
||||
class EwmaBandWidthEstimator {
|
||||
|
||||
constructor(hls) {
|
||||
this.hls = hls;
|
||||
this.defaultEstimate_ = 5e5; // 500kbps
|
||||
this.minWeight_ = 0.001;
|
||||
this.minDelayMs_ = 50;
|
||||
this.fast_ = new EWMA(hls.config.abrEwmaFast);
|
||||
this.slow_ = new EWMA(hls.config.abrEwmaSlow);
|
||||
}
|
||||
|
||||
sample(durationMs,numBytes) {
|
||||
durationMs = Math.max(durationMs, this.minDelayMs_);
|
||||
var bandwidth = 8000* numBytes / durationMs;
|
||||
//console.log('instant bw:'+ Math.round(bandwidth));
|
||||
// we weight sample using loading duration....
|
||||
var weigth = durationMs / 1000;
|
||||
this.fast_.sample(weigth,bandwidth);
|
||||
this.slow_.sample(weigth,bandwidth);
|
||||
}
|
||||
|
||||
|
||||
getEstimate() {
|
||||
if (this.fast_.getTotalWeight() < this.minWeight_) {
|
||||
return this.defaultEstimate_;
|
||||
}
|
||||
//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());
|
||||
}
|
||||
|
||||
destroy() {
|
||||
}
|
||||
}
|
||||
export default EwmaBandWidthEstimator;
|
||||
|
|
@ -43,6 +43,8 @@
|
|||
this._aacTrack = {container : 'video/mp2t', type: 'audio', id :-1, sequenceNumber: 0, samples : [], len : 0};
|
||||
this._id3Track = {type: 'id3', id :-1, sequenceNumber: 0, samples : [], len : 0};
|
||||
this._txtTrack = {type: 'text', id: -1, sequenceNumber: 0, samples: [], len: 0};
|
||||
// flush any partial content
|
||||
this.aacOverFlow = null;
|
||||
this.remuxer.switchLevel();
|
||||
}
|
||||
|
||||
|
@ -67,7 +69,8 @@
|
|||
logger.log('discontinuity detected');
|
||||
this.insertDiscontinuity();
|
||||
this.lastCC = cc;
|
||||
} else if (level !== this.lastLevel) {
|
||||
}
|
||||
if (level !== this.lastLevel) {
|
||||
logger.log('level switch detected');
|
||||
this.switchLevel();
|
||||
this.lastLevel = level;
|
||||
|
@ -76,11 +79,6 @@
|
|||
}
|
||||
this.lastSN = sn;
|
||||
|
||||
if(!this.contiguous) {
|
||||
// flush any partial content
|
||||
this.aacOverFlow = null;
|
||||
}
|
||||
|
||||
var pmtParsed = this.pmtParsed,
|
||||
avcId = this._avcTrack.id,
|
||||
aacId = this._aacTrack.id,
|
||||
|
|
|
@ -80,7 +80,11 @@ class Hls {
|
|||
streamController: StreamController,
|
||||
timelineController: TimelineController,
|
||||
enableCEA708Captions: true,
|
||||
enableMP2TPassThrough : false
|
||||
enableMP2TPassThrough : false,
|
||||
abrEwmaFast: 0,
|
||||
abrEwmaSlow: 0,
|
||||
abrBandWidthFactor : 0.8,
|
||||
abrBandWidthUpFactor : 0.7
|
||||
};
|
||||
}
|
||||
return Hls.defaultConfig;
|
||||
|
|
|
@ -137,7 +137,7 @@ class PlaylistLoader extends EventHandler {
|
|||
byteRangeEndOffset,
|
||||
byteRangeStartOffset;
|
||||
|
||||
regexp = /(?:#EXT-X-(MEDIA-SEQUENCE):(\d+))|(?:#EXT-X-(TARGETDURATION):(\d+))|(?:#EXT-X-(KEY):(.*))|(?:#EXT(INF):([\d\.]+)[^\r\n]*([\r\n]+[^#|\r\n]+)?)|(?:#EXT-X-(BYTERANGE):([\d]+[@[\d]*)]*[\r\n]+([^#|\r\n]+)?|(?:#EXT-X-(ENDLIST))|(?:#EXT-X-(DIS)CONTINUITY))|(?:#EXT-X-(PROGRAM-DATE-TIME):(.*))/g;
|
||||
regexp = /(?:#EXT-X-(MEDIA-SEQUENCE):(\d+))|(?:#EXT-X-(TARGETDURATION):(\d+))|(?:#EXT-X-(KEY):(.*))|(?:#EXT(INF):([\d\.]+)[^\r\n]*([\r\n]+[^#|\r\n]+)?)|(?:#EXT-X-(BYTERANGE):([\d]+[@[\d]*)]*[\r\n]+([^#|\r\n]+)?|(?:#EXT-X-(ENDLIST))|(?:#EXT-X-(DIS)CONTINUITY))|(?:#EXT-X-(PROGRAM-DATE-TIME):(.*)[\r\n]+([^#|\r\n]+)?)/g;
|
||||
while ((result = regexp.exec(string)) !== null) {
|
||||
result.shift();
|
||||
result = result.filter(function(n) { return (n !== undefined); });
|
||||
|
@ -212,6 +212,10 @@ class PlaylistLoader extends EventHandler {
|
|||
break;
|
||||
case 'PROGRAM-DATE-TIME':
|
||||
programDateTime = new Date(Date.parse(result[1]));
|
||||
if (frag && !frag.url && result.length >= 3) {
|
||||
frag.url = this.resolve(result[2], baseurl);
|
||||
frag.programDateTime = programDateTime;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
|
@ -25,7 +25,7 @@ class MP4Remuxer {
|
|||
}
|
||||
|
||||
insertDiscontinuity() {
|
||||
this._initPTS = this._initDTS = this.nextAacPts = this.nextAvcDts = undefined;
|
||||
this._initPTS = this._initDTS = undefined;
|
||||
}
|
||||
|
||||
switchLevel() {
|
||||
|
@ -303,7 +303,7 @@ class MP4Remuxer {
|
|||
}
|
||||
// always adjust sample duration to avoid av sync issue
|
||||
mp4Sample.duration = expectedSampleDuration;
|
||||
dtsnorm = expectedSampleDuration * pes2mp4ScaleFactor + lastDTS;
|
||||
ptsnorm = dtsnorm = expectedSampleDuration * pes2mp4ScaleFactor + lastDTS;
|
||||
} else {
|
||||
let nextAacPts, delta;
|
||||
if (contiguous) {
|
||||
|
|
37
dashboard-ui/bower_components/hls.js/src/utils/ewma.js
vendored
Normal file
37
dashboard-ui/bower_components/hls.js/src/utils/ewma.js
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* compute an Exponential Weighted moving average
|
||||
* - https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
|
||||
* - heavily inspired from shaka-player
|
||||
*/
|
||||
|
||||
class EWMA {
|
||||
|
||||
// About half of the estimated value will be from the last |halfLife| samples by weight.
|
||||
constructor(halfLife) {
|
||||
// Larger values of alpha expire historical data more slowly.
|
||||
this.alpha_ = halfLife ? Math.exp(Math.log(0.5) / halfLife) : 0;
|
||||
this.estimate_ = 0;
|
||||
this.totalWeight_ = 0;
|
||||
}
|
||||
|
||||
sample(weight,value) {
|
||||
var adjAlpha = Math.pow(this.alpha_, weight);
|
||||
this.estimate_ = value * (1 - adjAlpha) + adjAlpha * this.estimate_;
|
||||
this.totalWeight_ += weight;
|
||||
}
|
||||
|
||||
getTotalWeight() {
|
||||
return this.totalWeight_;
|
||||
}
|
||||
|
||||
getEstimate() {
|
||||
if (this.alpha_) {
|
||||
var zeroFactor = 1 - Math.pow(this.alpha_, this.totalWeight_);
|
||||
return this.estimate_ / zeroFactor;
|
||||
} else {
|
||||
return this.estimate_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default EWMA;
|
Loading…
Add table
Add a link
Reference in a new issue