]> Git in Space - website/commitdiff
Add livestreaming page
authorMira Ayre <mi@boxin.space>
Thu, 28 Jan 2021 04:53:15 +0000 (04:53 +0000)
committerMira Ayre <mi@boxin.space>
Sat, 19 Jun 2021 16:56:57 +0000 (17:56 +0100)
www/dash.js [new file with mode: 0644]
www/index.cf
www/live.cf [new file with mode: 0644]
www/style.css

diff --git a/www/dash.js b/www/dash.js
new file mode 100644 (file)
index 0000000..836da5d
--- /dev/null
@@ -0,0 +1,63073 @@
+(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(_dereq_,module,exports){
+/* $Date: 2007-06-12 18:02:31 $ */
+
+// from: http://bannister.us/weblog/2007/06/09/simple-base64-encodedecode-javascript/
+// Handles encode/decode of ASCII and Unicode strings.
+
+'use strict';
+
+var UTF8 = {};
+UTF8.encode = function (s) {
+    var u = [];
+    for (var i = 0; i < s.length; ++i) {
+        var c = s.charCodeAt(i);
+        if (c < 0x80) {
+            u.push(c);
+        } else if (c < 0x800) {
+            u.push(0xC0 | c >> 6);
+            u.push(0x80 | 63 & c);
+        } else if (c < 0x10000) {
+            u.push(0xE0 | c >> 12);
+            u.push(0x80 | 63 & c >> 6);
+            u.push(0x80 | 63 & c);
+        } else {
+            u.push(0xF0 | c >> 18);
+            u.push(0x80 | 63 & c >> 12);
+            u.push(0x80 | 63 & c >> 6);
+            u.push(0x80 | 63 & c);
+        }
+    }
+    return u;
+};
+UTF8.decode = function (u) {
+    var a = [];
+    var i = 0;
+    while (i < u.length) {
+        var v = u[i++];
+        if (v < 0x80) {
+            // no need to mask byte
+        } else if (v < 0xE0) {
+                v = (31 & v) << 6;
+                v |= 63 & u[i++];
+            } else if (v < 0xF0) {
+                v = (15 & v) << 12;
+                v |= (63 & u[i++]) << 6;
+                v |= 63 & u[i++];
+            } else {
+                v = (7 & v) << 18;
+                v |= (63 & u[i++]) << 12;
+                v |= (63 & u[i++]) << 6;
+                v |= 63 & u[i++];
+            }
+        a.push(String.fromCharCode(v));
+    }
+    return a.join('');
+};
+
+var BASE64 = {};
+(function (T) {
+    var encodeArray = function encodeArray(u) {
+        var i = 0;
+        var a = [];
+        var n = 0 | u.length / 3;
+        while (0 < n--) {
+            var v = (u[i] << 16) + (u[i + 1] << 8) + u[i + 2];
+            i += 3;
+            a.push(T.charAt(63 & v >> 18));
+            a.push(T.charAt(63 & v >> 12));
+            a.push(T.charAt(63 & v >> 6));
+            a.push(T.charAt(63 & v));
+        }
+        if (2 == u.length - i) {
+            var v = (u[i] << 16) + (u[i + 1] << 8);
+            a.push(T.charAt(63 & v >> 18));
+            a.push(T.charAt(63 & v >> 12));
+            a.push(T.charAt(63 & v >> 6));
+            a.push('=');
+        } else if (1 == u.length - i) {
+            var v = u[i] << 16;
+            a.push(T.charAt(63 & v >> 18));
+            a.push(T.charAt(63 & v >> 12));
+            a.push('==');
+        }
+        return a.join('');
+    };
+    var R = (function () {
+        var a = [];
+        for (var i = 0; i < T.length; ++i) {
+            a[T.charCodeAt(i)] = i;
+        }
+        a['='.charCodeAt(0)] = 0;
+        return a;
+    })();
+    var decodeArray = function decodeArray(s) {
+        var i = 0;
+        var u = [];
+        var n = 0 | s.length / 4;
+        while (0 < n--) {
+            var v = (R[s.charCodeAt(i)] << 18) + (R[s.charCodeAt(i + 1)] << 12) + (R[s.charCodeAt(i + 2)] << 6) + R[s.charCodeAt(i + 3)];
+            u.push(255 & v >> 16);
+            u.push(255 & v >> 8);
+            u.push(255 & v);
+            i += 4;
+        }
+        if (u) {
+            if ('=' == s.charAt(i - 2)) {
+                u.pop();
+                u.pop();
+            } else if ('=' == s.charAt(i - 1)) {
+                u.pop();
+            }
+        }
+        return u;
+    };
+    var ASCII = {};
+    ASCII.encode = function (s) {
+        var u = [];
+        for (var i = 0; i < s.length; ++i) {
+            u.push(s.charCodeAt(i));
+        }
+        return u;
+    };
+    ASCII.decode = function (u) {
+        for (var i = 0; i < s.length; ++i) {
+            a[i] = String.fromCharCode(a[i]);
+        }
+        return a.join('');
+    };
+    BASE64.decodeArray = function (s) {
+        var u = decodeArray(s);
+        return new Uint8Array(u);
+    };
+    BASE64.encodeASCII = function (s) {
+        var u = ASCII.encode(s);
+        return encodeArray(u);
+    };
+    BASE64.decodeASCII = function (s) {
+        var a = decodeArray(s);
+        return ASCII.decode(a);
+    };
+    BASE64.encode = function (s) {
+        var u = UTF8.encode(s);
+        return encodeArray(u);
+    };
+    BASE64.decode = function (s) {
+        var u = decodeArray(s);
+        return UTF8.decode(u);
+    };
+})("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
+
+/*The following polyfills are not used in dash.js but have caused multiplayer integration issues.
+ Therefore commenting them out.
+if (undefined === btoa) {
+    var btoa = BASE64.encode;
+}
+if (undefined === atob) {
+    var atob = BASE64.decode;
+}
+*/
+
+if (typeof exports !== 'undefined') {
+    exports.decode = BASE64.decode;
+    exports.decodeArray = BASE64.decodeArray;
+    exports.encode = BASE64.encode;
+    exports.encodeASCII = BASE64.encodeASCII;
+}
+
+},{}],2:[function(_dereq_,module,exports){
+/**
+ * The copyright in this software is being made available under the BSD License,
+ * included below. This software may be subject to other third party and contributor
+ * rights, including patent rights, and no such rights are granted under this license.
+ *
+ * Copyright (c) 2015-2016, DASH Industry Forum.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation and/or
+ *  other materials provided with the distribution.
+ *  2. Neither the name of Dash Industry Forum nor the names of its
+ *  contributors may be used to endorse or promote products derived from this software
+ *  without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY
+ *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ *  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ *  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ *  POSSIBILITY OF SUCH DAMAGE.
+ */
+'use strict';
+
+(function (exports) {
+
+    "use strict";
+
+    /**
+     *  Exceptions from regular ASCII. CodePoints are mapped to UTF-16 codes
+     */
+
+    var specialCea608CharsCodes = {
+        0x2a: 0xe1, // lowercase a, acute accent
+        0x5c: 0xe9, // lowercase e, acute accent
+        0x5e: 0xed, // lowercase i, acute accent
+        0x5f: 0xf3, // lowercase o, acute accent
+        0x60: 0xfa, // lowercase u, acute accent
+        0x7b: 0xe7, // lowercase c with cedilla
+        0x7c: 0xf7, // division symbol
+        0x7d: 0xd1, // uppercase N tilde
+        0x7e: 0xf1, // lowercase n tilde
+        0x7f: 0x2588, // Full block
+        // THIS BLOCK INCLUDES THE 16 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS
+        // THAT COME FROM HI BYTE=0x11 AND LOW BETWEEN 0x30 AND 0x3F
+        // THIS MEANS THAT \x50 MUST BE ADDED TO THE VALUES
+        0x80: 0xae, // Registered symbol (R)
+        0x81: 0xb0, // degree sign
+        0x82: 0xbd, // 1/2 symbol
+        0x83: 0xbf, // Inverted (open) question mark
+        0x84: 0x2122, // Trademark symbol (TM)
+        0x85: 0xa2, // Cents symbol
+        0x86: 0xa3, // Pounds sterling
+        0x87: 0x266a, // Music 8'th note
+        0x88: 0xe0, // lowercase a, grave accent
+        0x89: 0x20, // transparent space (regular)
+        0x8a: 0xe8, // lowercase e, grave accent
+        0x8b: 0xe2, // lowercase a, circumflex accent
+        0x8c: 0xea, // lowercase e, circumflex accent
+        0x8d: 0xee, // lowercase i, circumflex accent
+        0x8e: 0xf4, // lowercase o, circumflex accent
+        0x8f: 0xfb, // lowercase u, circumflex accent
+        // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS
+        // THAT COME FROM HI BYTE=0x12 AND LOW BETWEEN 0x20 AND 0x3F
+        0x90: 0xc1, // capital letter A with acute
+        0x91: 0xc9, // capital letter E with acute
+        0x92: 0xd3, // capital letter O with acute
+        0x93: 0xda, // capital letter U with acute
+        0x94: 0xdc, // capital letter U with diaresis
+        0x95: 0xfc, // lowercase letter U with diaeresis
+        0x96: 0x2018, // opening single quote
+        0x97: 0xa1, // inverted exclamation mark
+        0x98: 0x2a, // asterisk
+        0x99: 0x2019, // closing single quote
+        0x9a: 0x2501, // box drawings heavy horizontal
+        0x9b: 0xa9, // copyright sign
+        0x9c: 0x2120, // Service mark
+        0x9d: 0x2022, // (round) bullet
+        0x9e: 0x201c, // Left double quotation mark
+        0x9f: 0x201d, // Right double quotation mark
+        0xa0: 0xc0, // uppercase A, grave accent
+        0xa1: 0xc2, // uppercase A, circumflex
+        0xa2: 0xc7, // uppercase C with cedilla
+        0xa3: 0xc8, // uppercase E, grave accent
+        0xa4: 0xca, // uppercase E, circumflex
+        0xa5: 0xcb, // capital letter E with diaresis
+        0xa6: 0xeb, // lowercase letter e with diaresis
+        0xa7: 0xce, // uppercase I, circumflex
+        0xa8: 0xcf, // uppercase I, with diaresis
+        0xa9: 0xef, // lowercase i, with diaresis
+        0xaa: 0xd4, // uppercase O, circumflex
+        0xab: 0xd9, // uppercase U, grave accent
+        0xac: 0xf9, // lowercase u, grave accent
+        0xad: 0xdb, // uppercase U, circumflex
+        0xae: 0xab, // left-pointing double angle quotation mark
+        0xaf: 0xbb, // right-pointing double angle quotation mark
+        // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS
+        // THAT COME FROM HI BYTE=0x13 AND LOW BETWEEN 0x20 AND 0x3F
+        0xb0: 0xc3, // Uppercase A, tilde
+        0xb1: 0xe3, // Lowercase a, tilde
+        0xb2: 0xcd, // Uppercase I, acute accent
+        0xb3: 0xcc, // Uppercase I, grave accent
+        0xb4: 0xec, // Lowercase i, grave accent
+        0xb5: 0xd2, // Uppercase O, grave accent
+        0xb6: 0xf2, // Lowercase o, grave accent
+        0xb7: 0xd5, // Uppercase O, tilde
+        0xb8: 0xf5, // Lowercase o, tilde
+        0xb9: 0x7b, // Open curly brace
+        0xba: 0x7d, // Closing curly brace
+        0xbb: 0x5c, // Backslash
+        0xbc: 0x5e, // Caret
+        0xbd: 0x5f, // Underscore
+        0xbe: 0x7c, // Pipe (vertical line)
+        0xbf: 0x223c, // Tilde operator
+        0xc0: 0xc4, // Uppercase A, umlaut
+        0xc1: 0xe4, // Lowercase A, umlaut
+        0xc2: 0xd6, // Uppercase O, umlaut
+        0xc3: 0xf6, // Lowercase o, umlaut
+        0xc4: 0xdf, // Esszett (sharp S)
+        0xc5: 0xa5, // Yen symbol
+        0xc6: 0xa4, // Generic currency sign
+        0xc7: 0x2503, // Box drawings heavy vertical
+        0xc8: 0xc5, // Uppercase A, ring
+        0xc9: 0xe5, // Lowercase A, ring
+        0xca: 0xd8, // Uppercase O, stroke
+        0xcb: 0xf8, // Lowercase o, strok
+        0xcc: 0x250f, // Box drawings heavy down and right
+        0xcd: 0x2513, // Box drawings heavy down and left
+        0xce: 0x2517, // Box drawings heavy up and right
+        0xcf: 0x251b // Box drawings heavy up and left
+    };
+
+    /**
+     * Get Unicode Character from CEA-608 byte code
+     */
+    var getCharForByte = function getCharForByte(byte) {
+        var charCode = byte;
+        if (specialCea608CharsCodes.hasOwnProperty(byte)) {
+            charCode = specialCea608CharsCodes[byte];
+        }
+        return String.fromCharCode(charCode);
+    };
+
+    var NR_ROWS = 15,
+        NR_COLS = 32;
+    // Tables to look up row from PAC data
+    var rowsLowCh1 = { 0x11: 1, 0x12: 3, 0x15: 5, 0x16: 7, 0x17: 9, 0x10: 11, 0x13: 12, 0x14: 14 };
+    var rowsHighCh1 = { 0x11: 2, 0x12: 4, 0x15: 6, 0x16: 8, 0x17: 10, 0x13: 13, 0x14: 15 };
+    var rowsLowCh2 = { 0x19: 1, 0x1A: 3, 0x1D: 5, 0x1E: 7, 0x1F: 9, 0x18: 11, 0x1B: 12, 0x1C: 14 };
+    var rowsHighCh2 = { 0x19: 2, 0x1A: 4, 0x1D: 6, 0x1E: 8, 0x1F: 10, 0x1B: 13, 0x1C: 15 };
+
+    var backgroundColors = ['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta', 'black', 'transparent'];
+
+    /**
+     * Simple logger class to be able to write with time-stamps and filter on level.
+     */
+    var logger = {
+        verboseFilter: { 'DATA': 3, 'DEBUG': 3, 'INFO': 2, 'WARNING': 2, 'TEXT': 1, 'ERROR': 0 },
+        time: null,
+        verboseLevel: 0, // Only write errors
+        setTime: function setTime(newTime) {
+            this.time = newTime;
+        },
+        log: function log(severity, msg) {
+            var minLevel = this.verboseFilter[severity];
+            if (this.verboseLevel >= minLevel) {
+                console.log(this.time + " [" + severity + "] " + msg);
+            }
+        }
+    };
+
+    var numArrayToHexArray = function numArrayToHexArray(numArray) {
+        var hexArray = [];
+        for (var j = 0; j < numArray.length; j++) {
+            hexArray.push(numArray[j].toString(16));
+        }
+        return hexArray;
+    };
+
+    /**
+     * State of CEA-608 pen or character
+     * @constructor
+     */
+    var PenState = function PenState(foreground, underline, italics, background, flash) {
+        this.foreground = foreground || "white";
+        this.underline = underline || false;
+        this.italics = italics || false;
+        this.background = background || "black";
+        this.flash = flash || false;
+    };
+
+    PenState.prototype = {
+
+        reset: function reset() {
+            this.foreground = "white";
+            this.underline = false;
+            this.italics = false;
+            this.background = "black";
+            this.flash = false;
+        },
+
+        setStyles: function setStyles(styles) {
+            var attribs = ["foreground", "underline", "italics", "background", "flash"];
+            for (var i = 0; i < attribs.length; i++) {
+                var style = attribs[i];
+                if (styles.hasOwnProperty(style)) {
+                    this[style] = styles[style];
+                }
+            }
+        },
+
+        isDefault: function isDefault() {
+            return this.foreground === "white" && !this.underline && !this.italics && this.background === "black" && !this.flash;
+        },
+
+        equals: function equals(other) {
+            return this.foreground === other.foreground && this.underline === other.underline && this.italics === other.italics && this.background === other.background && this.flash === other.flash;
+        },
+
+        copy: function copy(newPenState) {
+            this.foreground = newPenState.foreground;
+            this.underline = newPenState.underline;
+            this.italics = newPenState.italics;
+            this.background = newPenState.background;
+            this.flash = newPenState.flash;
+        },
+
+        toString: function toString() {
+            return "color=" + this.foreground + ", underline=" + this.underline + ", italics=" + this.italics + ", background=" + this.background + ", flash=" + this.flash;
+        }
+    };
+
+    /**
+     * Unicode character with styling and background.
+     * @constructor
+     */
+    var StyledUnicodeChar = function StyledUnicodeChar(uchar, foreground, underline, italics, background, flash) {
+        this.uchar = uchar || ' '; // unicode character
+        this.penState = new PenState(foreground, underline, italics, background, flash);
+    };
+
+    StyledUnicodeChar.prototype = {
+
+        reset: function reset() {
+            this.uchar = ' ';
+            this.penState.reset();
+        },
+
+        setChar: function setChar(uchar, newPenState) {
+            this.uchar = uchar;
+            this.penState.copy(newPenState);
+        },
+
+        setPenState: function setPenState(newPenState) {
+            this.penState.copy(newPenState);
+        },
+
+        equals: function equals(other) {
+            return this.uchar === other.uchar && this.penState.equals(other.penState);
+        },
+
+        copy: function copy(newChar) {
+            this.uchar = newChar.uchar;
+            this.penState.copy(newChar.penState);
+        },
+
+        isEmpty: function isEmpty() {
+            return this.uchar === ' ' && this.penState.isDefault();
+        }
+    };
+
+    /**
+     * CEA-608 row consisting of NR_COLS instances of StyledUnicodeChar.
+     * @constructor
+     */
+    var Row = function Row() {
+        this.chars = [];
+        for (var i = 0; i < NR_COLS; i++) {
+            this.chars.push(new StyledUnicodeChar());
+        }
+        this.pos = 0;
+        this.currPenState = new PenState();
+    };
+
+    Row.prototype = {
+
+        equals: function equals(other) {
+            var equal = true;
+            for (var i = 0; i < NR_COLS; i++) {
+                if (!this.chars[i].equals(other.chars[i])) {
+                    equal = false;
+                    break;
+                }
+            }
+            return equal;
+        },
+
+        copy: function copy(other) {
+            for (var i = 0; i < NR_COLS; i++) {
+                this.chars[i].copy(other.chars[i]);
+            }
+        },
+
+        isEmpty: function isEmpty() {
+            var empty = true;
+            for (var i = 0; i < NR_COLS; i++) {
+                if (!this.chars[i].isEmpty()) {
+                    empty = false;
+                    break;
+                }
+            }
+            return empty;
+        },
+
+        /**
+         *  Set the cursor to a valid column.
+         */
+        setCursor: function setCursor(absPos) {
+            if (this.pos !== absPos) {
+                this.pos = absPos;
+            }
+            if (this.pos < 0) {
+                logger.log("ERROR", "Negative cursor position " + this.pos);
+                this.pos = 0;
+            } else if (this.pos > NR_COLS) {
+                logger.log("ERROR", "Too large cursor position " + this.pos);
+                this.pos = NR_COLS;
+            }
+        },
+
+        /** 
+         * Move the cursor relative to current position.
+         */
+        moveCursor: function moveCursor(relPos) {
+            var newPos = this.pos + relPos;
+            if (relPos > 1) {
+                for (var i = this.pos + 1; i < newPos + 1; i++) {
+                    this.chars[i].setPenState(this.currPenState);
+                }
+            }
+            this.setCursor(newPos);
+        },
+
+        /**
+         * Backspace, move one step back and clear character.
+         */
+        backSpace: function backSpace() {
+            this.moveCursor(-1);
+            this.chars[this.pos].setChar(' ', this.currPenState);
+        },
+
+        insertChar: function insertChar(byte) {
+            if (byte >= 0x90) {
+                //Extended char
+                this.backSpace();
+            }
+            var char = getCharForByte(byte);
+            if (this.pos >= NR_COLS) {
+                logger.log("ERROR", "Cannot insert " + byte.toString(16) + " (" + char + ") at position " + this.pos + ". Skipping it!");
+                return;
+            }
+            this.chars[this.pos].setChar(char, this.currPenState);
+            this.moveCursor(1);
+        },
+
+        clearFromPos: function clearFromPos(startPos) {
+            var i;
+            for (i = startPos; i < NR_COLS; i++) {
+                this.chars[i].reset();
+            }
+        },
+
+        clear: function clear() {
+            this.clearFromPos(0);
+            this.pos = 0;
+            this.currPenState.reset();
+        },
+
+        clearToEndOfRow: function clearToEndOfRow() {
+            this.clearFromPos(this.pos);
+        },
+
+        getTextString: function getTextString() {
+            var chars = [];
+            var empty = true;
+            for (var i = 0; i < NR_COLS; i++) {
+                var char = this.chars[i].uchar;
+                if (char !== " ") {
+                    empty = false;
+                }
+                chars.push(char);
+            }
+            if (empty) {
+                return "";
+            } else {
+                return chars.join("");
+            }
+        },
+
+        setPenStyles: function setPenStyles(styles) {
+            this.currPenState.setStyles(styles);
+            var currChar = this.chars[this.pos];
+            currChar.setPenState(this.currPenState);
+        }
+    };
+
+    /**
+     * Keep a CEA-608 screen of 32x15 styled characters
+     * @constructor
+    */
+    var CaptionScreen = function CaptionScreen() {
+
+        this.rows = [];
+        for (var i = 0; i < NR_ROWS; i++) {
+            this.rows.push(new Row()); // Note that we use zero-based numbering (0-14)
+        }
+        this.currRow = NR_ROWS - 1;
+        this.nrRollUpRows = null;
+        this.reset();
+    };
+
+    CaptionScreen.prototype = {
+
+        reset: function reset() {
+            for (var i = 0; i < NR_ROWS; i++) {
+                this.rows[i].clear();
+            }
+            this.currRow = NR_ROWS - 1;
+        },
+
+        equals: function equals(other) {
+            var equal = true;
+            for (var i = 0; i < NR_ROWS; i++) {
+                if (!this.rows[i].equals(other.rows[i])) {
+                    equal = false;
+                    break;
+                }
+            }
+            return equal;
+        },
+
+        copy: function copy(other) {
+            for (var i = 0; i < NR_ROWS; i++) {
+                this.rows[i].copy(other.rows[i]);
+            }
+        },
+
+        isEmpty: function isEmpty() {
+            var empty = true;
+            for (var i = 0; i < NR_ROWS; i++) {
+                if (!this.rows[i].isEmpty()) {
+                    empty = false;
+                    break;
+                }
+            }
+            return empty;
+        },
+
+        backSpace: function backSpace() {
+            var row = this.rows[this.currRow];
+            row.backSpace();
+        },
+
+        clearToEndOfRow: function clearToEndOfRow() {
+            var row = this.rows[this.currRow];
+            row.clearToEndOfRow();
+        },
+
+        /**
+         * Insert a character (without styling) in the current row.
+         */
+        insertChar: function insertChar(char) {
+            var row = this.rows[this.currRow];
+            row.insertChar(char);
+        },
+
+        setPen: function setPen(styles) {
+            var row = this.rows[this.currRow];
+            row.setPenStyles(styles);
+        },
+
+        moveCursor: function moveCursor(relPos) {
+            var row = this.rows[this.currRow];
+            row.moveCursor(relPos);
+        },
+
+        setCursor: function setCursor(absPos) {
+            logger.log("INFO", "setCursor: " + absPos);
+            var row = this.rows[this.currRow];
+            row.setCursor(absPos);
+        },
+
+        setPAC: function setPAC(pacData) {
+            logger.log("INFO", "pacData = " + JSON.stringify(pacData));
+            var newRow = pacData.row - 1;
+            if (this.nrRollUpRows && newRow < this.nrRollUpRows - 1) {
+                newRow = this.nrRollUpRows - 1;
+            }
+            this.currRow = newRow;
+            var row = this.rows[this.currRow];
+            if (pacData.indent !== null) {
+                var indent = pacData.indent;
+                var prevPos = Math.max(indent - 1, 0);
+                row.setCursor(pacData.indent);
+                pacData.color = row.chars[prevPos].penState.foreground;
+            }
+            var styles = { foreground: pacData.color, underline: pacData.underline, italics: pacData.italics, background: 'black', flash: false };
+            this.setPen(styles);
+        },
+
+        /**
+         * Set background/extra foreground, but first do back_space, and then insert space (backwards compatibility).
+         */
+        setBkgData: function setBkgData(bkgData) {
+
+            logger.log("INFO", "bkgData = " + JSON.stringify(bkgData));
+            this.backSpace();
+            this.setPen(bkgData);
+            this.insertChar(0x20); //Space
+        },
+
+        setRollUpRows: function setRollUpRows(nrRows) {
+            this.nrRollUpRows = nrRows;
+        },
+
+        rollUp: function rollUp() {
+            if (this.nrRollUpRows === null) {
+                logger.log("DEBUG", "roll_up but nrRollUpRows not set yet");
+                return; //Not properly setup
+            }
+            logger.log("TEXT", this.getDisplayText());
+            var topRowIndex = this.currRow + 1 - this.nrRollUpRows;
+            var topRow = this.rows.splice(topRowIndex, 1)[0];
+            topRow.clear();
+            this.rows.splice(this.currRow, 0, topRow);
+            logger.log("INFO", "Rolling up");
+            //logger.log("TEXT", this.get_display_text())
+        },
+
+        /**
+         * Get all non-empty rows with as unicode text. 
+         */
+        getDisplayText: function getDisplayText(asOneRow) {
+            asOneRow = asOneRow || false;
+            var displayText = [];
+            var text = "";
+            var rowNr = -1;
+            for (var i = 0; i < NR_ROWS; i++) {
+                var rowText = this.rows[i].getTextString();
+                if (rowText) {
+                    rowNr = i + 1;
+                    if (asOneRow) {
+                        displayText.push("Row " + rowNr + ': "' + rowText + '"');
+                    } else {
+                        displayText.push(rowText.trim());
+                    }
+                }
+            }
+            if (displayText.length > 0) {
+                if (asOneRow) {
+                    text = "[" + displayText.join(" | ") + "]";
+                } else {
+                    text = displayText.join("\n");
+                }
+            }
+            return text;
+        },
+
+        getTextAndFormat: function getTextAndFormat() {
+            return this.rows;
+        }
+    };
+
+    /**
+     * Handle a CEA-608 channel and send decoded data to outputFilter
+     * @constructor
+     * @param {Number} channelNumber (1 or 2)
+     * @param {CueHandler} outputFilter Output from channel1 newCue(startTime, endTime, captionScreen)
+    */
+    var Cea608Channel = function Cea608Channel(channelNumber, outputFilter) {
+
+        this.chNr = channelNumber;
+        this.outputFilter = outputFilter;
+        this.mode = null;
+        this.verbose = 0;
+        this.displayedMemory = new CaptionScreen();
+        this.nonDisplayedMemory = new CaptionScreen();
+        this.lastOutputScreen = new CaptionScreen();
+        this.currRollUpRow = this.displayedMemory.rows[NR_ROWS - 1];
+        this.writeScreen = this.displayedMemory;
+        this.mode = null;
+        this.cueStartTime = null; // Keeps track of where a cue started.
+    };
+
+    Cea608Channel.prototype = {
+
+        modes: ["MODE_ROLL-UP", "MODE_POP-ON", "MODE_PAINT-ON", "MODE_TEXT"],
+
+        reset: function reset() {
+            this.mode = null;
+            this.displayedMemory.reset();
+            this.nonDisplayedMemory.reset();
+            this.lastOutputScreen.reset();
+            this.currRollUpRow = this.displayedMemory.rows[NR_ROWS - 1];
+            this.writeScreen = this.displayedMemory;
+            this.mode = null;
+            this.cueStartTime = null;
+            this.lastCueEndTime = null;
+        },
+
+        getHandler: function getHandler() {
+            return this.outputFilter;
+        },
+
+        setHandler: function setHandler(newHandler) {
+            this.outputFilter = newHandler;
+        },
+
+        setPAC: function setPAC(pacData) {
+            this.writeScreen.setPAC(pacData);
+        },
+
+        setBkgData: function setBkgData(bkgData) {
+            this.writeScreen.setBkgData(bkgData);
+        },
+
+        setMode: function setMode(newMode) {
+            if (newMode === this.mode) {
+                return;
+            }
+            this.mode = newMode;
+            logger.log("INFO", "MODE=" + newMode);
+            if (this.mode == "MODE_POP-ON") {
+                this.writeScreen = this.nonDisplayedMemory;
+            } else {
+                this.writeScreen = this.displayedMemory;
+                this.writeScreen.reset();
+            }
+            if (this.mode !== "MODE_ROLL-UP") {
+                this.displayedMemory.nrRollUpRows = null;
+                this.nonDisplayedMemory.nrRollUpRows = null;
+            }
+            this.mode = newMode;
+        },
+
+        insertChars: function insertChars(chars) {
+            for (var i = 0; i < chars.length; i++) {
+                this.writeScreen.insertChar(chars[i]);
+            }
+            var screen = this.writeScreen === this.displayedMemory ? "DISP" : "NON_DISP";
+            logger.log("INFO", screen + ": " + this.writeScreen.getDisplayText(true));
+            if (this.mode === "MODE_PAINT-ON" || this.mode === "MODE_ROLL-UP") {
+                logger.log("TEXT", "DISPLAYED: " + this.displayedMemory.getDisplayText(true));
+                this.outputDataUpdate();
+            }
+        },
+
+        cc_RCL: function cc_RCL() {
+            // Resume Caption Loading (switch mode to Pop On)
+            logger.log("INFO", "RCL - Resume Caption Loading");
+            this.setMode("MODE_POP-ON");
+        },
+        cc_BS: function cc_BS() {
+            // BackSpace
+            logger.log("INFO", "BS - BackSpace");
+            if (this.mode === "MODE_TEXT") {
+                return;
+            }
+            this.writeScreen.backSpace();
+            if (this.writeScreen === this.displayedMemory) {
+                this.outputDataUpdate();
+            }
+        },
+        cc_AOF: function cc_AOF() {
+            // Reserved (formerly Alarm Off)
+            return;
+        },
+        cc_AON: function cc_AON() {
+            // Reserved (formerly Alarm On)
+            return;
+        },
+        cc_DER: function cc_DER() {
+            // Delete to End of Row
+            logger.log("INFO", "DER- Delete to End of Row");
+            this.writeScreen.clearToEndOfRow();
+            this.outputDataUpdate();
+        },
+        cc_RU: function cc_RU(nrRows) {
+            //Roll-Up Captions-2,3,or 4 Rows
+            logger.log("INFO", "RU(" + nrRows + ") - Roll Up");
+            this.writeScreen = this.displayedMemory;
+            this.setMode("MODE_ROLL-UP");
+            this.writeScreen.setRollUpRows(nrRows);
+        },
+        cc_FON: function cc_FON() {
+            //Flash On
+            logger.log("INFO", "FON - Flash On");
+            this.writeScreen.setPen({ flash: true });
+        },
+        cc_RDC: function cc_RDC() {
+            // Resume Direct Captioning (switch mode to PaintOn)
+            logger.log("INFO", "RDC - Resume Direct Captioning");
+            this.setMode("MODE_PAINT-ON");
+        },
+        cc_TR: function cc_TR() {
+            // Text Restart in text mode (not supported, however)
+            logger.log("INFO", "TR");
+            this.setMode("MODE_TEXT");
+        },
+        cc_RTD: function cc_RTD() {
+            // Resume Text Display in Text mode (not supported, however)
+            logger.log("INFO", "RTD");
+            this.setMode("MODE_TEXT");
+        },
+        cc_EDM: function cc_EDM() {
+            // Erase Displayed Memory
+            logger.log("INFO", "EDM - Erase Displayed Memory");
+            this.displayedMemory.reset();
+            this.outputDataUpdate();
+        },
+        cc_CR: function cc_CR() {
+            // Carriage Return
+            logger.log("CR - Carriage Return");
+            this.writeScreen.rollUp();
+            this.outputDataUpdate();
+        },
+        cc_ENM: function cc_ENM() {
+            //Erase Non-Displayed Memory
+            logger.log("INFO", "ENM - Erase Non-displayed Memory");
+            this.nonDisplayedMemory.reset();
+        },
+        cc_EOC: function cc_EOC() {
+            //End of Caption (Flip Memories)
+            logger.log("INFO", "EOC - End Of Caption");
+            if (this.mode === "MODE_POP-ON") {
+                var tmp = this.displayedMemory;
+                this.displayedMemory = this.nonDisplayedMemory;
+                this.nonDisplayedMemory = tmp;
+                this.writeScreen = this.nonDisplayedMemory;
+                logger.log("TEXT", "DISP: " + this.displayedMemory.getDisplayText());
+            }
+            this.outputDataUpdate();
+        },
+        cc_TO: function cc_TO(nrCols) {
+            // Tab Offset 1,2, or 3 columns
+            logger.log("INFO", "TO(" + nrCols + ") - Tab Offset");
+            this.writeScreen.moveCursor(nrCols);
+        },
+        cc_MIDROW: function cc_MIDROW(secondByte) {
+            // Parse MIDROW command
+            var styles = { flash: false };
+            styles.underline = secondByte % 2 === 1;
+            styles.italics = secondByte >= 0x2e;
+            if (!styles.italics) {
+                var colorIndex = Math.floor(secondByte / 2) - 0x10;
+                var colors = ["white", "green", "blue", "cyan", "red", "yellow", "magenta"];
+                styles.foreground = colors[colorIndex];
+            } else {
+                styles.foreground = "white";
+            }
+            logger.log("INFO", "MIDROW: " + JSON.stringify(styles));
+            this.writeScreen.setPen(styles);
+        },
+
+        outputDataUpdate: function outputDataUpdate() {
+            var t = logger.time;
+            if (t === null) {
+                return;
+            }
+            if (this.outputFilter) {
+                if (this.outputFilter.updateData) {
+                    this.outputFilter.updateData(t, this.displayedMemory);
+                }
+                if (this.cueStartTime === null && !this.displayedMemory.isEmpty()) {
+                    // Start of a new cue
+                    this.cueStartTime = t;
+                } else {
+                    if (!this.displayedMemory.equals(this.lastOutputScreen)) {
+                        if (this.outputFilter.newCue) {
+                            this.outputFilter.newCue(this.cueStartTime, t, this.lastOutputScreen);
+                        }
+                        this.cueStartTime = this.displayedMemory.isEmpty() ? null : t;
+                    }
+                }
+                this.lastOutputScreen.copy(this.displayedMemory);
+            }
+        },
+
+        cueSplitAtTime: function cueSplitAtTime(t) {
+            if (this.outputFilter) {
+                if (!this.displayedMemory.isEmpty()) {
+                    if (this.outputFilter.newCue) {
+                        this.outputFilter.newCue(this.cueStartTime, t, this.displayedMemory);
+                    }
+                    this.cueStartTime = t;
+                }
+            }
+        }
+    };
+
+    /**
+     * Parse CEA-608 data and send decoded data to out1 and out2.
+     * @constructor
+     * @param {Number} field  CEA-608 field (1 or 2)
+     * @param {CueHandler} out1 Output from channel1 newCue(startTime, endTime, captionScreen)
+     * @param {CueHandler} out2 Output from channel2 newCue(startTime, endTime, captionScreen)
+     */
+    var Cea608Parser = function Cea608Parser(field, out1, out2) {
+        this.field = field || 1;
+        this.outputs = [out1, out2];
+        this.channels = [new Cea608Channel(1, out1), new Cea608Channel(2, out2)];
+        this.currChNr = -1; // Will be 1 or 2
+        this.lastCmdA = null; // First byte of last command
+        this.lastCmdB = null; // Second byte of last command
+        this.bufferedData = [];
+        this.startTime = null;
+        this.lastTime = null;
+        this.dataCounters = { 'padding': 0, 'char': 0, 'cmd': 0, 'other': 0 };
+    };
+
+    Cea608Parser.prototype = {
+
+        getHandler: function getHandler(index) {
+            return this.channels[index].getHandler();
+        },
+
+        setHandler: function setHandler(index, newHandler) {
+            this.channels[index].setHandler(newHandler);
+        },
+
+        /**
+         * Add data for time t in forms of list of bytes (unsigned ints). The bytes are treated as pairs.
+         */
+        addData: function addData(t, byteList) {
+            var cmdFound,
+                a,
+                b,
+                charsFound = false;
+
+            this.lastTime = t;
+            logger.setTime(t);
+
+            for (var i = 0; i < byteList.length; i += 2) {
+                a = byteList[i] & 0x7f;
+                b = byteList[i + 1] & 0x7f;
+
+                if (a >= 0x10 && a <= 0x1f && a === this.lastCmdA && b === this.lastCmdB) {
+                    this.lastCmdA = null;
+                    this.lastCmdB = null;
+                    logger.log("DEBUG", "Repeated command (" + numArrayToHexArray([a, b]) + ") is dropped");
+                    continue; // Repeated commands are dropped (once)
+                }
+
+                if (a === 0 && b === 0) {
+                    this.dataCounters.padding += 2;
+                    continue;
+                } else {
+                    logger.log("DATA", "[" + numArrayToHexArray([byteList[i], byteList[i + 1]]) + "] -> (" + numArrayToHexArray([a, b]) + ")");
+                }
+                cmdFound = this.parseCmd(a, b);
+                if (!cmdFound) {
+                    cmdFound = this.parseMidrow(a, b);
+                }
+                if (!cmdFound) {
+                    cmdFound = this.parsePAC(a, b);
+                }
+                if (!cmdFound) {
+                    cmdFound = this.parseBackgroundAttributes(a, b);
+                }
+                if (!cmdFound) {
+                    charsFound = this.parseChars(a, b);
+                    if (charsFound) {
+                        if (this.currChNr && this.currChNr >= 0) {
+                            var channel = this.channels[this.currChNr - 1];
+                            channel.insertChars(charsFound);
+                        } else {
+                            logger.log("WARNING", "No channel found yet. TEXT-MODE?");
+                        }
+                    }
+                }
+                if (cmdFound) {
+                    this.dataCounters.cmd += 2;
+                } else if (charsFound) {
+                    this.dataCounters.char += 2;
+                } else {
+                    this.dataCounters.other += 2;
+                    logger.log("WARNING", "Couldn't parse cleaned data " + numArrayToHexArray([a, b]) + " orig: " + numArrayToHexArray([byteList[i], byteList[i + 1]]));
+                }
+            }
+        },
+
+        /**
+         * Parse Command.
+         * @returns {Boolean} Tells if a command was found
+         */
+        parseCmd: function parseCmd(a, b) {
+            var chNr = null;
+
+            var cond1 = (a === 0x14 || a === 0x15 || a === 0x1C || a === 0x1D) && 0x20 <= b && b <= 0x2F;
+            var cond2 = (a === 0x17 || a === 0x1F) && 0x21 <= b && b <= 0x23;
+            if (!(cond1 || cond2)) {
+                return false;
+            }
+
+            if (a === 0x14 || a === 0x15 || a === 0x17) {
+                chNr = 1;
+            } else {
+                chNr = 2; // (a === 0x1C || a === 0x1D || a=== 0x1f)
+            }
+
+            var channel = this.channels[chNr - 1];
+
+            if (a === 0x14 || a === 0x15 || a === 0x1C || a === 0x1D) {
+                if (b === 0x20) {
+                    channel.cc_RCL();
+                } else if (b === 0x21) {
+                    channel.cc_BS();
+                } else if (b === 0x22) {
+                    channel.cc_AOF();
+                } else if (b === 0x23) {
+                    channel.cc_AON();
+                } else if (b === 0x24) {
+                    channel.cc_DER();
+                } else if (b === 0x25) {
+                    channel.cc_RU(2);
+                } else if (b === 0x26) {
+                    channel.cc_RU(3);
+                } else if (b === 0x27) {
+                    channel.cc_RU(4);
+                } else if (b === 0x28) {
+                    channel.cc_FON();
+                } else if (b === 0x29) {
+                    channel.cc_RDC();
+                } else if (b === 0x2A) {
+                    channel.cc_TR();
+                } else if (b === 0x2B) {
+                    channel.cc_RTD();
+                } else if (b === 0x2C) {
+                    channel.cc_EDM();
+                } else if (b === 0x2D) {
+                    channel.cc_CR();
+                } else if (b === 0x2E) {
+                    channel.cc_ENM();
+                } else if (b === 0x2F) {
+                    channel.cc_EOC();
+                }
+            } else {
+                //a == 0x17 || a == 0x1F
+                channel.cc_TO(b - 0x20);
+            }
+            this.lastCmdA = a;
+            this.lastCmdB = b;
+            this.currChNr = chNr;
+            return true;
+        },
+
+        /**
+         * Parse midrow styling command
+         * @returns {Boolean}
+         */
+        parseMidrow: function parseMidrow(a, b) {
+            var chNr = null;
+
+            if ((a === 0x11 || a === 0x19) && 0x20 <= b && b <= 0x2f) {
+                if (a === 0x11) {
+                    chNr = 1;
+                } else {
+                    chNr = 2;
+                }
+                if (chNr !== this.currChNr) {
+                    logger.log("ERROR", "Mismatch channel in midrow parsing");
+                    return false;
+                }
+                var channel = this.channels[chNr - 1];
+                // cea608 spec says midrow codes should inject a space
+                channel.insertChars([0x20]);
+                channel.cc_MIDROW(b);
+                logger.log("DEBUG", "MIDROW (" + numArrayToHexArray([a, b]) + ")");
+                this.lastCmdA = a;
+                this.lastCmdB = b;
+                return true;
+            }
+            return false;
+        },
+        /**
+         * Parse Preable Access Codes (Table 53).
+         * @returns {Boolean} Tells if PAC found
+         */
+        parsePAC: function parsePAC(a, b) {
+
+            var chNr = null;
+            var row = null;
+
+            var case1 = (0x11 <= a && a <= 0x17 || 0x19 <= a && a <= 0x1F) && 0x40 <= b && b <= 0x7F;
+            var case2 = (a === 0x10 || a === 0x18) && 0x40 <= b && b <= 0x5F;
+            if (!(case1 || case2)) {
+                return false;
+            }
+
+            chNr = a <= 0x17 ? 1 : 2;
+
+            if (0x40 <= b && b <= 0x5F) {
+                row = chNr === 1 ? rowsLowCh1[a] : rowsLowCh2[a];
+            } else {
+                // 0x60 <= b <= 0x7F
+                row = chNr === 1 ? rowsHighCh1[a] : rowsHighCh2[a];
+            }
+            var pacData = this.interpretPAC(row, b);
+            var channel = this.channels[chNr - 1];
+            channel.setPAC(pacData);
+            this.lastCmdA = a;
+            this.lastCmdB = b;
+            this.currChNr = chNr;
+            return true;
+        },
+
+        /**
+         * Interpret the second byte of the pac, and return the information.
+         * @returns {Object} pacData with style parameters.
+         */
+        interpretPAC: function interpretPAC(row, byte) {
+            var pacIndex = byte;
+            var pacData = { color: null, italics: false, indent: null, underline: false, row: row };
+
+            if (byte > 0x5F) {
+                pacIndex = byte - 0x60;
+            } else {
+                pacIndex = byte - 0x40;
+            }
+            pacData.underline = (pacIndex & 1) === 1;
+            if (pacIndex <= 0xd) {
+                pacData.color = ['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta', 'white'][Math.floor(pacIndex / 2)];
+            } else if (pacIndex <= 0xf) {
+                pacData.italics = true;
+                pacData.color = 'white';
+            } else {
+                pacData.indent = Math.floor((pacIndex - 0x10) / 2) * 4;
+            }
+            return pacData; // Note that row has zero offset. The spec uses 1.
+        },
+
+        /**
+         * Parse characters.
+         * @returns An array with 1 to 2 codes corresponding to chars, if found. null otherwise.
+         */
+        parseChars: function parseChars(a, b) {
+
+            var channelNr = null,
+                charCodes = null,
+                charCode1 = null,
+                charCode2 = null;
+
+            if (a >= 0x19) {
+                channelNr = 2;
+                charCode1 = a - 8;
+            } else {
+                channelNr = 1;
+                charCode1 = a;
+            }
+            if (0x11 <= charCode1 && charCode1 <= 0x13) {
+                // Special character
+                var oneCode = b;
+                if (charCode1 === 0x11) {
+                    oneCode = b + 0x50;
+                } else if (charCode1 === 0x12) {
+                    oneCode = b + 0x70;
+                } else {
+                    oneCode = b + 0x90;
+                }
+                logger.log("INFO", "Special char '" + getCharForByte(oneCode) + "' in channel " + channelNr);
+                charCodes = [oneCode];
+                this.lastCmdA = a;
+                this.lastCmdB = b;
+            } else if (0x20 <= a && a <= 0x7f) {
+                charCodes = b === 0 ? [a] : [a, b];
+                this.lastCmdA = null;
+                this.lastCmdB = null;
+            }
+            if (charCodes) {
+                var hexCodes = numArrayToHexArray(charCodes);
+                logger.log("DEBUG", "Char codes =  " + hexCodes.join(","));
+            }
+            return charCodes;
+        },
+
+        /**
+        * Parse extended background attributes as well as new foreground color black.
+        * @returns{Boolean} Tells if background attributes are found
+        */
+        parseBackgroundAttributes: function parseBackgroundAttributes(a, b) {
+            var bkgData, index, chNr, channel;
+
+            var case1 = (a === 0x10 || a === 0x18) && 0x20 <= b && b <= 0x2f;
+            var case2 = (a === 0x17 || a === 0x1f) && 0x2d <= b && b <= 0x2f;
+            if (!(case1 || case2)) {
+                return false;
+            }
+            bkgData = {};
+            if (a === 0x10 || a === 0x18) {
+                index = Math.floor((b - 0x20) / 2);
+                bkgData.background = backgroundColors[index];
+                if (b % 2 === 1) {
+                    bkgData.background = bkgData.background + "_semi";
+                }
+            } else if (b === 0x2d) {
+                bkgData.background = "transparent";
+            } else {
+                bkgData.foreground = "black";
+                if (b === 0x2f) {
+                    bkgData.underline = true;
+                }
+            }
+            chNr = a < 0x18 ? 1 : 2;
+            channel = this.channels[chNr - 1];
+            channel.setBkgData(bkgData);
+            this.lastCmdA = a;
+            this.lastCmdB = b;
+            return true;
+        },
+
+        /**
+         * Reset state of parser and its channels.
+         */
+        reset: function reset() {
+            for (var i = 0; i < this.channels.length; i++) {
+                if (this.channels[i]) {
+                    this.channels[i].reset();
+                }
+            }
+            this.lastCmdA = null;
+            this.lastCmdB = null;
+        },
+
+        /**
+         * Trigger the generation of a cue, and the start of a new one if displayScreens are not empty.
+         */
+        cueSplitAtTime: function cueSplitAtTime(t) {
+            for (var i = 0; i < this.channels.length; i++) {
+                if (this.channels[i]) {
+                    this.channels[i].cueSplitAtTime(t);
+                }
+            }
+        }
+    };
+
+    /**
+     * Find ranges corresponding to SEA CEA-608 NALUS in sizeprepended NALU array.
+     * @param {raw} dataView of binary data
+     * @param {startPos} start position in raw
+     * @param {size} total size of data in raw to consider
+     * @returns 
+     */
+    var findCea608Nalus = function findCea608Nalus(raw, startPos, size) {
+        var nalSize = 0,
+            cursor = startPos,
+            nalType = 0,
+            cea608NaluRanges = [],
+
+        // Check SEI data according to ANSI-SCTE 128
+        isCEA608SEI = function isCEA608SEI(payloadType, payloadSize, raw, pos) {
+            if (payloadType !== 4 || payloadSize < 8) {
+                return null;
+            }
+            var countryCode = raw.getUint8(pos);
+            var providerCode = raw.getUint16(pos + 1);
+            var userIdentifier = raw.getUint32(pos + 3);
+            var userDataTypeCode = raw.getUint8(pos + 7);
+            return countryCode == 0xB5 && providerCode == 0x31 && userIdentifier == 0x47413934 && userDataTypeCode == 0x3;
+        };
+        while (cursor < startPos + size) {
+            nalSize = raw.getUint32(cursor);
+            nalType = raw.getUint8(cursor + 4) & 0x1F;
+            //console.log(time + "  NAL " + nalType);
+            if (nalType === 6) {
+                // SEI NAL Unit. The NAL header is the first byte
+                //console.log("SEI NALU of size " + nalSize + " at time " + time);
+                var pos = cursor + 5;
+                var payloadType = -1;
+                while (pos < cursor + 4 + nalSize - 1) {
+                    // The last byte should be rbsp_trailing_bits
+                    payloadType = 0;
+                    var b = 0xFF;
+                    while (b === 0xFF) {
+                        b = raw.getUint8(pos);
+                        payloadType += b;
+                        pos++;
+                    }
+                    var payloadSize = 0;
+                    b = 0xFF;
+                    while (b === 0xFF) {
+                        b = raw.getUint8(pos);
+                        payloadSize += b;
+                        pos++;
+                    }
+                    if (isCEA608SEI(payloadType, payloadSize, raw, pos)) {
+                        //console.log("CEA608 SEI " + time + " " + payloadSize);
+                        cea608NaluRanges.push([pos, payloadSize]);
+                    }
+                    pos += payloadSize;
+                }
+            }
+            cursor += nalSize + 4;
+        }
+        return cea608NaluRanges;
+    };
+
+    var extractCea608DataFromRange = function extractCea608DataFromRange(raw, cea608Range) {
+        var pos = cea608Range[0];
+        var fieldData = [[], []];
+
+        pos += 8; // Skip the identifier up to userDataTypeCode
+        var ccCount = raw.getUint8(pos) & 0x1f;
+        pos += 2; // Advance 1 and skip reserved byte
+
+        for (var i = 0; i < ccCount; i++) {
+            var byte = raw.getUint8(pos);
+            var ccValid = byte & 0x4;
+            var ccType = byte & 0x3;
+            pos++;
+            var ccData1 = raw.getUint8(pos); // Keep parity bit
+            pos++;
+            var ccData2 = raw.getUint8(pos); // Keep parity bit
+            pos++;
+            if (ccValid && (ccData1 & 0x7f) + (ccData2 & 0x7f) !== 0) {
+                //Check validity and non-empty data
+                if (ccType === 0) {
+                    fieldData[0].push(ccData1);
+                    fieldData[0].push(ccData2);
+                } else if (ccType === 1) {
+                    fieldData[1].push(ccData1);
+                    fieldData[1].push(ccData2);
+                }
+            }
+        }
+        return fieldData;
+    };
+
+    exports.logger = logger;
+    exports.PenState = PenState;
+    exports.CaptionScreen = CaptionScreen;
+    exports.Cea608Parser = Cea608Parser;
+    exports.findCea608Nalus = findCea608Nalus;
+    exports.extractCea608DataFromRange = extractCea608DataFromRange;
+})(typeof exports === 'undefined' ? undefined.cea608parser = {} : exports);
+
+},{}],3:[function(_dereq_,module,exports){
+/*
+ Copyright 2011-2013 Abdulla Abdurakhmanov
+ Original sources are available at https://code.google.com/p/x2js/
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+/*
+  Further modified for dashjs to:
+  - keep track of children nodes in order in attribute __children.
+  - add type conversion matchers
+  - re-add ignoreRoot
+  - allow zero-length attributePrefix
+  - don't add white-space text nodes
+  - remove explicit RequireJS support
+*/
+
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+    value: true
+});
+function X2JS(config) {
+    'use strict';
+
+    var VERSION = "1.2.0";
+
+    config = config || {};
+    initConfigDefaults();
+    initRequiredPolyfills();
+
+    function initConfigDefaults() {
+        if (config.escapeMode === undefined) {
+            config.escapeMode = true;
+        }
+
+        if (config.attributePrefix === undefined) {
+            config.attributePrefix = "_";
+        }
+
+        config.arrayAccessForm = config.arrayAccessForm || "none";
+        config.emptyNodeForm = config.emptyNodeForm || "text";
+
+        if (config.enableToStringFunc === undefined) {
+            config.enableToStringFunc = true;
+        }
+        config.arrayAccessFormPaths = config.arrayAccessFormPaths || [];
+        if (config.skipEmptyTextNodesForObj === undefined) {
+            config.skipEmptyTextNodesForObj = true;
+        }
+        if (config.stripWhitespaces === undefined) {
+            config.stripWhitespaces = true;
+        }
+        config.datetimeAccessFormPaths = config.datetimeAccessFormPaths || [];
+
+        if (config.useDoubleQuotes === undefined) {
+            config.useDoubleQuotes = false;
+        }
+
+        config.xmlElementsFilter = config.xmlElementsFilter || [];
+        config.jsonPropertiesFilter = config.jsonPropertiesFilter || [];
+
+        if (config.keepCData === undefined) {
+            config.keepCData = false;
+        }
+
+        if (config.ignoreRoot === undefined) {
+            config.ignoreRoot = false;
+        }
+    }
+
+    var DOMNodeTypes = {
+        ELEMENT_NODE: 1,
+        TEXT_NODE: 3,
+        CDATA_SECTION_NODE: 4,
+        COMMENT_NODE: 8,
+        DOCUMENT_NODE: 9
+    };
+
+    function initRequiredPolyfills() {}
+
+    function getNodeLocalName(node) {
+        var nodeLocalName = node.localName;
+        if (nodeLocalName == null) // Yeah, this is IE!!
+            nodeLocalName = node.baseName;
+        if (nodeLocalName == null || nodeLocalName == "") // =="" is IE too
+            nodeLocalName = node.nodeName;
+        return nodeLocalName;
+    }
+
+    function getNodePrefix(node) {
+        return node.prefix;
+    }
+
+    function escapeXmlChars(str) {
+        if (typeof str == "string") return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;');else return str;
+    }
+
+    function unescapeXmlChars(str) {
+        return str.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, '&');
+    }
+
+    function checkInStdFiltersArrayForm(stdFiltersArrayForm, obj, name, path) {
+        var idx = 0;
+        for (; idx < stdFiltersArrayForm.length; idx++) {
+            var filterPath = stdFiltersArrayForm[idx];
+            if (typeof filterPath === "string") {
+                if (filterPath == path) break;
+            } else if (filterPath instanceof RegExp) {
+                if (filterPath.test(path)) break;
+            } else if (typeof filterPath === "function") {
+                if (filterPath(obj, name, path)) break;
+            }
+        }
+        return idx != stdFiltersArrayForm.length;
+    }
+
+    function toArrayAccessForm(obj, childName, path) {
+        switch (config.arrayAccessForm) {
+            case "property":
+                if (!(obj[childName] instanceof Array)) obj[childName + "_asArray"] = [obj[childName]];else obj[childName + "_asArray"] = obj[childName];
+                break;
+            /*case "none":
+                break;*/
+        }
+
+        if (!(obj[childName] instanceof Array) && config.arrayAccessFormPaths.length > 0) {
+            if (checkInStdFiltersArrayForm(config.arrayAccessFormPaths, obj, childName, path)) {
+                obj[childName] = [obj[childName]];
+            }
+        }
+    }
+
+    function fromXmlDateTime(prop) {
+        // Implementation based up on http://stackoverflow.com/questions/8178598/xml-datetime-to-javascript-date-object
+        // Improved to support full spec and optional parts
+        var bits = prop.split(/[-T:+Z]/g);
+
+        var d = new Date(bits[0], bits[1] - 1, bits[2]);
+        var secondBits = bits[5].split("\.");
+        d.setHours(bits[3], bits[4], secondBits[0]);
+        if (secondBits.length > 1) d.setMilliseconds(secondBits[1]);
+
+        // Get supplied time zone offset in minutes
+        if (bits[6] && bits[7]) {
+            var offsetMinutes = bits[6] * 60 + Number(bits[7]);
+            var sign = /\d\d-\d\d:\d\d$/.test(prop) ? '-' : '+';
+
+            // Apply the sign
+            offsetMinutes = 0 + (sign == '-' ? -1 * offsetMinutes : offsetMinutes);
+
+            // Apply offset and local timezone
+            d.setMinutes(d.getMinutes() - offsetMinutes - d.getTimezoneOffset());
+        } else if (prop.indexOf("Z", prop.length - 1) !== -1) {
+            d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds()));
+        }
+
+        // d is now a local time equivalent to the supplied time
+        return d;
+    }
+
+    function checkFromXmlDateTimePaths(value, childName, fullPath) {
+        if (config.datetimeAccessFormPaths.length > 0) {
+            var path = fullPath.split("\.#")[0];
+            if (checkInStdFiltersArrayForm(config.datetimeAccessFormPaths, value, childName, path)) {
+                return fromXmlDateTime(value);
+            } else return value;
+        } else return value;
+    }
+
+    function checkXmlElementsFilter(obj, childType, childName, childPath) {
+        if (childType == DOMNodeTypes.ELEMENT_NODE && config.xmlElementsFilter.length > 0) {
+            return checkInStdFiltersArrayForm(config.xmlElementsFilter, obj, childName, childPath);
+        } else return true;
+    }
+
+    function parseDOMChildren(node, path) {
+        if (node.nodeType == DOMNodeTypes.DOCUMENT_NODE) {
+            var result = new Object();
+            var nodeChildren = node.childNodes;
+            // Alternative for firstElementChild which is not supported in some environments
+            for (var cidx = 0; cidx < nodeChildren.length; cidx++) {
+                var child = nodeChildren[cidx];
+                if (child.nodeType == DOMNodeTypes.ELEMENT_NODE) {
+                    if (config.ignoreRoot) {
+                        result = parseDOMChildren(child);
+                    } else {
+                        result = {};
+                        var childName = getNodeLocalName(child);
+                        result[childName] = parseDOMChildren(child);
+                    }
+                }
+            }
+            return result;
+        } else if (node.nodeType == DOMNodeTypes.ELEMENT_NODE) {
+            var result = new Object();
+            result.__cnt = 0;
+
+            var children = [];
+            var nodeChildren = node.childNodes;
+
+            // Children nodes
+            for (var cidx = 0; cidx < nodeChildren.length; cidx++) {
+                var child = nodeChildren[cidx];
+                var childName = getNodeLocalName(child);
+
+                if (child.nodeType != DOMNodeTypes.COMMENT_NODE) {
+                    var childPath = path + "." + childName;
+                    if (checkXmlElementsFilter(result, child.nodeType, childName, childPath)) {
+                        result.__cnt++;
+                        if (result[childName] == null) {
+                            var c = parseDOMChildren(child, childPath);
+                            if (childName != "#text" || /[^\s]/.test(c)) {
+                                var o = {};
+                                o[childName] = c;
+                                children.push(o);
+                            }
+                            result[childName] = c;
+                            toArrayAccessForm(result, childName, childPath);
+                        } else {
+                            if (result[childName] != null) {
+                                if (!(result[childName] instanceof Array)) {
+                                    result[childName] = [result[childName]];
+                                    toArrayAccessForm(result, childName, childPath);
+                                }
+                            }
+
+                            var c = parseDOMChildren(child, childPath);
+                            if (childName != "#text" || /[^\s]/.test(c)) {
+                                // Don't add white-space text nodes
+                                var o = {};
+                                o[childName] = c;
+                                children.push(o);
+                            }
+                            result[childName][result[childName].length] = c;
+                        }
+                    }
+                }
+            }
+
+            result.__children = children;
+
+            // Attributes
+            var nodeLocalName = getNodeLocalName(node);
+            for (var aidx = 0; aidx < node.attributes.length; aidx++) {
+                var attr = node.attributes[aidx];
+                result.__cnt++;
+
+                var value2 = attr.value;
+                for (var m = 0, ml = config.matchers.length; m < ml; m++) {
+                    var matchobj = config.matchers[m];
+                    if (matchobj.test(attr, nodeLocalName)) value2 = matchobj.converter(attr.value);
+                }
+
+                result[config.attributePrefix + attr.name] = value2;
+            }
+
+            // Node namespace prefix
+            var nodePrefix = getNodePrefix(node);
+            if (nodePrefix != null && nodePrefix != "") {
+                result.__cnt++;
+                result.__prefix = nodePrefix;
+            }
+
+            if (result["#text"] != null) {
+                result.__text = result["#text"];
+                if (result.__text instanceof Array) {
+                    result.__text = result.__text.join("\n");
+                }
+                //if(config.escapeMode)
+                //     result.__text = unescapeXmlChars(result.__text);
+                if (config.stripWhitespaces) result.__text = result.__text.trim();
+                delete result["#text"];
+                if (config.arrayAccessForm == "property") delete result["#text_asArray"];
+                result.__text = checkFromXmlDateTimePaths(result.__text, childName, path + "." + childName);
+            }
+            if (result["#cdata-section"] != null) {
+                result.__cdata = result["#cdata-section"];
+                delete result["#cdata-section"];
+                if (config.arrayAccessForm == "property") delete result["#cdata-section_asArray"];
+            }
+
+            if (result.__cnt == 0 && config.emptyNodeForm == "text") {
+                result = '';
+            } else if (result.__cnt == 1 && result.__text != null) {
+                result = result.__text;
+            } else if (result.__cnt == 1 && result.__cdata != null && !config.keepCData) {
+                result = result.__cdata;
+            } else if (result.__cnt > 1 && result.__text != null && config.skipEmptyTextNodesForObj) {
+                if (config.stripWhitespaces && result.__text == "" || result.__text.trim() == "") {
+                    delete result.__text;
+                }
+            }
+            delete result.__cnt;
+
+            if (config.enableToStringFunc && (result.__text != null || result.__cdata != null)) {
+                result.toString = function () {
+                    return (this.__text != null ? this.__text : '') + (this.__cdata != null ? this.__cdata : '');
+                };
+            }
+
+            return result;
+        } else if (node.nodeType == DOMNodeTypes.TEXT_NODE || node.nodeType == DOMNodeTypes.CDATA_SECTION_NODE) {
+            return node.nodeValue;
+        }
+    }
+
+    function startTag(jsonObj, element, attrList, closed) {
+        var resultStr = "<" + (jsonObj != null && jsonObj.__prefix != null ? jsonObj.__prefix + ":" : "") + element;
+        if (attrList != null) {
+            for (var aidx = 0; aidx < attrList.length; aidx++) {
+                var attrName = attrList[aidx];
+                var attrVal = jsonObj[attrName];
+                if (config.escapeMode) attrVal = escapeXmlChars(attrVal);
+                resultStr += " " + attrName.substr(config.attributePrefix.length) + "=";
+                if (config.useDoubleQuotes) resultStr += '"' + attrVal + '"';else resultStr += "'" + attrVal + "'";
+            }
+        }
+        if (!closed) resultStr += ">";else resultStr += "/>";
+        return resultStr;
+    }
+
+    function endTag(jsonObj, elementName) {
+        return "</" + (jsonObj.__prefix != null ? jsonObj.__prefix + ":" : "") + elementName + ">";
+    }
+
+    function endsWith(str, suffix) {
+        return str.indexOf(suffix, str.length - suffix.length) !== -1;
+    }
+
+    function jsonXmlSpecialElem(jsonObj, jsonObjField) {
+        if (config.arrayAccessForm == "property" && endsWith(jsonObjField.toString(), "_asArray") || jsonObjField.toString().indexOf(config.attributePrefix) == 0 || jsonObjField.toString().indexOf("__") == 0 || jsonObj[jsonObjField] instanceof Function) return true;else return false;
+    }
+
+    function jsonXmlElemCount(jsonObj) {
+        var elementsCnt = 0;
+        if (jsonObj instanceof Object) {
+            for (var it in jsonObj) {
+                if (jsonXmlSpecialElem(jsonObj, it)) continue;
+                elementsCnt++;
+            }
+        }
+        return elementsCnt;
+    }
+
+    function checkJsonObjPropertiesFilter(jsonObj, propertyName, jsonObjPath) {
+        return config.jsonPropertiesFilter.length == 0 || jsonObjPath == "" || checkInStdFiltersArrayForm(config.jsonPropertiesFilter, jsonObj, propertyName, jsonObjPath);
+    }
+
+    function parseJSONAttributes(jsonObj) {
+        var attrList = [];
+        if (jsonObj instanceof Object) {
+            for (var ait in jsonObj) {
+                if (ait.toString().indexOf("__") == -1 && ait.toString().indexOf(config.attributePrefix) == 0) {
+                    attrList.push(ait);
+                }
+            }
+        }
+        return attrList;
+    }
+
+    function parseJSONTextAttrs(jsonTxtObj) {
+        var result = "";
+
+        if (jsonTxtObj.__cdata != null) {
+            result += "<![CDATA[" + jsonTxtObj.__cdata + "]]>";
+        }
+
+        if (jsonTxtObj.__text != null) {
+            if (config.escapeMode) result += escapeXmlChars(jsonTxtObj.__text);else result += jsonTxtObj.__text;
+        }
+        return result;
+    }
+
+    function parseJSONTextObject(jsonTxtObj) {
+        var result = "";
+
+        if (jsonTxtObj instanceof Object) {
+            result += parseJSONTextAttrs(jsonTxtObj);
+        } else if (jsonTxtObj != null) {
+            if (config.escapeMode) result += escapeXmlChars(jsonTxtObj);else result += jsonTxtObj;
+        }
+
+        return result;
+    }
+
+    function getJsonPropertyPath(jsonObjPath, jsonPropName) {
+        if (jsonObjPath === "") {
+            return jsonPropName;
+        } else return jsonObjPath + "." + jsonPropName;
+    }
+
+    function parseJSONArray(jsonArrRoot, jsonArrObj, attrList, jsonObjPath) {
+        var result = "";
+        if (jsonArrRoot.length == 0) {
+            result += startTag(jsonArrRoot, jsonArrObj, attrList, true);
+        } else {
+            for (var arIdx = 0; arIdx < jsonArrRoot.length; arIdx++) {
+                result += startTag(jsonArrRoot[arIdx], jsonArrObj, parseJSONAttributes(jsonArrRoot[arIdx]), false);
+                result += parseJSONObject(jsonArrRoot[arIdx], getJsonPropertyPath(jsonObjPath, jsonArrObj));
+                result += endTag(jsonArrRoot[arIdx], jsonArrObj);
+            }
+        }
+        return result;
+    }
+
+    function parseJSONObject(jsonObj, jsonObjPath) {
+        var result = "";
+
+        var elementsCnt = jsonXmlElemCount(jsonObj);
+
+        if (elementsCnt > 0) {
+            for (var it in jsonObj) {
+
+                if (jsonXmlSpecialElem(jsonObj, it) || jsonObjPath != "" && !checkJsonObjPropertiesFilter(jsonObj, it, getJsonPropertyPath(jsonObjPath, it))) continue;
+
+                var subObj = jsonObj[it];
+
+                var attrList = parseJSONAttributes(subObj);
+
+                if (subObj == null || subObj == undefined) {
+                    result += startTag(subObj, it, attrList, true);
+                } else if (subObj instanceof Object) {
+
+                    if (subObj instanceof Array) {
+                        result += parseJSONArray(subObj, it, attrList, jsonObjPath);
+                    } else if (subObj instanceof Date) {
+                        result += startTag(subObj, it, attrList, false);
+                        result += subObj.toISOString();
+                        result += endTag(subObj, it);
+                    } else {
+                        var subObjElementsCnt = jsonXmlElemCount(subObj);
+                        if (subObjElementsCnt > 0 || subObj.__text != null || subObj.__cdata != null) {
+                            result += startTag(subObj, it, attrList, false);
+                            result += parseJSONObject(subObj, getJsonPropertyPath(jsonObjPath, it));
+                            result += endTag(subObj, it);
+                        } else {
+                            result += startTag(subObj, it, attrList, true);
+                        }
+                    }
+                } else {
+                    result += startTag(subObj, it, attrList, false);
+                    result += parseJSONTextObject(subObj);
+                    result += endTag(subObj, it);
+                }
+            }
+        }
+        result += parseJSONTextObject(jsonObj);
+
+        return result;
+    }
+
+    this.parseXmlString = function (xmlDocStr) {
+        var isIEParser = window.ActiveXObject || "ActiveXObject" in window;
+        if (xmlDocStr === undefined) {
+            return null;
+        }
+        var xmlDoc;
+        if (window.DOMParser) {
+            var parser = new window.DOMParser();
+            var parsererrorNS = null;
+            try {
+                xmlDoc = parser.parseFromString(xmlDocStr, "text/xml");
+                if (xmlDoc.getElementsByTagNameNS("*", "parsererror").length > 0) {
+                    xmlDoc = null;
+                }
+            } catch (err) {
+                xmlDoc = null;
+            }
+        } else {
+            // IE :(
+            if (xmlDocStr.indexOf("<?") == 0) {
+                xmlDocStr = xmlDocStr.substr(xmlDocStr.indexOf("?>") + 2);
+            }
+            xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
+            xmlDoc.async = "false";
+            xmlDoc.loadXML(xmlDocStr);
+        }
+        return xmlDoc;
+    };
+
+    this.asArray = function (prop) {
+        if (prop === undefined || prop == null) return [];else if (prop instanceof Array) return prop;else return [prop];
+    };
+
+    this.toXmlDateTime = function (dt) {
+        if (dt instanceof Date) return dt.toISOString();else if (typeof dt === 'number') return new Date(dt).toISOString();else return null;
+    };
+
+    this.asDateTime = function (prop) {
+        if (typeof prop == "string") {
+            return fromXmlDateTime(prop);
+        } else return prop;
+    };
+
+    this.xml2json = function (xmlDoc) {
+        return parseDOMChildren(xmlDoc);
+    };
+
+    this.xml_str2json = function (xmlDocStr) {
+        var xmlDoc = this.parseXmlString(xmlDocStr);
+        if (xmlDoc != null) return this.xml2json(xmlDoc);else return null;
+    };
+
+    this.json2xml_str = function (jsonObj) {
+        return parseJSONObject(jsonObj, "");
+    };
+
+    this.json2xml = function (jsonObj) {
+        var xmlDocStr = this.json2xml_str(jsonObj);
+        return this.parseXmlString(xmlDocStr);
+    };
+
+    this.getVersion = function () {
+        return VERSION;
+    };
+}
+
+exports["default"] = X2JS;
+module.exports = exports["default"];
+
+},{}],4:[function(_dereq_,module,exports){
+/**
+ * The copyright in this software is being made available under the BSD License,
+ * included below. This software may be subject to other third party and contributor
+ * rights, including patent rights, and no such rights are granted under this license.
+ *
+ * Copyright (c) 2013, Dash Industry Forum.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *  * Redistributions of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation and/or
+ *  other materials provided with the distribution.
+ *  * Neither the name of Dash Industry Forum nor the names of its
+ *  contributors may be used to endorse or promote products derived from this software
+ *  without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY
+ *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ *  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ *  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ *  POSSIBILITY OF SUCH DAMAGE.
+ */
+
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+  value: true
+});
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _index_mediaplayerOnly = _dereq_(5);
+
+var _srcStreamingMetricsMetricsReporting = _dereq_(125);
+
+var _srcStreamingMetricsMetricsReporting2 = _interopRequireDefault(_srcStreamingMetricsMetricsReporting);
+
+var _srcStreamingProtectionProtection = _dereq_(162);
+
+var _srcStreamingProtectionProtection2 = _interopRequireDefault(_srcStreamingProtectionProtection);
+
+var _srcStreamingMediaPlayerFactory = _dereq_(102);
+
+var _srcStreamingMediaPlayerFactory2 = _interopRequireDefault(_srcStreamingMediaPlayerFactory);
+
+var _srcCoreDebug = _dereq_(45);
+
+var _srcCoreDebug2 = _interopRequireDefault(_srcCoreDebug);
+
+dashjs.Protection = _srcStreamingProtectionProtection2['default'];
+dashjs.MetricsReporting = _srcStreamingMetricsMetricsReporting2['default'];
+dashjs.MediaPlayerFactory = _srcStreamingMediaPlayerFactory2['default'];
+dashjs.Debug = _srcCoreDebug2['default'];
+
+exports['default'] = dashjs;
+exports.MediaPlayer = _index_mediaplayerOnly.MediaPlayer;
+exports.Protection = _srcStreamingProtectionProtection2['default'];
+exports.MetricsReporting = _srcStreamingMetricsMetricsReporting2['default'];
+exports.MediaPlayerFactory = _srcStreamingMediaPlayerFactory2['default'];
+exports.Debug = _srcCoreDebug2['default'];
+
+},{"102":102,"125":125,"162":162,"45":45,"5":5}],5:[function(_dereq_,module,exports){
+(function (global){
+/**
+ * The copyright in this software is being made available under the BSD License,
+ * included below. This software may be subject to other third party and contributor
+ * rights, including patent rights, and no such rights are granted under this license.
+ *
+ * Copyright (c) 2013, Dash Industry Forum.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *  * Redistributions of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation and/or
+ *  other materials provided with the distribution.
+ *  * Neither the name of Dash Industry Forum nor the names of its
+ *  contributors may be used to endorse or promote products derived from this software
+ *  without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY
+ *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ *  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ *  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ *  POSSIBILITY OF SUCH DAMAGE.
+ */
+
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+  value: true
+});
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _srcStreamingMediaPlayer = _dereq_(100);
+
+var _srcStreamingMediaPlayer2 = _interopRequireDefault(_srcStreamingMediaPlayer);
+
+var _srcCoreFactoryMaker = _dereq_(47);
+
+var _srcCoreFactoryMaker2 = _interopRequireDefault(_srcCoreFactoryMaker);
+
+var _srcCoreDebug = _dereq_(45);
+
+var _srcCoreDebug2 = _interopRequireDefault(_srcCoreDebug);
+
+var _srcCoreVersion = _dereq_(50);
+
+// Shove both of these into the global scope
+var context = typeof window !== 'undefined' && window || global;
+
+var dashjs = context.dashjs;
+if (!dashjs) {
+  dashjs = context.dashjs = {};
+}
+
+dashjs.MediaPlayer = _srcStreamingMediaPlayer2['default'];
+dashjs.FactoryMaker = _srcCoreFactoryMaker2['default'];
+dashjs.Debug = _srcCoreDebug2['default'];
+dashjs.Version = (0, _srcCoreVersion.getVersionString)();
+
+exports['default'] = dashjs;
+exports.MediaPlayer = _srcStreamingMediaPlayer2['default'];
+exports.FactoryMaker = _srcCoreFactoryMaker2['default'];
+exports.Debug = _srcCoreDebug2['default'];
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"100":100,"45":45,"47":47,"50":50}],6:[function(_dereq_,module,exports){
+'use strict'
+
+exports.byteLength = byteLength
+exports.toByteArray = toByteArray
+exports.fromByteArray = fromByteArray
+
+var lookup = []
+var revLookup = []
+var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array
+
+var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+for (var i = 0, len = code.length; i < len; ++i) {
+  lookup[i] = code[i]
+  revLookup[code.charCodeAt(i)] = i
+}
+
+// Support decoding URL-safe base64 strings, as Node.js does.
+// See: https://en.wikipedia.org/wiki/Base64#URL_applications
+revLookup['-'.charCodeAt(0)] = 62
+revLookup['_'.charCodeAt(0)] = 63
+
+function getLens (b64) {
+  var len = b64.length
+
+  if (len % 4 > 0) {
+    throw new Error('Invalid string. Length must be a multiple of 4')
+  }
+
+  // Trim off extra bytes after placeholder bytes are found
+  // See: https://github.com/beatgammit/base64-js/issues/42
+  var validLen = b64.indexOf('=')
+  if (validLen === -1) validLen = len
+
+  var placeHoldersLen = validLen === len
+    ? 0
+    : 4 - (validLen % 4)
+
+  return [validLen, placeHoldersLen]
+}
+
+// base64 is 4/3 + up to two characters of the original data
+function byteLength (b64) {
+  var lens = getLens(b64)
+  var validLen = lens[0]
+  var placeHoldersLen = lens[1]
+  return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen
+}
+
+function _byteLength (b64, validLen, placeHoldersLen) {
+  return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen
+}
+
+function toByteArray (b64) {
+  var tmp
+  var lens = getLens(b64)
+  var validLen = lens[0]
+  var placeHoldersLen = lens[1]
+
+  var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen))
+
+  var curByte = 0
+
+  // if there are placeholders, only get up to the last complete 4 chars
+  var len = placeHoldersLen > 0
+    ? validLen - 4
+    : validLen
+
+  for (var i = 0; i < len; i += 4) {
+    tmp =
+      (revLookup[b64.charCodeAt(i)] << 18) |
+      (revLookup[b64.charCodeAt(i + 1)] << 12) |
+      (revLookup[b64.charCodeAt(i + 2)] << 6) |
+      revLookup[b64.charCodeAt(i + 3)]
+    arr[curByte++] = (tmp >> 16) & 0xFF
+    arr[curByte++] = (tmp >> 8) & 0xFF
+    arr[curByte++] = tmp & 0xFF
+  }
+
+  if (placeHoldersLen === 2) {
+    tmp =
+      (revLookup[b64.charCodeAt(i)] << 2) |
+      (revLookup[b64.charCodeAt(i + 1)] >> 4)
+    arr[curByte++] = tmp & 0xFF
+  }
+
+  if (placeHoldersLen === 1) {
+    tmp =
+      (revLookup[b64.charCodeAt(i)] << 10) |
+      (revLookup[b64.charCodeAt(i + 1)] << 4) |
+      (revLookup[b64.charCodeAt(i + 2)] >> 2)
+    arr[curByte++] = (tmp >> 8) & 0xFF
+    arr[curByte++] = tmp & 0xFF
+  }
+
+  return arr
+}
+
+function tripletToBase64 (num) {
+  return lookup[num >> 18 & 0x3F] +
+    lookup[num >> 12 & 0x3F] +
+    lookup[num >> 6 & 0x3F] +
+    lookup[num & 0x3F]
+}
+
+function encodeChunk (uint8, start, end) {
+  var tmp
+  var output = []
+  for (var i = start; i < end; i += 3) {
+    tmp =
+      ((uint8[i] << 16) & 0xFF0000) +
+      ((uint8[i + 1] << 8) & 0xFF00) +
+      (uint8[i + 2] & 0xFF)
+    output.push(tripletToBase64(tmp))
+  }
+  return output.join('')
+}
+
+function fromByteArray (uint8) {
+  var tmp
+  var len = uint8.length
+  var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes
+  var parts = []
+  var maxChunkLength = 16383 // must be multiple of 3
+
+  // go through the array every three bytes, we'll deal with trailing stuff later
+  for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
+    parts.push(encodeChunk(
+      uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)
+    ))
+  }
+
+  // pad the end with zeros, but make sure to not forget the extra bytes
+  if (extraBytes === 1) {
+    tmp = uint8[len - 1]
+    parts.push(
+      lookup[tmp >> 2] +
+      lookup[(tmp << 4) & 0x3F] +
+      '=='
+    )
+  } else if (extraBytes === 2) {
+    tmp = (uint8[len - 2] << 8) + uint8[len - 1]
+    parts.push(
+      lookup[tmp >> 10] +
+      lookup[(tmp >> 4) & 0x3F] +
+      lookup[(tmp << 2) & 0x3F] +
+      '='
+    )
+  }
+
+  return parts.join('')
+}
+
+},{}],7:[function(_dereq_,module,exports){
+
+},{}],8:[function(_dereq_,module,exports){
+/*!
+ * The buffer module from node.js, for the browser.
+ *
+ * @author   Feross Aboukhadijeh <https://feross.org>
+ * @license  MIT
+ */
+/* eslint-disable no-proto */
+
+'use strict'
+
+var base64 = _dereq_(6)
+var ieee754 = _dereq_(13)
+
+exports.Buffer = Buffer
+exports.SlowBuffer = SlowBuffer
+exports.INSPECT_MAX_BYTES = 50
+
+var K_MAX_LENGTH = 0x7fffffff
+exports.kMaxLength = K_MAX_LENGTH
+
+/**
+ * If `Buffer.TYPED_ARRAY_SUPPORT`:
+ *   === true    Use Uint8Array implementation (fastest)
+ *   === false   Print warning and recommend using `buffer` v4.x which has an Object
+ *               implementation (most compatible, even IE6)
+ *
+ * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,
+ * Opera 11.6+, iOS 4.2+.
+ *
+ * We report that the browser does not support typed arrays if the are not subclassable
+ * using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array`
+ * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support
+ * for __proto__ and has a buggy typed array implementation.
+ */
+Buffer.TYPED_ARRAY_SUPPORT = typedArraySupport()
+
+if (!Buffer.TYPED_ARRAY_SUPPORT && typeof console !== 'undefined' &&
+    typeof console.error === 'function') {
+  console.error(
+    'This browser lacks typed array (Uint8Array) support which is required by ' +
+    '`buffer` v5.x. Use `buffer` v4.x if you require old browser support.'
+  )
+}
+
+function typedArraySupport () {
+  // Can typed array instances can be augmented?
+  try {
+    var arr = new Uint8Array(1)
+    arr.__proto__ = { __proto__: Uint8Array.prototype, foo: function () { return 42 } }
+    return arr.foo() === 42
+  } catch (e) {
+    return false
+  }
+}
+
+Object.defineProperty(Buffer.prototype, 'parent', {
+  enumerable: true,
+  get: function () {
+    if (!Buffer.isBuffer(this)) return undefined
+    return this.buffer
+  }
+})
+
+Object.defineProperty(Buffer.prototype, 'offset', {
+  enumerable: true,
+  get: function () {
+    if (!Buffer.isBuffer(this)) return undefined
+    return this.byteOffset
+  }
+})
+
+function createBuffer (length) {
+  if (length > K_MAX_LENGTH) {
+    throw new RangeError('The value "' + length + '" is invalid for option "size"')
+  }
+  // Return an augmented `Uint8Array` instance
+  var buf = new Uint8Array(length)
+  buf.__proto__ = Buffer.prototype
+  return buf
+}
+
+/**
+ * The Buffer constructor returns instances of `Uint8Array` that have their
+ * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of
+ * `Uint8Array`, so the returned instances will have all the node `Buffer` methods
+ * and the `Uint8Array` methods. Square bracket notation works as expected -- it
+ * returns a single octet.
+ *
+ * The `Uint8Array` prototype remains unmodified.
+ */
+
+function Buffer (arg, encodingOrOffset, length) {
+  // Common case.
+  if (typeof arg === 'number') {
+    if (typeof encodingOrOffset === 'string') {
+      throw new TypeError(
+        'The "string" argument must be of type string. Received type number'
+      )
+    }
+    return allocUnsafe(arg)
+  }
+  return from(arg, encodingOrOffset, length)
+}
+
+// Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97
+if (typeof Symbol !== 'undefined' && Symbol.species != null &&
+    Buffer[Symbol.species] === Buffer) {
+  Object.defineProperty(Buffer, Symbol.species, {
+    value: null,
+    configurable: true,
+    enumerable: false,
+    writable: false
+  })
+}
+
+Buffer.poolSize = 8192 // not used by this implementation
+
+function from (value, encodingOrOffset, length) {
+  if (typeof value === 'string') {
+    return fromString(value, encodingOrOffset)
+  }
+
+  if (ArrayBuffer.isView(value)) {
+    return fromArrayLike(value)
+  }
+
+  if (value == null) {
+    throw TypeError(
+      'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' +
+      'or Array-like Object. Received type ' + (typeof value)
+    )
+  }
+
+  if (isInstance(value, ArrayBuffer) ||
+      (value && isInstance(value.buffer, ArrayBuffer))) {
+    return fromArrayBuffer(value, encodingOrOffset, length)
+  }
+
+  if (typeof value === 'number') {
+    throw new TypeError(
+      'The "value" argument must not be of type number. Received type number'
+    )
+  }
+
+  var valueOf = value.valueOf && value.valueOf()
+  if (valueOf != null && valueOf !== value) {
+    return Buffer.from(valueOf, encodingOrOffset, length)
+  }
+
+  var b = fromObject(value)
+  if (b) return b
+
+  if (typeof Symbol !== 'undefined' && Symbol.toPrimitive != null &&
+      typeof value[Symbol.toPrimitive] === 'function') {
+    return Buffer.from(
+      value[Symbol.toPrimitive]('string'), encodingOrOffset, length
+    )
+  }
+
+  throw new TypeError(
+    'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' +
+    'or Array-like Object. Received type ' + (typeof value)
+  )
+}
+
+/**
+ * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError
+ * if value is a number.
+ * Buffer.from(str[, encoding])
+ * Buffer.from(array)
+ * Buffer.from(buffer)
+ * Buffer.from(arrayBuffer[, byteOffset[, length]])
+ **/
+Buffer.from = function (value, encodingOrOffset, length) {
+  return from(value, encodingOrOffset, length)
+}
+
+// Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug:
+// https://github.com/feross/buffer/pull/148
+Buffer.prototype.__proto__ = Uint8Array.prototype
+Buffer.__proto__ = Uint8Array
+
+function assertSize (size) {
+  if (typeof size !== 'number') {
+    throw new TypeError('"size" argument must be of type number')
+  } else if (size < 0) {
+    throw new RangeError('The value "' + size + '" is invalid for option "size"')
+  }
+}
+
+function alloc (size, fill, encoding) {
+  assertSize(size)
+  if (size <= 0) {
+    return createBuffer(size)
+  }
+  if (fill !== undefined) {
+    // Only pay attention to encoding if it's a string. This
+    // prevents accidentally sending in a number that would
+    // be interpretted as a start offset.
+    return typeof encoding === 'string'
+      ? createBuffer(size).fill(fill, encoding)
+      : createBuffer(size).fill(fill)
+  }
+  return createBuffer(size)
+}
+
+/**
+ * Creates a new filled Buffer instance.
+ * alloc(size[, fill[, encoding]])
+ **/
+Buffer.alloc = function (size, fill, encoding) {
+  return alloc(size, fill, encoding)
+}
+
+function allocUnsafe (size) {
+  assertSize(size)
+  return createBuffer(size < 0 ? 0 : checked(size) | 0)
+}
+
+/**
+ * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance.
+ * */
+Buffer.allocUnsafe = function (size) {
+  return allocUnsafe(size)
+}
+/**
+ * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance.
+ */
+Buffer.allocUnsafeSlow = function (size) {
+  return allocUnsafe(size)
+}
+
+function fromString (string, encoding) {
+  if (typeof encoding !== 'string' || encoding === '') {
+    encoding = 'utf8'
+  }
+
+  if (!Buffer.isEncoding(encoding)) {
+    throw new TypeError('Unknown encoding: ' + encoding)
+  }
+
+  var length = byteLength(string, encoding) | 0
+  var buf = createBuffer(length)
+
+  var actual = buf.write(string, encoding)
+
+  if (actual !== length) {
+    // Writing a hex string, for example, that contains invalid characters will
+    // cause everything after the first invalid character to be ignored. (e.g.
+    // 'abxxcd' will be treated as 'ab')
+    buf = buf.slice(0, actual)
+  }
+
+  return buf
+}
+
+function fromArrayLike (array) {
+  var length = array.length < 0 ? 0 : checked(array.length) | 0
+  var buf = createBuffer(length)
+  for (var i = 0; i < length; i += 1) {
+    buf[i] = array[i] & 255
+  }
+  return buf
+}
+
+function fromArrayBuffer (array, byteOffset, length) {
+  if (byteOffset < 0 || array.byteLength < byteOffset) {
+    throw new RangeError('"offset" is outside of buffer bounds')
+  }
+
+  if (array.byteLength < byteOffset + (length || 0)) {
+    throw new RangeError('"length" is outside of buffer bounds')
+  }
+
+  var buf
+  if (byteOffset === undefined && length === undefined) {
+    buf = new Uint8Array(array)
+  } else if (length === undefined) {
+    buf = new Uint8Array(array, byteOffset)
+  } else {
+    buf = new Uint8Array(array, byteOffset, length)
+  }
+
+  // Return an augmented `Uint8Array` instance
+  buf.__proto__ = Buffer.prototype
+  return buf
+}
+
+function fromObject (obj) {
+  if (Buffer.isBuffer(obj)) {
+    var len = checked(obj.length) | 0
+    var buf = createBuffer(len)
+
+    if (buf.length === 0) {
+      return buf
+    }
+
+    obj.copy(buf, 0, 0, len)
+    return buf
+  }
+
+  if (obj.length !== undefined) {
+    if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) {
+      return createBuffer(0)
+    }
+    return fromArrayLike(obj)
+  }
+
+  if (obj.type === 'Buffer' && Array.isArray(obj.data)) {
+    return fromArrayLike(obj.data)
+  }
+}
+
+function checked (length) {
+  // Note: cannot use `length < K_MAX_LENGTH` here because that fails when
+  // length is NaN (which is otherwise coerced to zero.)
+  if (length >= K_MAX_LENGTH) {
+    throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
+                         'size: 0x' + K_MAX_LENGTH.toString(16) + ' bytes')
+  }
+  return length | 0
+}
+
+function SlowBuffer (length) {
+  if (+length != length) { // eslint-disable-line eqeqeq
+    length = 0
+  }
+  return Buffer.alloc(+length)
+}
+
+Buffer.isBuffer = function isBuffer (b) {
+  return b != null && b._isBuffer === true &&
+    b !== Buffer.prototype // so Buffer.isBuffer(Buffer.prototype) will be false
+}
+
+Buffer.compare = function compare (a, b) {
+  if (isInstance(a, Uint8Array)) a = Buffer.from(a, a.offset, a.byteLength)
+  if (isInstance(b, Uint8Array)) b = Buffer.from(b, b.offset, b.byteLength)
+  if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {
+    throw new TypeError(
+      'The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array'
+    )
+  }
+
+  if (a === b) return 0
+
+  var x = a.length
+  var y = b.length
+
+  for (var i = 0, len = Math.min(x, y); i < len; ++i) {
+    if (a[i] !== b[i]) {
+      x = a[i]
+      y = b[i]
+      break
+    }
+  }
+
+  if (x < y) return -1
+  if (y < x) return 1
+  return 0
+}
+
+Buffer.isEncoding = function isEncoding (encoding) {
+  switch (String(encoding).toLowerCase()) {
+    case 'hex':
+    case 'utf8':
+    case 'utf-8':
+    case 'ascii':
+    case 'latin1':
+    case 'binary':
+    case 'base64':
+    case 'ucs2':
+    case 'ucs-2':
+    case 'utf16le':
+    case 'utf-16le':
+      return true
+    default:
+      return false
+  }
+}
+
+Buffer.concat = function concat (list, length) {
+  if (!Array.isArray(list)) {
+    throw new TypeError('"list" argument must be an Array of Buffers')
+  }
+
+  if (list.length === 0) {
+    return Buffer.alloc(0)
+  }
+
+  var i
+  if (length === undefined) {
+    length = 0
+    for (i = 0; i < list.length; ++i) {
+      length += list[i].length
+    }
+  }
+
+  var buffer = Buffer.allocUnsafe(length)
+  var pos = 0
+  for (i = 0; i < list.length; ++i) {
+    var buf = list[i]
+    if (isInstance(buf, Uint8Array)) {
+      buf = Buffer.from(buf)
+    }
+    if (!Buffer.isBuffer(buf)) {
+      throw new TypeError('"list" argument must be an Array of Buffers')
+    }
+    buf.copy(buffer, pos)
+    pos += buf.length
+  }
+  return buffer
+}
+
+function byteLength (string, encoding) {
+  if (Buffer.isBuffer(string)) {
+    return string.length
+  }
+  if (ArrayBuffer.isView(string) || isInstance(string, ArrayBuffer)) {
+    return string.byteLength
+  }
+  if (typeof string !== 'string') {
+    throw new TypeError(
+      'The "string" argument must be one of type string, Buffer, or ArrayBuffer. ' +
+      'Received type ' + typeof string
+    )
+  }
+
+  var len = string.length
+  var mustMatch = (arguments.length > 2 && arguments[2] === true)
+  if (!mustMatch && len === 0) return 0
+
+  // Use a for loop to avoid recursion
+  var loweredCase = false
+  for (;;) {
+    switch (encoding) {
+      case 'ascii':
+      case 'latin1':
+      case 'binary':
+        return len
+      case 'utf8':
+      case 'utf-8':
+        return utf8ToBytes(string).length
+      case 'ucs2':
+      case 'ucs-2':
+      case 'utf16le':
+      case 'utf-16le':
+        return len * 2
+      case 'hex':
+        return len >>> 1
+      case 'base64':
+        return base64ToBytes(string).length
+      default:
+        if (loweredCase) {
+          return mustMatch ? -1 : utf8ToBytes(string).length // assume utf8
+        }
+        encoding = ('' + encoding).toLowerCase()
+        loweredCase = true
+    }
+  }
+}
+Buffer.byteLength = byteLength
+
+function slowToString (encoding, start, end) {
+  var loweredCase = false
+
+  // No need to verify that "this.length <= MAX_UINT32" since it's a read-only
+  // property of a typed array.
+
+  // This behaves neither like String nor Uint8Array in that we set start/end
+  // to their upper/lower bounds if the value passed is out of range.
+  // undefined is handled specially as per ECMA-262 6th Edition,
+  // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization.
+  if (start === undefined || start < 0) {
+    start = 0
+  }
+  // Return early if start > this.length. Done here to prevent potential uint32
+  // coercion fail below.
+  if (start > this.length) {
+    return ''
+  }
+
+  if (end === undefined || end > this.length) {
+    end = this.length
+  }
+
+  if (end <= 0) {
+    return ''
+  }
+
+  // Force coersion to uint32. This will also coerce falsey/NaN values to 0.
+  end >>>= 0
+  start >>>= 0
+
+  if (end <= start) {
+    return ''
+  }
+
+  if (!encoding) encoding = 'utf8'
+
+  while (true) {
+    switch (encoding) {
+      case 'hex':
+        return hexSlice(this, start, end)
+
+      case 'utf8':
+      case 'utf-8':
+        return utf8Slice(this, start, end)
+
+      case 'ascii':
+        return asciiSlice(this, start, end)
+
+      case 'latin1':
+      case 'binary':
+        return latin1Slice(this, start, end)
+
+      case 'base64':
+        return base64Slice(this, start, end)
+
+      case 'ucs2':
+      case 'ucs-2':
+      case 'utf16le':
+      case 'utf-16le':
+        return utf16leSlice(this, start, end)
+
+      default:
+        if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
+        encoding = (encoding + '').toLowerCase()
+        loweredCase = true
+    }
+  }
+}
+
+// This property is used by `Buffer.isBuffer` (and the `is-buffer` npm package)
+// to detect a Buffer instance. It's not possible to use `instanceof Buffer`
+// reliably in a browserify context because there could be multiple different
+// copies of the 'buffer' package in use. This method works even for Buffer
+// instances that were created from another copy of the `buffer` package.
+// See: https://github.com/feross/buffer/issues/154
+Buffer.prototype._isBuffer = true
+
+function swap (b, n, m) {
+  var i = b[n]
+  b[n] = b[m]
+  b[m] = i
+}
+
+Buffer.prototype.swap16 = function swap16 () {
+  var len = this.length
+  if (len % 2 !== 0) {
+    throw new RangeError('Buffer size must be a multiple of 16-bits')
+  }
+  for (var i = 0; i < len; i += 2) {
+    swap(this, i, i + 1)
+  }
+  return this
+}
+
+Buffer.prototype.swap32 = function swap32 () {
+  var len = this.length
+  if (len % 4 !== 0) {
+    throw new RangeError('Buffer size must be a multiple of 32-bits')
+  }
+  for (var i = 0; i < len; i += 4) {
+    swap(this, i, i + 3)
+    swap(this, i + 1, i + 2)
+  }
+  return this
+}
+
+Buffer.prototype.swap64 = function swap64 () {
+  var len = this.length
+  if (len % 8 !== 0) {
+    throw new RangeError('Buffer size must be a multiple of 64-bits')
+  }
+  for (var i = 0; i < len; i += 8) {
+    swap(this, i, i + 7)
+    swap(this, i + 1, i + 6)
+    swap(this, i + 2, i + 5)
+    swap(this, i + 3, i + 4)
+  }
+  return this
+}
+
+Buffer.prototype.toString = function toString () {
+  var length = this.length
+  if (length === 0) return ''
+  if (arguments.length === 0) return utf8Slice(this, 0, length)
+  return slowToString.apply(this, arguments)
+}
+
+Buffer.prototype.toLocaleString = Buffer.prototype.toString
+
+Buffer.prototype.equals = function equals (b) {
+  if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
+  if (this === b) return true
+  return Buffer.compare(this, b) === 0
+}
+
+Buffer.prototype.inspect = function inspect () {
+  var str = ''
+  var max = exports.INSPECT_MAX_BYTES
+  str = this.toString('hex', 0, max).replace(/(.{2})/g, '$1 ').trim()
+  if (this.length > max) str += ' ... '
+  return '<Buffer ' + str + '>'
+}
+
+Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) {
+  if (isInstance(target, Uint8Array)) {
+    target = Buffer.from(target, target.offset, target.byteLength)
+  }
+  if (!Buffer.isBuffer(target)) {
+    throw new TypeError(
+      'The "target" argument must be one of type Buffer or Uint8Array. ' +
+      'Received type ' + (typeof target)
+    )
+  }
+
+  if (start === undefined) {
+    start = 0
+  }
+  if (end === undefined) {
+    end = target ? target.length : 0
+  }
+  if (thisStart === undefined) {
+    thisStart = 0
+  }
+  if (thisEnd === undefined) {
+    thisEnd = this.length
+  }
+
+  if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) {
+    throw new RangeError('out of range index')
+  }
+
+  if (thisStart >= thisEnd && start >= end) {
+    return 0
+  }
+  if (thisStart >= thisEnd) {
+    return -1
+  }
+  if (start >= end) {
+    return 1
+  }
+
+  start >>>= 0
+  end >>>= 0
+  thisStart >>>= 0
+  thisEnd >>>= 0
+
+  if (this === target) return 0
+
+  var x = thisEnd - thisStart
+  var y = end - start
+  var len = Math.min(x, y)
+
+  var thisCopy = this.slice(thisStart, thisEnd)
+  var targetCopy = target.slice(start, end)
+
+  for (var i = 0; i < len; ++i) {
+    if (thisCopy[i] !== targetCopy[i]) {
+      x = thisCopy[i]
+      y = targetCopy[i]
+      break
+    }
+  }
+
+  if (x < y) return -1
+  if (y < x) return 1
+  return 0
+}
+
+// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`,
+// OR the last index of `val` in `buffer` at offset <= `byteOffset`.
+//
+// Arguments:
+// - buffer - a Buffer to search
+// - val - a string, Buffer, or number
+// - byteOffset - an index into `buffer`; will be clamped to an int32
+// - encoding - an optional encoding, relevant is val is a string
+// - dir - true for indexOf, false for lastIndexOf
+function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) {
+  // Empty buffer means no match
+  if (buffer.length === 0) return -1
+
+  // Normalize byteOffset
+  if (typeof byteOffset === 'string') {
+    encoding = byteOffset
+    byteOffset = 0
+  } else if (byteOffset > 0x7fffffff) {
+    byteOffset = 0x7fffffff
+  } else if (byteOffset < -0x80000000) {
+    byteOffset = -0x80000000
+  }
+  byteOffset = +byteOffset // Coerce to Number.
+  if (numberIsNaN(byteOffset)) {
+    // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer
+    byteOffset = dir ? 0 : (buffer.length - 1)
+  }
+
+  // Normalize byteOffset: negative offsets start from the end of the buffer
+  if (byteOffset < 0) byteOffset = buffer.length + byteOffset
+  if (byteOffset >= buffer.length) {
+    if (dir) return -1
+    else byteOffset = buffer.length - 1
+  } else if (byteOffset < 0) {
+    if (dir) byteOffset = 0
+    else return -1
+  }
+
+  // Normalize val
+  if (typeof val === 'string') {
+    val = Buffer.from(val, encoding)
+  }
+
+  // Finally, search either indexOf (if dir is true) or lastIndexOf
+  if (Buffer.isBuffer(val)) {
+    // Special case: looking for empty string/buffer always fails
+    if (val.length === 0) {
+      return -1
+    }
+    return arrayIndexOf(buffer, val, byteOffset, encoding, dir)
+  } else if (typeof val === 'number') {
+    val = val & 0xFF // Search for a byte value [0-255]
+    if (typeof Uint8Array.prototype.indexOf === 'function') {
+      if (dir) {
+        return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset)
+      } else {
+        return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset)
+      }
+    }
+    return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir)
+  }
+
+  throw new TypeError('val must be string, number or Buffer')
+}
+
+function arrayIndexOf (arr, val, byteOffset, encoding, dir) {
+  var indexSize = 1
+  var arrLength = arr.length
+  var valLength = val.length
+
+  if (encoding !== undefined) {
+    encoding = String(encoding).toLowerCase()
+    if (encoding === 'ucs2' || encoding === 'ucs-2' ||
+        encoding === 'utf16le' || encoding === 'utf-16le') {
+      if (arr.length < 2 || val.length < 2) {
+        return -1
+      }
+      indexSize = 2
+      arrLength /= 2
+      valLength /= 2
+      byteOffset /= 2
+    }
+  }
+
+  function read (buf, i) {
+    if (indexSize === 1) {
+      return buf[i]
+    } else {
+      return buf.readUInt16BE(i * indexSize)
+    }
+  }
+
+  var i
+  if (dir) {
+    var foundIndex = -1
+    for (i = byteOffset; i < arrLength; i++) {
+      if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) {
+        if (foundIndex === -1) foundIndex = i
+        if (i - foundIndex + 1 === valLength) return foundIndex * indexSize
+      } else {
+        if (foundIndex !== -1) i -= i - foundIndex
+        foundIndex = -1
+      }
+    }
+  } else {
+    if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength
+    for (i = byteOffset; i >= 0; i--) {
+      var found = true
+      for (var j = 0; j < valLength; j++) {
+        if (read(arr, i + j) !== read(val, j)) {
+          found = false
+          break
+        }
+      }
+      if (found) return i
+    }
+  }
+
+  return -1
+}
+
+Buffer.prototype.includes = function includes (val, byteOffset, encoding) {
+  return this.indexOf(val, byteOffset, encoding) !== -1
+}
+
+Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) {
+  return bidirectionalIndexOf(this, val, byteOffset, encoding, true)
+}
+
+Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) {
+  return bidirectionalIndexOf(this, val, byteOffset, encoding, false)
+}
+
+function hexWrite (buf, string, offset, length) {
+  offset = Number(offset) || 0
+  var remaining = buf.length - offset
+  if (!length) {
+    length = remaining
+  } else {
+    length = Number(length)
+    if (length > remaining) {
+      length = remaining
+    }
+  }
+
+  var strLen = string.length
+
+  if (length > strLen / 2) {
+    length = strLen / 2
+  }
+  for (var i = 0; i < length; ++i) {
+    var parsed = parseInt(string.substr(i * 2, 2), 16)
+    if (numberIsNaN(parsed)) return i
+    buf[offset + i] = parsed
+  }
+  return i
+}
+
+function utf8Write (buf, string, offset, length) {
+  return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)
+}
+
+function asciiWrite (buf, string, offset, length) {
+  return blitBuffer(asciiToBytes(string), buf, offset, length)
+}
+
+function latin1Write (buf, string, offset, length) {
+  return asciiWrite(buf, string, offset, length)
+}
+
+function base64Write (buf, string, offset, length) {
+  return blitBuffer(base64ToBytes(string), buf, offset, length)
+}
+
+function ucs2Write (buf, string, offset, length) {
+  return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length)
+}
+
+Buffer.prototype.write = function write (string, offset, length, encoding) {
+  // Buffer#write(string)
+  if (offset === undefined) {
+    encoding = 'utf8'
+    length = this.length
+    offset = 0
+  // Buffer#write(string, encoding)
+  } else if (length === undefined && typeof offset === 'string') {
+    encoding = offset
+    length = this.length
+    offset = 0
+  // Buffer#write(string, offset[, length][, encoding])
+  } else if (isFinite(offset)) {
+    offset = offset >>> 0
+    if (isFinite(length)) {
+      length = length >>> 0
+      if (encoding === undefined) encoding = 'utf8'
+    } else {
+      encoding = length
+      length = undefined
+    }
+  } else {
+    throw new Error(
+      'Buffer.write(string, encoding, offset[, length]) is no longer supported'
+    )
+  }
+
+  var remaining = this.length - offset
+  if (length === undefined || length > remaining) length = remaining
+
+  if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) {
+    throw new RangeError('Attempt to write outside buffer bounds')
+  }
+
+  if (!encoding) encoding = 'utf8'
+
+  var loweredCase = false
+  for (;;) {
+    switch (encoding) {
+      case 'hex':
+        return hexWrite(this, string, offset, length)
+
+      case 'utf8':
+      case 'utf-8':
+        return utf8Write(this, string, offset, length)
+
+      case 'ascii':
+        return asciiWrite(this, string, offset, length)
+
+      case 'latin1':
+      case 'binary':
+        return latin1Write(this, string, offset, length)
+
+      case 'base64':
+        // Warning: maxLength not taken into account in base64Write
+        return base64Write(this, string, offset, length)
+
+      case 'ucs2':
+      case 'ucs-2':
+      case 'utf16le':
+      case 'utf-16le':
+        return ucs2Write(this, string, offset, length)
+
+      default:
+        if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
+        encoding = ('' + encoding).toLowerCase()
+        loweredCase = true
+    }
+  }
+}
+
+Buffer.prototype.toJSON = function toJSON () {
+  return {
+    type: 'Buffer',
+    data: Array.prototype.slice.call(this._arr || this, 0)
+  }
+}
+
+function base64Slice (buf, start, end) {
+  if (start === 0 && end === buf.length) {
+    return base64.fromByteArray(buf)
+  } else {
+    return base64.fromByteArray(buf.slice(start, end))
+  }
+}
+
+function utf8Slice (buf, start, end) {
+  end = Math.min(buf.length, end)
+  var res = []
+
+  var i = start
+  while (i < end) {
+    var firstByte = buf[i]
+    var codePoint = null
+    var bytesPerSequence = (firstByte > 0xEF) ? 4
+      : (firstByte > 0xDF) ? 3
+        : (firstByte > 0xBF) ? 2
+          : 1
+
+    if (i + bytesPerSequence <= end) {
+      var secondByte, thirdByte, fourthByte, tempCodePoint
+
+      switch (bytesPerSequence) {
+        case 1:
+          if (firstByte < 0x80) {
+            codePoint = firstByte
+          }
+          break
+        case 2:
+          secondByte = buf[i + 1]
+          if ((secondByte & 0xC0) === 0x80) {
+            tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F)
+            if (tempCodePoint > 0x7F) {
+              codePoint = tempCodePoint
+            }
+          }
+          break
+        case 3:
+          secondByte = buf[i + 1]
+          thirdByte = buf[i + 2]
+          if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {
+            tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F)
+            if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {
+              codePoint = tempCodePoint
+            }
+          }
+          break
+        case 4:
+          secondByte = buf[i + 1]
+          thirdByte = buf[i + 2]
+          fourthByte = buf[i + 3]
+          if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {
+            tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F)
+            if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {
+              codePoint = tempCodePoint
+            }
+          }
+      }
+    }
+
+    if (codePoint === null) {
+      // we did not generate a valid codePoint so insert a
+      // replacement char (U+FFFD) and advance only 1 byte
+      codePoint = 0xFFFD
+      bytesPerSequence = 1
+    } else if (codePoint > 0xFFFF) {
+      // encode to utf16 (surrogate pair dance)
+      codePoint -= 0x10000
+      res.push(codePoint >>> 10 & 0x3FF | 0xD800)
+      codePoint = 0xDC00 | codePoint & 0x3FF
+    }
+
+    res.push(codePoint)
+    i += bytesPerSequence
+  }
+
+  return decodeCodePointsArray(res)
+}
+
+// Based on http://stackoverflow.com/a/22747272/680742, the browser with
+// the lowest limit is Chrome, with 0x10000 args.
+// We go 1 magnitude less, for safety
+var MAX_ARGUMENTS_LENGTH = 0x1000
+
+function decodeCodePointsArray (codePoints) {
+  var len = codePoints.length
+  if (len <= MAX_ARGUMENTS_LENGTH) {
+    return String.fromCharCode.apply(String, codePoints) // avoid extra slice()
+  }
+
+  // Decode in chunks to avoid "call stack size exceeded".
+  var res = ''
+  var i = 0
+  while (i < len) {
+    res += String.fromCharCode.apply(
+      String,
+      codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)
+    )
+  }
+  return res
+}
+
+function asciiSlice (buf, start, end) {
+  var ret = ''
+  end = Math.min(buf.length, end)
+
+  for (var i = start; i < end; ++i) {
+    ret += String.fromCharCode(buf[i] & 0x7F)
+  }
+  return ret
+}
+
+function latin1Slice (buf, start, end) {
+  var ret = ''
+  end = Math.min(buf.length, end)
+
+  for (var i = start; i < end; ++i) {
+    ret += String.fromCharCode(buf[i])
+  }
+  return ret
+}
+
+function hexSlice (buf, start, end) {
+  var len = buf.length
+
+  if (!start || start < 0) start = 0
+  if (!end || end < 0 || end > len) end = len
+
+  var out = ''
+  for (var i = start; i < end; ++i) {
+    out += toHex(buf[i])
+  }
+  return out
+}
+
+function utf16leSlice (buf, start, end) {
+  var bytes = buf.slice(start, end)
+  var res = ''
+  for (var i = 0; i < bytes.length; i += 2) {
+    res += String.fromCharCode(bytes[i] + (bytes[i + 1] * 256))
+  }
+  return res
+}
+
+Buffer.prototype.slice = function slice (start, end) {
+  var len = this.length
+  start = ~~start
+  end = end === undefined ? len : ~~end
+
+  if (start < 0) {
+    start += len
+    if (start < 0) start = 0
+  } else if (start > len) {
+    start = len
+  }
+
+  if (end < 0) {
+    end += len
+    if (end < 0) end = 0
+  } else if (end > len) {
+    end = len
+  }
+
+  if (end < start) end = start
+
+  var newBuf = this.subarray(start, end)
+  // Return an augmented `Uint8Array` instance
+  newBuf.__proto__ = Buffer.prototype
+  return newBuf
+}
+
+/*
+ * Need to make sure that buffer isn't trying to write out of bounds.
+ */
+function checkOffset (offset, ext, length) {
+  if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')
+  if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')
+}
+
+Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) checkOffset(offset, byteLength, this.length)
+
+  var val = this[offset]
+  var mul = 1
+  var i = 0
+  while (++i < byteLength && (mul *= 0x100)) {
+    val += this[offset + i] * mul
+  }
+
+  return val
+}
+
+Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) {
+    checkOffset(offset, byteLength, this.length)
+  }
+
+  var val = this[offset + --byteLength]
+  var mul = 1
+  while (byteLength > 0 && (mul *= 0x100)) {
+    val += this[offset + --byteLength] * mul
+  }
+
+  return val
+}
+
+Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 1, this.length)
+  return this[offset]
+}
+
+Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 2, this.length)
+  return this[offset] | (this[offset + 1] << 8)
+}
+
+Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 2, this.length)
+  return (this[offset] << 8) | this[offset + 1]
+}
+
+Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 4, this.length)
+
+  return ((this[offset]) |
+      (this[offset + 1] << 8) |
+      (this[offset + 2] << 16)) +
+      (this[offset + 3] * 0x1000000)
+}
+
+Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 4, this.length)
+
+  return (this[offset] * 0x1000000) +
+    ((this[offset + 1] << 16) |
+    (this[offset + 2] << 8) |
+    this[offset + 3])
+}
+
+Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) checkOffset(offset, byteLength, this.length)
+
+  var val = this[offset]
+  var mul = 1
+  var i = 0
+  while (++i < byteLength && (mul *= 0x100)) {
+    val += this[offset + i] * mul
+  }
+  mul *= 0x80
+
+  if (val >= mul) val -= Math.pow(2, 8 * byteLength)
+
+  return val
+}
+
+Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) {
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) checkOffset(offset, byteLength, this.length)
+
+  var i = byteLength
+  var mul = 1
+  var val = this[offset + --i]
+  while (i > 0 && (mul *= 0x100)) {
+    val += this[offset + --i] * mul
+  }
+  mul *= 0x80
+
+  if (val >= mul) val -= Math.pow(2, 8 * byteLength)
+
+  return val
+}
+
+Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 1, this.length)
+  if (!(this[offset] & 0x80)) return (this[offset])
+  return ((0xff - this[offset] + 1) * -1)
+}
+
+Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 2, this.length)
+  var val = this[offset] | (this[offset + 1] << 8)
+  return (val & 0x8000) ? val | 0xFFFF0000 : val
+}
+
+Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 2, this.length)
+  var val = this[offset + 1] | (this[offset] << 8)
+  return (val & 0x8000) ? val | 0xFFFF0000 : val
+}
+
+Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 4, this.length)
+
+  return (this[offset]) |
+    (this[offset + 1] << 8) |
+    (this[offset + 2] << 16) |
+    (this[offset + 3] << 24)
+}
+
+Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 4, this.length)
+
+  return (this[offset] << 24) |
+    (this[offset + 1] << 16) |
+    (this[offset + 2] << 8) |
+    (this[offset + 3])
+}
+
+Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 4, this.length)
+  return ieee754.read(this, offset, true, 23, 4)
+}
+
+Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 4, this.length)
+  return ieee754.read(this, offset, false, 23, 4)
+}
+
+Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 8, this.length)
+  return ieee754.read(this, offset, true, 52, 8)
+}
+
+Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 8, this.length)
+  return ieee754.read(this, offset, false, 52, 8)
+}
+
+function checkInt (buf, value, offset, ext, max, min) {
+  if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance')
+  if (value > max || value < min) throw new RangeError('"value" argument is out of bounds')
+  if (offset + ext > buf.length) throw new RangeError('Index out of range')
+}
+
+Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) {
+    var maxBytes = Math.pow(2, 8 * byteLength) - 1
+    checkInt(this, value, offset, byteLength, maxBytes, 0)
+  }
+
+  var mul = 1
+  var i = 0
+  this[offset] = value & 0xFF
+  while (++i < byteLength && (mul *= 0x100)) {
+    this[offset + i] = (value / mul) & 0xFF
+  }
+
+  return offset + byteLength
+}
+
+Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) {
+    var maxBytes = Math.pow(2, 8 * byteLength) - 1
+    checkInt(this, value, offset, byteLength, maxBytes, 0)
+  }
+
+  var i = byteLength - 1
+  var mul = 1
+  this[offset + i] = value & 0xFF
+  while (--i >= 0 && (mul *= 0x100)) {
+    this[offset + i] = (value / mul) & 0xFF
+  }
+
+  return offset + byteLength
+}
+
+Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)
+  this[offset] = (value & 0xff)
+  return offset + 1
+}
+
+Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
+  this[offset] = (value & 0xff)
+  this[offset + 1] = (value >>> 8)
+  return offset + 2
+}
+
+Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
+  this[offset] = (value >>> 8)
+  this[offset + 1] = (value & 0xff)
+  return offset + 2
+}
+
+Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
+  this[offset + 3] = (value >>> 24)
+  this[offset + 2] = (value >>> 16)
+  this[offset + 1] = (value >>> 8)
+  this[offset] = (value & 0xff)
+  return offset + 4
+}
+
+Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
+  this[offset] = (value >>> 24)
+  this[offset + 1] = (value >>> 16)
+  this[offset + 2] = (value >>> 8)
+  this[offset + 3] = (value & 0xff)
+  return offset + 4
+}
+
+Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) {
+    var limit = Math.pow(2, (8 * byteLength) - 1)
+
+    checkInt(this, value, offset, byteLength, limit - 1, -limit)
+  }
+
+  var i = 0
+  var mul = 1
+  var sub = 0
+  this[offset] = value & 0xFF
+  while (++i < byteLength && (mul *= 0x100)) {
+    if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) {
+      sub = 1
+    }
+    this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
+  }
+
+  return offset + byteLength
+}
+
+Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) {
+    var limit = Math.pow(2, (8 * byteLength) - 1)
+
+    checkInt(this, value, offset, byteLength, limit - 1, -limit)
+  }
+
+  var i = byteLength - 1
+  var mul = 1
+  var sub = 0
+  this[offset + i] = value & 0xFF
+  while (--i >= 0 && (mul *= 0x100)) {
+    if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) {
+      sub = 1
+    }
+    this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
+  }
+
+  return offset + byteLength
+}
+
+Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)
+  if (value < 0) value = 0xff + value + 1
+  this[offset] = (value & 0xff)
+  return offset + 1
+}
+
+Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
+  this[offset] = (value & 0xff)
+  this[offset + 1] = (value >>> 8)
+  return offset + 2
+}
+
+Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
+  this[offset] = (value >>> 8)
+  this[offset + 1] = (value & 0xff)
+  return offset + 2
+}
+
+Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
+  this[offset] = (value & 0xff)
+  this[offset + 1] = (value >>> 8)
+  this[offset + 2] = (value >>> 16)
+  this[offset + 3] = (value >>> 24)
+  return offset + 4
+}
+
+Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
+  if (value < 0) value = 0xffffffff + value + 1
+  this[offset] = (value >>> 24)
+  this[offset + 1] = (value >>> 16)
+  this[offset + 2] = (value >>> 8)
+  this[offset + 3] = (value & 0xff)
+  return offset + 4
+}
+
+function checkIEEE754 (buf, value, offset, ext, max, min) {
+  if (offset + ext > buf.length) throw new RangeError('Index out of range')
+  if (offset < 0) throw new RangeError('Index out of range')
+}
+
+function writeFloat (buf, value, offset, littleEndian, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) {
+    checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)
+  }
+  ieee754.write(buf, value, offset, littleEndian, 23, 4)
+  return offset + 4
+}
+
+Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) {
+  return writeFloat(this, value, offset, true, noAssert)
+}
+
+Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) {
+  return writeFloat(this, value, offset, false, noAssert)
+}
+
+function writeDouble (buf, value, offset, littleEndian, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) {
+    checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308)
+  }
+  ieee754.write(buf, value, offset, littleEndian, 52, 8)
+  return offset + 8
+}
+
+Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) {
+  return writeDouble(this, value, offset, true, noAssert)
+}
+
+Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) {
+  return writeDouble(this, value, offset, false, noAssert)
+}
+
+// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)
+Buffer.prototype.copy = function copy (target, targetStart, start, end) {
+  if (!Buffer.isBuffer(target)) throw new TypeError('argument should be a Buffer')
+  if (!start) start = 0
+  if (!end && end !== 0) end = this.length
+  if (targetStart >= target.length) targetStart = target.length
+  if (!targetStart) targetStart = 0
+  if (end > 0 && end < start) end = start
+
+  // Copy 0 bytes; we're done
+  if (end === start) return 0
+  if (target.length === 0 || this.length === 0) return 0
+
+  // Fatal error conditions
+  if (targetStart < 0) {
+    throw new RangeError('targetStart out of bounds')
+  }
+  if (start < 0 || start >= this.length) throw new RangeError('Index out of range')
+  if (end < 0) throw new RangeError('sourceEnd out of bounds')
+
+  // Are we oob?
+  if (end > this.length) end = this.length
+  if (target.length - targetStart < end - start) {
+    end = target.length - targetStart + start
+  }
+
+  var len = end - start
+
+  if (this === target && typeof Uint8Array.prototype.copyWithin === 'function') {
+    // Use built-in when available, missing from IE11
+    this.copyWithin(targetStart, start, end)
+  } else if (this === target && start < targetStart && targetStart < end) {
+    // descending copy from end
+    for (var i = len - 1; i >= 0; --i) {
+      target[i + targetStart] = this[i + start]
+    }
+  } else {
+    Uint8Array.prototype.set.call(
+      target,
+      this.subarray(start, end),
+      targetStart
+    )
+  }
+
+  return len
+}
+
+// Usage:
+//    buffer.fill(number[, offset[, end]])
+//    buffer.fill(buffer[, offset[, end]])
+//    buffer.fill(string[, offset[, end]][, encoding])
+Buffer.prototype.fill = function fill (val, start, end, encoding) {
+  // Handle string cases:
+  if (typeof val === 'string') {
+    if (typeof start === 'string') {
+      encoding = start
+      start = 0
+      end = this.length
+    } else if (typeof end === 'string') {
+      encoding = end
+      end = this.length
+    }
+    if (encoding !== undefined && typeof encoding !== 'string') {
+      throw new TypeError('encoding must be a string')
+    }
+    if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) {
+      throw new TypeError('Unknown encoding: ' + encoding)
+    }
+    if (val.length === 1) {
+      var code = val.charCodeAt(0)
+      if ((encoding === 'utf8' && code < 128) ||
+          encoding === 'latin1') {
+        // Fast path: If `val` fits into a single byte, use that numeric value.
+        val = code
+      }
+    }
+  } else if (typeof val === 'number') {
+    val = val & 255
+  }
+
+  // Invalid ranges are not set to a default, so can range check early.
+  if (start < 0 || this.length < start || this.length < end) {
+    throw new RangeError('Out of range index')
+  }
+
+  if (end <= start) {
+    return this
+  }
+
+  start = start >>> 0
+  end = end === undefined ? this.length : end >>> 0
+
+  if (!val) val = 0
+
+  var i
+  if (typeof val === 'number') {
+    for (i = start; i < end; ++i) {
+      this[i] = val
+    }
+  } else {
+    var bytes = Buffer.isBuffer(val)
+      ? val
+      : Buffer.from(val, encoding)
+    var len = bytes.length
+    if (len === 0) {
+      throw new TypeError('The value "' + val +
+        '" is invalid for argument "value"')
+    }
+    for (i = 0; i < end - start; ++i) {
+      this[i + start] = bytes[i % len]
+    }
+  }
+
+  return this
+}
+
+// HELPER FUNCTIONS
+// ================
+
+var INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g
+
+function base64clean (str) {
+  // Node takes equal signs as end of the Base64 encoding
+  str = str.split('=')[0]
+  // Node strips out invalid characters like \n and \t from the string, base64-js does not
+  str = str.trim().replace(INVALID_BASE64_RE, '')
+  // Node converts strings with length < 2 to ''
+  if (str.length < 2) return ''
+  // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not
+  while (str.length % 4 !== 0) {
+    str = str + '='
+  }
+  return str
+}
+
+function toHex (n) {
+  if (n < 16) return '0' + n.toString(16)
+  return n.toString(16)
+}
+
+function utf8ToBytes (string, units) {
+  units = units || Infinity
+  var codePoint
+  var length = string.length
+  var leadSurrogate = null
+  var bytes = []
+
+  for (var i = 0; i < length; ++i) {
+    codePoint = string.charCodeAt(i)
+
+    // is surrogate component
+    if (codePoint > 0xD7FF && codePoint < 0xE000) {
+      // last char was a lead
+      if (!leadSurrogate) {
+        // no lead yet
+        if (codePoint > 0xDBFF) {
+          // unexpected trail
+          if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+          continue
+        } else if (i + 1 === length) {
+          // unpaired lead
+          if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+          continue
+        }
+
+        // valid lead
+        leadSurrogate = codePoint
+
+        continue
+      }
+
+      // 2 leads in a row
+      if (codePoint < 0xDC00) {
+        if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+        leadSurrogate = codePoint
+        continue
+      }
+
+      // valid surrogate pair
+      codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000
+    } else if (leadSurrogate) {
+      // valid bmp char, but last char was a lead
+      if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+    }
+
+    leadSurrogate = null
+
+    // encode utf8
+    if (codePoint < 0x80) {
+      if ((units -= 1) < 0) break
+      bytes.push(codePoint)
+    } else if (codePoint < 0x800) {
+      if ((units -= 2) < 0) break
+      bytes.push(
+        codePoint >> 0x6 | 0xC0,
+        codePoint & 0x3F | 0x80
+      )
+    } else if (codePoint < 0x10000) {
+      if ((units -= 3) < 0) break
+      bytes.push(
+        codePoint >> 0xC | 0xE0,
+        codePoint >> 0x6 & 0x3F | 0x80,
+        codePoint & 0x3F | 0x80
+      )
+    } else if (codePoint < 0x110000) {
+      if ((units -= 4) < 0) break
+      bytes.push(
+        codePoint >> 0x12 | 0xF0,
+        codePoint >> 0xC & 0x3F | 0x80,
+        codePoint >> 0x6 & 0x3F | 0x80,
+        codePoint & 0x3F | 0x80
+      )
+    } else {
+      throw new Error('Invalid code point')
+    }
+  }
+
+  return bytes
+}
+
+function asciiToBytes (str) {
+  var byteArray = []
+  for (var i = 0; i < str.length; ++i) {
+    // Node's code seems to be doing this and not & 0x7F..
+    byteArray.push(str.charCodeAt(i) & 0xFF)
+  }
+  return byteArray
+}
+
+function utf16leToBytes (str, units) {
+  var c, hi, lo
+  var byteArray = []
+  for (var i = 0; i < str.length; ++i) {
+    if ((units -= 2) < 0) break
+
+    c = str.charCodeAt(i)
+    hi = c >> 8
+    lo = c % 256
+    byteArray.push(lo)
+    byteArray.push(hi)
+  }
+
+  return byteArray
+}
+
+function base64ToBytes (str) {
+  return base64.toByteArray(base64clean(str))
+}
+
+function blitBuffer (src, dst, offset, length) {
+  for (var i = 0; i < length; ++i) {
+    if ((i + offset >= dst.length) || (i >= src.length)) break
+    dst[i + offset] = src[i]
+  }
+  return i
+}
+
+// ArrayBuffer or Uint8Array objects from other contexts (i.e. iframes) do not pass
+// the `instanceof` check but they should be treated as of that type.
+// See: https://github.com/feross/buffer/issues/166
+function isInstance (obj, type) {
+  return obj instanceof type ||
+    (obj != null && obj.constructor != null && obj.constructor.name != null &&
+      obj.constructor.name === type.name)
+}
+function numberIsNaN (obj) {
+  // For IE11 support
+  return obj !== obj // eslint-disable-line no-self-compare
+}
+
+},{"13":13,"6":6}],9:[function(_dereq_,module,exports){
+/*! codem-isoboxer v0.3.6 https://github.com/madebyhiro/codem-isoboxer/blob/master/LICENSE.txt */
+var ISOBoxer = {};
+
+ISOBoxer.parseBuffer = function(arrayBuffer) {
+  return new ISOFile(arrayBuffer).parse();
+};
+
+ISOBoxer.addBoxProcessor = function(type, parser) {
+  if (typeof type !== 'string' || typeof parser !== 'function') {
+    return;
+  }
+  ISOBox.prototype._boxProcessors[type] = parser;
+};
+
+ISOBoxer.createFile = function() {
+  return new ISOFile();
+};
+
+// See ISOBoxer.append() for 'pos' parameter syntax
+ISOBoxer.createBox = function(type, parent, pos) {
+  var newBox = ISOBox.create(type);
+  if (parent) {
+    parent.append(newBox, pos);
+  }
+  return newBox;
+};
+
+// See ISOBoxer.append() for 'pos' parameter syntax
+ISOBoxer.createFullBox = function(type, parent, pos) {
+  var newBox = ISOBoxer.createBox(type, parent, pos);
+  newBox.version = 0;
+  newBox.flags = 0;
+  return newBox;
+};
+
+ISOBoxer.Utils = {};
+ISOBoxer.Utils.dataViewToString = function(dataView, encoding) {
+  var impliedEncoding = encoding || 'utf-8';
+  if (typeof TextDecoder !== 'undefined') {
+    return new TextDecoder(impliedEncoding).decode(dataView);
+  }
+  var a = [];
+  var i = 0;
+
+  if (impliedEncoding === 'utf-8') {
+    /* The following algorithm is essentially a rewrite of the UTF8.decode at
+    http://bannister.us/weblog/2007/simple-base64-encodedecode-javascript/
+    */
+
+    while (i < dataView.byteLength) {
+      var c = dataView.getUint8(i++);
+      if (c < 0x80) {
+        // 1-byte character (7 bits)
+      } else if (c < 0xe0) {
+        // 2-byte character (11 bits)
+        c = (c & 0x1f) << 6;
+        c |= (dataView.getUint8(i++) & 0x3f);
+      } else if (c < 0xf0) {
+        // 3-byte character (16 bits)
+        c = (c & 0xf) << 12;
+        c |= (dataView.getUint8(i++) & 0x3f) << 6;
+        c |= (dataView.getUint8(i++) & 0x3f);
+      } else {
+        // 4-byte character (21 bits)
+        c = (c & 0x7) << 18;
+        c |= (dataView.getUint8(i++) & 0x3f) << 12;
+        c |= (dataView.getUint8(i++) & 0x3f) << 6;
+        c |= (dataView.getUint8(i++) & 0x3f);
+      }
+      a.push(String.fromCharCode(c));
+    }
+  } else { // Just map byte-by-byte (probably wrong)
+    while (i < dataView.byteLength) {
+      a.push(String.fromCharCode(dataView.getUint8(i++)));
+    }
+  }
+  return a.join('');
+};
+
+ISOBoxer.Utils.utf8ToByteArray = function(string) {
+  // Only UTF-8 encoding is supported by TextEncoder
+  var u, i;
+  if (typeof TextEncoder !== 'undefined') {
+    u = new TextEncoder().encode(string);
+  } else {
+    u = [];
+    for (i = 0; i < string.length; ++i) {
+      var c = string.charCodeAt(i);
+      if (c < 0x80) {
+        u.push(c);
+      } else if (c < 0x800) {
+        u.push(0xC0 | (c >> 6));
+        u.push(0x80 | (63 & c));
+      } else if (c < 0x10000) {
+        u.push(0xE0 | (c >> 12));
+        u.push(0x80 | (63 & (c >> 6)));
+        u.push(0x80 | (63 & c));
+      } else {
+        u.push(0xF0 | (c >> 18));
+        u.push(0x80 | (63 & (c >> 12)));
+        u.push(0x80 | (63 & (c >> 6)));
+        u.push(0x80 | (63 & c));
+      }
+    }
+  }
+  return u;
+};
+
+// Method to append a box in the list of child boxes
+// The 'pos' parameter can be either:
+//   - (number) a position index at which to insert the new box
+//   - (string) the type of the box after which to insert the new box
+//   - (object) the box after which to insert the new box
+ISOBoxer.Utils.appendBox = function(parent, box, pos) {
+  box._offset = parent._cursor.offset;
+  box._root = (parent._root ? parent._root : parent);
+  box._raw = parent._raw;
+  box._parent = parent;
+
+  if (pos === -1) {
+    // The new box is a sub-box of the parent but not added in boxes array,
+    // for example when the new box is set as an entry (see dref and stsd for example)
+    return;
+  }
+
+  if (pos === undefined || pos === null) {
+    parent.boxes.push(box);
+    return;
+  }
+
+  var index = -1,
+      type;
+
+  if (typeof pos === "number") {
+    index = pos;
+  } else {
+    if (typeof pos === "string") {
+      type = pos;
+    } else if (typeof pos === "object" && pos.type) {
+      type = pos.type;
+    } else {
+      parent.boxes.push(box);
+      return;
+    }
+
+    for (var i = 0; i < parent.boxes.length; i++) {
+      if (type === parent.boxes[i].type) {
+        index = i + 1;
+        break;
+      }
+    }
+  }
+  parent.boxes.splice(index, 0, box);
+};
+
+if (typeof exports !== 'undefined') {
+  exports.parseBuffer     = ISOBoxer.parseBuffer;
+  exports.addBoxProcessor = ISOBoxer.addBoxProcessor;
+  exports.createFile      = ISOBoxer.createFile;
+  exports.createBox       = ISOBoxer.createBox;
+  exports.createFullBox   = ISOBoxer.createFullBox;
+  exports.Utils           = ISOBoxer.Utils;
+}
+
+ISOBoxer.Cursor = function(initialOffset) {
+  this.offset = (typeof initialOffset == 'undefined' ? 0 : initialOffset);
+};
+
+var ISOFile = function(arrayBuffer) {
+  this._cursor = new ISOBoxer.Cursor();
+  this.boxes = [];
+  if (arrayBuffer) {
+    this._raw = new DataView(arrayBuffer);
+  }
+};
+
+ISOFile.prototype.fetch = function(type) {
+  var result = this.fetchAll(type, true);
+  return (result.length ? result[0] : null);
+};
+
+ISOFile.prototype.fetchAll = function(type, returnEarly) {
+  var result = [];
+  ISOFile._sweep.call(this, type, result, returnEarly);
+  return result;
+};
+
+ISOFile.prototype.parse = function() {
+  this._cursor.offset = 0;
+  this.boxes = [];
+  while (this._cursor.offset < this._raw.byteLength) {
+    var box = ISOBox.parse(this);
+
+    // Box could not be parsed
+    if (typeof box.type === 'undefined') break;
+
+    this.boxes.push(box);
+  }
+  return this;
+};
+
+ISOFile._sweep = function(type, result, returnEarly) {
+  if (this.type && this.type == type) result.push(this);
+  for (var box in this.boxes) {
+    if (result.length && returnEarly) return;
+    ISOFile._sweep.call(this.boxes[box], type, result, returnEarly);
+  }
+};
+
+ISOFile.prototype.write = function() {
+
+  var length = 0,
+      i;
+
+  for (i = 0; i < this.boxes.length; i++) {
+    length += this.boxes[i].getLength(false);
+  }
+
+  var bytes = new Uint8Array(length);
+  this._rawo = new DataView(bytes.buffer);
+  this.bytes = bytes;
+  this._cursor.offset = 0;
+
+  for (i = 0; i < this.boxes.length; i++) {
+    this.boxes[i].write();
+  }
+
+  return bytes.buffer;
+};
+
+ISOFile.prototype.append = function(box, pos) {
+  ISOBoxer.Utils.appendBox(this, box, pos);
+};
+var ISOBox = function() {
+  this._cursor = new ISOBoxer.Cursor();
+};
+
+ISOBox.parse = function(parent) {
+  var newBox = new ISOBox();
+  newBox._offset = parent._cursor.offset;
+  newBox._root = (parent._root ? parent._root : parent);
+  newBox._raw = parent._raw;
+  newBox._parent = parent;
+  newBox._parseBox();
+  parent._cursor.offset = newBox._raw.byteOffset + newBox._raw.byteLength;
+  return newBox;
+};
+
+ISOBox.create = function(type) {
+  var newBox = new ISOBox();
+  newBox.type = type;
+  newBox.boxes = [];
+  return newBox;
+};
+
+ISOBox.prototype._boxContainers = ['dinf', 'edts', 'mdia', 'meco', 'mfra', 'minf', 'moof', 'moov', 'mvex', 'stbl', 'strk', 'traf', 'trak', 'tref', 'udta', 'vttc', 'sinf', 'schi', 'encv', 'enca'];
+
+ISOBox.prototype._boxProcessors = {};
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Generic read/write functions
+
+ISOBox.prototype._procField = function (name, type, size) {
+  if (this._parsing) {
+    this[name] = this._readField(type, size);
+  }
+  else {
+    this._writeField(type, size, this[name]);
+  }
+};
+
+ISOBox.prototype._procFieldArray = function (name, length, type, size) {
+  var i;
+  if (this._parsing) {
+    this[name] = [];
+    for (i = 0; i < length; i++) {
+      this[name][i] = this._readField(type, size);
+    }
+  }
+  else {
+    for (i = 0; i < this[name].length; i++) {
+      this._writeField(type, size, this[name][i]);
+    }
+  }
+};
+
+ISOBox.prototype._procFullBox = function() {
+  this._procField('version', 'uint', 8);
+  this._procField('flags', 'uint', 24);
+};
+
+ISOBox.prototype._procEntries = function(name, length, fn) {
+  var i;
+  if (this._parsing) {
+    this[name] = [];
+    for (i = 0; i < length; i++) {
+      this[name].push({});
+      fn.call(this, this[name][i]);
+    }
+  }
+  else {
+    for (i = 0; i < length; i++) {
+      fn.call(this, this[name][i]);
+    }
+  }
+};
+
+ISOBox.prototype._procSubEntries = function(entry, name, length, fn) {
+  var i;
+  if (this._parsing) {
+    entry[name] = [];
+    for (i = 0; i < length; i++) {
+      entry[name].push({});
+      fn.call(this, entry[name][i]);
+    }
+  }
+  else {
+    for (i = 0; i < length; i++) {
+      fn.call(this, entry[name][i]);
+    }
+  }
+};
+
+ISOBox.prototype._procEntryField = function (entry, name, type, size) {
+  if (this._parsing) {
+    entry[name] = this._readField(type, size);
+  }
+  else {
+    this._writeField(type, size, entry[name]);
+  }
+};
+
+ISOBox.prototype._procSubBoxes = function(name, length) {
+  var i;
+  if (this._parsing) {
+    this[name] = [];
+    for (i = 0; i < length; i++) {
+      this[name].push(ISOBox.parse(this));
+    }
+  }
+  else {
+    for (i = 0; i < length; i++) {
+      if (this._rawo) {
+        this[name][i].write();
+      } else {
+        this.size += this[name][i].getLength();
+      }
+    }
+  }
+};
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Read/parse functions
+
+ISOBox.prototype._readField = function(type, size) {
+  switch (type) {
+    case 'uint':
+      return this._readUint(size);
+    case 'int':
+      return this._readInt(size);
+    case 'template':
+      return this._readTemplate(size);
+    case 'string':
+      return (size === -1) ? this._readTerminatedString() : this._readString(size);
+    case 'data':
+      return this._readData(size);
+    case 'utf8':
+      return this._readUTF8String();
+    default:
+      return -1;
+  }
+};
+
+ISOBox.prototype._readInt = function(size) {
+  var result = null,
+      offset = this._cursor.offset - this._raw.byteOffset;
+  switch(size) {
+  case 8:
+    result = this._raw.getInt8(offset);
+    break;
+  case 16:
+    result = this._raw.getInt16(offset);
+    break;
+  case 32:
+    result = this._raw.getInt32(offset);
+    break;
+  case 64:
+    // Warning: JavaScript cannot handle 64-bit integers natively.
+    // This will give unexpected results for integers >= 2^53
+    var s1 = this._raw.getInt32(offset);
+    var s2 = this._raw.getInt32(offset + 4);
+    result = (s1 * Math.pow(2,32)) + s2;
+    break;
+  }
+  this._cursor.offset += (size >> 3);
+  return result;
+};
+
+ISOBox.prototype._readUint = function(size) {
+  var result = null,
+      offset = this._cursor.offset - this._raw.byteOffset,
+      s1, s2;
+  switch(size) {
+  case 8:
+    result = this._raw.getUint8(offset);
+    break;
+  case 16:
+    result = this._raw.getUint16(offset);
+    break;
+  case 24:
+    s1 = this._raw.getUint16(offset);
+    s2 = this._raw.getUint8(offset + 2);
+    result = (s1 << 8) + s2;
+    break;
+  case 32:
+    result = this._raw.getUint32(offset);
+    break;
+  case 64:
+    // Warning: JavaScript cannot handle 64-bit integers natively.
+    // This will give unexpected results for integers >= 2^53
+    s1 = this._raw.getUint32(offset);
+    s2 = this._raw.getUint32(offset + 4);
+    result = (s1 * Math.pow(2,32)) + s2;
+    break;
+  }
+  this._cursor.offset += (size >> 3);
+  return result;
+};
+
+ISOBox.prototype._readString = function(length) {
+  var str = '';
+  for (var c = 0; c < length; c++) {
+    var char = this._readUint(8);
+    str += String.fromCharCode(char);
+  }
+  return str;
+};
+
+ISOBox.prototype._readTemplate = function(size) {
+  var pre = this._readUint(size / 2);
+  var post = this._readUint(size / 2);
+  return pre + (post / Math.pow(2, size / 2));
+};
+
+ISOBox.prototype._readTerminatedString = function() {
+  var str = '';
+  while (this._cursor.offset - this._offset < this._raw.byteLength) {
+    var char = this._readUint(8);
+    if (char === 0) break;
+    str += String.fromCharCode(char);
+  }
+  return str;
+};
+
+ISOBox.prototype._readData = function(size) {
+  var length = (size > 0) ? size : (this._raw.byteLength - (this._cursor.offset - this._offset));
+  if (length > 0) {
+    var data = new Uint8Array(this._raw.buffer, this._cursor.offset, length);
+
+    this._cursor.offset += length;
+    return data;
+  }
+  else {
+    return null;
+  }
+};
+
+ISOBox.prototype._readUTF8String = function() {
+  var length = this._raw.byteLength - (this._cursor.offset - this._offset);
+  var data = null;
+  if (length > 0) {
+    data = new DataView(this._raw.buffer, this._cursor.offset, length);
+    this._cursor.offset += length;
+  }
+  return data ? ISOBoxer.Utils.dataViewToString(data) : data;
+};
+
+ISOBox.prototype._parseBox = function() {
+  this._parsing = true;
+  this._cursor.offset = this._offset;
+
+  // return immediately if there are not enough bytes to read the header
+  if (this._offset + 8 > this._raw.buffer.byteLength) {
+    this._root._incomplete = true;
+    return;
+  }
+
+  this._procField('size', 'uint', 32);
+  this._procField('type', 'string', 4);
+
+  if (this.size === 1)      { this._procField('largesize', 'uint', 64); }
+  if (this.type === 'uuid') { this._procFieldArray('usertype', 16, 'uint', 8); }
+
+  switch(this.size) {
+  case 0:
+    this._raw = new DataView(this._raw.buffer, this._offset, (this._raw.byteLength - this._cursor.offset + 8));
+    break;
+  case 1:
+    if (this._offset + this.size > this._raw.buffer.byteLength) {
+      this._incomplete = true;
+      this._root._incomplete = true;
+    } else {
+      this._raw = new DataView(this._raw.buffer, this._offset, this.largesize);
+    }
+    break;
+  default:
+    if (this._offset + this.size > this._raw.buffer.byteLength) {
+      this._incomplete = true;
+      this._root._incomplete = true;
+    } else {
+      this._raw = new DataView(this._raw.buffer, this._offset, this.size);
+    }
+  }
+
+  // additional parsing
+  if (!this._incomplete) {
+    if (this._boxProcessors[this.type]) {
+      this._boxProcessors[this.type].call(this);
+    }
+    if (this._boxContainers.indexOf(this.type) !== -1) {
+      this._parseContainerBox();
+    } else{
+      // Unknown box => read and store box content
+      this._data = this._readData();
+    }
+  }
+};
+
+ISOBox.prototype._parseFullBox = function() {
+  this.version = this._readUint(8);
+  this.flags = this._readUint(24);
+};
+
+ISOBox.prototype._parseContainerBox = function() {
+  this.boxes = [];
+  while (this._cursor.offset - this._raw.byteOffset < this._raw.byteLength) {
+    this.boxes.push(ISOBox.parse(this));
+  }
+};
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Write functions
+
+ISOBox.prototype.append = function(box, pos) {
+  ISOBoxer.Utils.appendBox(this, box, pos);
+};
+
+ISOBox.prototype.getLength = function() {
+  this._parsing = false;
+  this._rawo = null;
+
+  this.size = 0;
+  this._procField('size', 'uint', 32);
+  this._procField('type', 'string', 4);
+
+  if (this.size === 1)      { this._procField('largesize', 'uint', 64); }
+  if (this.type === 'uuid') { this._procFieldArray('usertype', 16, 'uint', 8); }
+
+  if (this._boxProcessors[this.type]) {
+    this._boxProcessors[this.type].call(this);
+  }
+
+  if (this._boxContainers.indexOf(this.type) !== -1) {
+    for (var i = 0; i < this.boxes.length; i++) {
+      this.size += this.boxes[i].getLength();
+    }
+  } 
+
+  if (this._data) {
+    this._writeData(this._data);
+  }
+
+  return this.size;
+};
+
+ISOBox.prototype.write = function() {
+  this._parsing = false;
+  this._cursor.offset = this._parent._cursor.offset;
+
+  switch(this.size) {
+  case 0:
+    this._rawo = new DataView(this._parent._rawo.buffer, this._cursor.offset, (this.parent._rawo.byteLength - this._cursor.offset));
+    break;
+  case 1:
+      this._rawo = new DataView(this._parent._rawo.buffer, this._cursor.offset, this.largesize);
+    break;
+  default:
+      this._rawo = new DataView(this._parent._rawo.buffer, this._cursor.offset, this.size);
+  }
+
+  this._procField('size', 'uint', 32);
+  this._procField('type', 'string', 4);
+
+  if (this.size === 1)      { this._procField('largesize', 'uint', 64); }
+  if (this.type === 'uuid') { this._procFieldArray('usertype', 16, 'uint', 8); }
+
+  if (this._boxProcessors[this.type]) {
+    this._boxProcessors[this.type].call(this);
+  }
+
+  if (this._boxContainers.indexOf(this.type) !== -1) {
+    for (var i = 0; i < this.boxes.length; i++) {
+      this.boxes[i].write();
+    }
+  } 
+
+  if (this._data) {
+    this._writeData(this._data);
+  }
+
+  this._parent._cursor.offset += this.size;
+
+  return this.size;
+};
+
+ISOBox.prototype._writeInt = function(size, value) {
+  if (this._rawo) {
+    var offset = this._cursor.offset - this._rawo.byteOffset;
+    switch(size) {
+    case 8:
+      this._rawo.setInt8(offset, value);
+      break;
+    case 16:
+      this._rawo.setInt16(offset, value);
+      break;
+    case 32:
+      this._rawo.setInt32(offset, value);
+      break;
+    case 64:
+      // Warning: JavaScript cannot handle 64-bit integers natively.
+      // This will give unexpected results for integers >= 2^53
+      var s1 = Math.floor(value / Math.pow(2,32));
+      var s2 = value - (s1 * Math.pow(2,32));
+      this._rawo.setUint32(offset, s1);
+      this._rawo.setUint32(offset + 4, s2);
+      break;
+    }
+    this._cursor.offset += (size >> 3);
+  } else {
+    this.size += (size >> 3);
+  }
+};
+
+ISOBox.prototype._writeUint = function(size, value) {
+
+  if (this._rawo) {
+    var offset = this._cursor.offset - this._rawo.byteOffset,
+        s1, s2;
+    switch(size) {
+    case 8:
+      this._rawo.setUint8(offset, value);
+      break;
+    case 16:
+      this._rawo.setUint16(offset, value);
+      break;
+    case 24:
+      s1 = (value & 0xFFFF00) >> 8;
+      s2 = (value & 0x0000FF);
+      this._rawo.setUint16(offset, s1);
+      this._rawo.setUint8(offset + 2, s2);
+      break;
+    case 32:
+      this._rawo.setUint32(offset, value);
+      break;
+    case 64:
+      // Warning: JavaScript cannot handle 64-bit integers natively.
+      // This will give unexpected results for integers >= 2^53
+      s1 = Math.floor(value / Math.pow(2,32));
+      s2 = value - (s1 * Math.pow(2,32));
+      this._rawo.setUint32(offset, s1);
+      this._rawo.setUint32(offset + 4, s2);
+      break;
+    }
+    this._cursor.offset += (size >> 3);
+  } else {
+    this.size += (size >> 3);
+  }
+};
+
+ISOBox.prototype._writeString = function(size, str) {
+  for (var c = 0; c < size; c++) {
+    this._writeUint(8, str.charCodeAt(c));
+  }
+};
+
+ISOBox.prototype._writeTerminatedString = function(str) {
+  if (str.length === 0) {
+    return;
+  }
+  for (var c = 0; c < str.length; c++) {
+    this._writeUint(8, str.charCodeAt(c));
+  }
+  this._writeUint(8, 0);
+};
+
+ISOBox.prototype._writeTemplate = function(size, value) {
+  var pre = Math.floor(value);
+  var post = (value - pre) * Math.pow(2, size / 2);
+  this._writeUint(size / 2, pre);
+  this._writeUint(size / 2, post);
+};
+
+ISOBox.prototype._writeData = function(data) {
+  var i;
+  //data to copy
+  if (data) {
+    if (this._rawo) {
+      //Array and Uint8Array has also to be managed
+      if (data instanceof Array) {
+        var offset = this._cursor.offset - this._rawo.byteOffset;
+        for (var i = 0; i < data.length; i++) {
+          this._rawo.setInt8(offset + i, data[i]);
+        }
+        this._cursor.offset += data.length;
+      } 
+
+      if (data instanceof Uint8Array) {
+        this._root.bytes.set(data, this._cursor.offset);
+        this._cursor.offset += data.length;
+      }
+
+    } else {
+      //nothing to copy only size to compute
+      this.size += data.length;
+    }
+  }
+};
+
+ISOBox.prototype._writeUTF8String = function(string) {
+  var u = ISOBoxer.Utils.utf8ToByteArray(string);
+  if (this._rawo) {
+    var dataView = new DataView(this._rawo.buffer, this._cursor.offset, u.length);
+    for (var i = 0; i < u.length; i++) {
+      dataView.setUint8(i, u[i]);
+    }
+  } else {
+    this.size += u.length;
+  }
+};
+
+ISOBox.prototype._writeField = function(type, size, value) {
+  switch (type) {
+  case 'uint':
+    this._writeUint(size, value);
+    break;
+  case 'int':
+    this._writeInt(size, value);
+    break;
+  case 'template':
+    this._writeTemplate(size, value);
+    break;
+  case 'string':
+      if (size == -1) {
+        this._writeTerminatedString(value);
+      } else {
+        this._writeString(size, value);
+      }
+      break;
+  case 'data':
+    this._writeData(value);
+    break;
+  case 'utf8':
+    this._writeUTF8String(value);
+    break;
+  default:
+    break;
+  }
+};
+
+// ISO/IEC 14496-15:2014 - avc1 box
+ISOBox.prototype._boxProcessors['avc1'] = ISOBox.prototype._boxProcessors['encv'] = function() {
+  // SampleEntry fields
+  this._procFieldArray('reserved1', 6,    'uint', 8);
+  this._procField('data_reference_index', 'uint', 16);
+  // VisualSampleEntry fields
+  this._procField('pre_defined1',         'uint',     16);
+  this._procField('reserved2',            'uint',     16);
+  this._procFieldArray('pre_defined2', 3, 'uint',     32);
+  this._procField('width',                'uint',     16);
+  this._procField('height',               'uint',     16);
+  this._procField('horizresolution',      'template', 32);
+  this._procField('vertresolution',       'template', 32);
+  this._procField('reserved3',            'uint',     32);
+  this._procField('frame_count',          'uint',     16);
+  this._procFieldArray('compressorname', 32,'uint',    8);
+  this._procField('depth',                'uint',     16);
+  this._procField('pre_defined3',         'int',      16);
+  // AVCSampleEntry fields
+  this._procField('config', 'data', -1);
+};
+
+// ISO/IEC 14496-12:2012 - 8.7.2 Data Reference Box
+ISOBox.prototype._boxProcessors['dref'] = function() {
+  this._procFullBox();
+  this._procField('entry_count', 'uint', 32);
+  this._procSubBoxes('entries', this.entry_count);
+};
+
+// ISO/IEC 14496-12:2012 - 8.6.6 Edit List Box
+ISOBox.prototype._boxProcessors['elst'] = function() {
+  this._procFullBox();
+  this._procField('entry_count', 'uint', 32);
+  this._procEntries('entries', this.entry_count, function(entry) {
+    this._procEntryField(entry, 'segment_duration',     'uint', (this.version === 1) ? 64 : 32);
+    this._procEntryField(entry, 'media_time',           'int',  (this.version === 1) ? 64 : 32);
+    this._procEntryField(entry, 'media_rate_integer',   'int',  16);
+    this._procEntryField(entry, 'media_rate_fraction',  'int',  16);
+  });
+};
+
+// ISO/IEC 23009-1:2014 - 5.10.3.3 Event Message Box
+ISOBox.prototype._boxProcessors['emsg'] = function() {
+  this._procFullBox();
+  if (this.version == 1) {
+    this._procField('timescale',                'uint',   32);
+    this._procField('presentation_time',        'uint',   64);
+    this._procField('event_duration',           'uint',   32);
+    this._procField('id',                       'uint',   32);
+    this._procField('scheme_id_uri',            'string', -1);
+    this._procField('value',                    'string', -1);
+  } else {
+    this._procField('scheme_id_uri',            'string', -1);
+    this._procField('value',                    'string', -1);
+    this._procField('timescale',                'uint',   32);
+    this._procField('presentation_time_delta',  'uint',   32);
+    this._procField('event_duration',           'uint',   32);
+    this._procField('id',                       'uint',   32);
+  }
+  this._procField('message_data',             'data',   -1);
+};
+// ISO/IEC 14496-12:2012 - 8.1.2 Free Space Box
+ISOBox.prototype._boxProcessors['free'] = ISOBox.prototype._boxProcessors['skip'] = function() {
+  this._procField('data', 'data', -1);
+};
+
+// ISO/IEC 14496-12:2012 - 8.12.2 Original Format Box
+ISOBox.prototype._boxProcessors['frma'] = function() {
+  this._procField('data_format', 'uint', 32);
+};
+// ISO/IEC 14496-12:2012 - 4.3 File Type Box / 8.16.2 Segment Type Box
+ISOBox.prototype._boxProcessors['ftyp'] =
+ISOBox.prototype._boxProcessors['styp'] = function() {
+  this._procField('major_brand', 'string', 4);
+  this._procField('minor_version', 'uint', 32);
+  var nbCompatibleBrands = -1;
+  if (this._parsing) {
+    nbCompatibleBrands = (this._raw.byteLength - (this._cursor.offset - this._raw.byteOffset)) / 4;
+  }
+  this._procFieldArray('compatible_brands', nbCompatibleBrands, 'string', 4);
+};
+
+// ISO/IEC 14496-12:2012 - 8.4.3 Handler Reference Box
+ISOBox.prototype._boxProcessors['hdlr'] = function() {
+  this._procFullBox();
+  this._procField('pre_defined',      'uint',   32);
+  this._procField('handler_type',     'string', 4);
+  this._procFieldArray('reserved', 3, 'uint', 32);
+  this._procField('name',             'string', -1);
+};
+
+// ISO/IEC 14496-12:2012 - 8.1.1 Media Data Box
+ISOBox.prototype._boxProcessors['mdat'] = function() {
+  this._procField('data', 'data', -1);
+};
+
+// ISO/IEC 14496-12:2012 - 8.4.2 Media Header Box
+ISOBox.prototype._boxProcessors['mdhd'] = function() {
+  this._procFullBox();
+  this._procField('creation_time',      'uint', (this.version == 1) ? 64 : 32);
+  this._procField('modification_time',  'uint', (this.version == 1) ? 64 : 32);
+  this._procField('timescale',          'uint', 32);
+  this._procField('duration',           'uint', (this.version == 1) ? 64 : 32);
+  if (!this._parsing && typeof this.language === 'string') {
+    // In case of writing and language has been set as a string, then convert it into char codes array
+    this.language = ((this.language.charCodeAt(0) - 0x60) << 10) |
+                    ((this.language.charCodeAt(1) - 0x60) << 5) |
+                    ((this.language.charCodeAt(2) - 0x60));
+  }
+  this._procField('language',           'uint', 16);
+  if (this._parsing) {
+    this.language = String.fromCharCode(((this.language >> 10) & 0x1F) + 0x60,
+                                        ((this.language >> 5) & 0x1F) + 0x60,
+                                        (this.language & 0x1F) + 0x60);
+  }
+  this._procField('pre_defined',        'uint', 16);
+};
+
+// ISO/IEC 14496-12:2012 - 8.8.2 Movie Extends Header Box
+ISOBox.prototype._boxProcessors['mehd'] = function() {
+  this._procFullBox();
+  this._procField('fragment_duration', 'uint', (this.version == 1) ? 64 : 32);
+};
+
+// ISO/IEC 14496-12:2012 - 8.8.5 Movie Fragment Header Box
+ISOBox.prototype._boxProcessors['mfhd'] = function() {
+  this._procFullBox();
+  this._procField('sequence_number', 'uint', 32);
+};
+
+// ISO/IEC 14496-12:2012 - 8.8.11 Movie Fragment Random Access Box
+ISOBox.prototype._boxProcessors['mfro'] = function() {
+  this._procFullBox();
+  this._procField('mfra_size', 'uint', 32); // Called mfra_size to distinguish from the normal "size" attribute of a box
+};
+
+
+// ISO/IEC 14496-12:2012 - 8.5.2.2 mp4a box (use AudioSampleEntry definition and naming)
+ISOBox.prototype._boxProcessors['mp4a'] = ISOBox.prototype._boxProcessors['enca'] = function() {
+  // SampleEntry fields
+  this._procFieldArray('reserved1', 6,    'uint', 8);
+  this._procField('data_reference_index', 'uint', 16);
+  // AudioSampleEntry fields
+  this._procFieldArray('reserved2', 2,    'uint', 32);
+  this._procField('channelcount',         'uint', 16);
+  this._procField('samplesize',           'uint', 16);
+  this._procField('pre_defined',          'uint', 16);
+  this._procField('reserved3',            'uint', 16);
+  this._procField('samplerate',           'template', 32);
+  // ESDescriptor fields
+  this._procField('esds',                 'data', -1);
+};
+
+// ISO/IEC 14496-12:2012 - 8.2.2 Movie Header Box
+ISOBox.prototype._boxProcessors['mvhd'] = function() {
+  this._procFullBox();
+  this._procField('creation_time',      'uint',     (this.version == 1) ? 64 : 32);
+  this._procField('modification_time',  'uint',     (this.version == 1) ? 64 : 32);
+  this._procField('timescale',          'uint',     32);
+  this._procField('duration',           'uint',     (this.version == 1) ? 64 : 32);
+  this._procField('rate',               'template', 32);
+  this._procField('volume',             'template', 16);
+  this._procField('reserved1',          'uint',  16);
+  this._procFieldArray('reserved2', 2,  'uint',     32);
+  this._procFieldArray('matrix', 9,     'template', 32);
+  this._procFieldArray('pre_defined', 6,'uint',   32);
+  this._procField('next_track_ID',      'uint',     32);
+};
+
+// ISO/IEC 14496-30:2014 - WebVTT Cue Payload Box.
+ISOBox.prototype._boxProcessors['payl'] = function() {
+  this._procField('cue_text', 'utf8');
+};
+
+//ISO/IEC 23001-7:2011 - 8.1 Protection System Specific Header Box
+ISOBox.prototype._boxProcessors['pssh'] = function() {
+  this._procFullBox();
+  
+  this._procFieldArray('SystemID', 16, 'uint', 8);
+  this._procField('DataSize', 'uint', 32);
+  this._procFieldArray('Data', this.DataSize, 'uint', 8);
+};
+// ISO/IEC 14496-12:2012 - 8.12.5 Scheme Type Box
+ISOBox.prototype._boxProcessors['schm'] = function() {
+    this._procFullBox();
+    
+    this._procField('scheme_type', 'uint', 32);
+    this._procField('scheme_version', 'uint', 32);
+
+    if (this.flags & 0x000001) {
+        this._procField('scheme_uri', 'string', -1);
+    }
+};
+// ISO/IEC 14496-12:2012 - 8.6.4.1 sdtp box 
+ISOBox.prototype._boxProcessors['sdtp'] = function() {
+  this._procFullBox();
+
+  var sample_count = -1;
+  if (this._parsing) {
+    sample_count = (this._raw.byteLength - (this._cursor.offset - this._raw.byteOffset));
+  }
+
+  this._procFieldArray('sample_dependency_table', sample_count, 'uint', 8);
+};
+
+// ISO/IEC 14496-12:2012 - 8.16.3 Segment Index Box
+ISOBox.prototype._boxProcessors['sidx'] = function() {
+  this._procFullBox();
+  this._procField('reference_ID', 'uint', 32);
+  this._procField('timescale', 'uint', 32);
+  this._procField('earliest_presentation_time', 'uint', (this.version == 1) ? 64 : 32);
+  this._procField('first_offset', 'uint', (this.version == 1) ? 64 : 32);
+  this._procField('reserved', 'uint', 16);
+  this._procField('reference_count', 'uint', 16);
+  this._procEntries('references', this.reference_count, function(entry) {
+    if (!this._parsing) {
+      entry.reference  = (entry.reference_type  & 0x00000001) << 31;
+      entry.reference |= (entry.referenced_size & 0x7FFFFFFF);
+      entry.sap  = (entry.starts_with_SAP & 0x00000001) << 31;
+      entry.sap |= (entry.SAP_type        & 0x00000003) << 28;
+      entry.sap |= (entry.SAP_delta_time  & 0x0FFFFFFF);
+    }
+    this._procEntryField(entry, 'reference', 'uint', 32);
+    this._procEntryField(entry, 'subsegment_duration', 'uint', 32);
+    this._procEntryField(entry, 'sap', 'uint', 32);
+    if (this._parsing) {
+      entry.reference_type = (entry.reference >> 31) & 0x00000001;
+      entry.referenced_size = entry.reference & 0x7FFFFFFF;
+      entry.starts_with_SAP  = (entry.sap >> 31) & 0x00000001;
+      entry.SAP_type = (entry.sap >> 28) & 0x00000007;
+      entry.SAP_delta_time = (entry.sap  & 0x0FFFFFFF);
+    }
+  });
+};
+
+// ISO/IEC 14496-12:2012 - 8.4.5.3 Sound Media Header Box
+ISOBox.prototype._boxProcessors['smhd'] = function() {
+  this._procFullBox();
+  this._procField('balance',  'uint', 16);
+  this._procField('reserved', 'uint', 16);
+};
+
+// ISO/IEC 14496-12:2012 - 8.16.4 Subsegment Index Box
+ISOBox.prototype._boxProcessors['ssix'] = function() {
+  this._procFullBox();
+  this._procField('subsegment_count', 'uint', 32);
+  this._procEntries('subsegments', this.subsegment_count, function(subsegment) {
+    this._procEntryField(subsegment, 'ranges_count', 'uint', 32);
+    this._procSubEntries(subsegment, 'ranges', subsegment.ranges_count, function(range) {
+      this._procEntryField(range, 'level', 'uint', 8);
+      this._procEntryField(range, 'range_size', 'uint', 24);
+    });
+  });
+};
+
+// ISO/IEC 14496-12:2012 - 8.5.2 Sample Description Box
+ISOBox.prototype._boxProcessors['stsd'] = function() {
+  this._procFullBox();
+  this._procField('entry_count', 'uint', 32);
+  this._procSubBoxes('entries', this.entry_count);
+};
+
+// ISO/IEC 14496-12:2015 - 8.7.7 Sub-Sample Information Box
+ISOBox.prototype._boxProcessors['subs'] = function () {
+  this._procFullBox();
+  this._procField('entry_count', 'uint', 32);
+  this._procEntries('entries', this.entry_count, function(entry) {
+    this._procEntryField(entry, 'sample_delta', 'uint', 32);
+    this._procEntryField(entry, 'subsample_count', 'uint', 16);
+    this._procSubEntries(entry, 'subsamples', entry.subsample_count, function(subsample) {
+      this._procEntryField(subsample, 'subsample_size', 'uint', (this.version === 1) ? 32 : 16);
+      this._procEntryField(subsample, 'subsample_priority', 'uint', 8);
+      this._procEntryField(subsample, 'discardable', 'uint', 8);
+      this._procEntryField(subsample, 'codec_specific_parameters', 'uint', 32);
+    });
+  });
+};
+
+//ISO/IEC 23001-7:2011 - 8.2 Track Encryption Box
+ISOBox.prototype._boxProcessors['tenc'] = function() {
+    this._procFullBox();
+
+    this._procField('default_IsEncrypted', 'uint', 24);
+    this._procField('default_IV_size', 'uint', 8);
+    this._procFieldArray('default_KID', 16,    'uint', 8);
+ };
+
+// ISO/IEC 14496-12:2012 - 8.8.12 Track Fragmnent Decode Time
+ISOBox.prototype._boxProcessors['tfdt'] = function() {
+  this._procFullBox();
+  this._procField('baseMediaDecodeTime', 'uint', (this.version == 1) ? 64 : 32);
+};
+
+// ISO/IEC 14496-12:2012 - 8.8.7 Track Fragment Header Box
+ISOBox.prototype._boxProcessors['tfhd'] = function() {
+  this._procFullBox();
+  this._procField('track_ID', 'uint', 32);
+  if (this.flags & 0x01) this._procField('base_data_offset',          'uint', 64);
+  if (this.flags & 0x02) this._procField('sample_description_offset', 'uint', 32);
+  if (this.flags & 0x08) this._procField('default_sample_duration',   'uint', 32);
+  if (this.flags & 0x10) this._procField('default_sample_size',       'uint', 32);
+  if (this.flags & 0x20) this._procField('default_sample_flags',      'uint', 32);
+};
+
+// ISO/IEC 14496-12:2012 - 8.8.10 Track Fragment Random Access Box
+ISOBox.prototype._boxProcessors['tfra'] = function() {
+  this._procFullBox();
+  this._procField('track_ID', 'uint', 32);
+  if (!this._parsing) {
+    this.reserved = 0;
+    this.reserved |= (this.length_size_of_traf_num  & 0x00000030) << 4;
+    this.reserved |= (this.length_size_of_trun_num  & 0x0000000C) << 2;
+    this.reserved |= (this.length_size_of_sample_num  & 0x00000003);
+  }
+  this._procField('reserved', 'uint', 32);
+  if (this._parsing) {
+    this.length_size_of_traf_num = (this.reserved & 0x00000030) >> 4;
+    this.length_size_of_trun_num = (this.reserved & 0x0000000C) >> 2;
+    this.length_size_of_sample_num = (this.reserved & 0x00000003);
+  }
+  this._procField('number_of_entry', 'uint', 32);
+  this._procEntries('entries', this.number_of_entry, function(entry) {
+    this._procEntryField(entry, 'time', 'uint', (this.version === 1) ? 64 : 32);
+    this._procEntryField(entry, 'moof_offset', 'uint', (this.version === 1) ? 64 : 32);
+    this._procEntryField(entry, 'traf_number', 'uint', (this.length_size_of_traf_num + 1) * 8);
+    this._procEntryField(entry, 'trun_number', 'uint', (this.length_size_of_trun_num + 1) * 8);
+    this._procEntryField(entry, 'sample_number', 'uint', (this.length_size_of_sample_num + 1) * 8);
+  });
+};
+
+// ISO/IEC 14496-12:2012 - 8.3.2 Track Header Box
+ISOBox.prototype._boxProcessors['tkhd'] = function() {
+  this._procFullBox();
+  this._procField('creation_time',      'uint',     (this.version == 1) ? 64 : 32);
+  this._procField('modification_time',  'uint',     (this.version == 1) ? 64 : 32);
+  this._procField('track_ID',           'uint',     32);
+  this._procField('reserved1',          'uint',     32);
+  this._procField('duration',           'uint',     (this.version == 1) ? 64 : 32);
+  this._procFieldArray('reserved2', 2,  'uint',     32);
+  this._procField('layer',              'uint',     16);
+  this._procField('alternate_group',    'uint',     16);
+  this._procField('volume',             'template', 16);
+  this._procField('reserved3',          'uint',     16);
+  this._procFieldArray('matrix', 9,     'template', 32);
+  this._procField('width',              'template', 32);
+  this._procField('height',             'template', 32);
+};
+
+// ISO/IEC 14496-12:2012 - 8.8.3 Track Extends Box
+ISOBox.prototype._boxProcessors['trex'] = function() {
+  this._procFullBox();
+  this._procField('track_ID',                         'uint', 32);
+  this._procField('default_sample_description_index', 'uint', 32);
+  this._procField('default_sample_duration',          'uint', 32);
+  this._procField('default_sample_size',              'uint', 32);
+  this._procField('default_sample_flags',             'uint', 32);
+};
+
+// ISO/IEC 14496-12:2012 - 8.8.8 Track Run Box
+// Note: the 'trun' box has a direct relation to the 'tfhd' box for defaults.
+// These defaults are not set explicitly here, but are left to resolve for the user.
+ISOBox.prototype._boxProcessors['trun'] = function() {
+  this._procFullBox();
+  this._procField('sample_count', 'uint', 32);
+  if (this.flags & 0x1) this._procField('data_offset', 'int', 32);
+  if (this.flags & 0x4) this._procField('first_sample_flags', 'uint', 32);
+  this._procEntries('samples', this.sample_count, function(sample) {
+    if (this.flags & 0x100) this._procEntryField(sample, 'sample_duration', 'uint', 32);
+    if (this.flags & 0x200) this._procEntryField(sample, 'sample_size', 'uint', 32);
+    if (this.flags & 0x400) this._procEntryField(sample, 'sample_flags', 'uint', 32);
+    if (this.flags & 0x800) this._procEntryField(sample, 'sample_composition_time_offset', (this.version === 1) ? 'int' : 'uint',  32);
+  });
+};
+
+// ISO/IEC 14496-12:2012 - 8.7.2 Data Reference Box
+ISOBox.prototype._boxProcessors['url '] = ISOBox.prototype._boxProcessors['urn '] = function() {
+  this._procFullBox();
+  if (this.type === 'urn ') {
+    this._procField('name', 'string', -1);
+  }
+  this._procField('location', 'string', -1);
+};
+
+// ISO/IEC 14496-30:2014 - WebVTT Source Label Box
+ISOBox.prototype._boxProcessors['vlab'] = function() {
+  this._procField('source_label', 'utf8');
+};
+
+// ISO/IEC 14496-12:2012 - 8.4.5.2 Video Media Header Box
+ISOBox.prototype._boxProcessors['vmhd'] = function() {
+  this._procFullBox();
+  this._procField('graphicsmode', 'uint', 16);
+  this._procFieldArray('opcolor', 3, 'uint', 16);
+};
+
+// ISO/IEC 14496-30:2014 - WebVTT Configuration Box
+ISOBox.prototype._boxProcessors['vttC'] = function() {
+  this._procField('config', 'utf8');
+};
+
+// ISO/IEC 14496-30:2014 - WebVTT Empty Sample Box
+ISOBox.prototype._boxProcessors['vtte'] = function() {
+  // Nothing should happen here.
+};
+
+},{}],10:[function(_dereq_,module,exports){
+(function (Buffer){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// NOTE: These type checking functions intentionally don't use `instanceof`
+// because it is fragile and can be easily faked with `Object.create()`.
+
+function isArray(arg) {
+  if (Array.isArray) {
+    return Array.isArray(arg);
+  }
+  return objectToString(arg) === '[object Array]';
+}
+exports.isArray = isArray;
+
+function isBoolean(arg) {
+  return typeof arg === 'boolean';
+}
+exports.isBoolean = isBoolean;
+
+function isNull(arg) {
+  return arg === null;
+}
+exports.isNull = isNull;
+
+function isNullOrUndefined(arg) {
+  return arg == null;
+}
+exports.isNullOrUndefined = isNullOrUndefined;
+
+function isNumber(arg) {
+  return typeof arg === 'number';
+}
+exports.isNumber = isNumber;
+
+function isString(arg) {
+  return typeof arg === 'string';
+}
+exports.isString = isString;
+
+function isSymbol(arg) {
+  return typeof arg === 'symbol';
+}
+exports.isSymbol = isSymbol;
+
+function isUndefined(arg) {
+  return arg === void 0;
+}
+exports.isUndefined = isUndefined;
+
+function isRegExp(re) {
+  return objectToString(re) === '[object RegExp]';
+}
+exports.isRegExp = isRegExp;
+
+function isObject(arg) {
+  return typeof arg === 'object' && arg !== null;
+}
+exports.isObject = isObject;
+
+function isDate(d) {
+  return objectToString(d) === '[object Date]';
+}
+exports.isDate = isDate;
+
+function isError(e) {
+  return (objectToString(e) === '[object Error]' || e instanceof Error);
+}
+exports.isError = isError;
+
+function isFunction(arg) {
+  return typeof arg === 'function';
+}
+exports.isFunction = isFunction;
+
+function isPrimitive(arg) {
+  return arg === null ||
+         typeof arg === 'boolean' ||
+         typeof arg === 'number' ||
+         typeof arg === 'string' ||
+         typeof arg === 'symbol' ||  // ES6 symbol
+         typeof arg === 'undefined';
+}
+exports.isPrimitive = isPrimitive;
+
+exports.isBuffer = Buffer.isBuffer;
+
+function objectToString(o) {
+  return Object.prototype.toString.call(o);
+}
+
+}).call(this,{"isBuffer":_dereq_(22)})
+
+},{"22":22}],11:[function(_dereq_,module,exports){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var objectCreate = Object.create || objectCreatePolyfill
+var objectKeys = Object.keys || objectKeysPolyfill
+var bind = Function.prototype.bind || functionBindPolyfill
+
+function EventEmitter() {
+  if (!this._events || !Object.prototype.hasOwnProperty.call(this, '_events')) {
+    this._events = objectCreate(null);
+    this._eventsCount = 0;
+  }
+
+  this._maxListeners = this._maxListeners || undefined;
+}
+module.exports = EventEmitter;
+
+// Backwards-compat with node 0.10.x
+EventEmitter.EventEmitter = EventEmitter;
+
+EventEmitter.prototype._events = undefined;
+EventEmitter.prototype._maxListeners = undefined;
+
+// By default EventEmitters will print a warning if more than 10 listeners are
+// added to it. This is a useful default which helps finding memory leaks.
+var defaultMaxListeners = 10;
+
+var hasDefineProperty;
+try {
+  var o = {};
+  if (Object.defineProperty) Object.defineProperty(o, 'x', { value: 0 });
+  hasDefineProperty = o.x === 0;
+} catch (err) { hasDefineProperty = false }
+if (hasDefineProperty) {
+  Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
+    enumerable: true,
+    get: function() {
+      return defaultMaxListeners;
+    },
+    set: function(arg) {
+      // check whether the input is a positive number (whose value is zero or
+      // greater and not a NaN).
+      if (typeof arg !== 'number' || arg < 0 || arg !== arg)
+        throw new TypeError('"defaultMaxListeners" must be a positive number');
+      defaultMaxListeners = arg;
+    }
+  });
+} else {
+  EventEmitter.defaultMaxListeners = defaultMaxListeners;
+}
+
+// Obviously not all Emitters should be limited to 10. This function allows
+// that to be increased. Set to zero for unlimited.
+EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
+  if (typeof n !== 'number' || n < 0 || isNaN(n))
+    throw new TypeError('"n" argument must be a positive number');
+  this._maxListeners = n;
+  return this;
+};
+
+function $getMaxListeners(that) {
+  if (that._maxListeners === undefined)
+    return EventEmitter.defaultMaxListeners;
+  return that._maxListeners;
+}
+
+EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
+  return $getMaxListeners(this);
+};
+
+// These standalone emit* functions are used to optimize calling of event
+// handlers for fast cases because emit() itself often has a variable number of
+// arguments and can be deoptimized because of that. These functions always have
+// the same number of arguments and thus do not get deoptimized, so the code
+// inside them can execute faster.
+function emitNone(handler, isFn, self) {
+  if (isFn)
+    handler.call(self);
+  else {
+    var len = handler.length;
+    var listeners = arrayClone(handler, len);
+    for (var i = 0; i < len; ++i)
+      listeners[i].call(self);
+  }
+}
+function emitOne(handler, isFn, self, arg1) {
+  if (isFn)
+    handler.call(self, arg1);
+  else {
+    var len = handler.length;
+    var listeners = arrayClone(handler, len);
+    for (var i = 0; i < len; ++i)
+      listeners[i].call(self, arg1);
+  }
+}
+function emitTwo(handler, isFn, self, arg1, arg2) {
+  if (isFn)
+    handler.call(self, arg1, arg2);
+  else {
+    var len = handler.length;
+    var listeners = arrayClone(handler, len);
+    for (var i = 0; i < len; ++i)
+      listeners[i].call(self, arg1, arg2);
+  }
+}
+function emitThree(handler, isFn, self, arg1, arg2, arg3) {
+  if (isFn)
+    handler.call(self, arg1, arg2, arg3);
+  else {
+    var len = handler.length;
+    var listeners = arrayClone(handler, len);
+    for (var i = 0; i < len; ++i)
+      listeners[i].call(self, arg1, arg2, arg3);
+  }
+}
+
+function emitMany(handler, isFn, self, args) {
+  if (isFn)
+    handler.apply(self, args);
+  else {
+    var len = handler.length;
+    var listeners = arrayClone(handler, len);
+    for (var i = 0; i < len; ++i)
+      listeners[i].apply(self, args);
+  }
+}
+
+EventEmitter.prototype.emit = function emit(type) {
+  var er, handler, len, args, i, events;
+  var doError = (type === 'error');
+
+  events = this._events;
+  if (events)
+    doError = (doError && events.error == null);
+  else if (!doError)
+    return false;
+
+  // If there is no 'error' event listener then throw.
+  if (doError) {
+    if (arguments.length > 1)
+      er = arguments[1];
+    if (er instanceof Error) {
+      throw er; // Unhandled 'error' event
+    } else {
+      // At least give some kind of context to the user
+      var err = new Error('Unhandled "error" event. (' + er + ')');
+      err.context = er;
+      throw err;
+    }
+    return false;
+  }
+
+  handler = events[type];
+
+  if (!handler)
+    return false;
+
+  var isFn = typeof handler === 'function';
+  len = arguments.length;
+  switch (len) {
+      // fast cases
+    case 1:
+      emitNone(handler, isFn, this);
+      break;
+    case 2:
+      emitOne(handler, isFn, this, arguments[1]);
+      break;
+    case 3:
+      emitTwo(handler, isFn, this, arguments[1], arguments[2]);
+      break;
+    case 4:
+      emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]);
+      break;
+      // slower
+    default:
+      args = new Array(len - 1);
+      for (i = 1; i < len; i++)
+        args[i - 1] = arguments[i];
+      emitMany(handler, isFn, this, args);
+  }
+
+  return true;
+};
+
+function _addListener(target, type, listener, prepend) {
+  var m;
+  var events;
+  var existing;
+
+  if (typeof listener !== 'function')
+    throw new TypeError('"listener" argument must be a function');
+
+  events = target._events;
+  if (!events) {
+    events = target._events = objectCreate(null);
+    target._eventsCount = 0;
+  } else {
+    // To avoid recursion in the case that type === "newListener"! Before
+    // adding it to the listeners, first emit "newListener".
+    if (events.newListener) {
+      target.emit('newListener', type,
+          listener.listener ? listener.listener : listener);
+
+      // Re-assign `events` because a newListener handler could have caused the
+      // this._events to be assigned to a new object
+      events = target._events;
+    }
+    existing = events[type];
+  }
+
+  if (!existing) {
+    // Optimize the case of one listener. Don't need the extra array object.
+    existing = events[type] = listener;
+    ++target._eventsCount;
+  } else {
+    if (typeof existing === 'function') {
+      // Adding the second element, need to change to array.
+      existing = events[type] =
+          prepend ? [listener, existing] : [existing, listener];
+    } else {
+      // If we've already got an array, just append.
+      if (prepend) {
+        existing.unshift(listener);
+      } else {
+        existing.push(listener);
+      }
+    }
+
+    // Check for listener leak
+    if (!existing.warned) {
+      m = $getMaxListeners(target);
+      if (m && m > 0 && existing.length > m) {
+        existing.warned = true;
+        var w = new Error('Possible EventEmitter memory leak detected. ' +
+            existing.length + ' "' + String(type) + '" listeners ' +
+            'added. Use emitter.setMaxListeners() to ' +
+            'increase limit.');
+        w.name = 'MaxListenersExceededWarning';
+        w.emitter = target;
+        w.type = type;
+        w.count = existing.length;
+        if (typeof console === 'object' && console.warn) {
+          console.warn('%s: %s', w.name, w.message);
+        }
+      }
+    }
+  }
+
+  return target;
+}
+
+EventEmitter.prototype.addListener = function addListener(type, listener) {
+  return _addListener(this, type, listener, false);
+};
+
+EventEmitter.prototype.on = EventEmitter.prototype.addListener;
+
+EventEmitter.prototype.prependListener =
+    function prependListener(type, listener) {
+      return _addListener(this, type, listener, true);
+    };
+
+function onceWrapper() {
+  if (!this.fired) {
+    this.target.removeListener(this.type, this.wrapFn);
+    this.fired = true;
+    switch (arguments.length) {
+      case 0:
+        return this.listener.call(this.target);
+      case 1:
+        return this.listener.call(this.target, arguments[0]);
+      case 2:
+        return this.listener.call(this.target, arguments[0], arguments[1]);
+      case 3:
+        return this.listener.call(this.target, arguments[0], arguments[1],
+            arguments[2]);
+      default:
+        var args = new Array(arguments.length);
+        for (var i = 0; i < args.length; ++i)
+          args[i] = arguments[i];
+        this.listener.apply(this.target, args);
+    }
+  }
+}
+
+function _onceWrap(target, type, listener) {
+  var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener };
+  var wrapped = bind.call(onceWrapper, state);
+  wrapped.listener = listener;
+  state.wrapFn = wrapped;
+  return wrapped;
+}
+
+EventEmitter.prototype.once = function once(type, listener) {
+  if (typeof listener !== 'function')
+    throw new TypeError('"listener" argument must be a function');
+  this.on(type, _onceWrap(this, type, listener));
+  return this;
+};
+
+EventEmitter.prototype.prependOnceListener =
+    function prependOnceListener(type, listener) {
+      if (typeof listener !== 'function')
+        throw new TypeError('"listener" argument must be a function');
+      this.prependListener(type, _onceWrap(this, type, listener));
+      return this;
+    };
+
+// Emits a 'removeListener' event if and only if the listener was removed.
+EventEmitter.prototype.removeListener =
+    function removeListener(type, listener) {
+      var list, events, position, i, originalListener;
+
+      if (typeof listener !== 'function')
+        throw new TypeError('"listener" argument must be a function');
+
+      events = this._events;
+      if (!events)
+        return this;
+
+      list = events[type];
+      if (!list)
+        return this;
+
+      if (list === listener || list.listener === listener) {
+        if (--this._eventsCount === 0)
+          this._events = objectCreate(null);
+        else {
+          delete events[type];
+          if (events.removeListener)
+            this.emit('removeListener', type, list.listener || listener);
+        }
+      } else if (typeof list !== 'function') {
+        position = -1;
+
+        for (i = list.length - 1; i >= 0; i--) {
+          if (list[i] === listener || list[i].listener === listener) {
+            originalListener = list[i].listener;
+            position = i;
+            break;
+          }
+        }
+
+        if (position < 0)
+          return this;
+
+        if (position === 0)
+          list.shift();
+        else
+          spliceOne(list, position);
+
+        if (list.length === 1)
+          events[type] = list[0];
+
+        if (events.removeListener)
+          this.emit('removeListener', type, originalListener || listener);
+      }
+
+      return this;
+    };
+
+EventEmitter.prototype.removeAllListeners =
+    function removeAllListeners(type) {
+      var listeners, events, i;
+
+      events = this._events;
+      if (!events)
+        return this;
+
+      // not listening for removeListener, no need to emit
+      if (!events.removeListener) {
+        if (arguments.length === 0) {
+          this._events = objectCreate(null);
+          this._eventsCount = 0;
+        } else if (events[type]) {
+          if (--this._eventsCount === 0)
+            this._events = objectCreate(null);
+          else
+            delete events[type];
+        }
+        return this;
+      }
+
+      // emit removeListener for all listeners on all events
+      if (arguments.length === 0) {
+        var keys = objectKeys(events);
+        var key;
+        for (i = 0; i < keys.length; ++i) {
+          key = keys[i];
+          if (key === 'removeListener') continue;
+          this.removeAllListeners(key);
+        }
+        this.removeAllListeners('removeListener');
+        this._events = objectCreate(null);
+        this._eventsCount = 0;
+        return this;
+      }
+
+      listeners = events[type];
+
+      if (typeof listeners === 'function') {
+        this.removeListener(type, listeners);
+      } else if (listeners) {
+        // LIFO order
+        for (i = listeners.length - 1; i >= 0; i--) {
+          this.removeListener(type, listeners[i]);
+        }
+      }
+
+      return this;
+    };
+
+function _listeners(target, type, unwrap) {
+  var events = target._events;
+
+  if (!events)
+    return [];
+
+  var evlistener = events[type];
+  if (!evlistener)
+    return [];
+
+  if (typeof evlistener === 'function')
+    return unwrap ? [evlistener.listener || evlistener] : [evlistener];
+
+  return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);
+}
+
+EventEmitter.prototype.listeners = function listeners(type) {
+  return _listeners(this, type, true);
+};
+
+EventEmitter.prototype.rawListeners = function rawListeners(type) {
+  return _listeners(this, type, false);
+};
+
+EventEmitter.listenerCount = function(emitter, type) {
+  if (typeof emitter.listenerCount === 'function') {
+    return emitter.listenerCount(type);
+  } else {
+    return listenerCount.call(emitter, type);
+  }
+};
+
+EventEmitter.prototype.listenerCount = listenerCount;
+function listenerCount(type) {
+  var events = this._events;
+
+  if (events) {
+    var evlistener = events[type];
+
+    if (typeof evlistener === 'function') {
+      return 1;
+    } else if (evlistener) {
+      return evlistener.length;
+    }
+  }
+
+  return 0;
+}
+
+EventEmitter.prototype.eventNames = function eventNames() {
+  return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : [];
+};
+
+// About 1.5x faster than the two-arg version of Array#splice().
+function spliceOne(list, index) {
+  for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1)
+    list[i] = list[k];
+  list.pop();
+}
+
+function arrayClone(arr, n) {
+  var copy = new Array(n);
+  for (var i = 0; i < n; ++i)
+    copy[i] = arr[i];
+  return copy;
+}
+
+function unwrapListeners(arr) {
+  var ret = new Array(arr.length);
+  for (var i = 0; i < ret.length; ++i) {
+    ret[i] = arr[i].listener || arr[i];
+  }
+  return ret;
+}
+
+function objectCreatePolyfill(proto) {
+  var F = function() {};
+  F.prototype = proto;
+  return new F;
+}
+function objectKeysPolyfill(obj) {
+  var keys = [];
+  for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k)) {
+    keys.push(k);
+  }
+  return k;
+}
+function functionBindPolyfill(context) {
+  var fn = this;
+  return function () {
+    return fn.apply(context, arguments);
+  };
+}
+
+},{}],12:[function(_dereq_,module,exports){
+'use strict';
+
+var isArray = Array.isArray;
+var keyList = Object.keys;
+var hasProp = Object.prototype.hasOwnProperty;
+
+module.exports = function equal(a, b) {
+  if (a === b) return true;
+
+  if (a && b && typeof a == 'object' && typeof b == 'object') {
+    var arrA = isArray(a)
+      , arrB = isArray(b)
+      , i
+      , length
+      , key;
+
+    if (arrA && arrB) {
+      length = a.length;
+      if (length != b.length) return false;
+      for (i = length; i-- !== 0;)
+        if (!equal(a[i], b[i])) return false;
+      return true;
+    }
+
+    if (arrA != arrB) return false;
+
+    var dateA = a instanceof Date
+      , dateB = b instanceof Date;
+    if (dateA != dateB) return false;
+    if (dateA && dateB) return a.getTime() == b.getTime();
+
+    var regexpA = a instanceof RegExp
+      , regexpB = b instanceof RegExp;
+    if (regexpA != regexpB) return false;
+    if (regexpA && regexpB) return a.toString() == b.toString();
+
+    var keys = keyList(a);
+    length = keys.length;
+
+    if (length !== keyList(b).length)
+      return false;
+
+    for (i = length; i-- !== 0;)
+      if (!hasProp.call(b, keys[i])) return false;
+
+    for (i = length; i-- !== 0;) {
+      key = keys[i];
+      if (!equal(a[key], b[key])) return false;
+    }
+
+    return true;
+  }
+
+  return a!==a && b!==b;
+};
+
+},{}],13:[function(_dereq_,module,exports){
+exports.read = function (buffer, offset, isLE, mLen, nBytes) {
+  var e, m
+  var eLen = (nBytes * 8) - mLen - 1
+  var eMax = (1 << eLen) - 1
+  var eBias = eMax >> 1
+  var nBits = -7
+  var i = isLE ? (nBytes - 1) : 0
+  var d = isLE ? -1 : 1
+  var s = buffer[offset + i]
+
+  i += d
+
+  e = s & ((1 << (-nBits)) - 1)
+  s >>= (-nBits)
+  nBits += eLen
+  for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {}
+
+  m = e & ((1 << (-nBits)) - 1)
+  e >>= (-nBits)
+  nBits += mLen
+  for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {}
+
+  if (e === 0) {
+    e = 1 - eBias
+  } else if (e === eMax) {
+    return m ? NaN : ((s ? -1 : 1) * Infinity)
+  } else {
+    m = m + Math.pow(2, mLen)
+    e = e - eBias
+  }
+  return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
+}
+
+exports.write = function (buffer, value, offset, isLE, mLen, nBytes) {
+  var e, m, c
+  var eLen = (nBytes * 8) - mLen - 1
+  var eMax = (1 << eLen) - 1
+  var eBias = eMax >> 1
+  var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
+  var i = isLE ? 0 : (nBytes - 1)
+  var d = isLE ? 1 : -1
+  var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0
+
+  value = Math.abs(value)
+
+  if (isNaN(value) || value === Infinity) {
+    m = isNaN(value) ? 1 : 0
+    e = eMax
+  } else {
+    e = Math.floor(Math.log(value) / Math.LN2)
+    if (value * (c = Math.pow(2, -e)) < 1) {
+      e--
+      c *= 2
+    }
+    if (e + eBias >= 1) {
+      value += rt / c
+    } else {
+      value += rt * Math.pow(2, 1 - eBias)
+    }
+    if (value * c >= 2) {
+      e++
+      c /= 2
+    }
+
+    if (e + eBias >= eMax) {
+      m = 0
+      e = eMax
+    } else if (e + eBias >= 1) {
+      m = ((value * c) - 1) * Math.pow(2, mLen)
+      e = e + eBias
+    } else {
+      m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)
+      e = 0
+    }
+  }
+
+  for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
+
+  e = (e << mLen) | m
+  eLen += mLen
+  for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
+
+  buffer[offset + i - d] |= s * 128
+}
+
+},{}],14:[function(_dereq_,module,exports){
+/* \r
+ * Copyright (c) 2016, Pierre-Anthony Lemieux <pal@sandflow.com>\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * * Redistributions of source code must retain the above copyright notice, this\r
+ *   list of conditions and the following disclaimer.\r
+ * * Redistributions in binary form must reproduce the above copyright notice,\r
+ *   this list of conditions and the following disclaimer in the documentation\r
+ *   and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+/**\r
+ * @module imscDoc\r
+ */\r
+\r
+;\r
+(function (imscDoc, sax, imscNames, imscStyles, imscUtils) {\r
+\r
+\r
+    /**\r
+     * Allows a client to provide callbacks to handle children of the <metadata> element\r
+     * @typedef {Object} MetadataHandler\r
+     * @property {?OpenTagCallBack} onOpenTag\r
+     * @property {?CloseTagCallBack} onCloseTag\r
+     * @property {?TextCallBack} onText\r
+     */\r
+\r
+    /**\r
+     * Called when the opening tag of an element node is encountered.\r
+     * @callback OpenTagCallBack\r
+     * @param {string} ns Namespace URI of the element\r
+     * @param {string} name Local name of the element\r
+     * @param {Object[]} attributes List of attributes, each consisting of a\r
+     *                              `uri`, `name` and `value`\r
+     */\r
+\r
+    /**\r
+     * Called when the closing tag of an element node is encountered.\r
+     * @callback CloseTagCallBack\r
+     */\r
+\r
+    /**\r
+     * Called when a text node is encountered.\r
+     * @callback TextCallBack\r
+     * @param {string} contents Contents of the text node\r
+     */\r
+\r
+    /**\r
+     * Parses an IMSC1 document into an opaque in-memory representation that exposes\r
+     * a single method <pre>getMediaTimeEvents()</pre> that returns a list of time\r
+     * offsets (in seconds) of the ISD, i.e. the points in time where the visual\r
+     * representation of the document change. `metadataHandler` allows the caller to\r
+     * be called back when nodes are present in <metadata> elements. \r
+     * \r
+     * @param {string} xmlstring XML document\r
+     * @param {?module:imscUtils.ErrorHandler} errorHandler Error callback\r
+     * @param {?MetadataHandler} metadataHandler Callback for <Metadata> elements\r
+     * @returns {Object} Opaque in-memory representation of an IMSC1 document\r
+     */\r
+\r
+    imscDoc.fromXML = function (xmlstring, errorHandler, metadataHandler) {\r
+        var p = sax.parser(true, {xmlns: true});\r
+        var estack = [];\r
+        var xmllangstack = [];\r
+        var xmlspacestack = [];\r
+        var metadata_depth = 0;\r
+        var doc = null;\r
+\r
+        p.onclosetag = function (node) {\r
+\r
+            if (estack[0] instanceof Styling) {\r
+\r
+                /* flatten chained referential styling */\r
+\r
+                for (var sid in estack[0].styles) {\r
+\r
+                    mergeChainedStyles(estack[0], estack[0].styles[sid], errorHandler);\r
+\r
+                }\r
+\r
+            } else if (estack[0] instanceof P || estack[0] instanceof Span) {\r
+\r
+                /* merge anonymous spans */\r
+\r
+                if (estack[0].contents.length > 1) {\r
+\r
+                    var cs = [estack[0].contents[0]];\r
+\r
+                    var c;\r
+\r
+                    for (c = 1; c < estack[0].contents.length; c++) {\r
+\r
+                        if (estack[0].contents[c] instanceof AnonymousSpan &&\r
+                                cs[cs.length - 1] instanceof AnonymousSpan) {\r
+\r
+                            cs[cs.length - 1].text += estack[0].contents[c].text;\r
+\r
+                        } else {\r
+\r
+                            cs.push(estack[0].contents[c]);\r
+\r
+                        }\r
+\r
+                    }\r
+\r
+                    estack[0].contents = cs;\r
+\r
+                }\r
+\r
+                // remove redundant nested anonymous spans (9.3.3(1)(c))\r
+\r
+                if (estack[0] instanceof Span &&\r
+                        estack[0].contents.length === 1 &&\r
+                        estack[0].contents[0] instanceof AnonymousSpan) {\r
+\r
+                    estack[0].text = estack[0].contents[0].text;\r
+                    delete estack[0].contents;\r
+\r
+                }\r
+\r
+            } else if (estack[0] instanceof ForeignElement) {\r
+\r
+                if (estack[0].node.uri === imscNames.ns_tt &&\r
+                        estack[0].node.local === 'metadata') {\r
+\r
+                    /* leave the metadata element */\r
+\r
+                    metadata_depth--;\r
+\r
+                } else if (metadata_depth > 0 &&\r
+                        metadataHandler &&\r
+                        'onCloseTag' in metadataHandler) {\r
+\r
+                    /* end of child of metadata element */\r
+\r
+                    metadataHandler.onCloseTag();\r
+\r
+                }\r
+\r
+            }\r
+\r
+            // TODO: delete stylerefs?\r
+\r
+            // maintain the xml:space stack\r
+\r
+            xmlspacestack.shift();\r
+\r
+            // maintain the xml:lang stack\r
+\r
+            xmllangstack.shift();\r
+\r
+            // prepare for the next element\r
+\r
+            estack.shift();\r
+        };\r
+\r
+        p.ontext = function (str) {\r
+\r
+            if (estack[0] === undefined) {\r
+\r
+                /* ignoring text outside of elements */\r
+\r
+            } else if (estack[0] instanceof Span || estack[0] instanceof P) {\r
+\r
+                /* create an anonymous span */\r
+\r
+                var s = new AnonymousSpan();\r
+\r
+                s.initFromText(doc, estack[0], str, xmlspacestack[0], errorHandler);\r
+\r
+                estack[0].contents.push(s);\r
+\r
+            } else if (estack[0] instanceof ForeignElement &&\r
+                    metadata_depth > 0 &&\r
+                    metadataHandler &&\r
+                    'onText' in metadataHandler) {\r
+\r
+                /* text node within a child of metadata element */\r
+\r
+                metadataHandler.onText(str);\r
+\r
+            }\r
+\r
+        };\r
+\r
+\r
+        p.onopentag = function (node) {\r
+\r
+            // maintain the xml:space stack\r
+\r
+            var xmlspace = node.attributes["xml:space"];\r
+\r
+            if (xmlspace) {\r
+\r
+                xmlspacestack.unshift(xmlspace.value);\r
+\r
+            } else {\r
+\r
+                if (xmlspacestack.length === 0) {\r
+\r
+                    xmlspacestack.unshift("default");\r
+\r
+                } else {\r
+\r
+                    xmlspacestack.unshift(xmlspacestack[0]);\r
+\r
+                }\r
+\r
+            }\r
+\r
+            /* maintain the xml:lang stack */\r
+\r
+\r
+            var xmllang = node.attributes["xml:lang"];\r
+\r
+            if (xmllang) {\r
+\r
+                xmllangstack.unshift(xmllang.value);\r
+\r
+            } else {\r
+\r
+                if (xmllangstack.length === 0) {\r
+\r
+                    xmllangstack.unshift("");\r
+\r
+                } else {\r
+\r
+                    xmllangstack.unshift(xmllangstack[0]);\r
+\r
+                }\r
+\r
+            }\r
+\r
+\r
+            /* process the element */\r
+\r
+            if (node.uri === imscNames.ns_tt) {\r
+\r
+                if (node.local === 'tt') {\r
+\r
+                    if (doc !== null) {\r
+\r
+                        reportFatal(errorHandler, "Two <tt> elements at (" + this.line + "," + this.column + ")");\r
+\r
+                    }\r
+\r
+                    doc = new TT();\r
+\r
+                    doc.initFromNode(node, errorHandler);\r
+\r
+                    estack.unshift(doc);\r
+\r
+                } else if (node.local === 'head') {\r
+\r
+                    if (!(estack[0] instanceof TT)) {\r
+                        reportFatal(errorHandler, "Parent of <head> element is not <tt> at (" + this.line + "," + this.column + ")");\r
+                    }\r
+\r
+                    if (doc.head !== null) {\r
+                        reportFatal("Second <head> element at (" + this.line + "," + this.column + ")");\r
+                    }\r
+\r
+                    doc.head = new Head();\r
+\r
+                    estack.unshift(doc.head);\r
+\r
+                } else if (node.local === 'styling') {\r
+\r
+                    if (!(estack[0] instanceof Head)) {\r
+                        reportFatal(errorHandler, "Parent of <styling> element is not <head> at (" + this.line + "," + this.column + ")");\r
+                    }\r
+\r
+                    if (doc.head.styling !== null) {\r
+                        reportFatal("Second <styling> element at (" + this.line + "," + this.column + ")");\r
+                    }\r
+\r
+                    doc.head.styling = new Styling();\r
+\r
+                    estack.unshift(doc.head.styling);\r
+\r
+                } else if (node.local === 'style') {\r
+\r
+                    var s;\r
+\r
+                    if (estack[0] instanceof Styling) {\r
+\r
+                        s = new Style();\r
+\r
+                        s.initFromNode(node, errorHandler);\r
+\r
+                        /* ignore <style> element missing @id */\r
+\r
+                        if (!s.id) {\r
+\r
+                            reportError(errorHandler, "<style> element missing @id attribute");\r
+\r
+                        } else {\r
+\r
+                            doc.head.styling.styles[s.id] = s;\r
+\r
+                        }\r
+\r
+                        estack.unshift(s);\r
+\r
+                    } else if (estack[0] instanceof Region) {\r
+\r
+                        /* nested styles can be merged with specified styles\r
+                         * immediately, with lower priority\r
+                         * (see 8.4.4.2(3) at TTML1 )\r
+                         */\r
+\r
+                        s = new Style();\r
+\r
+                        s.initFromNode(node, errorHandler);\r
+\r
+                        mergeStylesIfNotPresent(s.styleAttrs, estack[0].styleAttrs);\r
+\r
+                        estack.unshift(s);\r
+\r
+                    } else {\r
+\r
+                        reportFatal(errorHandler, "Parent of <style> element is not <styling> or <region> at (" + this.line + "," + this.column + ")");\r
+\r
+                    }\r
+\r
+                } else if (node.local === 'layout') {\r
+\r
+                    if (!(estack[0] instanceof Head)) {\r
+\r
+                        reportFatal(errorHandler, "Parent of <layout> element is not <head> at " + this.line + "," + this.column + ")");\r
+\r
+                    }\r
+\r
+                    if (doc.head.layout !== null) {\r
+\r
+                        reportFatal(errorHandler, "Second <layout> element at " + this.line + "," + this.column + ")");\r
+\r
+                    }\r
+\r
+                    doc.head.layout = new Layout();\r
+\r
+                    estack.unshift(doc.head.layout);\r
+\r
+                } else if (node.local === 'region') {\r
+\r
+                    if (!(estack[0] instanceof Layout)) {\r
+                        reportFatal(errorHandler, "Parent of <region> element is not <layout> at " + this.line + "," + this.column + ")");\r
+                    }\r
+\r
+                    var r = new Region();\r
+\r
+                    r.initFromNode(doc, node, errorHandler);\r
+\r
+                    if (!r.id || r.id in doc.head.layout.regions) {\r
+\r
+                        reportError(errorHandler, "Ignoring <region> with duplicate or missing @id at " + this.line + "," + this.column + ")");\r
+\r
+                    } else {\r
+\r
+                        doc.head.layout.regions[r.id] = r;\r
+\r
+                    }\r
+\r
+                    estack.unshift(r);\r
+\r
+                } else if (node.local === 'body') {\r
+\r
+                    if (!(estack[0] instanceof TT)) {\r
+\r
+                        reportFatal(errorHandler, "Parent of <body> element is not <tt> at " + this.line + "," + this.column + ")");\r
+\r
+                    }\r
+\r
+                    if (doc.body !== null) {\r
+\r
+                        reportFatal(errorHandler, "Second <body> element at " + this.line + "," + this.column + ")");\r
+\r
+                    }\r
+\r
+                    var b = new Body();\r
+\r
+                    b.initFromNode(doc, node, errorHandler);\r
+\r
+                    doc.body = b;\r
+\r
+                    estack.unshift(b);\r
+\r
+                } else if (node.local === 'div') {\r
+\r
+                    if (!(estack[0] instanceof Div || estack[0] instanceof Body)) {\r
+\r
+                        reportFatal(errorHandler, "Parent of <div> element is not <body> or <div> at " + this.line + "," + this.column + ")");\r
+\r
+                    }\r
+\r
+                    var d = new Div();\r
+\r
+                    d.initFromNode(doc, estack[0], node, errorHandler);\r
+\r
+                    estack[0].contents.push(d);\r
+\r
+                    estack.unshift(d);\r
+\r
+                } else if (node.local === 'p') {\r
+\r
+                    if (!(estack[0] instanceof Div)) {\r
+\r
+                        reportFatal(errorHandler, "Parent of <p> element is not <div> at " + this.line + "," + this.column + ")");\r
+\r
+                    }\r
+\r
+                    var p = new P();\r
+\r
+                    p.initFromNode(doc, estack[0], node, errorHandler);\r
+\r
+                    estack[0].contents.push(p);\r
+\r
+                    estack.unshift(p);\r
+\r
+                } else if (node.local === 'span') {\r
+\r
+                    if (!(estack[0] instanceof Span || estack[0] instanceof P)) {\r
+\r
+                        reportFatal(errorHandler, "Parent of <span> element is not <span> or <p> at " + this.line + "," + this.column + ")");\r
+\r
+                    }\r
+\r
+                    var ns = new Span();\r
+\r
+                    ns.initFromNode(doc, estack[0], node, xmlspacestack[0], errorHandler);\r
+\r
+                    estack[0].contents.push(ns);\r
+\r
+                    estack.unshift(ns);\r
+\r
+                } else if (node.local === 'br') {\r
+\r
+                    if (!(estack[0] instanceof Span || estack[0] instanceof P)) {\r
+\r
+                        reportFatal(errorHandler, "Parent of <br> element is not <span> or <p> at " + this.line + "," + this.column + ")");\r
+\r
+                    }\r
+\r
+                    var nb = new Br();\r
+\r
+                    nb.initFromNode(doc, estack[0], node, errorHandler);\r
+\r
+                    estack[0].contents.push(nb);\r
+\r
+                    estack.unshift(nb);\r
+\r
+                } else if (node.local === 'set') {\r
+\r
+                    if (!(estack[0] instanceof Span ||\r
+                            estack[0] instanceof P ||\r
+                            estack[0] instanceof Div ||\r
+                            estack[0] instanceof Body ||\r
+                            estack[0] instanceof Region ||\r
+                            estack[0] instanceof Br)) {\r
+\r
+                        reportFatal(errorHandler, "Parent of <set> element is not a content element or a region at " + this.line + "," + this.column + ")");\r
+\r
+                    }\r
+\r
+                    var st = new Set();\r
+\r
+                    st.initFromNode(doc, estack[0], node, errorHandler);\r
+\r
+                    estack[0].sets.push(st);\r
+\r
+                    estack.unshift(st);\r
+\r
+                } else {\r
+\r
+                    /* element in the TT namespace, but not a content element */\r
+\r
+                    estack.unshift(new ForeignElement(node));\r
+                }\r
+\r
+            } else {\r
+\r
+                /* ignore elements not in the TTML namespace unless in metadata element */\r
+\r
+                estack.unshift(new ForeignElement(node));\r
+\r
+            }\r
+\r
+            /* handle metadata callbacks */\r
+\r
+            if (estack[0] instanceof ForeignElement) {\r
+\r
+                if (node.uri === imscNames.ns_tt &&\r
+                        node.local === 'metadata') {\r
+\r
+                    /* enter the metadata element */\r
+\r
+                    metadata_depth++;\r
+\r
+                } else if (\r
+                        metadata_depth > 0 &&\r
+                        metadataHandler &&\r
+                        'onOpenTag' in metadataHandler\r
+                        ) {\r
+\r
+                    /* start of child of metadata element */\r
+\r
+                    var attrs = [];\r
+\r
+                    for (var a in node.attributes) {\r
+                        attrs[node.attributes[a].uri + " " + node.attributes[a].local] =\r
+                                {\r
+                                    uri: node.attributes[a].uri,\r
+                                    local: node.attributes[a].local,\r
+                                    value: node.attributes[a].value\r
+                                };\r
+                    }\r
+\r
+                    metadataHandler.onOpenTag(node.uri, node.local, attrs);\r
+\r
+                }\r
+\r
+            }\r
+\r
+        };\r
+\r
+        // parse the document\r
+\r
+        p.write(xmlstring).close();\r
+\r
+        // all referential styling has been flatten, so delete the styling elements if there is a head\r
+        // otherwise create an empty head\r
+\r
+        if (doc.head !== null) {\r
+            delete doc.head.styling;\r
+        } else {\r
+            doc.head = new Head();\r
+        }\r
+\r
+        // create default region if no regions specified\r
+\r
+        if (doc.head.layout === null) {\r
+\r
+            doc.head.layout = new Layout();\r
+\r
+        }\r
+\r
+        var hasRegions = false;\r
+\r
+        /* AFAIK the only way to determine whether an object has members */\r
+\r
+        for (var i in doc.head.layout.regions) {\r
+\r
+            hasRegions = true;\r
+\r
+            break;\r
+\r
+        }\r
+\r
+        if (!hasRegions) {\r
+\r
+            /* create default region */\r
+\r
+            var dr = Region.prototype.createDefaultRegion();\r
+\r
+            doc.head.layout.regions[dr.id] = dr;\r
+\r
+        }\r
+\r
+        /* resolve desired timing for regions */\r
+\r
+        for (var region_i in doc.head.layout.regions) {\r
+\r
+            resolveTiming(doc, doc.head.layout.regions[region_i], null, null);\r
+\r
+        }\r
+\r
+        /* resolve desired timing for content elements */\r
+\r
+        if (doc.body) {\r
+            resolveTiming(doc, doc.body, null, null);\r
+        }\r
+\r
+        return doc;\r
+    };\r
+\r
+    function resolveTiming(doc, element, prev_sibling, parent) {\r
+\r
+        /* are we in a seq container? */\r
+\r
+        var isinseq = parent && parent.timeContainer === "seq";\r
+\r
+        /* determine implicit begin */\r
+\r
+        var implicit_begin = 0; /* default */\r
+\r
+        if (parent) {\r
+\r
+            if (isinseq && prev_sibling) {\r
+\r
+                /*\r
+                 * if seq time container, offset from the previous sibling end\r
+                 */\r
+\r
+                implicit_begin = prev_sibling.end;\r
+\r
+\r
+            } else {\r
+\r
+                implicit_begin = parent.begin;\r
+\r
+            }\r
+\r
+        }\r
+\r
+        /* compute desired begin */\r
+\r
+        element.begin = element.explicit_begin ? element.explicit_begin + implicit_begin : implicit_begin;\r
+\r
+\r
+        /* determine implicit end */\r
+\r
+        var implicit_end = element.begin;\r
+\r
+        var s = null;\r
+\r
+        for (var set_i in element.sets) {\r
+\r
+            resolveTiming(doc, element.sets[set_i], s, element);\r
+\r
+            if (element.timeContainer === "seq") {\r
+\r
+                implicit_end = element.sets[set_i].end;\r
+\r
+            } else {\r
+\r
+                implicit_end = Math.max(implicit_end, element.sets[set_i].end);\r
+\r
+            }\r
+\r
+            s = element.sets[set_i];\r
+\r
+        }\r
+\r
+        if (!('contents' in element)) {\r
+\r
+            /* anonymous spans and regions and <set> and <br>s and spans with only children text nodes */\r
+\r
+            if (isinseq) {\r
+\r
+                /* in seq container, implicit duration is zero */\r
+\r
+                implicit_end = element.begin;\r
+\r
+            } else {\r
+\r
+                /* in par container, implicit duration is indefinite */\r
+\r
+                implicit_end = Number.POSITIVE_INFINITY;\r
+\r
+            }\r
+\r
+        } else {\r
+\r
+            for (var content_i in element.contents) {\r
+\r
+                resolveTiming(doc, element.contents[content_i], s, element);\r
+\r
+                if (element.timeContainer === "seq") {\r
+\r
+                    implicit_end = element.contents[content_i].end;\r
+\r
+                } else {\r
+\r
+                    implicit_end = Math.max(implicit_end, element.contents[content_i].end);\r
+\r
+                }\r
+\r
+                s = element.contents[content_i];\r
+\r
+            }\r
+\r
+        }\r
+\r
+        /* determine desired end */\r
+        /* it is never made really clear in SMIL that the explicit end is offset by the implicit begin */\r
+\r
+        if (element.explicit_end !== null && element.explicit_dur !== null) {\r
+\r
+            element.end = Math.min(element.begin + element.explicit_dur, implicit_begin + element.explicit_end);\r
+\r
+        } else if (element.explicit_end === null && element.explicit_dur !== null) {\r
+\r
+            element.end = element.begin + element.explicit_dur;\r
+\r
+        } else if (element.explicit_end !== null && element.explicit_dur === null) {\r
+\r
+            element.end = implicit_begin + element.explicit_end;\r
+\r
+        } else {\r
+\r
+            element.end = implicit_end;\r
+        }\r
+\r
+        delete element.explicit_begin;\r
+        delete element.explicit_dur;\r
+        delete element.explicit_end;\r
+\r
+        doc._registerEvent(element);\r
+\r
+    }\r
+\r
+    function ForeignElement(node) {\r
+        this.node = node;\r
+    }\r
+\r
+    function TT() {\r
+        this.events = [];\r
+        this.head = null;\r
+        this.body = null;\r
+    }\r
+\r
+    TT.prototype.initFromNode = function (node, errorHandler) {\r
+\r
+        /* compute cell resolution */\r
+\r
+        this.cellResolution = extractCellResolution(node, errorHandler);\r
+\r
+        /* extract frame rate and tick rate */\r
+\r
+        var frtr = extractFrameAndTickRate(node, errorHandler);\r
+\r
+        this.effectiveFrameRate = frtr.effectiveFrameRate;\r
+\r
+        this.tickRate = frtr.tickRate;\r
+\r
+        /* extract aspect ratio */\r
+\r
+        this.aspectRatio = extractAspectRatio(node, errorHandler);\r
+\r
+        /* check timebase */\r
+\r
+        var attr = findAttribute(node, imscNames.ns_ttp, "timeBase");\r
+\r
+        if (attr !== null && attr !== "media") {\r
+\r
+            reportFatal(errorHandler, "Unsupported time base");\r
+\r
+        }\r
+\r
+        /* retrieve extent */\r
+\r
+        var e = extractExtent(node, errorHandler);\r
+\r
+        if (e === null) {\r
+\r
+            /* TODO: remove once unit tests are ready */\r
+\r
+            this.pxDimensions = {'h': 480, 'w': 640};\r
+\r
+        } else {\r
+\r
+            if (e.h.unit !== "px" || e.w.unit !== "px") {\r
+                reportFatal(errorHandler, "Extent on TT must be in px or absent");\r
+            }\r
+\r
+            this.pxDimensions = {'h': e.h.value, 'w': e.w.value};\r
+        }\r
+\r
+    };\r
+\r
+    /* register a temporal events */\r
+    TT.prototype._registerEvent = function (elem) {\r
+\r
+        /* skip if begin is not < then end */\r
+\r
+        if (elem.end <= elem.begin)\r
+            return;\r
+\r
+        /* index the begin time of the event */\r
+\r
+        var b_i = indexOf(this.events, elem.begin);\r
+\r
+        if (!b_i.found) {\r
+            this.events.splice(b_i.index, 0, elem.begin);\r
+        }\r
+\r
+        /* index the end time of the event */\r
+\r
+        if (elem.end !== Number.POSITIVE_INFINITY) {\r
+\r
+            var e_i = indexOf(this.events, elem.end);\r
+\r
+            if (!e_i.found) {\r
+                this.events.splice(e_i.index, 0, elem.end);\r
+            }\r
+\r
+        }\r
+\r
+    };\r
+\r
+\r
+    /*\r
+     * Retrieves the range of ISD times covered by the document\r
+     * \r
+     * @returns {Array} Array of two elements: min_begin_time and max_begin_time\r
+     * \r
+     */\r
+    TT.prototype.getMediaTimeRange = function () {\r
+\r
+        return [this.events[0], this.events[this.events.length - 1]];\r
+    };\r
+\r
+    /*\r
+     * Returns list of ISD begin times  \r
+     * \r
+     * @returns {Array}\r
+     */\r
+    TT.prototype.getMediaTimeEvents = function () {\r
+\r
+        return this.events;\r
+    };\r
+\r
+    /*\r
+     * Represents a TTML Head element\r
+     */\r
+\r
+    function Head() {\r
+        this.styling = null;\r
+        this.layout = null;\r
+    }\r
+\r
+    /*\r
+     * Represents a TTML Styling element\r
+     */\r
+\r
+    function Styling() {\r
+        this.styles = {};\r
+    }\r
+\r
+    /*\r
+     * Represents a TTML Style element\r
+     */\r
+\r
+    function Style() {\r
+        this.id = null;\r
+        this.styleAttrs = null;\r
+        this.styleRefs = null;\r
+    }\r
+\r
+    Style.prototype.initFromNode = function (node, errorHandler) {\r
+        this.id = elementGetXMLID(node);\r
+        this.styleAttrs = elementGetStyles(node, errorHandler);\r
+        this.styleRefs = elementGetStyleRefs(node);\r
+    };\r
+\r
+    /*\r
+     * Represents a TTML Layout element\r
+     * \r
+     */\r
+\r
+    function Layout() {\r
+        this.regions = {};\r
+    }\r
+\r
+    /*\r
+     * TTML element utility functions\r
+     * \r
+     */\r
+\r
+    function ContentElement(kind) {\r
+        this.kind = kind;\r
+    }\r
+\r
+    function IdentifiedElement(id) {\r
+        this.id = id;\r
+    }\r
+\r
+    IdentifiedElement.prototype.initFromNode = function (doc, parent, node, errorHandler) {\r
+        this.id = elementGetXMLID(node);\r
+    };\r
+\r
+    function LayoutElement(id) {\r
+        this.regionID = id;\r
+    }\r
+\r
+    LayoutElement.prototype.initFromNode = function (doc, parent, node, errorHandler) {\r
+        this.regionID = elementGetRegionID(node);\r
+    };\r
+\r
+    function StyledElement(styleAttrs) {\r
+        this.styleAttrs = styleAttrs;\r
+    }\r
+\r
+    StyledElement.prototype.initFromNode = function (doc, parent, node, errorHandler) {\r
+\r
+        this.styleAttrs = elementGetStyles(node, errorHandler);\r
+\r
+        if (doc.head !== null && doc.head.styling !== null) {\r
+            mergeReferencedStyles(doc.head.styling, elementGetStyleRefs(node), this.styleAttrs, errorHandler);\r
+        }\r
+\r
+    };\r
+\r
+    function AnimatedElement(sets) {\r
+        this.sets = sets;\r
+    }\r
+\r
+    AnimatedElement.prototype.initFromNode = function (doc, parent, node, errorHandler) {\r
+        this.sets = [];\r
+    };\r
+\r
+    function ContainerElement(contents) {\r
+        this.contents = contents;\r
+    }\r
+\r
+    ContainerElement.prototype.initFromNode = function (doc, parent, node, errorHandler) {\r
+        this.contents = [];\r
+    };\r
+\r
+    function TimedElement(explicit_begin, explicit_end, explicit_dur) {\r
+        this.explicit_begin = explicit_begin;\r
+        this.explicit_end = explicit_end;\r
+        this.explicit_dur = explicit_dur;\r
+    }\r
+\r
+    TimedElement.prototype.initFromNode = function (doc, parent, node, errorHandler) {\r
+        var t = processTiming(doc, parent, node, errorHandler);\r
+        this.explicit_begin = t.explicit_begin;\r
+        this.explicit_end = t.explicit_end;\r
+        this.explicit_dur = t.explicit_dur;\r
+\r
+        this.timeContainer = elementGetTimeContainer(node, errorHandler);\r
+    };\r
+\r
+\r
+    /*\r
+     * Represents a TTML body element\r
+     */\r
+\r
+\r
+\r
+    function Body() {\r
+        ContentElement.call(this, 'body');\r
+    }\r
+\r
+\r
+    Body.prototype.initFromNode = function (doc, node, errorHandler) {\r
+        StyledElement.prototype.initFromNode.call(this, doc, null, node, errorHandler);\r
+        TimedElement.prototype.initFromNode.call(this, doc, null, node, errorHandler);\r
+        AnimatedElement.prototype.initFromNode.call(this, doc, null, node, errorHandler);\r
+        LayoutElement.prototype.initFromNode.call(this, doc, null, node, errorHandler);\r
+        ContainerElement.prototype.initFromNode.call(this, doc, null, node, errorHandler);\r
+    };\r
+\r
+    /*\r
+     * Represents a TTML div element\r
+     */\r
+\r
+    function Div() {\r
+        ContentElement.call(this, 'div');\r
+    }\r
+\r
+    Div.prototype.initFromNode = function (doc, parent, node, errorHandler) {\r
+        StyledElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+        TimedElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+        AnimatedElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+        LayoutElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+        ContainerElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+    };\r
+\r
+    /*\r
+     * Represents a TTML p element\r
+     */\r
+\r
+    function P() {\r
+        ContentElement.call(this, 'p');\r
+    }\r
+\r
+    P.prototype.initFromNode = function (doc, parent, node, errorHandler) {\r
+        StyledElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+        TimedElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+        AnimatedElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+        LayoutElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+        ContainerElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+    };\r
+\r
+    /*\r
+     * Represents a TTML span element\r
+     */\r
+\r
+    function Span() {\r
+        ContentElement.call(this, 'span');\r
+    }\r
+\r
+    Span.prototype.initFromNode = function (doc, parent, node, xmlspace, errorHandler) {\r
+        StyledElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+        TimedElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+        AnimatedElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+        LayoutElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+        ContainerElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+\r
+        this.space = xmlspace;\r
+    };\r
+\r
+    /*\r
+     * Represents a TTML anonymous span element\r
+     */\r
+\r
+    function AnonymousSpan() {\r
+        ContentElement.call(this, 'span');\r
+    }\r
+\r
+    AnonymousSpan.prototype.initFromText = function (doc, parent, text, xmlspace, errorHandler) {\r
+        TimedElement.prototype.initFromNode.call(this, doc, parent, null, errorHandler);\r
+\r
+        this.text = text;\r
+        this.space = xmlspace;\r
+    };\r
+\r
+    /*\r
+     * Represents a TTML br element\r
+     */\r
+\r
+    function Br() {\r
+        ContentElement.call(this, 'br');\r
+    }\r
+\r
+    Br.prototype.initFromNode = function (doc, parent, node, errorHandler) {\r
+        LayoutElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+        TimedElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+    };\r
+\r
+    /*\r
+     * Represents a TTML Region element\r
+     * \r
+     */\r
+\r
+    function Region() {\r
+    }\r
+\r
+    Region.prototype.createDefaultRegion = function () {\r
+        var r = new Region();\r
+\r
+        IdentifiedElement.call(r, '');\r
+        StyledElement.call(r, {});\r
+        AnimatedElement.call(r, []);\r
+        TimedElement.call(r, 0, Number.POSITIVE_INFINITY, null);\r
+\r
+        return r;\r
+    };\r
+\r
+    Region.prototype.initFromNode = function (doc, node, errorHandler) {\r
+        IdentifiedElement.prototype.initFromNode.call(this, doc, null, node, errorHandler);\r
+        StyledElement.prototype.initFromNode.call(this, doc, null, node, errorHandler);\r
+        TimedElement.prototype.initFromNode.call(this, doc, null, node, errorHandler);\r
+        AnimatedElement.prototype.initFromNode.call(this, doc, null, node, errorHandler);\r
+\r
+        /* immediately merge referenced styles */\r
+\r
+        if (doc.head !== null && doc.head.styling !== null) {\r
+            mergeReferencedStyles(doc.head.styling, elementGetStyleRefs(node), this.styleAttrs, errorHandler);\r
+        }\r
+\r
+    };\r
+\r
+    /*\r
+     * Represents a TTML Set element\r
+     * \r
+     */\r
+\r
+    function Set() {\r
+    }\r
+\r
+    Set.prototype.initFromNode = function (doc, parent, node, errorHandler) {\r
+\r
+        TimedElement.prototype.initFromNode.call(this, doc, parent, node, errorHandler);\r
+\r
+        var styles = elementGetStyles(node, errorHandler);\r
+\r
+        this.qname = null;\r
+        this.value = null;\r
+\r
+        for (var qname in styles) {\r
+\r
+            if (this.qname) {\r
+\r
+                reportError(errorHandler, "More than one style specified on set");\r
+                break;\r
+\r
+            }\r
+\r
+            this.qname = qname;\r
+            this.value = styles[qname];\r
+\r
+        }\r
+\r
+    };\r
+\r
+    /*\r
+     * Utility functions\r
+     * \r
+     */\r
+\r
+\r
+    function elementGetXMLID(node) {\r
+        return node && 'xml:id' in node.attributes ? node.attributes['xml:id'].value || null : null;\r
+    }\r
+\r
+    function elementGetRegionID(node) {\r
+        return node && 'region' in node.attributes ? node.attributes.region.value : '';\r
+    }\r
+\r
+    function elementGetTimeContainer(node, errorHandler) {\r
+\r
+        var tc = node && 'timeContainer' in node.attributes ? node.attributes.timeContainer.value : null;\r
+\r
+        if ((!tc) || tc === "par") {\r
+\r
+            return "par";\r
+\r
+        } else if (tc === "seq") {\r
+\r
+            return "seq";\r
+\r
+        } else {\r
+\r
+            reportError(errorHandler, "Illegal value of timeContainer (assuming 'par')");\r
+\r
+            return "par";\r
+\r
+        }\r
+\r
+    }\r
+\r
+    function elementGetStyleRefs(node) {\r
+\r
+        return node && 'style' in node.attributes ? node.attributes.style.value.split(" ") : [];\r
+\r
+    }\r
+\r
+    function elementGetStyles(node, errorHandler) {\r
+\r
+        var s = {};\r
+\r
+        if (node !== null) {\r
+\r
+            for (var i in node.attributes) {\r
+\r
+                var qname = node.attributes[i].uri + " " + node.attributes[i].local;\r
+\r
+                var sa = imscStyles.byQName[qname];\r
+\r
+                if (sa !== undefined) {\r
+\r
+                    var val = sa.parse(node.attributes[i].value);\r
+\r
+                    if (val !== null) {\r
+\r
+                        s[qname] = val;\r
+\r
+                        /* TODO: consider refactoring errorHandler into parse and compute routines */\r
+\r
+                        if (sa === imscStyles.byName.zIndex) {\r
+                            reportWarning(errorHandler, "zIndex attribute present but not used by IMSC1 since regions do not overlap");\r
+                        }\r
+\r
+                    } else {\r
+\r
+                        reportError(errorHandler, "Cannot parse styling attribute " + qname + " --> " + node.attributes[i].value);\r
+\r
+                    }\r
+\r
+                }\r
+\r
+            }\r
+\r
+        }\r
+\r
+        return s;\r
+    }\r
+\r
+    function findAttribute(node, ns, name) {\r
+        for (var i in node.attributes) {\r
+\r
+            if (node.attributes[i].uri === ns &&\r
+                    node.attributes[i].local === name) {\r
+\r
+                return node.attributes[i].value;\r
+            }\r
+        }\r
+\r
+        return null;\r
+    }\r
+\r
+    function extractAspectRatio(node, errorHandler) {\r
+\r
+        var ar = findAttribute(node, imscNames.ns_ittp, "aspectRatio");\r
+\r
+        var rslt = null;\r
+\r
+        if (ar !== null) {\r
+\r
+            var ASPECT_RATIO_RE = /(\d+) (\d+)/;\r
+\r
+            var m = ASPECT_RATIO_RE.exec(ar);\r
+\r
+            if (m !== null) {\r
+\r
+                var w = parseInt(m[1]);\r
+\r
+                var h = parseInt(m[2]);\r
+\r
+                if (w !== 0 && h !== 0) {\r
+\r
+                    rslt = w / h;\r
+\r
+                } else {\r
+\r
+                    reportError(errorHandler, "Illegal aspectRatio values (ignoring)");\r
+                }\r
+\r
+            } else {\r
+\r
+                reportError(errorHandler, "Malformed aspectRatio attribute (ignoring)");\r
+            }\r
+\r
+        }\r
+\r
+        return rslt;\r
+\r
+    }\r
+\r
+    /*\r
+     * Returns the cellResolution attribute from a node\r
+     * \r
+     */\r
+    function extractCellResolution(node, errorHandler) {\r
+\r
+        var cr = findAttribute(node, imscNames.ns_ttp, "cellResolution");\r
+\r
+        // initial value\r
+\r
+        var h = 15;\r
+        var w = 32;\r
+\r
+        if (cr !== null) {\r
+\r
+            var CELL_RESOLUTION_RE = /(\d+) (\d+)/;\r
+\r
+            var m = CELL_RESOLUTION_RE.exec(cr);\r
+\r
+            if (m !== null) {\r
+\r
+                w = parseInt(m[1]);\r
+\r
+                h = parseInt(m[2]);\r
+\r
+            } else {\r
+\r
+                reportWarning(errorHandler, "Malformed cellResolution value (using initial value instead)");\r
+\r
+            }\r
+\r
+        }\r
+\r
+        return {'w': w, 'h': h};\r
+\r
+    }\r
+\r
+\r
+    function extractFrameAndTickRate(node, errorHandler) {\r
+\r
+        // subFrameRate is ignored per IMSC1 specification\r
+\r
+        // extract frame rate\r
+\r
+        var fps_attr = findAttribute(node, imscNames.ns_ttp, "frameRate");\r
+\r
+        // initial value\r
+\r
+        var fps = 30;\r
+\r
+        // match variable\r
+\r
+        var m;\r
+\r
+        if (fps_attr !== null) {\r
+\r
+            var FRAME_RATE_RE = /(\d+)/;\r
+\r
+            m = FRAME_RATE_RE.exec(fps_attr);\r
+\r
+            if (m !== null) {\r
+\r
+                fps = parseInt(m[1]);\r
+\r
+            } else {\r
+\r
+                reportWarning(errorHandler, "Malformed frame rate attribute (using initial value instead)");\r
+            }\r
+\r
+        }\r
+\r
+        // extract frame rate multiplier\r
+\r
+        var frm_attr = findAttribute(node, imscNames.ns_ttp, "frameRateMultiplier");\r
+\r
+        // initial value\r
+\r
+        var frm = 1;\r
+\r
+        if (frm_attr !== null) {\r
+\r
+            var FRAME_RATE_MULT_RE = /(\d+) (\d+)/;\r
+\r
+            m = FRAME_RATE_MULT_RE.exec(frm_attr);\r
+\r
+            if (m !== null) {\r
+\r
+                frm = parseInt(m[1]) / parseInt(m[2]);\r
+\r
+            } else {\r
+\r
+                reportWarning(errorHandler, "Malformed frame rate multiplier attribute (using initial value instead)");\r
+            }\r
+\r
+        }\r
+\r
+        var efps = frm * fps;\r
+\r
+        // extract tick rate\r
+\r
+        var tr = 1;\r
+\r
+        var trattr = findAttribute(node, imscNames.ns_ttp, "tickRate");\r
+\r
+        if (trattr === null) {\r
+\r
+            if (fps_attr !== null)\r
+                tr = efps;\r
+\r
+        } else {\r
+\r
+            var TICK_RATE_RE = /(\d+)/;\r
+\r
+            m = TICK_RATE_RE.exec(trattr);\r
+\r
+            if (m !== null) {\r
+\r
+                tr = parseInt(m[1]);\r
+\r
+            } else {\r
+\r
+                reportWarning(errorHandler, "Malformed tick rate attribute (using initial value instead)");\r
+            }\r
+\r
+        }\r
+\r
+        return {effectiveFrameRate: efps, tickRate: tr};\r
+\r
+    }\r
+\r
+    function extractExtent(node, errorHandler) {\r
+\r
+        var attr = findAttribute(node, imscNames.ns_tts, "extent");\r
+\r
+        if (attr === null)\r
+            return null;\r
+\r
+        var s = attr.split(" ");\r
+\r
+        if (s.length !== 2) {\r
+\r
+            reportWarning(errorHandler, "Malformed extent (ignoring)");\r
+\r
+            return null;\r
+        }\r
+\r
+        var w = imscUtils.parseLength(s[0]);\r
+\r
+        var h = imscUtils.parseLength(s[1]);\r
+\r
+        if (!h || !w) {\r
+\r
+            reportWarning(errorHandler, "Malformed extent values (ignoring)");\r
+\r
+            return null;\r
+        }\r
+\r
+        return {'h': h, 'w': w};\r
+\r
+    }\r
+\r
+    function parseTimeExpression(tickRate, effectiveFrameRate, str) {\r
+\r
+        var CLOCK_TIME_FRACTION_RE = /^(\d{2,}):(\d\d):(\d\d(?:\.\d+)?)$/;\r
+        var CLOCK_TIME_FRAMES_RE = /^(\d{2,}):(\d\d):(\d\d)\:(\d{2,})$/;\r
+        var OFFSET_FRAME_RE = /^(\d+(?:\.\d+)?)f$/;\r
+        var OFFSET_TICK_RE = /^(\d+(?:\.\d+)?)t$/;\r
+        var OFFSET_MS_RE = /^(\d+(?:\.\d+)?)ms$/;\r
+        var OFFSET_S_RE = /^(\d+(?:\.\d+)?)s$/;\r
+        var OFFSET_H_RE = /^(\d+(?:\.\d+)?)h$/;\r
+        var OFFSET_M_RE = /^(\d+(?:\.\d+)?)m$/;\r
+        var m;\r
+        var r = null;\r
+        if ((m = OFFSET_FRAME_RE.exec(str)) !== null) {\r
+\r
+            if (effectiveFrameRate !== null) {\r
+\r
+                r = parseFloat(m[1]) / effectiveFrameRate;\r
+            }\r
+\r
+        } else if ((m = OFFSET_TICK_RE.exec(str)) !== null) {\r
+\r
+            if (tickRate !== null) {\r
+\r
+                r = parseFloat(m[1]) / tickRate;\r
+            }\r
+\r
+        } else if ((m = OFFSET_MS_RE.exec(str)) !== null) {\r
+\r
+            r = parseFloat(m[1]) / 1000.0;\r
+\r
+        } else if ((m = OFFSET_S_RE.exec(str)) !== null) {\r
+\r
+            r = parseFloat(m[1]);\r
+\r
+        } else if ((m = OFFSET_H_RE.exec(str)) !== null) {\r
+\r
+            r = parseFloat(m[1]) * 3600.0;\r
+\r
+        } else if ((m = OFFSET_M_RE.exec(str)) !== null) {\r
+\r
+            r = parseFloat(m[1]) * 60.0;\r
+\r
+        } else if ((m = CLOCK_TIME_FRACTION_RE.exec(str)) !== null) {\r
+\r
+            r = parseInt(m[1]) * 3600 +\r
+                    parseInt(m[2]) * 60 +\r
+                    parseFloat(m[3]);\r
+\r
+        } else if ((m = CLOCK_TIME_FRAMES_RE.exec(str)) !== null) {\r
+\r
+            /* this assumes that HH:MM:SS is a clock-time-with-fraction */\r
+\r
+            if (effectiveFrameRate !== null) {\r
+\r
+                r = parseInt(m[1]) * 3600 +\r
+                        parseInt(m[2]) * 60 +\r
+                        parseInt(m[3]) +\r
+                        (m[4] === null ? 0 : parseInt(m[4]) / effectiveFrameRate);\r
+            }\r
+\r
+        }\r
+\r
+        return r;\r
+    }\r
+\r
+    function processTiming(doc, parent, node, errorHandler) {\r
+\r
+        /* determine explicit begin */\r
+\r
+        var explicit_begin = null;\r
+\r
+        if (node && 'begin' in node.attributes) {\r
+\r
+            explicit_begin = parseTimeExpression(doc.tickRate, doc.effectiveFrameRate, node.attributes.begin.value);\r
+\r
+            if (explicit_begin === null) {\r
+\r
+                reportWarning(errorHandler, "Malformed begin value " + node.attributes.begin.value + " (using 0)");\r
+\r
+            }\r
+\r
+        }\r
+\r
+        /* determine explicit duration */\r
+\r
+        var explicit_dur = null;\r
+\r
+        if (node && 'dur' in node.attributes) {\r
+\r
+            explicit_dur = parseTimeExpression(doc.tickRate, doc.effectiveFrameRate, node.attributes.dur.value);\r
+\r
+            if (explicit_dur === null) {\r
+\r
+                reportWarning(errorHandler, "Malformed dur value " + node.attributes.dur.value + " (ignoring)");\r
+\r
+            }\r
+\r
+        }\r
+\r
+        /* determine explicit end */\r
+\r
+        var explicit_end = null;\r
+\r
+        if (node && 'end' in node.attributes) {\r
+\r
+            explicit_end = parseTimeExpression(doc.tickRate, doc.effectiveFrameRate, node.attributes.end.value);\r
+\r
+            if (explicit_end === null) {\r
+\r
+                reportWarning(errorHandler, "Malformed end value (ignoring)");\r
+\r
+            }\r
+\r
+        }\r
+\r
+        return {explicit_begin: explicit_begin,\r
+            explicit_end: explicit_end,\r
+            explicit_dur: explicit_dur};\r
+\r
+    }\r
+\r
+\r
+\r
+    function mergeChainedStyles(styling, style, errorHandler) {\r
+\r
+        while (style.styleRefs.length > 0) {\r
+\r
+            var sref = style.styleRefs.pop();\r
+\r
+            if (!(sref in styling.styles)) {\r
+                reportError(errorHandler, "Non-existant style id referenced");\r
+                continue;\r
+            }\r
+\r
+            mergeChainedStyles(styling, styling.styles[sref], errorHandler);\r
+\r
+            mergeStylesIfNotPresent(styling.styles[sref].styleAttrs, style.styleAttrs);\r
+\r
+        }\r
+\r
+    }\r
+\r
+    function mergeReferencedStyles(styling, stylerefs, styleattrs, errorHandler) {\r
+\r
+        for (var i = stylerefs.length - 1; i >= 0; i--) {\r
+\r
+            var sref = stylerefs[i];\r
+\r
+            if (!(sref in styling.styles)) {\r
+                reportError(errorHandler, "Non-existant style id referenced");\r
+                continue;\r
+            }\r
+\r
+            mergeStylesIfNotPresent(styling.styles[sref].styleAttrs, styleattrs);\r
+\r
+        }\r
+\r
+    }\r
+\r
+    function mergeStylesIfNotPresent(from_styles, into_styles) {\r
+\r
+        for (var sname in from_styles) {\r
+\r
+            if (sname in into_styles)\r
+                continue;\r
+\r
+            into_styles[sname] = from_styles[sname];\r
+\r
+        }\r
+\r
+    }\r
+\r
+    /* TODO: validate style format at parsing */\r
+\r
+\r
+    /*\r
+     * ERROR HANDLING UTILITY FUNCTIONS\r
+     * \r
+     */\r
+\r
+    function reportInfo(errorHandler, msg) {\r
+\r
+        if (errorHandler && errorHandler.info && errorHandler.info(msg))\r
+            throw msg;\r
+\r
+    }\r
+\r
+    function reportWarning(errorHandler, msg) {\r
+\r
+        if (errorHandler && errorHandler.warn && errorHandler.warn(msg))\r
+            throw msg;\r
+\r
+    }\r
+\r
+    function reportError(errorHandler, msg) {\r
+\r
+        if (errorHandler && errorHandler.error && errorHandler.error(msg))\r
+            throw msg;\r
+\r
+    }\r
+\r
+    function reportFatal(errorHandler, msg) {\r
+\r
+        if (errorHandler && errorHandler.fatal)\r
+            errorHandler.fatal(msg);\r
+\r
+        throw msg;\r
+\r
+    }\r
+\r
+    /*\r
+     * Binary search utility function\r
+     * \r
+     * @typedef {Object} BinarySearchResult\r
+     * @property {boolean} found Was an exact match found?\r
+     * @property {number} index Position of the exact match or insert position\r
+     * \r
+     * @returns {BinarySearchResult}\r
+     */\r
+\r
+    function indexOf(arr, searchval) {\r
+\r
+        var min = 0;\r
+        var max = arr.length - 1;\r
+        var cur;\r
+\r
+        while (min <= max) {\r
+\r
+            cur = Math.floor((min + max) / 2);\r
+\r
+            var curval = arr[cur];\r
+\r
+            if (curval < searchval) {\r
+\r
+                min = cur + 1;\r
+\r
+            } else if (curval > searchval) {\r
+\r
+                max = cur - 1;\r
+\r
+            } else {\r
+\r
+                return {found: true, index: cur};\r
+\r
+            }\r
+\r
+        }\r
+\r
+        return {found: false, index: min};\r
+    }\r
+\r
+\r
+})(typeof exports === 'undefined' ? this.imscDoc = {} : exports,\r
+        typeof sax === 'undefined' ? _dereq_(40) : sax,\r
+        typeof imscNames === 'undefined' ? _dereq_(18) : imscNames,\r
+        typeof imscStyles === 'undefined' ? _dereq_(19) : imscStyles,\r
+        typeof imscUtils === 'undefined' ? _dereq_(20) : imscUtils);\r
+
+},{"18":18,"19":19,"20":20,"40":40}],15:[function(_dereq_,module,exports){
+/* \r
+ * Copyright (c) 2016, Pierre-Anthony Lemieux <pal@sandflow.com>\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * * Redistributions of source code must retain the above copyright notice, this\r
+ *   list of conditions and the following disclaimer.\r
+ * * Redistributions in binary form must reproduce the above copyright notice,\r
+ *   this list of conditions and the following disclaimer in the documentation\r
+ *   and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+/**\r
+ * @module imscHTML\r
+ */\r
+\r
+;\r
+(function (imscHTML, imscNames, imscStyles) {\r
+\r
+    /**\r
+     * Function that maps <pre>smpte:background</pre> URIs to URLs resolving to image resource\r
+     * @callback IMGResolver\r
+     * @param {string} <pre>smpte:background</pre> URI\r
+     * @return {string} PNG resource URL\r
+     */\r
+\r
+\r
+    /**\r
+     * Renders an ISD object (returned by <pre>generateISD()</pre>) into a \r
+     * parent element, that must be attached to the DOM. The ISD will be rendered\r
+     * into a child <pre>div</pre>\r
+     * with heigh and width equal to the clientHeight and clientWidth of the element,\r
+     * unless explicitly specified otherwise by the caller. Images URIs specified \r
+     * by <pre>smpte:background</pre> attributes are mapped to image resource URLs\r
+     * by an <pre>imgResolver</pre> function. The latter takes the value of <code>smpte:background</code>\r
+     * attribute and an <code>img</code> DOM element as input, and is expected to\r
+     * set the <code>src</code> attribute of the <code>img</code> to the absolute URI of the image.\r
+     * <pre>displayForcedOnlyMode</pre> sets the (boolean)\r
+     * value of the IMSC1 displayForcedOnlyMode parameter. The function returns\r
+     * an opaque object that should passed in <code>previousISDState</code> when this function\r
+     * is called for the next ISD, otherwise <code>previousISDState</code> should be set to \r
+     * <code>null</code>.\r
+     * \r
+     * @param {Object} isd ISD to be rendered\r
+     * @param {Object} element Element into which the ISD is rendered\r
+     * @param {?IMGResolver} imgResolver Resolve <pre>smpte:background</pre> URIs into URLs.\r
+     * @param {?number} eheight Height (in pixel) of the child <div>div</div> or null \r
+     *                  to use clientHeight of the parent element\r
+     * @param {?number} ewidth Width (in pixel) of the child <div>div</div> or null\r
+     *                  to use clientWidth of the parent element\r
+     * @param {?boolean} displayForcedOnlyMode Value of the IMSC1 displayForcedOnlyMode parameter,\r
+     *                   or false if null         \r
+     * @param {?module:imscUtils.ErrorHandler} errorHandler Error callback\r
+     * @param {Object} previousISDState State saved during processing of the previous ISD, or null if initial call\r
+     * @param {?boolean} enableRollUp Enables roll-up animations (see CEA 708)\r
+     * @return {Object} ISD state to be provided when this funtion is called for the next ISD\r
+     */\r
+\r
+    imscHTML.render = function (isd,\r
+        element,\r
+        imgResolver,\r
+        eheight,\r
+        ewidth,\r
+        displayForcedOnlyMode,\r
+        errorHandler,\r
+        previousISDState,\r
+        enableRollUp\r
+        ) {\r
+\r
+        /* maintain aspect ratio if specified */\r
+\r
+        var height = eheight || element.clientHeight;\r
+        var width = ewidth || element.clientWidth;\r
+\r
+        if (isd.aspectRatio !== null) {\r
+\r
+            var twidth = height * isd.aspectRatio;\r
+\r
+            if (twidth > width) {\r
+\r
+                height = Math.round(width / isd.aspectRatio);\r
+\r
+            } else {\r
+\r
+                width = twidth;\r
+\r
+            }\r
+\r
+        }\r
+\r
+        var rootcontainer = document.createElement("div");\r
+\r
+        rootcontainer.style.position = "relative";\r
+        rootcontainer.style.width = width + "px";\r
+        rootcontainer.style.height = height + "px";\r
+        rootcontainer.style.margin = "auto";\r
+        rootcontainer.style.top = 0;\r
+        rootcontainer.style.bottom = 0;\r
+        rootcontainer.style.left = 0;\r
+        rootcontainer.style.right = 0;\r
+        rootcontainer.style.zIndex = 0;\r
+\r
+        var context = {\r
+            h: height,\r
+            w: width,\r
+            regionH: null,\r
+            regionW: null,\r
+            imgResolver: imgResolver,\r
+            displayForcedOnlyMode: displayForcedOnlyMode || false,\r
+            isd: isd,\r
+            errorHandler: errorHandler,\r
+            previousISDState: previousISDState,\r
+            enableRollUp: enableRollUp || false,\r
+            currentISDState: {},\r
+            flg: null, /* current fillLineGap value if active, null otherwise */\r
+            lp: null, /* current linePadding value if active, null otherwise */\r
+            mra: null, /* current multiRowAlign value if active, null otherwise */\r
+            ipd: null, /* inline progression direction (lr, rl, tb) */\r
+            bpd: null /* block progression direction (lr, rl, tb) */\r
+        };\r
+\r
+        element.appendChild(rootcontainer);\r
+\r
+        for (var i in isd.contents) {\r
+\r
+            processElement(context, rootcontainer, isd.contents[i]);\r
+\r
+        }\r
+\r
+        return context.currentISDState;\r
+\r
+    };\r
+\r
+    function processElement(context, dom_parent, isd_element) {\r
+\r
+        var e;\r
+\r
+        if (isd_element.kind === 'region') {\r
+\r
+            e = document.createElement("div");\r
+            e.style.position = "absolute";\r
+\r
+        } else if (isd_element.kind === 'body') {\r
+\r
+            e = document.createElement("div");\r
+\r
+        } else if (isd_element.kind === 'div') {\r
+\r
+            e = document.createElement("div");\r
+\r
+        } else if (isd_element.kind === 'p') {\r
+\r
+            e = document.createElement("p");\r
+\r
+        } else if (isd_element.kind === 'span') {\r
+\r
+            e = document.createElement("span");\r
+\r
+            //e.textContent = isd_element.text;\r
+\r
+        } else if (isd_element.kind === 'br') {\r
+\r
+            e = document.createElement("br");\r
+\r
+        }\r
+\r
+        if (!e) {\r
+\r
+            reportError(context.errorHandler, "Error processing ISD element kind: " + isd_element.kind);\r
+\r
+            return;\r
+\r
+        }\r
+\r
+        /* override UA default margin */\r
+        /* TODO: should apply to <p> only */\r
+\r
+        e.style.margin = "0";\r
+\r
+        /* tranform TTML styles to CSS styles */\r
+\r
+        for (var i in STYLING_MAP_DEFS) {\r
+\r
+            var sm = STYLING_MAP_DEFS[i];\r
+\r
+            var attr = isd_element.styleAttrs[sm.qname];\r
+\r
+            if (attr !== undefined && sm.map !== null) {\r
+\r
+                sm.map(context, e, isd_element, attr);\r
+\r
+            }\r
+\r
+        }\r
+\r
+        var proc_e = e;\r
+\r
+        /* remember writing direction */\r
+\r
+        if (isd_element.kind === "region") {\r
+\r
+            var wdir = isd_element.styleAttrs[imscStyles.byName.writingMode.qname];\r
+\r
+            if (wdir === "lrtb" || wdir === "lr") {\r
+\r
+                context.ipd = "lr";\r
+                context.bpd = "tb";\r
+\r
+            } else if (wdir === "rltb" || wdir === "rl") {\r
+\r
+                context.ipd = "rl";\r
+                context.bpd = "tb";\r
+\r
+            } else if (wdir === "tblr") {\r
+\r
+                context.ipd = "tb";\r
+                context.bpd = "lr";\r
+\r
+            } else if (wdir === "tbrl" || wdir === "tb") {\r
+\r
+                context.ipd = "tb";\r
+                context.bpd = "rl";\r
+\r
+            }\r
+\r
+        }\r
+\r
+        /* do we have linePadding ? */\r
+\r
+        var lp = isd_element.styleAttrs[imscStyles.byName.linePadding.qname];\r
+\r
+        if (lp && lp > 0) {\r
+\r
+            /* apply padding to the <p> so that line padding does not cause line wraps */\r
+\r
+            var padmeasure = Math.ceil(lp * context.h) + "px";\r
+\r
+            if (context.bpd === "tb") {\r
+\r
+                proc_e.style.paddingLeft = padmeasure;\r
+                proc_e.style.paddingRight = padmeasure;\r
+\r
+            } else {\r
+\r
+                proc_e.style.paddingTop = padmeasure;\r
+                proc_e.style.paddingBottom = padmeasure;\r
+\r
+            }\r
+\r
+            context.lp = lp;\r
+        }\r
+\r
+        // do we have multiRowAlign?\r
+\r
+        var mra = isd_element.styleAttrs[imscStyles.byName.multiRowAlign.qname];\r
+\r
+        if (mra && mra !== "auto") {\r
+\r
+            /* create inline block to handle multirowAlign */\r
+\r
+            var s = document.createElement("span");\r
+\r
+            s.style.display = "inline-block";\r
+\r
+            s.style.textAlign = mra;\r
+\r
+            e.appendChild(s);\r
+\r
+            proc_e = s;\r
+\r
+            context.mra = mra;\r
+\r
+        }\r
+\r
+        /* remember we are filling line gaps */\r
+\r
+        if (isd_element.styleAttrs[imscStyles.byName.fillLineGap.qname]) {\r
+            context.flg = true;\r
+        }\r
+\r
+\r
+        if (isd_element.kind === "span" && isd_element.text) {\r
+\r
+            if (context.lp || context.mra || context.flg) {\r
+\r
+                // wrap characters in spans to find the line wrap locations\r
+\r
+                var cbuf = '';\r
+\r
+                for (var j = 0; j < isd_element.text.length; j++) {\r
+\r
+                    cbuf += isd_element.text.charAt(j);\r
+\r
+                    var cc = isd_element.text.charCodeAt(j);\r
+\r
+                    if (cc < 0xD800 || cc > 0xDBFF || j === isd_element.text.length) {\r
+\r
+                        /* wrap the character(s) in a span unless it is a high surrogate */\r
+\r
+                        var span = document.createElement("span");\r
+\r
+                        span.textContent = cbuf;\r
+    \r
+                        e.appendChild(span);\r
+\r
+                        cbuf = '';\r
+\r
+                    }\r
+\r
+                }\r
+\r
+            } else {\r
+\r
+                e.textContent = isd_element.text;\r
+\r
+            }\r
+        }\r
+\r
+        dom_parent.appendChild(e);\r
+\r
+        /* process the children of the ISD element */\r
+\r
+        for (var k in isd_element.contents) {\r
+\r
+            processElement(context, proc_e, isd_element.contents[k]);\r
+\r
+        }\r
+\r
+        /* list of lines */\r
+\r
+        var linelist = [];\r
+\r
+\r
+        /* paragraph processing */\r
+        /* TODO: linePadding only supported for horizontal scripts */\r
+\r
+        if ((context.lp || context.mra || context.flg) && isd_element.kind === "p") {\r
+\r
+            constructLineList(context, proc_e, linelist, null);\r
+\r
+            /* insert line breaks for multirowalign */\r
+\r
+            if (context.mra) {\r
+\r
+                applyMultiRowAlign(linelist);\r
+\r
+                context.mra = null;\r
+\r
+            }\r
+\r
+            /* add linepadding */\r
+\r
+            if (context.lp) {\r
+\r
+                applyLinePadding(linelist, context.lp * context.h, context);\r
+\r
+                context.lp = null;\r
+\r
+            }\r
+\r
+            /* fill line gaps linepadding */\r
+\r
+            if (context.flg) {\r
+\r
+                var par_edges = rect2edges(proc_e.getBoundingClientRect(), context);\r
+\r
+                applyFillLineGap(linelist, par_edges.before, par_edges.after, context);\r
+\r
+                context.flg = null;\r
+\r
+            }\r
+\r
+        }\r
+\r
+\r
+        /* region processing */\r
+\r
+        if (isd_element.kind === "region") {\r
+\r
+            /* build line list */\r
+\r
+            constructLineList(context, proc_e, linelist);\r
+\r
+            /* perform roll up if needed */\r
+\r
+            if ((context.bpd === "tb") &&\r
+                context.enableRollUp &&\r
+                isd_element.contents.length > 0 &&\r
+                isd_element.styleAttrs[imscStyles.byName.displayAlign.qname] === 'after') {\r
+\r
+                /* horrible hack, perhaps default region id should be underscore everywhere? */\r
+\r
+                var rid = isd_element.id === '' ? '_' : isd_element.id;\r
+\r
+                var rb = new RegionPBuffer(rid, linelist);\r
+\r
+                context.currentISDState[rb.id] = rb;\r
+\r
+                if (context.previousISDState &&\r
+                    rb.id in context.previousISDState &&\r
+                    context.previousISDState[rb.id].plist.length > 0 &&\r
+                    rb.plist.length > 1 &&\r
+                    rb.plist[rb.plist.length - 2].text ===\r
+                    context.previousISDState[rb.id].plist[context.previousISDState[rb.id].plist.length - 1].text) {\r
+\r
+                    var body_elem = e.firstElementChild;\r
+                    \r
+                    var h = rb.plist[rb.plist.length - 1].after - rb.plist[rb.plist.length - 1].before;\r
+\r
+                    body_elem.style.bottom = "-" + h + "px";\r
+                    body_elem.style.transition = "transform 0.4s";\r
+                    body_elem.style.position = "relative";\r
+                    body_elem.style.transform = "translateY(-" + h + "px)";\r
+\r
+                }\r
+\r
+            }\r
+\r
+            /* TODO: clean-up the spans ? */\r
+\r
+        }\r
+    }\r
+\r
+    function applyLinePadding(lineList, lp, context) {\r
+\r
+        for (var i in lineList) {\r
+\r
+            var l = lineList[i].elements.length;\r
+\r
+            var se = lineList[i].elements[lineList[i].start_elem];\r
+\r
+            var ee = lineList[i].elements[lineList[i].end_elem];\r
+\r
+            var pospadpxlen = Math.ceil(lp) + "px";\r
+\r
+            var negpadpxlen = "-" + Math.ceil(lp) + "px";\r
+\r
+            if (l !== 0) {\r
+\r
+                if (context.ipd === "lr") {\r
+\r
+                    se.node.style.borderLeftColor = se.bgcolor || "#00000000";\r
+                    se.node.style.borderLeftStyle = "solid";\r
+                    se.node.style.borderLeftWidth = pospadpxlen;\r
+                    se.node.style.marginLeft = negpadpxlen;\r
+\r
+                } else if (context.ipd === "rl") {\r
+\r
+                    se.node.style.borderRightColor = se.bgcolor || "#00000000";\r
+                    se.node.style.borderRightStyle = "solid";\r
+                    se.node.style.borderRightWidth = pospadpxlen;\r
+                    se.node.style.marginRight = negpadpxlen;\r
+\r
+                } else if (context.ipd === "tb") {\r
+\r
+                    se.node.style.borderTopColor = se.bgcolor || "#00000000";\r
+                    se.node.style.borderTopStyle = "solid";\r
+                    se.node.style.borderTopWidth = pospadpxlen;\r
+                    se.node.style.marginTop = negpadpxlen;\r
+\r
+                }\r
+\r
+                if (context.ipd === "lr") {\r
+\r
+                    ee.node.style.borderRightColor = ee.bgcolor  || "#00000000";\r
+                    ee.node.style.borderRightStyle = "solid";\r
+                    ee.node.style.borderRightWidth = pospadpxlen;\r
+                    ee.node.style.marginRight = negpadpxlen;\r
+\r
+                } else if (context.ipd === "rl") {\r
+\r
+                    ee.node.style.borderLeftColor = ee.bgcolor || "#00000000";\r
+                    ee.node.style.borderLeftStyle = "solid";\r
+                    ee.node.style.borderLeftWidth = pospadpxlen;\r
+                    ee.node.style.marginLeft = negpadpxlen;\r
+\r
+                } else if (context.ipd === "tb") {\r
+\r
+                    ee.node.style.borderBottomColor = ee.bgcolor || "#00000000";\r
+                    ee.node.style.borderBottomStyle = "solid";\r
+                    ee.node.style.borderBottomWidth = pospadpxlen;\r
+                    ee.node.style.marginBottom = negpadpxlen;\r
+\r
+                }\r
+\r
+            }\r
+\r
+        }\r
+\r
+    }\r
+\r
+    function applyMultiRowAlign(lineList) {\r
+\r
+        /* apply an explicit br to all but the last line */\r
+\r
+        for (var i = 0; i < lineList.length - 1; i++) {\r
+\r
+            var l = lineList[i].elements.length;\r
+\r
+            if (l !== 0 && lineList[i].br === false) {\r
+                var br = document.createElement("br");\r
+\r
+                var lastnode = lineList[i].elements[l - 1].node;\r
+\r
+                lastnode.parentElement.insertBefore(br, lastnode.nextSibling);\r
+            }\r
+\r
+        }\r
+\r
+    }\r
+\r
+    function applyFillLineGap(lineList, par_before, par_after, context) {\r
+\r
+        /* positive for BPD = lr and tb, negative for BPD = rl */\r
+        var s = Math.sign(par_after - par_before);\r
+\r
+        for (var i = 0; i <= lineList.length; i++) {\r
+\r
+            /* compute frontier between lines */\r
+\r
+            var frontier;\r
+\r
+            if (i === 0) {\r
+\r
+                frontier = par_before;\r
+\r
+            } else if (i === lineList.length) {\r
+\r
+                frontier = par_after;\r
+\r
+            } else {\r
+\r
+                frontier = (lineList[i].before + lineList[i - 1].after) / 2;\r
+\r
+            }\r
+\r
+            /* padding amount */\r
+\r
+            var pad;\r
+\r
+            /* current element */\r
+\r
+            var e;\r
+\r
+            /* before line */\r
+\r
+            if (i > 0) {\r
+\r
+                for (var j = 0; j < lineList[i - 1].elements.length; j++) {\r
+\r
+                    if (lineList[i - 1].elements[j].bgcolor === null) continue;\r
+\r
+                    e = lineList[i - 1].elements[j];\r
+\r
+                    if (s * (e.after - frontier) < 0) {\r
+\r
+                        pad = Math.ceil(Math.abs(frontier - e.after)) + "px";\r
+\r
+                        e.node.style.backgroundColor = e.bgcolor;\r
+\r
+                        if (context.bpd === "lr") {\r
+\r
+                            e.node.style.paddingRight = pad;\r
+\r
+\r
+                        } else if (context.bpd === "rl") {\r
+\r
+                            e.node.style.paddingLeft = pad;\r
+\r
+                        } else if (context.bpd === "tb") {\r
+\r
+                            e.node.style.paddingBottom = pad;\r
+\r
+                        }\r
+\r
+                    }\r
+\r
+                }\r
+\r
+            }\r
+\r
+            /* after line */\r
+\r
+            if (i < lineList.length) {\r
+\r
+                for (var k = 0; k < lineList[i].elements.length; k++) {\r
+\r
+                    e = lineList[i].elements[k];\r
+\r
+                    if (e.bgcolor === null) continue;\r
+\r
+                    if (s * (e.before - frontier) > 0) {\r
+\r
+                        pad = Math.ceil(Math.abs(e.before - frontier)) + "px";\r
+\r
+                        e.node.style.backgroundColor = e.bgcolor;\r
+\r
+                        if (context.bpd === "lr") {\r
+\r
+                            e.node.style.paddingLeft = pad;\r
+\r
+\r
+                        } else if (context.bpd === "rl") {\r
+\r
+                            e.node.style.paddingRight = pad;\r
+\r
+\r
+                        } else if (context.bpd === "tb") {\r
+\r
+                            e.node.style.paddingTop = pad;\r
+\r
+                        }\r
+\r
+                    }\r
+\r
+                }\r
+\r
+            }\r
+\r
+        }\r
+\r
+    }\r
+\r
+    function RegionPBuffer(id, lineList) {\r
+\r
+        this.id = id;\r
+\r
+        this.plist = lineList;\r
+\r
+    }\r
+\r
+    function pruneEmptySpans(element) {\r
+\r
+        var child = element.firstChild;\r
+\r
+        while (child) {\r
+\r
+            var nchild = child.nextSibling;\r
+\r
+            if (child.nodeType === Node.ELEMENT_NODE &&\r
+                child.localName === 'span') {\r
+\r
+                pruneEmptySpans(child);\r
+\r
+                if (child.childElementCount === 0 &&\r
+                    child.textContent.length === 0) {\r
+\r
+                    element.removeChild(child);\r
+\r
+                }\r
+            }\r
+\r
+            child = nchild;\r
+        }\r
+\r
+    }\r
+\r
+    function rect2edges(rect, context) {\r
+\r
+        var edges = {before: null, after: null, start: null, end: null};\r
+\r
+        if (context.bpd === "tb") {\r
+\r
+            edges.before = rect.top;\r
+            edges.after = rect.bottom;\r
+\r
+            if (context.ipd === "lr") {\r
+\r
+                edges.start = rect.left;\r
+                edges.end = rect.right;\r
+\r
+            } else {\r
+\r
+                edges.start = rect.right;\r
+                edges.end = rect.left;\r
+            }\r
+\r
+        } else if (context.bpd === "lr") {\r
+\r
+            edges.before = rect.left;\r
+            edges.after = rect.right;\r
+            edges.start = rect.top;\r
+            edges.end = rect.bottom;\r
+\r
+        } else if (context.bpd === "rl") {\r
+\r
+            edges.before = rect.right;\r
+            edges.after = rect.left;\r
+            edges.start = rect.top;\r
+            edges.end = rect.bottom;\r
+\r
+        }\r
+\r
+        return edges;\r
+\r
+    }\r
+\r
+    function constructLineList(context, element, llist, bgcolor) {\r
+\r
+        var curbgcolor = element.style.backgroundColor || bgcolor;\r
+\r
+        if (element.childElementCount === 0) {\r
+\r
+            if (element.localName === 'span') {\r
+\r
+                var r = element.getBoundingClientRect();\r
+\r
+                /* skip if span is not displayed */\r
+\r
+                if (r.height === 0 || r.width === 0) return;\r
+\r
+                var edges = rect2edges(r, context);\r
+\r
+                if (llist.length === 0 ||\r
+                    (!isSameLine(edges.before, edges.after, llist[llist.length - 1].before, llist[llist.length - 1].after))\r
+                    ) {\r
+\r
+                    llist.push({\r
+                        before: edges.before,\r
+                        after: edges.after,\r
+                        start: edges.start,\r
+                        end: edges.end,\r
+                        start_elem: 0,\r
+                        end_elem: 0,\r
+                        elements: [],\r
+                        text: "",\r
+                        br: false\r
+                    });\r
+\r
+                } else {\r
+\r
+                    /* positive for BPD = lr and tb, negative for BPD = rl */\r
+                    var bpd_dir = Math.sign(edges.after - edges.before);\r
+\r
+                    /* positive for IPD = lr and tb, negative for IPD = rl */\r
+                    var ipd_dir = Math.sign(edges.end - edges.start);\r
+\r
+                    /* check if the line height has increased */\r
+\r
+                    if (bpd_dir * (edges.before - llist[llist.length - 1].before) < 0) {\r
+                        llist[llist.length - 1].before = edges.before;\r
+                    }\r
+\r
+                    if (bpd_dir * (edges.after - llist[llist.length - 1].after) > 0) {\r
+                        llist[llist.length - 1].after = edges.after;\r
+                    }\r
+\r
+                    if (ipd_dir * (edges.start - llist[llist.length - 1].start) < 0) {\r
+                        llist[llist.length - 1].start = edges.start;\r
+                        llist[llist.length - 1].start_elem = llist[llist.length - 1].elements.length;\r
+                    }\r
+\r
+                    if (ipd_dir * (edges.end - llist[llist.length - 1].end) > 0) {\r
+                        llist[llist.length - 1].end = edges.end;\r
+                        llist[llist.length - 1].end_elem = llist[llist.length - 1].elements.length;\r
+                    }\r
+\r
+                }\r
+\r
+                llist[llist.length - 1].text += element.textContent;\r
+\r
+                llist[llist.length - 1].elements.push(\r
+                    {\r
+                        node: element,\r
+                        bgcolor: curbgcolor,\r
+                        before: edges.before,\r
+                        after: edges.after\r
+                    }\r
+                );\r
+\r
+            } else if (element.localName === 'br' && llist.length !== 0) {\r
+\r
+                llist[llist.length - 1].br = true;\r
+\r
+            }\r
+\r
+        } else {\r
+\r
+            var child = element.firstChild;\r
+\r
+            while (child) {\r
+\r
+                if (child.nodeType === Node.ELEMENT_NODE) {\r
+\r
+                    constructLineList(context, child, llist, curbgcolor);\r
+\r
+                }\r
+\r
+                child = child.nextSibling;\r
+            }\r
+        }\r
+\r
+    }\r
+\r
+    function isSameLine(before1, after1, before2, after2) {\r
+\r
+        return ((after1 < after2) && (before1 > before2)) || ((after2 <= after1) && (before2 >= before1));\r
+\r
+    }\r
+\r
+    function HTMLStylingMapDefintion(qName, mapFunc) {\r
+        this.qname = qName;\r
+        this.map = mapFunc;\r
+    }\r
+\r
+    var STYLING_MAP_DEFS = [\r
+\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling backgroundColor",\r
+            function (context, dom_element, isd_element, attr) {\r
+\r
+                /* skip if transparent */\r
+                if (attr[3] === 0) return;\r
+\r
+                dom_element.style.backgroundColor = "rgba(" +\r
+                    attr[0].toString() + "," +\r
+                    attr[1].toString() + "," +\r
+                    attr[2].toString() + "," +\r
+                    (attr[3] / 255).toString() +\r
+                    ")";\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling color",\r
+            function (context, dom_element, isd_element, attr) {\r
+                dom_element.style.color = "rgba(" +\r
+                    attr[0].toString() + "," +\r
+                    attr[1].toString() + "," +\r
+                    attr[2].toString() + "," +\r
+                    (attr[3] / 255).toString() +\r
+                    ")";\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling direction",\r
+            function (context, dom_element, isd_element, attr) {\r
+                dom_element.style.direction = attr;\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling display",\r
+            function (context, dom_element, isd_element, attr) {}\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling displayAlign",\r
+            function (context, dom_element, isd_element, attr) {\r
+\r
+                /* see https://css-tricks.com/snippets/css/a-guide-to-flexbox/ */\r
+\r
+                /* TODO: is this affected by writing direction? */\r
+\r
+                dom_element.style.display = "flex";\r
+                dom_element.style.flexDirection = "column";\r
+\r
+\r
+                if (attr === "before") {\r
+\r
+                    dom_element.style.justifyContent = "flex-start";\r
+\r
+                } else if (attr === "center") {\r
+\r
+                    dom_element.style.justifyContent = "center";\r
+\r
+                } else if (attr === "after") {\r
+\r
+                    dom_element.style.justifyContent = "flex-end";\r
+                }\r
+\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling extent",\r
+            function (context, dom_element, isd_element, attr) {\r
+                /* TODO: this is super ugly */\r
+\r
+                context.regionH = (attr.h * context.h);\r
+                context.regionW = (attr.w * context.w);\r
+\r
+                /* \r
+                 * CSS height/width are measured against the content rectangle,\r
+                 * whereas TTML height/width include padding\r
+                 */\r
+\r
+                var hdelta = 0;\r
+                var wdelta = 0;\r
+\r
+                var p = isd_element.styleAttrs["http://www.w3.org/ns/ttml#styling padding"];\r
+\r
+                if (!p) {\r
+\r
+                    /* error */\r
+\r
+                } else {\r
+\r
+                    hdelta = (p[0] + p[2]) * context.h;\r
+                    wdelta = (p[1] + p[3]) * context.w;\r
+\r
+                }\r
+\r
+                dom_element.style.height = (context.regionH - hdelta) + "px";\r
+                dom_element.style.width = (context.regionW - wdelta) + "px";\r
+\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling fontFamily",\r
+            function (context, dom_element, isd_element, attr) {\r
+\r
+                var rslt = [];\r
+\r
+                /* per IMSC1 */\r
+\r
+                for (var i in attr) {\r
+\r
+                    if (attr[i] === "monospaceSerif") {\r
+\r
+                        rslt.push("Courier New");\r
+                        rslt.push('"Liberation Mono"');\r
+                        rslt.push("Courier");\r
+                        rslt.push("monospace");\r
+\r
+                    } else if (attr[i] === "proportionalSansSerif") {\r
+\r
+                        rslt.push("Arial");\r
+                        rslt.push("Helvetica");\r
+                        rslt.push('"Liberation Sans"');\r
+                        rslt.push("sans-serif");\r
+\r
+                    } else if (attr[i] === "monospace") {\r
+\r
+                        rslt.push("monospace");\r
+\r
+                    } else if (attr[i] === "sansSerif") {\r
+\r
+                        rslt.push("sans-serif");\r
+\r
+                    } else if (attr[i] === "serif") {\r
+\r
+                        rslt.push("serif");\r
+\r
+                    } else if (attr[i] === "monospaceSansSerif") {\r
+\r
+                        rslt.push("Consolas");\r
+                        rslt.push("monospace");\r
+\r
+                    } else if (attr[i] === "proportionalSerif") {\r
+\r
+                        rslt.push("serif");\r
+\r
+                    } else {\r
+\r
+                        rslt.push(attr[i]);\r
+\r
+                    }\r
+\r
+                }\r
+\r
+                dom_element.style.fontFamily = rslt.join(",");\r
+            }\r
+        ),\r
+\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling fontSize",\r
+            function (context, dom_element, isd_element, attr) {\r
+                dom_element.style.fontSize = (attr * context.h) + "px";\r
+            }\r
+        ),\r
+\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling fontStyle",\r
+            function (context, dom_element, isd_element, attr) {\r
+                dom_element.style.fontStyle = attr;\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling fontWeight",\r
+            function (context, dom_element, isd_element, attr) {\r
+                dom_element.style.fontWeight = attr;\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling lineHeight",\r
+            function (context, dom_element, isd_element, attr) {\r
+                if (attr === "normal") {\r
+\r
+                    dom_element.style.lineHeight = "normal";\r
+\r
+                } else {\r
+\r
+                    dom_element.style.lineHeight = (attr * context.h) + "px";\r
+                }\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling opacity",\r
+            function (context, dom_element, isd_element, attr) {\r
+                dom_element.style.opacity = attr;\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling origin",\r
+            function (context, dom_element, isd_element, attr) {\r
+                dom_element.style.top = (attr.h * context.h) + "px";\r
+                dom_element.style.left = (attr.w * context.w) + "px";\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling overflow",\r
+            function (context, dom_element, isd_element, attr) {\r
+                dom_element.style.overflow = attr;\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling padding",\r
+            function (context, dom_element, isd_element, attr) {\r
+\r
+                /* attr: top,left,bottom,right*/\r
+\r
+                /* style: top right bottom left*/\r
+\r
+                var rslt = [];\r
+\r
+                rslt[0] = (attr[0] * context.h) + "px";\r
+                rslt[1] = (attr[3] * context.w) + "px";\r
+                rslt[2] = (attr[2] * context.h) + "px";\r
+                rslt[3] = (attr[1] * context.w) + "px";\r
+\r
+                dom_element.style.padding = rslt.join(" ");\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling showBackground",\r
+            null\r
+            ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling textAlign",\r
+            function (context, dom_element, isd_element, attr) {\r
+\r
+                var ta;\r
+                var dir = isd_element.styleAttrs[imscStyles.byName.direction.qname];\r
+\r
+                /* handle UAs that do not understand start or end */\r
+\r
+                if (attr === "start") {\r
+\r
+                    ta = (dir === "rtl") ? "right" : "left";\r
+\r
+                } else if (attr === "end") {\r
+\r
+                    ta = (dir === "rtl") ? "left" : "right";\r
+\r
+                } else {\r
+\r
+                    ta = attr;\r
+\r
+                }\r
+\r
+                dom_element.style.textAlign = ta;\r
+\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling textDecoration",\r
+            function (context, dom_element, isd_element, attr) {\r
+                dom_element.style.textDecoration = attr.join(" ").replace("lineThrough", "line-through");\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling textOutline",\r
+            function (context, dom_element, isd_element, attr) {\r
+\r
+                if (attr === "none") {\r
+\r
+                    dom_element.style.textShadow = "";\r
+\r
+                } else {\r
+\r
+                    dom_element.style.textShadow = "rgba(" +\r
+                        attr.color[0].toString() + "," +\r
+                        attr.color[1].toString() + "," +\r
+                        attr.color[2].toString() + "," +\r
+                        (attr.color[3] / 255).toString() +\r
+                        ")" + " 0px 0px " +\r
+                        (attr.thickness * context.h) + "px";\r
+\r
+                }\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling unicodeBidi",\r
+            function (context, dom_element, isd_element, attr) {\r
+\r
+                var ub;\r
+\r
+                if (attr === 'bidiOverride') {\r
+                    ub = "bidi-override";\r
+                } else {\r
+                    ub = attr;\r
+                }\r
+\r
+                dom_element.style.unicodeBidi = ub;\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling visibility",\r
+            function (context, dom_element, isd_element, attr) {\r
+                dom_element.style.visibility = attr;\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling wrapOption",\r
+            function (context, dom_element, isd_element, attr) {\r
+\r
+                if (attr === "wrap") {\r
+\r
+                    if (isd_element.space === "preserve") {\r
+                        dom_element.style.whiteSpace = "pre-wrap";\r
+                    } else {\r
+                        dom_element.style.whiteSpace = "normal";\r
+                    }\r
+\r
+                } else {\r
+\r
+                    if (isd_element.space === "preserve") {\r
+\r
+                        dom_element.style.whiteSpace = "pre";\r
+\r
+                    } else {\r
+                        dom_element.style.whiteSpace = "noWrap";\r
+                    }\r
+\r
+                }\r
+\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling writingMode",\r
+            function (context, dom_element, isd_element, attr) {\r
+                if (attr === "lrtb" || attr === "lr") {\r
+\r
+                    dom_element.style.writingMode = "horizontal-tb";\r
+\r
+                } else if (attr === "rltb" || attr === "rl") {\r
+\r
+                    dom_element.style.writingMode = "horizontal-tb";\r
+\r
+                } else if (attr === "tblr") {\r
+\r
+                    dom_element.style.writingMode = "vertical-lr";\r
+\r
+                } else if (attr === "tbrl" || attr === "tb") {\r
+\r
+                    dom_element.style.writingMode = "vertical-rl";\r
+\r
+                }\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml#styling zIndex",\r
+            function (context, dom_element, isd_element, attr) {\r
+                dom_element.style.zIndex = attr;\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt backgroundImage",\r
+            function (context, dom_element, isd_element, attr) {\r
+\r
+                if (context.imgResolver !== null && attr !== null) {\r
+\r
+                    var img = document.createElement("img");\r
+\r
+                    var uri = context.imgResolver(attr, img);\r
+\r
+                    if (uri)\r
+                        img.src = uri;\r
+\r
+                    img.height = context.regionH;\r
+                    img.width = context.regionW;\r
+\r
+                    dom_element.appendChild(img);\r
+                }\r
+            }\r
+        ),\r
+        new HTMLStylingMapDefintion(\r
+            "http://www.w3.org/ns/ttml/profile/imsc1#styling forcedDisplay",\r
+            function (context, dom_element, isd_element, attr) {\r
+\r
+                if (context.displayForcedOnlyMode && attr === false) {\r
+                    dom_element.style.visibility = "hidden";\r
+                }\r
+\r
+            }\r
+        )\r
+    ];\r
+\r
+    var STYLMAP_BY_QNAME = {};\r
+\r
+    for (var i in STYLING_MAP_DEFS) {\r
+\r
+        STYLMAP_BY_QNAME[STYLING_MAP_DEFS[i].qname] = STYLING_MAP_DEFS[i];\r
+    }\r
+\r
+    function reportError(errorHandler, msg) {\r
+\r
+        if (errorHandler && errorHandler.error && errorHandler.error(msg))\r
+            throw msg;\r
+\r
+    }\r
+\r
+})(typeof exports === 'undefined' ? this.imscHTML = {} : exports,\r
+    typeof imscNames === 'undefined' ? _dereq_(18) : imscNames,\r
+    typeof imscStyles === 'undefined' ? _dereq_(19) : imscStyles);
+},{"18":18,"19":19}],16:[function(_dereq_,module,exports){
+/* \r
+ * Copyright (c) 2016, Pierre-Anthony Lemieux <pal@sandflow.com>\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * * Redistributions of source code must retain the above copyright notice, this\r
+ *   list of conditions and the following disclaimer.\r
+ * * Redistributions in binary form must reproduce the above copyright notice,\r
+ *   this list of conditions and the following disclaimer in the documentation\r
+ *   and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+/**\r
+ * @module imscISD\r
+ */\r
+\r
+\r
+;\r
+(function (imscISD, imscNames, imscStyles) { // wrapper for non-node envs\r
+\r
+    /** \r
+     * Creates a canonical representation of an IMSC1 document returned by <pre>imscDoc.fromXML()</pre>\r
+     * at a given absolute offset in seconds. This offset does not have to be one of the values returned\r
+     * by <pre>getMediaTimeEvents()</pre>.\r
+     * \r
+     * @param {Object} tt IMSC1 document\r
+     * @param {number} offset Absolute offset (in seconds)\r
+     * @param {?module:imscUtils.ErrorHandler} errorHandler Error callback\r
+     * @returns {Object} Opaque in-memory representation of an ISD\r
+     */\r
+\r
+    imscISD.generateISD = function (tt, offset, errorHandler) {\r
+\r
+        /* TODO check for tt and offset validity */\r
+\r
+        /* create the ISD object from the IMSC1 doc */\r
+\r
+        var isd = new ISD(tt);\r
+        \r
+        /* context */\r
+        \r
+        var context = {\r
+          \r
+            /* empty for now */\r
+            \r
+        };\r
+\r
+        /* process regions */\r
+\r
+        for (var r in tt.head.layout.regions) {\r
+\r
+            /* post-order traversal of the body tree per [construct intermediate document] */\r
+\r
+            var c = isdProcessContentElement(tt, offset, tt.head.layout.regions[r], tt.body, null, '', tt.head.layout.regions[r], errorHandler, context);\r
+\r
+            if (c !== null) {\r
+\r
+                /* add the region to the ISD */\r
+\r
+                isd.contents.push(c.element);\r
+            }\r
+\r
+\r
+        }\r
+\r
+        return isd;\r
+    };\r
+\r
+    function isdProcessContentElement(doc, offset, region, body, parent, inherited_region_id, elem, errorHandler, context) {\r
+\r
+        /* prune if temporally inactive */\r
+\r
+        if (offset < elem.begin || offset >= elem.end) {\r
+            return null;\r
+        }\r
+\r
+        /* \r
+         * set the associated region as specified by the regionID attribute, or the \r
+         * inherited associated region otherwise\r
+         */\r
+\r
+        var associated_region_id = 'regionID' in elem && elem.regionID !== '' ? elem.regionID : inherited_region_id;\r
+\r
+        /* prune the element if either:\r
+         * - the element is not terminal and the associated region is neither the default\r
+         *   region nor the parent region (this allows children to be associated with a \r
+         *   region later on)\r
+         * - the element is terminal and the associated region is not the parent region\r
+         */\r
+        \r
+        /* TODO: improve detection of terminal elements since <region> has no contents */\r
+\r
+        if (parent !== null /* are we in the region element */ &&\r
+            associated_region_id !== region.id &&\r
+                (\r
+                    (! ('contents' in elem)) ||\r
+                    ('contents' in elem && elem.contents.length === 0) ||\r
+                    associated_region_id !== ''\r
+                )\r
+             )\r
+            return null;\r
+\r
+        /* create an ISD element, including applying specified styles */\r
+\r
+        var isd_element = new ISDContentElement(elem);\r
+\r
+        /* apply set (animation) styling */\r
+\r
+        for (var i in elem.sets) {\r
+\r
+            if (offset < elem.sets[i].begin || offset >= elem.sets[i].end)\r
+                continue;\r
+\r
+            isd_element.styleAttrs[elem.sets[i].qname] = elem.sets[i].value;\r
+\r
+        }\r
+\r
+        /* \r
+         * keep track of specified styling attributes so that we\r
+         * can compute them later\r
+         */\r
+\r
+        var spec_attr = {};\r
+\r
+        for (var qname in isd_element.styleAttrs) {\r
+\r
+            spec_attr[qname] = true;\r
+\r
+            /* special rule for tts:writingMode (section 7.29.1 of XSL)\r
+             * direction is set consistently with writingMode only\r
+             * if writingMode sets inline-direction to LTR or RTL  \r
+             */\r
+\r
+            if (qname === imscStyles.byName.writingMode.qname &&\r
+                !(imscStyles.byName.direction.qname in isd_element.styleAttrs)) {\r
+\r
+                var wm = isd_element.styleAttrs[qname];\r
+\r
+                if (wm === "lrtb" || wm === "lr") {\r
+\r
+                    isd_element.styleAttrs[imscStyles.byName.direction.qname] = "ltr";\r
+\r
+                } else if (wm === "rltb" || wm === "rl") {\r
+\r
+                    isd_element.styleAttrs[imscStyles.byName.direction.qname] = "rtl";\r
+\r
+                }\r
+\r
+            }\r
+        }\r
+\r
+        /* inherited styling */\r
+\r
+        if (parent !== null) {\r
+\r
+            for (var j in imscStyles.all) {\r
+\r
+                var sa = imscStyles.all[j];\r
+\r
+                /* textDecoration has special inheritance rules */\r
+\r
+                if (sa.qname === imscStyles.byName.textDecoration.qname) {\r
+\r
+                    /* handle both textDecoration inheritance and specification */\r
+\r
+                    var ps = parent.styleAttrs[sa.qname];\r
+                    var es = isd_element.styleAttrs[sa.qname];\r
+                    var outs = [];\r
+\r
+                    if (es === undefined) {\r
+\r
+                        outs = ps;\r
+\r
+                    } else if (es.indexOf("none") === -1) {\r
+\r
+                        if ((es.indexOf("noUnderline") === -1 &&\r
+                            ps.indexOf("underline") !== -1) ||\r
+                            es.indexOf("underline") !== -1) {\r
+\r
+                            outs.push("underline");\r
+\r
+                        }\r
+\r
+                        if ((es.indexOf("noLineThrough") === -1 &&\r
+                            ps.indexOf("lineThrough") !== -1) ||\r
+                            es.indexOf("lineThrough") !== -1) {\r
+\r
+                            outs.push("lineThrough");\r
+\r
+                        }\r
+\r
+                        if ((es.indexOf("noOverline") === -1 &&\r
+                            ps.indexOf("overline") !== -1) ||\r
+                            es.indexOf("overline") !== -1) {\r
+\r
+         &