1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

update components

This commit is contained in:
Luke Pulverenti 2016-06-25 02:04:53 -04:00
parent 2d86f49653
commit ab58f98cc1
27 changed files with 276 additions and 129 deletions

View file

@ -15,12 +15,12 @@
}, },
"devDependencies": {}, "devDependencies": {},
"ignore": [], "ignore": [],
"version": "1.4.54", "version": "1.4.56",
"_release": "1.4.54", "_release": "1.4.56",
"_resolution": { "_resolution": {
"type": "version", "type": "version",
"tag": "1.4.54", "tag": "1.4.56",
"commit": "e54c908295025a828f897937f4e41d3ec9f122ba" "commit": "2e223ca2b059e6af4bbf718d3bce601c714d608e"
}, },
"_source": "https://github.com/MediaBrowser/emby-webcomponents.git", "_source": "https://github.com/MediaBrowser/emby-webcomponents.git",
"_target": "^1.2.0", "_target": "^1.2.0",

View file

@ -3,13 +3,21 @@
function enableAnimation(elem) { function enableAnimation(elem) {
if (browser.mobile) { if (browser.mobile) {
i
return false; return false;
} }
return elem.animate; return elem.animate;
} }
function enableRotation() {
if (browser.tv) {
return false;
}
return true;
}
function backdrop() { function backdrop() {
var self = this; var self = this;
@ -270,7 +278,7 @@
currentRotatingImages = images; currentRotatingImages = images;
currentRotationIndex = -1; currentRotationIndex = -1;
if (images.length > 1) { if (images.length > 1 && enableRotation()) {
rotationInterval = setInterval(onRotationInterval, 20000); rotationInterval = setInterval(onRotationInterval, 20000);
} }
onRotationInterval(); onRotationInterval();

View file

@ -216,7 +216,7 @@ define(['browser'], function (browser) {
var mp3Added = false; var mp3Added = false;
if (canPlayMkv || canPlayTs) { if (canPlayMkv || canPlayTs) {
if (supportsMp3VideoAudio) { if (supportsMp3VideoAudio && !browser.tizen) {
mp3Added = true; mp3Added = true;
videoAudioCodecs.push('mp3'); videoAudioCodecs.push('mp3');
hlsVideoAudioCodecs.push('mp3'); hlsVideoAudioCodecs.push('mp3');
@ -314,7 +314,7 @@ define(['browser'], function (browser) {
}); });
} }
if (canPlayTs) { if (canPlayTs && options.supportsCustomSeeking) {
profile.TranscodingProfiles.push({ profile.TranscodingProfiles.push({
Container: 'ts', Container: 'ts',
Type: 'Video', Type: 'Video',

View file

@ -1,6 +1,6 @@
{ {
"name": "hls.js", "name": "hls.js",
"version": "0.5.39", "version": "0.5.40",
"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.39", "_release": "0.5.40",
"_resolution": { "_resolution": {
"type": "version", "type": "version",
"tag": "v0.5.39", "tag": "v0.5.40",
"commit": "4a4a95907809da3c7391740000804a9b95355dce" "commit": "878b91d51073a6f4d7b6528c204208c3e2a4cf48"
}, },
"_source": "git://github.com/dailymotion/hls.js.git", "_source": "git://github.com/dailymotion/hls.js.git",
"_target": "~0.5.7", "_target": "~0.5.7",

View file

@ -474,19 +474,38 @@ whether or not to enable CEA-708 captions
parameter should be a boolean parameter should be a boolean
#### ```abrEwmaFast``` #### ```abrEwmaFastLive```
(default : 0.0) (default : 5.0)
Fast bitrate Exponential moving average half-life , used to compute average bitrate Fast bitrate Exponential moving average half-life , used to compute average bitrate for Live streams
Half of the estimate is based on the last abrEwmaFast seconds of sample history. Half of the estimate is based on the last abrEwmaFastLive seconds of sample history.
Each of the sample is weighted by the fragment loading duration.
parameter should be a float greater than 0 parameter should be a float greater than 0
#### ```abrEwmaSlow``` #### ```abrEwmaSlowLive```
(default : 0.0) (default : 9.0)
Slow bitrate Exponential moving average half-life , used to compute average bitrate Slow bitrate Exponential moving average half-life , used to compute average bitrate for Live streams
Half of the estimate is based on the last abrEwmaFast seconds of sample history. Half of the estimate is based on the last abrEwmaSlowLive seconds of sample history.
parameter should be a float greater than abrEwmaFast Each of the sample is weighted by the fragment loading duration.
parameter should be a float greater than abrEwmaFastLive
#### ```abrEwmaFastVoD```
(default : 4.0)
Fast bitrate Exponential moving average half-life , used to compute average bitrate for VoD streams
Half of the estimate is based on the last abrEwmaFastVoD seconds of sample history.
Each of the sample is weighted by the fragment loading duration.
parameter should be a float greater than 0
#### ```abrEwmaSlowVoD```
(default : 15.0)
Slow bitrate Exponential moving average half-life , used to compute average bitrate for VoD streams
Half of the estimate is based on the last abrEwmaSlowVoD seconds of sample history.
Each of the sample is weighted by the fragment loading duration.
parameter should be a float greater than abrEwmaFastVoD
#### ```abrBandWidthFactor``` #### ```abrBandWidthFactor```

View file

@ -1,6 +1,6 @@
{ {
"name": "hls.js", "name": "hls.js",
"version": "0.5.39", "version": "0.5.40",
"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",

View file

@ -22,10 +22,6 @@ design idea is pretty simple :
- if there are holes in video.buffered, smaller than config.maxBufferHole, they will be ignored. - if there are holes in video.buffered, smaller than config.maxBufferHole, they will be ignored.
- retrieve URL of fragment matching with this media position, and appropriate quality level - retrieve URL of fragment matching with this media position, and appropriate quality level
- trigger FRAG_LOADING event - trigger FRAG_LOADING event
- **monitor fragment loading speed** (by monitoring data received from FRAG_LOAD_PROGRESS event)
- "expected time of fragment load completion" is computed using "fragment loading instant bandwidth".
- this time is compared to the "expected time of buffer starvation".
- if we have less than 2 fragments buffered and if "expected time of fragment load completion" is bigger than "expected time of buffer starvation" and also bigger than duration needed to load fragment at next quality level (determined by auto quality switch algorithm), current fragment loading is aborted, stream-controller will **trigger an emergency switch down**.
- **trigger fragment demuxing** on FRAG_LOADED - **trigger fragment demuxing** on FRAG_LOADED
- trigger BUFFER_RESET on MANIFEST_PARSED or startLoad() - trigger BUFFER_RESET on MANIFEST_PARSED or startLoad()
- trigger BUFFER_CODECS on FRAG_PARSING_INIT_SEGMENT - trigger BUFFER_CODECS on FRAG_PARSING_INIT_SEGMENT
@ -52,7 +48,11 @@ design idea is pretty simple :
- [src/controller/abr-controller.js][] - [src/controller/abr-controller.js][]
- in charge of determining auto quality level. - in charge of determining auto quality level.
- auto quality switch algorithm is pretty naive and simple ATM and similar to the one that could be found in google [StageFright](https://android.googlesource.com/platform/frameworks/av/+/master/media/libstagefright/httplive/LiveSession.cpp) - auto quality switch algorithm is bitrate based : fragment loading bitrate is monitored and smoothed using 2 exponential weighted moving average (a fast one, to adapt quickly on bandwidth drop and a slow one, to avoid ramping up to quickly on bandwidth increase)
- in charge of **monitoring fragment loading speed** (by monitoring data received from FRAG_LOAD_PROGRESS event)
- "expected time of fragment load completion" is computed using "fragment loading instant bandwidth".
- this time is compared to the "expected time of buffer starvation".
- if we have less than 2 fragments buffered and if "expected time of fragment load completion" is bigger than "expected time of buffer starvation" and also bigger than duration needed to load fragment at next quality level (determined by auto quality switch algorithm), current fragment loading is aborted, stream-controller will **trigger an emergency switch down**.
- [src/controller/cap-level-controller.js][] - [src/controller/cap-level-controller.js][]
- in charge of determining best quality level to actual size (dimensions: width and height) of the player - in charge of determining best quality level to actual size (dimensions: width and height) of the player
- [src/crypt/aes.js][] - [src/crypt/aes.js][]

View file

@ -418,7 +418,6 @@ var AbrController = function (_EventHandler) {
_this._autoLevelCapping = -1; _this._autoLevelCapping = -1;
_this._nextAutoLevel = -1; _this._nextAutoLevel = -1;
_this.hls = hls; _this.hls = hls;
_this.bwEstimator = new _ewmaBandwidthEstimator2.default(hls);
_this.onCheck = _this.abandonRulesCheck.bind(_this); _this.onCheck = _this.abandonRulesCheck.bind(_this);
return _this; return _this;
} }
@ -435,6 +434,27 @@ var AbrController = function (_EventHandler) {
if (!this.timer) { if (!this.timer) {
this.timer = setInterval(this.onCheck, 100); this.timer = setInterval(this.onCheck, 100);
} }
// lazy init of bw Estimator, 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,
level = data.frag.level,
isLive = hls.levels[level].details.live,
config = hls.config,
ewmaFast = void 0,
ewmaSlow = void 0;
if (isLive) {
ewmaFast = config.abrEwmaFastLive;
ewmaSlow = config.abrEwmaSlowLive;
} else {
ewmaFast = config.abrEwmaFastVoD;
ewmaSlow = config.abrEwmaSlowVoD;
}
this.bwEstimator = new _ewmaBandwidthEstimator2.default(hls, ewmaSlow, ewmaFast);
}
var frag = data.frag; var frag = data.frag;
frag.trequest = performance.now(); frag.trequest = performance.now();
this.fragCurrent = frag; this.fragCurrent = frag;
@ -993,9 +1013,10 @@ var BufferController = function (_EventHandler) {
} else { } else {
// QuotaExceededError: http://www.w3.org/TR/html5/infrastructure.html#quotaexceedederror // QuotaExceededError: http://www.w3.org/TR/html5/infrastructure.html#quotaexceedederror
// let's stop appending any segments, and report BUFFER_FULL_ERROR error // let's stop appending any segments, and report BUFFER_FULL_ERROR error
segments = []; this.segments = [];
event.details = _errors.ErrorDetails.BUFFER_FULL_ERROR; event.details = _errors.ErrorDetails.BUFFER_FULL_ERROR;
hls.trigger(_events2.default.ERROR, event); hls.trigger(_events2.default.ERROR, event);
return;
} }
} }
} }
@ -1230,33 +1251,28 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var EwmaBandWidthEstimator = function () { var EwmaBandWidthEstimator = function () {
function EwmaBandWidthEstimator(hls) { function EwmaBandWidthEstimator(hls, slow, fast) {
_classCallCheck(this, EwmaBandWidthEstimator); _classCallCheck(this, EwmaBandWidthEstimator);
this.hls = hls; this.hls = hls;
this.defaultEstimate_ = 5e5; // 500kbps this.defaultEstimate_ = 5e5; // 500kbps
this.minWeight_ = 0.001; this.minWeight_ = 0.001;
this.minDelayMs_ = 50; this.minDelayMs_ = 50;
this.slow_ = new _ewma2.default(slow);
this.fast_ = new _ewma2.default(fast);
} }
_createClass(EwmaBandWidthEstimator, [{ _createClass(EwmaBandWidthEstimator, [{
key: 'sample', key: 'sample',
value: function sample(durationMs, numBytes) { value: function sample(durationMs, numBytes) {
durationMs = Math.max(durationMs, this.minDelayMs_); durationMs = Math.max(durationMs, this.minDelayMs_);
var bandwidth = 8000 * numBytes / durationMs; var bandwidth = 8000 * numBytes / durationMs,
//console.log('instant bw:'+ Math.round(bandwidth)); //console.log('instant bw:'+ Math.round(bandwidth));
// we weight sample using loading duration.... // we weight sample using loading duration....
var weigth = durationMs / 1000; weight = durationMs / 1000;
this.fast_.sample(weight, bandwidth);
// lazy initialization. this allows to take into account config param changes that could happen after Hls instantiation, this.slow_.sample(weight, bandwidth);
// but before first fragment loading. this is useful to A/B tests those params
if (!this.fast_) {
var config = this.hls.config;
this.fast_ = new _ewma2.default(config.abrEwmaFast);
this.slow_ = new _ewma2.default(config.abrEwmaSlow);
}
this.fast_.sample(weigth, bandwidth);
this.slow_.sample(weigth, bandwidth);
} }
}, { }, {
key: 'getEstimate', key: 'getEstimate',
@ -1959,8 +1975,21 @@ var StreamController = function (_EventHandler) {
//logger.log('find SN matching with pos:' + bufferEnd + ':' + frag.sn); //logger.log('find SN matching with pos:' + bufferEnd + ':' + frag.sn);
if (fragPrevious && frag.level === fragPrevious.level && frag.sn === fragPrevious.sn) { if (fragPrevious && frag.level === fragPrevious.level && frag.sn === fragPrevious.sn) {
if (frag.sn < levelDetails.endSN) { if (frag.sn < levelDetails.endSN) {
frag = fragments[frag.sn + 1 - levelDetails.startSN]; var deltaPTS = fragPrevious.deltaPTS,
_logger.logger.log('SN just loaded, load next one: ' + frag.sn); curSNIdx = frag.sn - levelDetails.startSN;
// if there is a significant delta between audio and video, larger than max allowed hole,
// it might be because video fragment does not start with a keyframe.
// let's try to load previous fragment again to get last keyframe
// then we will reload again current fragment (that way we should be able to fill the buffer hole ...)
if (deltaPTS && deltaPTS > config.maxBufferHole) {
frag = fragments[curSNIdx - 1];
_logger.logger.warn('SN just loaded, with large PTS gap between audio and video, maybe frag is not starting with a keyframe ? load previous one to try to overcome this');
// decrement previous frag load counter to avoid frag loop loading error when next fragment will get reloaded
fragPrevious.loadCounter--;
} else {
frag = fragments[curSNIdx + 1];
_logger.logger.log('SN just loaded, load next one: ' + frag.sn);
}
} else { } else {
// have we reached end of VOD playlist ? // have we reached end of VOD playlist ?
if (!levelDetails.live) { if (!levelDetails.live) {
@ -1973,7 +2002,7 @@ var StreamController = function (_EventHandler) {
this.state = State.ENDED; this.state = State.ENDED;
} }
} }
return; break;
} }
} }
//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));
@ -2654,11 +2683,15 @@ var StreamController = function (_EventHandler) {
} }
break; break;
case _errors.ErrorDetails.BUFFER_FULL_ERROR: case _errors.ErrorDetails.BUFFER_FULL_ERROR:
// trigger a smooth level switch to empty buffers // only reduce max buf len if in appending state
// also reduce max buffer length as it might be too high. we do this to avoid loop flushing ... if (this.state === State.PARSING || this.state === State.PARSED) {
this.config.maxMaxBufferLength /= 2; // reduce max buffer length as it might be too high. we do this to avoid loop flushing ...
_logger.logger.warn('reduce max buffer length to ' + this.config.maxMaxBufferLength + 's and trigger a nextLevelSwitch to flush old buffer and fix QuotaExceededError'); this.config.maxMaxBufferLength /= 2;
this.nextLevelSwitch(); _logger.logger.warn('reduce max buffer length to ' + this.config.maxMaxBufferLength + 's and switch to IDLE state');
// increase fragment load Index to avoid frag loop loading error after buffer flush
this.fragLoadIdx += 2 * this.config.fragLoadingLoopThreshold;
this.state = State.IDLE;
}
break; break;
default: default:
break; break;
@ -5765,6 +5798,13 @@ var LevelHelper = function () {
fragments = details.fragments; fragments = details.fragments;
frag = fragments[fragIdx]; frag = fragments[fragIdx];
if (!isNaN(frag.startPTS)) { if (!isNaN(frag.startPTS)) {
// delta PTS between audio and video
var deltaPTS = Math.abs(frag.startPTS - startPTS);
if (isNaN(frag.deltaPTS)) {
frag.deltaPTS = deltaPTS;
} else {
frag.deltaPTS = Math.max(deltaPTS, frag.deltaPTS);
}
startPTS = Math.min(startPTS, frag.startPTS); startPTS = Math.min(startPTS, frag.startPTS);
endPTS = Math.max(endPTS, frag.endPTS); endPTS = Math.max(endPTS, frag.endPTS);
startDTS = Math.min(startDTS, frag.startDTS); startDTS = Math.min(startDTS, frag.startDTS);
@ -5966,8 +6006,10 @@ var Hls = function () {
timelineController: _timelineController2.default, timelineController: _timelineController2.default,
enableCEA708Captions: true, enableCEA708Captions: true,
enableMP2TPassThrough: false, enableMP2TPassThrough: false,
abrEwmaFast: 0, abrEwmaFastLive: 5,
abrEwmaSlow: 0, abrEwmaSlowLive: 9,
abrEwmaFastVoD: 4,
abrEwmaSlowVoD: 15,
abrBandWidthFactor: 0.8, abrBandWidthFactor: 0.8,
abrBandWidthUpFactor: 0.7 abrBandWidthUpFactor: 0.7
}; };
@ -7509,6 +7551,14 @@ var MP4Remuxer = function () {
dtsnorm, dtsnorm,
flags, flags,
samples = []; samples = [];
// handle broken streams with PTS < DTS, tolerance up 200ms (18000 in 90kHz timescale)
var PTSDTSshift = track.samples.reduce(function (prev, curr) {
return Math.max(Math.min(prev, curr.pts - curr.dts), -18000);
}, 0);
if (PTSDTSshift < 0) {
_logger.logger.warn('PTS < DTS detected in video samples, shifting DTS by ' + Math.round(PTSDTSshift / 90) + ' ms to overcome this issue');
}
/* concatenate the video data and construct the mdat in place /* concatenate the video data and construct the mdat in place
(need 8 more bytes to fill length and mpdat type) */ (need 8 more bytes to fill length and mpdat type) */
mdat = new Uint8Array(track.len + 4 * track.nbNalu + 8); mdat = new Uint8Array(track.len + 4 * track.nbNalu + 8);
@ -7528,10 +7578,11 @@ var MP4Remuxer = function () {
mp4SampleLength += 4 + unit.data.byteLength; mp4SampleLength += 4 + unit.data.byteLength;
} }
pts = avcSample.pts - this._initDTS; pts = avcSample.pts - this._initDTS;
dts = avcSample.dts - this._initDTS; // shift dts by PTSDTSshift, to ensure that PTS >= DTS
// ensure DTS is not bigger than PTS dts = avcSample.dts - this._initDTS + PTSDTSshift;
// ensure DTS is not bigger than PTS // strap belt !!!
dts = Math.min(pts, dts); dts = Math.min(pts, dts);
//logger.log(`Video/PTS/DTS:${Math.round(pts/90)}/${Math.round(dts/90)}`); //logger.log(`Video/PTS/DTS/ptsnorm/DTSnorm:${Math.round(avcSample.pts/90)}/${Math.round(avcSample.dts/90)}/${Math.round(pts/90)}/${Math.round(dts/90)}`);
// if not first AVC sample of video track, normalize PTS/DTS with previous sample value // if not first AVC sample of video track, normalize PTS/DTS with previous sample value
// and ensure that sample duration is positive // and ensure that sample duration is positive
if (lastDTS !== undefined) { if (lastDTS !== undefined) {
@ -7574,7 +7625,7 @@ var MP4Remuxer = function () {
firstPTS = Math.max(0, ptsnorm); firstPTS = Math.max(0, ptsnorm);
firstDTS = Math.max(0, dtsnorm); firstDTS = Math.max(0, dtsnorm);
} }
//console.log('PTS/DTS/initDTS/normPTS/normDTS/relative PTS : ${avcSample.pts}/${avcSample.dts}/${this._initDTS}/${ptsnorm}/${dtsnorm}/${(avcSample.pts/4294967296).toFixed(3)}'); //console.log(`PTS/DTS/initDTS/normPTS/normDTS/relative PTS : ${avcSample.pts}/${avcSample.dts}/${this._initDTS}/${ptsnorm}/${dtsnorm}/${(avcSample.pts/4294967296).toFixed(3)});
mp4Sample = { mp4Sample = {
size: mp4SampleLength, size: mp4SampleLength,
duration: 0, duration: 0,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,6 @@
{ {
"name": "hls.js", "name": "hls.js",
"version": "0.5.39", "version": "0.5.40",
"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",

View file

@ -21,7 +21,6 @@ class AbrController extends EventHandler {
this._autoLevelCapping = -1; this._autoLevelCapping = -1;
this._nextAutoLevel = -1; this._nextAutoLevel = -1;
this.hls = hls; this.hls = hls;
this.bwEstimator = new EwmaBandWidthEstimator(hls);
this.onCheck = this.abandonRulesCheck.bind(this); this.onCheck = this.abandonRulesCheck.bind(this);
} }
@ -34,6 +33,26 @@ class AbrController extends EventHandler {
if (!this.timer) { if (!this.timer) {
this.timer = setInterval(this.onCheck, 100); this.timer = setInterval(this.onCheck, 100);
} }
// lazy init of bw Estimator, 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) {
let hls = this.hls,
level = data.frag.level,
isLive = hls.levels[level].details.live,
config = hls.config,
ewmaFast, ewmaSlow;
if (isLive) {
ewmaFast = config.abrEwmaFastLive;
ewmaSlow = config.abrEwmaSlowLive;
} else {
ewmaFast = config.abrEwmaFastVoD;
ewmaSlow = config.abrEwmaSlowVoD;
}
this.bwEstimator = new EwmaBandWidthEstimator(hls,ewmaSlow,ewmaFast);
}
let frag = data.frag; let frag = data.frag;
frag.trequest = performance.now(); frag.trequest = performance.now();
this.fragCurrent = frag; this.fragCurrent = frag;

View file

@ -338,9 +338,10 @@ class BufferController extends EventHandler {
} else { } else {
// QuotaExceededError: http://www.w3.org/TR/html5/infrastructure.html#quotaexceedederror // QuotaExceededError: http://www.w3.org/TR/html5/infrastructure.html#quotaexceedederror
// let's stop appending any segments, and report BUFFER_FULL_ERROR error // let's stop appending any segments, and report BUFFER_FULL_ERROR error
segments = []; this.segments = [];
event.details = ErrorDetails.BUFFER_FULL_ERROR; event.details = ErrorDetails.BUFFER_FULL_ERROR;
hls.trigger(Event.ERROR,event); hls.trigger(Event.ERROR,event);
return;
} }
} }
} }

View file

@ -11,29 +11,23 @@ import EWMA from '../utils/ewma';
class EwmaBandWidthEstimator { class EwmaBandWidthEstimator {
constructor(hls) { constructor(hls,slow,fast) {
this.hls = hls; this.hls = hls;
this.defaultEstimate_ = 5e5; // 500kbps this.defaultEstimate_ = 5e5; // 500kbps
this.minWeight_ = 0.001; this.minWeight_ = 0.001;
this.minDelayMs_ = 50; this.minDelayMs_ = 50;
this.slow_ = new EWMA(slow);
this.fast_ = new EWMA(fast);
} }
sample(durationMs,numBytes) { sample(durationMs,numBytes) {
durationMs = Math.max(durationMs, this.minDelayMs_); durationMs = Math.max(durationMs, this.minDelayMs_);
var bandwidth = 8000* numBytes / durationMs; var bandwidth = 8000* numBytes / durationMs,
//console.log('instant bw:'+ Math.round(bandwidth)); //console.log('instant bw:'+ Math.round(bandwidth));
// we weight sample using loading duration.... // we weight sample using loading duration....
var weigth = durationMs / 1000; weight = durationMs / 1000;
this.fast_.sample(weight,bandwidth);
// lazy initialization. this allows to take into account config param changes that could happen after Hls instantiation, this.slow_.sample(weight,bandwidth);
// but before first fragment loading. this is useful to A/B tests those params
if(!this.fast_) {
let config = this.hls.config;
this.fast_ = new EWMA(config.abrEwmaFast);
this.slow_ = new EWMA(config.abrEwmaSlow);
}
this.fast_.sample(weigth,bandwidth);
this.slow_.sample(weigth,bandwidth);
} }

View file

@ -285,8 +285,21 @@ class StreamController extends EventHandler {
//logger.log('find SN matching with pos:' + bufferEnd + ':' + frag.sn); //logger.log('find SN matching with pos:' + bufferEnd + ':' + frag.sn);
if (fragPrevious && frag.level === fragPrevious.level && frag.sn === fragPrevious.sn) { if (fragPrevious && frag.level === fragPrevious.level && frag.sn === fragPrevious.sn) {
if (frag.sn < levelDetails.endSN) { if (frag.sn < levelDetails.endSN) {
frag = fragments[frag.sn + 1 - levelDetails.startSN]; let deltaPTS = fragPrevious.deltaPTS,
logger.log(`SN just loaded, load next one: ${frag.sn}`); curSNIdx = frag.sn - levelDetails.startSN;
// if there is a significant delta between audio and video, larger than max allowed hole,
// it might be because video fragment does not start with a keyframe.
// let's try to load previous fragment again to get last keyframe
// then we will reload again current fragment (that way we should be able to fill the buffer hole ...)
if (deltaPTS && deltaPTS > config.maxBufferHole) {
frag = fragments[curSNIdx-1];
logger.warn(`SN just loaded, with large PTS gap between audio and video, maybe frag is not starting with a keyframe ? load previous one to try to overcome this`);
// decrement previous frag load counter to avoid frag loop loading error when next fragment will get reloaded
fragPrevious.loadCounter--;
} else {
frag = fragments[curSNIdx+1];
logger.log(`SN just loaded, load next one: ${frag.sn}`);
}
} else { } else {
// have we reached end of VOD playlist ? // have we reached end of VOD playlist ?
if (!levelDetails.live) { if (!levelDetails.live) {
@ -299,7 +312,7 @@ class StreamController extends EventHandler {
this.state = State.ENDED; this.state = State.ENDED;
} }
} }
return; break;
} }
} }
//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));
@ -974,11 +987,15 @@ class StreamController extends EventHandler {
} }
break; break;
case ErrorDetails.BUFFER_FULL_ERROR: case ErrorDetails.BUFFER_FULL_ERROR:
// trigger a smooth level switch to empty buffers // only reduce max buf len if in appending state
// also reduce max buffer length as it might be too high. we do this to avoid loop flushing ... if (this.state === State.PARSING || this.state === State.PARSED) {
this.config.maxMaxBufferLength/=2; // reduce max buffer length as it might be too high. we do this to avoid loop flushing ...
logger.warn(`reduce max buffer length to ${this.config.maxMaxBufferLength}s and trigger a nextLevelSwitch to flush old buffer and fix QuotaExceededError`); this.config.maxMaxBufferLength/=2;
this.nextLevelSwitch(); logger.warn(`reduce max buffer length to ${this.config.maxMaxBufferLength}s and switch to IDLE state`);
// increase fragment load Index to avoid frag loop loading error after buffer flush
this.fragLoadIdx += 2 * this.config.fragLoadingLoopThreshold;
this.state = State.IDLE;
}
break; break;
default: default:
break; break;

View file

@ -71,6 +71,13 @@ class LevelHelper {
fragments = details.fragments; fragments = details.fragments;
frag = fragments[fragIdx]; frag = fragments[fragIdx];
if(!isNaN(frag.startPTS)) { if(!isNaN(frag.startPTS)) {
// delta PTS between audio and video
let deltaPTS = Math.abs(frag.startPTS-startPTS);
if (isNaN(frag.deltaPTS)) {
frag.deltaPTS = deltaPTS;
} else {
frag.deltaPTS = Math.max(deltaPTS,frag.deltaPTS);
}
startPTS = Math.min(startPTS,frag.startPTS); startPTS = Math.min(startPTS,frag.startPTS);
endPTS = Math.max(endPTS, frag.endPTS); endPTS = Math.max(endPTS, frag.endPTS);
startDTS = Math.min(startDTS,frag.startDTS); startDTS = Math.min(startDTS,frag.startDTS);

View file

@ -83,8 +83,10 @@ class Hls {
timelineController: TimelineController, timelineController: TimelineController,
enableCEA708Captions: true, enableCEA708Captions: true,
enableMP2TPassThrough : false, enableMP2TPassThrough : false,
abrEwmaFast: 0, abrEwmaFastLive: 5,
abrEwmaSlow: 0, abrEwmaSlowLive: 9,
abrEwmaFastVoD: 4,
abrEwmaSlowVoD: 15,
abrBandWidthFactor : 0.8, abrBandWidthFactor : 0.8,
abrBandWidthUpFactor : 0.7 abrBandWidthUpFactor : 0.7
}; };

View file

@ -145,6 +145,12 @@ class MP4Remuxer {
pts, dts, ptsnorm, dtsnorm, pts, dts, ptsnorm, dtsnorm,
flags, flags,
samples = []; samples = [];
// handle broken streams with PTS < DTS, tolerance up 200ms (18000 in 90kHz timescale)
let PTSDTSshift = track.samples.reduce( (prev, curr) => Math.max(Math.min(prev,curr.pts-curr.dts),-18000),0);
if (PTSDTSshift < 0) {
logger.warn(`PTS < DTS detected in video samples, shifting DTS by ${Math.round(PTSDTSshift/90)} ms to overcome this issue`);
}
/* concatenate the video data and construct the mdat in place /* concatenate the video data and construct the mdat in place
(need 8 more bytes to fill length and mpdat type) */ (need 8 more bytes to fill length and mpdat type) */
mdat = new Uint8Array(track.len + (4 * track.nbNalu) + 8); mdat = new Uint8Array(track.len + (4 * track.nbNalu) + 8);
@ -164,10 +170,11 @@ class MP4Remuxer {
mp4SampleLength += 4 + unit.data.byteLength; mp4SampleLength += 4 + unit.data.byteLength;
} }
pts = avcSample.pts - this._initDTS; pts = avcSample.pts - this._initDTS;
dts = avcSample.dts - this._initDTS; // shift dts by PTSDTSshift, to ensure that PTS >= DTS
// ensure DTS is not bigger than PTS dts = avcSample.dts - this._initDTS + PTSDTSshift;
// ensure DTS is not bigger than PTS // strap belt !!!
dts = Math.min(pts,dts); dts = Math.min(pts,dts);
//logger.log(`Video/PTS/DTS:${Math.round(pts/90)}/${Math.round(dts/90)}`); //logger.log(`Video/PTS/DTS/ptsnorm/DTSnorm:${Math.round(avcSample.pts/90)}/${Math.round(avcSample.dts/90)}/${Math.round(pts/90)}/${Math.round(dts/90)}`);
// if not first AVC sample of video track, normalize PTS/DTS with previous sample value // if not first AVC sample of video track, normalize PTS/DTS with previous sample value
// and ensure that sample duration is positive // and ensure that sample duration is positive
if (lastDTS !== undefined) { if (lastDTS !== undefined) {
@ -209,7 +216,7 @@ class MP4Remuxer {
firstPTS = Math.max(0, ptsnorm); firstPTS = Math.max(0, ptsnorm);
firstDTS = Math.max(0, dtsnorm); firstDTS = Math.max(0, dtsnorm);
} }
//console.log('PTS/DTS/initDTS/normPTS/normDTS/relative PTS : ${avcSample.pts}/${avcSample.dts}/${this._initDTS}/${ptsnorm}/${dtsnorm}/${(avcSample.pts/4294967296).toFixed(3)}'); //console.log(`PTS/DTS/initDTS/normPTS/normDTS/relative PTS : ${avcSample.pts}/${avcSample.dts}/${this._initDTS}/${ptsnorm}/${dtsnorm}/${(avcSample.pts/4294967296).toFixed(3)});
mp4Sample = { mp4Sample = {
size: mp4SampleLength, size: mp4SampleLength,
duration: 0, duration: 0,

View file

@ -1,6 +1,6 @@
{ {
"name": "iron-a11y-keys-behavior", "name": "iron-a11y-keys-behavior",
"version": "1.1.4", "version": "1.1.5",
"description": "A behavior that enables keybindings for greater a11y.", "description": "A behavior that enables keybindings for greater a11y.",
"keywords": [ "keywords": [
"web-components", "web-components",
@ -30,14 +30,14 @@
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0" "webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0"
}, },
"ignore": [], "ignore": [],
"homepage": "https://github.com/polymerelements/iron-a11y-keys-behavior", "homepage": "https://github.com/PolymerElements/iron-a11y-keys-behavior",
"_release": "1.1.4", "_release": "1.1.5",
"_resolution": { "_resolution": {
"type": "version", "type": "version",
"tag": "v1.1.4", "tag": "v1.1.5",
"commit": "36362671d1cee654758d0e2167548f9fdcff2805" "commit": "b6f935cf203b328651f96ac3d21979cac7c9c241"
}, },
"_source": "git://github.com/polymerelements/iron-a11y-keys-behavior.git", "_source": "git://github.com/PolymerElements/iron-a11y-keys-behavior.git",
"_target": "^1.0.0", "_target": "^1.0.0",
"_originalSource": "polymerelements/iron-a11y-keys-behavior" "_originalSource": "PolymerElements/iron-a11y-keys-behavior"
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "iron-a11y-keys-behavior", "name": "iron-a11y-keys-behavior",
"version": "1.1.4", "version": "1.1.5",
"description": "A behavior that enables keybindings for greater a11y.", "description": "A behavior that enables keybindings for greater a11y.",
"keywords": [ "keywords": [
"web-components", "web-components",

View file

@ -391,24 +391,23 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_resetKeyEventListeners: function() { _resetKeyEventListeners: function() {
this._unlistenKeyEventListeners(); this._unlistenKeyEventListeners();
if (this.isAttached && this.keyEventTarget) { if (this.isAttached) {
this._listenKeyEventListeners(); this._listenKeyEventListeners();
} }
}, },
_listenKeyEventListeners: function () { _listenKeyEventListeners: function() {
if (!this.keyEventTarget) {
return;
}
Object.keys(this._keyBindings).forEach(function(eventName) {
var keyBindings = this._keyBindings[eventName];
var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings);
if (this.keyEventTarget) { this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyHandler]);
Object.keys(this._keyBindings).forEach(function (eventName) {
var keyBindings = this._keyBindings[eventName];
var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings);
this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyHandler]);
this.keyEventTarget.addEventListener(eventName, boundKeyHandler);
}, this);
}
this.keyEventTarget.addEventListener(eventName, boundKeyHandler);
}, this);
}, },
_unlistenKeyEventListeners: function() { _unlistenKeyEventListeners: function() {

View file

@ -431,6 +431,15 @@ suite('Polymer.IronA11yKeysBehavior', function() {
}); });
}); });
suite('remove key behavior with null target', function () {
test('add and remove a iron-a11y-keys-behavior', function () {
var element = document.createElement('x-a11y-basic-keys');
element.keyEventTarget = null;
document.body.appendChild(element);
document.body.removeChild(element);
});
});
}); });
</script> </script>
</body> </body>

View file

@ -1,6 +1,6 @@
{ {
"name": "paper-input", "name": "paper-input",
"version": "1.1.12", "version": "1.1.13",
"description": "Material design text fields", "description": "Material design text fields",
"authors": [ "authors": [
"The Polymer Authors" "The Polymer Authors"
@ -48,11 +48,11 @@
"web-component-tester": "^4.0.0", "web-component-tester": "^4.0.0",
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0" "webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0"
}, },
"_release": "1.1.12", "_release": "1.1.13",
"_resolution": { "_resolution": {
"type": "version", "type": "version",
"tag": "v1.1.12", "tag": "v1.1.13",
"commit": "5dd3174ecee88ea280e960c6b6fbd04ce5c3da0e" "commit": "ce90cc77b23b8d0047715651940c48450f4d8d7b"
}, },
"_source": "git://github.com/PolymerElements/paper-input.git", "_source": "git://github.com/PolymerElements/paper-input.git",
"_target": "^1.1.11", "_target": "^1.1.11",

View file

@ -1,6 +1,6 @@
{ {
"name": "paper-input", "name": "paper-input",
"version": "1.1.12", "version": "1.1.13",
"description": "Material design text fields", "description": "Material design text fields",
"authors": [ "authors": [
"The Polymer Authors" "The Polymer Authors"

View file

@ -87,7 +87,7 @@ Custom property | Description | Default
state.value = state.value || ''; state.value = state.value || '';
var counter = state.value.length.toString(); var counter = state.value.toString().length.toString();
if (state.inputElement.hasAttribute('maxlength')) { if (state.inputElement.hasAttribute('maxlength')) {
counter += '/' + state.inputElement.getAttribute('maxlength'); counter += '/' + state.inputElement.getAttribute('maxlength');

View file

@ -83,6 +83,12 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</template> </template>
</test-fixture> </test-fixture>
<test-fixture id="type-number-char-counter">
<template>
<paper-input type="number" char-counter value="1138"></paper-input>
</template>
</test-fixture>
<test-fixture id="always-float-label"> <test-fixture id="always-float-label">
<template> <template>
<paper-input always-float-label label="foo"></paper-input> <paper-input always-float-label label="foo"></paper-input>
@ -179,6 +185,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
assert.equal(counter._charCounterStr, input.value.length, 'character counter shows the value length'); assert.equal(counter._charCounterStr, input.value.length, 'character counter shows the value length');
}); });
test('character counter is correct for type=number', function() {
var input = fixture('type-number-char-counter');
forceXIfStamp(input);
var counter = Polymer.dom(input.root).querySelector('paper-input-char-counter')
assert.ok(counter, 'paper-input-char-counter exists');
assert.equal(counter._charCounterStr, input.value.toString().length, 'character counter shows the value length');
});
test('validator is used', function() { test('validator is used', function() {
var input = fixture('validator'); var input = fixture('validator');
assert.ok(input.inputElement.invalid, 'input is invalid'); assert.ok(input.inputElement.invalid, 'input is invalid');

View file

@ -31,14 +31,14 @@
"web-component-tester": "*" "web-component-tester": "*"
}, },
"private": true, "private": true,
"homepage": "https://github.com/Polymer/polymer", "homepage": "https://github.com/polymer/polymer",
"_release": "1.5.0", "_release": "1.5.0",
"_resolution": { "_resolution": {
"type": "version", "type": "version",
"tag": "v1.5.0", "tag": "v1.5.0",
"commit": "ce5b9fb2d8aa03c698410e2e55cffcfa0b788a3a" "commit": "ce5b9fb2d8aa03c698410e2e55cffcfa0b788a3a"
}, },
"_source": "git://github.com/Polymer/polymer.git", "_source": "git://github.com/polymer/polymer.git",
"_target": "^1.1.0", "_target": "^1.0.0",
"_originalSource": "Polymer/polymer" "_originalSource": "polymer/polymer"
} }