71246287 by David LaPalomento

Add documentation for M3U8 parsing

Document the stream object and the components of M3U8 parsing.
1 parent 1598394f
1 /**
2 * Utilities for parsing M3U8 files. If the entire manifest is available,
3 * `Parser` will create a object representation with enough detail for managing
4 * playback. `ParseStream` and `LineStream` are lower-level parsing primitives
5 * that do not assume the entirety of the manifest is ready and expose a
6 * ReadableStream-like interface.
7 */
1 (function(videojs, parseInt, isFinite, mergeOptions, undefined) { 8 (function(videojs, parseInt, isFinite, mergeOptions, undefined) {
2 var 9 var
3 noop = function() {}, 10 noop = function() {},
...@@ -18,10 +25,18 @@ ...@@ -18,10 +25,18 @@
18 ParseStream, 25 ParseStream,
19 Parser; 26 Parser;
20 27
28 /**
29 * A stream that buffers string input and generates a `data` event for each
30 * line.
31 */
21 LineStream = function() { 32 LineStream = function() {
22 var buffer = ''; 33 var buffer = '';
23 LineStream.prototype.init.call(this); 34 LineStream.prototype.init.call(this);
24 35
36 /**
37 * Add new data to be parsed.
38 * @param data {string} the text to process
39 */
25 this.push = function(data) { 40 this.push = function(data) {
26 var nextNewline; 41 var nextNewline;
27 42
...@@ -36,10 +51,35 @@ ...@@ -36,10 +51,35 @@
36 }; 51 };
37 LineStream.prototype = new Stream(); 52 LineStream.prototype = new Stream();
38 53
54 /**
55 * A line-level M3U8 parser event stream. It expects to receive input one
56 * line at a time and performs a context-free parse of its contents. A stream
57 * interpretation of a manifest can be useful if the manifest is expected to
58 * be too large to fit comfortably into memory or the entirety of the input
59 * is not immediately available. Otherwise, it's probably much easier to work
60 * with a regular `Parser` object.
61 *
62 * Produces `data` events with an object that captures the parser's
63 * interpretation of the input. That object has a property `tag` that is one
64 * of `uri`, `comment`, or `tag`. URIs only have a single additional
65 * property, `line`, which captures the entirety of the input without
66 * interpretation. Comments similarly have a single additional property
67 * `text` which is the input without the leading `#`.
68 *
69 * Tags always have a property `tagType` which is the lower-cased version of
70 * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
71 * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
72 * tags are given the tag type `unknown` and a single additional property
73 * `data` with the remainder of the input.
74 */
39 ParseStream = function() { 75 ParseStream = function() {
40 ParseStream.prototype.init.call(this); 76 ParseStream.prototype.init.call(this);
41 }; 77 };
42 ParseStream.prototype = new Stream(); 78 ParseStream.prototype = new Stream();
79 /**
80 * Parses an additional line of input.
81 * @param line {string} a single line of an M3U8 file to parse
82 */
43 ParseStream.prototype.push = function(line) { 83 ParseStream.prototype.push = function(line) {
44 var match, event; 84 var match, event;
45 if (line.length === 0) { 85 if (line.length === 0) {
...@@ -213,6 +253,24 @@ ...@@ -213,6 +253,24 @@
213 }); 253 });
214 }; 254 };
215 255
256 /**
257 * A parser for M3U8 files. The current interpretation of the input is
258 * exposed as a property `manifest` on parser objects. It's just two lines to
259 * create and parse a manifest once you have the contents available as a string:
260 *
261 * ```js
262 * var parser = new videojs.m3u8.Parser();
263 * parser.push(xhr.responseText);
264 * ```
265 *
266 * New input can later be applied to update the manifest object by calling
267 * `push` again.
268 *
269 * The parser attempts to create a usable manifest object even if the
270 * underlying input is somewhat nonsensical. It emits `info` and `warning`
271 * events during the parse if it encounters input that seems invalid or
272 * requires some property of the manifest object to be defaulted.
273 */
216 Parser = function() { 274 Parser = function() {
217 var 275 var
218 self = this, 276 self = this,
...@@ -331,9 +389,18 @@ ...@@ -331,9 +389,18 @@
331 }); 389 });
332 }; 390 };
333 Parser.prototype = new Stream(); 391 Parser.prototype = new Stream();
392 /**
393 * Parse the input string and update the manifest object.
394 * @param chunk {string} a potentially incomplete portion of the manifest
395 */
334 Parser.prototype.push = function(chunk) { 396 Parser.prototype.push = function(chunk) {
335 this.lineStream.push(chunk); 397 this.lineStream.push(chunk);
336 }; 398 };
399 /**
400 * Flush any remaining input. This can be handy if the last line of an M3U8
401 * manifest did not contain a trailing newline but the file has been
402 * completely received.
403 */
337 Parser.prototype.end = function() { 404 Parser.prototype.end = function() {
338 // flush any buffered input 405 // flush any buffered input
339 this.lineStream.push('\n'); 406 this.lineStream.push('\n');
......
1 /** 1 /**
2 * A lightweight stream implemention that handles event dispatching. Objects 2 * A lightweight readable stream implemention that handles event dispatching.
3 * 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 (function(videojs, undefined) {
6 var Stream = function() { 6 var Stream = function() {
7 this.init = function() { 7 this.init = function() {
8 var listeners = {}; 8 var listeners = {};
9 /**
10 * Add a listener for a specified event type.
11 * @param type {string} the event name
12 * @param listener {function} the callback to be invoked when an event of
13 * the specified type occurs
14 */
9 this.on = function(type, listener) { 15 this.on = function(type, listener) {
10 if (!listeners[type]) { 16 if (!listeners[type]) {
11 listeners[type] = []; 17 listeners[type] = [];
12 } 18 }
13 listeners[type].push(listener); 19 listeners[type].push(listener);
14 }; 20 };
21 /**
22 * Remove a listener for a specified event type.
23 * @param type {string} the event name
24 * @param listener {function} a function previously registered for this
25 * type of event through `on`
26 */
15 this.off = function(type, listener) { 27 this.off = function(type, listener) {
16 var index; 28 var index;
17 if (!listeners[type]) { 29 if (!listeners[type]) {
...@@ -21,6 +33,11 @@ ...@@ -21,6 +33,11 @@
21 listeners[type].splice(index, 1); 33 listeners[type].splice(index, 1);
22 return index > -1; 34 return index > -1;
23 }; 35 };
36 /**
37 * Trigger an event of the specified type on this stream. Any additional
38 * arguments to this function are passed as parameters to event listeners.
39 * @param type {string} the event name
40 */
24 this.trigger = function(type) { 41 this.trigger = function(type) {
25 var callbacks, i, length, args; 42 var callbacks, i, length, args;
26 callbacks = listeners[type]; 43 callbacks = listeners[type];
...@@ -35,6 +52,13 @@ ...@@ -35,6 +52,13 @@
35 }; 52 };
36 }; 53 };
37 }; 54 };
55 /**
56 * Forwards all `data` events on this stream to the destination stream. The
57 * destination stream should provide a method `push` to receive the data
58 * events as they arrive.
59 * @param destination {stream} the stream that will receive all `data` events
60 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
61 */
38 Stream.prototype.pipe = function(destination) { 62 Stream.prototype.pipe = function(destination) {
39 this.on('data', function(data) { 63 this.on('data', function(data) {
40 destination.push(data); 64 destination.push(data);
......