mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
update components
This commit is contained in:
parent
49ef08c64b
commit
07de1c52f9
11 changed files with 213 additions and 95 deletions
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "hls.js",
|
"name": "hls.js",
|
||||||
"version": "0.5.11",
|
"version": "0.5.13",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Media Source Extension - HLS library, by/for Dailymotion",
|
"description": "Media Source Extension - HLS library, by/for Dailymotion",
|
||||||
"homepage": "https://github.com/dailymotion/hls.js",
|
"homepage": "https://github.com/dailymotion/hls.js",
|
||||||
|
@ -16,11 +16,11 @@
|
||||||
"test",
|
"test",
|
||||||
"tests"
|
"tests"
|
||||||
],
|
],
|
||||||
"_release": "0.5.11",
|
"_release": "0.5.13",
|
||||||
"_resolution": {
|
"_resolution": {
|
||||||
"type": "version",
|
"type": "version",
|
||||||
"tag": "v0.5.11",
|
"tag": "v0.5.13",
|
||||||
"commit": "8896e0062b3f8384379c7b990424e3fab9c1c41b"
|
"commit": "ef9fae497dd6d6e80a6d471c55eef2310e883853"
|
||||||
},
|
},
|
||||||
"_source": "git://github.com/dailymotion/hls.js.git",
|
"_source": "git://github.com/dailymotion/hls.js.git",
|
||||||
"_target": "~0.5.7",
|
"_target": "~0.5.7",
|
||||||
|
|
26
dashboard-ui/bower_components/hls.js/API.md
vendored
26
dashboard-ui/bower_components/hls.js/API.md
vendored
|
@ -190,6 +190,7 @@ configuration parameters could be provided to hls.js upon instantiation of Hls O
|
||||||
maxBufferSize : 60*1000*1000,
|
maxBufferSize : 60*1000*1000,
|
||||||
maxBufferHole : 0.3,
|
maxBufferHole : 0.3,
|
||||||
maxSeekHole : 2,
|
maxSeekHole : 2,
|
||||||
|
maxFragLookUpTolerance : 0.2,
|
||||||
liveSyncDurationCount : 3,
|
liveSyncDurationCount : 3,
|
||||||
liveMaxLatencyDurationCount: 10,
|
liveMaxLatencyDurationCount: 10,
|
||||||
enableWorker : true,
|
enableWorker : true,
|
||||||
|
@ -268,6 +269,25 @@ in case playback is stalled, and a buffered range is available upfront, less tha
|
||||||
hls.js will jump over this buffer hole to reach the beginning of this following buffered range.
|
hls.js will jump over this buffer hole to reach the beginning of this following buffered range.
|
||||||
```maxSeekHole``` allows to configure this jumpable threshold.
|
```maxSeekHole``` allows to configure this jumpable threshold.
|
||||||
|
|
||||||
|
#### ```maxFragLookUpTolerance```
|
||||||
|
(default 0.2s)
|
||||||
|
|
||||||
|
this tolerance factor is used during fragment lookup.
|
||||||
|
instead of checking whether buffered.end is located within [start, end] range, frag lookup will be done by checking within [start-maxFragLookUpTolerance, end-maxFragLookUpTolerance] range
|
||||||
|
|
||||||
|
this tolerance factor is used to cope with situations like
|
||||||
|
buffered.end = 9.991
|
||||||
|
frag[Ø] : [0,10]
|
||||||
|
frag[1] : [10,20]
|
||||||
|
=> buffered.end is within frag[0] range, but as we are close to frag[1], frag[1] should be choosen instead
|
||||||
|
|
||||||
|
|
||||||
|
if maxFragLookUpTolerance=0.2,
|
||||||
|
this lookup will be adjusted to
|
||||||
|
frag[Ø] : [-0.2,9.8]
|
||||||
|
frag[1] : [9.8,19.8]
|
||||||
|
=> this time, buffered.end is within frag[1] range, and frag[1] will be the next fragment to be loaded, as expected.
|
||||||
|
|
||||||
#### ```maxMaxBufferLength```
|
#### ```maxMaxBufferLength```
|
||||||
(default 600s)
|
(default 600s)
|
||||||
|
|
||||||
|
@ -495,6 +515,12 @@ get : return last loaded fragment quality level.
|
||||||
set : set quality level for next loaded fragment
|
set : set quality level for next loaded fragment
|
||||||
set to -1 for automatic level selection
|
set to -1 for automatic level selection
|
||||||
|
|
||||||
|
#### ```hls.nextLoadLevel```
|
||||||
|
get : return quality level that will be used to load next fragment
|
||||||
|
|
||||||
|
set : force quality level for next loaded fragment. quality level will be forced only for that fragment.
|
||||||
|
after a fragment at this quality level has been loaded, ```hls.loadLevel``` will prevail.
|
||||||
|
|
||||||
#### ```hls.firstLevel```
|
#### ```hls.firstLevel```
|
||||||
|
|
||||||
get : first level index (index of first level appearing in Manifest. it is usually defined as start level hint for player)
|
get : first level index (index of first level appearing in Manifest. it is usually defined as start level hint for player)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "hls.js",
|
"name": "hls.js",
|
||||||
"version": "0.5.11",
|
"version": "0.5.13",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Media Source Extension - HLS library, by/for Dailymotion",
|
"description": "Media Source Extension - HLS library, by/for Dailymotion",
|
||||||
"homepage": "https://github.com/dailymotion/hls.js",
|
"homepage": "https://github.com/dailymotion/hls.js",
|
||||||
|
|
102
dashboard-ui/bower_components/hls.js/dist/hls.js
vendored
102
dashboard-ui/bower_components/hls.js/dist/hls.js
vendored
|
@ -1125,15 +1125,6 @@ var LevelController = function (_EventHandler) {
|
||||||
this.hls.trigger(_events2.default.LEVEL_LOADING, { url: level.url[urlId], level: levelId, id: urlId });
|
this.hls.trigger(_events2.default.LEVEL_LOADING, { url: level.url[urlId], level: levelId, id: urlId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, {
|
|
||||||
key: 'nextLoadLevel',
|
|
||||||
value: function nextLoadLevel() {
|
|
||||||
if (this._manualLevel !== -1) {
|
|
||||||
return this._manualLevel;
|
|
||||||
} else {
|
|
||||||
return this.hls.abrController.nextAutoLevel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
}, {
|
||||||
key: 'levels',
|
key: 'levels',
|
||||||
get: function get() {
|
get: function get() {
|
||||||
|
@ -1180,6 +1171,21 @@ var LevelController = function (_EventHandler) {
|
||||||
set: function set(newLevel) {
|
set: function set(newLevel) {
|
||||||
this._startLevel = newLevel;
|
this._startLevel = newLevel;
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
key: 'nextLoadLevel',
|
||||||
|
get: function get() {
|
||||||
|
if (this._manualLevel !== -1) {
|
||||||
|
return this._manualLevel;
|
||||||
|
} else {
|
||||||
|
return this.hls.abrController.nextAutoLevel;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: function set(nextLevel) {
|
||||||
|
this.level = nextLevel;
|
||||||
|
if (this._manualLevel === -1) {
|
||||||
|
this.hls.abrController.nextAutoLevel = nextLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
return LevelController;
|
return LevelController;
|
||||||
|
@ -1333,6 +1339,8 @@ var StreamController = function (_EventHandler) {
|
||||||
}, {
|
}, {
|
||||||
key: 'doTick',
|
key: 'doTick',
|
||||||
value: function doTick() {
|
value: function doTick() {
|
||||||
|
var _this2 = this;
|
||||||
|
|
||||||
var pos,
|
var pos,
|
||||||
level,
|
level,
|
||||||
levelDetails,
|
levelDetails,
|
||||||
|
@ -1453,14 +1461,30 @@ var StreamController = function (_EventHandler) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!_frag) {
|
if (!_frag) {
|
||||||
var foundFrag;
|
(function () {
|
||||||
|
var foundFrag = undefined;
|
||||||
|
var maxFragLookUpTolerance = config.maxFragLookUpTolerance;
|
||||||
if (bufferEnd < end) {
|
if (bufferEnd < end) {
|
||||||
|
if (bufferEnd > end - maxFragLookUpTolerance) {
|
||||||
|
maxFragLookUpTolerance = 0;
|
||||||
|
}
|
||||||
foundFrag = _binarySearch2.default.search(fragments, function (candidate) {
|
foundFrag = _binarySearch2.default.search(fragments, function (candidate) {
|
||||||
|
// offset should be within fragment boundary - config.maxFragLookUpTolerance
|
||||||
|
// this is to cope with situations like
|
||||||
|
// bufferEnd = 9.991
|
||||||
|
// frag[Ø] : [0,10]
|
||||||
|
// frag[1] : [10,20]
|
||||||
|
// bufferEnd is within frag[0] range ... although what we are expecting is to return frag[1] here
|
||||||
|
// frag start frag start+duration
|
||||||
|
// |-----------------------------|
|
||||||
|
// <---> <--->
|
||||||
|
// ...--------><-----------------------------><---------....
|
||||||
|
// previous frag matching fragment next frag
|
||||||
|
// return -1 return 0 return 1
|
||||||
//logger.log(`level/sn/start/end/bufEnd:${level}/${candidate.sn}/${candidate.start}/${(candidate.start+candidate.duration)}/${bufferEnd}`);
|
//logger.log(`level/sn/start/end/bufEnd:${level}/${candidate.sn}/${candidate.start}/${(candidate.start+candidate.duration)}/${bufferEnd}`);
|
||||||
// offset should be within fragment boundary
|
if (candidate.start + candidate.duration - maxFragLookUpTolerance <= bufferEnd) {
|
||||||
if (candidate.start + candidate.duration <= bufferEnd) {
|
|
||||||
return 1;
|
return 1;
|
||||||
} else if (candidate.start > bufferEnd) {
|
} else if (candidate.start - maxFragLookUpTolerance > bufferEnd) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1480,13 +1504,14 @@ var StreamController = function (_EventHandler) {
|
||||||
} else {
|
} else {
|
||||||
// have we reached end of VOD playlist ?
|
// have we reached end of VOD playlist ?
|
||||||
if (!levelDetails.live) {
|
if (!levelDetails.live) {
|
||||||
this.hls.trigger(_events2.default.BUFFER_EOS);
|
_this2.hls.trigger(_events2.default.BUFFER_EOS);
|
||||||
this.state = State.ENDED;
|
_this2.state = State.ENDED;
|
||||||
}
|
}
|
||||||
_frag = null;
|
_frag = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
if (_frag) {
|
if (_frag) {
|
||||||
//logger.log(' loading frag ' + i +',pos/bufEnd:' + pos.toFixed(3) + '/' + bufferEnd.toFixed(3));
|
//logger.log(' loading frag ' + i +',pos/bufEnd:' + pos.toFixed(3) + '/' + bufferEnd.toFixed(3));
|
||||||
|
@ -1548,28 +1573,50 @@ var StreamController = function (_EventHandler) {
|
||||||
var requestDelay = performance.now() - frag.trequest;
|
var requestDelay = performance.now() - frag.trequest;
|
||||||
// monitor fragment load progress after half of expected fragment duration,to stabilize bitrate
|
// monitor fragment load progress after half of expected fragment duration,to stabilize bitrate
|
||||||
if (requestDelay > 500 * frag.duration) {
|
if (requestDelay > 500 * frag.duration) {
|
||||||
var loadRate = frag.loaded * 1000 / requestDelay; // byte/s
|
var loadRate = Math.max(1, frag.loaded * 1000 / requestDelay); // byte/s; at least 1 byte/s to avoid division by zero
|
||||||
if (frag.expectedLen < frag.loaded) {
|
if (frag.expectedLen < frag.loaded) {
|
||||||
frag.expectedLen = frag.loaded;
|
frag.expectedLen = frag.loaded;
|
||||||
}
|
}
|
||||||
pos = v.currentTime;
|
pos = v.currentTime;
|
||||||
var fragLoadedDelay = (frag.expectedLen - frag.loaded) / loadRate;
|
var fragLoadedDelay = (frag.expectedLen - frag.loaded) / loadRate;
|
||||||
var bufferStarvationDelay = this.bufferInfo(pos, config.maxBufferHole).end - pos;
|
var bufferStarvationDelay = this.bufferInfo(pos, config.maxBufferHole).end - pos;
|
||||||
var fragLevelNextLoadedDelay = frag.duration * this.levels[hls.nextLoadLevel].bitrate / (8 * loadRate); //bps/Bps
|
// consider emergency switch down only if we have less than 2 frag buffered AND
|
||||||
/* if we have less than 2 frag duration in buffer and if frag loaded delay is greater than buffer starvation delay
|
// time to finish loading current fragment is bigger than buffer starvation delay
|
||||||
... and also bigger than duration needed to load fragment at next level ...*/
|
// ie if we risk buffer starvation if bw does not increase quickly
|
||||||
if (bufferStarvationDelay < 2 * frag.duration && fragLoadedDelay > bufferStarvationDelay && fragLoadedDelay > fragLevelNextLoadedDelay) {
|
if (bufferStarvationDelay < 2 * frag.duration && fragLoadedDelay > bufferStarvationDelay) {
|
||||||
|
var fragLevelNextLoadedDelay = undefined,
|
||||||
|
nextLoadLevel = undefined;
|
||||||
|
// 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 = this.level - 1; nextLoadLevel >= 0; 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)
|
||||||
|
fragLevelNextLoadedDelay = frag.duration * this.levels[nextLoadLevel].bitrate / (8 * 0.8 * loadRate);
|
||||||
|
_logger.logger.log('fragLoadedDelay/bufferStarvationDelay/fragLevelNextLoadedDelay[' + nextLoadLevel + '] :' + fragLoadedDelay.toFixed(1) + '/' + bufferStarvationDelay.toFixed(1) + '/' + fragLevelNextLoadedDelay.toFixed(1));
|
||||||
|
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) {
|
||||||
|
// ensure nextLoadLevel is not negative
|
||||||
|
nextLoadLevel = Math.max(0, nextLoadLevel);
|
||||||
|
// force next load level in auto mode
|
||||||
|
hls.nextLoadLevel = nextLoadLevel;
|
||||||
// abort fragment loading ...
|
// abort fragment loading ...
|
||||||
_logger.logger.warn('loading too slow, abort fragment loading');
|
_logger.logger.warn('loading too slow, abort fragment loading and switch to level ' + nextLoadLevel);
|
||||||
_logger.logger.log('fragLoadedDelay/bufferStarvationDelay/fragLevelNextLoadedDelay :' + fragLoadedDelay.toFixed(1) + '/' + bufferStarvationDelay.toFixed(1) + '/' + fragLevelNextLoadedDelay.toFixed(1));
|
|
||||||
//abort fragment loading
|
//abort fragment loading
|
||||||
frag.loader.abort();
|
frag.loader.abort();
|
||||||
hls.trigger(_events2.default.FRAG_LOAD_EMERGENCY_ABORTED, { frag: frag });
|
hls.trigger(_events2.default.FRAG_LOAD_EMERGENCY_ABORTED, { frag: frag });
|
||||||
// switch back to IDLE state to request new fragment at lowest level
|
// switch back to IDLE state to request new fragment at lower level
|
||||||
this.state = State.IDLE;
|
this.state = State.IDLE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case State.FRAG_LOADING_WAITING_RETRY:
|
case State.FRAG_LOADING_WAITING_RETRY:
|
||||||
var now = performance.now();
|
var now = performance.now();
|
||||||
|
@ -2151,7 +2198,7 @@ var StreamController = function (_EventHandler) {
|
||||||
}, {
|
}, {
|
||||||
key: 'onFragParsingData',
|
key: 'onFragParsingData',
|
||||||
value: function onFragParsingData(data) {
|
value: function onFragParsingData(data) {
|
||||||
var _this2 = this;
|
var _this3 = this;
|
||||||
|
|
||||||
if (this.state === State.PARSING) {
|
if (this.state === State.PARSING) {
|
||||||
this.tparse2 = Date.now();
|
this.tparse2 = Date.now();
|
||||||
|
@ -2166,7 +2213,7 @@ var StreamController = function (_EventHandler) {
|
||||||
|
|
||||||
[data.data1, data.data2].forEach(function (buffer) {
|
[data.data1, data.data2].forEach(function (buffer) {
|
||||||
if (buffer) {
|
if (buffer) {
|
||||||
_this2.pendingAppending++;
|
_this3.pendingAppending++;
|
||||||
hls.trigger(_events2.default.BUFFER_APPENDING, { type: data.type, data: buffer });
|
hls.trigger(_events2.default.BUFFER_APPENDING, { type: data.type, data: buffer });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -5356,6 +5403,7 @@ var Hls = function () {
|
||||||
maxBufferSize: 60 * 1000 * 1000,
|
maxBufferSize: 60 * 1000 * 1000,
|
||||||
maxBufferHole: 0.5,
|
maxBufferHole: 0.5,
|
||||||
maxSeekHole: 2,
|
maxSeekHole: 2,
|
||||||
|
maxFragLookUpTolerance: 0.2,
|
||||||
liveSyncDurationCount: 3,
|
liveSyncDurationCount: 3,
|
||||||
liveMaxLatencyDurationCount: Infinity,
|
liveMaxLatencyDurationCount: Infinity,
|
||||||
liveSyncDuration: undefined,
|
liveSyncDuration: undefined,
|
||||||
|
@ -5574,13 +5622,13 @@ var Hls = function () {
|
||||||
}, {
|
}, {
|
||||||
key: 'nextLoadLevel',
|
key: 'nextLoadLevel',
|
||||||
get: function get() {
|
get: function get() {
|
||||||
return this.levelController.nextLoadLevel();
|
return this.levelController.nextLoadLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** set quality level of next loaded fragment **/
|
/** set quality level of next loaded fragment **/
|
||||||
,
|
,
|
||||||
set: function set(level) {
|
set: function set(level) {
|
||||||
this.levelController.level = level;
|
this.levelController.nextLoadLevel = level;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return first level (index of first level referenced in manifest)
|
/** Return first level (index of first level referenced in manifest)
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "hls.js",
|
"name": "hls.js",
|
||||||
"version": "0.5.11",
|
"version": "0.5.13",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Media Source Extension - HLS library, by/for Dailymotion",
|
"description": "Media Source Extension - HLS library, by/for Dailymotion",
|
||||||
"homepage": "https://github.com/dailymotion/hls.js",
|
"homepage": "https://github.com/dailymotion/hls.js",
|
||||||
|
|
|
@ -241,13 +241,20 @@ class LevelController extends EventHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nextLoadLevel() {
|
get nextLoadLevel() {
|
||||||
if (this._manualLevel !== -1) {
|
if (this._manualLevel !== -1) {
|
||||||
return this._manualLevel;
|
return this._manualLevel;
|
||||||
} else {
|
} else {
|
||||||
return this.hls.abrController.nextAutoLevel;
|
return this.hls.abrController.nextAutoLevel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set nextLoadLevel(nextLevel) {
|
||||||
|
this.level = nextLevel;
|
||||||
|
if (this._manualLevel === -1) {
|
||||||
|
this.hls.abrController.nextAutoLevel = nextLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LevelController;
|
export default LevelController;
|
||||||
|
|
|
@ -231,15 +231,30 @@ class StreamController extends EventHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!frag) {
|
if (!frag) {
|
||||||
var foundFrag;
|
let foundFrag;
|
||||||
|
let maxFragLookUpTolerance = config.maxFragLookUpTolerance;
|
||||||
if (bufferEnd < end) {
|
if (bufferEnd < end) {
|
||||||
|
if (bufferEnd > end - maxFragLookUpTolerance) {
|
||||||
|
maxFragLookUpTolerance = 0;
|
||||||
|
}
|
||||||
foundFrag = BinarySearch.search(fragments, (candidate) => {
|
foundFrag = BinarySearch.search(fragments, (candidate) => {
|
||||||
|
// offset should be within fragment boundary - config.maxFragLookUpTolerance
|
||||||
|
// this is to cope with situations like
|
||||||
|
// bufferEnd = 9.991
|
||||||
|
// frag[Ø] : [0,10]
|
||||||
|
// frag[1] : [10,20]
|
||||||
|
// bufferEnd is within frag[0] range ... although what we are expecting is to return frag[1] here
|
||||||
|
// frag start frag start+duration
|
||||||
|
// |-----------------------------|
|
||||||
|
// <---> <--->
|
||||||
|
// ...--------><-----------------------------><---------....
|
||||||
|
// previous frag matching fragment next frag
|
||||||
|
// return -1 return 0 return 1
|
||||||
//logger.log(`level/sn/start/end/bufEnd:${level}/${candidate.sn}/${candidate.start}/${(candidate.start+candidate.duration)}/${bufferEnd}`);
|
//logger.log(`level/sn/start/end/bufEnd:${level}/${candidate.sn}/${candidate.start}/${(candidate.start+candidate.duration)}/${bufferEnd}`);
|
||||||
// offset should be within fragment boundary
|
if ((candidate.start + candidate.duration - maxFragLookUpTolerance) <= bufferEnd) {
|
||||||
if ((candidate.start + candidate.duration) <= bufferEnd) {
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
else if (candidate.start > bufferEnd) {
|
else if (candidate.start - maxFragLookUpTolerance > bufferEnd) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -323,31 +338,52 @@ class StreamController extends EventHandler {
|
||||||
/* only monitor frag retrieval time if
|
/* only monitor frag retrieval time if
|
||||||
(video not paused OR first fragment being loaded) AND autoswitching enabled AND not lowest level AND multiple levels */
|
(video not paused OR first fragment being loaded) AND autoswitching enabled AND not lowest level AND multiple levels */
|
||||||
if (v && (!v.paused || this.loadedmetadata === false) && frag.autoLevel && this.level && this.levels.length > 1) {
|
if (v && (!v.paused || this.loadedmetadata === false) && frag.autoLevel && this.level && this.levels.length > 1) {
|
||||||
var requestDelay = performance.now() - frag.trequest;
|
let requestDelay = performance.now() - frag.trequest;
|
||||||
// monitor fragment load progress after half of expected fragment duration,to stabilize bitrate
|
// monitor fragment load progress after half of expected fragment duration,to stabilize bitrate
|
||||||
if (requestDelay > (500 * frag.duration)) {
|
if (requestDelay > (500 * frag.duration)) {
|
||||||
var loadRate = frag.loaded * 1000 / requestDelay; // byte/s
|
let loadRate = Math.max(1,frag.loaded * 1000 / requestDelay); // byte/s; at least 1 byte/s to avoid division by zero
|
||||||
if (frag.expectedLen < frag.loaded) {
|
if (frag.expectedLen < frag.loaded) {
|
||||||
frag.expectedLen = frag.loaded;
|
frag.expectedLen = frag.loaded;
|
||||||
}
|
}
|
||||||
pos = v.currentTime;
|
pos = v.currentTime;
|
||||||
var fragLoadedDelay = (frag.expectedLen - frag.loaded) / loadRate;
|
let fragLoadedDelay = (frag.expectedLen - frag.loaded) / loadRate;
|
||||||
var bufferStarvationDelay = this.bufferInfo(pos,config.maxBufferHole).end - pos;
|
let bufferStarvationDelay = this.bufferInfo(pos,config.maxBufferHole).end - pos;
|
||||||
var fragLevelNextLoadedDelay = frag.duration * this.levels[hls.nextLoadLevel].bitrate / (8 * loadRate); //bps/Bps
|
// consider emergency switch down only if we have less than 2 frag buffered AND
|
||||||
/* if we have less than 2 frag duration in buffer and if frag loaded delay is greater than buffer starvation delay
|
// time to finish loading current fragment is bigger than buffer starvation delay
|
||||||
... and also bigger than duration needed to load fragment at next level ...*/
|
// ie if we risk buffer starvation if bw does not increase quickly
|
||||||
if (bufferStarvationDelay < (2 * frag.duration) && fragLoadedDelay > bufferStarvationDelay && fragLoadedDelay > fragLevelNextLoadedDelay) {
|
if (bufferStarvationDelay < 2*frag.duration && fragLoadedDelay > bufferStarvationDelay) {
|
||||||
|
let fragLevelNextLoadedDelay, nextLoadLevel;
|
||||||
|
// 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 = this.level - 1 ; nextLoadLevel >=0 ; 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)
|
||||||
|
fragLevelNextLoadedDelay = frag.duration * this.levels[nextLoadLevel].bitrate / (8 * 0.8 * loadRate);
|
||||||
|
logger.log(`fragLoadedDelay/bufferStarvationDelay/fragLevelNextLoadedDelay[${nextLoadLevel}] :${fragLoadedDelay.toFixed(1)}/${bufferStarvationDelay.toFixed(1)}/${fragLevelNextLoadedDelay.toFixed(1)}`);
|
||||||
|
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) {
|
||||||
|
// ensure nextLoadLevel is not negative
|
||||||
|
nextLoadLevel = Math.max(0,nextLoadLevel);
|
||||||
|
// force next load level in auto mode
|
||||||
|
hls.nextLoadLevel = nextLoadLevel;
|
||||||
// abort fragment loading ...
|
// abort fragment loading ...
|
||||||
logger.warn('loading too slow, abort fragment loading');
|
logger.warn(`loading too slow, abort fragment loading and switch to level ${nextLoadLevel}`);
|
||||||
logger.log(`fragLoadedDelay/bufferStarvationDelay/fragLevelNextLoadedDelay :${fragLoadedDelay.toFixed(1)}/${bufferStarvationDelay.toFixed(1)}/${fragLevelNextLoadedDelay.toFixed(1)}`);
|
|
||||||
//abort fragment loading
|
//abort fragment loading
|
||||||
frag.loader.abort();
|
frag.loader.abort();
|
||||||
hls.trigger(Event.FRAG_LOAD_EMERGENCY_ABORTED, {frag: frag});
|
hls.trigger(Event.FRAG_LOAD_EMERGENCY_ABORTED, {frag: frag});
|
||||||
// switch back to IDLE state to request new fragment at lowest level
|
// switch back to IDLE state to request new fragment at lower level
|
||||||
this.state = State.IDLE;
|
this.state = State.IDLE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case State.FRAG_LOADING_WAITING_RETRY:
|
case State.FRAG_LOADING_WAITING_RETRY:
|
||||||
var now = performance.now();
|
var now = performance.now();
|
||||||
|
|
|
@ -45,6 +45,7 @@ class Hls {
|
||||||
maxBufferSize: 60 * 1000 * 1000,
|
maxBufferSize: 60 * 1000 * 1000,
|
||||||
maxBufferHole: 0.5,
|
maxBufferHole: 0.5,
|
||||||
maxSeekHole: 2,
|
maxSeekHole: 2,
|
||||||
|
maxFragLookUpTolerance : 0.2,
|
||||||
liveSyncDurationCount:3,
|
liveSyncDurationCount:3,
|
||||||
liveMaxLatencyDurationCount: Infinity,
|
liveMaxLatencyDurationCount: Infinity,
|
||||||
liveSyncDuration: undefined,
|
liveSyncDuration: undefined,
|
||||||
|
@ -223,12 +224,12 @@ class Hls {
|
||||||
|
|
||||||
/** Return the quality level of next loaded fragment **/
|
/** Return the quality level of next loaded fragment **/
|
||||||
get nextLoadLevel() {
|
get nextLoadLevel() {
|
||||||
return this.levelController.nextLoadLevel();
|
return this.levelController.nextLoadLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** set quality level of next loaded fragment **/
|
/** set quality level of next loaded fragment **/
|
||||||
set nextLoadLevel(level) {
|
set nextLoadLevel(level) {
|
||||||
this.levelController.level = level;
|
this.levelController.nextLoadLevel = level;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return first level (index of first level referenced in manifest)
|
/** Return first level (index of first level referenced in manifest)
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
"tag": "v1.3.0",
|
"tag": "v1.3.0",
|
||||||
"commit": "1662093611cda3fd29125cdab94a61d3d88093da"
|
"commit": "1662093611cda3fd29125cdab94a61d3d88093da"
|
||||||
},
|
},
|
||||||
"_source": "git://github.com/polymerelements/iron-selector.git",
|
"_source": "git://github.com/PolymerElements/iron-selector.git",
|
||||||
"_target": "^1.0.0",
|
"_target": "^1.0.0",
|
||||||
"_originalSource": "polymerelements/iron-selector"
|
"_originalSource": "PolymerElements/iron-selector"
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue