You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1411 lines
42 KiB
1411 lines
42 KiB
import {ParseStream, LineStream, Parser} from '../src';
|
|
import QUnit from 'qunit';
|
|
import sinon from 'sinon';
|
|
import testDataExpected from './dist/test-expected.js';
|
|
import testDataManifests from './dist/test-manifests.js';
|
|
|
|
QUnit.module('LineStream', {
|
|
beforeEach() {
|
|
this.lineStream = new LineStream();
|
|
}
|
|
});
|
|
QUnit.test('empty inputs produce no tokens', function(assert) {
|
|
let data = false;
|
|
|
|
this.lineStream.on('data', function() {
|
|
data = true;
|
|
});
|
|
this.lineStream.push('');
|
|
assert.ok(!data, 'no tokens were produced');
|
|
});
|
|
QUnit.test('splits on newlines', function(assert) {
|
|
const lines = [];
|
|
|
|
this.lineStream.on('data', function(line) {
|
|
lines.push(line);
|
|
});
|
|
this.lineStream.push('#EXTM3U\nmovie.ts\n');
|
|
|
|
assert.strictEqual(2, lines.length, 'two lines are ready');
|
|
assert.strictEqual('#EXTM3U', lines.shift(), 'the first line is the first token');
|
|
assert.strictEqual('movie.ts', lines.shift(), 'the second line is the second token');
|
|
});
|
|
QUnit.test('empty lines become empty strings', function(assert) {
|
|
const lines = [];
|
|
|
|
this.lineStream.on('data', function(line) {
|
|
lines.push(line);
|
|
});
|
|
this.lineStream.push('\n\n');
|
|
|
|
assert.strictEqual(2, lines.length, 'two lines are ready');
|
|
assert.strictEqual('', lines.shift(), 'the first line is empty');
|
|
assert.strictEqual('', lines.shift(), 'the second line is empty');
|
|
});
|
|
QUnit.test('handles lines broken across appends', function(assert) {
|
|
const lines = [];
|
|
|
|
this.lineStream.on('data', function(line) {
|
|
lines.push(line);
|
|
});
|
|
this.lineStream.push('#EXTM');
|
|
assert.strictEqual(0, lines.length, 'no lines are ready');
|
|
|
|
this.lineStream.push('3U\nmovie.ts\n');
|
|
assert.strictEqual(2, lines.length, 'two lines are ready');
|
|
assert.strictEqual('#EXTM3U', lines.shift(), 'the first line is the first token');
|
|
assert.strictEqual('movie.ts', lines.shift(), 'the second line is the second token');
|
|
});
|
|
QUnit.test('stops sending events after deregistering', function(assert) {
|
|
const temporaryLines = [];
|
|
const temporary = function(line) {
|
|
temporaryLines.push(line);
|
|
};
|
|
const permanentLines = [];
|
|
const permanent = function(line) {
|
|
permanentLines.push(line);
|
|
};
|
|
|
|
this.lineStream.on('data', temporary);
|
|
this.lineStream.on('data', permanent);
|
|
this.lineStream.push('line one\n');
|
|
assert.strictEqual(
|
|
temporaryLines.length,
|
|
permanentLines.length,
|
|
'both callbacks receive the event'
|
|
);
|
|
|
|
assert.ok(this.lineStream.off('data', temporary), 'a listener was removed');
|
|
this.lineStream.push('line two\n');
|
|
assert.strictEqual(1, temporaryLines.length, 'no new events are received');
|
|
assert.strictEqual(2, permanentLines.length, 'new events are still received');
|
|
});
|
|
|
|
QUnit.module('ParseStream', {
|
|
beforeEach() {
|
|
this.lineStream = new LineStream();
|
|
this.parseStream = new ParseStream();
|
|
this.lineStream.pipe(this.parseStream);
|
|
}
|
|
});
|
|
|
|
QUnit.test('parses custom tags', function(assert) {
|
|
const manifest = '#VOD-STARTTIMESTAMP:1501533337573\n';
|
|
let element;
|
|
|
|
this.parseStream.addParser({
|
|
expression: /^#VOD-STARTTIMESTAMP/,
|
|
customType: 'startTimestamp'
|
|
});
|
|
|
|
this.parseStream.on('data', function(elem) {
|
|
element = elem;
|
|
});
|
|
|
|
this.lineStream.push(manifest);
|
|
assert.ok(element, 'element');
|
|
assert.strictEqual(element.type, 'custom', 'the type of the data is custom');
|
|
assert.strictEqual(
|
|
element.customType,
|
|
'startTimestamp',
|
|
'the customType is startTimestamp'
|
|
);
|
|
});
|
|
|
|
QUnit.test('mapper does not conflict with parser', function(assert) {
|
|
const manifest = '#EXAMPLE\n';
|
|
const commentMapper = sinon.spy(line => '#NEW-COMMENT');
|
|
const commentMapper2 = sinon.spy(line => '#SOMETHING-ELSE');
|
|
const dataCallback = sinon.spy();
|
|
|
|
this.parseStream.addTagMapper({
|
|
expression: /^#EXAMPLE/,
|
|
map: commentMapper
|
|
});
|
|
this.parseStream.addTagMapper({
|
|
expression: /^#EXAMPLE/,
|
|
map: commentMapper2
|
|
});
|
|
|
|
this.parseStream.addParser({
|
|
expression: /^#EXAMPLE/,
|
|
customType: 'test'
|
|
});
|
|
this.parseStream.addParser({
|
|
expression: /^#NEW-COMMENT/,
|
|
customType: 'test2'
|
|
});
|
|
|
|
this.parseStream.on('data', dataCallback);
|
|
this.lineStream.push(manifest);
|
|
|
|
assert.ok(commentMapper.called);
|
|
assert.ok(commentMapper2.called);
|
|
assert.strictEqual(dataCallback.callCount, 3);
|
|
|
|
assert.deepEqual(dataCallback.getCall(0).args[0], {
|
|
data: '#EXAMPLE',
|
|
type: 'custom',
|
|
customType: 'test',
|
|
segment: undefined
|
|
});
|
|
assert.deepEqual(dataCallback.getCall(1).args[0], {
|
|
data: '#NEW-COMMENT',
|
|
type: 'custom',
|
|
customType: 'test2',
|
|
segment: undefined
|
|
});
|
|
assert.deepEqual(dataCallback.getCall(2).args[0], {
|
|
text: 'SOMETHING-ELSE',
|
|
type: 'comment'
|
|
});
|
|
});
|
|
|
|
QUnit.test('maps custom tags', function(assert) {
|
|
const manifest = '#EXAMPLE\n';
|
|
const commentMapper = sinon.spy(line => '#NEW-COMMENT');
|
|
const dataCallback = sinon.spy();
|
|
|
|
this.parseStream.addTagMapper({
|
|
expression: /^#EXAMPLE/,
|
|
map: commentMapper
|
|
});
|
|
|
|
this.parseStream.on('data', dataCallback);
|
|
this.lineStream.push(manifest);
|
|
|
|
assert.ok(commentMapper.called);
|
|
assert.strictEqual(dataCallback.callCount, 2);
|
|
|
|
assert.deepEqual(dataCallback.getCall(0).args[0], {
|
|
text: 'EXAMPLE',
|
|
type: 'comment'
|
|
});
|
|
assert.deepEqual(dataCallback.getCall(1).args[0], {
|
|
text: 'NEW-COMMENT',
|
|
type: 'comment'
|
|
});
|
|
});
|
|
|
|
QUnit.test('maps multiple custom tags', function(assert) {
|
|
const manifest = '#VOD-STARTTIMESTAMP:1501533337573\n';
|
|
const commentMapper = sinon.spy(line => '#NEW-COMMENT');
|
|
const pdtMapper = sinon.spy((line) => {
|
|
const match = /#VOD-STARTTIMESTAMP:(\d+)/g.exec(line)[1];
|
|
const ISOdate = new Date(Number(match)).toISOString();
|
|
|
|
return `#EXT-X-PROGRAM-DATE-TIME:${ISOdate}`;
|
|
});
|
|
const dataCallback = sinon.spy();
|
|
|
|
this.parseStream.addTagMapper({
|
|
expression: /^#VOD-STARTTIMESTAMP/,
|
|
map: commentMapper
|
|
});
|
|
this.parseStream.addTagMapper({
|
|
expression: /^#VOD-STARTTIMESTAMP/,
|
|
map: pdtMapper
|
|
});
|
|
|
|
this.parseStream.on('data', dataCallback);
|
|
this.lineStream.push(manifest);
|
|
|
|
assert.ok(commentMapper.called);
|
|
assert.ok(pdtMapper.called);
|
|
assert.strictEqual(dataCallback.callCount, 3);
|
|
|
|
assert.deepEqual(dataCallback.getCall(0).args[0], {
|
|
text: 'VOD-STARTTIMESTAMP:1501533337573',
|
|
type: 'comment'
|
|
});
|
|
|
|
assert.deepEqual(dataCallback.getCall(1).args[0], {
|
|
text: 'NEW-COMMENT',
|
|
type: 'comment'
|
|
});
|
|
|
|
const dateTag = dataCallback.getCall(2).args[0];
|
|
|
|
assert.strictEqual(dateTag.dateTimeString, '2017-07-31T20:35:37.573Z');
|
|
assert.strictEqual(dateTag.tagType, 'program-date-time');
|
|
assert.strictEqual(dateTag.type, 'tag');
|
|
});
|
|
|
|
QUnit.test('mapper ignores tags', function(assert) {
|
|
const manifest = '#TAG\n';
|
|
const dataCallback = sinon.spy();
|
|
|
|
this.parseStream.addTagMapper({
|
|
expression: /^#NO-MATCH/,
|
|
map(line) {
|
|
return '#MAPPED';
|
|
}
|
|
});
|
|
|
|
this.parseStream.on('data', dataCallback);
|
|
this.lineStream.push(manifest);
|
|
|
|
assert.strictEqual(dataCallback.callCount, 1);
|
|
assert.deepEqual(dataCallback.getCall(0).args[0], {
|
|
text: 'TAG',
|
|
type: 'comment'
|
|
});
|
|
});
|
|
|
|
QUnit.test('parses comment lines', function(assert) {
|
|
const manifest = '# a line that starts with a hash mark without "EXT" is a comment\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, 'comment', 'the type is comment');
|
|
assert.strictEqual(
|
|
element.text,
|
|
manifest.slice(1, manifest.length - 1),
|
|
'the comment text is parsed'
|
|
);
|
|
});
|
|
QUnit.test('parses uri lines', function(assert) {
|
|
const manifest = 'any non-blank line that does not start with a hash-mark is a URI\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, 'uri', 'the type is uri');
|
|
assert.strictEqual(
|
|
element.uri,
|
|
manifest.substring(0, manifest.length - 1),
|
|
'the uri text is parsed'
|
|
);
|
|
});
|
|
QUnit.test('parses unknown tag types', function(assert) {
|
|
const manifest = '#EXT-X-EXAMPLE-TAG:some,additional,stuff\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 type is tag');
|
|
assert.strictEqual(
|
|
element.data,
|
|
manifest.slice(4, manifest.length - 1),
|
|
'unknown tag data is preserved'
|
|
);
|
|
});
|
|
|
|
// #EXTM3U
|
|
QUnit.test('parses #EXTM3U tags', function(assert) {
|
|
const manifest = '#EXTM3U\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, 'm3u', 'the tag type is m3u');
|
|
});
|
|
|
|
// #EXTINF
|
|
QUnit.test('parses minimal #EXTINF tags', function(assert) {
|
|
const manifest = '#EXTINF\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, 'inf', 'the tag type is inf');
|
|
});
|
|
QUnit.test('parses #EXTINF tags with durations', function(assert) {
|
|
let manifest = '#EXTINF:15\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, 'inf', 'the tag type is inf');
|
|
assert.strictEqual(element.duration, 15, 'the duration is parsed');
|
|
assert.ok(!('title' in element), 'no title is parsed');
|
|
|
|
manifest = '#EXTINF:21,\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, 'inf', 'the tag type is inf');
|
|
assert.strictEqual(element.duration, 21, 'the duration is parsed');
|
|
assert.ok(!('title' in element), 'no title is parsed');
|
|
});
|
|
QUnit.test('parses #EXTINF tags with a duration and title', function(assert) {
|
|
const manifest = '#EXTINF:13,Does anyone really use the title attribute?\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, 'inf', 'the tag type is inf');
|
|
assert.strictEqual(element.duration, 13, 'the duration is parsed');
|
|
assert.strictEqual(
|
|
element.title,
|
|
manifest.substring(manifest.indexOf(',') + 1, manifest.length - 1),
|
|
'the title is parsed'
|
|
);
|
|
});
|
|
QUnit.test('parses #EXTINF tags with carriage returns', function(assert) {
|
|
const manifest = '#EXTINF:13,Does anyone really use the title attribute?\r\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, 'inf', 'the tag type is inf');
|
|
assert.strictEqual(element.duration, 13, 'the duration is parsed');
|
|
assert.strictEqual(
|
|
element.title,
|
|
manifest.substring(manifest.indexOf(',') + 1, manifest.length - 2),
|
|
'the title is parsed'
|
|
);
|
|
});
|
|
|
|
// #EXT-X-TARGETDURATION
|
|
QUnit.test('parses minimal #EXT-X-TARGETDURATION tags', function(assert) {
|
|
const manifest = '#EXT-X-TARGETDURATION\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, 'targetduration', 'the tag type is targetduration');
|
|
assert.ok(!('duration' in element), 'no duration is parsed');
|
|
});
|
|
QUnit.test('parses #EXT-X-TARGETDURATION with duration', function(assert) {
|
|
const manifest = '#EXT-X-TARGETDURATION:47\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, 'targetduration', 'the tag type is targetduration');
|
|
assert.strictEqual(element.duration, 47, 'the duration is parsed');
|
|
});
|
|
|
|
// #EXT-X-VERSION
|
|
QUnit.test('parses minimal #EXT-X-VERSION tags', function(assert) {
|
|
const manifest = '#EXT-X-VERSION:\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, 'version', 'the tag type is version');
|
|
assert.ok(!('version' in element), 'no version is present');
|
|
});
|
|
QUnit.test('parses #EXT-X-VERSION with a version', function(assert) {
|
|
const manifest = '#EXT-X-VERSION:99\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, 'version', 'the tag type is version');
|
|
assert.strictEqual(element.version, 99, 'the version is parsed');
|
|
});
|
|
|
|
// #EXT-X-MEDIA-SEQUENCE
|
|
QUnit.test('parses minimal #EXT-X-MEDIA-SEQUENCE tags', function(assert) {
|
|
const manifest = '#EXT-X-MEDIA-SEQUENCE\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, 'media-sequence', 'the tag type is media-sequence');
|
|
assert.ok(!('number' in element), 'no number is present');
|
|
});
|
|
QUnit.test('parses #EXT-X-MEDIA-SEQUENCE with sequence numbers', function(assert) {
|
|
const manifest = '#EXT-X-MEDIA-SEQUENCE:109\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, 'media-sequence', 'the tag type is media-sequence');
|
|
assert.ok(element.number, 109, 'the number is parsed');
|
|
});
|
|
|
|
// #EXT-X-PLAYLIST-TYPE
|
|
QUnit.test('parses minimal #EXT-X-PLAYLIST-TYPE tags', function(assert) {
|
|
const manifest = '#EXT-X-PLAYLIST-TYPE:\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, 'playlist-type', 'the tag type is playlist-type');
|
|
assert.ok(!('playlistType' in element), 'no playlist type is present');
|
|
});
|
|
QUnit.test('parses #EXT-X-PLAYLIST-TYPE with mutability info', function(assert) {
|
|
let manifest = '#EXT-X-PLAYLIST-TYPE:EVENT\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, 'playlist-type', 'the tag type is playlist-type');
|
|
assert.strictEqual(element.playlistType, 'EVENT', 'the playlist type is EVENT');
|
|
|
|
manifest = '#EXT-X-PLAYLIST-TYPE:VOD\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, 'playlist-type', 'the tag type is playlist-type');
|
|
assert.strictEqual(element.playlistType, 'VOD', 'the playlist type is VOD');
|
|
|
|
manifest = '#EXT-X-PLAYLIST-TYPE:nonsense\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, 'playlist-type', 'the tag type is playlist-type');
|
|
assert.strictEqual(element.playlistType, 'nonsense', 'the playlist type is parsed');
|
|
});
|
|
|
|
// #EXT-X-BYTERANGE
|
|
QUnit.test('parses minimal #EXT-X-BYTERANGE tags', function(assert) {
|
|
const manifest = '#EXT-X-BYTERANGE\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, 'byterange', 'the tag type is byterange');
|
|
assert.ok(!('length' in element), 'no length is present');
|
|
assert.ok(!('offset' in element), 'no offset is present');
|
|
});
|
|
QUnit.test('parses #EXT-X-BYTERANGE with length and offset', function(assert) {
|
|
let manifest = '#EXT-X-BYTERANGE:45\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, 'byterange', 'the tag type is byterange');
|
|
assert.strictEqual(element.length, 45, 'length is parsed');
|
|
assert.ok(!('offset' in element), 'no offset is present');
|
|
|
|
manifest = '#EXT-X-BYTERANGE:108@16\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, 'byterange', 'the tag type is byterange');
|
|
assert.strictEqual(element.length, 108, 'length is parsed');
|
|
assert.strictEqual(element.offset, 16, 'offset is parsed');
|
|
});
|
|
|
|
// #EXT-X-ALLOW-CACHE
|
|
QUnit.test('parses minimal #EXT-X-ALLOW-CACHE tags', function(assert) {
|
|
const manifest = '#EXT-X-ALLOW-CACHE:\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, 'allow-cache', 'the tag type is allow-cache');
|
|
assert.ok(!('allowed' in element), 'no allowed is present');
|
|
});
|
|
QUnit.test('parses valid #EXT-X-ALLOW-CACHE tags', function(assert) {
|
|
let manifest = '#EXT-X-ALLOW-CACHE:YES\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, 'allow-cache', 'the tag type is allow-cache');
|
|
assert.ok(element.allowed, 'allowed is parsed');
|
|
|
|
manifest = '#EXT-X-ALLOW-CACHE:NO\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, 'allow-cache', 'the tag type is allow-cache');
|
|
assert.ok(!element.allowed, 'allowed is parsed');
|
|
});
|
|
// #EXT-X-MAP
|
|
QUnit.test('parses minimal #EXT-X-MAP tags', function(assert) {
|
|
const manifest = '#EXT-X-MAP:URI="init.m4s"\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, 'map', 'the tag type is map');
|
|
assert.strictEqual(element.uri, 'init.m4s', 'parsed the uri');
|
|
});
|
|
QUnit.test('parses #EXT-X-MAP tags with a byterange', function(assert) {
|
|
const manifest = '#EXT-X-MAP:URI="0.m4s", BYTERANGE="1000@23"\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.uri, '0.m4s', 'parsed the uri');
|
|
assert.strictEqual(
|
|
element.byterange.length,
|
|
1000,
|
|
'parsed the byterange length'
|
|
);
|
|
assert.strictEqual(
|
|
element.byterange.offset,
|
|
23,
|
|
'parsed the byterange offset'
|
|
);
|
|
});
|
|
QUnit.test('parses #EXT-X-MAP tags with arbitrary attributes', function(assert) {
|
|
const manifest = '#EXT-X-MAP:URI="init.mp4", SOMETHING=YES,BYTERANGE="720@0"\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.uri, 'init.mp4', 'parsed the uri');
|
|
assert.strictEqual(
|
|
element.byterange.length,
|
|
720,
|
|
'parsed the byterange length'
|
|
);
|
|
assert.strictEqual(
|
|
element.byterange.offset,
|
|
0,
|
|
'parsed the byterange offset'
|
|
);
|
|
});
|
|
// #EXT-X-STREAM-INF
|
|
QUnit.test('parses minimal #EXT-X-STREAM-INF tags', function(assert) {
|
|
const manifest = '#EXT-X-STREAM-INF\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, 'stream-inf', 'the tag type is stream-inf');
|
|
assert.ok(!('attributes' in element), 'no attributes are present');
|
|
});
|
|
// #EXT-X-PROGRAM-DATE-TIME
|
|
QUnit.test('parses minimal EXT-X-PROGRAM-DATE-TIME tags', function(assert) {
|
|
const manifest = '#EXT-X-PROGRAM-DATE-TIME\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, 'program-date-time', 'the tag type is date-time');
|
|
assert.ok(!('dateTimeString' in element), 'no dateTime is present');
|
|
});
|
|
QUnit.test(
|
|
'parses EXT-X-PROGRAM-DATE-TIME tags with valid date-time formats',
|
|
function(assert) {
|
|
let manifest = '#EXT-X-PROGRAM-DATE-TIME:2016-06-22T09:20:16.166-04:00\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, 'program-date-time', 'the tag type is date-time');
|
|
assert.strictEqual(
|
|
element.dateTimeString, '2016-06-22T09:20:16.166-04:00',
|
|
'dateTimeString is parsed'
|
|
);
|
|
assert.deepEqual(
|
|
element.dateTimeObject, new Date('2016-06-22T09:20:16.166-04:00'),
|
|
'dateTimeObject is parsed'
|
|
);
|
|
|
|
manifest = '#EXT-X-PROGRAM-DATE-TIME:2016-06-22T09:20:16.16389Z\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, 'program-date-time', 'the tag type is date-time');
|
|
assert.strictEqual(
|
|
element.dateTimeString, '2016-06-22T09:20:16.16389Z',
|
|
'dateTimeString is parsed'
|
|
);
|
|
assert.deepEqual(
|
|
element.dateTimeObject, new Date('2016-06-22T09:20:16.16389Z'),
|
|
'dateTimeObject is parsed'
|
|
);
|
|
}
|
|
);
|
|
QUnit.test('parses #EXT-X-STREAM-INF with common attributes', function(assert) {
|
|
let manifest = '#EXT-X-STREAM-INF:BANDWIDTH=14400\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, 'stream-inf', 'the tag type is stream-inf');
|
|
assert.strictEqual(element.attributes.BANDWIDTH, 14400, 'bandwidth is parsed');
|
|
|
|
manifest = '#EXT-X-STREAM-INF:PROGRAM-ID=7\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['PROGRAM-ID'], 7, 'program-id is parsed');
|
|
|
|
manifest = '#EXT-X-STREAM-INF:RESOLUTION=396x224\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.RESOLUTION.width, 396, 'width is parsed');
|
|
assert.strictEqual(element.attributes.RESOLUTION.height, 224, 'heigth is parsed');
|
|
|
|
manifest = '#EXT-X-STREAM-INF:CODECS="avc1.4d400d, mp4a.40.2"\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.CODECS,
|
|
'avc1.4d400d, mp4a.40.2',
|
|
'codecs are 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';
|
|
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, 'stream-inf', 'the tag type is stream-inf');
|
|
assert.strictEqual(element.attributes.NUMERIC, '24', 'numeric attributes are parsed');
|
|
assert.strictEqual(
|
|
element.attributes.ALPHA,
|
|
'Value',
|
|
'alphabetic attributes are parsed'
|
|
);
|
|
assert.strictEqual(element.attributes.MIXED, '123abc', 'mixed attributes are parsed');
|
|
});
|
|
// #EXT-X-ENDLIST
|
|
QUnit.test('parses #EXT-X-ENDLIST tags', function(assert) {
|
|
const manifest = '#EXT-X-ENDLIST\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, 'endlist', 'the tag type is stream-inf');
|
|
});
|
|
|
|
// #EXT-X-KEY
|
|
QUnit.test('parses valid #EXT-X-KEY tags', function(assert) {
|
|
let manifest =
|
|
'#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=52"\n';
|
|
let element;
|
|
|
|
this.parseStream.on('data', function(elem) {
|
|
element = elem;
|
|
});
|
|
this.lineStream.push(manifest);
|
|
|
|
assert.ok(element, 'an event was triggered');
|
|
assert.deepEqual(element, {
|
|
type: 'tag',
|
|
tagType: 'key',
|
|
attributes: {
|
|
METHOD: 'AES-128',
|
|
URI: 'https://priv.example.com/key.php?r=52'
|
|
}
|
|
}, 'parsed a valid key');
|
|
|
|
manifest = '#EXT-X-KEY:URI="https://example.com/key#1",METHOD=FutureType-1024\n';
|
|
this.lineStream.push(manifest);
|
|
assert.ok(element, 'an event was triggered');
|
|
assert.deepEqual(element, {
|
|
type: 'tag',
|
|
tagType: 'key',
|
|
attributes: {
|
|
METHOD: 'FutureType-1024',
|
|
URI: 'https://example.com/key#1'
|
|
}
|
|
}, 'parsed the attribute list independent of order');
|
|
|
|
manifest = '#EXT-X-KEY:IV=1234567890abcdef1234567890abcdef\n';
|
|
this.lineStream.push(manifest);
|
|
assert.ok(element.attributes.IV, 'detected an IV attribute');
|
|
assert.deepEqual(element.attributes.IV, new Uint32Array([
|
|
0x12345678,
|
|
0x90abcdef,
|
|
0x12345678,
|
|
0x90abcdef
|
|
]), 'parsed an IV value');
|
|
});
|
|
|
|
QUnit.test('parses minimal #EXT-X-KEY tags', function(assert) {
|
|
const manifest = '#EXT-X-KEY:\n';
|
|
let element;
|
|
|
|
this.parseStream.on('data', function(elem) {
|
|
element = elem;
|
|
});
|
|
this.lineStream.push(manifest);
|
|
|
|
assert.ok(element, 'an event was triggered');
|
|
assert.deepEqual(element, {
|
|
type: 'tag',
|
|
tagType: 'key'
|
|
}, 'parsed a minimal key tag');
|
|
});
|
|
|
|
QUnit.test('parses lightly-broken #EXT-X-KEY tags', function(assert) {
|
|
let manifest = '#EXT-X-KEY:URI=\'https://example.com/single-quote\',METHOD=AES-128\n';
|
|
let element;
|
|
|
|
this.parseStream.on('data', function(elem) {
|
|
element = elem;
|
|
});
|
|
this.lineStream.push(manifest);
|
|
|
|
assert.strictEqual(
|
|
element.attributes.URI,
|
|
'https://example.com/single-quote',
|
|
'parsed a single-quoted uri'
|
|
);
|
|
|
|
element = null;
|
|
manifest = '#EXT-X-KEYURI="https://example.com/key",METHOD=AES-128\n';
|
|
this.lineStream.push(manifest);
|
|
assert.strictEqual(element.tagType, 'key', 'parsed the tag type');
|
|
assert.strictEqual(
|
|
element.attributes.URI,
|
|
'https://example.com/key',
|
|
'inferred a colon after the tag type'
|
|
);
|
|
|
|
element = null;
|
|
manifest = '#EXT-X-KEY: URI = "https://example.com/key",METHOD=AES-128\n';
|
|
this.lineStream.push(manifest);
|
|
assert.strictEqual(
|
|
element.attributes.URI,
|
|
'https://example.com/key',
|
|
'trims and removes quotes around the URI'
|
|
);
|
|
});
|
|
|
|
QUnit.test('parses prefixed with 0x or 0X #EXT-X-KEY:IV tags', function(assert) {
|
|
let manifest;
|
|
let element;
|
|
|
|
this.parseStream.on('data', function(elem) {
|
|
element = elem;
|
|
});
|
|
|
|
manifest = '#EXT-X-KEY:IV=0x1234567890abcdef1234567890abcdef\n';
|
|
this.lineStream.push(manifest);
|
|
assert.ok(element.attributes.IV, 'detected an IV attribute');
|
|
assert.deepEqual(element.attributes.IV, new Uint32Array([
|
|
0x12345678,
|
|
0x90abcdef,
|
|
0x12345678,
|
|
0x90abcdef
|
|
]), 'parsed an IV value with 0x');
|
|
|
|
manifest = '#EXT-X-KEY:IV=0X1234567890abcdef1234567890abcdef\n';
|
|
this.lineStream.push(manifest);
|
|
assert.ok(element.attributes.IV, 'detected an IV attribute');
|
|
assert.deepEqual(element.attributes.IV, new Uint32Array([
|
|
0x12345678,
|
|
0x90abcdef,
|
|
0x12345678,
|
|
0x90abcdef
|
|
]), 'parsed an IV value with 0X');
|
|
});
|
|
|
|
// #EXT-X-START
|
|
QUnit.test('parses EXT-X-START tags', function(assert) {
|
|
const manifest = '#EXT-X-START:TIME-OFFSET=1.1\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, 'start', 'the tag type is start');
|
|
assert.strictEqual(element.attributes['TIME-OFFSET'], 1.1, 'parses time offset');
|
|
assert.strictEqual(element.attributes.PRECISE, false, 'precise defaults to false');
|
|
});
|
|
QUnit.test('parses EXT-X-START PRECISE attribute', function(assert) {
|
|
const manifest = '#EXT-X-START:TIME-OFFSET=1.4,PRECISE=YES\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, 'start', 'the tag type is start');
|
|
assert.strictEqual(element.attributes['TIME-OFFSET'], 1.4, 'parses time offset');
|
|
assert.strictEqual(element.attributes.PRECISE, true, 'parses precise attribute');
|
|
});
|
|
QUnit.test('flags missing EXT-X-START TIME-OFFSET attribute', function(assert) {
|
|
const parser = new Parser();
|
|
|
|
const manifest = [
|
|
'#EXT-X-VERSION:3',
|
|
'#EXT-X-TARGETDURATION:10',
|
|
'#EXT-X-START:PRECISE=YES',
|
|
'#EXTINF:10,',
|
|
'media-00001.ts',
|
|
'#EXT-X-ENDLIST'
|
|
].join('\n');
|
|
let warning;
|
|
|
|
parser.on('warn', function(warn) {
|
|
warning = warn;
|
|
});
|
|
parser.push(manifest);
|
|
|
|
assert.ok(warning, 'a warning was triggered');
|
|
assert.ok((/ignoring start/).test(warning.message), 'message is about start tag');
|
|
assert.strictEqual(typeof parser.manifest.start, 'undefined', 'does not parse start');
|
|
});
|
|
|
|
QUnit.test('ignores empty lines', function(assert) {
|
|
const manifest = '\n';
|
|
let event = false;
|
|
|
|
this.parseStream.on('data', function() {
|
|
event = true;
|
|
});
|
|
this.lineStream.push(manifest);
|
|
|
|
assert.ok(!event, 'no event is triggered');
|
|
});
|
|
|
|
QUnit.module('m3u8 parser');
|
|
|
|
QUnit.test('can be constructed', function(assert) {
|
|
assert.notStrictEqual(typeof new Parser(), 'undefined', 'parser is defined');
|
|
});
|
|
|
|
QUnit.test('can set custom parsers', function(assert) {
|
|
const parser = new Parser();
|
|
const manifest = [
|
|
'#EXTM3U',
|
|
'#EXT-X-VERSION:3',
|
|
'#EXT-X-TARGETDURATION:10',
|
|
'#EXT-X-MEDIA-SEQUENCE:0',
|
|
'#EXT-X-PROGRAM-DATE-TIME:2017-07-31T20:35:35.053+00:00',
|
|
'#VOD-STARTTIMESTAMP:1501533337573',
|
|
'#VOD-TOTALDELETEDDURATION:0.0',
|
|
'#VOD-FRAMERATE:29.97',
|
|
''
|
|
].join('\n');
|
|
|
|
parser.addParser({
|
|
expression: /^#VOD-STARTTIMESTAMP/,
|
|
customType: 'startTimestamp'
|
|
});
|
|
parser.addParser({
|
|
expression: /^#VOD-TOTALDELETEDDURATION/,
|
|
customType: 'totalDeleteDuration'
|
|
});
|
|
parser.addParser({
|
|
expression: /^#VOD-FRAMERATE/,
|
|
customType: 'framerate',
|
|
dataParser: (line) => (line.split(':')[1])
|
|
});
|
|
|
|
parser.push(manifest);
|
|
assert.strictEqual(
|
|
parser.manifest.custom.startTimestamp,
|
|
'#VOD-STARTTIMESTAMP:1501533337573',
|
|
'sets custom timestamp line'
|
|
);
|
|
|
|
assert.strictEqual(
|
|
parser.manifest.custom.totalDeleteDuration,
|
|
'#VOD-TOTALDELETEDDURATION:0.0',
|
|
'sets custom delete duration'
|
|
);
|
|
|
|
assert.strictEqual(parser.manifest.custom.framerate, '29.97', 'sets framerate');
|
|
});
|
|
|
|
QUnit.test('segment level custom data', function(assert) {
|
|
const parser = new Parser();
|
|
|
|
const manifest = [
|
|
'#EXTM3U',
|
|
'#VOD-TIMING:1511816599485',
|
|
'#COMMENT',
|
|
'#EXTINF:8.0,',
|
|
'ex1.ts',
|
|
'#VOD-TIMING',
|
|
'#EXTINF:8.0,',
|
|
'ex2.ts',
|
|
'#VOD-TIMING:1511816615485',
|
|
'#EXT-UNKNOWN',
|
|
'#EXTINF:8.0,',
|
|
'ex3.ts',
|
|
'#VOD-TIMING:1511816623485',
|
|
'#EXTINF:8.0,',
|
|
'ex3.ts',
|
|
'#EXT-X-ENDLIST'
|
|
].join('\n');
|
|
|
|
parser.addParser({
|
|
expression: /^#VOD-TIMING/,
|
|
customType: 'vodTiming',
|
|
segment: true
|
|
});
|
|
|
|
parser.push(manifest);
|
|
assert.equal(
|
|
parser.manifest.segments[0].custom.vodTiming,
|
|
'#VOD-TIMING:1511816599485',
|
|
'parser attached segment level custom data'
|
|
);
|
|
assert.equal(
|
|
parser.manifest.segments[1].custom.vodTiming,
|
|
'#VOD-TIMING',
|
|
'parser got segment level custom data without :'
|
|
);
|
|
});
|
|
|
|
QUnit.test('attaches cue-out data to segment', function(assert) {
|
|
const parser = new Parser();
|
|
|
|
const manifest = [
|
|
'#EXTM3U',
|
|
'#EXTINF:5,',
|
|
'#COMMENT',
|
|
'ex1.ts',
|
|
'#EXT-X-CUE-OUT:10',
|
|
'#EXTINF:5,',
|
|
'ex2.ts',
|
|
'#EXT-X-CUE-OUT15',
|
|
'#EXT-UKNOWN-TAG',
|
|
'#EXTINF:5,',
|
|
'ex3.ts',
|
|
'#EXT-X-CUE-OUT',
|
|
'#EXTINF:5,',
|
|
'ex3.ts',
|
|
'#EXT-X-ENDLIST'
|
|
].join('\n');
|
|
|
|
parser.push(manifest);
|
|
|
|
assert.equal(parser.manifest.segments[1].cueOut, '10', 'parser attached cue out tag');
|
|
assert.equal(parser.manifest.segments[2].cueOut, '15', 'cue out without : seperator');
|
|
assert.equal(parser.manifest.segments[3].cueOut, '', 'cue out without data');
|
|
});
|
|
|
|
QUnit.test('attaches cue-out-cont data to segment', function(assert) {
|
|
const parser = new Parser();
|
|
|
|
const manifest = [
|
|
'#EXTM3U',
|
|
'#EXTINF:5,',
|
|
'#COMMENT',
|
|
'ex1.ts',
|
|
'#EXT-X-CUE-OUT-CONT:10/60',
|
|
'#EXTINF:5,',
|
|
'ex2.ts',
|
|
'#EXT-X-CUE-OUT-CONT15/30',
|
|
'#EXT-UKNOWN-TAG',
|
|
'#EXTINF:5,',
|
|
'ex3.ts',
|
|
'#EXT-X-CUE-OUT-CONT',
|
|
'#EXTINF:5,',
|
|
'ex3.ts',
|
|
'#EXT-X-ENDLIST'
|
|
].join('\n');
|
|
|
|
parser.push(manifest);
|
|
|
|
assert.equal(
|
|
parser.manifest.segments[1].cueOutCont, '10/60',
|
|
'parser attached cue out cont tag'
|
|
);
|
|
assert.equal(
|
|
parser.manifest.segments[2].cueOutCont, '15/30',
|
|
'cue out cont without : seperator'
|
|
);
|
|
assert.equal(parser.manifest.segments[3].cueOutCont, '', 'cue out cont without data');
|
|
});
|
|
|
|
QUnit.test('attaches cue-in data to segment', function(assert) {
|
|
const parser = new Parser();
|
|
|
|
const manifest = [
|
|
'#EXTM3U',
|
|
'#EXTINF:5,',
|
|
'#COMMENT',
|
|
'ex1.ts',
|
|
'#EXT-X-CUE-IN',
|
|
'#EXTINF:5,',
|
|
'ex2.ts',
|
|
'#EXT-X-CUE-IN:15',
|
|
'#EXT-UKNOWN-TAG',
|
|
'#EXTINF:5,',
|
|
'ex3.ts',
|
|
'#EXT-X-CUE-IN=abc',
|
|
'#EXTINF:5,',
|
|
'ex3.ts',
|
|
'#EXT-X-ENDLIST'
|
|
].join('\n');
|
|
|
|
parser.push(manifest);
|
|
|
|
assert.equal(parser.manifest.segments[1].cueIn, '', 'parser attached cue in tag');
|
|
assert.equal(parser.manifest.segments[2].cueIn, '15', 'cue in with data');
|
|
assert.equal(
|
|
parser.manifest.segments[3].cueIn, '=abc',
|
|
'cue in without colon seperator'
|
|
);
|
|
});
|
|
|
|
QUnit.test('parses characteristics attribute', function(assert) {
|
|
const parser = new Parser();
|
|
|
|
const manifest = [
|
|
'#EXTM3U',
|
|
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",CHARACTERISTICS="char",NAME="test"',
|
|
'#EXT-X-STREAM-INF:BANDWIDTH=1,CODECS="mp4a.40.2, avc1.4d400d",SUBTITLES="subs"',
|
|
'index.m3u8'
|
|
].join('\n');
|
|
|
|
parser.push(manifest);
|
|
|
|
assert.equal(
|
|
parser.manifest.mediaGroups.SUBTITLES.subs.test.characteristics,
|
|
'char',
|
|
'parsed CHARACTERISTICS attribute'
|
|
);
|
|
});
|
|
|
|
QUnit.test('parses FORCED attribute', function(assert) {
|
|
const parser = new Parser();
|
|
|
|
const manifest = [
|
|
'#EXTM3U',
|
|
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",CHARACTERISTICS="char",NAME="test",FORCED=YES',
|
|
'#EXT-X-STREAM-INF:BANDWIDTH=1,CODECS="mp4a.40.2, avc1.4d400d",SUBTITLES="subs"',
|
|
'index.m3u8'
|
|
].join('\n');
|
|
|
|
parser.push(manifest);
|
|
|
|
assert.ok(
|
|
parser.manifest.mediaGroups.SUBTITLES.subs.test.forced,
|
|
'parsed FORCED attribute'
|
|
);
|
|
});
|
|
|
|
QUnit.test('parses Widevine #EXT-X-KEY attributes and attaches to manifest', function(assert) {
|
|
const parser = new Parser();
|
|
|
|
const manifest = [
|
|
'#EXTM3U',
|
|
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,' +
|
|
'URI="data:text/plain;base64,AAAAPnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAB4iFnN' +
|
|
'oYWthX2NlYzJmNjRhYTc4OTBhMTFI49yVmwY=",KEYID=0x800AACAA522958AE888062B5695DB6BF,' +
|
|
'KEYFORMATVERSIONS="1",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"',
|
|
'#EXTINF:5,',
|
|
'ex1.ts',
|
|
'#EXT-X-ENDLIST'
|
|
].join('\n');
|
|
|
|
parser.push(manifest);
|
|
|
|
assert.ok(parser.manifest.contentProtection, 'contentProtection property added');
|
|
assert.equal(
|
|
parser.manifest.contentProtection['com.widevine.alpha'].attributes.schemeIdUri,
|
|
'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed',
|
|
'schemeIdUri set correctly'
|
|
);
|
|
assert.equal(
|
|
parser.manifest.contentProtection['com.widevine.alpha'].attributes.keyId,
|
|
'800AACAA522958AE888062B5695DB6BF',
|
|
'keyId set correctly'
|
|
);
|
|
assert.equal(
|
|
parser.manifest.contentProtection['com.widevine.alpha'].pssh.byteLength,
|
|
62,
|
|
'base64 URI decoded to TypedArray'
|
|
);
|
|
});
|
|
|
|
QUnit.test('Widevine #EXT-X-KEY attributes not attached to manifest if METHOD is invalid', function(assert) {
|
|
const parser = new Parser();
|
|
|
|
const manifest = [
|
|
'#EXTM3U',
|
|
'#EXT-X-KEY:METHOD=NONE,' +
|
|
'URI="data:text/plain;base64,AAAAPnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAB4iFnN' +
|
|
'oYWthX2NlYzJmNjRhYTc4OTBhMTFI49yVmwY=",KEYID=0x800AACAA522958AE888062B5695DB6BF,' +
|
|
'KEYFORMATVERSIONS="1",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"',
|
|
'#EXTINF:5,',
|
|
'ex1.ts',
|
|
'#EXT-X-ENDLIST'
|
|
].join('\n');
|
|
|
|
parser.push(manifest);
|
|
|
|
assert.notOk(parser.manifest.contentProtection, 'contentProtection not added');
|
|
});
|
|
|
|
QUnit.test('Widevine #EXT-X-KEY attributes not attached to manifest if URI is invalid', function(assert) {
|
|
const parser = new Parser();
|
|
|
|
const manifest = [
|
|
'#EXTM3U',
|
|
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,' +
|
|
'URI="AAAAPnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAB4iFnN' +
|
|
'oYWthX2NlYzJmNjRhYTc4OTBhMTFI49yVmwY=",KEYID=0x800AACAA522958AE888062B5695DB6BF,' +
|
|
'KEYFORMATVERSIONS="1",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"',
|
|
'#EXTINF:5,',
|
|
'ex1.ts',
|
|
'#EXT-X-ENDLIST'
|
|
].join('\n');
|
|
|
|
parser.push(manifest);
|
|
|
|
assert.notOk(parser.manifest.contentProtection, 'contentProtection not added');
|
|
});
|
|
|
|
QUnit.test('Widevine #EXT-X-KEY attributes not attached to manifest if KEYID is invalid', function(assert) {
|
|
const parser = new Parser();
|
|
|
|
const manifest = [
|
|
'#EXTM3U',
|
|
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,' +
|
|
'URI="data:text/plain;base64,AAAAPnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAB4iFnN' +
|
|
'oYWthX2NlYzJmNjRhYTc4OTBhMTFI49yVmwY=",KEYID=800AACAA522958AE888062B5695DB6BF,' +
|
|
'KEYFORMATVERSIONS="1",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"',
|
|
'#EXTINF:5,',
|
|
'ex1.ts',
|
|
'#EXT-X-ENDLIST'
|
|
].join('\n');
|
|
|
|
parser.push(manifest);
|
|
|
|
assert.notOk(parser.manifest.contentProtection, 'contentProtection not added');
|
|
});
|
|
|
|
QUnit.test('Widevine #EXT-X-KEY attributes not attached to manifest if KEYFORMAT is not Widevine UUID', function(assert) {
|
|
const parser = new Parser();
|
|
|
|
const manifest = [
|
|
'#EXTM3U',
|
|
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,' +
|
|
'URI="data:text/plain;base64,AAAAPnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAB4iFnN' +
|
|
'oYWthX2NlYzJmNjRhYTc4OTBhMTFI49yVmwY=",KEYID=0x800AACAA522958AE888062B5695DB6BF,' +
|
|
'KEYFORMATVERSIONS="1",KEYFORMAT="invalid-keyformat"',
|
|
'#EXTINF:5,',
|
|
'ex1.ts',
|
|
'#EXT-X-ENDLIST'
|
|
].join('\n');
|
|
|
|
parser.push(manifest);
|
|
|
|
assert.notOk(parser.manifest.contentProtection, 'contentProtection not added');
|
|
});
|
|
|
|
QUnit.test('byterange offset defaults to next byte', function(assert) {
|
|
const parser = new Parser();
|
|
|
|
const manifest = [
|
|
'#EXTM3U',
|
|
'#EXTINF:5,',
|
|
'#EXT-X-BYTERANGE:10@5',
|
|
'segment.ts',
|
|
'#EXTINF:5,',
|
|
'#EXT-X-BYTERANGE:20',
|
|
'segment.ts',
|
|
'#EXTINF:5,',
|
|
'#EXT-X-BYTERANGE:30',
|
|
'segment.ts',
|
|
'#EXTINF:5,',
|
|
'segment2.ts',
|
|
'#EXT-X-BYTERANGE:15@100',
|
|
'segment.ts',
|
|
'#EXT-X-BYTERANGE:17',
|
|
'segment.ts',
|
|
'#EXT-X-ENDLIST'
|
|
].join('\n');
|
|
|
|
parser.push(manifest);
|
|
|
|
assert.deepEqual(
|
|
parser.manifest.segments[0].byterange,
|
|
{ length: 10, offset: 5 },
|
|
'first segment has correct byterange'
|
|
);
|
|
assert.deepEqual(
|
|
parser.manifest.segments[1].byterange,
|
|
{ length: 20, offset: 15 },
|
|
'second segment has correct byterange'
|
|
);
|
|
assert.deepEqual(
|
|
parser.manifest.segments[2].byterange,
|
|
{ length: 30, offset: 35 },
|
|
'third segment has correct byterange'
|
|
);
|
|
assert.notOk(parser.manifest.segments[3].byterange, 'fourth segment has no byterange');
|
|
assert.deepEqual(
|
|
parser.manifest.segments[4].byterange,
|
|
{ length: 15, offset: 100 },
|
|
'fifth segment has correct byterange'
|
|
);
|
|
// not tested is a segment with no offset coming after a segment that isn't a sub range,
|
|
// as the spec requires that a byterange without an offset must follow a segment that
|
|
// is a sub range of the same media resource
|
|
assert.deepEqual(
|
|
parser.manifest.segments[5].byterange,
|
|
{ length: 17, offset: 115 },
|
|
'sixth segment has correct byterange'
|
|
);
|
|
});
|
|
|
|
QUnit.module('m3u8s');
|
|
|
|
QUnit.test('parses static manifests as expected', function(assert) {
|
|
let key;
|
|
|
|
for (key in testDataManifests) {
|
|
if (testDataExpected[key]) {
|
|
const parser = new Parser();
|
|
|
|
parser.push(testDataManifests[key]);
|
|
assert.deepEqual(
|
|
parser.manifest,
|
|
testDataExpected[key],
|
|
key + '.m3u8 was parsed correctly'
|
|
);
|
|
}
|
|
}
|
|
});
|