mirror of
https://github.com/videojs/m3u8-parser.git
synced 2026-06-06 08:22:33 +00:00
feat: add support for #EXT-X-DEFINE (#185)
* feat: add support for EXT-X-DEFINE * tests * readme * cleanup * work with relative URLs * missing return * fix typo Co-authored-by: Dzianis Dashkevich <98566601+dzianis-dashkevich@users.noreply.github.com> * lint * lint --------- Co-authored-by: Dzianis Dashkevich <98566601+dzianis-dashkevich@users.noreply.github.com>
This commit is contained in:
parent
e5dbdb6288
commit
ba6e7cbafe
4 changed files with 331 additions and 39 deletions
22
README.md
22
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
|
||||
|
||||
|
|
@ -174,13 +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
|
||||
|
||||
|
|
|
|||
|
|
@ -693,6 +693,16 @@ export default class ParseStream extends Stream {
|
|||
|
||||
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', {
|
||||
|
|
|
|||
119
src/parser.js
119
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 */
|
||||
|
|
@ -164,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
|
||||
|
|
@ -737,6 +757,100 @@ export default class Parser extends Stream {
|
|||
['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,
|
||||
|
|
@ -750,6 +864,7 @@ export default class Parser extends Stream {
|
|||
['BANDWIDTH', 'URI']
|
||||
);
|
||||
}
|
||||
|
||||
})[entry.tagType] || noop).call(self);
|
||||
},
|
||||
uri() {
|
||||
|
|
|
|||
|
|
@ -1158,6 +1158,57 @@ 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',
|
||||
|
|
@ -1256,55 +1307,153 @@ QUnit.module('m3u8s', function(hooks) {
|
|||
assert.deepEqual(this.warnings, warning, 'warnings as expected');
|
||||
});
|
||||
|
||||
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();
|
||||
QUnit.module('define', {
|
||||
// https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.2.3
|
||||
beforeEach() {
|
||||
this.errors = [];
|
||||
|
||||
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);
|
||||
this.parser.on('error', (err) => this.errors.push(err.message));
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('warns when #EXT-X-I-FRAME-STREAM-INF missing BANDWIDTH/URI attributes', function(assert) {
|
||||
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-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'
|
||||
'#EXT-X-DEFINE:QUERYPARAM="aParam"',
|
||||
'#EXTINF:10',
|
||||
'segment.ts?replaced_param={$aParam}'
|
||||
].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.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.deepEqual(
|
||||
this.warnings,
|
||||
warnings,
|
||||
'warnings as expected'
|
||||
);
|
||||
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');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue