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 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>
......