Skip to content

Commit

Permalink
feat: add warn/info triggers and defaults for ll-hls tags (videojs#131)
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonocasey authored Jan 22, 2021
1 parent 0ec49c6 commit e721ead
Show file tree
Hide file tree
Showing 8 changed files with 596 additions and 60 deletions.
76 changes: 38 additions & 38 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@
"@rollup/plugin-replace": "^2.3.4",
"@videojs/generator-helpers": "~2.0.1",
"karma": "^5.2.3",
"rollup": "^2.36.1",
"rollup": "^2.37.1",
"rollup-plugin-data-files": "^0.1.0",
"sinon": "^9.2.3",
"videojs-generate-karma-config": "~7.0.0",
"videojs-generate-rollup-config": "~6.1.0",
"videojs-generate-karma-config": "~7.1.0",
"videojs-generate-rollup-config": "~6.2.0",
"videojs-generator-verify": "~3.0.1",
"videojs-standard": "^8.0.4"
},
Expand Down
143 changes: 142 additions & 1 deletion src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,55 @@ import decodeB64ToUint8Array from '@videojs/vhs-utils/es/decode-b64-to-uint8-arr
import LineStream from './line-stream';
import ParseStream from './parse-stream';

// set SERVER-CONTROL hold back based upon targetDuration and partTargetDuration
// we need this helper because defaults are based upon targetDuration and
// partTargetDuration being set, but they may not be if SERVER-CONTROL appears before
// target durations are set.
const setHoldBack = function(manifest) {
const {serverControl, targetDuration, partTargetDuration} = manifest;

if (!serverControl) {
return;
}

const tag = '#EXT-X-SERVER-CONTROL';
const hb = 'HOLD-BACK';
const phb = 'PART-HOLD-BACK';
const minTargetDuration = targetDuration && targetDuration * 3;
const minPartDuration = partTargetDuration && partTargetDuration * 2;

if (targetDuration && !serverControl.hasOwnProperty(hb)) {
serverControl[hb] = minTargetDuration;
this.trigger('info', {
message: `${tag} defaulting ${hb} to targetDuration * 3 (${minTargetDuration}).`
});
}

if (minTargetDuration && serverControl[hb] < minTargetDuration) {
this.trigger('warn', {
message: `${tag} clamping ${hb} (${serverControl[hb]}) to targetDuration * 3 (${minTargetDuration})`
});
serverControl[hb] = minTargetDuration;
}

// default no part hold back to part target duration * 3
if (partTargetDuration && !serverControl.hasOwnProperty(phb)) {
serverControl[phb] = partTargetDuration * 3;
this.trigger('info', {
message: `${tag} defaulting ${phb} to partTargetDuration * 3 (${serverControl[phb]}).`
});
}

// if part hold back is too small default it to part target duration * 2
if (partTargetDuration && serverControl[phb] < (minPartDuration)) {
this.trigger('warn', {
message: `${tag} clamping ${phb} (${serverControl[phb]}) to partTargetDuration * 2 (${minPartDuration}).`
});

serverControl[phb] = minPartDuration;
}
};

/**
* A parser for M3U8 files. The current interpretation of the input is
* exposed as a property `manifest` on parser objects. It's just two lines to
Expand Down Expand Up @@ -353,6 +402,8 @@ export default class Parser extends Stream {
return;
}
this.manifest.targetDuration = entry.duration;

setHoldBack.call(this, this.manifest);
},
totalduration() {
if (!isFinite(entry.duration) || entry.duration < 0) {
Expand Down Expand Up @@ -386,24 +437,114 @@ export default class Parser extends Stream {
},
'skip'() {
this.manifest.skip = entry.attributes;

if (!entry.attributes.hasOwnProperty('SKIPPED-SEGMENTS')) {
this.trigger('warn', {
message: '#EXT-X-SKIP lacks required attribute: SKIPPED-SEGMENTS'
});
}
},
'part'() {
this.manifest.parts = this.manifest.parts || [];
this.manifest.parts.push(entry.attributes);
const missingAttributes = [];

['URI', 'DURATION'].forEach(function(k) {
if (!entry.attributes.hasOwnProperty(k)) {
missingAttributes.push(k);
}
});

if (missingAttributes.length) {
const index = this.manifest.parts.length - 1;

this.trigger('warn', {
message: `#EXT-X-PART #${index} lacks required attribute(s): ${missingAttributes.join(', ')}`
});
}

if (this.manifest.renditionReports) {
this.manifest.renditionReports.forEach((r, i) => {
if (!r.hasOwnProperty('LAST-PART')) {
this.trigger('warn', {
message: `#EXT-X-RENDITION-REPORT #${i} lacks required attribute(s): LAST-PART`
});
}
});
}
},
'server-control'() {
this.manifest.serverControl = entry.attributes;
const attrs = entry.attributes;

this.manifest.serverControl = attrs;
if (!attrs.hasOwnProperty('CAN-BLOCK-RELOAD')) {
this.manifest.serverControl['CAN-BLOCK-RELOAD'] = false;
this.trigger('info', {
message: '#EXT-X-SERVER-CONTROL defaulting CAN-BLOCK-RELOAD to false'
});
}
setHoldBack.call(this, this.manifest);

if (attrs['CAN-SKIP-DATERANGES'] && !attrs.hasOwnProperty('CAN-SKIP-UNTIL')) {
this.trigger('warn', {
message: '#EXT-X-SERVER-CONTROL lacks required attribute CAN-SKIP-UNTIL which is required when CAN-SKIP-DATERANGES is set'
});

}
},
'preload-hint'() {
this.manifest.preloadHints = this.manifest.preloadHints || [];
this.manifest.preloadHints.push(entry.attributes);

const missingAttributes = [];

['TYPE', 'URI'].forEach(function(k) {
if (!entry.attributes.hasOwnProperty(k)) {
missingAttributes.push(k);
}
});

if (missingAttributes.length) {
const index = this.manifest.preloadHints.length - 1;

this.trigger('warn', {
message: `#EXT-X-PRELOAD-HINT #${index} lacks required attribute(s): ${missingAttributes.join(', ')}`
});
}
},
'rendition-report'() {
this.manifest.renditionReports = this.manifest.renditionReports || [];
this.manifest.renditionReports.push(entry.attributes);
const index = this.manifest.renditionReports.length - 1;
const missingAttributes = [];
const warning = `#EXT-X-RENDITION-REPORT #${index} lacks required attribute(s):`;

['LAST-MSN', 'URI'].forEach(function(k) {
if (!entry.attributes.hasOwnProperty(k)) {
missingAttributes.push(k);
}
});

if (this.manifest.parts && !entry.attributes['LAST-PART']) {
missingAttributes.push('LAST-PART');
}

if (missingAttributes.length) {
this.trigger('warn', {message: `${warning} ${missingAttributes.join(', ')}`});
}
},
'part-inf'() {
this.manifest.partInf = entry.attributes;

if (!entry.attributes.hasOwnProperty('PART-TARGET')) {
this.trigger('warn', {
message: '#EXT-X-PART-INF lacks required attribute: PART-TARGET'
});
} else {
this.manifest.partTargetDuration = entry.attributes['PART-TARGET'];
}

setHoldBack.call(this, this.manifest);
}
})[entry.tagType] || noop).call(self);
},
Expand Down
5 changes: 3 additions & 2 deletions test/fixtures/integration/llhls.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module.exports = {
mediaSequence: 266,
preloadHints: [
{TYPE: 'PART', URI: 'filePart273.3.mp4'},
{TYPE: 'PART', URI: 'filePart273.4.mp4'}
{'TYPE': 'PART', 'URI': 'filePart273.4.mp4', 'BYTERANGE-LENGTH': 10, 'BYTERANGE-START': 0}
],
renditionReports: [
{'LAST-MSN': 273, 'LAST-PART': 2, 'URI': '../1M/waitForMSN.php'},
Expand All @@ -16,6 +16,7 @@ module.exports = {
partInf: {
'PART-TARGET': 0.33334
},
partTargetDuration: 0.33334,
parts: [
{
DURATION: 0.33334,
Expand Down Expand Up @@ -199,7 +200,7 @@ module.exports = {
'CAN-BLOCK-RELOAD': true,
'CAN-SKIP-UNTIL': 12,
'PART-HOLD-BACK': 1,
'HOLD-BACK': 2
'HOLD-BACK': 12
},
targetDuration: 4
};
Loading

0 comments on commit e721ead

Please sign in to comment.