mirror of
https://github.com/videojs/m3u8-parser.git
synced 2026-06-06 08:22:33 +00:00
Compare commits
7 commits
feature/ad
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26d880375e | ||
|
|
c060bc7338 |
||
|
|
ba6e7cbafe |
||
|
|
e5dbdb6288 |
||
|
|
3f49bb4331 |
||
|
|
990c6ced71 |
||
|
|
f8c9817a95 |
70 changed files with 1111 additions and 30 deletions
14
CHANGELOG.md
14
CHANGELOG.md
|
|
@ -1,3 +1,17 @@
|
|||
<a name="7.2.0"></a>
|
||||
# [7.2.0](https://github.com/videojs/m3u8-parser/compare/v7.1.0...v7.2.0) (2024-08-21)
|
||||
|
||||
### Features
|
||||
|
||||
* add support for #EXT-X-DEFINE ([#185](https://github.com/videojs/m3u8-parser/issues/185)) ([ba6e7cb](https://github.com/videojs/m3u8-parser/commit/ba6e7cb))
|
||||
* add support for #EXT-X-I-FRAME-STREAM-INF ([#171](https://github.com/videojs/m3u8-parser/issues/171)) ([990c6ce](https://github.com/videojs/m3u8-parser/commit/990c6ce)), closes [/datatracker.ietf.org/doc/html/rfc8216#section-4](https://github.com//datatracker.ietf.org/doc/html/rfc8216/issues/section-4) [/datatracker.ietf.org/doc/html/rfc8216#section-4](https://github.com//datatracker.ietf.org/doc/html/rfc8216/issues/section-4)
|
||||
* add support for #EXT-X-I-FRAMES-ONLY ([#173](https://github.com/videojs/m3u8-parser/issues/173)) ([e5dbdb6](https://github.com/videojs/m3u8-parser/commit/e5dbdb6)), closes [/datatracker.ietf.org/doc/html/rfc8216#section-4](https://github.com//datatracker.ietf.org/doc/html/rfc8216/issues/section-4) [#171](https://github.com/videojs/m3u8-parser/issues/171)
|
||||
|
||||
### Chores
|
||||
|
||||
* add content-steering tag to readme ([#177](https://github.com/videojs/m3u8-parser/issues/177)) ([f8c9817](https://github.com/videojs/m3u8-parser/commit/f8c9817))
|
||||
* update vhs-utils dependency ([#182](https://github.com/videojs/m3u8-parser/issues/182)) ([c060bc7](https://github.com/videojs/m3u8-parser/commit/c060bc7))
|
||||
|
||||
<a name="7.1.0"></a>
|
||||
# [7.1.0](https://github.com/videojs/m3u8-parser/compare/v7.0.0...v7.1.0) (2023-08-07)
|
||||
|
||||
|
|
|
|||
27
README.md
27
README.md
|
|
@ -13,12 +13,13 @@ m3u8 parser
|
|||
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [Constructor Options](#constructor-options)
|
||||
- [Parsed Output](#parsed-output)
|
||||
- [Supported Tags](#supported-tags)
|
||||
- [Basic Playlist Tags](#basic-playlist-tags)
|
||||
- [Media Segment Tags](#media-segment-tags)
|
||||
- [Media Playlist Tags](#media-playlist-tags)
|
||||
- [Master Playlist Tags](#master-playlist-tags)
|
||||
- [Main Playlist Tags](#main-playlist-tags)
|
||||
- [Experimental Tags](#experimental-tags)
|
||||
- [EXT-X-CUE-OUT](#ext-x-cue-out)
|
||||
- [EXT-X-CUE-OUT-CONT](#ext-x-cue-out-cont)
|
||||
|
|
@ -70,6 +71,21 @@ parser.end();
|
|||
|
||||
var parsedManifest = parser.manifest;
|
||||
```
|
||||
### Constructor Options
|
||||
|
||||
The constructor optinally takes an options object with two properties. These are needed when using `#EXT-X-DEFINE` for variable replacement.
|
||||
|
||||
```js
|
||||
var parser = new m3u8Parser.Parser({
|
||||
url: 'https://exmaple.com/video.m3u8?param_a=34¶m_b=abc',
|
||||
mainDefinitions: {
|
||||
param_c: 'def'
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
* `options.url` _string_ The URL from which the playlist was fetched. If the request was redirected this should be the final URL. This is required if using `QUERYSTRING` rules with `#EXT-X-DEFINE`.
|
||||
* `options.mainDefinitions` _object_ An object of definitions from the main playlist. This is required if using `IMPORT` rules with `#EXT-X-DEFINE`.
|
||||
|
||||
### Parsed Output
|
||||
|
||||
|
|
@ -163,6 +179,7 @@ Manifest {
|
|||
* [EXT-X-MAP](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.2.5)
|
||||
* [EXT-X-PROGRAM-DATE-TIME](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.2.6)
|
||||
* [EXT-X-DATERANGE](https://datatracker.ietf.org/doc/html/draft-pantos-http-live-streaming-23#section-4.3.2.7)
|
||||
* [EXT-X-I-FRAMES-ONLY](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.3.6)
|
||||
|
||||
### Media Playlist Tags
|
||||
|
||||
|
|
@ -173,11 +190,15 @@ Manifest {
|
|||
* [EXT-X-PLAYLIST-TYPE](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.3.5)
|
||||
* [EXT-X-START](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.5.2)
|
||||
* [EXT-X-INDEPENDENT-SEGMENTS](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.5.1)
|
||||
* [EXT-X-DEFINE](https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.2.3)
|
||||
|
||||
### Master Playlist Tags
|
||||
### Main Playlist Tags
|
||||
|
||||
* [EXT-X-MEDIA](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.1)
|
||||
* [EXT-X-STREAM-INF](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.2)
|
||||
* [EXT-X-I-FRAME-STREAM-INF](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.3)
|
||||
* [EXT-X-CONTENT-STEERING](https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.6.6)
|
||||
* [EXT-X-DEFINE](https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.2.3)
|
||||
|
||||
### Experimental Tags
|
||||
|
||||
|
|
@ -242,8 +263,6 @@ Example media playlist using `EXT-X-CUE-` tags.
|
|||
|
||||
### Not Yet Supported
|
||||
|
||||
* [EXT-X-I-FRAMES-ONLY](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.3.6)
|
||||
* [EXT-X-I-FRAME-STREAM-INF](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.3)
|
||||
* [EXT-X-SESSION-DATA](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.4)
|
||||
* [EXT-X-SESSION-KEY](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.5)
|
||||
|
||||
|
|
|
|||
16
package-lock.json
generated
16
package-lock.json
generated
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "m3u8-parser",
|
||||
"version": "7.1.0",
|
||||
"version": "7.2.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
@ -1579,13 +1579,12 @@
|
|||
}
|
||||
},
|
||||
"@videojs/vhs-utils": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz",
|
||||
"integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.1.1.tgz",
|
||||
"integrity": "sha512-5iLX6sR2ownbv4Mtejw6Ax+naosGvoT9kY+gcuHzANyUZZ+4NpeNdKMUhb6ag0acYej1Y7cmr/F2+4PrggMiVA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"global": "^4.4.0",
|
||||
"url-toolkit": "^2.2.1"
|
||||
"global": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"JSONStream": {
|
||||
|
|
@ -8813,11 +8812,6 @@
|
|||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"url-toolkit": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz",
|
||||
"integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg=="
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "m3u8-parser",
|
||||
"version": "7.1.0",
|
||||
"version": "7.2.0",
|
||||
"description": "m3u8 parser",
|
||||
"main": "dist/m3u8-parser.cjs.js",
|
||||
"module": "dist/m3u8-parser.es.js",
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"@videojs/vhs-utils": "^4.1.1",
|
||||
"global": "^4.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -71,6 +71,29 @@ const parseAttributes = function(attributes) {
|
|||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a string into a resolution object
|
||||
*
|
||||
* @param {string} resolution a string such as 3840x2160
|
||||
*
|
||||
* @return {Object} An object representing the resolution
|
||||
*
|
||||
*/
|
||||
const parseResolution = (resolution) => {
|
||||
const split = resolution.split('x');
|
||||
const result = {};
|
||||
|
||||
if (split[0]) {
|
||||
result.width = parseInt(split[0], 10);
|
||||
}
|
||||
|
||||
if (split[1]) {
|
||||
result.height = parseInt(split[1], 10);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* A line-level M3U8 parser event stream. It expects to receive input one
|
||||
* line at a time and performs a context-free parse of its contents. A stream
|
||||
|
|
@ -296,16 +319,7 @@ export default class ParseStream extends Stream {
|
|||
event.attributes = parseAttributes(match[1]);
|
||||
|
||||
if (event.attributes.RESOLUTION) {
|
||||
const split = event.attributes.RESOLUTION.split('x');
|
||||
const resolution = {};
|
||||
|
||||
if (split[0]) {
|
||||
resolution.width = parseInt(split[0], 10);
|
||||
}
|
||||
if (split[1]) {
|
||||
resolution.height = parseInt(split[1], 10);
|
||||
}
|
||||
event.attributes.RESOLUTION = resolution;
|
||||
event.attributes.RESOLUTION = parseResolution(event.attributes.RESOLUTION);
|
||||
}
|
||||
if (event.attributes.BANDWIDTH) {
|
||||
event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
|
||||
|
|
@ -429,7 +443,7 @@ export default class ParseStream extends Stream {
|
|||
this.trigger('data', event);
|
||||
return;
|
||||
}
|
||||
match = (/^#EXT-X-CUE-IN:(.*)?$/).exec(newLine);
|
||||
match = (/^#EXT-X-CUE-IN:?(.*)?$/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
|
|
@ -624,6 +638,16 @@ export default class ParseStream extends Stream {
|
|||
});
|
||||
return;
|
||||
}
|
||||
|
||||
match = (/^#EXT-X-I-FRAMES-ONLY/).exec(newLine);
|
||||
if (match) {
|
||||
this.trigger('data', {
|
||||
type: 'tag',
|
||||
tagType: 'i-frames-only'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
match = (/^#EXT-X-CONTENT-STEERING:(.*)$/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
|
|
@ -632,6 +656,51 @@ export default class ParseStream extends Stream {
|
|||
};
|
||||
event.attributes = parseAttributes(match[1]);
|
||||
this.trigger('data', event);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
match = (/^#EXT-X-I-FRAME-STREAM-INF:(.*)$/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
tagType: 'i-frame-playlist'
|
||||
};
|
||||
|
||||
event.attributes = parseAttributes(match[1]);
|
||||
|
||||
if (event.attributes.URI) {
|
||||
event.uri = event.attributes.URI;
|
||||
}
|
||||
|
||||
if (event.attributes.BANDWIDTH) {
|
||||
event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
|
||||
}
|
||||
|
||||
if (event.attributes.RESOLUTION) {
|
||||
event.attributes.RESOLUTION = parseResolution(event.attributes.RESOLUTION);
|
||||
}
|
||||
|
||||
if (event.attributes['AVERAGE-BANDWIDTH']) {
|
||||
event.attributes['AVERAGE-BANDWIDTH'] = parseInt(event.attributes['AVERAGE-BANDWIDTH'], 10);
|
||||
}
|
||||
|
||||
if (event.attributes['FRAME-RATE']) {
|
||||
event.attributes['FRAME-RATE'] = parseFloat(event.attributes['FRAME-RATE']);
|
||||
}
|
||||
|
||||
this.trigger('data', event);
|
||||
|
||||
return;
|
||||
}
|
||||
match = (/^#EXT-X-DEFINE:(.*)$/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
tagType: 'define'
|
||||
};
|
||||
event.attributes = parseAttributes(match[1]);
|
||||
this.trigger('data', event);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
146
src/parser.js
146
src/parser.js
|
|
@ -88,15 +88,19 @@ const setHoldBack = function(manifest) {
|
|||
* requires some property of the manifest object to be defaulted.
|
||||
*
|
||||
* @class Parser
|
||||
* @param {Object} [opts] Options for the constructor, needed for substitutions
|
||||
* @param {string} [opts.uri] URL to check for query params
|
||||
* @param {Object} [opts.mainDefinitions] Definitions on main playlist that can be imported
|
||||
* @extends Stream
|
||||
*/
|
||||
export default class Parser extends Stream {
|
||||
constructor() {
|
||||
constructor(opts = {}) {
|
||||
super();
|
||||
this.lineStream = new LineStream();
|
||||
this.parseStream = new ParseStream();
|
||||
this.lineStream.pipe(this.parseStream);
|
||||
|
||||
this.mainDefinitions = opts.mainDefinitions || {};
|
||||
this.params = new URL(opts.uri, 'https://a.com').searchParams;
|
||||
this.lastProgramDateTime = null;
|
||||
|
||||
/* eslint-disable consistent-this */
|
||||
|
|
@ -127,6 +131,7 @@ export default class Parser extends Stream {
|
|||
allowCache: true,
|
||||
discontinuityStarts: [],
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
segments: []
|
||||
};
|
||||
// keep track of the last seen segment's byte range end, as segments are not required
|
||||
|
|
@ -163,6 +168,22 @@ export default class Parser extends Stream {
|
|||
let mediaGroup;
|
||||
let rendition;
|
||||
|
||||
// Replace variables in uris and attributes as defined in #EXT-X-DEFINE tags
|
||||
if (self.manifest.definitions) {
|
||||
for (const def in self.manifest.definitions) {
|
||||
if (entry.uri) {
|
||||
entry.uri = entry.uri.replace(`{$${def}}`, self.manifest.definitions[def]);
|
||||
}
|
||||
if (entry.attributes) {
|
||||
for (const attr in entry.attributes) {
|
||||
if (typeof entry.attributes[attr] === 'string') {
|
||||
entry.attributes[attr] = entry.attributes[attr].replace(`{$${def}}`, self.manifest.definitions[def]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
({
|
||||
tag() {
|
||||
// switch based on the tag type
|
||||
|
|
@ -723,6 +744,11 @@ export default class Parser extends Stream {
|
|||
'independent-segments'() {
|
||||
this.manifest.independentSegments = true;
|
||||
},
|
||||
'i-frames-only'() {
|
||||
this.manifest.iFramesOnly = true;
|
||||
|
||||
this.requiredCompatibilityversion(this.manifest.version, 4);
|
||||
},
|
||||
'content-steering'() {
|
||||
this.manifest.contentSteering = camelCaseKeys(entry.attributes);
|
||||
this.warnOnMissingAttributes_(
|
||||
|
|
@ -730,7 +756,115 @@ export default class Parser extends Stream {
|
|||
entry.attributes,
|
||||
['SERVER-URI']
|
||||
);
|
||||
},
|
||||
|
||||
/** @this {Parser} */
|
||||
define() {
|
||||
this.manifest.definitions = this.manifest.definitions || { };
|
||||
|
||||
const addDef = (n, v) => {
|
||||
if (n in this.manifest.definitions) {
|
||||
// An EXT-X-DEFINE tag MUST NOT specify the same Variable Name as any other
|
||||
// EXT-X-DEFINE tag in the same Playlist. Parsers that encounter duplicate
|
||||
// Variable Name declarations MUST fail to parse the Playlist.
|
||||
this.trigger('error', {
|
||||
message: `EXT-X-DEFINE: Duplicate name ${n}`
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.manifest.definitions[n] = v;
|
||||
};
|
||||
|
||||
if ('QUERYPARAM' in entry.attributes) {
|
||||
if ('NAME' in entry.attributes || 'IMPORT' in entry.attributes) {
|
||||
// An EXT-X-DEFINE tag MUST contain either a NAME, an IMPORT, or a
|
||||
// QUERYPARAM attribute, but only one of the three. Otherwise, the
|
||||
// client MUST fail to parse the Playlist.
|
||||
this.trigger('error', {
|
||||
message: 'EXT-X-DEFINE: Invalid attributes'
|
||||
});
|
||||
return;
|
||||
}
|
||||
const val = this.params.get(entry.attributes.QUERYPARAM);
|
||||
|
||||
if (!val) {
|
||||
// If the QUERYPARAM attribute value does not match any query parameter in
|
||||
// the URI or the matching parameter has no associated value, the parser
|
||||
// MUST fail to parse the Playlist. If more than one parameter matches,
|
||||
// any of the associated values MAY be used.
|
||||
this.trigger('error', {
|
||||
message: `EXT-X-DEFINE: No query param ${entry.attributes.QUERYPARAM}`
|
||||
});
|
||||
return;
|
||||
}
|
||||
addDef(entry.attributes.QUERYPARAM, decodeURIComponent(val));
|
||||
return;
|
||||
}
|
||||
|
||||
if ('NAME' in entry.attributes) {
|
||||
if ('IMPORT' in entry.attributes) {
|
||||
// An EXT-X-DEFINE tag MUST contain either a NAME, an IMPORT, or a
|
||||
// QUERYPARAM attribute, but only one of the three. Otherwise, the
|
||||
// client MUST fail to parse the Playlist.
|
||||
this.trigger('error', {
|
||||
message: 'EXT-X-DEFINE: Invalid attributes'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!('VALUE' in entry.attributes) || typeof entry.attributes.VALUE !== 'string') {
|
||||
// This attribute is REQUIRED if the EXT-X-DEFINE tag has a NAME attribute.
|
||||
// The quoted-string MAY be empty.
|
||||
this.trigger('error', {
|
||||
message: `EXT-X-DEFINE: No value for ${entry.attributes.NAME}`
|
||||
});
|
||||
return;
|
||||
}
|
||||
addDef(entry.attributes.NAME, entry.attributes.VALUE);
|
||||
return;
|
||||
}
|
||||
|
||||
if ('IMPORT' in entry.attributes) {
|
||||
if (!this.mainDefinitions[entry.attributes.IMPORT]) {
|
||||
// Covers two conditions, as mainDefinitions will always be empty on main
|
||||
//
|
||||
// EXT-X-DEFINE tags containing the IMPORT attribute MUST NOT occur in
|
||||
// Multivariant Playlists; they are only allowed in Media Playlists.
|
||||
//
|
||||
// If the IMPORT attribute value does not match any Variable Name in the
|
||||
// Multivariant Playlist, or if the Media Playlist loaded from a
|
||||
// Multivariant Playlist, the parser MUST fail the Playlist.
|
||||
this.trigger('error', {
|
||||
message: `EXT-X-DEFINE: No value ${entry.attributes.IMPORT} to import, or IMPORT used on main playlist`
|
||||
});
|
||||
return;
|
||||
}
|
||||
addDef(entry.attributes.IMPORT, this.mainDefinitions[entry.attributes.IMPORT]);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// An EXT-X-DEFINE tag MUST contain either a NAME, an IMPORT, or a QUERYPARAM
|
||||
// attribute, but only one of the three. Otherwise, the client MUST fail to
|
||||
// parse the Playlist.
|
||||
this.trigger('error', {
|
||||
message: 'EXT-X-DEFINE: No attribute'
|
||||
});
|
||||
},
|
||||
|
||||
'i-frame-playlist'() {
|
||||
this.manifest.iFramePlaylists.push({
|
||||
attributes: entry.attributes,
|
||||
uri: entry.uri,
|
||||
timeline: currentTimeline
|
||||
});
|
||||
|
||||
this.warnOnMissingAttributes_(
|
||||
'#EXT-X-I-FRAME-STREAM-INF',
|
||||
entry.attributes,
|
||||
['BANDWIDTH', 'URI']
|
||||
);
|
||||
}
|
||||
|
||||
})[entry.tagType] || noop).call(self);
|
||||
},
|
||||
uri() {
|
||||
|
|
@ -784,6 +918,14 @@ export default class Parser extends Stream {
|
|||
});
|
||||
}
|
||||
|
||||
requiredCompatibilityversion(currentVersion, targetVersion) {
|
||||
if (currentVersion < targetVersion || !currentVersion) {
|
||||
this.trigger('warn', {
|
||||
message: `manifest must be at least version ${targetVersion}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
warnOnMissingAttributes_(identifier, attributes, required) {
|
||||
const missing = [];
|
||||
|
||||
|
|
|
|||
1
test/fixtures/integration/absoluteUris.js
vendored
1
test/fixtures/integration/absoluteUris.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
1
test/fixtures/integration/allowCache.js
vendored
1
test/fixtures/integration/allowCache.js
vendored
|
|
@ -1,5 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
dateRanges: [],
|
||||
playlistType: 'VOD',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
dateRanges: [],
|
||||
playlistType: 'VOD',
|
||||
|
|
|
|||
1
test/fixtures/integration/alternateAudio.js
vendored
1
test/fixtures/integration/alternateAudio.js
vendored
|
|
@ -2,6 +2,7 @@ module.exports = {
|
|||
allowCache: true,
|
||||
discontinuityStarts: [],
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaGroups: {
|
||||
// TYPE
|
||||
'AUDIO': {
|
||||
|
|
|
|||
1
test/fixtures/integration/alternateVideo.js
vendored
1
test/fixtures/integration/alternateVideo.js
vendored
|
|
@ -2,6 +2,7 @@ module.exports = {
|
|||
allowCache: true,
|
||||
discontinuityStarts: [],
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaGroups: {
|
||||
'AUDIO': {
|
||||
aac: {
|
||||
|
|
|
|||
1
test/fixtures/integration/brightcove.js
vendored
1
test/fixtures/integration/brightcove.js
vendored
|
|
@ -1,5 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
iFramePlaylists: [],
|
||||
dateRanges: [],
|
||||
playlists: [
|
||||
{
|
||||
|
|
|
|||
1
test/fixtures/integration/byteRange.js
vendored
1
test/fixtures/integration/byteRange.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
1
test/fixtures/integration/dateTime.js
vendored
1
test/fixtures/integration/dateTime.js
vendored
|
|
@ -1,5 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: false,
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
dateRanges: [],
|
||||
playlistType: 'VOD',
|
||||
|
|
|
|||
1
test/fixtures/integration/diff-init-key.js
vendored
1
test/fixtures/integration/diff-init-key.js
vendored
|
|
@ -3,6 +3,7 @@ module.exports = {
|
|||
discontinuitySequence: 0,
|
||||
discontinuityStarts: [],
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 7794,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
1
test/fixtures/integration/disallowCache.js
vendored
1
test/fixtures/integration/disallowCache.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: false,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
1
test/fixtures/integration/disc-sequence.js
vendored
1
test/fixtures/integration/disc-sequence.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
discontinuitySequence: 3,
|
||||
segments: [
|
||||
|
|
|
|||
1
test/fixtures/integration/discontinuity.js
vendored
1
test/fixtures/integration/discontinuity.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
discontinuitySequence: 0,
|
||||
segments: [
|
||||
|
|
|
|||
1
test/fixtures/integration/domainUris.js
vendored
1
test/fixtures/integration/domainUris.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
1
test/fixtures/integration/empty.js
vendored
1
test/fixtures/integration/empty.js
vendored
|
|
@ -2,5 +2,6 @@ module.exports = {
|
|||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
segments: []
|
||||
};
|
||||
|
|
|
|||
1
test/fixtures/integration/emptyAllowCache.js
vendored
1
test/fixtures/integration/emptyAllowCache.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
playlists: [
|
||||
{
|
||||
attributes: {
|
||||
|
|
|
|||
1
test/fixtures/integration/encrypted.js
vendored
1
test/fixtures/integration/encrypted.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 7794,
|
||||
discontinuitySequence: 0,
|
||||
discontinuityStarts: [],
|
||||
|
|
|
|||
1
test/fixtures/integration/event.js
vendored
1
test/fixtures/integration/event.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'EVENT',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 1,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
1
test/fixtures/integration/extinf.js
vendored
1
test/fixtures/integration/extinf.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
1
test/fixtures/integration/fmp4.js
vendored
1
test/fixtures/integration/fmp4.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 1,
|
||||
playlistType: 'VOD',
|
||||
targetDuration: 6,
|
||||
|
|
|
|||
1
test/fixtures/integration/headerOnly.js
vendored
1
test/fixtures/integration/headerOnly.js
vendored
|
|
@ -2,5 +2,6 @@ module.exports = {
|
|||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
segments: []
|
||||
};
|
||||
|
|
|
|||
288
test/fixtures/integration/iFramePlaylist.js
vendored
Normal file
288
test/fixtures/integration/iFramePlaylist.js
vendored
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [
|
||||
{
|
||||
attributes: {
|
||||
'AVERAGE-BANDWIDTH': 248586,
|
||||
'BANDWIDTH': 593626,
|
||||
'CODECS': 'hvc1.2.4.L123.B0',
|
||||
'HDCP-LEVEL': 'NONE',
|
||||
'RESOLUTION': { width: 1280, height: 720 },
|
||||
'URI': 'sdr_720/iframe_index.m3u8',
|
||||
'VIDEO-RANGE': 'SDR'
|
||||
},
|
||||
timeline: 0,
|
||||
uri: 'sdr_720/iframe_index.m3u8'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'AVERAGE-BANDWIDTH': 399790,
|
||||
'BANDWIDTH': 956552,
|
||||
'CODECS': 'hvc1.2.4.L123.B0',
|
||||
'HDCP-LEVEL': 'TYPE-0',
|
||||
'RESOLUTION': { width: 1920, height: 1080 },
|
||||
'URI': 'sdr_1080/iframe_index.m3u8',
|
||||
'VIDEO-RANGE': 'SDR'
|
||||
},
|
||||
timeline: 0,
|
||||
uri: 'sdr_1080/iframe_index.m3u8'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'AVERAGE-BANDWIDTH': 826971,
|
||||
'BANDWIDTH': 1941397,
|
||||
'CODECS': 'hvc1.2.4.L150.B0',
|
||||
'HDCP-LEVEL': 'TYPE-1',
|
||||
'RESOLUTION': { width: 3840, height: 2160 },
|
||||
'URI': 'sdr_2160/iframe_index.m3u8',
|
||||
'VIDEO-RANGE': 'SDR'
|
||||
},
|
||||
timeline: 0,
|
||||
uri: 'sdr_2160/iframe_index.m3u8'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'AVERAGE-BANDWIDTH': 232253,
|
||||
'BANDWIDTH': 573073,
|
||||
'CODECS': 'dvh1.05.01',
|
||||
'HDCP-LEVEL': 'NONE',
|
||||
'RESOLUTION': { width: 1280, height: 720 },
|
||||
'URI': 'dolby_720/iframe_index.m3u8',
|
||||
'VIDEO-RANGE': 'PQ'
|
||||
},
|
||||
timeline: 0,
|
||||
uri: 'dolby_720/iframe_index.m3u8'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'AVERAGE-BANDWIDTH': 365337,
|
||||
'BANDWIDTH': 905037,
|
||||
'CODECS': 'dvh1.05.03',
|
||||
'HDCP-LEVEL': 'TYPE-0',
|
||||
'RESOLUTION': { width: 1920, height: 1080 },
|
||||
'URI': 'dolby_1080/iframe_index.m3u8',
|
||||
'VIDEO-RANGE': 'PQ'
|
||||
},
|
||||
timeline: 0,
|
||||
uri: 'dolby_1080/iframe_index.m3u8'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'AVERAGE-BANDWIDTH': 739114,
|
||||
'BANDWIDTH': 1893236,
|
||||
'CODECS': 'dvh1.05.06',
|
||||
'HDCP-LEVEL': 'TYPE-1',
|
||||
'RESOLUTION': { width: 3840, height: 2160 },
|
||||
'URI': 'dolby_2160/iframe_index.m3u8',
|
||||
'VIDEO-RANGE': 'PQ'
|
||||
},
|
||||
timeline: 0,
|
||||
uri: 'dolby_2160/iframe_index.m3u8'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'AVERAGE-BANDWIDTH': 232511,
|
||||
'BANDWIDTH': 572673,
|
||||
'CODECS': 'hvc1.2.4.L123.B0',
|
||||
'HDCP-LEVEL': 'NONE',
|
||||
'RESOLUTION': { width: 1280, height: 720 },
|
||||
'URI': 'hdr10_720/iframe_index.m3u8',
|
||||
'VIDEO-RANGE': 'PQ'
|
||||
},
|
||||
timeline: 0,
|
||||
uri: 'hdr10_720/iframe_index.m3u8'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'AVERAGE-BANDWIDTH': 364552,
|
||||
'BANDWIDTH': 905053,
|
||||
'CODECS': 'hvc1.2.4.L123.B0',
|
||||
'HDCP-LEVEL': 'TYPE-0',
|
||||
'RESOLUTION': { width: 1920, height: 1080 },
|
||||
'URI': 'hdr10_1080/iframe_index.m3u8',
|
||||
'VIDEO-RANGE': 'PQ'
|
||||
},
|
||||
timeline: 0,
|
||||
uri: 'hdr10_1080/iframe_index.m3u8'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'AVERAGE-BANDWIDTH': 739757,
|
||||
'BANDWIDTH': 1895477,
|
||||
'CODECS': 'hvc1.2.4.L150.B0',
|
||||
'HDCP-LEVEL': 'TYPE-1',
|
||||
'RESOLUTION': { width: 3840, height: 2160 },
|
||||
'URI': 'hdr10_2160/iframe_index.m3u8',
|
||||
'VIDEO-RANGE': 'PQ'
|
||||
},
|
||||
timeline: 0,
|
||||
uri: 'hdr10_2160/iframe_index.m3u8'
|
||||
}
|
||||
],
|
||||
independentSegments: true,
|
||||
mediaGroups: {
|
||||
'AUDIO': {},
|
||||
'CLOSED-CAPTIONS': {},
|
||||
'SUBTITLES': {},
|
||||
'VIDEO': {}
|
||||
},
|
||||
playlists: [
|
||||
{
|
||||
attributes: {
|
||||
'HDCP-LEVEL': 'NONE',
|
||||
'CLOSED-CAPTIONS': 'NONE',
|
||||
'FRAME-RATE': 23.976,
|
||||
'RESOLUTION': {
|
||||
width: 1280,
|
||||
height: 720
|
||||
},
|
||||
'CODECS': 'hvc1.2.4.L123.B0',
|
||||
'VIDEO-RANGE': 'SDR',
|
||||
'BANDWIDTH': 3971374,
|
||||
'AVERAGE-BANDWIDTH': '2778321'
|
||||
},
|
||||
uri: 'sdr_720/prog_index.m3u8',
|
||||
timeline: 0
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'HDCP-LEVEL': 'TYPE-0',
|
||||
'CLOSED-CAPTIONS': 'NONE',
|
||||
'FRAME-RATE': 23.976,
|
||||
'RESOLUTION': {
|
||||
width: 1920,
|
||||
height: 1080
|
||||
},
|
||||
'CODECS': 'hvc1.2.4.L123.B0',
|
||||
'VIDEO-RANGE': 'SDR',
|
||||
'BANDWIDTH': 10022043,
|
||||
'AVERAGE-BANDWIDTH': '6759875'
|
||||
},
|
||||
uri: 'sdr_1080/prog_index.m3u8',
|
||||
timeline: 0
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'HDCP-LEVEL': 'TYPE-1',
|
||||
'CLOSED-CAPTIONS': 'NONE',
|
||||
'FRAME-RATE': 23.976,
|
||||
'RESOLUTION': {
|
||||
width: 3840,
|
||||
height: 2160
|
||||
},
|
||||
'CODECS': 'hvc1.2.4.L150.B0',
|
||||
'VIDEO-RANGE': 'SDR',
|
||||
'BANDWIDTH': 28058971,
|
||||
'AVERAGE-BANDWIDTH': '20985770'
|
||||
},
|
||||
uri: 'sdr_2160/prog_index.m3u8',
|
||||
timeline: 0
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'HDCP-LEVEL': 'NONE',
|
||||
'CLOSED-CAPTIONS': 'NONE',
|
||||
'FRAME-RATE': 23.976,
|
||||
'RESOLUTION': {
|
||||
width: 1280,
|
||||
height: 720
|
||||
},
|
||||
'CODECS': 'dvh1.05.01',
|
||||
'VIDEO-RANGE': 'PQ',
|
||||
'BANDWIDTH': 5327059,
|
||||
'AVERAGE-BANDWIDTH': '3385450'
|
||||
},
|
||||
uri: 'dolby_720/prog_index.m3u8',
|
||||
timeline: 0
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'HDCP-LEVEL': 'TYPE-0',
|
||||
'CLOSED-CAPTIONS': 'NONE',
|
||||
'FRAME-RATE': 23.976,
|
||||
'RESOLUTION': {
|
||||
width: 1920,
|
||||
height: 1080
|
||||
},
|
||||
'CODECS': 'dvh1.05.03',
|
||||
'VIDEO-RANGE': 'PQ',
|
||||
'BANDWIDTH': 12876596,
|
||||
'AVERAGE-BANDWIDTH': '7999361'
|
||||
},
|
||||
uri: 'dolby_1080/prog_index.m3u8',
|
||||
timeline: 0
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'HDCP-LEVEL': 'TYPE-1',
|
||||
'CLOSED-CAPTIONS': 'NONE',
|
||||
'FRAME-RATE': 23.976,
|
||||
'RESOLUTION': {
|
||||
width: 3840,
|
||||
height: 2160
|
||||
},
|
||||
'CODECS': 'dvh1.05.06',
|
||||
'VIDEO-RANGE': 'PQ',
|
||||
'BANDWIDTH': 30041698,
|
||||
'AVERAGE-BANDWIDTH': '24975091'
|
||||
},
|
||||
uri: 'dolby_2160/prog_index.m3u8',
|
||||
timeline: 0
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'HDCP-LEVEL': 'NONE',
|
||||
'CLOSED-CAPTIONS': 'NONE',
|
||||
'FRAME-RATE': 23.976,
|
||||
'RESOLUTION': {
|
||||
width: 1280,
|
||||
height: 720
|
||||
},
|
||||
'CODECS': 'hvc1.2.4.L123.B0',
|
||||
'VIDEO-RANGE': 'PQ',
|
||||
'BANDWIDTH': 5280654,
|
||||
'AVERAGE-BANDWIDTH': '3320040'
|
||||
},
|
||||
uri: 'hdr10_720/prog_index.m3u8',
|
||||
timeline: 0
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'HDCP-LEVEL': 'TYPE-0',
|
||||
'CLOSED-CAPTIONS': 'NONE',
|
||||
'FRAME-RATE': 23.976,
|
||||
'RESOLUTION': {
|
||||
width: 1920,
|
||||
height: 1080
|
||||
},
|
||||
'CODECS': 'hvc1.2.4.L123.B0',
|
||||
'VIDEO-RANGE': 'PQ',
|
||||
'BANDWIDTH': 12886714,
|
||||
'AVERAGE-BANDWIDTH': '7964551'
|
||||
},
|
||||
uri: 'hdr10_1080/prog_index.m3u8',
|
||||
timeline: 0
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'HDCP-LEVEL': 'TYPE-1',
|
||||
'CLOSED-CAPTIONS': 'NONE',
|
||||
'FRAME-RATE': 23.976,
|
||||
'RESOLUTION': {
|
||||
width: 3840,
|
||||
height: 2160
|
||||
},
|
||||
'CODECS': 'hvc1.2.4.L150.B0',
|
||||
'VIDEO-RANGE': 'PQ',
|
||||
'BANDWIDTH': 29983769,
|
||||
'AVERAGE-BANDWIDTH': '24833402'
|
||||
},
|
||||
uri: 'hdr10_2160/prog_index.m3u8',
|
||||
timeline: 0
|
||||
}
|
||||
],
|
||||
segments: [],
|
||||
version: 7
|
||||
};
|
||||
42
test/fixtures/integration/iFramePlaylist.m3u8
vendored
Normal file
42
test/fixtures/integration/iFramePlaylist.m3u8
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:7
|
||||
#EXT-X-INDEPENDENT-SEGMENTS
|
||||
|
||||
# https://developer.apple.com/documentation/http-live-streaming/hls-authoring-specification-for-apple-devices-appendixes#Example-playlist
|
||||
|
||||
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=248586,BANDWIDTH=593626,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,HDCP-LEVEL=NONE,URI="sdr_720/iframe_index.m3u8"
|
||||
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=399790,BANDWIDTH=956552,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,HDCP-LEVEL=TYPE-0,URI="sdr_1080/iframe_index.m3u8"
|
||||
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=826971,BANDWIDTH=1941397,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="sdr_2160/iframe_index.m3u8"
|
||||
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=232253,BANDWIDTH=573073,VIDEO-RANGE=PQ,CODECS="dvh1.05.01",RESOLUTION=1280x720,HDCP-LEVEL=NONE,URI="dolby_720/iframe_index.m3u8"
|
||||
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=365337,BANDWIDTH=905037,VIDEO-RANGE=PQ,CODECS="dvh1.05.03",RESOLUTION=1920x1080,HDCP-LEVEL=TYPE-0,URI="dolby_1080/iframe_index.m3u8"
|
||||
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=739114,BANDWIDTH=1893236,VIDEO-RANGE=PQ,CODECS="dvh1.05.06",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="dolby_2160/iframe_index.m3u8"
|
||||
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=232511,BANDWIDTH=572673,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,HDCP-LEVEL=NONE,URI="hdr10_720/iframe_index.m3u8"
|
||||
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=364552,BANDWIDTH=905053,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,HDCP-LEVEL=TYPE-0,URI="hdr10_1080/iframe_index.m3u8"
|
||||
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=739757,BANDWIDTH=1895477,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="hdr10_2160/iframe_index.m3u8"
|
||||
|
||||
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=2778321,BANDWIDTH=3971374,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=NONE
|
||||
sdr_720/prog_index.m3u8
|
||||
|
||||
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=6759875,BANDWIDTH=10022043,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-0
|
||||
sdr_1080/prog_index.m3u8
|
||||
|
||||
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=20985770,BANDWIDTH=28058971,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-1
|
||||
sdr_2160/prog_index.m3u8
|
||||
|
||||
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=3385450,BANDWIDTH=5327059,VIDEO-RANGE=PQ,CODECS="dvh1.05.01",RESOLUTION=1280x720,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=NONE
|
||||
dolby_720/prog_index.m3u8
|
||||
|
||||
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=7999361,BANDWIDTH=12876596,VIDEO-RANGE=PQ,CODECS="dvh1.05.03",RESOLUTION=1920x1080,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-0
|
||||
dolby_1080/prog_index.m3u8
|
||||
|
||||
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=24975091,BANDWIDTH=30041698,VIDEO-RANGE=PQ,CODECS="dvh1.05.06",RESOLUTION=3840x2160,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-1
|
||||
dolby_2160/prog_index.m3u8
|
||||
|
||||
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=3320040,BANDWIDTH=5280654,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=NONE
|
||||
hdr10_720/prog_index.m3u8
|
||||
|
||||
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=7964551,BANDWIDTH=12886714,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-0
|
||||
hdr10_1080/prog_index.m3u8
|
||||
|
||||
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=24833402,BANDWIDTH=29983769,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-1
|
||||
hdr10_2160/prog_index.m3u8
|
||||
45
test/fixtures/integration/iFramesOnly.js
vendored
Normal file
45
test/fixtures/integration/iFramesOnly.js
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
iFramesOnly: true,
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
{
|
||||
duration: 2.002,
|
||||
timeline: 0,
|
||||
uri: '001.ts'
|
||||
},
|
||||
{
|
||||
duration: 2.002,
|
||||
timeline: 0,
|
||||
uri: '002.ts'
|
||||
},
|
||||
{
|
||||
duration: 2.002,
|
||||
timeline: 0,
|
||||
uri: '003.ts'
|
||||
},
|
||||
{
|
||||
duration: 2.002,
|
||||
timeline: 0,
|
||||
uri: '004.ts'
|
||||
},
|
||||
{
|
||||
duration: 2.002,
|
||||
timeline: 0,
|
||||
uri: '005.ts'
|
||||
},
|
||||
{
|
||||
duration: 2.002,
|
||||
timeline: 0,
|
||||
uri: '006.ts'
|
||||
}
|
||||
],
|
||||
targetDuration: 3,
|
||||
endList: true,
|
||||
discontinuitySequence: 0,
|
||||
discontinuityStarts: [],
|
||||
version: 4
|
||||
};
|
||||
19
test/fixtures/integration/iFramesOnly.m3u8
vendored
Normal file
19
test/fixtures/integration/iFramesOnly.m3u8
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:4
|
||||
#EXT-X-PLAYLIST-TYPE:VOD
|
||||
#EXT-X-MEDIA-SEQUENCE:0
|
||||
#EXT-X-TARGETDURATION:3
|
||||
#EXT-X-I-FRAMES-ONLY
|
||||
#EXTINF:2.002,
|
||||
001.ts
|
||||
#EXTINF:2.002,
|
||||
002.ts
|
||||
#EXTINF:2.002,
|
||||
003.ts
|
||||
#EXTINF:2.002,
|
||||
004.ts
|
||||
#EXTINF:2.002,
|
||||
005.ts
|
||||
#EXTINF:2.002,
|
||||
006.ts
|
||||
#EXT-X-ENDLIST
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ module.exports = {
|
|||
dateRanges: [],
|
||||
discontinuitySequence: 0,
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
preloadSegment: {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ module.exports = {
|
|||
dateRanges: [],
|
||||
discontinuitySequence: 0,
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
preloadSegment: {
|
||||
|
|
|
|||
1
test/fixtures/integration/llhls.js
vendored
1
test/fixtures/integration/llhls.js
vendored
|
|
@ -5,6 +5,7 @@ module.exports = {
|
|||
dateRanges: [],
|
||||
discontinuitySequence: 0,
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 266,
|
||||
preloadSegment: {
|
||||
map: {uri: 'init.mp4'},
|
||||
|
|
|
|||
1
test/fixtures/integration/llhlsDelta.js
vendored
1
test/fixtures/integration/llhlsDelta.js
vendored
|
|
@ -5,6 +5,7 @@ module.exports = {
|
|||
dateRanges: [],
|
||||
discontinuitySequence: 0,
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 266,
|
||||
preloadSegment: {
|
||||
timeline: 0,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
86
test/fixtures/integration/master-fmp4.js
vendored
86
test/fixtures/integration/master-fmp4.js
vendored
|
|
@ -2,6 +2,92 @@ module.exports = {
|
|||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [
|
||||
{
|
||||
attributes: {
|
||||
'AVERAGE-BANDWIDTH': 163198,
|
||||
'BANDWIDTH': 166942,
|
||||
'CODECS': 'avc1.64002a',
|
||||
'RESOLUTION': {
|
||||
height: 1080,
|
||||
width: 1920
|
||||
},
|
||||
'URI': 'v6/iframe_index.m3u8'
|
||||
},
|
||||
timeline: 0,
|
||||
uri: 'v6/iframe_index.m3u8'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'AVERAGE-BANDWIDTH': 131314,
|
||||
'BANDWIDTH': 139041,
|
||||
'CODECS': 'avc1.640020',
|
||||
'RESOLUTION': {
|
||||
height: 720,
|
||||
width: 1280
|
||||
},
|
||||
'URI': 'v5/iframe_index.m3u8'
|
||||
},
|
||||
timeline: 0,
|
||||
uri: 'v5/iframe_index.m3u8'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'AVERAGE-BANDWIDTH': 100233,
|
||||
'BANDWIDTH': 101724,
|
||||
'CODECS': 'avc1.640020',
|
||||
'RESOLUTION': {
|
||||
height: 540,
|
||||
width: 960
|
||||
},
|
||||
'URI': 'v4/iframe_index.m3u8'
|
||||
},
|
||||
timeline: 0,
|
||||
uri: 'v4/iframe_index.m3u8'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'AVERAGE-BANDWIDTH': 81002,
|
||||
'BANDWIDTH': 84112,
|
||||
'CODECS': 'avc1.64001e',
|
||||
'RESOLUTION': {
|
||||
height: 432,
|
||||
width: 768
|
||||
},
|
||||
'URI': 'v3/iframe_index.m3u8'
|
||||
},
|
||||
timeline: 0,
|
||||
uri: 'v3/iframe_index.m3u8'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'AVERAGE-BANDWIDTH': 64987,
|
||||
'BANDWIDTH': 65835,
|
||||
'CODECS': 'avc1.64001e',
|
||||
'RESOLUTION': {
|
||||
height: 360,
|
||||
width: 640
|
||||
},
|
||||
'URI': 'v2/iframe_index.m3u8'
|
||||
},
|
||||
timeline: 0,
|
||||
uri: 'v2/iframe_index.m3u8'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'AVERAGE-BANDWIDTH': 41547,
|
||||
'BANDWIDTH': 42106,
|
||||
'CODECS': 'avc1.640015',
|
||||
'RESOLUTION': {
|
||||
height: 270,
|
||||
width: 480
|
||||
},
|
||||
'URI': 'v1/iframe_index.m3u8'
|
||||
},
|
||||
timeline: 0,
|
||||
uri: 'v1/iframe_index.m3u8'
|
||||
}
|
||||
],
|
||||
mediaGroups: {
|
||||
'AUDIO': {
|
||||
aud1: {
|
||||
|
|
|
|||
1
test/fixtures/integration/master.js
vendored
1
test/fixtures/integration/master.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
playlists: [
|
||||
{
|
||||
attributes: {
|
||||
|
|
|
|||
1
test/fixtures/integration/media.js
vendored
1
test/fixtures/integration/media.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
1
test/fixtures/integration/mediaSequence.js
vendored
1
test/fixtures/integration/mediaSequence.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
1
test/fixtures/integration/missingEndlist.js
vendored
1
test/fixtures/integration/missingEndlist.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
1
test/fixtures/integration/missingExtinf.js
vendored
1
test/fixtures/integration/missingExtinf.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ module.exports = {
|
|||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaGroups: {
|
||||
'AUDIO': {
|
||||
'audio-lo': {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ module.exports = {
|
|||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaGroups: {
|
||||
'AUDIO': {
|
||||
'audio-lo': {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
targetDuration: 10,
|
||||
segments: [
|
||||
|
|
|
|||
1
test/fixtures/integration/multipleVideo.js
vendored
1
test/fixtures/integration/multipleVideo.js
vendored
|
|
@ -2,6 +2,7 @@ module.exports = {
|
|||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaGroups: {
|
||||
'AUDIO': {
|
||||
aac: {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: -11,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
1
test/fixtures/integration/playlist.js
vendored
1
test/fixtures/integration/playlist.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 17,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
1
test/fixtures/integration/start.js
vendored
1
test/fixtures/integration/start.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
playlists: [
|
||||
{
|
||||
attributes: {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 11,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
1
test/fixtures/integration/versionInvalid.js
vendored
1
test/fixtures/integration/versionInvalid.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
1
test/fixtures/integration/whiteSpace.js
vendored
1
test/fixtures/integration/whiteSpace.js
vendored
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
1
test/fixtures/integration/zeroDuration.js
vendored
1
test/fixtures/integration/zeroDuration.js
vendored
|
|
@ -2,6 +2,7 @@ module.exports = {
|
|||
allowCache: true,
|
||||
mediaSequence: 0,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -997,3 +997,40 @@ QUnit.test('ignores empty lines', function(assert) {
|
|||
|
||||
assert.ok(!event, 'no event is triggered');
|
||||
});
|
||||
|
||||
QUnit.test('parses #EXT-X-I-FRAME-STREAM-INF', function(assert) {
|
||||
const manifest = '#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=739757,BANDWIDTH=1895477,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="hdr10_2160/iframe_index.m3u8"\n';
|
||||
let element;
|
||||
|
||||
this.parseStream.on('data', function(elem) {
|
||||
element = elem;
|
||||
});
|
||||
this.lineStream.push(manifest);
|
||||
|
||||
assert.ok(element, 'an event was triggered');
|
||||
assert.strictEqual(element.type, 'tag', 'the line type is tag');
|
||||
assert.strictEqual(element.tagType, 'i-frame-playlist', 'the tag type is i-frame-playlist');
|
||||
assert.strictEqual(element.uri, 'hdr10_2160/iframe_index.m3u8', 'the uri text is parsed');
|
||||
assert.strictEqual(element.attributes['AVERAGE-BANDWIDTH'], 739757, 'the average bandwidth is parsed');
|
||||
assert.strictEqual(element.attributes.BANDWIDTH, 1895477, 'the bandwidth is parsed');
|
||||
assert.strictEqual(element.attributes['VIDEO-RANGE'], 'PQ', 'the video range is parsed');
|
||||
assert.strictEqual(element.attributes.CODECS, 'hvc1.2.4.L150.B0', 'the codecs is parsed');
|
||||
assert.strictEqual(element.attributes.RESOLUTION.width, 3840, 'the resolution width is parsed');
|
||||
assert.strictEqual(element.attributes.RESOLUTION.height, 2160, 'the resolution height is parsed');
|
||||
assert.strictEqual(element.attributes['HDCP-LEVEL'], 'TYPE-1', 'the HDCP level is parsed');
|
||||
assert.strictEqual(element.attributes.URI, 'hdr10_2160/iframe_index.m3u8', 'the uri text is parsed');
|
||||
});
|
||||
|
||||
QUnit.test('parses #EXT-X-I-FRAMES-ONLY', function(assert) {
|
||||
const manifest = '#EXT-X-I-FRAMES-ONLY\n';
|
||||
let element;
|
||||
|
||||
this.parseStream.on('data', function(elem) {
|
||||
element = elem;
|
||||
});
|
||||
this.lineStream.push(manifest);
|
||||
|
||||
assert.ok(element, 'an event was triggered');
|
||||
assert.strictEqual(element.type, 'tag', 'the line type is tag');
|
||||
assert.strictEqual(element.tagType, 'i-frames-only', 'the tag type is i-frames-only');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1158,6 +1158,126 @@ QUnit.module('m3u8s', function(hooks) {
|
|||
assert.equal(this.parser.manifest.independentSegments, true);
|
||||
});
|
||||
|
||||
QUnit.test('parses #EXT-X-I-FRAME-STREAM-INF', function(assert) {
|
||||
this.parser.push([
|
||||
'#EXTM3U',
|
||||
'#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=86000,URI="low/iframe.m3u8"',
|
||||
'#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=150000,URI="mid/iframe.m3u8"',
|
||||
'#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=550000,URI="hi/iframe.m3u8"',
|
||||
'#EXT-X-STREAM-INF:BANDWIDTH=1280000',
|
||||
'low/audio-video.m3u8',
|
||||
'#EXT-X-STREAM-INF:BANDWIDTH=2560000',
|
||||
'mid/audio-video.m3u8',
|
||||
'#EXT-X-STREAM-INF:BANDWIDTH=7680000',
|
||||
'hi/audio-video.m3u8',
|
||||
'#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS="mp4a.40.5"',
|
||||
'audio-only.m3u8'
|
||||
].join('\n'));
|
||||
this.parser.end();
|
||||
|
||||
assert.equal(this.parser.manifest.iFramePlaylists.length, 3);
|
||||
assert.equal(this.parser.manifest.iFramePlaylists[0].uri, 'low/iframe.m3u8');
|
||||
assert.strictEqual(this.parser.manifest.iFramePlaylists[0].attributes.BANDWIDTH, 86000);
|
||||
});
|
||||
|
||||
QUnit.test('warns when #EXT-X-I-FRAME-STREAM-INF missing BANDWIDTH/URI attributes', function(assert) {
|
||||
this.parser.push([
|
||||
'#EXTM3U',
|
||||
'#EXT-X-I-FRAME-STREAM-INF:URI="low/iframe.m3u8"',
|
||||
'#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=150000,URI="mid/iframe.m3u8"',
|
||||
'#EXT-X-I-FRAME-STREAM-INF:',
|
||||
'#EXT-X-STREAM-INF:BANDWIDTH=1280000',
|
||||
'low/audio-video.m3u8',
|
||||
'#EXT-X-STREAM-INF:BANDWIDTH=2560000',
|
||||
'mid/audio-video.m3u8',
|
||||
'#EXT-X-STREAM-INF:BANDWIDTH=7680000',
|
||||
'hi/audio-video.m3u8',
|
||||
'#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS="mp4a.40.5"',
|
||||
'audio-only.m3u8'
|
||||
].join('\n'));
|
||||
this.parser.end();
|
||||
|
||||
const warnings = [
|
||||
'#EXT-X-I-FRAME-STREAM-INF lacks required attribute(s): BANDWIDTH',
|
||||
'#EXT-X-I-FRAME-STREAM-INF lacks required attribute(s): BANDWIDTH, URI'
|
||||
];
|
||||
|
||||
assert.deepEqual(
|
||||
this.warnings,
|
||||
warnings,
|
||||
'warnings as expected'
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('warns when #EXT-X-I-FRAMES-ONLY the minimum version required is not supported', function(assert) {
|
||||
this.parser.push([
|
||||
'#EXTM3U',
|
||||
'#EXT-X-VERSION:3',
|
||||
'#EXT-X-PLAYLIST-TYPE:VOD',
|
||||
'#EXT-X-MEDIA-SEQUENCE:0',
|
||||
'#EXT-X-TARGETDURATION:3',
|
||||
'#EXT-X-I-FRAMES-ONLY',
|
||||
'#EXTINF:2.002,',
|
||||
'001.ts',
|
||||
'#EXTINF:2.002,',
|
||||
'002.ts',
|
||||
'#EXTINF:2.002,',
|
||||
'003.ts',
|
||||
'#EXTINF:2.002,',
|
||||
'004.ts',
|
||||
'#EXTINF:2.002,',
|
||||
'005.ts',
|
||||
'#EXTINF:2.002,',
|
||||
'006.ts',
|
||||
'#EXT-X-ENDLIST'
|
||||
].join('\n'));
|
||||
this.parser.end();
|
||||
|
||||
const warnings = [
|
||||
'manifest must be at least version 4'
|
||||
];
|
||||
|
||||
assert.deepEqual(
|
||||
this.warnings,
|
||||
warnings,
|
||||
'warnings as expected'
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('warns when #EXT-X-I-FRAMES-ONLY does not contain a version number', function(assert) {
|
||||
this.parser.push([
|
||||
'#EXTM3U',
|
||||
'#EXT-X-PLAYLIST-TYPE:VOD',
|
||||
'#EXT-X-MEDIA-SEQUENCE:0',
|
||||
'#EXT-X-TARGETDURATION:3',
|
||||
'#EXT-X-I-FRAMES-ONLY',
|
||||
'#EXTINF:2.002,',
|
||||
'001.ts',
|
||||
'#EXTINF:2.002,',
|
||||
'002.ts',
|
||||
'#EXTINF:2.002,',
|
||||
'003.ts',
|
||||
'#EXTINF:2.002,',
|
||||
'004.ts',
|
||||
'#EXTINF:2.002,',
|
||||
'005.ts',
|
||||
'#EXTINF:2.002,',
|
||||
'006.ts',
|
||||
'#EXT-X-ENDLIST'
|
||||
].join('\n'));
|
||||
this.parser.end();
|
||||
|
||||
const warnings = [
|
||||
'manifest must be at least version 4'
|
||||
];
|
||||
|
||||
assert.deepEqual(
|
||||
this.warnings,
|
||||
warnings,
|
||||
'warnings as expected'
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('parses #EXT-X-CONTENT-STEERING', function(assert) {
|
||||
const expectedContentSteeringObject = {
|
||||
serverUri: '/foo?bar=00012',
|
||||
|
|
@ -1187,6 +1307,155 @@ QUnit.module('m3u8s', function(hooks) {
|
|||
assert.deepEqual(this.warnings, warning, 'warnings as expected');
|
||||
});
|
||||
|
||||
QUnit.module('define', {
|
||||
// https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.2.3
|
||||
beforeEach() {
|
||||
this.errors = [];
|
||||
|
||||
this.parser.on('error', (err) => this.errors.push(err.message));
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('fails on missing attributes', function(assert) {
|
||||
const err = ['EXT-X-DEFINE: No attribute'];
|
||||
|
||||
this.parser.push('#EXT-X-DEFINE:');
|
||||
this.parser.end();
|
||||
assert.deepEqual(this.errors, err, 'errors as expected');
|
||||
});
|
||||
|
||||
QUnit.test('fails on disallowed combinatons', function(assert) {
|
||||
const permutations = [
|
||||
'#EXT-X-DEFINE:NAME="a",QUERYPARAM="b"',
|
||||
'#EXT-X-DEFINE:NAME="a",IMPORT="b"',
|
||||
'#EXT-X-DEFINE:QUERYPARAM="a",IMPORT="b"',
|
||||
'#EXT-X-DEFINE:NAME="a",QUERYPARAM="b",IMPORT="c"'
|
||||
];
|
||||
|
||||
assert.expect(permutations.length);
|
||||
|
||||
permutations.forEach((p) => {
|
||||
this.parser = new Parser();
|
||||
this.parser.on('error', (e) => {
|
||||
assert.equal(e.message, 'EXT-X-DEFINE: Invalid attributes', `${p} errors as expected`);
|
||||
});
|
||||
this.parser.push(p);
|
||||
this.parser.end();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('query params substituted', function(assert) {
|
||||
this.parser = new Parser({
|
||||
uri: 'https://example.com?aParam=aValue'
|
||||
});
|
||||
this.parser.push([
|
||||
'#EXTM3U',
|
||||
'#EXT-X-DEFINE:QUERYPARAM="aParam"',
|
||||
'#EXTINF:10',
|
||||
'segment.ts?replaced_param={$aParam}'
|
||||
].join('\n'));
|
||||
this.parser.end();
|
||||
|
||||
assert.equal('aValue', this.parser.manifest.definitions.aParam, 'value of param stored');
|
||||
assert.equal('segment.ts?replaced_param=aValue', this.parser.manifest.segments[0].uri, 'substituted in url');
|
||||
});
|
||||
|
||||
QUnit.test('query params substituted with relative URL', function(assert) {
|
||||
this.parser = new Parser({
|
||||
uri: 'playlist.m3u8?aParam=aValue'
|
||||
});
|
||||
this.parser.push([
|
||||
'#EXTM3U',
|
||||
'#EXT-X-DEFINE:QUERYPARAM="aParam"',
|
||||
'#EXTINF:10',
|
||||
'segment.ts?replaced_param={$aParam}'
|
||||
].join('\n'));
|
||||
this.parser.end();
|
||||
|
||||
assert.equal('aValue', this.parser.manifest.definitions.aParam, 'value of param stored');
|
||||
assert.equal('segment.ts?replaced_param=aValue', this.parser.manifest.segments[0].uri, 'substituted in url');
|
||||
});
|
||||
|
||||
QUnit.test('fails with missing query params', function(assert) {
|
||||
assert.expect(1);
|
||||
this.parser = new Parser({
|
||||
uri: 'https://example.com?bParam=bValue'
|
||||
});
|
||||
this.parser.on('error', (e) => {
|
||||
assert.equal(e.message, 'EXT-X-DEFINE: No query param aParam');
|
||||
});
|
||||
this.parser.push([
|
||||
'#EXTM3U',
|
||||
'#EXT-X-DEFINE:QUERYPARAM="aParam"',
|
||||
'#EXTINF:10',
|
||||
'segment.ts?replacedparam={$aParam}'
|
||||
].join('\n'));
|
||||
this.parser.end();
|
||||
});
|
||||
|
||||
QUnit.test('fails on redefinition', function(assert) {
|
||||
const permutations = [
|
||||
['#EXT-X-DEFINE:NAME="a",VALUE="b"', '#EXT-X-DEFINE:NAME="a",VALUE="c"'],
|
||||
['#EXT-X-DEFINE:NAME="a",VALUE="b"', '#EXT-X-DEFINE:IMPORT="a"'],
|
||||
['#EXT-X-DEFINE:NAME="a",VALUE="b"', '#EXT-X-DEFINE:QUERYPARAM="a"'],
|
||||
['#EXT-X-DEFINE:IMPORT="a"', '#EXT-X-DEFINE:IMPORT="a"'],
|
||||
['#EXT-X-DEFINE:IMPORT="a"', '#EXT-X-DEFINE:QUERYPARAM="a"'],
|
||||
['#EXT-X-DEFINE:IMPORT="a"', '#EXT-X-DEFINE:NAME="a",VALUE="c"'],
|
||||
['#EXT-X-DEFINE:QUERYPARAM="a"', '#EXT-X-DEFINE:IMPORT="a"'],
|
||||
['#EXT-X-DEFINE:QUERYPARAM="a"', '#EXT-X-DEFINE:QUERYPARAM="a"'],
|
||||
['#EXT-X-DEFINE:QUERYPARAM="a"', '#EXT-X-DEFINE:NAME="a",VALUE="c"']
|
||||
];
|
||||
|
||||
assert.expect(permutations.length);
|
||||
|
||||
permutations.forEach((p) => {
|
||||
this.parser = new Parser({
|
||||
uri: 'https:example.com?a=1',
|
||||
mainDefinitions: {
|
||||
a: 2
|
||||
}
|
||||
});
|
||||
this.parser.on('error', (e) => {
|
||||
assert.equal(e.message, 'EXT-X-DEFINE: Duplicate name a', 'errosr on combination');
|
||||
});
|
||||
this.parser.push(p.join('\n'));
|
||||
this.parser.end();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('fails with IMPORT on main playlist', function(assert) {
|
||||
this.parser.on('error', function(e) {
|
||||
assert.equal(e.message, 'EXT-X-DEFINE: No value imported_param to import, or IMPORT used on main playlist', 'fails when missing');
|
||||
});
|
||||
this.parser.push('#EXT-X-DEFINE:IMPORT="imported_param"');
|
||||
this.parser.end();
|
||||
});
|
||||
|
||||
QUnit.test('named and imported substiutions work', function(assert) {
|
||||
this.parser = new Parser({
|
||||
mainDefinitions: {
|
||||
aParam: 'aValue',
|
||||
engLabel: 'Anglais'
|
||||
}
|
||||
});
|
||||
this.parser.push([
|
||||
'#EXTM3U',
|
||||
'#EXT-X-DEFINE:IMPORT="aParam"',
|
||||
'#EXT-X-DEFINE:NAME="bParam",VALUE="bValue"',
|
||||
'#EXT-X-DEFINE:IMPORT="engLabel"',
|
||||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",LANGUAGE="eng",NAME="{$engLabel}",AUTOSELECT=YES,DEFAULT=YES,URI="eng/prog_index.m3u8?bParam={$bParam}"',
|
||||
'#EXTINF:10',
|
||||
'segment.ts?aParam={$aParam}&bParam={$bParam}'
|
||||
].join('\n'));
|
||||
this.parser.end();
|
||||
|
||||
assert.equal('aValue', this.parser.manifest.definitions.aParam, 'value of param from import stored');
|
||||
assert.equal('bValue', this.parser.manifest.definitions.bParam, 'value of param from name stored');
|
||||
assert.equal('segment.ts?aParam=aValue&bParam=bValue', this.parser.manifest.segments[0].uri, 'substituted in uri');
|
||||
assert.ok(this.parser.manifest.mediaGroups.AUDIO.aac.hasOwnProperty('Anglais'), 'replacement in attribute');
|
||||
assert.equal('eng/prog_index.m3u8?bParam=bValue', this.parser.manifest.mediaGroups.AUDIO.aac.Anglais.uri, 'replacement in uri in attribute');
|
||||
});
|
||||
|
||||
QUnit.module('integration');
|
||||
|
||||
for (const key in testDataExpected) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue