mirror of
https://github.com/videojs/m3u8-parser.git
synced 2026-06-06 08:22:33 +00:00
Compare commits
2 commits
main
...
fix/dateTi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4500ab1185 | ||
|
|
2341b4fcd2 |
70 changed files with 37 additions and 1222 deletions
30
CHANGELOG.md
30
CHANGELOG.md
|
|
@ -1,33 +1,3 @@
|
|||
<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)
|
||||
|
||||
### Features
|
||||
|
||||
* parse content steering tags and attributes ([#176](https://github.com/videojs/m3u8-parser/issues/176)) ([42472c5](https://github.com/videojs/m3u8-parser/commit/42472c5))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add dateTimeObject and dateTimeString for backward compatibility ([#174](https://github.com/videojs/m3u8-parser/issues/174)) ([6944bb1](https://github.com/videojs/m3u8-parser/commit/6944bb1))
|
||||
* merge dateRange tags with same IDs and no conflicting attributes ([#175](https://github.com/videojs/m3u8-parser/issues/175)) ([73d934c](https://github.com/videojs/m3u8-parser/commit/73d934c))
|
||||
|
||||
### Chores
|
||||
|
||||
* update v7.0.0 documentation ([#172](https://github.com/videojs/m3u8-parser/issues/172)) ([72da994](https://github.com/videojs/m3u8-parser/commit/72da994))
|
||||
|
||||
<a name="7.0.0"></a>
|
||||
# [7.0.0](https://github.com/videojs/m3u8-parser/compare/v6.2.0...v7.0.0) (2023-07-10)
|
||||
|
||||
|
|
|
|||
27
README.md
27
README.md
|
|
@ -13,13 +13,12 @@ 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)
|
||||
- [Main Playlist Tags](#main-playlist-tags)
|
||||
- [Master Playlist Tags](#master-playlist-tags)
|
||||
- [Experimental Tags](#experimental-tags)
|
||||
- [EXT-X-CUE-OUT](#ext-x-cue-out)
|
||||
- [EXT-X-CUE-OUT-CONT](#ext-x-cue-out-cont)
|
||||
|
|
@ -71,21 +70,6 @@ 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
|
||||
|
||||
|
|
@ -179,7 +163,6 @@ 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
|
||||
|
||||
|
|
@ -190,15 +173,11 @@ 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)
|
||||
|
||||
### Main Playlist Tags
|
||||
### Master 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
|
||||
|
||||
|
|
@ -263,6 +242,8 @@ 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.2.0",
|
||||
"version": "7.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
@ -1579,12 +1579,13 @@
|
|||
}
|
||||
},
|
||||
"@videojs/vhs-utils": {
|
||||
"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==",
|
||||
"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==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"global": "^4.4.0"
|
||||
"global": "^4.4.0",
|
||||
"url-toolkit": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"JSONStream": {
|
||||
|
|
@ -8812,6 +8813,11 @@
|
|||
"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.2.0",
|
||||
"version": "7.0.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": "^4.1.1",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"global": "^4.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -71,29 +71,6 @@ 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
|
||||
|
|
@ -319,7 +296,16 @@ export default class ParseStream extends Stream {
|
|||
event.attributes = parseAttributes(match[1]);
|
||||
|
||||
if (event.attributes.RESOLUTION) {
|
||||
event.attributes.RESOLUTION = parseResolution(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;
|
||||
}
|
||||
if (event.attributes.BANDWIDTH) {
|
||||
event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
|
||||
|
|
@ -443,7 +429,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',
|
||||
|
|
@ -639,71 +625,6 @@ 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 = {
|
||||
type: 'tag',
|
||||
tagType: 'content-steering'
|
||||
};
|
||||
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;
|
||||
}
|
||||
|
||||
// unknown tag type
|
||||
this.trigger('data', {
|
||||
type: 'tag',
|
||||
|
|
|
|||
167
src/parser.js
167
src/parser.js
|
|
@ -88,19 +88,15 @@ 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(opts = {}) {
|
||||
constructor() {
|
||||
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 */
|
||||
|
|
@ -131,7 +127,6 @@ 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
|
||||
|
|
@ -168,22 +163,6 @@ 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
|
||||
|
|
@ -717,7 +696,7 @@ export default class Parser extends Stream {
|
|||
}
|
||||
if (dateRange.duration && dateRange.endDate) {
|
||||
const startDate = dateRange.startDate;
|
||||
const newDateInSeconds = startDate.getTime() + (dateRange.duration * 1000);
|
||||
const newDateInSeconds = startDate.setSeconds(startDate.getSeconds() + dateRange.duration);
|
||||
|
||||
this.manifest.dateRanges[index].endDate = new Date(newDateInSeconds);
|
||||
}
|
||||
|
|
@ -725,146 +704,18 @@ export default class Parser extends Stream {
|
|||
dateRangeTags[dateRange.id] = dateRange;
|
||||
} else {
|
||||
for (const attribute in dateRangeTags[dateRange.id]) {
|
||||
if (!!dateRange[attribute] && JSON.stringify(dateRangeTags[dateRange.id][attribute]) !== JSON.stringify(dateRange[attribute])) {
|
||||
if (dateRangeTags[dateRange.id][attribute] !== dateRange[attribute]) {
|
||||
this.trigger('warn', {
|
||||
message: 'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes values'
|
||||
message: 'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes and same attribute values'
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if tags with the same ID do not have conflicting attributes, merge them
|
||||
const dateRangeWithSameId = this.manifest.dateRanges.findIndex((dateRangeToFind) => dateRangeToFind.id === dateRange.id);
|
||||
|
||||
this.manifest.dateRanges[dateRangeWithSameId] = Object.assign(this.manifest.dateRanges[dateRangeWithSameId], dateRange);
|
||||
dateRangeTags[dateRange.id] = Object.assign(dateRangeTags[dateRange.id], dateRange);
|
||||
// after merging, delete the duplicate dateRange that was added last
|
||||
this.manifest.dateRanges.pop();
|
||||
}
|
||||
},
|
||||
'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_(
|
||||
'#EXT-X-CONTENT-STEERING',
|
||||
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() {
|
||||
|
|
@ -918,14 +769,6 @@ 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,7 +1,6 @@
|
|||
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,6 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
dateRanges: [],
|
||||
playlistType: 'VOD',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
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,7 +2,6 @@ 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,7 +2,6 @@ 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,6 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
iFramePlaylists: [],
|
||||
dateRanges: [],
|
||||
playlists: [
|
||||
{
|
||||
|
|
|
|||
1
test/fixtures/integration/byteRange.js
vendored
1
test/fixtures/integration/byteRange.js
vendored
|
|
@ -1,7 +1,6 @@
|
|||
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,6 +1,5 @@
|
|||
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,7 +3,6 @@ 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,7 +1,6 @@
|
|||
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,7 +1,6 @@
|
|||
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,7 +1,6 @@
|
|||
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,7 +1,6 @@
|
|||
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,6 +2,5 @@ module.exports = {
|
|||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
segments: []
|
||||
};
|
||||
|
|
|
|||
1
test/fixtures/integration/emptyAllowCache.js
vendored
1
test/fixtures/integration/emptyAllowCache.js
vendored
|
|
@ -1,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
playlists: [
|
||||
{
|
||||
attributes: {
|
||||
|
|
|
|||
1
test/fixtures/integration/encrypted.js
vendored
1
test/fixtures/integration/encrypted.js
vendored
|
|
@ -1,7 +1,6 @@
|
|||
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,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'EVENT',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 1,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
1
test/fixtures/integration/extinf.js
vendored
1
test/fixtures/integration/extinf.js
vendored
|
|
@ -1,7 +1,6 @@
|
|||
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,7 +1,6 @@
|
|||
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,6 +2,5 @@ module.exports = {
|
|||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
segments: []
|
||||
};
|
||||
|
|
|
|||
288
test/fixtures/integration/iFramePlaylist.js
vendored
288
test/fixtures/integration/iFramePlaylist.js
vendored
|
|
@ -1,288 +0,0 @@
|
|||
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
42
test/fixtures/integration/iFramePlaylist.m3u8
vendored
|
|
@ -1,42 +0,0 @@
|
|||
#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
45
test/fixtures/integration/iFramesOnly.js
vendored
|
|
@ -1,45 +0,0 @@
|
|||
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
19
test/fixtures/integration/iFramesOnly.m3u8
vendored
|
|
@ -1,19 +0,0 @@
|
|||
#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,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ module.exports = {
|
|||
dateRanges: [],
|
||||
discontinuitySequence: 0,
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
preloadSegment: {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ 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,7 +5,6 @@ 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,7 +5,6 @@ module.exports = {
|
|||
dateRanges: [],
|
||||
discontinuitySequence: 0,
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 266,
|
||||
preloadSegment: {
|
||||
timeline: 0,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
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,92 +2,6 @@ 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,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
playlists: [
|
||||
{
|
||||
attributes: {
|
||||
|
|
|
|||
1
test/fixtures/integration/media.js
vendored
1
test/fixtures/integration/media.js
vendored
|
|
@ -1,7 +1,6 @@
|
|||
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,7 +1,6 @@
|
|||
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,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
1
test/fixtures/integration/missingExtinf.js
vendored
1
test/fixtures/integration/missingExtinf.js
vendored
|
|
@ -1,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ module.exports = {
|
|||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaGroups: {
|
||||
'AUDIO': {
|
||||
'audio-lo': {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ module.exports = {
|
|||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaGroups: {
|
||||
'AUDIO': {
|
||||
'audio-lo': {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
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,7 +2,6 @@ module.exports = {
|
|||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaGroups: {
|
||||
'AUDIO': {
|
||||
aac: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
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,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
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,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
playlists: [
|
||||
{
|
||||
attributes: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
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,7 +1,6 @@
|
|||
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,7 +1,6 @@
|
|||
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,7 +2,6 @@ module.exports = {
|
|||
allowCache: true,
|
||||
mediaSequence: 0,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -698,14 +698,6 @@ QUnit.test('parses #EXT-X-STREAM-INF with common attributes', function(assert) {
|
|||
'avc1.4d400d, mp4a.40.2',
|
||||
'codecs are parsed'
|
||||
);
|
||||
|
||||
manifest = '#EXT-X-STREAM-INF:PATHWAY-ID="CDN-A"\n';
|
||||
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, 'stream-inf', 'the tag type is stream-inf');
|
||||
assert.strictEqual(element.attributes['PATHWAY-ID'], 'CDN-A', 'pathway-id is parsed');
|
||||
});
|
||||
QUnit.test('parses #EXT-X-STREAM-INF with arbitrary attributes', function(assert) {
|
||||
const manifest = '#EXT-X-STREAM-INF:NUMERIC=24,ALPHA=Value,MIXED=123abc\n';
|
||||
|
|
@ -997,40 +989,3 @@ 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');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1031,7 +1031,7 @@ QUnit.module('m3u8s', function(hooks) {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test('warns when playlist has multiple #EXT-X-DATERANGE tag same ID but different attribute values', function(assert) {
|
||||
QUnit.test('warns when playlist has multiple #EXT-X-DATERANGE tag same ID but different attribute names and values', function(assert) {
|
||||
this.parser.push([
|
||||
'#EXT-X-VERSION:3',
|
||||
'#EXT-X-MEDIA-SEQUENCE:0',
|
||||
|
|
@ -1041,12 +1041,12 @@ QUnit.module('m3u8s', function(hooks) {
|
|||
'#EXT-X-ENDLIST',
|
||||
'#EXT-X-PROGRAM-DATE-TIME:2017-07-31T20:35:35.053+00:00',
|
||||
'#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T18:16:15.840000Z",END-ON-NEXT=YES,CLASS="CLASSATTRIBUTE"',
|
||||
'#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T18:16:15.840000Z",CLASS="CLASSATTRIBUTE1"'
|
||||
'#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T18:16:20.840000Z"'
|
||||
].join('\n'));
|
||||
this.parser.end();
|
||||
|
||||
const warnings = [
|
||||
'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes values'
|
||||
'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes and same attribute values'
|
||||
];
|
||||
|
||||
assert.deepEqual(
|
||||
|
|
@ -1095,33 +1095,7 @@ QUnit.module('m3u8s', function(hooks) {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test('playlist with multiple ext-x-daterange with same ID but no conflicting attributes', function(assert) {
|
||||
const expectedDateRange = {
|
||||
id: '12345',
|
||||
scte35In: '0xFC30200FFF2',
|
||||
scte35Out: '0xFC30200FFF2',
|
||||
startDate: new Date('2023-04-13T18:16:15.840000Z'),
|
||||
class: 'CLASSATTRIBUTE'
|
||||
};
|
||||
|
||||
this.parser.push([
|
||||
'#EXT-X-VERSION:3',
|
||||
'#EXT-X-MEDIA-SEQUENCE:0',
|
||||
'#EXT-X-DISCONTINUITY-SEQUENCE:0',
|
||||
'#EXTINF:10,',
|
||||
'media-00001.ts',
|
||||
'#EXT-X-ENDLIST',
|
||||
'#EXT-X-PROGRAM-DATE-TIME:2017-07-31T20:35:35.053+00:00',
|
||||
'#EXT-X-DATERANGE:ID="12345",SCTE35-IN=0xFC30200FFF2,START-DATE="2023-04-13T18:16:15.840000Z",CLASS="CLASSATTRIBUTE"',
|
||||
'#EXT-X-DATERANGE:ID="12345",SCTE35-OUT=0xFC30200FFF2,START-DATE="2023-04-13T18:16:15.840000Z"'
|
||||
].join('\n'));
|
||||
this.parser.end();
|
||||
assert.equal(this.parser.manifest.dateRanges.length, 1, 'two dateranges with same ID are merged');
|
||||
assert.deepEqual(this.parser.manifest.dateRanges[0], expectedDateRange);
|
||||
|
||||
});
|
||||
|
||||
QUnit.test('playlist with multiple ext-x-daterange ', function(assert) {
|
||||
QUnit.test(' playlist with multiple ext-x-daterange ', function(assert) {
|
||||
this.parser.push([
|
||||
' #EXTM3U',
|
||||
'#EXT-X-VERSION:6',
|
||||
|
|
@ -1158,304 +1132,6 @@ 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',
|
||||
pathwayId: 'CDN-A'
|
||||
};
|
||||
|
||||
this.parser.push('#EXT-X-CONTENT-STEERING:SERVER-URI="/foo?bar=00012",PATHWAY-ID="CDN-A"');
|
||||
this.parser.end();
|
||||
assert.deepEqual(this.parser.manifest.contentSteering, expectedContentSteeringObject);
|
||||
});
|
||||
|
||||
QUnit.test('parses #EXT-X-CONTENT-STEERING without PATHWAY-ID', function(assert) {
|
||||
const expectedContentSteeringObject = {
|
||||
serverUri: '/bar?foo=00012'
|
||||
};
|
||||
|
||||
this.parser.push('#EXT-X-CONTENT-STEERING:SERVER-URI="/bar?foo=00012"');
|
||||
this.parser.end();
|
||||
assert.deepEqual(this.parser.manifest.contentSteering, expectedContentSteeringObject);
|
||||
});
|
||||
|
||||
QUnit.test('warns on #EXT-X-CONTENT-STEERING missing SERVER-URI', function(assert) {
|
||||
const warning = ['#EXT-X-CONTENT-STEERING lacks required attribute(s): SERVER-URI'];
|
||||
|
||||
this.parser.push('#EXT-X-CONTENT-STEERING:PATHWAY-ID="CDN-A"');
|
||||
this.parser.end();
|
||||
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