08ba349e by brandonocasey

browserify-p2: m3u8, stream, and stub

stub old functionality to get unit tests working for now
stub old test functionality to get unit tests working for now
fixed some issues with the first browserify part
convert m3u8 and stream modules to es6/node/browserify
fixed npm scripts to work for current branch
1 parent b0caca88
...@@ -40,14 +40,17 @@ ...@@ -40,14 +40,17 @@
40 </label> 40 </label>
41 <button type=submit>Load</button> 41 <button type=submit>Load</button>
42 </form> 42 </form>
43 <ul>
44 <li><a href="/test/">Run unit tests in browser.</a></li>
45 <li><a href="/docs/api/">Read generated docs.</a></li>
46 </ul>
43 47
44 <script src="/node_modules/video.js/dist/video.js"></script> 48 <script src="/node_modules/video.js/dist/video.js"></script>
45 <script src="/node_modules/videojs-contrib-media-sources/dist/videojs-media-sources.js"></script> 49 <script src="/node_modules/videojs-contrib-media-sources/dist/videojs-media-sources.js"></script>
46 <script src="/node_modules/pkcs7/dist/pkcs7.unpad.js"></script> 50 <script src="/node_modules/pkcs7/dist/pkcs7.unpad.js"></script>
47 <script src="/src/videojs-hls.js"></script> 51 <script src="/src/videojs-contrib-hls.js"></script>
48 <script src="/src/xhr.js"></script> 52 <script src="/src/xhr.js"></script>
49 <script src="/src/stream.js"></script> 53 <script src="/dist/videojs-contrib-hls.js"></script>
50 <script src="/src/m3u8/m3u8-parser.js"></script>
51 <script src="/src/playlist.js"></script> 54 <script src="/src/playlist.js"></script>
52 <script src="/src/playlist-loader.js"></script> 55 <script src="/src/playlist-loader.js"></script>
53 <script src="/src/decrypter.js"></script> 56 <script src="/src/decrypter.js"></script>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
2 "name": "videojs-contrib-hls", 2 "name": "videojs-contrib-hls",
3 "version": "1.3.5", 3 "version": "1.3.5",
4 "description": "Play back HLS with video.js, even where it's not natively supported", 4 "description": "Play back HLS with video.js, even where it's not natively supported",
5 "main": "es5/videojs-hls.js", 5 "main": "es5/stub.js",
6 "engines": { 6 "engines": {
7 "node": ">= 0.10.12" 7 "node": ">= 0.10.12"
8 }, 8 },
...@@ -13,16 +13,17 @@ ...@@ -13,16 +13,17 @@
13 "scripts": { 13 "scripts": {
14 "prebuild": "npm run clean", 14 "prebuild": "npm run clean",
15 "build": "npm-run-all -p build:*", 15 "build": "npm-run-all -p build:*",
16 "build:manifest": "node -e \"var b=require('./scripts/manifest-data.js'); b.build();\"",
17 "build:js": "npm-run-all build:js:babel build:js:browserify build:js:bannerize build:js:uglify", 16 "build:js": "npm-run-all build:js:babel build:js:browserify build:js:bannerize build:js:uglify",
18 "build:js:babel": "babel src -d es5", 17 "build:js:babel": "babel src -d es5",
19 "build:js:bannerize": "bannerize dist/videojs-contrib-hls.js --banner=scripts/banner.ejs", 18 "build:js:bannerize": "bannerize dist/videojs-contrib-hls.js --banner=scripts/banner.ejs",
20 "build:js:browserify": "browserify . -s src/videojs-hls.js -o dist/videojs-contrib-hls.js", 19 "build:js:browserify": "browserify . -s videojs-contrib-hls -o dist/videojs-contrib-hls.js",
21 "build:js:uglify": "uglifyjs dist/videojs-contrib-hls.js --comments --mangle --compress -o dist/videojs-contrib-hls.min.js", 20 "build:js:uglify": "uglifyjs dist/videojs-contrib-hls.js --comments --mangle --compress -o dist/videojs-contrib-hls.min.js",
22 "build:test": "node scripts/build-test.js", 21 "build:test": "npm-run-all build:test:manifest build:test:js",
23 "clean": "npm-run-all clean:*", 22 "build:test:js": "node scripts/build-test.js",
23 "build:test:manifest": "node -e \"var b=require('./scripts/manifest-data.js'); b.build();\"",
24 "clean": "npm-run-all -p clean:*",
24 "clean:build": "node -e \"var s=require('shelljs'),d=['dist','dist-test','es5'];s.rm('-rf',d);s.mkdir('-p',d);\"", 25 "clean:build": "node -e \"var s=require('shelljs'),d=['dist','dist-test','es5'];s.rm('-rf',d);s.mkdir('-p',d);\"",
25 "clean:manifest": "node -e \"var b=require('./scripts/manifest-data.js'); b.clean();\"", 26 "clean:test": "node -e \"var b=require('./scripts/manifest-data.js'); b.clean();\"",
26 "docs": "npm-run-all docs:*", 27 "docs": "npm-run-all docs:*",
27 "docs:api": "jsdoc src -r -d docs/api", 28 "docs:api": "jsdoc src -r -d docs/api",
28 "docs:toc": "doctoc README.md", 29 "docs:toc": "doctoc README.md",
...@@ -39,9 +40,10 @@ ...@@ -39,9 +40,10 @@
39 "preversion": "npm test", 40 "preversion": "npm test",
40 "version": "npm run build", 41 "version": "npm run build",
41 "watch": "npm-run-all -p watch:*", 42 "watch": "npm-run-all -p watch:*",
42 "watch:manifest": "node -e \"var b=require('./scripts/manifest-data.js'); b.watch();\"", 43 "watch:js": "watchify src/stub.js -t babelify -v -o dist/videojs-contrib-hls.js",
43 "watch:js": "watchify src/videojs-hls.js -t babelify -v -o dist/videojs-contrib-hls.js", 44 "watch:test": "npm-run-all -p watch:test:*",
44 "watch:test": "node scripts/watch-test.js", 45 "watch:test:js": "node scripts/watch-test.js",
46 "watch:test:manifest": "node -e \"var b=require('./scripts/manifest-data.js'); b.watch();\"",
45 "prepublish": "npm run build" 47 "prepublish": "npm run build"
46 }, 48 },
47 "keywords": [ 49 "keywords": [
......
...@@ -2,7 +2,7 @@ var browserify = require('browserify'); ...@@ -2,7 +2,7 @@ var browserify = require('browserify');
2 var fs = require('fs'); 2 var fs = require('fs');
3 var glob = require('glob'); 3 var glob = require('glob');
4 4
5 glob('test/**/*.test.js', function(err, files) { 5 glob('test/{m3u8,stub}.test.js', function(err, files) {
6 browserify(files) 6 browserify(files)
7 .transform('babelify') 7 .transform('babelify')
8 .bundle() 8 .bundle()
......
...@@ -9,8 +9,8 @@ var expectedFilepath = testDataDir + '/expected.js'; ...@@ -9,8 +9,8 @@ var expectedFilepath = testDataDir + '/expected.js';
9 9
10 10
11 var build = function() { 11 var build = function() {
12 var manifests = 'window.manifests = {\n'; 12 var manifests = 'export default {\n';
13 var expected = 'window.expected = {\n'; 13 var expected = 'export default {\n';
14 14
15 var files = fs.readdirSync(manifestDir); 15 var files = fs.readdirSync(manifestDir);
16 while (files.length > 0) { 16 while (files.length > 0) {
......
...@@ -3,7 +3,7 @@ var fs = require('fs'); ...@@ -3,7 +3,7 @@ var fs = require('fs');
3 var glob = require('glob'); 3 var glob = require('glob');
4 var watchify = require('watchify'); 4 var watchify = require('watchify');
5 5
6 glob('test/**/*.test.js', function(err, files) { 6 glob('test/{m3u8,stub}.test.js', function(err, files) {
7 var b = browserify(files, { 7 var b = browserify(files, {
8 cache: {}, 8 cache: {},
9 packageCache: {}, 9 packageCache: {},
......
1 {
2 "curly": true,
3 "eqeqeq": true,
4 "globals": {
5 "console": true
6 },
7 "immed": true,
8 "latedef": true,
9 "newcap": true,
10 "noarg": true,
11 "sub": true,
12 "undef": true,
13 "unused": true,
14 "boss": true,
15 "eqnull": true,
16 "browser": true
17 }
...@@ -5,79 +5,38 @@ ...@@ -5,79 +5,38 @@
5 * that do not assume the entirety of the manifest is ready and expose a 5 * that do not assume the entirety of the manifest is ready and expose a
6 * ReadableStream-like interface. 6 * ReadableStream-like interface.
7 */ 7 */
8 (function(videojs, parseInt, isFinite, mergeOptions, undefined) {
9 var
10 noop = function() {},
11
12 // "forgiving" attribute list psuedo-grammar:
13 // attributes -> keyvalue (',' keyvalue)*
14 // keyvalue -> key '=' value
15 // key -> [^=]*
16 // value -> '"' [^"]* '"' | [^,]*
17 attributeSeparator = (function() {
18 var
19 key = '[^=]*',
20 value = '"[^"]*"|[^,]*',
21 keyvalue = '(?:' + key + ')=(?:' + value + ')';
22 8
23 return new RegExp('(?:^|,)(' + keyvalue + ')'); 9 import Parser from './parser';
24 })(), 10 import Stream from '../stream';
25 parseAttributes = function(attributes) { 11 import {mergeOptions} from 'video.js';
26 var 12 /**
27 // split the string using attributes as the separator
28 attrs = attributes.split(attributeSeparator),
29 i = attrs.length,
30 result = {},
31 attr;
32
33 while (i--) {
34 // filter out unmatched portions of the string
35 if (attrs[i] === '') {
36 continue;
37 }
38
39 // split the key and value
40 attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1);
41 // trim whitespace and remove optional quotes around the value
42 attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
43 attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
44 attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
45 result[attr[0]] = attr[1];
46 }
47 return result;
48 },
49 Stream = videojs.Hls.Stream,
50 LineStream,
51 ParseStream,
52 Parser;
53
54 /**
55 * A stream that buffers string input and generates a `data` event for each 13 * A stream that buffers string input and generates a `data` event for each
56 * line. 14 * line.
57 */ 15 */
58 LineStream = function() { 16 export class LineStream extends Stream {
59 var buffer = ''; 17 constructor() {
60 LineStream.prototype.init.call(this); 18 super();
19 this.buffer = '';
20 }
61 21
62 /** 22 /**
63 * Add new data to be parsed. 23 * Add new data to be parsed.
64 * @param data {string} the text to process 24 * @param data {string} the text to process
65 */ 25 */
66 this.push = function(data) { 26 push(data) {
67 var nextNewline; 27 let nextNewline;
68 28
69 buffer += data; 29 this.buffer += data;
70 nextNewline = buffer.indexOf('\n'); 30 nextNewline = this.buffer.indexOf('\n');
71 31
72 for (; nextNewline > -1; nextNewline = buffer.indexOf('\n')) { 32 for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
73 this.trigger('data', buffer.substring(0, nextNewline)); 33 this.trigger('data', this.buffer.substring(0, nextNewline));
74 buffer = buffer.substring(nextNewline + 1); 34 this.buffer = this.buffer.substring(nextNewline + 1);
75 } 35 }
76 }; 36 }
77 }; 37 }
78 LineStream.prototype = new Stream();
79 38
80 /** 39 /**
81 * A line-level M3U8 parser event stream. It expects to receive input one 40 * A line-level M3U8 parser event stream. It expects to receive input one
82 * line at a time and performs a context-free parse of its contents. A stream 41 * line at a time and performs a context-free parse of its contents. A stream
83 * interpretation of a manifest can be useful if the manifest is expected to 42 * interpretation of a manifest can be useful if the manifest is expected to
...@@ -98,18 +57,59 @@ ...@@ -98,18 +57,59 @@
98 * tags are given the tag type `unknown` and a single additional property 57 * tags are given the tag type `unknown` and a single additional property
99 * `data` with the remainder of the input. 58 * `data` with the remainder of the input.
100 */ 59 */
101 ParseStream = function() { 60
102 ParseStream.prototype.init.call(this); 61
103 }; 62 // "forgiving" attribute list psuedo-grammar:
104 ParseStream.prototype = new Stream(); 63 // attributes -> keyvalue (',' keyvalue)*
64 // keyvalue -> key '=' value
65 // key -> [^=]*
66 // value -> '"' [^"]* '"' | [^,]*
67 const attributeSeparator = function() {
68 let key = '[^=]*';
69 let value = '"[^"]*"|[^,]*';
70 let keyvalue = '(?:' + key + ')=(?:' + value + ')';
71
72 return new RegExp('(?:^|,)(' + keyvalue + ')');
73 };
74
75 const parseAttributes = function(attributes) {
76 // split the string using attributes as the separator
77 let attrs = attributes.split(attributeSeparator());
78 let i = attrs.length;
79 let result = {};
80 let attr;
81
82 while (i--) {
83 // filter out unmatched portions of the string
84 if (attrs[i] === '') {
85 continue;
86 }
87
88 // split the key and value
89 attr = (/([^=]*)=(.*)/).exec(attrs[i]).slice(1);
90 // trim whitespace and remove optional quotes around the value
91 attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
92 attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
93 attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
94 result[attr[0]] = attr[1];
95 }
96 return result;
97 };
98
99 export class ParseStream extends Stream {
100 constructor() {
101 super();
102 }
103
105 /** 104 /**
106 * Parses an additional line of input. 105 * Parses an additional line of input.
107 * @param line {string} a single line of an M3U8 file to parse 106 * @param line {string} a single line of an M3U8 file to parse
108 */ 107 */
109 ParseStream.prototype.push = function(line) { 108 push(line) {
110 var match, event; 109 let match;
110 let event;
111 111
112 //strip whitespace 112 // strip whitespace
113 line = line.replace(/^[\u0000\s]+|[\u0000\s]+$/g, ''); 113 line = line.replace(/^[\u0000\s]+|[\u0000\s]+$/g, '');
114 if (line.length === 0) { 114 if (line.length === 0) {
115 // ignore empty lines 115 // ignore empty lines
...@@ -134,12 +134,12 @@ ...@@ -134,12 +134,12 @@
134 return; 134 return;
135 } 135 }
136 136
137 //strip off any carriage returns here so the regex matching 137 // strip off any carriage returns here so the regex matching
138 //doesn't have to account for them. 138 // doesn't have to account for them.
139 line = line.replace('\r',''); 139 line = line.replace('\r', '');
140 140
141 // Tags 141 // Tags
142 match = /^#EXTM3U/.exec(line); 142 match = (/^#EXTM3U/).exec(line);
143 if (match) { 143 if (match) {
144 this.trigger('data', { 144 this.trigger('data', {
145 type: 'tag', 145 type: 'tag',
...@@ -271,10 +271,9 @@ ...@@ -271,10 +271,9 @@
271 event.attributes = parseAttributes(match[1]); 271 event.attributes = parseAttributes(match[1]);
272 272
273 if (event.attributes.RESOLUTION) { 273 if (event.attributes.RESOLUTION) {
274 (function() { 274 let split = event.attributes.RESOLUTION.split('x');
275 var 275 let resolution = {};
276 split = event.attributes.RESOLUTION.split('x'), 276
277 resolution = {};
278 if (split[0]) { 277 if (split[0]) {
279 resolution.width = parseInt(split[0], 10); 278 resolution.width = parseInt(split[0], 10);
280 } 279 }
...@@ -282,7 +281,6 @@ ...@@ -282,7 +281,6 @@
282 resolution.height = parseInt(split[1], 10); 281 resolution.height = parseInt(split[1], 10);
283 } 282 }
284 event.attributes.RESOLUTION = resolution; 283 event.attributes.RESOLUTION = resolution;
285 })();
286 } 284 }
287 if (event.attributes.BANDWIDTH) { 285 if (event.attributes.BANDWIDTH) {
288 event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10); 286 event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
...@@ -320,7 +318,7 @@ ...@@ -320,7 +318,7 @@
320 event.attributes = parseAttributes(match[1]); 318 event.attributes = parseAttributes(match[1]);
321 // parse the IV string into a Uint32Array 319 // parse the IV string into a Uint32Array
322 if (event.attributes.IV) { 320 if (event.attributes.IV) {
323 if (event.attributes.IV.substring(0,2) === '0x') { 321 if (event.attributes.IV.substring(0, 2) === '0x') {
324 event.attributes.IV = event.attributes.IV.substring(2); 322 event.attributes.IV = event.attributes.IV.substring(2);
325 } 323 }
326 324
...@@ -341,37 +339,23 @@ ...@@ -341,37 +339,23 @@
341 type: 'tag', 339 type: 'tag',
342 data: line.slice(4, line.length) 340 data: line.slice(4, line.length)
343 }); 341 });
344 }; 342 }
343 }
345 344
346 /**
347 * A parser for M3U8 files. The current interpretation of the input is
348 * exposed as a property `manifest` on parser objects. It's just two lines to
349 * create and parse a manifest once you have the contents available as a string:
350 *
351 * ```js
352 * var parser = new videojs.m3u8.Parser();
353 * parser.push(xhr.responseText);
354 * ```
355 *
356 * New input can later be applied to update the manifest object by calling
357 * `push` again.
358 *
359 * The parser attempts to create a usable manifest object even if the
360 * underlying input is somewhat nonsensical. It emits `info` and `warning`
361 * events during the parse if it encounters input that seems invalid or
362 * requires some property of the manifest object to be defaulted.
363 */
364 Parser = function() {
365 var
366 self = this,
367 uris = [],
368 currentUri = {},
369 key;
370 Parser.prototype.init.call(this);
371 345
346 export default class Parser extends Stream {
347 constructor() {
348 super();
372 this.lineStream = new LineStream(); 349 this.lineStream = new LineStream();
373 this.parseStream = new ParseStream(); 350 this.parseStream = new ParseStream();
374 this.lineStream.pipe(this.parseStream); 351 this.lineStream.pipe(this.parseStream);
352 /* eslint-disable consistent-this */
353 let self = this;
354 /* eslint-enable consistent-this */
355 let uris = [];
356 let currentUri = {};
357 let key;
358 let noop = function() {};
375 359
376 // the manifest is empty until the parse stream begins delivering data 360 // the manifest is empty until the parse stream begins delivering data
377 this.manifest = { 361 this.manifest = {
...@@ -382,10 +366,10 @@ ...@@ -382,10 +366,10 @@
382 // update the manifest with the m3u8 entry from the parse stream 366 // update the manifest with the m3u8 entry from the parse stream
383 this.parseStream.on('data', function(entry) { 367 this.parseStream.on('data', function(entry) {
384 ({ 368 ({
385 tag: function() { 369 tag() {
386 // switch based on the tag type 370 // switch based on the tag type
387 (({ 371 (({
388 'allow-cache': function() { 372 'allow-cache'() {
389 this.manifest.allowCache = entry.allowed; 373 this.manifest.allowCache = entry.allowed;
390 if (!('allowed' in entry)) { 374 if (!('allowed' in entry)) {
391 this.trigger('info', { 375 this.trigger('info', {
...@@ -394,8 +378,9 @@ ...@@ -394,8 +378,9 @@
394 this.manifest.allowCache = true; 378 this.manifest.allowCache = true;
395 } 379 }
396 }, 380 },
397 'byterange': function() { 381 byterange() {
398 var byterange = {}; 382 let byterange = {};
383
399 if ('length' in entry) { 384 if ('length' in entry) {
400 currentUri.byterange = byterange; 385 currentUri.byterange = byterange;
401 byterange.length = entry.length; 386 byterange.length = entry.length;
...@@ -412,10 +397,10 @@ ...@@ -412,10 +397,10 @@
412 byterange.offset = entry.offset; 397 byterange.offset = entry.offset;
413 } 398 }
414 }, 399 },
415 'endlist': function() { 400 endlist() {
416 this.manifest.endList = true; 401 this.manifest.endList = true;
417 }, 402 },
418 'inf': function() { 403 inf() {
419 if (!('mediaSequence' in this.manifest)) { 404 if (!('mediaSequence' in this.manifest)) {
420 this.manifest.mediaSequence = 0; 405 this.manifest.mediaSequence = 0;
421 this.trigger('info', { 406 this.trigger('info', {
...@@ -435,7 +420,7 @@ ...@@ -435,7 +420,7 @@
435 this.manifest.segments = uris; 420 this.manifest.segments = uris;
436 421
437 }, 422 },
438 'key': function() { 423 key() {
439 if (!entry.attributes) { 424 if (!entry.attributes) {
440 this.trigger('warn', { 425 this.trigger('warn', {
441 message: 'ignoring key declaration without attribute list' 426 message: 'ignoring key declaration without attribute list'
...@@ -465,11 +450,11 @@ ...@@ -465,11 +450,11 @@
465 uri: entry.attributes.URI 450 uri: entry.attributes.URI
466 }; 451 };
467 452
468 if (entry.attributes.IV !== undefined) { 453 if (typeof entry.attributes.IV !== 'undefined') {
469 key.iv = entry.attributes.IV; 454 key.iv = entry.attributes.IV;
470 } 455 }
471 }, 456 },
472 'media-sequence': function() { 457 'media-sequence'() {
473 if (!isFinite(entry.number)) { 458 if (!isFinite(entry.number)) {
474 this.trigger('warn', { 459 this.trigger('warn', {
475 message: 'ignoring invalid media sequence: ' + entry.number 460 message: 'ignoring invalid media sequence: ' + entry.number
...@@ -478,7 +463,7 @@ ...@@ -478,7 +463,7 @@
478 } 463 }
479 this.manifest.mediaSequence = entry.number; 464 this.manifest.mediaSequence = entry.number;
480 }, 465 },
481 'discontinuity-sequence': function() { 466 'discontinuity-sequence'() {
482 if (!isFinite(entry.number)) { 467 if (!isFinite(entry.number)) {
483 this.trigger('warn', { 468 this.trigger('warn', {
484 message: 'ignoring invalid discontinuity sequence: ' + entry.number 469 message: 'ignoring invalid discontinuity sequence: ' + entry.number
...@@ -487,7 +472,7 @@ ...@@ -487,7 +472,7 @@
487 } 472 }
488 this.manifest.discontinuitySequence = entry.number; 473 this.manifest.discontinuitySequence = entry.number;
489 }, 474 },
490 'playlist-type': function() { 475 'playlist-type'() {
491 if (!(/VOD|EVENT/).test(entry.playlistType)) { 476 if (!(/VOD|EVENT/).test(entry.playlistType)) {
492 this.trigger('warn', { 477 this.trigger('warn', {
493 message: 'ignoring unknown playlist type: ' + entry.playlist 478 message: 'ignoring unknown playlist type: ' + entry.playlist
...@@ -496,7 +481,7 @@ ...@@ -496,7 +481,7 @@
496 } 481 }
497 this.manifest.playlistType = entry.playlistType; 482 this.manifest.playlistType = entry.playlistType;
498 }, 483 },
499 'stream-inf': function() { 484 'stream-inf'() {
500 this.manifest.playlists = uris; 485 this.manifest.playlists = uris;
501 486
502 if (!entry.attributes) { 487 if (!entry.attributes) {
...@@ -509,14 +494,16 @@ ...@@ -509,14 +494,16 @@
509 if (!currentUri.attributes) { 494 if (!currentUri.attributes) {
510 currentUri.attributes = {}; 495 currentUri.attributes = {};
511 } 496 }
512 currentUri.attributes = mergeOptions(currentUri.attributes, 497 currentUri.attributes = mergeOptions(
513 entry.attributes); 498 currentUri.attributes,
499 entry.attributes
500 );
514 }, 501 },
515 'discontinuity': function() { 502 discontinuity() {
516 currentUri.discontinuity = true; 503 currentUri.discontinuity = true;
517 this.manifest.discontinuityStarts.push(uris.length); 504 this.manifest.discontinuityStarts.push(uris.length);
518 }, 505 },
519 'targetduration': function() { 506 targetduration() {
520 if (!isFinite(entry.duration) || entry.duration < 0) { 507 if (!isFinite(entry.duration) || entry.duration < 0) {
521 this.trigger('warn', { 508 this.trigger('warn', {
522 message: 'ignoring invalid target duration: ' + entry.duration 509 message: 'ignoring invalid target duration: ' + entry.duration
...@@ -525,7 +512,7 @@ ...@@ -525,7 +512,7 @@
525 } 512 }
526 this.manifest.targetDuration = entry.duration; 513 this.manifest.targetDuration = entry.duration;
527 }, 514 },
528 'totalduration': function() { 515 totalduration() {
529 if (!isFinite(entry.duration) || entry.duration < 0) { 516 if (!isFinite(entry.duration) || entry.duration < 0) {
530 this.trigger('warn', { 517 this.trigger('warn', {
531 message: 'ignoring invalid total duration: ' + entry.duration 518 message: 'ignoring invalid total duration: ' + entry.duration
...@@ -536,7 +523,7 @@ ...@@ -536,7 +523,7 @@
536 } 523 }
537 })[entry.tagType] || noop).call(self); 524 })[entry.tagType] || noop).call(self);
538 }, 525 },
539 uri: function() { 526 uri() {
540 currentUri.uri = entry.uri; 527 currentUri.uri = entry.uri;
541 uris.push(currentUri); 528 uris.push(currentUri);
542 529
...@@ -556,33 +543,36 @@ ...@@ -556,33 +543,36 @@
556 // prepare for the next URI 543 // prepare for the next URI
557 currentUri = {}; 544 currentUri = {};
558 }, 545 },
559 comment: function() { 546 comment() {
560 // comments are not important for playback 547 // comments are not important for playback
561 } 548 }
562 })[entry.type].call(self); 549 })[entry.type].call(self);
563 }); 550 });
564 }; 551
565 Parser.prototype = new Stream(); 552 }
553
566 /** 554 /**
567 * Parse the input string and update the manifest object. 555 * Parse the input string and update the manifest object.
568 * @param chunk {string} a potentially incomplete portion of the manifest 556 * @param chunk {string} a potentially incomplete portion of the manifest
569 */ 557 */
570 Parser.prototype.push = function(chunk) { 558 push(chunk) {
571 this.lineStream.push(chunk); 559 this.lineStream.push(chunk);
572 }; 560 }
561
573 /** 562 /**
574 * Flush any remaining input. This can be handy if the last line of an M3U8 563 * Flush any remaining input. This can be handy if the last line of an M3U8
575 * manifest did not contain a trailing newline but the file has been 564 * manifest did not contain a trailing newline but the file has been
576 * completely received. 565 * completely received.
577 */ 566 */
578 Parser.prototype.end = function() { 567 end() {
579 // flush any buffered input 568 // flush any buffered input
580 this.lineStream.push('\n'); 569 this.lineStream.push('\n');
581 }; 570 }
582 571
583 window.videojs.m3u8 = { 572 }
584 LineStream: LineStream, 573
585 ParseStream: ParseStream, 574 export default {
586 Parser: Parser 575 LineStream,
587 }; 576 ParseStream,
588 })(window.videojs, window.parseInt, window.isFinite, window.videojs.mergeOptions); 577 Parser
578 };
......
...@@ -2,45 +2,57 @@ ...@@ -2,45 +2,57 @@
2 * A lightweight readable stream implemention that handles event dispatching. 2 * A lightweight readable stream implemention that handles event dispatching.
3 * Objects that inherit from streams should call init in their constructors. 3 * Objects that inherit from streams should call init in their constructors.
4 */ 4 */
5 (function(videojs, undefined) { 5 export default class Stream {
6 var Stream = function() { 6 constructor() {
7 this.init = function() { 7 this.init();
8 var listeners = {}; 8 }
9
10 init() {
11 this.listeners = {};
12 }
13
9 /** 14 /**
10 * Add a listener for a specified event type. 15 * Add a listener for a specified event type.
11 * @param type {string} the event name 16 * @param type {string} the event name
12 * @param listener {function} the callback to be invoked when an event of 17 * @param listener {function} the callback to be invoked when an event of
13 * the specified type occurs 18 * the specified type occurs
14 */ 19 */
15 this.on = function(type, listener) { 20 on(type, listener) {
16 if (!listeners[type]) { 21 if (!this.listeners[type]) {
17 listeners[type] = []; 22 this.listeners[type] = [];
18 } 23 }
19 listeners[type].push(listener); 24 this.listeners[type].push(listener);
20 }; 25 }
26
21 /** 27 /**
22 * Remove a listener for a specified event type. 28 * Remove a listener for a specified event type.
23 * @param type {string} the event name 29 * @param type {string} the event name
24 * @param listener {function} a function previously registered for this 30 * @param listener {function} a function previously registered for this
25 * type of event through `on` 31 * type of event through `on`
26 */ 32 */
27 this.off = function(type, listener) { 33 off(type, listener) {
28 var index; 34 let index;
29 if (!listeners[type]) { 35
36 if (!this.listeners[type]) {
30 return false; 37 return false;
31 } 38 }
32 index = listeners[type].indexOf(listener); 39 index = this.listeners[type].indexOf(listener);
33 listeners[type].splice(index, 1); 40 this.listeners[type].splice(index, 1);
34 return index > -1; 41 return index > -1;
35 }; 42 }
43
36 /** 44 /**
37 * Trigger an event of the specified type on this stream. Any additional 45 * Trigger an event of the specified type on this stream. Any additional
38 * arguments to this function are passed as parameters to event listeners. 46 * arguments to this function are passed as parameters to event listeners.
39 * @param type {string} the event name 47 * @param type {string} the event name
40 */ 48 */
41 this.trigger = function(type) { 49 trigger(type) {
42 var callbacks, i, length, args; 50 let callbacks;
43 callbacks = listeners[type]; 51 let i;
52 let length;
53 let args;
54
55 callbacks = this.listeners[type];
44 if (!callbacks) { 56 if (!callbacks) {
45 return; 57 return;
46 } 58 }
...@@ -60,15 +72,14 @@ ...@@ -60,15 +72,14 @@
60 callbacks[i].apply(this, args); 72 callbacks[i].apply(this, args);
61 } 73 }
62 } 74 }
63 }; 75 }
76
64 /** 77 /**
65 * Destroys the stream and cleans up. 78 * Destroys the stream and cleans up.
66 */ 79 */
67 this.dispose = function() { 80 dispose() {
68 listeners = {}; 81 this.listeners = {};
69 }; 82 }
70 };
71 };
72 /** 83 /**
73 * Forwards all `data` events on this stream to the destination stream. The 84 * Forwards all `data` events on this stream to the destination stream. The
74 * destination stream should provide a method `push` to receive the data 85 * destination stream should provide a method `push` to receive the data
...@@ -76,11 +87,9 @@ ...@@ -76,11 +87,9 @@
76 * @param destination {stream} the stream that will receive all `data` events 87 * @param destination {stream} the stream that will receive all `data` events
77 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 88 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
78 */ 89 */
79 Stream.prototype.pipe = function(destination) { 90 pipe(destination) {
80 this.on('data', function(data) { 91 this.on('data', function(data) {
81 destination.push(data); 92 destination.push(data);
82 }); 93 });
83 }; 94 }
84 95 }
85 videojs.Hls.Stream = Stream;
86 })(window.videojs);
......
1 import m3u8 from './m3u8';
2 import Stream from './Stream';
3 import videojs from 'video.js';
4
5 if(typeof window.videojs.Hls === 'undefined') {
6 videojs.Hls = {};
7 }
8 videojs.Hls.Stream = Stream;
9 videojs.m3u8 = m3u8;
10
...@@ -16,22 +16,16 @@ ...@@ -16,22 +16,16 @@
16 <script src="/node_modules/video.js/dist/video.js"></script> 16 <script src="/node_modules/video.js/dist/video.js"></script>
17 <script src="/node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js"></script> 17 <script src="/node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js"></script>
18 18
19 <script src="/src/videojs-hls.js"></script> 19 <script src="/src/videojs-contrib-hls.js"></script>
20 <script src="/src/xhr.js"></script> 20 <script src="/src/xhr.js"></script>
21 <script src="/src/stream.js"></script> 21 <script src="/dist/videojs-contrib-hls.js"></script>
22 <script src="/src/m3u8/m3u8-parser.js"></script>
23 <script src="/src/playlist.js"></script> 22 <script src="/src/playlist.js"></script>
24 <script src="/src/playlist-loader.js"></script> 23 <script src="/src/playlist-loader.js"></script>
25 <script src="/src/decrypter.js"></script> 24 <script src="/src/decrypter.js"></script>
26 <script src="/src/bin-utils.js"></script> 25 <script src="/src/bin-utils.js"></script>
27 26
28 <script src="/test/data/manifests.js"></script> 27 <script src="/test/videojs-contrib-hls.test.js"></script>
29 <script src="/test/data/expected.js"></script> 28 <script src="/dist-test/videojs-contrib-hls.js"></script>
30 <script src="/test/data/ts-segment-bc.js"></script>
31
32
33 <script src="/test/videojs-hls.test.js"></script>
34 <script src="/test/m3u8.test.js"></script>
35 <script src="/test/playlist.test.js"></script> 29 <script src="/test/playlist.test.js"></script>
36 <script src="/test/playlist-loader.test.js"></script> 30 <script src="/test/playlist-loader.test.js"></script>
37 <script src="/test/decrypter.test.js"></script> 31 <script src="/test/decrypter.test.js"></script>
......
...@@ -2,8 +2,7 @@ var merge = require('lodash-compat/object/merge'); ...@@ -2,8 +2,7 @@ var merge = require('lodash-compat/object/merge');
2 2
3 var DEFAULTS = { 3 var DEFAULTS = {
4 basePath: '../..', 4 basePath: '../..',
5 //frameworks: ['browserify', 'qunit'], 5 frameworks: ['browserify', 'qunit'],
6 frameworks: ['qunit'],
7 6
8 7
9 files: [ 8 files: [
...@@ -16,20 +15,19 @@ var DEFAULTS = { ...@@ -16,20 +15,19 @@ var DEFAULTS = {
16 'node_modules/pkcs7/dist/pkcs7.unpad.js', 15 'node_modules/pkcs7/dist/pkcs7.unpad.js',
17 'node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js', 16 'node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js',
18 17
19 'src/videojs-hls.js', 18 // these two stub old functionality
19 'src/videojs-contrib-hls.js',
20 'src/xhr.js', 20 'src/xhr.js',
21 'src/stream.js', 21 'dist/videojs-contrib-hls.js',
22 'src/m3u8/m3u8-parser.js', 22
23 'src/playlist.js', 23 'src/playlist.js',
24 'src/playlist-loader.js', 24 'src/playlist-loader.js',
25 'src/decrypter.js', 25 'src/decrypter.js',
26 'src/bin-utils.js', 26 'src/bin-utils.js',
27 27
28 'test/data/manifests.js', 28 'test/stub.test.js',
29 'test/data/expected.js',
30 'test/data/ts-segment-bc.js',
31 29
32 'test/videojs-hls.test.js', 30 'test/videojs-contrib-hls.test.js',
33 'test/m3u8.test.js', 31 'test/m3u8.test.js',
34 'test/playlist.test.js', 32 'test/playlist.test.js',
35 'test/playlist-loader.test.js', 33 'test/playlist-loader.test.js',
...@@ -44,12 +42,12 @@ var DEFAULTS = { ...@@ -44,12 +42,12 @@ var DEFAULTS = {
44 ], 42 ],
45 43
46 plugins: [ 44 plugins: [
47 // 'karma-browserify', 45 'karma-browserify',
48 'karma-qunit' 46 'karma-qunit'
49 ], 47 ],
50 48
51 preprocessors: { 49 preprocessors: {
52 // 'test/**/*.js': ['browserify'] 50 'test/{stub,m3u8}.test.js': ['browserify']
53 }, 51 },
54 52
55 reporters: ['dots'], 53 reporters: ['dots'],
...@@ -59,18 +57,16 @@ var DEFAULTS = { ...@@ -59,18 +57,16 @@ var DEFAULTS = {
59 singleRun: true, 57 singleRun: true,
60 concurrency: Infinity, 58 concurrency: Infinity,
61 59
62 /*
63 browserify: { 60 browserify: {
64 debug: true, 61 debug: true,
65 transform: [ 62 transform: [
66 'babelify', 63 'babelify',
67 'browserify-shim' 64 'browserify-shim'
68 ], 65 ],
69 noparse: [ 66 noParse: [
70 'test/data/**', 67 'test/data/**',
71 ] 68 ]
72 } 69 }
73 */
74 }; 70 };
75 71
76 /** 72 /**
......
...@@ -29,6 +29,7 @@ module.exports = function(config) { ...@@ -29,6 +29,7 @@ module.exports = function(config) {
29 postDetection: function(availableBrowsers) { 29 postDetection: function(availableBrowsers) {
30 var safariIndex = availableBrowsers.indexOf('Safari'); 30 var safariIndex = availableBrowsers.indexOf('Safari');
31 if(safariIndex !== -1) { 31 if(safariIndex !== -1) {
32 console.log("Not running safari it is/was broken");
32 availableBrowsers.splice(safariIndex, 1); 33 availableBrowsers.splice(safariIndex, 1);
33 } 34 }
34 return availableBrowsers; 35 return availableBrowsers;
......
1 (function(window, undefined) { 1 import {ParseStream, LineStream, Parser} from '../src/m3u8';
2 var 2 import QUnit from 'qunit';
3 //manifestController = this.manifestController, 3 import testDataExpected from './data/expected.js';
4 m3u8 = window.videojs.m3u8, 4 import testDataManifests from './data/manifests.js';
5 ParseStream = m3u8.ParseStream, 5
6 parseStream, 6 QUnit.module('LineStream', {
7 LineStream = m3u8.LineStream, 7 beforeEach() {
8 lineStream, 8 this.lineStream = new LineStream();
9 Parser = m3u8.Parser,
10 parser;
11
12 /*
13 M3U8 Test Suite
14 */
15
16 QUnit.module('LineStream', {
17 setup: function() {
18 lineStream = new LineStream();
19 } 9 }
20 }); 10 });
21 test('empty inputs produce no tokens', function() { 11 QUnit.test('empty inputs produce no tokens', function() {
22 var data = false; 12 let data = false;
23 lineStream.on('data', function() { 13
14 this.lineStream.on('data', function() {
24 data = true; 15 data = true;
25 }); 16 });
26 lineStream.push(''); 17 this.lineStream.push('');
27 ok(!data, 'no tokens were produced'); 18 QUnit.ok(!data, 'no tokens were produced');
28 }); 19 });
29 test('splits on newlines', function() { 20 QUnit.test('splits on newlines', function() {
30 var lines = []; 21 let lines = [];
31 lineStream.on('data', function(line) {
32 lines.push(line);
33 });
34 lineStream.push('#EXTM3U\nmovie.ts\n');
35 22
36 strictEqual(2, lines.length, 'two lines are ready'); 23 this.lineStream.on('data', function(line) {
37 strictEqual('#EXTM3U', lines.shift(), 'the first line is the first token');
38 strictEqual('movie.ts', lines.shift(), 'the second line is the second token');
39 });
40 test('empty lines become empty strings', function() {
41 var lines = [];
42 lineStream.on('data', function(line) {
43 lines.push(line); 24 lines.push(line);
44 }); 25 });
45 lineStream.push('\n\n'); 26 this.lineStream.push('#EXTM3U\nmovie.ts\n');
46 27
47 strictEqual(2, lines.length, 'two lines are ready'); 28 QUnit.strictEqual(2, lines.length, 'two lines are ready');
48 strictEqual('', lines.shift(), 'the first line is empty'); 29 QUnit.strictEqual('#EXTM3U', lines.shift(), 'the first line is the first token');
49 strictEqual('', lines.shift(), 'the second line is empty'); 30 QUnit.strictEqual('movie.ts', lines.shift(), 'the second line is the second token');
50 }); 31 });
51 test('handles lines broken across appends', function() { 32 QUnit.test('empty lines become empty strings', function() {
52 var lines = []; 33 let lines = [];
53 lineStream.on('data', function(line) { 34
35 this.lineStream.on('data', function(line) {
54 lines.push(line); 36 lines.push(line);
55 }); 37 });
56 lineStream.push('#EXTM'); 38 this.lineStream.push('\n\n');
57 strictEqual(0, lines.length, 'no lines are ready'); 39
40 QUnit.strictEqual(2, lines.length, 'two lines are ready');
41 QUnit.strictEqual('', lines.shift(), 'the first line is empty');
42 QUnit.strictEqual('', lines.shift(), 'the second line is empty');
43 });
44 QUnit.test('handles lines broken across appends', function() {
45 let lines = [];
58 46
59 lineStream.push('3U\nmovie.ts\n'); 47 this.lineStream.on('data', function(line) {
60 strictEqual(2, lines.length, 'two lines are ready'); 48 lines.push(line);
61 strictEqual('#EXTM3U', lines.shift(), 'the first line is the first token');
62 strictEqual('movie.ts', lines.shift(), 'the second line is the second token');
63 }); 49 });
64 test('stops sending events after deregistering', function() { 50 this.lineStream.push('#EXTM');
65 var 51 QUnit.strictEqual(0, lines.length, 'no lines are ready');
66 temporaryLines = [], 52
67 temporary = function(line) { 53 this.lineStream.push('3U\nmovie.ts\n');
54 QUnit.strictEqual(2, lines.length, 'two lines are ready');
55 QUnit.strictEqual('#EXTM3U', lines.shift(), 'the first line is the first token');
56 QUnit.strictEqual('movie.ts', lines.shift(), 'the second line is the second token');
57 });
58 QUnit.test('stops sending events after deregistering', function() {
59 let temporaryLines = [];
60 let temporary = function(line) {
68 temporaryLines.push(line); 61 temporaryLines.push(line);
69 }, 62 };
70 permanentLines = [], 63 let permanentLines = [];
71 permanent = function(line) { 64 let permanent = function(line) {
72 permanentLines.push(line); 65 permanentLines.push(line);
73 }; 66 };
74 67
75 lineStream.on('data', temporary); 68 this.lineStream.on('data', temporary);
76 lineStream.on('data', permanent); 69 this.lineStream.on('data', permanent);
77 lineStream.push('line one\n'); 70 this.lineStream.push('line one\n');
78 strictEqual(temporaryLines.length, permanentLines.length, 'both callbacks receive the event'); 71 QUnit.strictEqual(
79 72 temporaryLines.length,
80 ok(lineStream.off('data', temporary), 'a listener was removed'); 73 permanentLines.length,
81 lineStream.push('line two\n'); 74 'both callbacks receive the event'
82 strictEqual(1, temporaryLines.length, 'no new events are received'); 75 );
83 strictEqual(2, permanentLines.length, 'new events are still received'); 76
84 }); 77 QUnit.ok(this.lineStream.off('data', temporary), 'a listener was removed');
85 78 this.lineStream.push('line two\n');
86 QUnit.module('ParseStream', { 79 QUnit.strictEqual(1, temporaryLines.length, 'no new events are received');
87 setup: function() { 80 QUnit.strictEqual(2, permanentLines.length, 'new events are still received');
88 lineStream = new LineStream(); 81 });
89 parseStream = new ParseStream(); 82
90 lineStream.pipe(parseStream); 83 QUnit.module('ParseStream', {
84 beforeEach() {
85 this.lineStream = new LineStream();
86 this.parseStream = new ParseStream();
87 this.lineStream.pipe(this.parseStream);
91 } 88 }
92 }); 89 });
93 test('parses comment lines', function() { 90 QUnit.test('parses comment lines', function() {
94 var 91 let manifest = '# a line that starts with a hash mark without "EXT" is a comment\n';
95 manifest = '# a line that starts with a hash mark without "EXT" is a comment\n', 92 let element;
96 element; 93
97 parseStream.on('data', function(elem) { 94 this.parseStream.on('data', function(elem) {
98 element = elem; 95 element = elem;
99 }); 96 });
100 lineStream.push(manifest); 97 this.lineStream.push(manifest);
101 98
102 ok(element, 'an event was triggered'); 99 QUnit.ok(element, 'an event was triggered');
103 strictEqual(element.type, 'comment', 'the type is comment'); 100 QUnit.strictEqual(element.type, 'comment', 'the type is comment');
104 strictEqual(element.text, 101 QUnit.strictEqual(element.text,
105 manifest.slice(1, manifest.length - 1), 102 manifest.slice(1, manifest.length - 1),
106 'the comment text is parsed'); 103 'the comment text is parsed');
107 }); 104 });
108 test('parses uri lines', function() { 105 QUnit.test('parses uri lines', function() {
109 var 106 let manifest = 'any non-blank line that does not start with a hash-mark is a URI\n';
110 manifest = 'any non-blank line that does not start with a hash-mark is a URI\n', 107 let element;
111 element; 108
112 parseStream.on('data', function(elem) { 109 this.parseStream.on('data', function(elem) {
113 element = elem; 110 element = elem;
114 }); 111 });
115 lineStream.push(manifest); 112 this.lineStream.push(manifest);
116 113
117 ok(element, 'an event was triggered'); 114 QUnit.ok(element, 'an event was triggered');
118 strictEqual(element.type, 'uri', 'the type is uri'); 115 QUnit.strictEqual(element.type, 'uri', 'the type is uri');
119 strictEqual(element.uri, 116 QUnit.strictEqual(element.uri,
120 manifest.substring(0, manifest.length - 1), 117 manifest.substring(0, manifest.length - 1),
121 'the uri text is parsed'); 118 'the uri text is parsed');
122 }); 119 });
123 test('parses unknown tag types', function() { 120 QUnit.test('parses unknown tag types', function() {
124 var 121 let manifest = '#EXT-X-EXAMPLE-TAG:some,additional,stuff\n';
125 manifest = '#EXT-X-EXAMPLE-TAG:some,additional,stuff\n', 122 let element;
126 element; 123
127 parseStream.on('data', function(elem) { 124 this.parseStream.on('data', function(elem) {
128 element = elem; 125 element = elem;
129 }); 126 });
130 lineStream.push(manifest); 127 this.lineStream.push(manifest);
131 128
132 ok(element, 'an event was triggered'); 129 QUnit.ok(element, 'an event was triggered');
133 strictEqual(element.type, 'tag', 'the type is tag'); 130 QUnit.strictEqual(element.type, 'tag', 'the type is tag');
134 strictEqual(element.data, 131 QUnit.strictEqual(element.data,
135 manifest.slice(4, manifest.length - 1), 132 manifest.slice(4, manifest.length - 1),
136 'unknown tag data is preserved'); 133 'unknown tag data is preserved');
137 }); 134 });
138 135
139 // #EXTM3U 136 // #EXTM3U
140 test('parses #EXTM3U tags', function() { 137 QUnit.test('parses #EXTM3U tags', function() {
141 var 138 let manifest = '#EXTM3U\n';
142 manifest = '#EXTM3U\n', 139 let element;
143 element; 140
144 parseStream.on('data', function(elem) { 141 this.parseStream.on('data', function(elem) {
145 element = elem; 142 element = elem;
146 }); 143 });
147 lineStream.push(manifest); 144 this.lineStream.push(manifest);
148 145
149 ok(element, 'an event was triggered'); 146 QUnit.ok(element, 'an event was triggered');
150 strictEqual(element.type, 'tag', 'the line type is tag'); 147 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
151 strictEqual(element.tagType, 'm3u', 'the tag type is m3u'); 148 QUnit.strictEqual(element.tagType, 'm3u', 'the tag type is m3u');
152 }); 149 });
153 150
154 // #EXTINF 151 // #EXTINF
155 test('parses minimal #EXTINF tags', function() { 152 QUnit.test('parses minimal #EXTINF tags', function() {
156 var 153 let manifest = '#EXTINF\n';
157 manifest = '#EXTINF\n', 154 let element;
158 element; 155
159 parseStream.on('data', function(elem) { 156 this.parseStream.on('data', function(elem) {
160 element = elem; 157 element = elem;
161 }); 158 });
162 lineStream.push(manifest); 159 this.lineStream.push(manifest);
163 160
164 ok(element, 'an event was triggered'); 161 QUnit.ok(element, 'an event was triggered');
165 strictEqual(element.type, 'tag', 'the line type is tag'); 162 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
166 strictEqual(element.tagType, 'inf', 'the tag type is inf'); 163 QUnit.strictEqual(element.tagType, 'inf', 'the tag type is inf');
167 }); 164 });
168 test('parses #EXTINF tags with durations', function() { 165 QUnit.test('parses #EXTINF tags with durations', function() {
169 var 166 let manifest = '#EXTINF:15\n';
170 manifest = '#EXTINF:15\n', 167 let element;
171 element; 168
172 parseStream.on('data', function(elem) { 169 this.parseStream.on('data', function(elem) {
173 element = elem; 170 element = elem;
174 }); 171 });
175 lineStream.push(manifest); 172 this.lineStream.push(manifest);
176 173
177 ok(element, 'an event was triggered'); 174 QUnit.ok(element, 'an event was triggered');
178 strictEqual(element.type, 'tag', 'the line type is tag'); 175 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
179 strictEqual(element.tagType, 'inf', 'the tag type is inf'); 176 QUnit.strictEqual(element.tagType, 'inf', 'the tag type is inf');
180 strictEqual(element.duration, 15, 'the duration is parsed'); 177 QUnit.strictEqual(element.duration, 15, 'the duration is parsed');
181 ok(!('title' in element), 'no title is parsed'); 178 QUnit.ok(!('title' in element), 'no title is parsed');
182 179
183 manifest = '#EXTINF:21,\n'; 180 manifest = '#EXTINF:21,\n';
184 lineStream.push(manifest); 181 this.lineStream.push(manifest);
185 182
186 ok(element, 'an event was triggered'); 183 QUnit.ok(element, 'an event was triggered');
187 strictEqual(element.type, 'tag', 'the line type is tag'); 184 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
188 strictEqual(element.tagType, 'inf', 'the tag type is inf'); 185 QUnit.strictEqual(element.tagType, 'inf', 'the tag type is inf');
189 strictEqual(element.duration, 21, 'the duration is parsed'); 186 QUnit.strictEqual(element.duration, 21, 'the duration is parsed');
190 ok(!('title' in element), 'no title is parsed'); 187 QUnit.ok(!('title' in element), 'no title is parsed');
191 }); 188 });
192 test('parses #EXTINF tags with a duration and title', function() { 189 QUnit.test('parses #EXTINF tags with a duration and title', function() {
193 var 190 let manifest = '#EXTINF:13,Does anyone really use the title attribute?\n';
194 manifest = '#EXTINF:13,Does anyone really use the title attribute?\n', 191 let element;
195 element; 192
196 parseStream.on('data', function(elem) { 193 this.parseStream.on('data', function(elem) {
197 element = elem; 194 element = elem;
198 }); 195 });
199 lineStream.push(manifest); 196 this.lineStream.push(manifest);
200 197
201 ok(element, 'an event was triggered'); 198 QUnit.ok(element, 'an event was triggered');
202 strictEqual(element.type, 'tag', 'the line type is tag'); 199 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
203 strictEqual(element.tagType, 'inf', 'the tag type is inf'); 200 QUnit.strictEqual(element.tagType, 'inf', 'the tag type is inf');
204 strictEqual(element.duration, 13, 'the duration is parsed'); 201 QUnit.strictEqual(element.duration, 13, 'the duration is parsed');
205 strictEqual(element.title, 202 QUnit.strictEqual(element.title,
206 manifest.substring(manifest.indexOf(',') + 1, manifest.length - 1), 203 manifest.substring(manifest.indexOf(',') + 1, manifest.length - 1),
207 'the title is parsed'); 204 'the title is parsed');
208 }); 205 });
209 test('parses #EXTINF tags with carriage returns', function() { 206 QUnit.test('parses #EXTINF tags with carriage returns', function() {
210 var 207 let manifest = '#EXTINF:13,Does anyone really use the title attribute?\r\n';
211 manifest = '#EXTINF:13,Does anyone really use the title attribute?\r\n', 208 let element;
212 element; 209
213 parseStream.on('data', function(elem) { 210 this.parseStream.on('data', function(elem) {
214 element = elem; 211 element = elem;
215 }); 212 });
216 lineStream.push(manifest); 213 this.lineStream.push(manifest);
217 214
218 ok(element, 'an event was triggered'); 215 QUnit.ok(element, 'an event was triggered');
219 strictEqual(element.type, 'tag', 'the line type is tag'); 216 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
220 strictEqual(element.tagType, 'inf', 'the tag type is inf'); 217 QUnit.strictEqual(element.tagType, 'inf', 'the tag type is inf');
221 strictEqual(element.duration, 13, 'the duration is parsed'); 218 QUnit.strictEqual(element.duration, 13, 'the duration is parsed');
222 strictEqual(element.title, 219 QUnit.strictEqual(element.title,
223 manifest.substring(manifest.indexOf(',') + 1, manifest.length - 2), 220 manifest.substring(manifest.indexOf(',') + 1, manifest.length - 2),
224 'the title is parsed'); 221 'the title is parsed');
225 }); 222 });
226 223
227 // #EXT-X-TARGETDURATION 224 // #EXT-X-TARGETDURATION
228 test('parses minimal #EXT-X-TARGETDURATION tags', function() { 225 QUnit.test('parses minimal #EXT-X-TARGETDURATION tags', function() {
229 var 226 let manifest = '#EXT-X-TARGETDURATION\n';
230 manifest = '#EXT-X-TARGETDURATION\n', 227 let element;
231 element; 228
232 parseStream.on('data', function(elem) { 229 this.parseStream.on('data', function(elem) {
233 element = elem; 230 element = elem;
234 }); 231 });
235 lineStream.push(manifest); 232 this.lineStream.push(manifest);
236 233
237 ok(element, 'an event was triggered'); 234 QUnit.ok(element, 'an event was triggered');
238 strictEqual(element.type, 'tag', 'the line type is tag'); 235 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
239 strictEqual(element.tagType, 'targetduration', 'the tag type is targetduration'); 236 QUnit.strictEqual(element.tagType, 'targetduration', 'the tag type is targetduration');
240 ok(!('duration' in element), 'no duration is parsed'); 237 QUnit.ok(!('duration' in element), 'no duration is parsed');
241 }); 238 });
242 test('parses #EXT-X-TARGETDURATION with duration', function() { 239 QUnit.test('parses #EXT-X-TARGETDURATION with duration', function() {
243 var 240 let manifest = '#EXT-X-TARGETDURATION:47\n';
244 manifest = '#EXT-X-TARGETDURATION:47\n', 241 let element;
245 element; 242
246 parseStream.on('data', function(elem) { 243 this.parseStream.on('data', function(elem) {
247 element = elem; 244 element = elem;
248 }); 245 });
249 lineStream.push(manifest); 246 this.lineStream.push(manifest);
250 247
251 ok(element, 'an event was triggered'); 248 QUnit.ok(element, 'an event was triggered');
252 strictEqual(element.type, 'tag', 'the line type is tag'); 249 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
253 strictEqual(element.tagType, 'targetduration', 'the tag type is targetduration'); 250 QUnit.strictEqual(element.tagType, 'targetduration', 'the tag type is targetduration');
254 strictEqual(element.duration, 47, 'the duration is parsed'); 251 QUnit.strictEqual(element.duration, 47, 'the duration is parsed');
255 }); 252 });
256 253
257 // #EXT-X-VERSION 254 // #EXT-X-VERSION
258 test('parses minimal #EXT-X-VERSION tags', function() { 255 QUnit.test('parses minimal #EXT-X-VERSION tags', function() {
259 var 256 let manifest = '#EXT-X-VERSION:\n';
260 manifest = '#EXT-X-VERSION:\n', 257 let element;
261 element; 258
262 parseStream.on('data', function(elem) { 259 this.parseStream.on('data', function(elem) {
263 element = elem; 260 element = elem;
264 }); 261 });
265 lineStream.push(manifest); 262 this.lineStream.push(manifest);
266 263
267 ok(element, 'an event was triggered'); 264 QUnit.ok(element, 'an event was triggered');
268 strictEqual(element.type, 'tag', 'the line type is tag'); 265 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
269 strictEqual(element.tagType, 'version', 'the tag type is version'); 266 QUnit.strictEqual(element.tagType, 'version', 'the tag type is version');
270 ok(!('version' in element), 'no version is present'); 267 QUnit.ok(!('version' in element), 'no version is present');
271 }); 268 });
272 test('parses #EXT-X-VERSION with a version', function() { 269 QUnit.test('parses #EXT-X-VERSION with a version', function() {
273 var 270 let manifest = '#EXT-X-VERSION:99\n';
274 manifest = '#EXT-X-VERSION:99\n', 271 let element;
275 element; 272
276 parseStream.on('data', function(elem) { 273 this.parseStream.on('data', function(elem) {
277 element = elem; 274 element = elem;
278 }); 275 });
279 lineStream.push(manifest); 276 this.lineStream.push(manifest);
280 277
281 ok(element, 'an event was triggered'); 278 QUnit.ok(element, 'an event was triggered');
282 strictEqual(element.type, 'tag', 'the line type is tag'); 279 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
283 strictEqual(element.tagType, 'version', 'the tag type is version'); 280 QUnit.strictEqual(element.tagType, 'version', 'the tag type is version');
284 strictEqual(element.version, 99, 'the version is parsed'); 281 QUnit.strictEqual(element.version, 99, 'the version is parsed');
285 }); 282 });
283
284 // #EXT-X-MEDIA-SEQUENCE
285 QUnit.test('parses minimal #EXT-X-MEDIA-SEQUENCE tags', function() {
286 let manifest = '#EXT-X-MEDIA-SEQUENCE\n';
287 let element;
286 288
287 // #EXT-X-MEDIA-SEQUENCE 289 this.parseStream.on('data', function(elem) {
288 test('parses minimal #EXT-X-MEDIA-SEQUENCE tags', function() {
289 var
290 manifest = '#EXT-X-MEDIA-SEQUENCE\n',
291 element;
292 parseStream.on('data', function(elem) {
293 element = elem; 290 element = elem;
294 }); 291 });
295 lineStream.push(manifest); 292 this.lineStream.push(manifest);
296 293
297 ok(element, 'an event was triggered'); 294 QUnit.ok(element, 'an event was triggered');
298 strictEqual(element.type, 'tag', 'the line type is tag'); 295 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
299 strictEqual(element.tagType, 'media-sequence', 'the tag type is media-sequence'); 296 QUnit.strictEqual(element.tagType, 'media-sequence', 'the tag type is media-sequence');
300 ok(!('number' in element), 'no number is present'); 297 QUnit.ok(!('number' in element), 'no number is present');
301 }); 298 });
302 test('parses #EXT-X-MEDIA-SEQUENCE with sequence numbers', function() { 299 QUnit.test('parses #EXT-X-MEDIA-SEQUENCE with sequence numbers', function() {
303 var 300 let manifest = '#EXT-X-MEDIA-SEQUENCE:109\n';
304 manifest = '#EXT-X-MEDIA-SEQUENCE:109\n', 301 let element;
305 element; 302
306 parseStream.on('data', function(elem) { 303 this.parseStream.on('data', function(elem) {
307 element = elem; 304 element = elem;
308 }); 305 });
309 lineStream.push(manifest); 306 this.lineStream.push(manifest);
310 307
311 ok(element, 'an event was triggered'); 308 QUnit.ok(element, 'an event was triggered');
312 strictEqual(element.type, 'tag', 'the line type is tag'); 309 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
313 strictEqual(element.tagType, 'media-sequence', 'the tag type is media-sequence'); 310 QUnit.strictEqual(element.tagType, 'media-sequence', 'the tag type is media-sequence');
314 ok(element.number, 109, 'the number is parsed'); 311 QUnit.ok(element.number, 109, 'the number is parsed');
315 }); 312 });
313
314 // #EXT-X-PLAYLIST-TYPE
315 QUnit.test('parses minimal #EXT-X-PLAYLIST-TYPE tags', function() {
316 let manifest = '#EXT-X-PLAYLIST-TYPE:\n';
317 let element;
316 318
317 // #EXT-X-PLAYLIST-TYPE 319 this.parseStream.on('data', function(elem) {
318 test('parses minimal #EXT-X-PLAYLIST-TYPE tags', function() {
319 var
320 manifest = '#EXT-X-PLAYLIST-TYPE:\n',
321 element;
322 parseStream.on('data', function(elem) {
323 element = elem; 320 element = elem;
324 }); 321 });
325 lineStream.push(manifest); 322 this.lineStream.push(manifest);
326 323
327 ok(element, 'an event was triggered'); 324 QUnit.ok(element, 'an event was triggered');
328 strictEqual(element.type, 'tag', 'the line type is tag'); 325 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
329 strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type'); 326 QUnit.strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type');
330 ok(!('playlistType' in element), 'no playlist type is present'); 327 QUnit.ok(!('playlistType' in element), 'no playlist type is present');
331 }); 328 });
332 test('parses #EXT-X-PLAYLIST-TYPE with mutability info', function() { 329 QUnit.test('parses #EXT-X-PLAYLIST-TYPE with mutability info', function() {
333 var 330 let manifest = '#EXT-X-PLAYLIST-TYPE:EVENT\n';
334 manifest = '#EXT-X-PLAYLIST-TYPE:EVENT\n', 331 let element;
335 element; 332
336 parseStream.on('data', function(elem) { 333 this.parseStream.on('data', function(elem) {
337 element = elem; 334 element = elem;
338 }); 335 });
339 lineStream.push(manifest); 336 this.lineStream.push(manifest);
340 337
341 ok(element, 'an event was triggered'); 338 QUnit.ok(element, 'an event was triggered');
342 strictEqual(element.type, 'tag', 'the line type is tag'); 339 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
343 strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type'); 340 QUnit.strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type');
344 strictEqual(element.playlistType, 'EVENT', 'the playlist type is EVENT'); 341 QUnit.strictEqual(element.playlistType, 'EVENT', 'the playlist type is EVENT');
345 342
346 manifest = '#EXT-X-PLAYLIST-TYPE:VOD\n'; 343 manifest = '#EXT-X-PLAYLIST-TYPE:VOD\n';
347 lineStream.push(manifest); 344 this.lineStream.push(manifest);
348 ok(element, 'an event was triggered'); 345 QUnit.ok(element, 'an event was triggered');
349 strictEqual(element.type, 'tag', 'the line type is tag'); 346 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
350 strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type'); 347 QUnit.strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type');
351 strictEqual(element.playlistType, 'VOD', 'the playlist type is VOD'); 348 QUnit.strictEqual(element.playlistType, 'VOD', 'the playlist type is VOD');
352 349
353 manifest = '#EXT-X-PLAYLIST-TYPE:nonsense\n'; 350 manifest = '#EXT-X-PLAYLIST-TYPE:nonsense\n';
354 lineStream.push(manifest); 351 this.lineStream.push(manifest);
355 ok(element, 'an event was triggered'); 352 QUnit.ok(element, 'an event was triggered');
356 strictEqual(element.type, 'tag', 'the line type is tag'); 353 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
357 strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type'); 354 QUnit.strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type');
358 strictEqual(element.playlistType, 'nonsense', 'the playlist type is parsed'); 355 QUnit.strictEqual(element.playlistType, 'nonsense', 'the playlist type is parsed');
359 }); 356 });
360 357
361 // #EXT-X-BYTERANGE 358 // #EXT-X-BYTERANGE
362 test('parses minimal #EXT-X-BYTERANGE tags', function() { 359 QUnit.test('parses minimal #EXT-X-BYTERANGE tags', function() {
363 var 360 let manifest = '#EXT-X-BYTERANGE\n';
364 manifest = '#EXT-X-BYTERANGE\n', 361 let element;
365 element; 362
366 parseStream.on('data', function(elem) { 363 this.parseStream.on('data', function(elem) {
367 element = elem; 364 element = elem;
368 }); 365 });
369 lineStream.push(manifest); 366 this.lineStream.push(manifest);
370 367
371 ok(element, 'an event was triggered'); 368 QUnit.ok(element, 'an event was triggered');
372 strictEqual(element.type, 'tag', 'the line type is tag'); 369 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
373 strictEqual(element.tagType, 'byterange', 'the tag type is byterange'); 370 QUnit.strictEqual(element.tagType, 'byterange', 'the tag type is byterange');
374 ok(!('length' in element), 'no length is present'); 371 QUnit.ok(!('length' in element), 'no length is present');
375 ok(!('offset' in element), 'no offset is present'); 372 QUnit.ok(!('offset' in element), 'no offset is present');
376 }); 373 });
377 test('parses #EXT-X-BYTERANGE with length and offset', function() { 374 QUnit.test('parses #EXT-X-BYTERANGE with length and offset', function() {
378 var 375 let manifest = '#EXT-X-BYTERANGE:45\n';
379 manifest = '#EXT-X-BYTERANGE:45\n', 376 let element;
380 element; 377
381 parseStream.on('data', function(elem) { 378 this.parseStream.on('data', function(elem) {
382 element = elem; 379 element = elem;
383 }); 380 });
384 lineStream.push(manifest); 381 this.lineStream.push(manifest);
385 382
386 ok(element, 'an event was triggered'); 383 QUnit.ok(element, 'an event was triggered');
387 strictEqual(element.type, 'tag', 'the line type is tag'); 384 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
388 strictEqual(element.tagType, 'byterange', 'the tag type is byterange'); 385 QUnit.strictEqual(element.tagType, 'byterange', 'the tag type is byterange');
389 strictEqual(element.length, 45, 'length is parsed'); 386 QUnit.strictEqual(element.length, 45, 'length is parsed');
390 ok(!('offset' in element), 'no offset is present'); 387 QUnit.ok(!('offset' in element), 'no offset is present');
391 388
392 manifest = '#EXT-X-BYTERANGE:108@16\n'; 389 manifest = '#EXT-X-BYTERANGE:108@16\n';
393 lineStream.push(manifest); 390 this.lineStream.push(manifest);
394 ok(element, 'an event was triggered'); 391 QUnit.ok(element, 'an event was triggered');
395 strictEqual(element.type, 'tag', 'the line type is tag'); 392 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
396 strictEqual(element.tagType, 'byterange', 'the tag type is byterange'); 393 QUnit.strictEqual(element.tagType, 'byterange', 'the tag type is byterange');
397 strictEqual(element.length, 108, 'length is parsed'); 394 QUnit.strictEqual(element.length, 108, 'length is parsed');
398 strictEqual(element.offset, 16, 'offset is parsed'); 395 QUnit.strictEqual(element.offset, 16, 'offset is parsed');
399 }); 396 });
400 397
401 // #EXT-X-ALLOW-CACHE 398 // #EXT-X-ALLOW-CACHE
402 test('parses minimal #EXT-X-ALLOW-CACHE tags', function() { 399 QUnit.test('parses minimal #EXT-X-ALLOW-CACHE tags', function() {
403 var 400 let manifest = '#EXT-X-ALLOW-CACHE:\n';
404 manifest = '#EXT-X-ALLOW-CACHE:\n', 401 let element;
405 element; 402
406 parseStream.on('data', function(elem) { 403 this.parseStream.on('data', function(elem) {
407 element = elem; 404 element = elem;
408 }); 405 });
409 lineStream.push(manifest); 406 this.lineStream.push(manifest);
410 407
411 ok(element, 'an event was triggered'); 408 QUnit.ok(element, 'an event was triggered');
412 strictEqual(element.type, 'tag', 'the line type is tag'); 409 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
413 strictEqual(element.tagType, 'allow-cache', 'the tag type is allow-cache'); 410 QUnit.strictEqual(element.tagType, 'allow-cache', 'the tag type is allow-cache');
414 ok(!('allowed' in element), 'no allowed is present'); 411 QUnit.ok(!('allowed' in element), 'no allowed is present');
415 }); 412 });
416 test('parses valid #EXT-X-ALLOW-CACHE tags', function() { 413 QUnit.test('parses valid #EXT-X-ALLOW-CACHE tags', function() {
417 var 414 let manifest = '#EXT-X-ALLOW-CACHE:YES\n';
418 manifest = '#EXT-X-ALLOW-CACHE:YES\n', 415 let element;
419 element; 416
420 parseStream.on('data', function(elem) { 417 this.parseStream.on('data', function(elem) {
421 element = elem; 418 element = elem;
422 }); 419 });
423 lineStream.push(manifest); 420 this.lineStream.push(manifest);
424 421
425 ok(element, 'an event was triggered'); 422 QUnit.ok(element, 'an event was triggered');
426 strictEqual(element.type, 'tag', 'the line type is tag'); 423 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
427 strictEqual(element.tagType, 'allow-cache', 'the tag type is allow-cache'); 424 QUnit.strictEqual(element.tagType, 'allow-cache', 'the tag type is allow-cache');
428 ok(element.allowed, 'allowed is parsed'); 425 QUnit.ok(element.allowed, 'allowed is parsed');
429 426
430 manifest = '#EXT-X-ALLOW-CACHE:NO\n'; 427 manifest = '#EXT-X-ALLOW-CACHE:NO\n';
431 lineStream.push(manifest); 428 this.lineStream.push(manifest);
432 429
433 ok(element, 'an event was triggered'); 430 QUnit.ok(element, 'an event was triggered');
434 strictEqual(element.type, 'tag', 'the line type is tag'); 431 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
435 strictEqual(element.tagType, 'allow-cache', 'the tag type is allow-cache'); 432 QUnit.strictEqual(element.tagType, 'allow-cache', 'the tag type is allow-cache');
436 ok(!element.allowed, 'allowed is parsed'); 433 QUnit.ok(!element.allowed, 'allowed is parsed');
437 }); 434 });
438 // #EXT-X-STREAM-INF 435 // #EXT-X-STREAM-INF
439 test('parses minimal #EXT-X-STREAM-INF tags', function() { 436 QUnit.test('parses minimal #EXT-X-STREAM-INF tags', function() {
440 var 437 let manifest = '#EXT-X-STREAM-INF\n';
441 manifest = '#EXT-X-STREAM-INF\n', 438 let element;
442 element; 439
443 parseStream.on('data', function(elem) { 440 this.parseStream.on('data', function(elem) {
444 element = elem; 441 element = elem;
445 }); 442 });
446 lineStream.push(manifest); 443 this.lineStream.push(manifest);
447 444
448 ok(element, 'an event was triggered'); 445 QUnit.ok(element, 'an event was triggered');
449 strictEqual(element.type, 'tag', 'the line type is tag'); 446 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
450 strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf'); 447 QUnit.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
451 ok(!('attributes' in element), 'no attributes are present'); 448 QUnit.ok(!('attributes' in element), 'no attributes are present');
452 }); 449 });
453 test('parses #EXT-X-STREAM-INF with common attributes', function() { 450 QUnit.test('parses #EXT-X-STREAM-INF with common attributes', function() {
454 var 451 let manifest = '#EXT-X-STREAM-INF:BANDWIDTH=14400\n';
455 manifest = '#EXT-X-STREAM-INF:BANDWIDTH=14400\n', 452 let element;
456 element; 453
457 parseStream.on('data', function(elem) { 454 this.parseStream.on('data', function(elem) {
458 element = elem; 455 element = elem;
459 }); 456 });
460 lineStream.push(manifest); 457 this.lineStream.push(manifest);
461 458
462 ok(element, 'an event was triggered'); 459 QUnit.ok(element, 'an event was triggered');
463 strictEqual(element.type, 'tag', 'the line type is tag'); 460 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
464 strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf'); 461 QUnit.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
465 strictEqual(element.attributes.BANDWIDTH, 14400, 'bandwidth is parsed'); 462 QUnit.strictEqual(element.attributes.BANDWIDTH, 14400, 'bandwidth is parsed');
466 463
467 manifest = '#EXT-X-STREAM-INF:PROGRAM-ID=7\n'; 464 manifest = '#EXT-X-STREAM-INF:PROGRAM-ID=7\n';
468 lineStream.push(manifest); 465 this.lineStream.push(manifest);
469 466
470 ok(element, 'an event was triggered'); 467 QUnit.ok(element, 'an event was triggered');
471 strictEqual(element.type, 'tag', 'the line type is tag'); 468 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
472 strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf'); 469 QUnit.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
473 strictEqual(element.attributes['PROGRAM-ID'], 7, 'program-id is parsed'); 470 QUnit.strictEqual(element.attributes['PROGRAM-ID'], 7, 'program-id is parsed');
474 471
475 manifest = '#EXT-X-STREAM-INF:RESOLUTION=396x224\n'; 472 manifest = '#EXT-X-STREAM-INF:RESOLUTION=396x224\n';
476 lineStream.push(manifest); 473 this.lineStream.push(manifest);
477 474
478 ok(element, 'an event was triggered'); 475 QUnit.ok(element, 'an event was triggered');
479 strictEqual(element.type, 'tag', 'the line type is tag'); 476 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
480 strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf'); 477 QUnit.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
481 strictEqual(element.attributes.RESOLUTION.width, 396, 'width is parsed'); 478 QUnit.strictEqual(element.attributes.RESOLUTION.width, 396, 'width is parsed');
482 strictEqual(element.attributes.RESOLUTION.height, 224, 'heigth is parsed'); 479 QUnit.strictEqual(element.attributes.RESOLUTION.height, 224, 'heigth is parsed');
483 480
484 manifest = '#EXT-X-STREAM-INF:CODECS="avc1.4d400d, mp4a.40.2"\n'; 481 manifest = '#EXT-X-STREAM-INF:CODECS="avc1.4d400d, mp4a.40.2"\n';
485 lineStream.push(manifest); 482 this.lineStream.push(manifest);
486 483
487 ok(element, 'an event was triggered'); 484 QUnit.ok(element, 'an event was triggered');
488 strictEqual(element.type, 'tag', 'the line type is tag'); 485 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
489 strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf'); 486 QUnit.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
490 strictEqual(element.attributes.CODECS, 487 QUnit.strictEqual(element.attributes.CODECS,
491 'avc1.4d400d, mp4a.40.2', 488 'avc1.4d400d, mp4a.40.2',
492 'codecs are parsed'); 489 'codecs are parsed');
493 }); 490 });
494 test('parses #EXT-X-STREAM-INF with arbitrary attributes', function() { 491 QUnit.test('parses #EXT-X-STREAM-INF with arbitrary attributes', function() {
495 var 492 let manifest = '#EXT-X-STREAM-INF:NUMERIC=24,ALPHA=Value,MIXED=123abc\n';
496 manifest = '#EXT-X-STREAM-INF:NUMERIC=24,ALPHA=Value,MIXED=123abc\n', 493 let element;
497 element; 494
498 parseStream.on('data', function(elem) { 495 this.parseStream.on('data', function(elem) {
499 element = elem; 496 element = elem;
500 }); 497 });
501 lineStream.push(manifest); 498 this.lineStream.push(manifest);
502 499
503 ok(element, 'an event was triggered'); 500 QUnit.ok(element, 'an event was triggered');
504 strictEqual(element.type, 'tag', 'the line type is tag'); 501 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
505 strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf'); 502 QUnit.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
506 strictEqual(element.attributes.NUMERIC, '24', 'numeric attributes are parsed'); 503 QUnit.strictEqual(element.attributes.NUMERIC, '24', 'numeric attributes are parsed');
507 strictEqual(element.attributes.ALPHA, 'Value', 'alphabetic attributes are parsed'); 504 QUnit.strictEqual(
508 strictEqual(element.attributes.MIXED, '123abc', 'mixed attributes are parsed'); 505 element.attributes.ALPHA,
509 }); 506 'Value',
510 // #EXT-X-ENDLIST 507 'alphabetic attributes are parsed'
511 test('parses #EXT-X-ENDLIST tags', function() { 508 );
512 var 509 QUnit.strictEqual(element.attributes.MIXED, '123abc', 'mixed attributes are parsed');
513 manifest = '#EXT-X-ENDLIST\n', 510 });
514 element; 511 // #EXT-X-ENDLIST
515 parseStream.on('data', function(elem) { 512 QUnit.test('parses #EXT-X-ENDLIST tags', function() {
513 let manifest = '#EXT-X-ENDLIST\n';
514 let element;
515
516 this.parseStream.on('data', function(elem) {
516 element = elem; 517 element = elem;
517 }); 518 });
518 lineStream.push(manifest); 519 this.lineStream.push(manifest);
519 520
520 ok(element, 'an event was triggered'); 521 QUnit.ok(element, 'an event was triggered');
521 strictEqual(element.type, 'tag', 'the line type is tag'); 522 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
522 strictEqual(element.tagType, 'endlist', 'the tag type is stream-inf'); 523 QUnit.strictEqual(element.tagType, 'endlist', 'the tag type is stream-inf');
523 }); 524 });
525
526 // #EXT-X-KEY
527 QUnit.test('parses valid #EXT-X-KEY tags', function() {
528 let manifest =
529 '#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=52"\n';
530 let element;
524 531
525 // #EXT-X-KEY 532 this.parseStream.on('data', function(elem) {
526 test('parses valid #EXT-X-KEY tags', function() {
527 var
528 manifest = '#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=52"\n',
529 element;
530 parseStream.on('data', function(elem) {
531 element = elem; 533 element = elem;
532 }); 534 });
533 lineStream.push(manifest); 535 this.lineStream.push(manifest);
534 536
535 ok(element, 'an event was triggered'); 537 QUnit.ok(element, 'an event was triggered');
536 deepEqual(element, { 538 QUnit.deepEqual(element, {
537 type: 'tag', 539 type: 'tag',
538 tagType: 'key', 540 tagType: 'key',
539 attributes: { 541 attributes: {
...@@ -543,9 +545,9 @@ ...@@ -543,9 +545,9 @@
543 }, 'parsed a valid key'); 545 }, 'parsed a valid key');
544 546
545 manifest = '#EXT-X-KEY:URI="https://example.com/key#1",METHOD=FutureType-1024\n'; 547 manifest = '#EXT-X-KEY:URI="https://example.com/key#1",METHOD=FutureType-1024\n';
546 lineStream.push(manifest); 548 this.lineStream.push(manifest);
547 ok(element, 'an event was triggered'); 549 QUnit.ok(element, 'an event was triggered');
548 deepEqual(element, { 550 QUnit.deepEqual(element, {
549 type: 'tag', 551 type: 'tag',
550 tagType: 'key', 552 tagType: 'key',
551 attributes: { 553 attributes: {
...@@ -555,92 +557,94 @@ ...@@ -555,92 +557,94 @@
555 }, 'parsed the attribute list independent of order'); 557 }, 'parsed the attribute list independent of order');
556 558
557 manifest = '#EXT-X-KEY:IV=1234567890abcdef1234567890abcdef\n'; 559 manifest = '#EXT-X-KEY:IV=1234567890abcdef1234567890abcdef\n';
558 lineStream.push(manifest); 560 this.lineStream.push(manifest);
559 ok(element.attributes.IV, 'detected an IV attribute'); 561 QUnit.ok(element.attributes.IV, 'detected an IV attribute');
560 deepEqual(element.attributes.IV, new Uint32Array([ 562 QUnit.deepEqual(element.attributes.IV, new Uint32Array([
561 0x12345678, 563 0x12345678,
562 0x90abcdef, 564 0x90abcdef,
563 0x12345678, 565 0x12345678,
564 0x90abcdef 566 0x90abcdef
565 ]), 'parsed an IV value'); 567 ]), 'parsed an IV value');
566 }); 568 });
569
570 QUnit.test('parses minimal #EXT-X-KEY tags', function() {
571 let manifest = '#EXT-X-KEY:\n';
572 let element;
567 573
568 test('parses minimal #EXT-X-KEY tags', function() { 574 this.parseStream.on('data', function(elem) {
569 var
570 manifest = '#EXT-X-KEY:\n',
571 element;
572 parseStream.on('data', function(elem) {
573 element = elem; 575 element = elem;
574 }); 576 });
575 lineStream.push(manifest); 577 this.lineStream.push(manifest);
576 578
577 ok(element, 'an event was triggered'); 579 QUnit.ok(element, 'an event was triggered');
578 deepEqual(element, { 580 QUnit.deepEqual(element, {
579 type: 'tag', 581 type: 'tag',
580 tagType: 'key' 582 tagType: 'key'
581 }, 'parsed a minimal key tag'); 583 }, 'parsed a minimal key tag');
582 }); 584 });
585
586 QUnit.test('parses lightly-broken #EXT-X-KEY tags', function() {
587 let manifest = '#EXT-X-KEY:URI=\'https://example.com/single-quote\',METHOD=AES-128\n';
588 let element;
583 589
584 test('parses lightly-broken #EXT-X-KEY tags', function() { 590 this.parseStream.on('data', function(elem) {
585 var
586 manifest = '#EXT-X-KEY:URI=\'https://example.com/single-quote\',METHOD=AES-128\n',
587 element;
588 parseStream.on('data', function(elem) {
589 element = elem; 591 element = elem;
590 }); 592 });
591 lineStream.push(manifest); 593 this.lineStream.push(manifest);
592 594
593 strictEqual(element.attributes.URI, 595 QUnit.strictEqual(element.attributes.URI,
594 'https://example.com/single-quote', 596 'https://example.com/single-quote',
595 'parsed a single-quoted uri'); 597 'parsed a single-quoted uri');
596 598
597 element = null; 599 element = null;
598 manifest = '#EXT-X-KEYURI="https://example.com/key",METHOD=AES-128\n'; 600 manifest = '#EXT-X-KEYURI="https://example.com/key",METHOD=AES-128\n';
599 lineStream.push(manifest); 601 this.lineStream.push(manifest);
600 strictEqual(element.tagType, 'key', 'parsed the tag type'); 602 QUnit.strictEqual(element.tagType, 'key', 'parsed the tag type');
601 strictEqual(element.attributes.URI, 603 QUnit.strictEqual(element.attributes.URI,
602 'https://example.com/key', 604 'https://example.com/key',
603 'inferred a colon after the tag type'); 605 'inferred a colon after the tag type');
604 606
605 element = null; 607 element = null;
606 manifest = '#EXT-X-KEY: URI = "https://example.com/key",METHOD=AES-128\n'; 608 manifest = '#EXT-X-KEY: URI = "https://example.com/key",METHOD=AES-128\n';
607 lineStream.push(manifest); 609 this.lineStream.push(manifest);
608 strictEqual(element.attributes.URI, 610 QUnit.strictEqual(element.attributes.URI,
609 'https://example.com/key', 611 'https://example.com/key',
610 'trims and removes quotes around the URI'); 612 'trims and removes quotes around the URI');
611 }); 613 });
614
615 QUnit.test('ignores empty lines', function() {
616 let manifest = '\n';
617 let event = false;
612 618
613 test('ignores empty lines', function() { 619 this.parseStream.on('data', function() {
614 var
615 manifest = '\n',
616 event = false;
617 parseStream.on('data', function() {
618 event = true; 620 event = true;
619 }); 621 });
620 lineStream.push(manifest); 622 this.lineStream.push(manifest);
621 623
622 ok(!event, 'no event is triggered'); 624 QUnit.ok(!event, 'no event is triggered');
623 }); 625 });
624 626
625 QUnit.module('m3u8 parser'); 627 QUnit.module('m3u8 parser');
626 628
627 test('can be constructed', function() { 629 QUnit.test('can be constructed', function() {
628 notStrictEqual(new Parser(), undefined, 'parser is defined'); 630 QUnit.notStrictEqual(typeof new Parser(), 'undefined', 'parser is defined');
629 }); 631 });
630 632
631 QUnit.module('m3u8s'); 633 QUnit.module('m3u8s');
632 634
633 test('parses static manifests as expected', function() { 635 QUnit.test('parses static manifests as expected', function() {
634 var key; 636 let key;
635 for (key in window.manifests) { 637
636 if (window.expected[key]) { 638 for (key in testDataManifests) {
637 parser = new Parser(); 639 if (testDataExpected[key]) {
638 parser.push(window.manifests[key]); 640 let parser = new Parser();
639 deepEqual(parser.manifest, 641
640 window.expected[key], 642 parser.push(testDataManifests[key]);
641 key + '.m3u8 was parsed correctly'); 643 QUnit.deepEqual(
644 parser.manifest,
645 testDataExpected[key],
646 key + '.m3u8 was parsed correctly'
647 );
642 } 648 }
643 } 649 }
644 }); 650 });
645
646 })(window, window.console);
......
1 import manifests from './data/manifests';
2 import expected from './data/expected';
3 window.manifests = manifests;
4 window.expected = expected;
5