71246287 by David LaPalomento

Add documentation for M3U8 parsing

Document the stream object and the components of M3U8 parsing.
1 parent 1598394f
/**
* Utilities for parsing M3U8 files. If the entire manifest is available,
* `Parser` will create a object representation with enough detail for managing
* playback. `ParseStream` and `LineStream` are lower-level parsing primitives
* that do not assume the entirety of the manifest is ready and expose a
* ReadableStream-like interface.
*/
(function(videojs, parseInt, isFinite, mergeOptions, undefined) {
var
noop = function() {},
......@@ -18,10 +25,18 @@
ParseStream,
Parser;
/**
* A stream that buffers string input and generates a `data` event for each
* line.
*/
LineStream = function() {
var buffer = '';
LineStream.prototype.init.call(this);
/**
* Add new data to be parsed.
* @param data {string} the text to process
*/
this.push = function(data) {
var nextNewline;
......@@ -36,10 +51,35 @@
};
LineStream.prototype = new Stream();
/**
* A line-level M3U8 parser event stream. It expects to receive input one
* line at a time and performs a context-free parse of its contents. A stream
* interpretation of a manifest can be useful if the manifest is expected to
* be too large to fit comfortably into memory or the entirety of the input
* is not immediately available. Otherwise, it's probably much easier to work
* with a regular `Parser` object.
*
* Produces `data` events with an object that captures the parser's
* interpretation of the input. That object has a property `tag` that is one
* of `uri`, `comment`, or `tag`. URIs only have a single additional
* property, `line`, which captures the entirety of the input without
* interpretation. Comments similarly have a single additional property
* `text` which is the input without the leading `#`.
*
* Tags always have a property `tagType` which is the lower-cased version of
* the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
* `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
* tags are given the tag type `unknown` and a single additional property
* `data` with the remainder of the input.
*/
ParseStream = function() {
ParseStream.prototype.init.call(this);
};
ParseStream.prototype = new Stream();
/**
* Parses an additional line of input.
* @param line {string} a single line of an M3U8 file to parse
*/
ParseStream.prototype.push = function(line) {
var match, event;
if (line.length === 0) {
......@@ -213,6 +253,24 @@
});
};
/**
* A parser for M3U8 files. The current interpretation of the input is
* exposed as a property `manifest` on parser objects. It's just two lines to
* create and parse a manifest once you have the contents available as a string:
*
* ```js
* var parser = new videojs.m3u8.Parser();
* parser.push(xhr.responseText);
* ```
*
* New input can later be applied to update the manifest object by calling
* `push` again.
*
* The parser attempts to create a usable manifest object even if the
* underlying input is somewhat nonsensical. It emits `info` and `warning`
* events during the parse if it encounters input that seems invalid or
* requires some property of the manifest object to be defaulted.
*/
Parser = function() {
var
self = this,
......@@ -331,9 +389,18 @@
});
};
Parser.prototype = new Stream();
/**
* Parse the input string and update the manifest object.
* @param chunk {string} a potentially incomplete portion of the manifest
*/
Parser.prototype.push = function(chunk) {
this.lineStream.push(chunk);
};
/**
* Flush any remaining input. This can be handy if the last line of an M3U8
* manifest did not contain a trailing newline but the file has been
* completely received.
*/
Parser.prototype.end = function() {
// flush any buffered input
this.lineStream.push('\n');
......
/**
* A lightweight stream implemention that handles event dispatching. Objects
* that inherit from streams should call init in their constructors.
* A lightweight readable stream implemention that handles event dispatching.
* Objects that inherit from streams should call init in their constructors.
*/
(function(videojs, undefined) {
var Stream = function() {
this.init = function() {
var listeners = {};
/**
* Add a listener for a specified event type.
* @param type {string} the event name
* @param listener {function} the callback to be invoked when an event of
* the specified type occurs
*/
this.on = function(type, listener) {
if (!listeners[type]) {
listeners[type] = [];
}
listeners[type].push(listener);
};
/**
* Remove a listener for a specified event type.
* @param type {string} the event name
* @param listener {function} a function previously registered for this
* type of event through `on`
*/
this.off = function(type, listener) {
var index;
if (!listeners[type]) {
......@@ -21,6 +33,11 @@
listeners[type].splice(index, 1);
return index > -1;
};
/**
* Trigger an event of the specified type on this stream. Any additional
* arguments to this function are passed as parameters to event listeners.
* @param type {string} the event name
*/
this.trigger = function(type) {
var callbacks, i, length, args;
callbacks = listeners[type];
......@@ -35,6 +52,13 @@
};
};
};
/**
* Forwards all `data` events on this stream to the destination stream. The
* destination stream should provide a method `push` to receive the data
* events as they arrive.
* @param destination {stream} the stream that will receive all `data` events
* @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
*/
Stream.prototype.pipe = function(destination) {
this.on('data', function(data) {
destination.push(data);
......