Add documentation for M3U8 parsing
Document the stream object and the components of M3U8 parsing.
Showing
2 changed files
with
93 additions
and
2 deletions
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); | ... | ... |
-
Please register or sign in to post a comment