d7019d06 by David LaPalomento

Merge pull request #534 from BrandonOCasey/browserify-p2

browserify-p2: m3u8, stream, and stub
2 parents b0caca88 c2ce93db
...@@ -34,5 +34,5 @@ dist-test/ ...@@ -34,5 +34,5 @@ dist-test/
34 docs/api/ 34 docs/api/
35 es5/ 35 es5/
36 tmp 36 tmp
37 test/data/manifests.js 37 test/test-manifests.js
38 test/data/expected.js 38 test/test-expected.js
......
...@@ -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": [
...@@ -69,7 +71,8 @@ ...@@ -69,7 +71,8 @@
69 "test/karma", 71 "test/karma",
70 "scripts", 72 "scripts",
71 "utils", 73 "utils",
72 "test/data" 74 "test/test-manifests.js",
75 "test/test-expected.js"
73 ] 76 ]
74 }, 77 },
75 "files": [ 78 "files": [
......
...@@ -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()
......
1 var fs = require('fs'); 1 var fs = require('fs');
2 var path = require('path'); 2 var path = require('path');
3 3
4 var basePath = path.resolve(__dirname + '/..'); 4 var basePath = path.resolve(__dirname, '..');
5 var testDataDir = basePath + '/test/data'; 5 var testDataDir = path.join(basePath,'test');
6 var manifestDir = basePath + '/utils/manifest'; 6 var manifestDir = path.join(basePath, 'utils', 'manifest');
7 var manifestFilepath = testDataDir + '/manifests.js'; 7 var manifestFilepath = path.join(testDataDir, 'test-manifests.js');
8 var expectedFilepath = testDataDir + '/expected.js'; 8 var expectedFilepath = path.join(testDataDir, 'test-expected.js');
9
10 9
11 var build = function() { 10 var build = function() {
12 var manifests = 'window.manifests = {\n'; 11 var manifests = 'export default {\n';
13 var expected = 'window.expected = {\n'; 12 var expected = 'export default {\n';
14 13
15 var files = fs.readdirSync(manifestDir); 14 var files = fs.readdirSync(manifestDir);
16 while (files.length > 0) { 15 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,30 +5,55 @@ ...@@ -5,30 +5,55 @@
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) { 8
9 var 9 import Stream from '../stream';
10 noop = function() {}, 10 import {mergeOptions} from 'video.js';
11 11 /**
12 // "forgiving" attribute list psuedo-grammar: 12 * A stream that buffers string input and generates a `data` event for each
13 // attributes -> keyvalue (',' keyvalue)* 13 * line.
14 // keyvalue -> key '=' value 14 */
15 // key -> [^=]* 15 export class LineStream extends Stream {
16 // value -> '"' [^"]* '"' | [^,]* 16 constructor() {
17 attributeSeparator = (function() { 17 super();
18 var 18 this.buffer = '';
19 key = '[^=]*', 19 }
20 value = '"[^"]*"|[^,]*', 20
21 keyvalue = '(?:' + key + ')=(?:' + value + ')'; 21 /**
22 * Add new data to be parsed.
23 * @param data {string} the text to process
24 */
25 push(data) {
26 let nextNewline;
27
28 this.buffer += data;
29 nextNewline = this.buffer.indexOf('\n');
30
31 for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
32 this.trigger('data', this.buffer.substring(0, nextNewline));
33 this.buffer = this.buffer.substring(nextNewline + 1);
34 }
35 }
36 }
37
38 // "forgiving" attribute list psuedo-grammar:
39 // attributes -> keyvalue (',' keyvalue)*
40 // keyvalue -> key '=' value
41 // key -> [^=]*
42 // value -> '"' [^"]* '"' | [^,]*
43 const attributeSeparator = function() {
44 let key = '[^=]*';
45 let value = '"[^"]*"|[^,]*';
46 let keyvalue = '(?:' + key + ')=(?:' + value + ')';
22 47
23 return new RegExp('(?:^|,)(' + keyvalue + ')'); 48 return new RegExp('(?:^|,)(' + keyvalue + ')');
24 })(), 49 };
25 parseAttributes = function(attributes) { 50
26 var 51 const parseAttributes = function(attributes) {
27 // split the string using attributes as the separator 52 // split the string using attributes as the separator
28 attrs = attributes.split(attributeSeparator), 53 let attrs = attributes.split(attributeSeparator());
29 i = attrs.length, 54 let i = attrs.length;
30 result = {}, 55 let result = {};
31 attr; 56 let attr;
32 57
33 while (i--) { 58 while (i--) {
34 // filter out unmatched portions of the string 59 // filter out unmatched portions of the string
...@@ -37,7 +62,7 @@ ...@@ -37,7 +62,7 @@
37 } 62 }
38 63
39 // split the key and value 64 // split the key and value
40 attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); 65 attr = (/([^=]*)=(.*)/).exec(attrs[i]).slice(1);
41 // trim whitespace and remove optional quotes around the value 66 // trim whitespace and remove optional quotes around the value
42 attr[0] = attr[0].replace(/^\s+|\s+$/g, ''); 67 attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
43 attr[1] = attr[1].replace(/^\s+|\s+$/g, ''); 68 attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
...@@ -45,39 +70,9 @@ ...@@ -45,39 +70,9 @@
45 result[attr[0]] = attr[1]; 70 result[attr[0]] = attr[1];
46 } 71 }
47 return result; 72 return result;
48 }, 73 };
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
56 * line.
57 */
58 LineStream = function() {
59 var buffer = '';
60 LineStream.prototype.init.call(this);
61
62 /**
63 * Add new data to be parsed.
64 * @param data {string} the text to process
65 */
66 this.push = function(data) {
67 var nextNewline;
68
69 buffer += data;
70 nextNewline = buffer.indexOf('\n');
71
72 for (; nextNewline > -1; nextNewline = buffer.indexOf('\n')) {
73 this.trigger('data', buffer.substring(0, nextNewline));
74 buffer = buffer.substring(nextNewline + 1);
75 }
76 };
77 };
78 LineStream.prototype = new Stream();
79 74
80 /** 75 /**
81 * A line-level M3U8 parser event stream. It expects to receive input one 76 * 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 77 * 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 78 * interpretation of a manifest can be useful if the manifest is expected to
...@@ -98,18 +93,20 @@ ...@@ -98,18 +93,20 @@
98 * tags are given the tag type `unknown` and a single additional property 93 * tags are given the tag type `unknown` and a single additional property
99 * `data` with the remainder of the input. 94 * `data` with the remainder of the input.
100 */ 95 */
101 ParseStream = function() { 96 export class ParseStream extends Stream {
102 ParseStream.prototype.init.call(this); 97 constructor() {
103 }; 98 super();
104 ParseStream.prototype = new Stream(); 99 }
100
105 /** 101 /**
106 * Parses an additional line of input. 102 * Parses an additional line of input.
107 * @param line {string} a single line of an M3U8 file to parse 103 * @param line {string} a single line of an M3U8 file to parse
108 */ 104 */
109 ParseStream.prototype.push = function(line) { 105 push(line) {
110 var match, event; 106 let match;
107 let event;
111 108
112 //strip whitespace 109 // strip whitespace
113 line = line.replace(/^[\u0000\s]+|[\u0000\s]+$/g, ''); 110 line = line.replace(/^[\u0000\s]+|[\u0000\s]+$/g, '');
114 if (line.length === 0) { 111 if (line.length === 0) {
115 // ignore empty lines 112 // ignore empty lines
...@@ -134,12 +131,12 @@ ...@@ -134,12 +131,12 @@
134 return; 131 return;
135 } 132 }
136 133
137 //strip off any carriage returns here so the regex matching 134 // strip off any carriage returns here so the regex matching
138 //doesn't have to account for them. 135 // doesn't have to account for them.
139 line = line.replace('\r',''); 136 line = line.replace('\r', '');
140 137
141 // Tags 138 // Tags
142 match = /^#EXTM3U/.exec(line); 139 match = (/^#EXTM3U/).exec(line);
143 if (match) { 140 if (match) {
144 this.trigger('data', { 141 this.trigger('data', {
145 type: 'tag', 142 type: 'tag',
...@@ -271,10 +268,9 @@ ...@@ -271,10 +268,9 @@
271 event.attributes = parseAttributes(match[1]); 268 event.attributes = parseAttributes(match[1]);
272 269
273 if (event.attributes.RESOLUTION) { 270 if (event.attributes.RESOLUTION) {
274 (function() { 271 let split = event.attributes.RESOLUTION.split('x');
275 var 272 let resolution = {};
276 split = event.attributes.RESOLUTION.split('x'), 273
277 resolution = {};
278 if (split[0]) { 274 if (split[0]) {
279 resolution.width = parseInt(split[0], 10); 275 resolution.width = parseInt(split[0], 10);
280 } 276 }
...@@ -282,7 +278,6 @@ ...@@ -282,7 +278,6 @@
282 resolution.height = parseInt(split[1], 10); 278 resolution.height = parseInt(split[1], 10);
283 } 279 }
284 event.attributes.RESOLUTION = resolution; 280 event.attributes.RESOLUTION = resolution;
285 })();
286 } 281 }
287 if (event.attributes.BANDWIDTH) { 282 if (event.attributes.BANDWIDTH) {
288 event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10); 283 event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
...@@ -320,7 +315,7 @@ ...@@ -320,7 +315,7 @@
320 event.attributes = parseAttributes(match[1]); 315 event.attributes = parseAttributes(match[1]);
321 // parse the IV string into a Uint32Array 316 // parse the IV string into a Uint32Array
322 if (event.attributes.IV) { 317 if (event.attributes.IV) {
323 if (event.attributes.IV.substring(0,2) === '0x') { 318 if (event.attributes.IV.substring(0, 2) === '0x') {
324 event.attributes.IV = event.attributes.IV.substring(2); 319 event.attributes.IV = event.attributes.IV.substring(2);
325 } 320 }
326 321
...@@ -341,7 +336,8 @@ ...@@ -341,7 +336,8 @@
341 type: 'tag', 336 type: 'tag',
342 data: line.slice(4, line.length) 337 data: line.slice(4, line.length)
343 }); 338 });
344 }; 339 }
340 }
345 341
346 /** 342 /**
347 * A parser for M3U8 files. The current interpretation of the input is 343 * A parser for M3U8 files. The current interpretation of the input is
...@@ -361,17 +357,19 @@ ...@@ -361,17 +357,19 @@
361 * events during the parse if it encounters input that seems invalid or 357 * events during the parse if it encounters input that seems invalid or
362 * requires some property of the manifest object to be defaulted. 358 * requires some property of the manifest object to be defaulted.
363 */ 359 */
364 Parser = function() { 360 export class Parser extends Stream {
365 var 361 constructor() {
366 self = this, 362 super();
367 uris = [],
368 currentUri = {},
369 key;
370 Parser.prototype.init.call(this);
371
372 this.lineStream = new LineStream(); 363 this.lineStream = new LineStream();
373 this.parseStream = new ParseStream(); 364 this.parseStream = new ParseStream();
374 this.lineStream.pipe(this.parseStream); 365 this.lineStream.pipe(this.parseStream);
366 /* eslint-disable consistent-this */
367 let self = this;
368 /* eslint-enable consistent-this */
369 let uris = [];
370 let currentUri = {};
371 let key;
372 let noop = function() {};
375 373
376 // the manifest is empty until the parse stream begins delivering data 374 // the manifest is empty until the parse stream begins delivering data
377 this.manifest = { 375 this.manifest = {
...@@ -382,10 +380,10 @@ ...@@ -382,10 +380,10 @@
382 // update the manifest with the m3u8 entry from the parse stream 380 // update the manifest with the m3u8 entry from the parse stream
383 this.parseStream.on('data', function(entry) { 381 this.parseStream.on('data', function(entry) {
384 ({ 382 ({
385 tag: function() { 383 tag() {
386 // switch based on the tag type 384 // switch based on the tag type
387 (({ 385 (({
388 'allow-cache': function() { 386 'allow-cache'() {
389 this.manifest.allowCache = entry.allowed; 387 this.manifest.allowCache = entry.allowed;
390 if (!('allowed' in entry)) { 388 if (!('allowed' in entry)) {
391 this.trigger('info', { 389 this.trigger('info', {
...@@ -394,8 +392,9 @@ ...@@ -394,8 +392,9 @@
394 this.manifest.allowCache = true; 392 this.manifest.allowCache = true;
395 } 393 }
396 }, 394 },
397 'byterange': function() { 395 byterange() {
398 var byterange = {}; 396 let byterange = {};
397
399 if ('length' in entry) { 398 if ('length' in entry) {
400 currentUri.byterange = byterange; 399 currentUri.byterange = byterange;
401 byterange.length = entry.length; 400 byterange.length = entry.length;
...@@ -412,10 +411,10 @@ ...@@ -412,10 +411,10 @@
412 byterange.offset = entry.offset; 411 byterange.offset = entry.offset;
413 } 412 }
414 }, 413 },
415 'endlist': function() { 414 endlist() {
416 this.manifest.endList = true; 415 this.manifest.endList = true;
417 }, 416 },
418 'inf': function() { 417 inf() {
419 if (!('mediaSequence' in this.manifest)) { 418 if (!('mediaSequence' in this.manifest)) {
420 this.manifest.mediaSequence = 0; 419 this.manifest.mediaSequence = 0;
421 this.trigger('info', { 420 this.trigger('info', {
...@@ -435,7 +434,7 @@ ...@@ -435,7 +434,7 @@
435 this.manifest.segments = uris; 434 this.manifest.segments = uris;
436 435
437 }, 436 },
438 'key': function() { 437 key() {
439 if (!entry.attributes) { 438 if (!entry.attributes) {
440 this.trigger('warn', { 439 this.trigger('warn', {
441 message: 'ignoring key declaration without attribute list' 440 message: 'ignoring key declaration without attribute list'
...@@ -465,11 +464,11 @@ ...@@ -465,11 +464,11 @@
465 uri: entry.attributes.URI 464 uri: entry.attributes.URI
466 }; 465 };
467 466
468 if (entry.attributes.IV !== undefined) { 467 if (typeof entry.attributes.IV !== 'undefined') {
469 key.iv = entry.attributes.IV; 468 key.iv = entry.attributes.IV;
470 } 469 }
471 }, 470 },
472 'media-sequence': function() { 471 'media-sequence'() {
473 if (!isFinite(entry.number)) { 472 if (!isFinite(entry.number)) {
474 this.trigger('warn', { 473 this.trigger('warn', {
475 message: 'ignoring invalid media sequence: ' + entry.number 474 message: 'ignoring invalid media sequence: ' + entry.number
...@@ -478,7 +477,7 @@ ...@@ -478,7 +477,7 @@
478 } 477 }
479 this.manifest.mediaSequence = entry.number; 478 this.manifest.mediaSequence = entry.number;
480 }, 479 },
481 'discontinuity-sequence': function() { 480 'discontinuity-sequence'() {
482 if (!isFinite(entry.number)) { 481 if (!isFinite(entry.number)) {
483 this.trigger('warn', { 482 this.trigger('warn', {
484 message: 'ignoring invalid discontinuity sequence: ' + entry.number 483 message: 'ignoring invalid discontinuity sequence: ' + entry.number
...@@ -487,7 +486,7 @@ ...@@ -487,7 +486,7 @@
487 } 486 }
488 this.manifest.discontinuitySequence = entry.number; 487 this.manifest.discontinuitySequence = entry.number;
489 }, 488 },
490 'playlist-type': function() { 489 'playlist-type'() {
491 if (!(/VOD|EVENT/).test(entry.playlistType)) { 490 if (!(/VOD|EVENT/).test(entry.playlistType)) {
492 this.trigger('warn', { 491 this.trigger('warn', {
493 message: 'ignoring unknown playlist type: ' + entry.playlist 492 message: 'ignoring unknown playlist type: ' + entry.playlist
...@@ -496,7 +495,7 @@ ...@@ -496,7 +495,7 @@
496 } 495 }
497 this.manifest.playlistType = entry.playlistType; 496 this.manifest.playlistType = entry.playlistType;
498 }, 497 },
499 'stream-inf': function() { 498 'stream-inf'() {
500 this.manifest.playlists = uris; 499 this.manifest.playlists = uris;
501 500
502 if (!entry.attributes) { 501 if (!entry.attributes) {
...@@ -512,11 +511,11 @@ ...@@ -512,11 +511,11 @@
512 currentUri.attributes = mergeOptions(currentUri.attributes, 511 currentUri.attributes = mergeOptions(currentUri.attributes,
513 entry.attributes); 512 entry.attributes);
514 }, 513 },
515 'discontinuity': function() { 514 discontinuity() {
516 currentUri.discontinuity = true; 515 currentUri.discontinuity = true;
517 this.manifest.discontinuityStarts.push(uris.length); 516 this.manifest.discontinuityStarts.push(uris.length);
518 }, 517 },
519 'targetduration': function() { 518 targetduration() {
520 if (!isFinite(entry.duration) || entry.duration < 0) { 519 if (!isFinite(entry.duration) || entry.duration < 0) {
521 this.trigger('warn', { 520 this.trigger('warn', {
522 message: 'ignoring invalid target duration: ' + entry.duration 521 message: 'ignoring invalid target duration: ' + entry.duration
...@@ -525,7 +524,7 @@ ...@@ -525,7 +524,7 @@
525 } 524 }
526 this.manifest.targetDuration = entry.duration; 525 this.manifest.targetDuration = entry.duration;
527 }, 526 },
528 'totalduration': function() { 527 totalduration() {
529 if (!isFinite(entry.duration) || entry.duration < 0) { 528 if (!isFinite(entry.duration) || entry.duration < 0) {
530 this.trigger('warn', { 529 this.trigger('warn', {
531 message: 'ignoring invalid total duration: ' + entry.duration 530 message: 'ignoring invalid total duration: ' + entry.duration
...@@ -536,7 +535,7 @@ ...@@ -536,7 +535,7 @@
536 } 535 }
537 })[entry.tagType] || noop).call(self); 536 })[entry.tagType] || noop).call(self);
538 }, 537 },
539 uri: function() { 538 uri() {
540 currentUri.uri = entry.uri; 539 currentUri.uri = entry.uri;
541 uris.push(currentUri); 540 uris.push(currentUri);
542 541
...@@ -556,33 +555,36 @@ ...@@ -556,33 +555,36 @@
556 // prepare for the next URI 555 // prepare for the next URI
557 currentUri = {}; 556 currentUri = {};
558 }, 557 },
559 comment: function() { 558 comment() {
560 // comments are not important for playback 559 // comments are not important for playback
561 } 560 }
562 })[entry.type].call(self); 561 })[entry.type].call(self);
563 }); 562 });
564 }; 563
565 Parser.prototype = new Stream(); 564 }
565
566 /** 566 /**
567 * Parse the input string and update the manifest object. 567 * Parse the input string and update the manifest object.
568 * @param chunk {string} a potentially incomplete portion of the manifest 568 * @param chunk {string} a potentially incomplete portion of the manifest
569 */ 569 */
570 Parser.prototype.push = function(chunk) { 570 push(chunk) {
571 this.lineStream.push(chunk); 571 this.lineStream.push(chunk);
572 }; 572 }
573
573 /** 574 /**
574 * Flush any remaining input. This can be handy if the last line of an M3U8 575 * 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 576 * manifest did not contain a trailing newline but the file has been
576 * completely received. 577 * completely received.
577 */ 578 */
578 Parser.prototype.end = function() { 579 end() {
579 // flush any buffered input 580 // flush any buffered input
580 this.lineStream.push('\n'); 581 this.lineStream.push('\n');
581 }; 582 }
582 583
583 window.videojs.m3u8 = { 584 }
584 LineStream: LineStream, 585
585 ParseStream: ParseStream, 586 export default {
586 Parser: Parser 587 LineStream,
587 }; 588 ParseStream,
588 })(window.videojs, window.parseInt, window.isFinite, window.videojs.mergeOptions); 589 Parser
590 };
......
...@@ -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 './test-expected.js';
4 m3u8 = window.videojs.m3u8, 4 import testDataManifests from './test-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(temporaryLines.length,
79 72 permanentLines.length,
80 ok(lineStream.off('data', temporary), 'a listener was removed'); 73 'both callbacks receive the event');
81 lineStream.push('line two\n'); 74
82 strictEqual(1, temporaryLines.length, 'no new events are received'); 75 QUnit.ok(this.lineStream.off('data', temporary), 'a listener was removed');
83 strictEqual(2, permanentLines.length, 'new events are still received'); 76 this.lineStream.push('line two\n');
84 }); 77 QUnit.strictEqual(1, temporaryLines.length, 'no new events are received');
85 78 QUnit.strictEqual(2, permanentLines.length, 'new events are still received');
86 QUnit.module('ParseStream', { 79 });
87 setup: function() { 80
88 lineStream = new LineStream(); 81 QUnit.module('ParseStream', {
89 parseStream = new ParseStream(); 82 beforeEach() {
90 lineStream.pipe(parseStream); 83 this.lineStream = new LineStream();
84 this.parseStream = new ParseStream();
85 this.lineStream.pipe(this.parseStream);
91 } 86 }
92 }); 87 });
93 test('parses comment lines', function() { 88 QUnit.test('parses comment lines', function() {
94 var 89 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', 90 let element;
96 element; 91
97 parseStream.on('data', function(elem) { 92 this.parseStream.on('data', function(elem) {
98 element = elem; 93 element = elem;
99 }); 94 });
100 lineStream.push(manifest); 95 this.lineStream.push(manifest);
101 96
102 ok(element, 'an event was triggered'); 97 QUnit.ok(element, 'an event was triggered');
103 strictEqual(element.type, 'comment', 'the type is comment'); 98 QUnit.strictEqual(element.type, 'comment', 'the type is comment');
104 strictEqual(element.text, 99 QUnit.strictEqual(element.text,
105 manifest.slice(1, manifest.length - 1), 100 manifest.slice(1, manifest.length - 1),
106 'the comment text is parsed'); 101 'the comment text is parsed');
107 }); 102 });
108 test('parses uri lines', function() { 103 QUnit.test('parses uri lines', function() {
109 var 104 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', 105 let element;
111 element; 106
112 parseStream.on('data', function(elem) { 107 this.parseStream.on('data', function(elem) {
113 element = elem; 108 element = elem;
114 }); 109 });
115 lineStream.push(manifest); 110 this.lineStream.push(manifest);
116 111
117 ok(element, 'an event was triggered'); 112 QUnit.ok(element, 'an event was triggered');
118 strictEqual(element.type, 'uri', 'the type is uri'); 113 QUnit.strictEqual(element.type, 'uri', 'the type is uri');
119 strictEqual(element.uri, 114 QUnit.strictEqual(element.uri,
120 manifest.substring(0, manifest.length - 1), 115 manifest.substring(0, manifest.length - 1),
121 'the uri text is parsed'); 116 'the uri text is parsed');
122 }); 117 });
123 test('parses unknown tag types', function() { 118 QUnit.test('parses unknown tag types', function() {
124 var 119 let manifest = '#EXT-X-EXAMPLE-TAG:some,additional,stuff\n';
125 manifest = '#EXT-X-EXAMPLE-TAG:some,additional,stuff\n', 120 let element;
126 element; 121
127 parseStream.on('data', function(elem) { 122 this.parseStream.on('data', function(elem) {
128 element = elem; 123 element = elem;
129 }); 124 });
130 lineStream.push(manifest); 125 this.lineStream.push(manifest);
131 126
132 ok(element, 'an event was triggered'); 127 QUnit.ok(element, 'an event was triggered');
133 strictEqual(element.type, 'tag', 'the type is tag'); 128 QUnit.strictEqual(element.type, 'tag', 'the type is tag');
134 strictEqual(element.data, 129 QUnit.strictEqual(element.data,
135 manifest.slice(4, manifest.length - 1), 130 manifest.slice(4, manifest.length - 1),
136 'unknown tag data is preserved'); 131 'unknown tag data is preserved');
137 }); 132 });
138 133
139 // #EXTM3U 134 // #EXTM3U
140 test('parses #EXTM3U tags', function() { 135 QUnit.test('parses #EXTM3U tags', function() {
141 var 136 let manifest = '#EXTM3U\n';
142 manifest = '#EXTM3U\n', 137 let element;
143 element; 138
144 parseStream.on('data', function(elem) { 139 this.parseStream.on('data', function(elem) {
145 element = elem; 140 element = elem;
146 }); 141 });
147 lineStream.push(manifest); 142 this.lineStream.push(manifest);
148 143
149 ok(element, 'an event was triggered'); 144 QUnit.ok(element, 'an event was triggered');
150 strictEqual(element.type, 'tag', 'the line type is tag'); 145 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
151 strictEqual(element.tagType, 'm3u', 'the tag type is m3u'); 146 QUnit.strictEqual(element.tagType, 'm3u', 'the tag type is m3u');
152 }); 147 });
153 148
154 // #EXTINF 149 // #EXTINF
155 test('parses minimal #EXTINF tags', function() { 150 QUnit.test('parses minimal #EXTINF tags', function() {
156 var 151 let manifest = '#EXTINF\n';
157 manifest = '#EXTINF\n', 152 let element;
158 element; 153
159 parseStream.on('data', function(elem) { 154 this.parseStream.on('data', function(elem) {
160 element = elem; 155 element = elem;
161 }); 156 });
162 lineStream.push(manifest); 157 this.lineStream.push(manifest);
163 158
164 ok(element, 'an event was triggered'); 159 QUnit.ok(element, 'an event was triggered');
165 strictEqual(element.type, 'tag', 'the line type is tag'); 160 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
166 strictEqual(element.tagType, 'inf', 'the tag type is inf'); 161 QUnit.strictEqual(element.tagType, 'inf', 'the tag type is inf');
167 }); 162 });
168 test('parses #EXTINF tags with durations', function() { 163 QUnit.test('parses #EXTINF tags with durations', function() {
169 var 164 let manifest = '#EXTINF:15\n';
170 manifest = '#EXTINF:15\n', 165 let element;
171 element; 166
172 parseStream.on('data', function(elem) { 167 this.parseStream.on('data', function(elem) {
173 element = elem; 168 element = elem;
174 }); 169 });
175 lineStream.push(manifest); 170 this.lineStream.push(manifest);
176 171
177 ok(element, 'an event was triggered'); 172 QUnit.ok(element, 'an event was triggered');
178 strictEqual(element.type, 'tag', 'the line type is tag'); 173 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
179 strictEqual(element.tagType, 'inf', 'the tag type is inf'); 174 QUnit.strictEqual(element.tagType, 'inf', 'the tag type is inf');
180 strictEqual(element.duration, 15, 'the duration is parsed'); 175 QUnit.strictEqual(element.duration, 15, 'the duration is parsed');
181 ok(!('title' in element), 'no title is parsed'); 176 QUnit.ok(!('title' in element), 'no title is parsed');
182 177
183 manifest = '#EXTINF:21,\n'; 178 manifest = '#EXTINF:21,\n';
184 lineStream.push(manifest); 179 this.lineStream.push(manifest);
185 180
186 ok(element, 'an event was triggered'); 181 QUnit.ok(element, 'an event was triggered');
187 strictEqual(element.type, 'tag', 'the line type is tag'); 182 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
188 strictEqual(element.tagType, 'inf', 'the tag type is inf'); 183 QUnit.strictEqual(element.tagType, 'inf', 'the tag type is inf');
189 strictEqual(element.duration, 21, 'the duration is parsed'); 184 QUnit.strictEqual(element.duration, 21, 'the duration is parsed');
190 ok(!('title' in element), 'no title is parsed'); 185 QUnit.ok(!('title' in element), 'no title is parsed');
191 }); 186 });
192 test('parses #EXTINF tags with a duration and title', function() { 187 QUnit.test('parses #EXTINF tags with a duration and title', function() {
193 var 188 let manifest = '#EXTINF:13,Does anyone really use the title attribute?\n';
194 manifest = '#EXTINF:13,Does anyone really use the title attribute?\n', 189 let element;
195 element; 190
196 parseStream.on('data', function(elem) { 191 this.parseStream.on('data', function(elem) {
197 element = elem; 192 element = elem;
198 }); 193 });
199 lineStream.push(manifest); 194 this.lineStream.push(manifest);
200 195
201 ok(element, 'an event was triggered'); 196 QUnit.ok(element, 'an event was triggered');
202 strictEqual(element.type, 'tag', 'the line type is tag'); 197 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
203 strictEqual(element.tagType, 'inf', 'the tag type is inf'); 198 QUnit.strictEqual(element.tagType, 'inf', 'the tag type is inf');
204 strictEqual(element.duration, 13, 'the duration is parsed'); 199 QUnit.strictEqual(element.duration, 13, 'the duration is parsed');
205 strictEqual(element.title, 200 QUnit.strictEqual(element.title,
206 manifest.substring(manifest.indexOf(',') + 1, manifest.length - 1), 201 manifest.substring(manifest.indexOf(',') + 1, manifest.length - 1),
207 'the title is parsed'); 202 'the title is parsed');
208 }); 203 });
209 test('parses #EXTINF tags with carriage returns', function() { 204 QUnit.test('parses #EXTINF tags with carriage returns', function() {
210 var 205 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', 206 let element;
212 element; 207
213 parseStream.on('data', function(elem) { 208 this.parseStream.on('data', function(elem) {
214 element = elem; 209 element = elem;
215 }); 210 });
216 lineStream.push(manifest); 211 this.lineStream.push(manifest);
217 212
218 ok(element, 'an event was triggered'); 213 QUnit.ok(element, 'an event was triggered');
219 strictEqual(element.type, 'tag', 'the line type is tag'); 214 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
220 strictEqual(element.tagType, 'inf', 'the tag type is inf'); 215 QUnit.strictEqual(element.tagType, 'inf', 'the tag type is inf');
221 strictEqual(element.duration, 13, 'the duration is parsed'); 216 QUnit.strictEqual(element.duration, 13, 'the duration is parsed');
222 strictEqual(element.title, 217 QUnit.strictEqual(element.title,
223 manifest.substring(manifest.indexOf(',') + 1, manifest.length - 2), 218 manifest.substring(manifest.indexOf(',') + 1, manifest.length - 2),
224 'the title is parsed'); 219 'the title is parsed');
225 }); 220 });
226 221
227 // #EXT-X-TARGETDURATION 222 // #EXT-X-TARGETDURATION
228 test('parses minimal #EXT-X-TARGETDURATION tags', function() { 223 QUnit.test('parses minimal #EXT-X-TARGETDURATION tags', function() {
229 var 224 let manifest = '#EXT-X-TARGETDURATION\n';
230 manifest = '#EXT-X-TARGETDURATION\n', 225 let element;
231 element; 226
232 parseStream.on('data', function(elem) { 227 this.parseStream.on('data', function(elem) {
233 element = elem; 228 element = elem;
234 }); 229 });
235 lineStream.push(manifest); 230 this.lineStream.push(manifest);
236 231
237 ok(element, 'an event was triggered'); 232 QUnit.ok(element, 'an event was triggered');
238 strictEqual(element.type, 'tag', 'the line type is tag'); 233 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
239 strictEqual(element.tagType, 'targetduration', 'the tag type is targetduration'); 234 QUnit.strictEqual(element.tagType, 'targetduration', 'the tag type is targetduration');
240 ok(!('duration' in element), 'no duration is parsed'); 235 QUnit.ok(!('duration' in element), 'no duration is parsed');
241 }); 236 });
242 test('parses #EXT-X-TARGETDURATION with duration', function() { 237 QUnit.test('parses #EXT-X-TARGETDURATION with duration', function() {
243 var 238 let manifest = '#EXT-X-TARGETDURATION:47\n';
244 manifest = '#EXT-X-TARGETDURATION:47\n', 239 let element;
245 element; 240
246 parseStream.on('data', function(elem) { 241 this.parseStream.on('data', function(elem) {
247 element = elem; 242 element = elem;
248 }); 243 });
249 lineStream.push(manifest); 244 this.lineStream.push(manifest);
250 245
251 ok(element, 'an event was triggered'); 246 QUnit.ok(element, 'an event was triggered');
252 strictEqual(element.type, 'tag', 'the line type is tag'); 247 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
253 strictEqual(element.tagType, 'targetduration', 'the tag type is targetduration'); 248 QUnit.strictEqual(element.tagType, 'targetduration', 'the tag type is targetduration');
254 strictEqual(element.duration, 47, 'the duration is parsed'); 249 QUnit.strictEqual(element.duration, 47, 'the duration is parsed');
255 }); 250 });
256 251
257 // #EXT-X-VERSION 252 // #EXT-X-VERSION
258 test('parses minimal #EXT-X-VERSION tags', function() { 253 QUnit.test('parses minimal #EXT-X-VERSION tags', function() {
259 var 254 let manifest = '#EXT-X-VERSION:\n';
260 manifest = '#EXT-X-VERSION:\n', 255 let element;
261 element; 256
262 parseStream.on('data', function(elem) { 257 this.parseStream.on('data', function(elem) {
263 element = elem; 258 element = elem;
264 }); 259 });
265 lineStream.push(manifest); 260 this.lineStream.push(manifest);
266 261
267 ok(element, 'an event was triggered'); 262 QUnit.ok(element, 'an event was triggered');
268 strictEqual(element.type, 'tag', 'the line type is tag'); 263 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
269 strictEqual(element.tagType, 'version', 'the tag type is version'); 264 QUnit.strictEqual(element.tagType, 'version', 'the tag type is version');
270 ok(!('version' in element), 'no version is present'); 265 QUnit.ok(!('version' in element), 'no version is present');
271 }); 266 });
272 test('parses #EXT-X-VERSION with a version', function() { 267 QUnit.test('parses #EXT-X-VERSION with a version', function() {
273 var 268 let manifest = '#EXT-X-VERSION:99\n';
274 manifest = '#EXT-X-VERSION:99\n', 269 let element;
275 element; 270
276 parseStream.on('data', function(elem) { 271 this.parseStream.on('data', function(elem) {
277 element = elem; 272 element = elem;
278 }); 273 });
279 lineStream.push(manifest); 274 this.lineStream.push(manifest);
280 275
281 ok(element, 'an event was triggered'); 276 QUnit.ok(element, 'an event was triggered');
282 strictEqual(element.type, 'tag', 'the line type is tag'); 277 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
283 strictEqual(element.tagType, 'version', 'the tag type is version'); 278 QUnit.strictEqual(element.tagType, 'version', 'the tag type is version');
284 strictEqual(element.version, 99, 'the version is parsed'); 279 QUnit.strictEqual(element.version, 99, 'the version is parsed');
285 }); 280 });
281
282 // #EXT-X-MEDIA-SEQUENCE
283 QUnit.test('parses minimal #EXT-X-MEDIA-SEQUENCE tags', function() {
284 let manifest = '#EXT-X-MEDIA-SEQUENCE\n';
285 let element;
286 286
287 // #EXT-X-MEDIA-SEQUENCE 287 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; 288 element = elem;
294 }); 289 });
295 lineStream.push(manifest); 290 this.lineStream.push(manifest);
296 291
297 ok(element, 'an event was triggered'); 292 QUnit.ok(element, 'an event was triggered');
298 strictEqual(element.type, 'tag', 'the line type is tag'); 293 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
299 strictEqual(element.tagType, 'media-sequence', 'the tag type is media-sequence'); 294 QUnit.strictEqual(element.tagType, 'media-sequence', 'the tag type is media-sequence');
300 ok(!('number' in element), 'no number is present'); 295 QUnit.ok(!('number' in element), 'no number is present');
301 }); 296 });
302 test('parses #EXT-X-MEDIA-SEQUENCE with sequence numbers', function() { 297 QUnit.test('parses #EXT-X-MEDIA-SEQUENCE with sequence numbers', function() {
303 var 298 let manifest = '#EXT-X-MEDIA-SEQUENCE:109\n';
304 manifest = '#EXT-X-MEDIA-SEQUENCE:109\n', 299 let element;
305 element; 300
306 parseStream.on('data', function(elem) { 301 this.parseStream.on('data', function(elem) {
307 element = elem; 302 element = elem;
308 }); 303 });
309 lineStream.push(manifest); 304 this.lineStream.push(manifest);
310 305
311 ok(element, 'an event was triggered'); 306 QUnit.ok(element, 'an event was triggered');
312 strictEqual(element.type, 'tag', 'the line type is tag'); 307 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
313 strictEqual(element.tagType, 'media-sequence', 'the tag type is media-sequence'); 308 QUnit.strictEqual(element.tagType, 'media-sequence', 'the tag type is media-sequence');
314 ok(element.number, 109, 'the number is parsed'); 309 QUnit.ok(element.number, 109, 'the number is parsed');
315 }); 310 });
311
312 // #EXT-X-PLAYLIST-TYPE
313 QUnit.test('parses minimal #EXT-X-PLAYLIST-TYPE tags', function() {
314 let manifest = '#EXT-X-PLAYLIST-TYPE:\n';
315 let element;
316 316
317 // #EXT-X-PLAYLIST-TYPE 317 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; 318 element = elem;
324 }); 319 });
325 lineStream.push(manifest); 320 this.lineStream.push(manifest);
326 321
327 ok(element, 'an event was triggered'); 322 QUnit.ok(element, 'an event was triggered');
328 strictEqual(element.type, 'tag', 'the line type is tag'); 323 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
329 strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type'); 324 QUnit.strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type');
330 ok(!('playlistType' in element), 'no playlist type is present'); 325 QUnit.ok(!('playlistType' in element), 'no playlist type is present');
331 }); 326 });
332 test('parses #EXT-X-PLAYLIST-TYPE with mutability info', function() { 327 QUnit.test('parses #EXT-X-PLAYLIST-TYPE with mutability info', function() {
333 var 328 let manifest = '#EXT-X-PLAYLIST-TYPE:EVENT\n';
334 manifest = '#EXT-X-PLAYLIST-TYPE:EVENT\n', 329 let element;
335 element; 330
336 parseStream.on('data', function(elem) { 331 this.parseStream.on('data', function(elem) {
337 element = elem; 332 element = elem;
338 }); 333 });
339 lineStream.push(manifest); 334 this.lineStream.push(manifest);
340 335
341 ok(element, 'an event was triggered'); 336 QUnit.ok(element, 'an event was triggered');
342 strictEqual(element.type, 'tag', 'the line type is tag'); 337 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
343 strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type'); 338 QUnit.strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type');
344 strictEqual(element.playlistType, 'EVENT', 'the playlist type is EVENT'); 339 QUnit.strictEqual(element.playlistType, 'EVENT', 'the playlist type is EVENT');
345 340
346 manifest = '#EXT-X-PLAYLIST-TYPE:VOD\n'; 341 manifest = '#EXT-X-PLAYLIST-TYPE:VOD\n';
347 lineStream.push(manifest); 342 this.lineStream.push(manifest);
348 ok(element, 'an event was triggered'); 343 QUnit.ok(element, 'an event was triggered');
349 strictEqual(element.type, 'tag', 'the line type is tag'); 344 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
350 strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type'); 345 QUnit.strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type');
351 strictEqual(element.playlistType, 'VOD', 'the playlist type is VOD'); 346 QUnit.strictEqual(element.playlistType, 'VOD', 'the playlist type is VOD');
352 347
353 manifest = '#EXT-X-PLAYLIST-TYPE:nonsense\n'; 348 manifest = '#EXT-X-PLAYLIST-TYPE:nonsense\n';
354 lineStream.push(manifest); 349 this.lineStream.push(manifest);
355 ok(element, 'an event was triggered'); 350 QUnit.ok(element, 'an event was triggered');
356 strictEqual(element.type, 'tag', 'the line type is tag'); 351 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
357 strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type'); 352 QUnit.strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type');
358 strictEqual(element.playlistType, 'nonsense', 'the playlist type is parsed'); 353 QUnit.strictEqual(element.playlistType, 'nonsense', 'the playlist type is parsed');
359 }); 354 });
360 355
361 // #EXT-X-BYTERANGE 356 // #EXT-X-BYTERANGE
362 test('parses minimal #EXT-X-BYTERANGE tags', function() { 357 QUnit.test('parses minimal #EXT-X-BYTERANGE tags', function() {
363 var 358 let manifest = '#EXT-X-BYTERANGE\n';
364 manifest = '#EXT-X-BYTERANGE\n', 359 let element;
365 element; 360
366 parseStream.on('data', function(elem) { 361 this.parseStream.on('data', function(elem) {
367 element = elem; 362 element = elem;
368 }); 363 });
369 lineStream.push(manifest); 364 this.lineStream.push(manifest);
370 365
371 ok(element, 'an event was triggered'); 366 QUnit.ok(element, 'an event was triggered');
372 strictEqual(element.type, 'tag', 'the line type is tag'); 367 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
373 strictEqual(element.tagType, 'byterange', 'the tag type is byterange'); 368 QUnit.strictEqual(element.tagType, 'byterange', 'the tag type is byterange');
374 ok(!('length' in element), 'no length is present'); 369 QUnit.ok(!('length' in element), 'no length is present');
375 ok(!('offset' in element), 'no offset is present'); 370 QUnit.ok(!('offset' in element), 'no offset is present');
376 }); 371 });
377 test('parses #EXT-X-BYTERANGE with length and offset', function() { 372 QUnit.test('parses #EXT-X-BYTERANGE with length and offset', function() {
378 var 373 let manifest = '#EXT-X-BYTERANGE:45\n';
379 manifest = '#EXT-X-BYTERANGE:45\n', 374 let element;
380 element; 375
381 parseStream.on('data', function(elem) { 376 this.parseStream.on('data', function(elem) {
382 element = elem; 377 element = elem;
383 }); 378 });
384 lineStream.push(manifest); 379 this.lineStream.push(manifest);
385 380
386 ok(element, 'an event was triggered'); 381 QUnit.ok(element, 'an event was triggered');
387 strictEqual(element.type, 'tag', 'the line type is tag'); 382 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
388 strictEqual(element.tagType, 'byterange', 'the tag type is byterange'); 383 QUnit.strictEqual(element.tagType, 'byterange', 'the tag type is byterange');
389 strictEqual(element.length, 45, 'length is parsed'); 384 QUnit.strictEqual(element.length, 45, 'length is parsed');
390 ok(!('offset' in element), 'no offset is present'); 385 QUnit.ok(!('offset' in element), 'no offset is present');
391 386
392 manifest = '#EXT-X-BYTERANGE:108@16\n'; 387 manifest = '#EXT-X-BYTERANGE:108@16\n';
393 lineStream.push(manifest); 388 this.lineStream.push(manifest);
394 ok(element, 'an event was triggered'); 389 QUnit.ok(element, 'an event was triggered');
395 strictEqual(element.type, 'tag', 'the line type is tag'); 390 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
396 strictEqual(element.tagType, 'byterange', 'the tag type is byterange'); 391 QUnit.strictEqual(element.tagType, 'byterange', 'the tag type is byterange');
397 strictEqual(element.length, 108, 'length is parsed'); 392 QUnit.strictEqual(element.length, 108, 'length is parsed');
398 strictEqual(element.offset, 16, 'offset is parsed'); 393 QUnit.strictEqual(element.offset, 16, 'offset is parsed');
399 }); 394 });
400 395
401 // #EXT-X-ALLOW-CACHE 396 // #EXT-X-ALLOW-CACHE
402 test('parses minimal #EXT-X-ALLOW-CACHE tags', function() { 397 QUnit.test('parses minimal #EXT-X-ALLOW-CACHE tags', function() {
403 var 398 let manifest = '#EXT-X-ALLOW-CACHE:\n';
404 manifest = '#EXT-X-ALLOW-CACHE:\n', 399 let element;
405 element; 400
406 parseStream.on('data', function(elem) { 401 this.parseStream.on('data', function(elem) {
407 element = elem; 402 element = elem;
408 }); 403 });
409 lineStream.push(manifest); 404 this.lineStream.push(manifest);
410 405
411 ok(element, 'an event was triggered'); 406 QUnit.ok(element, 'an event was triggered');
412 strictEqual(element.type, 'tag', 'the line type is tag'); 407 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
413 strictEqual(element.tagType, 'allow-cache', 'the tag type is allow-cache'); 408 QUnit.strictEqual(element.tagType, 'allow-cache', 'the tag type is allow-cache');
414 ok(!('allowed' in element), 'no allowed is present'); 409 QUnit.ok(!('allowed' in element), 'no allowed is present');
415 }); 410 });
416 test('parses valid #EXT-X-ALLOW-CACHE tags', function() { 411 QUnit.test('parses valid #EXT-X-ALLOW-CACHE tags', function() {
417 var 412 let manifest = '#EXT-X-ALLOW-CACHE:YES\n';
418 manifest = '#EXT-X-ALLOW-CACHE:YES\n', 413 let element;
419 element; 414
420 parseStream.on('data', function(elem) { 415 this.parseStream.on('data', function(elem) {
421 element = elem; 416 element = elem;
422 }); 417 });
423 lineStream.push(manifest); 418 this.lineStream.push(manifest);
424 419
425 ok(element, 'an event was triggered'); 420 QUnit.ok(element, 'an event was triggered');
426 strictEqual(element.type, 'tag', 'the line type is tag'); 421 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
427 strictEqual(element.tagType, 'allow-cache', 'the tag type is allow-cache'); 422 QUnit.strictEqual(element.tagType, 'allow-cache', 'the tag type is allow-cache');
428 ok(element.allowed, 'allowed is parsed'); 423 QUnit.ok(element.allowed, 'allowed is parsed');
429 424
430 manifest = '#EXT-X-ALLOW-CACHE:NO\n'; 425 manifest = '#EXT-X-ALLOW-CACHE:NO\n';
431 lineStream.push(manifest); 426 this.lineStream.push(manifest);
432 427
433 ok(element, 'an event was triggered'); 428 QUnit.ok(element, 'an event was triggered');
434 strictEqual(element.type, 'tag', 'the line type is tag'); 429 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
435 strictEqual(element.tagType, 'allow-cache', 'the tag type is allow-cache'); 430 QUnit.strictEqual(element.tagType, 'allow-cache', 'the tag type is allow-cache');
436 ok(!element.allowed, 'allowed is parsed'); 431 QUnit.ok(!element.allowed, 'allowed is parsed');
437 }); 432 });
438 // #EXT-X-STREAM-INF 433 // #EXT-X-STREAM-INF
439 test('parses minimal #EXT-X-STREAM-INF tags', function() { 434 QUnit.test('parses minimal #EXT-X-STREAM-INF tags', function() {
440 var 435 let manifest = '#EXT-X-STREAM-INF\n';
441 manifest = '#EXT-X-STREAM-INF\n', 436 let element;
442 element; 437
443 parseStream.on('data', function(elem) { 438 this.parseStream.on('data', function(elem) {
444 element = elem; 439 element = elem;
445 }); 440 });
446 lineStream.push(manifest); 441 this.lineStream.push(manifest);
447 442
448 ok(element, 'an event was triggered'); 443 QUnit.ok(element, 'an event was triggered');
449 strictEqual(element.type, 'tag', 'the line type is tag'); 444 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
450 strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf'); 445 QUnit.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
451 ok(!('attributes' in element), 'no attributes are present'); 446 QUnit.ok(!('attributes' in element), 'no attributes are present');
452 }); 447 });
453 test('parses #EXT-X-STREAM-INF with common attributes', function() { 448 QUnit.test('parses #EXT-X-STREAM-INF with common attributes', function() {
454 var 449 let manifest = '#EXT-X-STREAM-INF:BANDWIDTH=14400\n';
455 manifest = '#EXT-X-STREAM-INF:BANDWIDTH=14400\n', 450 let element;
456 element; 451
457 parseStream.on('data', function(elem) { 452 this.parseStream.on('data', function(elem) {
458 element = elem; 453 element = elem;
459 }); 454 });
460 lineStream.push(manifest); 455 this.lineStream.push(manifest);
461 456
462 ok(element, 'an event was triggered'); 457 QUnit.ok(element, 'an event was triggered');
463 strictEqual(element.type, 'tag', 'the line type is tag'); 458 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
464 strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf'); 459 QUnit.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
465 strictEqual(element.attributes.BANDWIDTH, 14400, 'bandwidth is parsed'); 460 QUnit.strictEqual(element.attributes.BANDWIDTH, 14400, 'bandwidth is parsed');
466 461
467 manifest = '#EXT-X-STREAM-INF:PROGRAM-ID=7\n'; 462 manifest = '#EXT-X-STREAM-INF:PROGRAM-ID=7\n';
468 lineStream.push(manifest); 463 this.lineStream.push(manifest);
469 464
470 ok(element, 'an event was triggered'); 465 QUnit.ok(element, 'an event was triggered');
471 strictEqual(element.type, 'tag', 'the line type is tag'); 466 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
472 strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf'); 467 QUnit.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
473 strictEqual(element.attributes['PROGRAM-ID'], 7, 'program-id is parsed'); 468 QUnit.strictEqual(element.attributes['PROGRAM-ID'], 7, 'program-id is parsed');
474 469
475 manifest = '#EXT-X-STREAM-INF:RESOLUTION=396x224\n'; 470 manifest = '#EXT-X-STREAM-INF:RESOLUTION=396x224\n';
476 lineStream.push(manifest); 471 this.lineStream.push(manifest);
477 472
478 ok(element, 'an event was triggered'); 473 QUnit.ok(element, 'an event was triggered');
479 strictEqual(element.type, 'tag', 'the line type is tag'); 474 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
480 strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf'); 475 QUnit.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
481 strictEqual(element.attributes.RESOLUTION.width, 396, 'width is parsed'); 476 QUnit.strictEqual(element.attributes.RESOLUTION.width, 396, 'width is parsed');
482 strictEqual(element.attributes.RESOLUTION.height, 224, 'heigth is parsed'); 477 QUnit.strictEqual(element.attributes.RESOLUTION.height, 224, 'heigth is parsed');
483 478
484 manifest = '#EXT-X-STREAM-INF:CODECS="avc1.4d400d, mp4a.40.2"\n'; 479 manifest = '#EXT-X-STREAM-INF:CODECS="avc1.4d400d, mp4a.40.2"\n';
485 lineStream.push(manifest); 480 this.lineStream.push(manifest);
486 481
487 ok(element, 'an event was triggered'); 482 QUnit.ok(element, 'an event was triggered');
488 strictEqual(element.type, 'tag', 'the line type is tag'); 483 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
489 strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf'); 484 QUnit.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
490 strictEqual(element.attributes.CODECS, 485 QUnit.strictEqual(element.attributes.CODECS,
491 'avc1.4d400d, mp4a.40.2', 486 'avc1.4d400d, mp4a.40.2',
492 'codecs are parsed'); 487 'codecs are parsed');
493 }); 488 });
494 test('parses #EXT-X-STREAM-INF with arbitrary attributes', function() { 489 QUnit.test('parses #EXT-X-STREAM-INF with arbitrary attributes', function() {
495 var 490 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', 491 let element;
497 element; 492
498 parseStream.on('data', function(elem) { 493 this.parseStream.on('data', function(elem) {
499 element = elem; 494 element = elem;
500 }); 495 });
501 lineStream.push(manifest); 496 this.lineStream.push(manifest);
502 497
503 ok(element, 'an event was triggered'); 498 QUnit.ok(element, 'an event was triggered');
504 strictEqual(element.type, 'tag', 'the line type is tag'); 499 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
505 strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf'); 500 QUnit.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
506 strictEqual(element.attributes.NUMERIC, '24', 'numeric attributes are parsed'); 501 QUnit.strictEqual(element.attributes.NUMERIC, '24', 'numeric attributes are parsed');
507 strictEqual(element.attributes.ALPHA, 'Value', 'alphabetic attributes are parsed'); 502 QUnit.strictEqual(element.attributes.ALPHA,
508 strictEqual(element.attributes.MIXED, '123abc', 'mixed attributes are parsed'); 503 'Value',
509 }); 504 'alphabetic attributes are parsed');
510 // #EXT-X-ENDLIST 505 QUnit.strictEqual(element.attributes.MIXED, '123abc', 'mixed attributes are parsed');
511 test('parses #EXT-X-ENDLIST tags', function() { 506 });
512 var 507 // #EXT-X-ENDLIST
513 manifest = '#EXT-X-ENDLIST\n', 508 QUnit.test('parses #EXT-X-ENDLIST tags', function() {
514 element; 509 let manifest = '#EXT-X-ENDLIST\n';
515 parseStream.on('data', function(elem) { 510 let element;
511
512 this.parseStream.on('data', function(elem) {
516 element = elem; 513 element = elem;
517 }); 514 });
518 lineStream.push(manifest); 515 this.lineStream.push(manifest);
519 516
520 ok(element, 'an event was triggered'); 517 QUnit.ok(element, 'an event was triggered');
521 strictEqual(element.type, 'tag', 'the line type is tag'); 518 QUnit.strictEqual(element.type, 'tag', 'the line type is tag');
522 strictEqual(element.tagType, 'endlist', 'the tag type is stream-inf'); 519 QUnit.strictEqual(element.tagType, 'endlist', 'the tag type is stream-inf');
523 }); 520 });
521
522 // #EXT-X-KEY
523 QUnit.test('parses valid #EXT-X-KEY tags', function() {
524 let manifest =
525 '#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=52"\n';
526 let element;
524 527
525 // #EXT-X-KEY 528 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; 529 element = elem;
532 }); 530 });
533 lineStream.push(manifest); 531 this.lineStream.push(manifest);
534 532
535 ok(element, 'an event was triggered'); 533 QUnit.ok(element, 'an event was triggered');
536 deepEqual(element, { 534 QUnit.deepEqual(element, {
537 type: 'tag', 535 type: 'tag',
538 tagType: 'key', 536 tagType: 'key',
539 attributes: { 537 attributes: {
...@@ -543,9 +541,9 @@ ...@@ -543,9 +541,9 @@
543 }, 'parsed a valid key'); 541 }, 'parsed a valid key');
544 542
545 manifest = '#EXT-X-KEY:URI="https://example.com/key#1",METHOD=FutureType-1024\n'; 543 manifest = '#EXT-X-KEY:URI="https://example.com/key#1",METHOD=FutureType-1024\n';
546 lineStream.push(manifest); 544 this.lineStream.push(manifest);
547 ok(element, 'an event was triggered'); 545 QUnit.ok(element, 'an event was triggered');
548 deepEqual(element, { 546 QUnit.deepEqual(element, {
549 type: 'tag', 547 type: 'tag',
550 tagType: 'key', 548 tagType: 'key',
551 attributes: { 549 attributes: {
...@@ -555,92 +553,93 @@ ...@@ -555,92 +553,93 @@
555 }, 'parsed the attribute list independent of order'); 553 }, 'parsed the attribute list independent of order');
556 554
557 manifest = '#EXT-X-KEY:IV=1234567890abcdef1234567890abcdef\n'; 555 manifest = '#EXT-X-KEY:IV=1234567890abcdef1234567890abcdef\n';
558 lineStream.push(manifest); 556 this.lineStream.push(manifest);
559 ok(element.attributes.IV, 'detected an IV attribute'); 557 QUnit.ok(element.attributes.IV, 'detected an IV attribute');
560 deepEqual(element.attributes.IV, new Uint32Array([ 558 QUnit.deepEqual(element.attributes.IV, new Uint32Array([
561 0x12345678, 559 0x12345678,
562 0x90abcdef, 560 0x90abcdef,
563 0x12345678, 561 0x12345678,
564 0x90abcdef 562 0x90abcdef
565 ]), 'parsed an IV value'); 563 ]), 'parsed an IV value');
566 }); 564 });
565
566 QUnit.test('parses minimal #EXT-X-KEY tags', function() {
567 let manifest = '#EXT-X-KEY:\n';
568 let element;
567 569
568 test('parses minimal #EXT-X-KEY tags', function() { 570 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; 571 element = elem;
574 }); 572 });
575 lineStream.push(manifest); 573 this.lineStream.push(manifest);
576 574
577 ok(element, 'an event was triggered'); 575 QUnit.ok(element, 'an event was triggered');
578 deepEqual(element, { 576 QUnit.deepEqual(element, {
579 type: 'tag', 577 type: 'tag',
580 tagType: 'key' 578 tagType: 'key'
581 }, 'parsed a minimal key tag'); 579 }, 'parsed a minimal key tag');
582 }); 580 });
581
582 QUnit.test('parses lightly-broken #EXT-X-KEY tags', function() {
583 let manifest = '#EXT-X-KEY:URI=\'https://example.com/single-quote\',METHOD=AES-128\n';
584 let element;
583 585
584 test('parses lightly-broken #EXT-X-KEY tags', function() { 586 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; 587 element = elem;
590 }); 588 });
591 lineStream.push(manifest); 589 this.lineStream.push(manifest);
592 590
593 strictEqual(element.attributes.URI, 591 QUnit.strictEqual(element.attributes.URI,
594 'https://example.com/single-quote', 592 'https://example.com/single-quote',
595 'parsed a single-quoted uri'); 593 'parsed a single-quoted uri');
596 594
597 element = null; 595 element = null;
598 manifest = '#EXT-X-KEYURI="https://example.com/key",METHOD=AES-128\n'; 596 manifest = '#EXT-X-KEYURI="https://example.com/key",METHOD=AES-128\n';
599 lineStream.push(manifest); 597 this.lineStream.push(manifest);
600 strictEqual(element.tagType, 'key', 'parsed the tag type'); 598 QUnit.strictEqual(element.tagType, 'key', 'parsed the tag type');
601 strictEqual(element.attributes.URI, 599 QUnit.strictEqual(element.attributes.URI,
602 'https://example.com/key', 600 'https://example.com/key',
603 'inferred a colon after the tag type'); 601 'inferred a colon after the tag type');
604 602
605 element = null; 603 element = null;
606 manifest = '#EXT-X-KEY: URI = "https://example.com/key",METHOD=AES-128\n'; 604 manifest = '#EXT-X-KEY: URI = "https://example.com/key",METHOD=AES-128\n';
607 lineStream.push(manifest); 605 this.lineStream.push(manifest);
608 strictEqual(element.attributes.URI, 606 QUnit.strictEqual(element.attributes.URI,
609 'https://example.com/key', 607 'https://example.com/key',
610 'trims and removes quotes around the URI'); 608 'trims and removes quotes around the URI');
611 }); 609 });
610
611 QUnit.test('ignores empty lines', function() {
612 let manifest = '\n';
613 let event = false;
612 614
613 test('ignores empty lines', function() { 615 this.parseStream.on('data', function() {
614 var
615 manifest = '\n',
616 event = false;
617 parseStream.on('data', function() {
618 event = true; 616 event = true;
619 }); 617 });
620 lineStream.push(manifest); 618 this.lineStream.push(manifest);
621 619
622 ok(!event, 'no event is triggered'); 620 QUnit.ok(!event, 'no event is triggered');
623 }); 621 });
624 622
625 QUnit.module('m3u8 parser'); 623 QUnit.module('m3u8 parser');
626 624
627 test('can be constructed', function() { 625 QUnit.test('can be constructed', function() {
628 notStrictEqual(new Parser(), undefined, 'parser is defined'); 626 QUnit.notStrictEqual(typeof new Parser(), 'undefined', 'parser is defined');
629 }); 627 });
630 628
631 QUnit.module('m3u8s'); 629 QUnit.module('m3u8s');
632 630
633 test('parses static manifests as expected', function() { 631 QUnit.test('parses static manifests as expected', function() {
634 var key; 632 let key;
635 for (key in window.manifests) { 633
636 if (window.expected[key]) { 634 for (key in testDataManifests) {
637 parser = new Parser(); 635 if (testDataExpected[key]) {
638 parser.push(window.manifests[key]); 636 let parser = new Parser();
639 deepEqual(parser.manifest, 637
640 window.expected[key], 638 parser.push(testDataManifests[key]);
641 key + '.m3u8 was parsed correctly'); 639 QUnit.deepEqual(parser.manifest,
640 testDataExpected[key],
641 key + '.m3u8 was parsed correctly'
642 );
642 } 643 }
643 } 644 }
644 }); 645 });
645
646 })(window, window.console);
......
1 import manifests from './test-manifests';
2 import expected from './test-expected';
3 window.manifests = manifests;
4 window.expected = expected;
5