15b36142 by David LaPalomento

Collect parsing events into a manifest object

Create a parser that interprets parsing events and produces a manifest object. Get all the tests working. Comment a few manifest controller tests out because the interface of that object needs to be updated to use the new parser.
1 parent 9628b848
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
18 "grunt": "~0.4.1" 18 "grunt": "~0.4.1"
19 }, 19 },
20 "dependencies": { 20 "dependencies": {
21 "video.js": "~4.2.2", 21 "video.js": "git+https://github.com/dmlap/video-js.git#v4.3.0-3",
22 "videojs-contrib-media-sources": "0.0.0" 22 "videojs-contrib-media-sources": "0.0.0"
23 } 23 }
24 } 24 }
......
1 (function(parseInt, undefined) { 1 (function(parseInt, isFinite, mergeOptions, undefined) {
2 var 2 var
3 noop = function() {},
3 parseAttributes = function(attributes) { 4 parseAttributes = function(attributes) {
4 var 5 var
5 attrs = attributes.split(','), 6 attrs = attributes.split(','),
...@@ -13,37 +14,40 @@ ...@@ -13,37 +14,40 @@
13 return result; 14 return result;
14 }, 15 },
15 Stream, 16 Stream,
16 Tokenizer, 17 LineStream,
18 ParseStream,
17 Parser; 19 Parser;
18 20
19 Stream = function() { 21 Stream = function() {
20 var listeners = {}; 22 this.init = function() {
21 this.on = function(type, listener) { 23 var listeners = {};
22 if (!listeners[type]) { 24 this.on = function(type, listener) {
23 listeners[type] = []; 25 if (!listeners[type]) {
24 } 26 listeners[type] = [];
25 listeners[type].push(listener); 27 }
26 }; 28 listeners[type].push(listener);
27 this.off = function(type, listener) { 29 };
28 var index; 30 this.off = function(type, listener) {
29 if (!listeners[type]) { 31 var index;
30 return false; 32 if (!listeners[type]) {
31 } 33 return false;
32 index = listeners[type].indexOf(listener); 34 }
33 listeners[type].splice(index, 1); 35 index = listeners[type].indexOf(listener);
34 return index > -1; 36 listeners[type].splice(index, 1);
35 }; 37 return index > -1;
36 this.trigger = function(type) { 38 };
37 var callbacks, i, length, args; 39 this.trigger = function(type) {
38 callbacks = listeners[type]; 40 var callbacks, i, length, args;
39 if (!callbacks) { 41 callbacks = listeners[type];
40 return; 42 if (!callbacks) {
41 } 43 return;
42 args = Array.prototype.slice.call(arguments, 1); 44 }
43 length = callbacks.length; 45 args = Array.prototype.slice.call(arguments, 1);
44 for (i = 0; i < length; ++i) { 46 length = callbacks.length;
45 callbacks[i].apply(this, args); 47 for (i = 0; i < length; ++i) {
46 } 48 callbacks[i].apply(this, args);
49 }
50 };
47 }; 51 };
48 }; 52 };
49 Stream.prototype.pipe = function(destination) { 53 Stream.prototype.pipe = function(destination) {
...@@ -52,10 +56,9 @@ ...@@ -52,10 +56,9 @@
52 }); 56 });
53 }; 57 };
54 58
55 Tokenizer = function() { 59 LineStream = function() {
56 var 60 var buffer = '';
57 buffer = '', 61 LineStream.prototype.init.call(this);
58 tokenizer;
59 62
60 this.push = function(data) { 63 this.push = function(data) {
61 var nextNewline; 64 var nextNewline;
...@@ -69,11 +72,13 @@ ...@@ -69,11 +72,13 @@
69 } 72 }
70 }; 73 };
71 }; 74 };
72 Tokenizer.prototype = new Stream(); 75 LineStream.prototype = new Stream();
73 76
74 Parser = function() {}; 77 ParseStream = function() {
75 Parser.prototype = new Stream(); 78 ParseStream.prototype.init.call(this);
76 Parser.prototype.push = function(line) { 79 };
80 ParseStream.prototype = new Stream();
81 ParseStream.prototype.push = function(line) {
77 var match, event; 82 var match, event;
78 if (line.length === 0) { 83 if (line.length === 0) {
79 // ignore empty lines 84 // ignore empty lines
...@@ -114,7 +119,7 @@ ...@@ -114,7 +119,7 @@
114 tagType: 'inf' 119 tagType: 'inf'
115 }; 120 };
116 if (match[1]) { 121 if (match[1]) {
117 event.duration = parseInt(match[1], 10); 122 event.duration = parseFloat(match[1], 10);
118 } 123 }
119 if (match[2]) { 124 if (match[2]) {
120 event.title = match[2]; 125 event.title = match[2];
...@@ -146,7 +151,7 @@ ...@@ -146,7 +151,7 @@
146 this.trigger('data', event); 151 this.trigger('data', event);
147 return; 152 return;
148 } 153 }
149 match = (/^#EXT-X-MEDIA-SEQUENCE:?([0-9.]*)?/).exec(line); 154 match = (/^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/).exec(line);
150 if (match) { 155 if (match) {
151 event = { 156 event = {
152 type: 'tag', 157 type: 'tag',
...@@ -246,8 +251,135 @@ ...@@ -246,8 +251,135 @@
246 }); 251 });
247 }; 252 };
248 253
254 Parser = function() {
255 var
256 self = this,
257 uris = [],
258 currentUri = {};
259 Parser.prototype.init.call(this);
260
261 this.lineStream = new LineStream();
262 this.parseStream = new ParseStream();
263 this.lineStream.pipe(this.parseStream);
264
265 // the manifest is empty until the parse stream begins delivering data
266 this.manifest = {
267 allowCache: true
268 };
269
270 // update the manifest with the m3u8 entry from the parse stream
271 this.parseStream.on('data', function(entry) {
272 ({
273 tag: function() {
274 // switch based on the tag type
275 (({
276 'allow-cache': function() {
277 this.manifest.allowCache = entry.allowed;
278 if (!('allowed' in entry)) {
279 this.trigger('info', {
280 message: 'defaulting allowCache to YES'
281 });
282 this.manifest.allowCache = true;
283 }
284 },
285 'byterange': function() {
286 var byterange = {};
287 if ('length' in entry) {
288 currentUri.byterange = byterange;
289 byterange.length = entry.length;
290
291 if (!('offset' in entry)) {
292 this.trigger('info', {
293 message: 'defaulting offset to zero'
294 });
295 entry.offset = 0;
296 }
297 }
298 if ('offset' in entry) {
299 currentUri.byterange = byterange;
300 byterange.offset = entry.offset;
301 }
302 },
303 'inf': function() {
304 if (!this.manifest.playlistType) {
305 this.manifest.playlistType = 'VOD';
306 this.trigger('info', {
307 message: 'defaulting playlist type to VOD'
308 });
309 }
310 if (!('mediaSequence' in this.manifest)) {
311 this.manifest.mediaSequence = 0;
312 this.trigger('info', {
313 message: 'defaulting media sequence to zero'
314 });
315 }
316 if (entry.duration >= 0) {
317 currentUri.duration = entry.duration;
318 }
319 this.manifest.segments = uris;
320 },
321 'media-sequence': function() {
322 if (!isFinite(entry.number)) {
323 this.trigger('warn', {
324 message: 'ignoring invalid media sequence: ' + entry.number
325 });
326 return;
327 }
328 this.manifest.mediaSequence = entry.number;
329 },
330 'playlist-type': function() {
331 if (!(/VOD|EVENT/).test(entry.playlistType)) {
332 this.trigger('warn', {
333 message: 'ignoring unknown playlist type: ' + entry.playlist
334 });
335 return;
336 }
337 this.manifest.playlistType = entry.playlistType;
338 },
339 'stream-inf': function() {
340 if (!currentUri.attributes) {
341 currentUri.attributes = {};
342 }
343 currentUri.attributes = mergeOptions(currentUri.attributes,
344 entry.attributes);
345 this.manifest.playlists = uris;
346 },
347 'targetduration': function() {
348 if (!isFinite(entry.duration) || entry.duration < 0) {
349 this.trigger('warn', {
350 message: 'ignoring invalid target duration: ' + entry.duration
351 });
352 return;
353 }
354 this.manifest.targetDuration = entry.duration;
355 }
356 })[entry.tagType] || noop).call(self);
357 },
358 uri: function() {
359 currentUri.uri = entry.uri;
360 uris.push(currentUri);
361
362 // prepare for the next URI
363 currentUri = {};
364 },
365 comment: function() {
366 // comments are not important for playback
367 }
368 })[entry.type].call(self);
369 });
370 };
371 Parser.prototype = new Stream();
372 Parser.prototype.push = function(chunk) {
373 this.lineStream.push(chunk);
374 };
375 Parser.prototype.end = function() {
376 // flush any buffered input
377 this.lineStream.push('\n');
378 };
379
249 window.videojs.m3u8 = { 380 window.videojs.m3u8 = {
250 Tokenizer: Tokenizer, 381 LineStream: LineStream,
382 ParseStream: ParseStream,
251 Parser: Parser 383 Parser: Parser
252 }; 384 };
253 })(window.parseInt); 385 })(window.parseInt, window.isFinite, window.videojs.util.mergeOptions);
......
1 (function(window, console) { 1 (function(window, undefined) {
2 var 2 var
3 Handlebars = this.Handlebars, 3 Handlebars = this.Handlebars,
4 manifestController = this.manifestController, 4 //manifestController = this.manifestController,
5 ParseStream = window.videojs.m3u8.ParseStream,
6 parseStream,
7 LineStream = window.videojs.m3u8.LineStream,
8 lineStream,
5 Parser = window.videojs.m3u8.Parser, 9 Parser = window.videojs.m3u8.Parser,
6 parser, 10 parser;
7 Tokenizer = window.videojs.m3u8.Tokenizer,
8 tokenizer;
9 11
10 module('environment'); 12 module('environment');
11 13
...@@ -18,68 +20,67 @@ ...@@ -18,68 +20,67 @@
18 Manifest controller 20 Manifest controller
19 */ 21 */
20 22
21 module('manifest controller', { 23 // module('manifest controller', {
22 setup: function() { 24 // setup: function() {
23 manifestController = new window.videojs.hls.ManifestController(); 25 // manifestController = new window.videojs.hls.ManifestController();
24 this.vjsget = window.videojs.get; 26 // this.vjsget = window.videojs.get;
25 window.videojs.get = function(url, success) { 27 // window.videojs.get = function(url, success) {
26 success(window.brightcove_playlist_data); 28 // success(window.brightcove_playlist_data);
27 }; 29 // };
28 }, 30 // },
29 teardown: function() { 31 // teardown: function() {
30 window.videojs.get = this.vjsget; 32 // window.videojs.get = this.vjsget;
31 } 33 // }
32 }); 34 // });
33 35
34 test('should create', function() { 36 // test('should create', function() {
35 ok(manifestController); 37 // ok(manifestController);
36 }); 38 // });
37 39
38 test('should return a parsed object', function() { 40 // test('should return a parsed object', function() {
39 var data = manifestController.parseManifest(window.brightcove_playlist_data); 41 // parser.push(window.brightcove_playlist_data);
40 42
41 ok(data); 43 // strictEqual(parser.manifest.playlists.length, 4, 'Has correct rendition count');
42 strictEqual(data.playlists.length, 4, 'Has correct rendition count'); 44 // strictEqual(parser.manifest.playlists[0].attributes.BANDWIDTH, 240000, 'First rendition index bandwidth is correct');
43 strictEqual(data.playlists[0].attributes.bandwidth, 240000, 'First rendition index bandwidth is correct'); 45 // strictEqual(parser.manifest.playlists[0].attributes['PROGRAM-ID'], 1, 'First rendition index program-id is correct');
44 strictEqual(data.playlists[0].attributes.programId, 1, 'First rendition index program-id is correct'); 46 // strictEqual(parser.manifest.playlists[0].attributes.RESOLUTION.width, 396, 'First rendition index resolution width is correct');
45 strictEqual(data.playlists[0].attributes.resolution.width, 396, 'First rendition index resolution width is correct'); 47 // strictEqual(parser.manifest.playlists[0].attributes.RESOLUTION.height, 224, 'First rendition index resolution height is correct');
46 strictEqual(data.playlists[0].attributes.resolution.height, 224, 'First rendition index resolution height is correct'); 48 // });
47 });
48 49
49 test('should get a manifest from an external URL', function() { 50 // test('should get a manifest from an external URL', function() {
50 manifestController.loadManifest('http://example.com/16x9-master.m3u8', 51 // manifestController.loadManifest('http://example.com/16x9-master.m3u8',
51 function(responseData) { 52 // function(responseData) {
52 ok(responseData); 53 // ok(responseData);
53 }, 54 // },
54 function() { 55 // function() {
55 ok(false, 'does not error'); 56 // ok(false, 'does not error');
56 }, 57 // },
57 function() {}); 58 // function() {});
58 }); 59 // });
59 60
60 /* 61 /*
61 M3U8 Test Suite 62 M3U8 Test Suite
62 */ 63 */
63 64
64 module('M3U8 Tokenizer', { 65 module('LineStream', {
65 setup: function() { 66 setup: function() {
66 tokenizer = new Tokenizer(); 67 lineStream = new LineStream();
67 } 68 }
68 }); 69 });
69 test('empty inputs produce no tokens', function() { 70 test('empty inputs produce no tokens', function() {
70 var data = false; 71 var data = false;
71 tokenizer.on('data', function() { 72 lineStream.on('data', function() {
72 data = true; 73 data = true;
73 }); 74 });
74 tokenizer.push(''); 75 lineStream.push('');
75 ok(!data, 'no tokens were produced'); 76 ok(!data, 'no tokens were produced');
76 }); 77 });
77 test('splits on newlines', function() { 78 test('splits on newlines', function() {
78 var lines = []; 79 var lines = [];
79 tokenizer.on('data', function(line) { 80 lineStream.on('data', function(line) {
80 lines.push(line); 81 lines.push(line);
81 }); 82 });
82 tokenizer.push('#EXTM3U\nmovie.ts\n'); 83 lineStream.push('#EXTM3U\nmovie.ts\n');
83 84
84 strictEqual(2, lines.length, 'two lines are ready'); 85 strictEqual(2, lines.length, 'two lines are ready');
85 strictEqual('#EXTM3U', lines.shift(), 'the first line is the first token'); 86 strictEqual('#EXTM3U', lines.shift(), 'the first line is the first token');
...@@ -87,10 +88,10 @@ ...@@ -87,10 +88,10 @@
87 }); 88 });
88 test('empty lines become empty strings', function() { 89 test('empty lines become empty strings', function() {
89 var lines = []; 90 var lines = [];
90 tokenizer.on('data', function(line) { 91 lineStream.on('data', function(line) {
91 lines.push(line); 92 lines.push(line);
92 }); 93 });
93 tokenizer.push('\n\n'); 94 lineStream.push('\n\n');
94 95
95 strictEqual(2, lines.length, 'two lines are ready'); 96 strictEqual(2, lines.length, 'two lines are ready');
96 strictEqual('', lines.shift(), 'the first line is empty'); 97 strictEqual('', lines.shift(), 'the first line is empty');
...@@ -98,13 +99,13 @@ ...@@ -98,13 +99,13 @@
98 }); 99 });
99 test('handles lines broken across appends', function() { 100 test('handles lines broken across appends', function() {
100 var lines = []; 101 var lines = [];
101 tokenizer.on('data', function(line) { 102 lineStream.on('data', function(line) {
102 lines.push(line); 103 lines.push(line);
103 }); 104 });
104 tokenizer.push('#EXTM'); 105 lineStream.push('#EXTM');
105 strictEqual(0, lines.length, 'no lines are ready'); 106 strictEqual(0, lines.length, 'no lines are ready');
106 107
107 tokenizer.push('3U\nmovie.ts\n'); 108 lineStream.push('3U\nmovie.ts\n');
108 strictEqual(2, lines.length, 'two lines are ready'); 109 strictEqual(2, lines.length, 'two lines are ready');
109 strictEqual('#EXTM3U', lines.shift(), 'the first line is the first token'); 110 strictEqual('#EXTM3U', lines.shift(), 'the first line is the first token');
110 strictEqual('movie.ts', lines.shift(), 'the second line is the second token'); 111 strictEqual('movie.ts', lines.shift(), 'the second line is the second token');
...@@ -120,32 +121,32 @@ ...@@ -120,32 +121,32 @@
120 permanentLines.push(line); 121 permanentLines.push(line);
121 }; 122 };
122 123
123 tokenizer.on('data', temporary); 124 lineStream.on('data', temporary);
124 tokenizer.on('data', permanent); 125 lineStream.on('data', permanent);
125 tokenizer.push('line one\n'); 126 lineStream.push('line one\n');
126 strictEqual(temporaryLines.length, permanentLines.length, 'both callbacks receive the event'); 127 strictEqual(temporaryLines.length, permanentLines.length, 'both callbacks receive the event');
127 128
128 ok(tokenizer.off('data', temporary), 'a listener was removed'); 129 ok(lineStream.off('data', temporary), 'a listener was removed');
129 tokenizer.push('line two\n'); 130 lineStream.push('line two\n');
130 strictEqual(1, temporaryLines.length, 'no new events are received'); 131 strictEqual(1, temporaryLines.length, 'no new events are received');
131 strictEqual(2, permanentLines.length, 'new events are still received'); 132 strictEqual(2, permanentLines.length, 'new events are still received');
132 }); 133 });
133 134
134 module('M3U8 Parser', { 135 module('ParseStream', {
135 setup: function() { 136 setup: function() {
136 tokenizer = new Tokenizer(); 137 lineStream = new LineStream();
137 parser = new Parser(); 138 parseStream = new ParseStream();
138 tokenizer.pipe(parser); 139 lineStream.pipe(parseStream);
139 } 140 }
140 }); 141 });
141 test('parses comment lines', function() { 142 test('parses comment lines', function() {
142 var 143 var
143 manifest = '# a line that starts with a hash mark without "EXT" is a comment\n', 144 manifest = '# a line that starts with a hash mark without "EXT" is a comment\n',
144 element; 145 element;
145 parser.on('data', function(elem) { 146 parseStream.on('data', function(elem) {
146 element = elem; 147 element = elem;
147 }); 148 });
148 tokenizer.push(manifest); 149 lineStream.push(manifest);
149 150
150 ok(element, 'an event was triggered'); 151 ok(element, 'an event was triggered');
151 strictEqual(element.type, 'comment', 'the type is comment'); 152 strictEqual(element.type, 'comment', 'the type is comment');
...@@ -157,10 +158,10 @@ ...@@ -157,10 +158,10 @@
157 var 158 var
158 manifest = 'any non-blank line that does not start with a hash-mark is a URI\n', 159 manifest = 'any non-blank line that does not start with a hash-mark is a URI\n',
159 element; 160 element;
160 parser.on('data', function(elem) { 161 parseStream.on('data', function(elem) {
161 element = elem; 162 element = elem;
162 }); 163 });
163 tokenizer.push(manifest); 164 lineStream.push(manifest);
164 165
165 ok(element, 'an event was triggered'); 166 ok(element, 'an event was triggered');
166 strictEqual(element.type, 'uri', 'the type is uri'); 167 strictEqual(element.type, 'uri', 'the type is uri');
...@@ -172,10 +173,10 @@ ...@@ -172,10 +173,10 @@
172 var 173 var
173 manifest = '#EXT-X-EXAMPLE-TAG:some,additional,stuff\n', 174 manifest = '#EXT-X-EXAMPLE-TAG:some,additional,stuff\n',
174 element; 175 element;
175 parser.on('data', function(elem) { 176 parseStream.on('data', function(elem) {
176 element = elem; 177 element = elem;
177 }); 178 });
178 tokenizer.push(manifest); 179 lineStream.push(manifest);
179 180
180 ok(element, 'an event was triggered'); 181 ok(element, 'an event was triggered');
181 strictEqual(element.type, 'tag', 'the type is tag'); 182 strictEqual(element.type, 'tag', 'the type is tag');
...@@ -189,10 +190,10 @@ ...@@ -189,10 +190,10 @@
189 var 190 var
190 manifest = '#EXTM3U\n', 191 manifest = '#EXTM3U\n',
191 element; 192 element;
192 parser.on('data', function(elem) { 193 parseStream.on('data', function(elem) {
193 element = elem; 194 element = elem;
194 }); 195 });
195 tokenizer.push(manifest); 196 lineStream.push(manifest);
196 197
197 ok(element, 'an event was triggered'); 198 ok(element, 'an event was triggered');
198 strictEqual(element.type, 'tag', 'the line type is tag'); 199 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -204,10 +205,10 @@ ...@@ -204,10 +205,10 @@
204 var 205 var
205 manifest = '#EXTINF\n', 206 manifest = '#EXTINF\n',
206 element; 207 element;
207 parser.on('data', function(elem) { 208 parseStream.on('data', function(elem) {
208 element = elem; 209 element = elem;
209 }); 210 });
210 tokenizer.push(manifest); 211 lineStream.push(manifest);
211 212
212 ok(element, 'an event was triggered'); 213 ok(element, 'an event was triggered');
213 strictEqual(element.type, 'tag', 'the line type is tag'); 214 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -217,10 +218,10 @@ ...@@ -217,10 +218,10 @@
217 var 218 var
218 manifest = '#EXTINF:15\n', 219 manifest = '#EXTINF:15\n',
219 element; 220 element;
220 parser.on('data', function(elem) { 221 parseStream.on('data', function(elem) {
221 element = elem; 222 element = elem;
222 }); 223 });
223 tokenizer.push(manifest); 224 lineStream.push(manifest);
224 225
225 ok(element, 'an event was triggered'); 226 ok(element, 'an event was triggered');
226 strictEqual(element.type, 'tag', 'the line type is tag'); 227 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -228,8 +229,8 @@ ...@@ -228,8 +229,8 @@
228 strictEqual(element.duration, 15, 'the duration is parsed'); 229 strictEqual(element.duration, 15, 'the duration is parsed');
229 ok(!('title' in element), 'no title is parsed'); 230 ok(!('title' in element), 'no title is parsed');
230 231
231 manifest = '#EXTINF:21,\n' 232 manifest = '#EXTINF:21,\n';
232 tokenizer.push(manifest); 233 lineStream.push(manifest);
233 234
234 ok(element, 'an event was triggered'); 235 ok(element, 'an event was triggered');
235 strictEqual(element.type, 'tag', 'the line type is tag'); 236 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -241,10 +242,10 @@ ...@@ -241,10 +242,10 @@
241 var 242 var
242 manifest = '#EXTINF:13,Does anyone really use the title attribute?\n', 243 manifest = '#EXTINF:13,Does anyone really use the title attribute?\n',
243 element; 244 element;
244 parser.on('data', function(elem) { 245 parseStream.on('data', function(elem) {
245 element = elem; 246 element = elem;
246 }); 247 });
247 tokenizer.push(manifest); 248 lineStream.push(manifest);
248 249
249 ok(element, 'an event was triggered'); 250 ok(element, 'an event was triggered');
250 strictEqual(element.type, 'tag', 'the line type is tag'); 251 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -260,10 +261,10 @@ ...@@ -260,10 +261,10 @@
260 var 261 var
261 manifest = '#EXT-X-TARGETDURATION\n', 262 manifest = '#EXT-X-TARGETDURATION\n',
262 element; 263 element;
263 parser.on('data', function(elem) { 264 parseStream.on('data', function(elem) {
264 element = elem; 265 element = elem;
265 }); 266 });
266 tokenizer.push(manifest); 267 lineStream.push(manifest);
267 268
268 ok(element, 'an event was triggered'); 269 ok(element, 'an event was triggered');
269 strictEqual(element.type, 'tag', 'the line type is tag'); 270 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -274,10 +275,10 @@ ...@@ -274,10 +275,10 @@
274 var 275 var
275 manifest = '#EXT-X-TARGETDURATION:47\n', 276 manifest = '#EXT-X-TARGETDURATION:47\n',
276 element; 277 element;
277 parser.on('data', function(elem) { 278 parseStream.on('data', function(elem) {
278 element = elem; 279 element = elem;
279 }); 280 });
280 tokenizer.push(manifest); 281 lineStream.push(manifest);
281 282
282 ok(element, 'an event was triggered'); 283 ok(element, 'an event was triggered');
283 strictEqual(element.type, 'tag', 'the line type is tag'); 284 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -290,10 +291,10 @@ ...@@ -290,10 +291,10 @@
290 var 291 var
291 manifest = '#EXT-X-VERSION:\n', 292 manifest = '#EXT-X-VERSION:\n',
292 element; 293 element;
293 parser.on('data', function(elem) { 294 parseStream.on('data', function(elem) {
294 element = elem; 295 element = elem;
295 }); 296 });
296 tokenizer.push(manifest); 297 lineStream.push(manifest);
297 298
298 ok(element, 'an event was triggered'); 299 ok(element, 'an event was triggered');
299 strictEqual(element.type, 'tag', 'the line type is tag'); 300 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -304,10 +305,10 @@ ...@@ -304,10 +305,10 @@
304 var 305 var
305 manifest = '#EXT-X-VERSION:99\n', 306 manifest = '#EXT-X-VERSION:99\n',
306 element; 307 element;
307 parser.on('data', function(elem) { 308 parseStream.on('data', function(elem) {
308 element = elem; 309 element = elem;
309 }); 310 });
310 tokenizer.push(manifest); 311 lineStream.push(manifest);
311 312
312 ok(element, 'an event was triggered'); 313 ok(element, 'an event was triggered');
313 strictEqual(element.type, 'tag', 'the line type is tag'); 314 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -320,10 +321,10 @@ ...@@ -320,10 +321,10 @@
320 var 321 var
321 manifest = '#EXT-X-MEDIA-SEQUENCE\n', 322 manifest = '#EXT-X-MEDIA-SEQUENCE\n',
322 element; 323 element;
323 parser.on('data', function(elem) { 324 parseStream.on('data', function(elem) {
324 element = elem; 325 element = elem;
325 }); 326 });
326 tokenizer.push(manifest); 327 lineStream.push(manifest);
327 328
328 ok(element, 'an event was triggered'); 329 ok(element, 'an event was triggered');
329 strictEqual(element.type, 'tag', 'the line type is tag'); 330 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -334,10 +335,10 @@ ...@@ -334,10 +335,10 @@
334 var 335 var
335 manifest = '#EXT-X-MEDIA-SEQUENCE:109\n', 336 manifest = '#EXT-X-MEDIA-SEQUENCE:109\n',
336 element; 337 element;
337 parser.on('data', function(elem) { 338 parseStream.on('data', function(elem) {
338 element = elem; 339 element = elem;
339 }); 340 });
340 tokenizer.push(manifest); 341 lineStream.push(manifest);
341 342
342 ok(element, 'an event was triggered'); 343 ok(element, 'an event was triggered');
343 strictEqual(element.type, 'tag', 'the line type is tag'); 344 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -350,10 +351,10 @@ ...@@ -350,10 +351,10 @@
350 var 351 var
351 manifest = '#EXT-X-PLAYLIST-TYPE:\n', 352 manifest = '#EXT-X-PLAYLIST-TYPE:\n',
352 element; 353 element;
353 parser.on('data', function(elem) { 354 parseStream.on('data', function(elem) {
354 element = elem; 355 element = elem;
355 }); 356 });
356 tokenizer.push(manifest); 357 lineStream.push(manifest);
357 358
358 ok(element, 'an event was triggered'); 359 ok(element, 'an event was triggered');
359 strictEqual(element.type, 'tag', 'the line type is tag'); 360 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -364,10 +365,10 @@ ...@@ -364,10 +365,10 @@
364 var 365 var
365 manifest = '#EXT-X-PLAYLIST-TYPE:EVENT\n', 366 manifest = '#EXT-X-PLAYLIST-TYPE:EVENT\n',
366 element; 367 element;
367 parser.on('data', function(elem) { 368 parseStream.on('data', function(elem) {
368 element = elem; 369 element = elem;
369 }); 370 });
370 tokenizer.push(manifest); 371 lineStream.push(manifest);
371 372
372 ok(element, 'an event was triggered'); 373 ok(element, 'an event was triggered');
373 strictEqual(element.type, 'tag', 'the line type is tag'); 374 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -375,14 +376,14 @@ ...@@ -375,14 +376,14 @@
375 strictEqual(element.playlistType, 'EVENT', 'the playlist type is EVENT'); 376 strictEqual(element.playlistType, 'EVENT', 'the playlist type is EVENT');
376 377
377 manifest = '#EXT-X-PLAYLIST-TYPE:VOD\n'; 378 manifest = '#EXT-X-PLAYLIST-TYPE:VOD\n';
378 tokenizer.push(manifest); 379 lineStream.push(manifest);
379 ok(element, 'an event was triggered'); 380 ok(element, 'an event was triggered');
380 strictEqual(element.type, 'tag', 'the line type is tag'); 381 strictEqual(element.type, 'tag', 'the line type is tag');
381 strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type'); 382 strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type');
382 strictEqual(element.playlistType, 'VOD', 'the playlist type is VOD'); 383 strictEqual(element.playlistType, 'VOD', 'the playlist type is VOD');
383 384
384 manifest = '#EXT-X-PLAYLIST-TYPE:nonsense\n'; 385 manifest = '#EXT-X-PLAYLIST-TYPE:nonsense\n';
385 tokenizer.push(manifest); 386 lineStream.push(manifest);
386 ok(element, 'an event was triggered'); 387 ok(element, 'an event was triggered');
387 strictEqual(element.type, 'tag', 'the line type is tag'); 388 strictEqual(element.type, 'tag', 'the line type is tag');
388 strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type'); 389 strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type');
...@@ -394,10 +395,10 @@ ...@@ -394,10 +395,10 @@
394 var 395 var
395 manifest = '#EXT-X-BYTERANGE\n', 396 manifest = '#EXT-X-BYTERANGE\n',
396 element; 397 element;
397 parser.on('data', function(elem) { 398 parseStream.on('data', function(elem) {
398 element = elem; 399 element = elem;
399 }); 400 });
400 tokenizer.push(manifest); 401 lineStream.push(manifest);
401 402
402 ok(element, 'an event was triggered'); 403 ok(element, 'an event was triggered');
403 strictEqual(element.type, 'tag', 'the line type is tag'); 404 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -409,10 +410,10 @@ ...@@ -409,10 +410,10 @@
409 var 410 var
410 manifest = '#EXT-X-BYTERANGE:45\n', 411 manifest = '#EXT-X-BYTERANGE:45\n',
411 element; 412 element;
412 parser.on('data', function(elem) { 413 parseStream.on('data', function(elem) {
413 element = elem; 414 element = elem;
414 }); 415 });
415 tokenizer.push(manifest); 416 lineStream.push(manifest);
416 417
417 ok(element, 'an event was triggered'); 418 ok(element, 'an event was triggered');
418 strictEqual(element.type, 'tag', 'the line type is tag'); 419 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -421,7 +422,7 @@ ...@@ -421,7 +422,7 @@
421 ok(!('offset' in element), 'no offset is present'); 422 ok(!('offset' in element), 'no offset is present');
422 423
423 manifest = '#EXT-X-BYTERANGE:108@16\n'; 424 manifest = '#EXT-X-BYTERANGE:108@16\n';
424 tokenizer.push(manifest); 425 lineStream.push(manifest);
425 ok(element, 'an event was triggered'); 426 ok(element, 'an event was triggered');
426 strictEqual(element.type, 'tag', 'the line type is tag'); 427 strictEqual(element.type, 'tag', 'the line type is tag');
427 strictEqual(element.tagType, 'byterange', 'the tag type is byterange'); 428 strictEqual(element.tagType, 'byterange', 'the tag type is byterange');
...@@ -434,10 +435,10 @@ ...@@ -434,10 +435,10 @@
434 var 435 var
435 manifest = '#EXT-X-ALLOW-CACHE:\n', 436 manifest = '#EXT-X-ALLOW-CACHE:\n',
436 element; 437 element;
437 parser.on('data', function(elem) { 438 parseStream.on('data', function(elem) {
438 element = elem; 439 element = elem;
439 }); 440 });
440 tokenizer.push(manifest); 441 lineStream.push(manifest);
441 442
442 ok(element, 'an event was triggered'); 443 ok(element, 'an event was triggered');
443 strictEqual(element.type, 'tag', 'the line type is tag'); 444 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -448,10 +449,10 @@ ...@@ -448,10 +449,10 @@
448 var 449 var
449 manifest = '#EXT-X-ALLOW-CACHE:YES\n', 450 manifest = '#EXT-X-ALLOW-CACHE:YES\n',
450 element; 451 element;
451 parser.on('data', function(elem) { 452 parseStream.on('data', function(elem) {
452 element = elem; 453 element = elem;
453 }); 454 });
454 tokenizer.push(manifest); 455 lineStream.push(manifest);
455 456
456 ok(element, 'an event was triggered'); 457 ok(element, 'an event was triggered');
457 strictEqual(element.type, 'tag', 'the line type is tag'); 458 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -459,7 +460,7 @@ ...@@ -459,7 +460,7 @@
459 ok(element.allowed, 'allowed is parsed'); 460 ok(element.allowed, 'allowed is parsed');
460 461
461 manifest = '#EXT-X-ALLOW-CACHE:NO\n'; 462 manifest = '#EXT-X-ALLOW-CACHE:NO\n';
462 tokenizer.push(manifest); 463 lineStream.push(manifest);
463 464
464 ok(element, 'an event was triggered'); 465 ok(element, 'an event was triggered');
465 strictEqual(element.type, 'tag', 'the line type is tag'); 466 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -471,10 +472,10 @@ ...@@ -471,10 +472,10 @@
471 var 472 var
472 manifest = '#EXT-X-STREAM-INF\n', 473 manifest = '#EXT-X-STREAM-INF\n',
473 element; 474 element;
474 parser.on('data', function(elem) { 475 parseStream.on('data', function(elem) {
475 element = elem; 476 element = elem;
476 }); 477 });
477 tokenizer.push(manifest); 478 lineStream.push(manifest);
478 479
479 ok(element, 'an event was triggered'); 480 ok(element, 'an event was triggered');
480 strictEqual(element.type, 'tag', 'the line type is tag'); 481 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -485,10 +486,10 @@ ...@@ -485,10 +486,10 @@
485 var 486 var
486 manifest = '#EXT-X-STREAM-INF:BANDWIDTH=14400\n', 487 manifest = '#EXT-X-STREAM-INF:BANDWIDTH=14400\n',
487 element; 488 element;
488 parser.on('data', function(elem) { 489 parseStream.on('data', function(elem) {
489 element = elem; 490 element = elem;
490 }); 491 });
491 tokenizer.push(manifest); 492 lineStream.push(manifest);
492 493
493 ok(element, 'an event was triggered'); 494 ok(element, 'an event was triggered');
494 strictEqual(element.type, 'tag', 'the line type is tag'); 495 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -496,7 +497,7 @@ ...@@ -496,7 +497,7 @@
496 strictEqual(element.attributes.BANDWIDTH, 14400, 'bandwidth is parsed'); 497 strictEqual(element.attributes.BANDWIDTH, 14400, 'bandwidth is parsed');
497 498
498 manifest = '#EXT-X-STREAM-INF:PROGRAM-ID=7\n'; 499 manifest = '#EXT-X-STREAM-INF:PROGRAM-ID=7\n';
499 tokenizer.push(manifest); 500 lineStream.push(manifest);
500 501
501 ok(element, 'an event was triggered'); 502 ok(element, 'an event was triggered');
502 strictEqual(element.type, 'tag', 'the line type is tag'); 503 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -504,7 +505,7 @@ ...@@ -504,7 +505,7 @@
504 strictEqual(element.attributes['PROGRAM-ID'], 7, 'program-id is parsed'); 505 strictEqual(element.attributes['PROGRAM-ID'], 7, 'program-id is parsed');
505 506
506 manifest = '#EXT-X-STREAM-INF:RESOLUTION=396x224\n'; 507 manifest = '#EXT-X-STREAM-INF:RESOLUTION=396x224\n';
507 tokenizer.push(manifest); 508 lineStream.push(manifest);
508 509
509 ok(element, 'an event was triggered'); 510 ok(element, 'an event was triggered');
510 strictEqual(element.type, 'tag', 'the line type is tag'); 511 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -516,10 +517,10 @@ ...@@ -516,10 +517,10 @@
516 var 517 var
517 manifest = '#EXT-X-STREAM-INF:NUMERIC=24,ALPHA=Value,MIXED=123abc\n', 518 manifest = '#EXT-X-STREAM-INF:NUMERIC=24,ALPHA=Value,MIXED=123abc\n',
518 element; 519 element;
519 parser.on('data', function(elem) { 520 parseStream.on('data', function(elem) {
520 element = elem; 521 element = elem;
521 }); 522 });
522 tokenizer.push(manifest); 523 lineStream.push(manifest);
523 524
524 ok(element, 'an event was triggered'); 525 ok(element, 'an event was triggered');
525 strictEqual(element.type, 'tag', 'the line type is tag'); 526 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -533,10 +534,10 @@ ...@@ -533,10 +534,10 @@
533 var 534 var
534 manifest = '#EXT-X-ENDLIST\n', 535 manifest = '#EXT-X-ENDLIST\n',
535 element; 536 element;
536 parser.on('data', function(elem) { 537 parseStream.on('data', function(elem) {
537 element = elem; 538 element = elem;
538 }); 539 });
539 tokenizer.push(manifest); 540 lineStream.push(manifest);
540 541
541 ok(element, 'an event was triggered'); 542 ok(element, 'an event was triggered');
542 strictEqual(element.type, 'tag', 'the line type is tag'); 543 strictEqual(element.type, 'tag', 'the line type is tag');
...@@ -547,51 +548,39 @@ ...@@ -547,51 +548,39 @@
547 var 548 var
548 manifest = '\n', 549 manifest = '\n',
549 event = false; 550 event = false;
550 parser.on('data', function() { 551 parseStream.on('data', function() {
551 event = true; 552 event = true;
552 }); 553 });
553 tokenizer.push(manifest); 554 lineStream.push(manifest);
554 555
555 ok(!event, 'no event is triggered'); 556 ok(!event, 'no event is triggered');
556 }); 557 });
557 558
558 module('m3u8 parser', { 559 module('m3u8 parser', {
559 setup: function() { 560 setup: function() {
560 tokenizer = new Tokenizer();
561 parser = new Parser(); 561 parser = new Parser();
562 tokenizer.pipe(parser);
563 } 562 }
564 }); 563 });
565 564
566 test('should create my parser', function() { 565 test('should create a parser', function() {
567 ok(parser !== undefined); 566 notStrictEqual(parser, undefined, 'parser is defined');
568 }); 567 });
569 568
570 test('should successfully parse manifest data', function() { 569 test('should successfully parse manifest data', function() {
571 var parsedData; 570 parser.push(window.playlistM3U8data);
572 parser.on('data', function(manifest) { 571 ok(parser.manifest);
573 parsedData = manifest;
574 });
575 tokenizer.push(window.playlistData);
576 ok(parsedData);
577 }); 572 });
578 573
579 test('valid manifest should populate the manifest data object', function() { 574 test('valid manifest should populate the manifest data object', function() {
580 var data; 575 parser.push(window.playlistM3U8data);
581 parser.on('data', function(manifest) {
582 data = manifest;
583 });
584 tokenizer.push(window.playlistData);
585 576
586 notStrictEqual(data, null, 'data is not NULL'); 577 ok(parser.manifest, 'the manifest is parsed');
587 strictEqual(data.openTag, true, 'data has valid EXTM3U'); 578 strictEqual(parser.manifest.targetDuration, 10, 'the manifest has correct TARGET DURATION');
588 strictEqual(data.targetDuration, 10, 'data has correct TARGET DURATION'); 579 strictEqual(parser.manifest.allowCache, true, 'allow-cache is defaulted to true');
589 strictEqual(data.allowCache, undefined, 'ALLOW-CACHE is not present in the manifest'); 580 strictEqual(parser.manifest.playlistType, 'VOD', 'playlist type is VOD');
590 strictEqual(data.playlistType, "VOD", 'acceptable PLAYLIST TYPE'); 581 strictEqual(parser.manifest.segments.length, 17, 'there are 17 segments in the manifest');
591 strictEqual(data.segments.length, 17, 'there are 17 segments in the manifest'); 582 strictEqual(parser.manifest.mediaSequence, 0, 'MEDIA SEQUENCE is correct');
592 strictEqual(data.mediaSequence, 0, 'MEDIA SEQUENCE is correct'); 583 ok(!('duration' in parser.manifest), "no total duration is specified");
593 strictEqual(data.totalDuration, undefined, "no total duration is specified");
594 strictEqual(data.closeTag, true, 'should have ENDLIST tag');
595 }); 584 });
596 585
597 /*3.4.7. EXT-X-PLAYLIST-TYPE 586 /*3.4.7. EXT-X-PLAYLIST-TYPE
...@@ -611,74 +600,57 @@ ...@@ -611,74 +600,57 @@
611 test('should have parsed VOD playlist type', function() { 600 test('should have parsed VOD playlist type', function() {
612 var 601 var
613 playlistTemplate = Handlebars.compile(window.playlist_type_template), 602 playlistTemplate = Handlebars.compile(window.playlist_type_template),
614 testData = {playlistType: 'VOD'}, 603 testData = { playlistType: 'VOD' };
615 playlistData = playlistTemplate(testData), 604 parser.push(playlistTemplate(testData));
616 data;
617 parser.on('data', function(element) {
618 data = element;
619 });
620 tokenizer.push(window.playlistData);
621 605
622 notStrictEqual(data, null, 'data is not NULL'); 606 notStrictEqual(parser.manifest, null, 'manifest is parsed');
623 //strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.'); 607 //strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.');
624 strictEqual(data.playlistType, "VOD", 'acceptable PLAYLIST TYPE'); 608 strictEqual(parser.manifest.playlistType, 'VOD', 'playlist type is vod');
625 }); 609 });
626 610
627 test('should have parsed EVENT playlist type', function() { 611 test('should have parsed EVENT playlist type', function() {
628 var 612 var
629 playlistTemplate = Handlebars.compile(window.playlist_type_template), 613 playlistTemplate = Handlebars.compile(window.playlist_type_template),
630 testData = {playlistType: 'EVENT'}, 614 testData = { playlistType: 'EVENT' };
631 playlistData = playlistTemplate(testData), 615 parser.push(playlistTemplate(testData));
632 data;
633 parser.on('data', function(element) {
634 data = element;
635 });
636 tokenizer.push(window.playlistData);
637 616
638 notStrictEqual(data, null, 'data is not NULL'); 617 notStrictEqual(parser.manifest, null, 'manifest is parsed');
639 //strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.'); 618 //strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.');
640 strictEqual(data.playlistType, "EVENT", 'acceptable PLAYLIST TYPE'); 619 strictEqual(parser.manifest.playlistType, 'EVENT', 'playlist type is event');
641 }); 620 });
642 621
643 test('handles a missing playlist type', function() { 622 test('handles a missing playlist type', function() {
644 var 623 var
645 playlistTemplate = Handlebars.compile(window.playlist_type_template), 624 playlistTemplate = Handlebars.compile(window.playlist_type_template),
646 testData = {}, 625 testData = {};
647 playlistData = playlistTemplate(testData), 626 parser.push(playlistTemplate(testData));
648 data;
649 parser.on('data', function(element) {
650 data = element;
651 });
652 tokenizer.push(window.playlistData);
653 627
654 notStrictEqual(data, null, 'data is not NULL');
655 //strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.'); 628 //strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.');
656 //strictEqual(data.warnings, 'EXT-X-PLAYLIST-TYPE was empty or missing. Assuming VOD'); 629 //strictEqual(data.warnings, 'EXT-X-PLAYLIST-TYPE was empty or missing. Assuming VOD');
657 strictEqual(data.playlistType, undefined, 'no PLAYLIST TYPE present'); 630 strictEqual(parser.manifest.playlistType, 'VOD', 'playlist type defaults to vod');
658 }); 631 });
659 632
660 test('should have an invalid reason due to invalid playlist type', function() { 633 test('should default invalid playlist types to vod', function() {
661 var 634 var
662 playlistTemplate = Handlebars.compile(window.playlist_type_template), 635 playlistTemplate = Handlebars.compile(window.playlist_type_template),
663 testData = {playlistType: 'baklsdhfajsdf'}, 636 testData = { playlistType: 'baklsdhfajsdf' };
664 playlistData = playlistTemplate(testData), 637 parser.push(playlistTemplate(testData));
665 data = m3u8parser.parse(playlistData); 638
666 notStrictEqual(data, null, 'data is not NULL'); 639 strictEqual(parser.manifest.playlistType, 'VOD', 'invalid playlist types default to vod');
667 //strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons'); 640 //strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons');
668 //strictEqual(data.invalidReasons[0], 'Invalid Playlist Type Value: \'baklsdhfajsdf\''); 641 //strictEqual(data.invalidReasons[0], 'Invalid Playlist Type Value: \'baklsdhfajsdf\'');
669 }); 642 });
670 643
671 // test('handles an empty playlist type', function() { 644 test('handles an empty playlist type', function() {
672 // var 645 var
673 // playlistTemplate = Handlebars.compile(window.playlist_type_template), 646 playlistTemplate = Handlebars.compile(window.playlist_type_template),
674 // testData = {playlistType: ''}, 647 testData = { playlistType: '' };
675 // playlistData = playlistTemplate(testData), 648 parser.push(playlistTemplate(testData));
676 // data = m3u8parser.parse(playlistData); 649
677 // notStrictEqual(data, null, 'data is not NULL'); 650 //strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.');
678 // //strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.'); 651 //strictEqual(data.warnings, 'EXT-X-PLAYLIST-TYPE was empty or missing. Assuming VOD');
679 // //strictEqual(data.warnings, 'EXT-X-PLAYLIST-TYPE was empty or missing. Assuming VOD'); 652 strictEqual(parser.manifest.playlistType, 'VOD', 'playlist type defaults to vod');
680 // strictEqual(data.playlistType, '', 'PLAYLIST TYPE is the empty string'); 653 });
681 // });
682 654
683 /*3.4.2. EXT-X-TARGETDURATION 655 /*3.4.2. EXT-X-TARGETDURATION
684 656
...@@ -700,23 +672,20 @@ ...@@ -700,23 +672,20 @@
700 test('valid target duration', function() { 672 test('valid target duration', function() {
701 var 673 var
702 playlistTemplate = Handlebars.compile(window.playlist_target_duration_template), 674 playlistTemplate = Handlebars.compile(window.playlist_target_duration_template),
703 testData = {targetDuration: '10'}, 675 testData = { targetDuration: '10' };
704 playlistData = playlistTemplate(testData), 676 parser.push(playlistTemplate(testData));
705 data = m3u8parser.parse(playlistData); 677
706 notStrictEqual(data, null, 'data is not NULL'); 678 strictEqual(parser.manifest.targetDuration, 10, 'manifest has correct TARGET DURATION');
707 strictEqual(data.targetDuration, 10, 'data has correct TARGET DURATION');
708 //strictEqual(data.invalidReasons.length, 0, 'data has 1 invalid reasons'); 679 //strictEqual(data.invalidReasons.length, 0, 'data has 1 invalid reasons');
709 }); 680 });
710 681
711 test('NaN target duration', function() { 682 test('NaN target duration', function() {
712 var 683 var
713 playlistTemplate = Handlebars.compile(window.playlist_target_duration_template), 684 playlistTemplate = Handlebars.compile(window.playlist_target_duration_template),
714 testData = {targetDuration: 'string'}, 685 testData = { targetDuration: 'string' };
715 playlistData = playlistTemplate(testData), 686 parser.push(playlistTemplate(testData));
716 data = m3u8parser.parse(playlistData); 687
717 console.log(playlistData); 688 ok(!('targetDuration' in parser.manifest), 'target duration is not defined');
718 console.log(data.targetDuration);
719 notStrictEqual(data, null, 'data is not NULL');
720 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 689 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
721 // strictEqual(data.invalidReasons.length, 1, 'data has 0 invalid reasons'); 690 // strictEqual(data.invalidReasons.length, 1, 'data has 0 invalid reasons');
722 // strictEqual(data.invalidReasons[0], 'Invalid Target Duration Value: \'NaN\''); 691 // strictEqual(data.invalidReasons[0], 'Invalid Target Duration Value: \'NaN\'');
...@@ -725,43 +694,24 @@ ...@@ -725,43 +694,24 @@
725 test('empty target duration', function() { 694 test('empty target duration', function() {
726 var 695 var
727 playlistTemplate = Handlebars.compile(window.playlist_target_duration_template), 696 playlistTemplate = Handlebars.compile(window.playlist_target_duration_template),
728 testData = {targetDuration: '\'\''}, 697 testData = { targetDuration: '\'\'' };
729 playlistData = playlistTemplate(testData), 698 parser.push(playlistTemplate(testData));
730 data = m3u8parser.parse(playlistData);
731 console.log(playlistData);
732 console.log(data.targetDuration);
733 notStrictEqual(data, null, 'data is not NULL');
734 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
735 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons');
736 // strictEqual(data.invalidReasons[0], 'Invalid Target Duration Value: \'NaN\'');
737 });
738 699
739 test('undefined target duration', function() { 700 ok(!('targetDuration' in parser.manifest), 'target duration is not defined');
740 var
741 playlistTemplate = Handlebars.compile(window.playlist_target_duration_template),
742 testData = {},
743 playlistData = playlistTemplate(testData),
744 data = m3u8parser.parse(playlistData);
745 console.log(playlistData);
746 console.log(data.targetDuration);
747 notStrictEqual(data, null, 'data is not NULL');
748 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 701 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
749 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons'); 702 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons');
750 // strictEqual(data.invalidReasons[0], 'Invalid Target Duration Value: \'undefined\''); 703 // strictEqual(data.invalidReasons[0], 'Invalid Target Duration Value: \'NaN\'');
751
752 }); 704 });
753 705
754 test('target duration lower than segment', function() { 706 test('empty target duration', function() {
755 var 707 var
756 playlistTemplate = Handlebars.compile(window.playlist_target_duration_template), 708 playlistTemplate = Handlebars.compile(window.playlist_target_duration_template);
757 testData = {targetDuration: '4'}, 709 parser.push(playlistTemplate({}));
758 playlistData = playlistTemplate(testData),
759 data = m3u8parser.parse(playlistData);
760 710
761 notStrictEqual(data, null, 'data is not NULL'); 711 ok(!('targetDuration' in parser.manifest), 'target duration is not defined');
762 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 712 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
763 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons'); 713 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons');
764 // strictEqual(data.invalidReasons[0], 'Invalid Target Duration Value: 4 is lower than segments'); 714 // strictEqual(data.invalidReasons[0], 'Invalid Target Duration Value: \'what\'');
765 }); 715 });
766 716
767 /*3.4.3. EXT-X-MEDIA-SEQUENCE 717 /*3.4.3. EXT-X-MEDIA-SEQUENCE
...@@ -790,90 +740,75 @@ ...@@ -790,90 +740,75 @@
790 test('media sequence is valid in the playlist', function() { 740 test('media sequence is valid in the playlist', function() {
791 var 741 var
792 playlistTemplate = Handlebars.compile(window.playlist_media_sequence_template), 742 playlistTemplate = Handlebars.compile(window.playlist_media_sequence_template),
793 testData = {mediaSequence: '0'}, 743 testData = { mediaSequence: '0' };
794 playlistData = playlistTemplate(testData), 744 parser.push(playlistTemplate(testData));
795 data = m3u8parser.parse(playlistData);
796 745
797 notStrictEqual(data, null, 'data is not NULL');
798 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 746 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
799 // strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.'); 747 // strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.');
800 strictEqual(data.mediaSequence, 0, 'MEDIA SEQUENCE is correct'); 748 strictEqual(parser.manifest.mediaSequence, 0, 'MEDIA SEQUENCE is zero');
801 }); 749 });
802 750
803 test('media sequence is encountered twice in the playlist', function() { 751 test('media sequence is encountered twice in the playlist', function() {
804 var 752 var
805 playlistTemplate = Handlebars.compile(window.playlist_media_sequence_template), 753 playlistTemplate = Handlebars.compile(window.playlist_media_sequence_template),
806 testData = {mediaSequence: '0', mediaSequence1: '1'}, 754 testData = {
807 playlistData = playlistTemplate(testData), 755 mediaSequence: '0',
808 data = m3u8parser.parse(playlistData); 756 mediaSequence1: '1'
757 };
758 parser.push(playlistTemplate(testData));
809 759
810 notStrictEqual(data, null, 'data is not NULL');
811 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 760 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
812 // strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.'); 761 // strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.');
813 strictEqual(data.mediaSequence, 0, 'MEDIA SEQUENCE tags after the first should be ignored'); 762 strictEqual(parser.manifest.mediaSequence,
763 1,
764 'the most recently encountered media sequence is stored');
814 }); 765 });
815 766
816 test('media sequence is undefined in the playlist', function() { 767 test('media sequence is zero if not present in media playlists', function() {
817 var 768 var playlistTemplate = Handlebars.compile(window.playlist_media_sequence_template);
818 playlistTemplate = Handlebars.compile(window.playlist_media_sequence_template), 769 parser.push(playlistTemplate({}));
819 testData = {mediaSequence: ''},
820 playlistData = playlistTemplate(testData),
821 data = m3u8parser.parse(playlistData);
822 770
823 notStrictEqual(data, null, 'data is not NULL');
824 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 771 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
825 // strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.'); 772 // strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.');
826 strictEqual(data.mediaSequence, undefined, 'MEDIA SEQUENCE is undefined'); 773 strictEqual(parser.manifest.mediaSequence, 0, 'mediaSequence is defaulted to zero');
827 }); 774 });
828 775
829 // test('media sequence is empty in the playlist', function() { 776 test('empty media sequence numbers is ignored in media playlists', function() {
830 // var
831 // playlistTemplate = Handlebars.compile(window.playlist_media_sequence_template),
832 // testData = {mediaSequence: ''},
833 // playlistData = playlistTemplate(testData),
834 // data = m3u8parser.parse(playlistData);
835
836 // notStrictEqual(data, null, 'data is not NULL');
837 // // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
838 // // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons');
839 // strictEqual(data.mediaSequence, '', 'media sequence is the empty string');
840 // });
841
842 test('media sequence is high (non-zero in first file) in the playlist', function() {
843 var 777 var
844 playlistTemplate = Handlebars.compile(window.playlist_media_sequence_template), 778 playlistTemplate = Handlebars.compile(window.playlist_media_sequence_template),
845 testData = {mediaSequence: '1'}, 779 testData = { mediaSequence: '' };
846 playlistData = playlistTemplate(testData), 780 parser.push(playlistTemplate(testData));
847 data = m3u8parser.parse(playlistData);
848 781
849 notStrictEqual(data, null, 'data is not NULL');
850 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 782 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
851 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons'); 783 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons');
852 // strictEqual(data.invalidReasons[0], 'Invalid Media Sequence Value: \'1\''); 784 strictEqual(parser.manifest.mediaSequence,
785 0,
786 'empty media sequences are defaulted');
853 }); 787 });
854 788
855 test('handles invalid media sequence numbers in the playlist', function() { 789 test('handles invalid media sequence numbers in the playlist', function() {
856 var 790 var
857 playlistTemplate = Handlebars.compile(window.playlist_media_sequence_template), 791 playlistTemplate = Handlebars.compile(window.playlist_media_sequence_template),
858 testData = {mediaSequence: '-1'}, 792 testData = { mediaSequence: '-1' };
859 playlistData = playlistTemplate(testData), 793 parser.push(playlistTemplate(testData));
860 data = m3u8parser.parse(playlistData);
861 794
862 notStrictEqual(data, null, 'data is not NULL');
863 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 795 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
864 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons'); 796 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons');
865 // strictEqual(data.invalidReasons[0], 'Invalid Media Sequence Value: \'-1\''); 797 // strictEqual(data.invalidReasons[0], 'Invalid Media Sequence Value: \'-1\'');
866 strictEqual(data.mediaSequence, -1, 'negative media sequence numbers don\'t break parsing'); 798 strictEqual(parser.manifest.mediaSequence,
799 -1,
800 'negative media sequence numbers are parsed');
867 }); 801 });
868 802
869 test('media sequence invalid (string) in the playlist', function() { 803 test('invalid media sequences are defaulted', function() {
870 var 804 var
871 playlistTemplate = Handlebars.compile(window.playlist_media_sequence_template), 805 playlistTemplate = Handlebars.compile(window.playlist_media_sequence_template),
872 testData = {mediaSequence: 'asdfkasdkfl'}, 806 testData = {
873 playlistData = playlistTemplate(testData), 807 mediaSequence: 'asdfkasdkfl'
874 data = m3u8parser.parse(playlistData); 808 };
809 parser.push(playlistTemplate(testData));
875 810
876 notStrictEqual(data, null, 'data is not NULL'); 811 strictEqual(parser.manifest.mediaSequence, 0, 'invalid media sequences default to zero');
877 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 812 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
878 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons'); 813 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons');
879 // strictEqual(data.invalidReasons[0], 'Invalid Media Sequence Value: \'asdfkasdkfl\''); 814 // strictEqual(data.invalidReasons[0], 'Invalid Media Sequence Value: \'asdfkasdkfl\'');
...@@ -881,21 +816,26 @@ ...@@ -881,21 +816,26 @@
881 816
882 module('Representative Playlist', { 817 module('Representative Playlist', {
883 setup: function() { 818 setup: function() {
884 m3u8parser = window.videojs.hls.M3U8Parser; 819 parser = new Parser();
820 },
821 teardown: function() {
822 parser = null;
885 } 823 }
886 }); 824 });
887 825
888 test('should parse real manifest data', function() { 826 test('should parse real manifest data', function() {
889 var data = m3u8parser.parse(window.brightcove_playlist_data); 827 parser.push(window.brightcove_playlist_data);
890 828 parser.end();
891 ok(data); 829
892 strictEqual(data.playlists.length, 4, 'has correct playlist count'); 830 ok(parser.manifest, 'a manifest is parsed');
893 strictEqual(data.playlists[0].attributes.bandwidth, 240000, 'first rendition index bandwidth is correct'); 831 ok(!('segments' in parser.manifest), 'no segments should be parsed');
894 strictEqual(data.playlists[0].attributes.programId, 1, 'first rendition index program-id is correct'); 832 strictEqual(parser.manifest.playlists.length, 4, 'has correct playlist count');
895 strictEqual(data.playlists[0].attributes.resolution.width, 833 strictEqual(parser.manifest.playlists[0].attributes.BANDWIDTH, 240000, 'first rendition index bandwidth is correct');
834 strictEqual(parser.manifest.playlists[0].attributes['PROGRAM-ID'], 1, 'first rendition index program-id is correct');
835 strictEqual(parser.manifest.playlists[0].attributes.RESOLUTION.width,
896 396, 836 396,
897 'first rendition index resolution width is correct'); 837 'first rendition index resolution width is correct');
898 strictEqual(data.playlists[0].attributes.resolution.height, 838 strictEqual(parser.manifest.playlists[0].attributes.RESOLUTION.height,
899 224, 839 224,
900 'first rendition index resolution height is correct'); 840 'first rendition index resolution height is correct');
901 841
...@@ -927,37 +867,62 @@ ...@@ -927,37 +867,62 @@
927 test('test valid extinf values in playlist', function() { 867 test('test valid extinf values in playlist', function() {
928 var 868 var
929 playlistTemplate = Handlebars.compile(window.playlist_extinf_template), 869 playlistTemplate = Handlebars.compile(window.playlist_extinf_template),
930 testData = {version: 4, extInf: '10', extInf1: '10', extInf2: '10', segment: 'hls_450k_video.ts'}, 870 testData = {
931 playlistData = playlistTemplate(testData), 871 version: 4,
932 data = m3u8parser.parse(playlistData); 872 extInf: '10',
933 873 extInf1: '10',
934 notStrictEqual(data, null, 'data is not NULL'); 874 extInf2: '10',
875 segment: 'hls_450k_video.ts'
876 };
877 parser.push(playlistTemplate(testData));
878
879 strictEqual(parser.manifest.segments.length, 17, 'the number of playlists is inferred');
880 strictEqual(parser.manifest.segments[0].duration,
881 10,
882 'the first playlist duration is parsed');
883 strictEqual(parser.manifest.segments[1].duration,
884 10,
885 'the second playlist duration is parsed');
886 strictEqual(parser.manifest.segments[2].duration,
887 10,
888 'the third playlist duration is parsed');
889 strictEqual(parser.manifest.segments[3].duration,
890 10,
891 'the fourth playlist duration is parsed');
935 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 892 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
936 // strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.'); 893 // strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.');
937 }); 894 });
938 895
939 test('test valid extinf without associated segment in playlist', function() { 896 test('the last encountered extinf tag before a segment takes precedance', function() {
940 var 897 var
941 playlistTemplate = Handlebars.compile(window.playlist_extinf_template), 898 playlistTemplate = Handlebars.compile(window.playlist_extinf_template),
942 testData = {version: 4, extInf: '10', extInf1: '10', extInf2: '10'}, 899 testData = {
943 playlistData = playlistTemplate(testData), 900 version: 4,
944 data = m3u8parser.parse(playlistData); 901 extInf: '1',
902 extInf1: '2',
903 extInf2: '3'
904 };
905 parser.push(playlistTemplate(testData));
945 906
946 notStrictEqual(data, null, 'data is not NULL'); 907 strictEqual(parser.manifest.segments[0].duration,
947 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 908 2,
948 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons'); 909 'the most recent duration is stored');
949 //strictEqual(data.invalidReasons[0], 'Invalid Segment Data: \'#EXTINF missing segment\'');
950 }); 910 });
951 911
952 // 912 //
953 test('test invalid extinf values in playlist', function() { 913 test('ignore invalid extinf values', function() {
954 var 914 var
955 playlistTemplate = Handlebars.compile(window.playlist_extinf_template), 915 playlistTemplate = Handlebars.compile(window.playlist_extinf_template),
956 testData = {version: 4, extInf: 'asdf', extInf1: '10', extInf2: '10', segment: 'hls_450k_video.ts'}, 916 testData = {
957 playlistData = playlistTemplate(testData), 917 version: 4,
958 data = m3u8parser.parse(playlistData); 918 extInf: 'asdf',
919 extInf1: '10',
920 extInf2: '10',
921 segment: 'hls_450k_video.ts'
922 };
923 parser.push(playlistTemplate(testData));
959 924
960 notStrictEqual(data, null, 'data is not NULL'); 925 ok(!('duration' in parser.manifest.segments[0]), 'invalid durations are ignored');
961 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 926 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
962 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons'); 927 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons');
963 }); 928 });
...@@ -966,65 +931,61 @@ ...@@ -966,65 +931,61 @@
966 test('test inconsistent extinf values in playlist below target duration', function() { 931 test('test inconsistent extinf values in playlist below target duration', function() {
967 var 932 var
968 playlistTemplate = Handlebars.compile(window.playlist_extinf_template), 933 playlistTemplate = Handlebars.compile(window.playlist_extinf_template),
969 testData = {version: 4, extInf: '10', extInf1: '7', extInf2: '10', segment: 'hls_450k_video.ts'}, 934 testData = {
970 playlistData = playlistTemplate(testData), 935 version: 4,
971 data = m3u8parser.parse(playlistData); 936 extInf: '10',
972 937 extInf1: '7',
973 notStrictEqual(data, null, 'data is not NULL'); 938 extInf2: '10',
939 segment: 'hls_450k_video.ts'
940 };
941 parser.push(playlistTemplate(testData));
942
943 strictEqual(parser.manifest.segments[0].duration,
944 10,
945 'the first duration is parsed');
946 strictEqual(parser.manifest.segments[1].duration,
947 7,
948 'the second duration is parsed');
949 strictEqual(parser.manifest.segments[2].duration,
950 10,
951 'the third duration is parsed');
974 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 952 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
975 // strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.'); 953 // strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.');
976 }); 954 });
977 955
978 //extinf values must be below the target duration 956 //extinf values must be below the target duration
979 test('test inconsistent extinf values in playlist above target duration', function() { 957 test('test floating-point values are accepted with version 3', function() {
980 var
981 playlistTemplate = Handlebars.compile(window.playlist_extinf_template),
982 testData = {version: 4, extInf: '10', extInf1: '7', extInf2: '10', segment: 'hls_450k_video.ts'},
983 playlistData = playlistTemplate(testData),
984 data = m3u8parser.parse(playlistData);
985
986 notStrictEqual(data, null, 'data is not NULL');
987 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
988 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons');
989 // strictEqual(data.invalidReasons[0], 'Invalid Segment Data: \'#EXTINF value higher than #TARGETDURATION\'');
990 });
991
992 //extinf values must be below the target duration
993 test('test floating-point values not accepted with version 3', function() {
994 var 958 var
995 playlistTemplate = Handlebars.compile(window.playlist_extinf_template), 959 playlistTemplate = Handlebars.compile(window.playlist_extinf_template),
996 testData = {version: 3, extInf: '10.5', extInf1: '10.5', extInf2: '10.5', segment: 'hls_450k_video.ts'}, 960 testData = {
997 playlistData = playlistTemplate(testData), 961 version: 3,
998 data = m3u8parser.parse(playlistData); 962 extInf: '10.5',
963 extInf1: '10.5',
964 extInf2: '10.5',
965 segment: 'hls_450k_video.ts'
966 };
967 parser.push(playlistTemplate(testData));
999 968
1000 notStrictEqual(data, null, 'data is not NULL'); 969 strictEqual(parser.manifest.segments[0].duration, 10.5, 'fractional durations are parsed');
1001 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 970 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
1002 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons'); 971 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons');
1003 // strictEqual(data.invalidReasons[0], 'Invalid Segment Data: \'#EXTINF value not an integer\''); 972 // strictEqual(data.invalidReasons[0], 'Invalid Segment Data: \'#EXTINF value not an integer\'');
1004 }); 973 });
1005 974
1006 //extinf values must be below the target duration 975 //extinf values must be below the target duration
1007 test('test floating-point values accepted with version 4', function() {
1008 var
1009 playlistTemplate = Handlebars.compile(window.playlist_extinf_template),
1010 testData = {version: 4, extInf: '10.5', extInf1: '10.5', extInf2: '10.5', segment: 'hls_450k_video.ts'},
1011 playlistData = playlistTemplate(testData),
1012 data = m3u8parser.parse(playlistData);
1013
1014 notStrictEqual(data, null, 'data is not NULL');
1015 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
1016 // strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.');
1017 });
1018
1019 //extinf values must be below the target duration
1020 test('test empty EXTINF values', function() { 976 test('test empty EXTINF values', function() {
1021 var 977 var
1022 playlistTemplate = Handlebars.compile(window.playlist_extinf_template), 978 playlistTemplate = Handlebars.compile(window.playlist_extinf_template),
1023 testData = {version: 4, extInf: '', extInf1: '10.5', extInf2: '10.5', segment: 'hls_450k_video.ts'}, 979 testData = {
1024 playlistData = playlistTemplate(testData), 980 version: 4,
1025 data = m3u8parser.parse(playlistData); 981 extInf: '',
982 extInf1: '10.5',
983 extInf2: '10.5',
984 segment: 'hls_450k_video.ts'
985 };
986 parser.push(playlistTemplate(testData));
1026 987
1027 notStrictEqual(data, null, 'data is not NULL'); 988 ok(!('duration' in parser.manifest.segments[0]), 'empty durations are ignored');
1028 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 989 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
1029 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons'); 990 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons');
1030 // strictEqual(data.invalidReasons[0], 'Invalid Segment Data: \'#EXTINF value empty\''); 991 // strictEqual(data.invalidReasons[0], 'Invalid Segment Data: \'#EXTINF value empty\'');
...@@ -1045,100 +1006,124 @@ ...@@ -1045,100 +1006,124 @@
1045 test('test EXT-X-ALLOW-CACHE YES', function() { 1006 test('test EXT-X-ALLOW-CACHE YES', function() {
1046 var 1007 var
1047 playlistTemplate = Handlebars.compile(window.playlist_allow_cache), 1008 playlistTemplate = Handlebars.compile(window.playlist_allow_cache),
1048 testData = {version: 4, allowCache: 'YES'}, 1009 testData = { version: 4, allowCache: 'YES' };
1049 playlistData = playlistTemplate(testData), 1010 parser.push(playlistTemplate(testData));
1050 data = m3u8parser.parse(playlistData);
1051 1011
1052 notStrictEqual(data, null, 'data is not NULL');
1053 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 1012 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
1054 // strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.'); 1013 // strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.');
1055 strictEqual(data.allowCache, 'YES', 'EXT-X-ALLOW-CACHE should be YES'); 1014 strictEqual(parser.manifest.allowCache, true, 'allowCache is true');
1056 }); 1015 });
1057 1016
1058 test('test EXT-X-ALLOW-CACHE NO', function() { 1017 test('test EXT-X-ALLOW-CACHE NO', function() {
1059 var 1018 var
1060 playlistTemplate = Handlebars.compile(window.playlist_allow_cache), 1019 playlistTemplate = Handlebars.compile(window.playlist_allow_cache),
1061 testData = {version: 4, allowCache: 'NO'}, 1020 testData = {
1062 playlistData = playlistTemplate(testData), 1021 version: 4,
1063 data = m3u8parser.parse(playlistData); 1022 allowCache: 'NO'
1023 };
1024 parser.push(playlistTemplate(testData));
1064 1025
1065 notStrictEqual(data, null, 'data is not NULL');
1066 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 1026 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
1067 // strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.'); 1027 // strictEqual(data.invalidReasons.length, 0, 'Errors object should not be empty.');
1068 strictEqual(data.allowCache, 'NO', 'EXT-X-ALLOW-CACHE should be NO'); 1028 strictEqual(parser.manifest.allowCache, false, 'allowCache is false');
1069 }); 1029 });
1070 1030
1071 test('test EXT-X-ALLOW-CACHE invalid, default to YES', function() { 1031 test('test EXT-X-ALLOW-CACHE invalid, default to YES', function() {
1072 var 1032 var
1073 playlistTemplate = Handlebars.compile(window.playlist_allow_cache), 1033 playlistTemplate = Handlebars.compile(window.playlist_allow_cache),
1074 testData = {version: 4, allowCache: 'YESTERDAYNO'}, 1034 testData = {
1075 playlistData = playlistTemplate(testData), 1035 version: 4,
1076 data = m3u8parser.parse(playlistData); 1036 allowCache: 'YESTERDAYNO'
1037 };
1038 parser.push(playlistTemplate(testData));
1077 1039
1078 notStrictEqual(data, null, 'data is not NULL');
1079 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 1040 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
1080 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons'); 1041 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons');
1081 // strictEqual(data.invalidReasons[0], 'Invalid EXT-X-ALLOW-CACHE value: \'YESTERDAYNO\''); 1042 // strictEqual(data.invalidReasons[0], 'Invalid EXT-X-ALLOW-CACHE value: \'YESTERDAYNO\'');
1082 strictEqual(data.allowCache, 'YES', 'EXT-X-ALLOW-CACHE should default to YES.'); 1043 strictEqual(parser.manifest.allowCache, true, 'allowCache defaults to true');
1083 }); 1044 });
1084 1045
1085 // test('test EXT-X-ALLOW-CACHE empty, default to YES', function() { 1046 test('empty EXT-X-ALLOW-CACHE defaults to YES', function() {
1086 // var 1047 var
1087 // playlistTemplate = Handlebars.compile(window.playlist_allow_cache), 1048 playlistTemplate = Handlebars.compile(window.playlist_allow_cache),
1088 // testData = {version: 4, allowCache: ''}, 1049 testData = {
1089 // playlistData = playlistTemplate(testData), 1050 version: 4,
1090 // data = m3u8parser.parse(playlistData); 1051 allowCache: ''
1052 };
1053 parser.push(playlistTemplate(testData));
1091 1054
1092 // notStrictEqual(data, null, 'data is not NULL'); 1055 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
1093 // // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL'); 1056 // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons');
1094 // // strictEqual(data.invalidReasons.length, 1, 'data has 1 invalid reasons'); 1057 // strictEqual(data.invalidReasons[0], 'Invalid EXT-X-ALLOW-CACHE value: \'\'');
1095 // // strictEqual(data.invalidReasons[0], 'Invalid EXT-X-ALLOW-CACHE value: \'\''); 1058 strictEqual(parser.manifest.allowCache, true, 'allowCache should default to YES.');
1096 // strictEqual(data.allowCache, 'YES', 'EXT-X-ALLOW-CACHE should default to YES.'); 1059 });
1097 // });
1098 1060
1099 // test('test EXT-X-ALLOW-CACHE missing, default to YES', function() { 1061 test('missing EXT-X-ALLOW-CACHE defaults to YES', function() {
1100 // var 1062 var
1101 // playlistTemplate = Handlebars.compile(window.playlist_allow_cache), 1063 playlistTemplate = Handlebars.compile(window.playlist_allow_cache),
1102 // testData = {version: 4}, 1064 testData = {version: 4};
1103 // playlistData = playlistTemplate(testData), 1065 parser.push(playlistTemplate(testData));
1104 // data = m3u8parser.parse(playlistData);
1105
1106 // notStrictEqual(data, null, 'data is not NULL');
1107 // // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
1108 // // strictEqual(data.invalidReasons.length, 1, 'No EXT-X-ALLOW-CACHE specified. Default: YES.');
1109 // strictEqual(data.allowCache, 'YES', 'EXT-X-ALLOW-CACHE should default to YES');
1110 // });
1111 1066
1112 // test('test EXT-X-BYTERANGE valid', function() { 1067 // notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
1113 // var 1068 // strictEqual(data.invalidReasons.length, 1, 'No EXT-X-ALLOW-CACHE specified. Default: YES.');
1114 // playlistTemplate = Handlebars.compile(window.playlist_byte_range), 1069 strictEqual(parser.manifest.allowCache, true, 'allowCache should default to YES');
1115 // testData = {version: 4, byteRange: '522828,0', byteRange1: '587500,522828', byteRange2: '44556,8353216'}, 1070 });
1116 // playlistData = playlistTemplate(testData),
1117 // data = m3u8parser.parse(playlistData);
1118
1119 // notStrictEqual(data, null, 'data is not NULL');
1120 // //notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
1121 // //strictEqual(data.invalidReasons.length, 0, 'Errors object should be empty.');
1122 // //TODO: Validate the byteRange info
1123 // strictEqual(data.segments.length, 16, '16 segments should have been parsed.');
1124 // strictEqual(data.segments[0].byterange, testData.byteRange, 'byteRange incorrect.');
1125 // strictEqual(data.segments[1].byterange, testData.byteRange1, 'byteRange1 incorrect.');
1126 // strictEqual(data.segments[15].byterange, testData.byteRange2, 'byteRange2 incorrect.');
1127 // });
1128 1071
1129 // test('test EXT-X-BYTERANGE used but version is < 4', function() { 1072 test('valid byteranges are parsed', function() {
1130 // var 1073 var
1131 // playlistTemplate = Handlebars.compile(window.playlist_byte_range), 1074 playlistTemplate = Handlebars.compile(window.playlist_byte_range),
1132 // testData = {version: 3, byteRange: ['522828,0'], byteRange1: ['587500,522828'], byteRange2: ['44556,8353216']}, 1075 testData = {
1133 // playlistData = playlistTemplate(testData), 1076 version: 4,
1134 // data = m3u8parser.parse(playlistData); 1077 byteRange: '522828@0',
1135 1078 byteRange1: '587500@522828',
1136 // notStrictEqual(data, null, 'data is not NULL'); 1079 byteRange2: '44556@8353216'
1137 // strictEqual(data.segments.length, 16, '16 segments should have been parsed.'); 1080 };
1138 // // notStrictEqual(data.invalidReasons, null, 'there should be an error'); 1081 parser.push(playlistTemplate(testData));
1139 // // strictEqual(data.invalidReasons.length, 1, 'there should be 1 error'); 1082
1140 // // //TODO: Validate the byteRange info 1083 //notStrictEqual(data.invalidReasons, null, 'invalidReasons is not NULL');
1141 // // strictEqual(data.invalidReasons[0], 'EXT-X-BYTERANGE used but version is < 4.');x 1084 //strictEqual(data.invalidReasons.length, 0, 'Errors object should be empty.');
1142 // }); 1085 //TODO: Validate the byteRange info
1086 strictEqual(parser.manifest.segments.length,
1087 17,
1088 '17 segments should have been parsed.');
1089 strictEqual(parser.manifest.segments[0].byterange.length,
1090 522828,
1091 'byteRange length incorrect');
1092 strictEqual(parser.manifest.segments[0].byterange.offset,
1093 0,
1094 'byteRange offset incorrect');
1095 strictEqual(parser.manifest.segments[1].byterange.length,
1096 587500,
1097 'byteRange length incorrect');
1098 strictEqual(parser.manifest.segments[1].byterange.offset,
1099 522828,
1100 'byteRange offset incorrect');
1101 });
1102
1103 test('EXT-X-BYTERANGE used but version is < 4', function() {
1104 var
1105 playlistTemplate = Handlebars.compile(window.playlist_byte_range),
1106 testData = {
1107 version: 3,
1108 // incorrect syntax, '@' is the offset separator
1109 byteRange: '522828,0',
1110 byteRange1: '587500,522828',
1111 byteRange2: '44556,8353216'
1112 };
1113 parser.push(playlistTemplate(testData));
1114
1115 strictEqual(parser.manifest.segments.length,
1116 17,
1117 '17 segments should have been parsed.');
1118 strictEqual(parser.manifest.segments[0].byterange.length,
1119 522828,
1120 'the byterange length was parsed');
1121 strictEqual(parser.manifest.segments[0].byterange.offset,
1122 0,
1123 'the byterange offset was parsed');
1124 strictEqual(parser.manifest.segments[1].byterange.offset,
1125 0,
1126 'the byterange offset was defaulted');
1127 });
1143 1128
1144 })(window, window.console); 1129 })(window, window.console);
......
1 window.playlistData = '#EXTM3U\n'+ 1 window.playlistM3U8data = '#EXTM3U\n'+
2 '#EXT-X-TARGETDURATION:10\n' + 2 '#EXT-X-TARGETDURATION:10\n' +
3 '#EXT-X-VERSION:4\n' + 3 '#EXT-X-VERSION:4\n' +
4 '#EXT-X-MEDIA-SEQUENCE:0\n' + 4 '#EXT-X-MEDIA-SEQUENCE:0\n' +
......
1 window.playlist_media_sequence_template = '#EXTM3U\n'+ 1 window.playlist_media_sequence_template = '#EXTM3U\n'+
2 '#EXT-X-PLAYLIST-TYPE:VOD\n'+ 2 '#EXT-X-PLAYLIST-TYPE:VOD\n'+
3 '{{#if mediaSequence}}#EXT-X-MEDIA-SEQUENCE:{{{mediaSequence}}}{{/if}}\n'+ 3 '{{#if mediaSequence}}#EXT-X-MEDIA-SEQUENCE:{{{mediaSequence}}}{{/if}}\n'+
4 '{{#if mediaSequence1}}#EXT-X-MEDIA-SEQUENCE:{{{mediaSequence2}}}{{/if}}\n'+ 4 '{{#if mediaSequence1}}#EXT-X-MEDIA-SEQUENCE:{{{mediaSequence1}}}{{/if}}\n'+
5 '#EXT-X-ALLOW-CACHE:YES\n'+ 5 '#EXT-X-ALLOW-CACHE:YES\n'+
6 '#EXT-X-TARGETDURATION:8\n'+ 6 '#EXT-X-TARGETDURATION:8\n'+
7 '#EXTINF:6.640,{}\n'+ 7 '#EXTINF:6.640,{}\n'+
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
11 <script src="../libs/handlebars/handlebars-v1.1.2.js"></script> 11 <script src="../libs/handlebars/handlebars-v1.1.2.js"></script>
12 12
13 <!-- video.js --> 13 <!-- video.js -->
14 <script src="../node_modules/video.js/video.dev.js"></script> 14 <script src="../node_modules/video.js/dist/video-js/video.js"></script>
15 15
16 <!-- HLS plugin --> 16 <!-- HLS plugin -->
17 <script src="../src/video-js-hls.js"></script> 17 <script src="../src/video-js-hls.js"></script>
...@@ -22,10 +22,9 @@ ...@@ -22,10 +22,9 @@
22 <script src="../src/segment-parser.js"></script> 22 <script src="../src/segment-parser.js"></script>
23 23
24 <!-- M3U8 --> 24 <!-- M3U8 -->
25 <script src="../src/m3u8/m3u8-tokenizer.js"></script> 25 <script src="../src/m3u8/m3u8-parser.js"></script>
26 <script src="../src/m3u8/m3u8.js"></script> 26 <script src="../src/m3u8/m3u8.js"></script>
27 <script src="../src/m3u8/m3u8-tag-types.js"></script> 27 <script src="../src/m3u8/m3u8-tag-types.js"></script>
28 <script src="../build/m3u8-parser.js"></script>
29 <script src="../src/manifest-controller.js"></script> 28 <script src="../src/manifest-controller.js"></script>
30 <!-- M3U8 TEST DATA --> 29 <!-- M3U8 TEST DATA -->
31 <script src="manifest/playlistM3U8data.js"></script> 30 <script src="manifest/playlistM3U8data.js"></script>
......