]> id.pley.net Git - movie_parser.git/commitdiff
Add support for many new atom types.
authorJer Noble <jer.noble@apple.com>
Thu, 9 Feb 2017 23:40:54 +0000 (15:40 -0800)
committerJer Noble <jer.noble@apple.com>
Thu, 9 Feb 2017 23:40:54 +0000 (15:40 -0800)
Atom.js
AtomTester.html

diff --git a/Atom.js b/Atom.js
index 8a73803fd4bc2d5a0f21c0c50958f80f1f1f7370..a3ff73b2dbe1dc8c3abc193b506cdede999bb730 100644 (file)
--- a/Atom.js
+++ b/Atom.js
-var Atom = function (buffer, offset) {
-    this.setDefaults();
-    return this.parse(buffer, offset) ? this : null;
-};
-
-Atom.create = function (buffer, offset) {
-    // 'offset' is optional.
-    if (arguments.length < 2) {
-        offset = 0;
+class BitReader {
+    constructor(buffer, offset) {
+        this.buffer = buffer;
+        this.bitPos = offset * 8;
     }
 
     }
 
-    if (buffer.byteLength - offset < this.minimumSize)
-        return null;
+    readOneBit() {
+        var offset = Math.floor(this.bitPos / 8),
+            shift = 7 - this.bitPos % 8;
+        this.bitPos += 1;
+        return (this.buffer[offset] >> shift) & 1;
+    }
 
 
-    var typeArrayView = new Uint8Array(buffer, offset + 4, 4);
-    var type = String.fromCharCode.apply(null, typeArrayView);
-    var atom;
-
-    switch (type) {
-        case 'ftyp':
-            return new FileTypeAtom(buffer, offset);
-        case 'moov':
-            return new ContainerAtom(buffer, offset, 'Movie Atom');
-        case 'trak':
-            return new ContainerAtom(buffer, offset, 'Track Atom');
-        case 'mdia':
-            return new ContainerAtom(buffer, offset, 'Media Atom');
-        case 'minf':
-            return new ContainerAtom(buffer, offset, 'Media Info Atom');
-        case 'mvex':
-            return new ContainerAtom(buffer, offset, 'Movie Extends Atom');
-        case 'stbl':
-            return new ContainerAtom(buffer, offset);
-        case 'mvhd':
-            return new MovieHeaderAtom(buffer, offset);
-        case 'tkhd':
-            return new TrackHeaderAtom(buffer, offset);
-        case 'mdhd':
-            return new MediaHeaderAtom(buffer, offset);
-        case 'stss':
-            return new SyncSampleAtom(buffer, offset);
-        case 'stts':
-            return new TimeToSampleAtom(buffer, offset);
-        case 'stsz':
-            return new SampleSizeAtom(buffer, offset);
-        case 'trex':
-            return new TrackExtendsAtom(buffer, offset);
-        case 'sinf':
-            return new ContainerAtom(buffer, offset, 'Protection Scheme Info Atom');
-        case 'ipro':
-            return new ContainerAtom(buffer, offset, 'Item Protection Atom');
-        case 'frma':
-            return new OriginalFormatBox(buffer, offset);
-        case 'schm':
-            return new SchemeTypeBox(buffer, offset);
-        case 'schi':
-            return new SchemeInfoBox(Buffer, offset);
-        default:
-            return new Atom(buffer, offset);
+    readBits(n) {
+        var i, value = 0;
+        for (i = 0; i < n; i += 1) {
+            value = value << 1 | this.readOneBit();
+        }
+        return value;
     }
     }
-};
 
 
-Atom.prototype.super = function (object) {
-    return Object.getPrototypeOf(object.prototype);
-};
+    isEnd() {
+        return Math.floor(this.bitPos / 8) >= this.buffer.length;
+    }
+}
 
 
-Atom.prototype.setDefaults = function () {
-    Object.defineProperty(this, "is64bit", {
+class Atom {
+    constructor(parent) {
+        Object.defineProperty(this, "is64bit", {
             value: false,
             writable: true,
             enumerable: false,
             configurable: true,
         });
             value: false,
             writable: true,
             enumerable: false,
             configurable: true,
         });
-    Object.defineProperty(this, "minimumSize", {
+        Object.defineProperty(this, "minimumSize", {
             value: 8,
             writable: true,
             enumerable: false,
             configurable: true,
         });
             value: 8,
             writable: true,
             enumerable: false,
             configurable: true,
         });
-    Object.defineProperty(this, "parent", {
+        Object.defineProperty(this, "parent", {
             value: null,
             writable: true,
             enumerable: false,
             configurable: true,
         });
             value: null,
             writable: true,
             enumerable: false,
             configurable: true,
         });
-    Object.defineProperty(this, "description", {
+        Object.defineProperty(this, "description", {
             value: "Undifferentiated Atom",
             writable: true,
             enumerable: false,
             configurable: true,
         });
             value: "Undifferentiated Atom",
             writable: true,
             enumerable: false,
             configurable: true,
         });
-    this.offset = 0;
-    this.size = 0;
-    this.type = '';
-};
 
 
-Atom.prototype.parse = function (buffer, offset) {
-    // 'offset' is optional.
-    if (typeof(offset) == 'undefined')
-        offset = 0;
+        this.offset = 0;
+        this.size = 0;
+        this.type = '';    
+        this.parent = parent;
+
+        return this;
+    };
+
+    static create(buffer, offset, parent) {
+        // 'offset' is optional.
+        if (arguments.length < 2) {
+            offset = 0;
+        }
+
+        var type = this.getType(buffer, offset);
+        var atom;
 
 
-    this.offset = offset;
+        if (typeof(Atom.constructorMap[type]) == 'undefined')
+            atom = new Atom(parent);
+        else
+            atom = new Atom.constructorMap[type](parent);
+        atom.parse(buffer, offset);
+        return atom;
+    };
 
 
-    if (buffer.byteLength - offset < this.minimumSize)
-        throw 'Buffer not long enough';
+    static getType(buffer, offset) {
+        // 'offset' is optional.
+        if (arguments.length < 2) {
+            offset = 0;
+        }
 
 
-    var view = new DataView(buffer, offset, 4);
-    var headerOffset = 0;
+        if (buffer.byteLength - offset < this.minimumSize)
+            return null;
 
 
-    this.size = view.getUint32(0);
-    headerOffset += 4;
+        var view = new DataView(buffer, offset, 4);
+        var size = view.getUint32(0);
+        if (size == 1) {
+            var upper = view.getUint32(8);
+            var lower = view.getUint32(12);
+            size = (upper << 32)  + lower;
+        }
 
 
-    var typeArrayView = new Uint8Array(buffer, offset + headerOffset, 4);
-    this.type = String.fromCharCode.apply(null, typeArrayView);
-    headerOffset += 4;
+        if (!size || buffer.byteLength < offset + size)
+            return null;
+
+        var typeArrayView = new Uint8Array(buffer, offset + 4, 4);
+        return String.fromCharCode.apply(null, typeArrayView);
+    };
+
+
+    parse(buffer, offset) {
+        // 'offset' is optional.
+        if (typeof(offset) == 'undefined')
+            offset = 0;
+
+        this.offset = offset;
 
 
-    if (this.size == 1) {
-        this.is64bit = true;
         if (buffer.byteLength - offset < 8)
         if (buffer.byteLength - offset < 8)
-            throw 'Malformed extended size field';
-
-        // NOTE: JavaScript can only represent up to 2^53 as precise integer.
-        // This calculation may result in incorrect values.
-        var view = new DataView(buffer, offset + headerOffset, 8);
-        var upper = view.getUint32(0);
-        var lower = view.getUint32(4);
-        this.size = (upper << 32)  + lower;
-        headerOffset += 8;
-    }
+            throw 'Buffer not long enough';
 
 
-    return headerOffset;
-};
+        var view = new DataView(buffer, offset, 4);
+        var headerOffset = 0;
+
+        this.size = view.getUint32(0);
+        headerOffset += 4;
+
+        var typeArrayView = new Uint8Array(buffer, offset + headerOffset, 4);
+        this.type = String.fromCharCode.apply(null, typeArrayView);
+        headerOffset += 4;
+
+        if (this.size == 1) {
+            this.is64bit = true;
+            if (buffer.byteLength - offset < 8)
+                throw 'Malformed extended size field';
+
+            // NOTE: JavaScript can only represent up to 2^53 as precise integer.
+            // This calculation may result in incorrect values.
+            view = new DataView(buffer, offset + headerOffset, 8);
+            var upper = view.getUint32(0);
+            var lower = view.getUint32(4);
+            this.size = (upper << 32)  + lower;
+            headerOffset += 8;
+        }
+
+        if (this.type === 'uuid') {
+            var extendedTypeArray = new Uint8Array(buffer, offset + headerOffset, 16);
+            this.uuid = String.fromCharCode.apply(null, extendedTypeArray);
+            headerOffset += 16;
+        }
+
+        return headerOffset;
+    };
+
+    getAtomByType(type) {
+        if (typeof(this.childAtoms) == 'undefined')
+            return null;
+
+        // Bredth first
+        var result = this.childAtoms.find(function(atom) {
+            return atom.type == type;
+        });
+        if (result)
+            return result;
+
+        for (var i = 0; i < this.childAtoms.length; ++i) {
+            var atom = this.childAtoms[i].getAtomByType(type);
+            if (atom)
+                return atom;
+        }
 
 
-Atom.prototype.getAtomByType = function (type) {
-    if (typeof(this.childAtoms) == 'undefined')
         return null;
         return null;
+    };
 
 
-    // Bredth first
-    for (var index in this.childAtoms) {
-        if (this.childAtoms[index].type == type)
-            return this.childAtoms[index];
-    }
+    getAtomsByType(type) {
+        if (typeof(this.childAtoms) == 'undefined')
+            return [];
 
 
-    var result = null;
-    for (var index in this.childAtoms) {
-        if (result = this.childAtoms[index].getAtomByType(type))
-            break;
-    }
-    return result;
+        // Bredth first
+        var result = this.childAtoms.filter(function(atom) {
+            return atom.type === type;
+        });
+
+        this.childAtoms.forEach(function(atom) {
+            result = result.concat(atom.getAtomsByType(type));
+        });
+
+        return result;
+    };
 };
 
 };
 
-Atom.prototype.getAtomsByType = function (type) {
-    if (typeof(this.childAtoms) == 'undefined')
-        return [];
+Atom.constructorMap = { };
 
 
-    var result = [];
+class FileTypeAtom extends Atom {
+    constructor(parent) {
+        super(parent);
 
 
-    // Bredth first
-    for (var index in this.childAtoms) {
-        if (this.childAtoms[index].type == type)
-            result.push(this.childAtoms[index]);
+        this.description = "File Type Atom";
+        this.minimumSize = 16;
+        this.brand = "";
+        this.version = 0;
+        this.compatible_brands = [];
     }
 
     }
 
-    for (var index in this.childAtoms)
-        result = result.concat(this.childAtoms[index].getAtomsByType(type));
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset, this.size);
 
 
-    return result;
+        var brandArrayView = new Uint8Array(buffer, offset + headerOffset, 4);
+        this.brand = String.fromCharCode.apply(null, brandArrayView);
+        headerOffset += 4;
+
+        this.version = view.getUint32(headerOffset);
+        headerOffset += 4;
+
+        while (headerOffset < this.size - 4) {
+            brandArrayView = new Uint8Array(buffer, offset + headerOffset, 4);
+            this.compatible_brands.push(String.fromCharCode.apply(null, brandArrayView));
+            headerOffset += 4;
+        }
+
+        return headerOffset;
+    };
 };
 
 };
 
-var FileTypeAtom = function (buffer, offset) {
-    this.super(FileTypeAtom).constructor.call(this, buffer, offset);
-}
+Atom.constructorMap['ftyp'] = FileTypeAtom.bind(null);
+
+class ContainerAtom extends Atom {
+    constructor(description, parent) {
+        super(parent);
+        this.description = description;
+        this.childAtoms = [];
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset, this);
+        while (headerOffset < this.size) {
+            var childAtom = Atom.create(buffer, offset + headerOffset, this);
+            if (!childAtom)
+                break;
+            headerOffset += childAtom.size;
+            this.childAtoms.push(childAtom);
+        }
+        return headerOffset;
+    };
+};
 
 
-FileTypeAtom.prototype = Object.create(Atom.prototype);
+Atom.constructorMap['moov'] = ContainerAtom.bind(null, 'Movie Atom');
+Atom.constructorMap['trak'] = ContainerAtom.bind(null, 'Track Atom');
+Atom.constructorMap['mdia'] = ContainerAtom.bind(null, 'Media Atom');
+Atom.constructorMap['minf'] = ContainerAtom.bind(null, 'Media Info Atom');
+Atom.constructorMap['mvex'] = ContainerAtom.bind(null, 'Movie Extends Atom');
+Atom.constructorMap['sinf'] = ContainerAtom.bind(null, 'Protection Scheme Info Atom');
+Atom.constructorMap['ipro'] = ContainerAtom.bind(null, 'Item Protection Atom');
+Atom.constructorMap['stbl'] = ContainerAtom.bind(null, 'Sample Table Atom');
+Atom.constructorMap['moof'] = ContainerAtom.bind(null, 'Movie Fragment Atom');
+Atom.constructorMap['traf'] = ContainerAtom.bind(null, 'Track Fragment Atom');
+Atom.constructorMap['edts'] = ContainerAtom.bind(null, 'Edit Box');
+
+class FullBox extends Atom {
+    constructor(parent) {
+        super(parent);
+        this.version = 0;
+        this.flags = 0;
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        this.version = view.getUint8(headerOffset);
+        headerOffset += 1;
+
+        // 'flags' is a 3-byte field, so retrieve from one extra byte and concatenate
+        this.flags = (view.getUint8(headerOffset) << 8) + view.getUint16(headerOffset + 1);
+        headerOffset += 3;
+
+        return headerOffset;
+    };
+};
 
 
-FileTypeAtom.prototype.setDefaults = function () {
-    this.super(FileTypeAtom).setDefaults.call(this);
-    this.description = "File Type Atom";
-    this.minimumSize = 16;
-    this.brand = "";
-    this.version = 0;
-    this.compatible_brands = [];
-}
+class MovieHeaderAtom extends FullBox {
+    constructor(parent) {
+        super(parent);
+        this.description = "Movie Header Atom";
+        this.creationTime = 0;
+        this.modificationTime = 0;
+        this.timeScale = 0;
+        this.duration = 0;
+        this.preferredRate = 0.0;
+        this.preferredVolume = 0.0;
+        this.movieMatrix = [[]];
+        this.previewTime = 0;
+        this.posterTime = 0;
+        this.selectionTime = 0;
+        this.selectionDuration = 0;
+        this.nextTrackID = 0;
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        this.creationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
+        headerOffset += 4;
 
 
-FileTypeAtom.prototype.parse = function (buffer, offset) {
-    var headerOffset = this.super(FileTypeAtom).parse.call(this, buffer, offset);
-    var view = new DataView(buffer, offset, this.size);
+        this.modificationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
+        headerOffset += 4;
 
 
-    var brandArrayView = new Uint8Array(buffer, offset + headerOffset, 4);
-    this.brand = String.fromCharCode.apply(null, brandArrayView);
-    headerOffset += 4;
+        this.timeScale = view.getUint32(headerOffset);
+        headerOffset += 4;
 
 
-    this.version = view.getUint32(headerOffset);
-    headerOffset += 4;
+        this.duration  = view.getUint32(headerOffset);
+        headerOffset += 4;
 
 
-    while (headerOffset < this.size - 4) {
-        var brandArrayView = new Uint8Array(buffer, offset + headerOffset, 4);
-        this.compatible_brands.push(String.fromCharCode.apply(null, brandArrayView));
+        this.preferredRate = view.getUint32(headerOffset) / (1 << 16);
         headerOffset += 4;
         headerOffset += 4;
-    }
 
 
-    return true;
-}
+        this.preferredVolume = view.getUint16(headerOffset) / (1 << 8);
+        headerOffset += 2;
 
 
-var ContainerAtom = function (buffer, offset, description) {
-    var atom = this.super(ContainerAtom).constructor.call(this, buffer, offset);
-    atom.description = description;
-    return;
-}
+        // Reserved
+        // Ten bytes reserved for use by Apple. Set to 0.
+        headerOffset += 10;
 
 
-ContainerAtom.prototype = Object.create(Atom.prototype);
+        this.movieMatrix = new Array(3);
+        // a, b, u:
+        this.movieMatrix[0] = new Array(3);
+        this.movieMatrix[0][0] = view.getUint32(headerOffset) / (1 << 16);
+        headerOffset += 4;
+        this.movieMatrix[0][1] = view.getUint32(headerOffset) / (1 << 16);
+        headerOffset += 4;
+        this.movieMatrix[0][2] = view.getUint32(headerOffset) / (1 << 30);
+        headerOffset += 4;
 
 
-ContainerAtom.prototype.setDefaults = function () {
-    this.super(ContainerAtom).setDefaults.call(this);
-    this.childAtoms = [];
-}
+        // c, d, v:
+        this.movieMatrix[1] = new Array(3);
+        this.movieMatrix[1][0] = view.getUint32(headerOffset) / (1 << 16);
+        headerOffset += 4;
+        this.movieMatrix[1][1] = view.getUint32(headerOffset) / (1 << 16);
+        headerOffset += 4;
+        this.movieMatrix[1][2] = view.getUint32(headerOffset) / (1 << 30);
+        headerOffset += 4;
 
 
-ContainerAtom.prototype.parse = function (buffer, offset) {
-    var headerOffset = this.super(ContainerAtom).parse.call(this, buffer, offset);
-    while (headerOffset < this.size) {
-        var childAtom = Atom.create(buffer, offset + headerOffset);
-        if (!childAtom)
-            break;
-        headerOffset += childAtom.size;
-        this.childAtoms.push(childAtom);
-        childAtom.parent = this;
-    }
-       return headerOffset;
-}
+        // x, y, w:
+        this.movieMatrix[2] = new Array(3);
+        this.movieMatrix[2][0] = view.getUint32(headerOffset) / (1 << 16);
+        headerOffset += 4;
+        this.movieMatrix[2][1] = view.getUint32(headerOffset) / (1 << 16);
+        headerOffset += 4;
+        this.movieMatrix[2][2] = view.getUint32(headerOffset) / (1 << 30);
+        headerOffset += 4;
 
 
-var VersionFlagsAtom = function (buffer, offset) {
-    this.super(VersionFlagsAtom).constructor.call(this, buffer, offset);
-}
+        this.previewTime = view.getUint32(headerOffset);
+        headerOffset += 4;
 
 
-VersionFlagsAtom.prototype = Object.create(Atom.prototype);
+        this.previewDuration = view.getUint32(headerOffset);
+        headerOffset += 4;
 
 
-VersionFlagsAtom.prototype.setDefaults = function () {
-    this.super(VersionFlagsAtom).setDefaults.call(this);
-    this.version = 0;
-    this.flags = 0;
-}
+        this.posterTime = view.getUint32(headerOffset);
+        headerOffset += 4;
 
 
-VersionFlagsAtom.prototype.parse = function (buffer, offset) {
-    var headerOffset = this.super(VersionFlagsAtom).parse.call(this, buffer, offset);
-    var view = new DataView(buffer, offset);
+        this.selectionTime = view.getUint32(headerOffset);
+        headerOffset += 4;
 
 
-    this.version = view.getUint8(headerOffset);
-    headerOffset += 1;
+        this.selectionDuration = view.getUint32(headerOffset);
+        headerOffset += 4;
 
 
-    // 'flags' is a 3-byte field, so retrieve from one extra byte and concatenate
-    this.flags = (view.getUint8(headerOffset) << 8) + view.getUint16(headerOffset + 1);
-    headerOffset += 3;
+        this.nextTrackID = view.getUint32(headerOffset);
+        headerOffset += 4;
 
 
-    return headerOffset;
-}
+        return headerOffset;
+    };
+};
 
 
-var MovieHeaderAtom = function (buffer, offset) {
-    return this.super(MovieHeaderAtom).constructor.call(this, buffer, offset);
-}
+Atom.constructorMap['mvhd'] = MovieHeaderAtom.bind(null);
 
 
-MovieHeaderAtom.prototype = Object.create(VersionFlagsAtom.prototype);
-
-MovieHeaderAtom.prototype.setDefaults = function () {
-    this.super(MovieHeaderAtom).setDefaults.call(this);
-    this.description = "Movie Header Atom";
-    this.creationTime = 0;
-    this.modificationTime = 0;
-    this.timeScale = 0;
-    this.duration = 0;
-    this.preferredRate = 0.0;
-    this.preferredVolume = 0.0;
-    this.movieMatrix = [[]];
-    this.previewTime = 0;
-    this.posterTime = 0;
-    this.selectionTime = 0;
-    this.selectionDuration = 0;
-    this.nextTrackID = 0;
-}
+class EditListBox extends FullBox {
+    constructor(parent) {
+        super(parent);
+        this.description = "Edit List Box";
+        this.edits = [];
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        var count = view.getUint32(headerOffset);
+        headerOffset += 4;
+
+        for (var index = 0; index < count; ++index) {
+            var segmentDuration = 0;
+            var mediaTime = 0;
+            if (this.version === 1) {
+                var upper = view.getUint32(headerOffset);
+                var lower = view.getUint32(headerOffset + 4);
+                segmentDuration = (upper << 32) + lower;
+                headerOffset += 8;
+
+                upper = view.getUint32(headerOffset);
+                lower = view.getUint32(headerOffset + 4);
+                mediaTime = (upper << 32) + lower;
+                headerOffset += 8;
+            } else {
+                segmentDuration = view.getUint32(headerOffset);
+                headerOffset += 4;
+
+                mediaTime = view.getUint32(headerOffset);
+                headerOffset += 4;
+            }
+
+            var mediaRateInteger = view.getUint16(headerOffset);
+            headerOffset += 2;
+
+            var mediaRateFraction = view.getUint16(headerOffset);
+            headerOffset += 2;
+
+            this.edits.push([segmentDuration, mediaTime, mediaRateFraction, mediaRateInteger]);
+        }
+
+        return headerOffset;
+    };
+};
 
 
-MovieHeaderAtom.prototype.parse = function (buffer, offset) {
-    var headerOffset = this.super(MovieHeaderAtom).parse.call(this, buffer, offset);
-    var view = new DataView(buffer, offset);
+Atom.constructorMap['elst'] = EditListBox.bind(null);
+
+class TrackHeaderAtom extends FullBox {
+    constructor(parent) {
+        super(parent);
+
+        this.description = "Track Header Atom";
+        this.creationTime = 0;
+        this.modificationTime = 0;
+        this.trackID = 0;
+        this.duration = 0;
+        this.layer = 0;
+        this.alternateGroup = 0;
+        this.volume = 0.0;
+        this.trackMatrix = [];
+        this.width = 0;
+        this.height = 0;
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        this.creationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
+        headerOffset += 4;
 
 
-    this.creationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
-    headerOffset += 4;
+        this.modificationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
+        headerOffset += 4;
 
 
-    this.modificationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
-    headerOffset += 4;
+        this.trackID = view.getUint32(headerOffset);
+        headerOffset += 4;
 
 
-    this.timeScale = view.getUint32(headerOffset);
-    headerOffset += 4;
+        // Reserved
+        // A 32-bit integer that is reserved for use by Apple. Set this field to 0.
+        headerOffset += 4;
 
 
-    this.duration  = view.getUint32(headerOffset);
-    headerOffset += 4;
+        this.duration = view.getUint32(headerOffset);
+        headerOffset += 4;
 
 
-    this.preferredRate = view.getUint32(headerOffset) / (1 << 16);
-    headerOffset += 4;
+        // Reserved
+        // An 8-byte value that is reserved for use by Apple. Set this field to 0.
+        headerOffset += 8;
 
 
-    this.preferredVolume = view.getUint16(headerOffset) / (1 << 8);
-    headerOffset += 2;
+        this.layer = view.getUint16(headerOffset);
+        headerOffset += 2;
 
 
-    // Reserved
-    // Ten bytes reserved for use by Apple. Set to 0.
-    headerOffset += 10;
-
-    this.movieMatrix = new Array(3);
-    // a, b, u:
-    this.movieMatrix[0] = new Array(3);
-    this.movieMatrix[0][0] = view.getUint32(headerOffset) / (1 << 16);
-    headerOffset += 4;
-    this.movieMatrix[0][1] = view.getUint32(headerOffset) / (1 << 16);
-    headerOffset += 4;
-    this.movieMatrix[0][2] = view.getUint32(headerOffset) / (1 << 30);
-    headerOffset += 4;
-
-    // c, d, v:
-    this.movieMatrix[1] = new Array(3);
-    this.movieMatrix[1][0] = view.getUint32(headerOffset) / (1 << 16);
-    headerOffset += 4;
-    this.movieMatrix[1][1] = view.getUint32(headerOffset) / (1 << 16);
-    headerOffset += 4;
-    this.movieMatrix[1][2] = view.getUint32(headerOffset) / (1 << 30);
-    headerOffset += 4;
-
-    // x, y, w:
-    this.movieMatrix[2] = new Array(3);
-    this.movieMatrix[2][0] = view.getUint32(headerOffset) / (1 << 16);
-    headerOffset += 4;
-    this.movieMatrix[2][1] = view.getUint32(headerOffset) / (1 << 16);
-    headerOffset += 4;
-    this.movieMatrix[2][2] = view.getUint32(headerOffset) / (1 << 30);
-    headerOffset += 4;
-
-    this.previewTime = view.getUint32(headerOffset);
-    headerOffset += 4;
-
-    this.previewDuration = view.getUint32(headerOffset);
-    headerOffset += 4;
-
-    this.posterTime = view.getUint32(headerOffset);
-    headerOffset += 4;
-
-    this.selectionTime = view.getUint32(headerOffset);
-    headerOffset += 4;
-
-    this.selectionDuration = view.getUint32(headerOffset);
-    headerOffset += 4;
-
-    this.nextTrackID = view.getUint32(headerOffset);
-    headerOffset += 4;
-
-    return true;
-}
+        this.alternateGroup = view.getUint16(headerOffset);
+        headerOffset += 2;
 
 
-var TrackHeaderAtom = function (buffer, offset) {
-    this.super(TrackHeaderAtom).constructor.call(this, buffer, offset);
-}
+        this.volume = view.getUint16(headerOffset) / (1 << 8);
+        headerOffset += 2;
 
 
-TrackHeaderAtom.prototype = Object.create(VersionFlagsAtom.prototype);
-
-TrackHeaderAtom.prototype.setDefaults = function () {
-    this.super(TrackHeaderAtom).setDefaults.call(this);
-
-    this.description = "Track Header Atom";
-    this.creationTime = 0;
-    this.modificationTime = 0;
-    this.trackID = 0;
-    this.duration = 0;
-    this.layer = 0;
-    this.alternateGroup = 0;
-    this.volume = 0.0;
-    this.trackMatrix = [];
-    this.width = 0;
-    this.height = 0;
-}
+        // Reserved
+        // A 16-bit integer that is reserved for use by Apple. Set this field to 0.
+        headerOffset += 2;
 
 
-TrackHeaderAtom.prototype.parse = function (buffer, offset) {
-    var headerOffset = this.super(TrackHeaderAtom).parse.call(this, buffer, offset);
-    var view = new DataView(buffer, offset);
+        this.trackMatrix = new Array(3);
+        // a, b, u:
+        this.trackMatrix[0] = new Array(3);
+        this.trackMatrix[0][0] = view.getUint32(headerOffset) / (1 << 16);
+        headerOffset += 4;
+        this.trackMatrix[0][1] = view.getUint32(headerOffset) / (1 << 16);
+        headerOffset += 4;
+        this.trackMatrix[0][2] = view.getUint32(headerOffset) / (1 << 30);
+        headerOffset += 4;
 
 
-    this.creationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
-    headerOffset += 4;
+        // c, d, v:
+        this.trackMatrix[1] = new Array(3);
+        this.trackMatrix[1][0] = view.getUint32(headerOffset) / (1 << 16);
+        headerOffset += 4;
+        this.trackMatrix[1][1] = view.getUint32(headerOffset) / (1 << 16);
+        headerOffset += 4;
+        this.trackMatrix[1][2] = view.getUint32(headerOffset) / (1 << 30);
+        headerOffset += 4;
 
 
-    this.modificationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
-    headerOffset += 4;
+        // x, y, w:
+        this.trackMatrix[2] = new Array(3);
+        this.trackMatrix[2][0] = view.getUint32(headerOffset) / (1 << 16);
+        headerOffset += 4;
+        this.trackMatrix[2][1] = view.getUint32(headerOffset) / (1 << 16);
+        headerOffset += 4;
+        this.trackMatrix[2][2] = view.getUint32(headerOffset) / (1 << 30);
+        headerOffset += 4;
 
 
-    this.trackID = view.getUint32(headerOffset);
-    headerOffset += 4;
+        this.width = view.getUint32(headerOffset) / (1 << 16);
+        headerOffset += 4;
 
 
-    // Reserved
-    // A 32-bit integer that is reserved for use by Apple. Set this field to 0.
-    headerOffset += 4;
+        this.height = view.getUint32(headerOffset) / (1 << 16);
+        headerOffset += 4;
 
 
-    this.duration = view.getUint32(headerOffset);
-    headerOffset += 4;
+        return headerOffset;
+    };
+};
 
 
-    // Reserved
-    // An 8-byte value that is reserved for use by Apple. Set this field to 0.
-    headerOffset += 8;
+Atom.constructorMap['tkhd'] = TrackHeaderAtom.bind(null);
 
 
-    this.layer = view.getUint16(headerOffset);
-    headerOffset += 2;
+class MediaHeaderAtom extends FullBox {
+    constructor(parent) {
+        super(parent);
 
 
-    this.alternateGroup = view.getUint16(headerOffset);
-    headerOffset += 2;
+        this.description = "Media Header Atom";
+        this.creationTime = 0;
+        this.modificationTime = 0;
+        this.timeScale = 0;
+        this.duration = 0;
+        this.language = 0;
+        this.quality = 0;
+    };
 
 
-    this.volume = view.getUint16(headerOffset) / (1 << 8);
-    headerOffset += 2;
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
 
 
-    // Reserved
-    // A 16-bit integer that is reserved for use by Apple. Set this field to 0.
-    headerOffset += 2;
+        this.creationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
+        headerOffset += 4;
 
 
-    this.trackMatrix = new Array(3);
-    // a, b, u:
-    this.trackMatrix[0] = new Array(3);
-    this.trackMatrix[0][0] = view.getUint32(headerOffset) / (1 << 16);
-    headerOffset += 4;
-    this.trackMatrix[0][1] = view.getUint32(headerOffset) / (1 << 16);
-    headerOffset += 4;
-    this.trackMatrix[0][2] = view.getUint32(headerOffset) / (1 << 30);
-    headerOffset += 4;
-
-    // c, d, v:
-    this.trackMatrix[1] = new Array(3);
-    this.trackMatrix[1][0] = view.getUint32(headerOffset) / (1 << 16);
-    headerOffset += 4;
-    this.trackMatrix[1][1] = view.getUint32(headerOffset) / (1 << 16);
-    headerOffset += 4;
-    this.trackMatrix[1][2] = view.getUint32(headerOffset) / (1 << 30);
-    headerOffset += 4;
-
-    // x, y, w:
-    this.trackMatrix[2] = new Array(3);
-    this.trackMatrix[2][0] = view.getUint32(headerOffset) / (1 << 16);
-    headerOffset += 4;
-    this.trackMatrix[2][1] = view.getUint32(headerOffset) / (1 << 16);
-    headerOffset += 4;
-    this.trackMatrix[2][2] = view.getUint32(headerOffset) / (1 << 30);
-    headerOffset += 4;
-
-    this.width = view.getUint32(headerOffset) / (1 << 16);
-    headerOffset += 4;
-
-    this.height = view.getUint32(headerOffset) / (1 << 16);
-    headerOffset += 4;
-}
+        this.modificationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
+        headerOffset += 4;
 
 
-var MediaHeaderAtom = function (buffer, offset) {
-    this.super(MediaHeaderAtom).constructor.call(this, buffer, offset);
-}
+        this.timeScale = view.getUint32(headerOffset);
+        headerOffset += 4;
 
 
-MediaHeaderAtom.prototype = Object.create(VersionFlagsAtom.prototype);
+        this.duration = view.getUint32(headerOffset);
+        headerOffset += 4;
 
 
-MediaHeaderAtom.prototype.setDefaults = function () {
-    this.super(MediaHeaderAtom).setDefaults.call(this);
+        this.language = view.getUint16(headerOffset);
+        headerOffset += 2;
 
 
-    this.description = "Media Header Atom";
-    this.creationTime = 0;
-    this.modificationTime = 0;
-    this.timeScale = 0;
-    this.duration = 0;
-    this.language = 0;
-    this.quality = 0;
-}
+        this.quality = view.getUint16(headerOffset);
+        headerOffset += 2;
 
 
-MediaHeaderAtom.prototype.parse = function (buffer, offset) {
-    var headerOffset = this.super(MediaHeaderAtom).parse.call(this, buffer, offset);
-    var view = new DataView(buffer, offset);
+        return headerOffset;
+    };
+};
 
 
-    this.creationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
-    headerOffset += 4;
+Atom.constructorMap['mdhd'] = MediaHeaderAtom.bind(null);
 
 
-    this.modificationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
-    headerOffset += 4;
+class HandlerReferenceBox extends FullBox {
+    constructor(parent) {
+        super(parent);
 
 
-    this.timeScale = view.getUint32(headerOffset);
-    headerOffset += 4;
+        this.description = 'Handler Reference Box';
+        this.handlerType = '';
+        this.name = '';
+    };
 
 
-    this.duration = view.getUint32(headerOffset);
-    headerOffset += 4;
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
 
 
-    this.language = view.getUint16(headerOffset);
-    headerOffset += 2;
+        // unsigned int(32) predefined = 0;
+        headerOffset += 4;
 
 
-    this.quality = view.getUint16(headerOffset);
-    headerOffset += 2;
+        var array = new Uint8Array(buffer, offset + headerOffset, 4);
+        this.handlerType = String.fromCharCode.apply(null, array);
+        headerOffset += 4;
+        
+        // unsigned int(32)[3] reserved = 0;
+        headerOffset += 12;
+
+        var remaining = this.size - headerOffset;
+        array = new Uint8Array(buffer, offset + headerOffset, remaining);
+        this.name = String.fromCharCode.apply(null, array);
+
+        headerOffset += remaining;
+
+        return headerOffset;
+    };
 };
 
 };
 
-var SyncSampleAtom = function (buffer, offset) {
-    this.super(SyncSampleAtom).constructor.call(this, buffer, offset);
+Atom.constructorMap['hdlr'] = HandlerReferenceBox.bind(null);
+
+class SyncSampleAtom extends Atom {
+    constructor(parent) {
+        super(parent);
+
+        this.description = "Sync Sample Atom";
+        this.version = 0;
+        this.flags = 0;
+        this.entries = 0;
+        this.syncSamples = [];
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        this.version = view.getUint8(headerOffset);
+        headerOffset += 1;
+
+        // 'flags' is a 3-byte field, so retrieve from one extra byte and concatenate
+        this.flags = (view.getUint8(headerOffset) << 8) + view.getUint16(headerOffset + 1);
+        headerOffset += 3;
+
+        this.entries = view.getUint32(headerOffset);
+        headerOffset += 4;
+
+        this.syncSamples = new Uint32Array(this.entries);
+        var i = 0;
+        while (headerOffset < this.size) {
+            var sampleNumber = view.getUint32(headerOffset);
+            headerOffset += 4;
+            this.syncSamples[i] = sampleNumber;
+            ++i;
+        }
+
+        return headerOffset;
+    };
 };
 
 };
 
-SyncSampleAtom.prototype = Object.create(Atom.prototype);
+Atom.constructorMap['stss'] = SyncSampleAtom.bind(null);
 
 
-SyncSampleAtom.prototype.setDefaults = function () {
-    this.super(SyncSampleAtom).setDefaults.call(this);
+class TimeToSampleAtom extends FullBox {
+    constructor(parent) {
+        super(parent);
+        this.description = "Time-to-Sample Atom";
+        this.entries = 0;
+        
+        Object.defineProperty(this, "timeToSamples", {
+            value: null,
+            writable: true,
+            enumerable: false,
+            configurable: true,
+        });
+    };
 
 
-    this.description = "Sync Sample Atom";
-    this.version = 0;
-    this.flags = 0;
-    this.entries = 0;
-    this.syncSamples = [];
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        this.entries = view.getUint32(headerOffset);
+        headerOffset += 4;
+
+        this.timeToSamples = new Array(this.entries);
+        var i = 0;
+
+        while (headerOffset < this.size) {
+            var sampleCount = view.getUint32(headerOffset);
+            headerOffset += 4;
+
+            var sampleDuration = view.getUint32(headerOffset);
+            headerOffset += 4;
+
+            this.timeToSamples[i] = [sampleCount, sampleDuration];
+            ++i;
+        }
+
+        return headerOffset;
+    };
+
+
+    timeForIndex(index)
+    {
+        var sampleSum = 0;
+        var timeSum = 0;
+
+        for (var j = 0; j < this.timeToSamples.length; ++j) {
+            var samplesWithTime = this.timeToSamples[j][0];
+            var sampleLength = this.timeToSamples[j][1];
+            var samplesThisPass = Math.min(index - sampleSum, samplesWithTime);
+            if (isNaN(samplesWithTime) || isNaN(sampleLength))
+                break;
+
+            sampleSum += samplesThisPass;
+            timeSum += samplesThisPass * sampleLength;
+
+            if (sampleSum >= index)
+                break;
+        }
+
+        return timeSum;
+    };
 };
 
 };
 
-SyncSampleAtom.prototype.parse = function (buffer, offset) {
-    var headerOffset = this.super(SyncSampleAtom).parse.call(this, buffer, offset);
-    var view = new DataView(buffer, offset);
+Atom.constructorMap['stts'] = TimeToSampleAtom.bind(null);
 
 
-    this.version = view.getUint8(headerOffset);
-    headerOffset += 1;
+class SampleSizeAtom extends FullBox {
+    constructor(parent) {
+        super(parent);
+        this.description = "Sample Size Atom";
+        this.sampleSize = 0;
+        this.entries = 0;
+        
+        Object.defineProperty(this, "sampleSizes", {
+            value: null,
+            writable: true,
+            enumerable: false,
+            configurable: true,
+        });
+    };
 
 
-    // 'flags' is a 3-byte field, so retrieve from one extra byte and concatenate
-    this.flags = (view.getUint8(headerOffset) << 8) + view.getUint16(headerOffset + 1);
-    headerOffset += 3;
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
 
 
-    this.entries = view.getUint32(headerOffset);
-    headerOffset += 4;
+        this.sampleSize = view.getUint32(headerOffset);
+        headerOffset += 4;
 
 
-    this.syncSamples = new Uint32Array(this.entries);
-    var i = 0;
-    while (headerOffset < this.size) {
-        var sampleNumber = view.getUint32(headerOffset);
+        this.entries = view.getUint32(headerOffset);
         headerOffset += 4;
         headerOffset += 4;
-        this.syncSamples[i] = sampleNumber;
-        ++i;
-    }
+
+        this.sampleSizes = new Uint32Array(this.entries);
+        var i = 0;
+
+        while (headerOffset < this.size) {
+            this.sampleSizes[i] = view.getUint32(headerOffset);
+            headerOffset += 4;
+            ++i;
+        }
+
+        return headerOffset;
+    };
 };
 
 };
 
-var TimeToSampleAtom = function (buffer, offset) {
-    this.super(TimeToSampleAtom).constructor.call(this, buffer, offset);
+Atom.constructorMap['stsz'] = SampleSizeAtom.bind(null);
+
+class SampleDescriptionBox extends FullBox {
+    constructor(parent) {
+        super(parent);
+
+        this.description = "Sample Description Box";
+        this.childAtoms = [];
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        if (this.parent.type !== 'stbl' || this.parent.parent.type !== 'minf' || this.parent.parent.parent.type !== 'mdia')
+            return;
+
+        var handlerBox = this.parent.parent.parent.getAtomByType('hdlr');
+        if (!handlerBox)
+            return;
+
+        var entryCount = view.getUint32(headerOffset);
+        headerOffset += 4;
+
+        for (var index = 0; index < entryCount; ++index) {
+            var entry;
+            var type = Atom.getType(buffer, offset + headerOffset);
+            if (typeof(Atom.constructorMap[type]) !== 'undefined')
+                entry = Atom.create(buffer, offset + headerOffset);
+            else {
+                switch (handlerBox.handlerType) {
+                    case 'soun':
+                    entry = new AudioSampleEntry(this);
+                    break;
+                    case 'vide':
+                    entry = new VisualSampleEntry(this);
+                    break;
+                    case 'hint':
+                    entry = new HintSampleDescriptionBox(this);
+                    break;
+                    case 'meta':
+                    entry = new MetadataSampleDescriptionBox(this);
+                    break;
+                    default:
+                    return;
+                }
+                entry.parse(buffer, offset + headerOffset);
+            }
+            headerOffset += entry.size;
+            this.childAtoms.push(entry);
+        }
+
+        return headerOffset;
+    };
 };
 
 };
 
-TimeToSampleAtom.prototype = Object.create(VersionFlagsAtom.prototype);
+Atom.constructorMap['stsd'] = SampleDescriptionBox.bind(null);
 
 
-TimeToSampleAtom.prototype.setDefaults = function () {
-    this.super(TimeToSampleAtom).setDefaults.call(this);
+class SampleEntry extends Atom {
+    constructor(parent) {
+        super(parent);
+
+        this.description = 'Sample Entry';
+        this.dataReferenceIndex = 0;
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+    // unsigned int(8)[6] reserved = 0
+    headerOffset += 6;
+
+    this.dataReferenceIndex = view.getUint16(headerOffset);
+    headerOffset += 2;
 
 
-    this.description = "Time-to-Sample Atom";
-    this.entries = 0;
-    
-    Object.defineProperty(this, "timeToSamples", {
-        value: null,
-        writable: true,
-        enumerable: false,
-        configurable: true,
-    });
+    return headerOffset;
 }
 }
+};
+
+class AudioSampleEntry extends SampleEntry {
+    constructor(parent) {
+        super(parent);
+        this.description = 'Audio Sample Entry';
+        this.channelCount = 0;
+        this.sampleSize = 0;
+        this.sampleRate = 0;
+    };
 
 
-TimeToSampleAtom.prototype.parse = function (buffer, offset) {
-    var headerOffset = this.super(TimeToSampleAtom).parse.call(this, buffer, offset);
-    var view = new DataView(buffer, offset);
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
 
 
-    this.entries = view.getUint32(headerOffset);
-    headerOffset += 4;
+        // unsigned int(32)[2] reserved = 0
+        headerOffset += 8;
 
 
-    this.timeToSamples = new Array(this.entries);
-    var i = 0;
+        this.channelCount = view.getUint16(headerOffset);
+        headerOffset += 2;
 
 
-    while (headerOffset < this.size) {
-        var sampleCount = view.getUint32(headerOffset);
+        this.sampleSize = view.getUint16(headerOffset);
+        headerOffset += 2;
+
+        // unsigned int(16) pre_defined = 0
+        // const unsigned int(16) reserved = 0
         headerOffset += 4;
 
         headerOffset += 4;
 
-        var sampleDuration = view.getUint32(headerOffset);
+        this.sampleRate = (view.getUint32(headerOffset) >> 16) & 0xFFFF;
         headerOffset += 4;
 
         headerOffset += 4;
 
-        this.timeToSamples[i] = [sampleCount, sampleDuration];
-        ++i;
+        return headerOffset;
+    };
+};
+
+class MP4AudioSampleEntry extends AudioSampleEntry {
+    constructor(parent) {
+        super(parent);
+        this.description = 'MP4 Audio Sample Entry';
+        this.childAtoms = [];
+    };
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var ES = new ESDBox(this);
+        ES.parse(buffer, offset+ headerOffset);
+        this.childAtoms.push(ES);
+        headerOffset += ES.size;
+
+        return headerOffset;
+    };
+};
+Atom.constructorMap['mp4a'] = MP4AudioSampleEntry.bind(null);
+
+class ESDBox extends FullBox {
+    constructor(parent) {
+        super(parent);
+        this.description = 'Sample Description Box'
     }
     }
-}
 
 
-var SampleSizeAtom = function (buffer, offset) {
-    this.super(SampleSizeAtom).constructor.call(this, buffer, offset);
-}
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
 
 
-SampleSizeAtom.prototype = Object.create(VersionFlagsAtom.prototype);
-
-SampleSizeAtom.prototype.setDefaults = function () {
-    this.super(SampleSizeAtom).setDefaults.call(this);
-
-    this.description = "Sample Size Atom";
-    this.sampleSize = 0;
-    this.entries = 0;
-    
-    Object.defineProperty(this, "sampleSizes", {
-        value: null,
-        writable: true,
-        enumerable: false,
-        configurable: true,
-    });
-}
+        this.descriptor = new ESDescriptor(this);
+        headerOffset += this.descriptor.parse(buffer, offset + headerOffset);
+
+        return headerOffset;
+    }
+};
+
+Atom.constructorMap['esds'] = ESDBox.bind(null);
+
+class BaseDescriptor {
+    constructor(parent) {
+        Object.defineProperty(this, "parent", {
+            value: parent,
+            writable: true,
+            enumerable: false,
+            configurable: true,
+        });
+        Object.defineProperty(this, "description", {
+            value: "Abstract Descriptor",
+            writable: true,
+            enumerable: false,
+            configurable: true,
+        });
+
+        this.tag = 0;
+        this.size = 0;
+    };
+    parse(buffer, offset) {
+        var headerOffset = 0;
+        var view = new DataView(buffer, offset);
+
+        this.tag = view.getUint8(headerOffset);
+        headerOffset += 1;
+
+        var tagInfo = BaseDescriptor.TagMap[this.tag];
+        if (typeof(tagInfo) !== 'undefined')
+            this.name = tagInfo.name;
+
+        // BaseDescriptor starts at a size of 2, and can be extended:
+        this.size = 2;
+        for (var i = 0; i < 4; ++i) {
+            var nextSizeByte = view.getUint8(headerOffset);
+            headerOffset += 1;
+
+            var msb = nextSizeByte & 0x80;
+            var size = nextSizeByte & 0x7f;
+            this.size += size;
+
+            if (!msb)
+                break;
+        }
+        return headerOffset;
+    };
+};
+
+BaseDescriptor.TagMap = {
+    3: { name: 'ES_DescrTag' },
+    4: { name: 'DecoderConfigDescrTag' },
+    5: { name: 'DecSpecificInfoTag' },
+};
+
+class ESDescriptor extends BaseDescriptor {
+    constructor(parent) {
+        super(parent);
+        this.description = "ES Descriptor"
+        this.ES_ID = 0;
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);;
+        var view = new DataView(buffer, offset);
+
+        this.ES_ID = view.getUint16(headerOffset);
+        headerOffset += 2;
+
+        var nextByte = view.getUint8(headerOffset);
+        headerOffset += 1;
 
 
-SampleSizeAtom.prototype.parse = function (buffer, offset) {
-    var headerOffset = this.super(SampleSizeAtom).parse.call(this, buffer, offset);
-    var view = new DataView(buffer, offset);
+        this.streamDependencyFlag = nextByte & (1 << 7);
+        this.urlFlag = nextByte & (1 << 6);
+        this.ocrStreamFlag = nextByte & (1 << 5);
+        this.streamPriority = nextByte & 0x1f;
 
 
-    this.sampleSize = view.getUint32(headerOffset);
-    headerOffset += 4;
+        if (this.streamDependencyFlag) {
+            this.dependsOn_ES_Number = view.getUint16(headerOffset);
+            headerOffset += 2;
+        }
 
 
-    this.entries = view.getUint32(headerOffset);
-    headerOffset += 4;
+        if (this.urlFlag) {
+            var urlLength = view.getUint8(headerOffset);
+            headerOffset += 1;
 
 
-    this.sampleSizes = new Uint32Array(this.entries);
-    var i = 0;
+            var array = new Uint8Array(buffer, offset + headerOffset, urlLength);
+            headerOffset += urlLength;
+            this.url = String.fromCharCode.apply(null, array);
+        }
 
 
-    while (headerOffset < this.size) {
-        this.sampleSizes[i] = view.getUint32(headerOffset);
+        if (this.ocrStreamFlag) {
+            this.ocr_ES_ID = view.getUint16(headerOffset);
+            headerOffset += 2;   
+        }
+
+        this.decoderConfigDescriptor = new DecoderConfigDescriptor(this);
+        headerOffset += this.decoderConfigDescriptor.parse(buffer, offset + headerOffset);
+
+        return headerOffset;
+    }
+};
+
+class DecoderConfigDescriptor extends BaseDescriptor {
+    constructor(parent) { 
+        super(parent);
+        this.description = "Decoder Config Descriptor"
+        this.streamType = 0;
+        this.objectTypeIndication = 0;
+        this.upStream = 0;
+        this.specificInfoFlag = 0;
+        this.bufferSizeDB = 0;
+        this.maxBitrate = 0;
+        this.avgBitrate = 0;
+        this.specificInfo = [];
+    };
+
+    parse(buffer, offset)
+    {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        this.objectTypeIndication = view.getUint8(headerOffset);
+        headerOffset += 1;
+
+        var nextByte = view.getUint8(headerOffset);
+        this.streamType = (nextByte >> 2) & 0x3f;
+        this.upStream = nextByte & 0x2;
+        this.specificInfoFlag = nextByte & 0x1;
+        headerOffset += 1;
+
+        var next4Bytes = view.getUint32(headerOffset);
+        this.bufferSizeDB = (next4Bytes >> 8) & 0xFFFFFF
+        headerOffset += 3;
+
+        this.maxBitrate = view.getUint32(headerOffset);
+        headerOffset += 4;
+
+        this.avgBitrate = view.getUint32(headerOffset);
         headerOffset += 4;
         headerOffset += 4;
-        ++i;
+
+        while (this.specificInfoFlag && headerOffset < this.size) {
+            var specificInfo = new DecoderSpecificInfo(this);
+            specificInfo.parse(buffer, offset + headerOffset)
+            headerOffset += specificInfo.size;
+
+            this.specificInfo.push(specificInfo);
+        }
+
+        return headerOffset;
     }
     }
-}
+};
 
 
-var TrackExtendsAtom = function (buffer, offset) {
-    this.super(TrackExtendsAtom).constructor.call(this, buffer, offset);
+class DecoderSpecificInfo extends BaseDescriptor {
+    constructor(parent) {
+        // 'Audio ISO/IEC 14496-3' && 'AudioStreamType'
+        if (parent.objectTypeIndication == 0x40 && parent.streamType == 0x5)
+            return new AudioSpecificConfig(parent);
+
+        super(parent);
+        this.description = 'Decoder Specific Info';
+    }
 }
 
 }
 
-TrackExtendsAtom.prototype = Object.create(VersionFlagsAtom.prototype);
+class AudioSpecificConfig extends BaseDescriptor {
+    constructor(parent) {
+        super(parent);
+        this.audioObjectType = 0;
+        this.samplingFrequencyIndex = 0;
+        this.channelConfiguration = 0;
+    }
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+               if (this.size < headerOffset)
+                       return;
+
+        var array = new Uint8Array(buffer, offset + headerOffset, this.size - headerOffset);
+        var bitReader = new BitReader(array, 0);
+
+        this.audioObjectType = bitReader.readBits(5);
+        if (this.audioObjectType === 0x1f)
+            this.audioObjectType = 32 + bitReader.readBits(6);
 
 
-TrackExtendsAtom.prototype.setDefaults = function () {
-    this.super(TrackExtendsAtom).setDefaults.call(this);
+        this.samplingFrequencyIndex = bitReader.readBits(4);
+        if (this.samplingFrequencyIndex === 0xf)
+            this.samplingFrequencyIndex += bitReader.readBits(24);
 
 
-    this.description = "Track Extends Atom";
-    this.trackID = 0;
-    this.default_sample_description_index = 0;
-    this.default_sample_duration = 0;
-    this.default_sample_size = 0;
-    this.default_sample_flags = 0;
+        return headerOffset;
+    }
 }
 
 }
 
-TrackExtendsAtom.prototype.parse = function (buffer, offset) {
-    var headerOffset = this.super(TrackExtendsAtom).parse.call(this, buffer, offset);
-    var view = new DataView(buffer, offset);
+class VisualSampleEntry extends SampleEntry {
+    constructor(parent) {
+        super(parent);
+
+        this.description = 'Visual Sample Entry';
+        this.width = 0;
+        this.height = 0;
+        this.horizontalResolution = 0;
+        this.verticalResolution = 0;
+        this.frameCount;
+        this.compressorName;
+        this.depth;
+        this.childAtoms = [];
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        // unsigned int(16) pre_defined = 0
+        // const unsigned int(16) reserved = 0
+        // unsigned int(32)[3] pre_defined = 0
+        headerOffset += 16;
+
+        this.width = view.getUint16(headerOffset);
+        headerOffset += 2;
+
+        this.height = view.getUint16(headerOffset);
+        headerOffset += 2;
+
+        this.horizontalResolution = view.getUint32(headerOffset) / (1 << 16);
+        headerOffset += 4;
 
 
-    this.trackID = view.getUint32(headerOffset);
-    headerOffset += 4;
+        this.verticalResolution = view.getUint32(headerOffset) / (1 << 16);
+        headerOffset += 4;
 
 
-    this.default_sample_description_index = view.getUint32(headerOffset);
-    headerOffset += 4;
+        // const unsigned int(32) reserved = 0
+        headerOffset += 4;
 
 
-    this.default_sample_duration = view.getUint32(headerOffset);
-    headerOffset += 4;
+        this.frameCount = view.getUint16(headerOffset);
+        headerOffset += 2;
 
 
-    this.default_sample_size = view.getUint32(headerOffset);
-    headerOffset += 4;
+        var array = new Uint8Array(buffer, offset + headerOffset, 32);
+        this.compressorName = String.fromCharCode.apply(null, array);
+        headerOffset += 32;
 
 
-    this.default_sample_flags = view.getUint32(headerOffset);
-    headerOffset += 4;
-}
+        this.depth = view.getUint16(headerOffset);
+        headerOffset += 2;
 
 
-var OriginalFormatBox = function (buffer, offset) {
-    this.super(OriginalFormatBox).constructor.call(this, buffer, offset);
-}
+        // int(16) pre_defined = -1;
+        headerOffset += 2;
 
 
-OriginalFormatBox.prototype = Object.create(Atom.prototype);
+        while (this.size - headerOffset > 8) {
+            var childAtom = Atom.create(buffer, offset + headerOffset, this);
+            if (!childAtom)
+                break;
+            headerOffset += childAtom.size;
+            this.childAtoms.push(childAtom);
+        }
+        return headerOffset;
+    };
+};
 
 
-OriginalFormatBox.prototype.setDefaults = function () {
-    this.super(OriginalFormatBox).setDefaults.call(this);
+class AVCConfigurationBox extends Atom {
+    constructor(parent) {
+        super(parent);
 
 
-    this.description = "Original Format Box";
-    this.dataFormat = 0;
-}
+        this.description = 'AVC Configuration Box';
+        this.configurationVersion = 0;
+        this.AVCProfileIndication = 0;
+        this.profileCompatibility = 0;
+        this.AVCLevelIndication = 0;
+        this.sequenceParameterSets = [];
+        this.pictureParameterSets = [];
+    };
 
 
-OriginalFormatBox.prototype.parse = function (buffer, offset) {
-    var headerOffset = this.super(OriginalFormatBox).parse.call(this, buffer, offset);
-    var view = new DataView(buffer, offset);
+    parse(buffer, offset)
+    {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
 
 
-    this.dataFormat = view.getUint32(headerOffset);
-    headerOffset += 4;
-}
+        this.configurationVersion = view.getUint8(headerOffset);
+        ++headerOffset;
 
 
-var SchemeTypeBox = function (buffer, offset) {
-    this.super(SchemeTypeBox).constructor.call(this, buffer, offset);
-}
+        this.AVCProfileIndication = view.getUint8(headerOffset);
+        ++headerOffset;
 
 
-SchemeTypeBox.prototype = Object.create(VersionFlagsAtom.prototype);
+        this.profileCompatibility = view.getUint8(headerOffset);
+        ++headerOffset;
 
 
-SchemeTypeBox.prototype.setDefaults = function () {
-    this.super(SchemeTypeBox).setDefaults.call(this);
+        this.AVCLevelIndication = view.getUint8(headerOffset);
+        ++headerOffset;
 
 
-    this.description = "Scheme Type Box";
-    this.schemeType = 0;
-    this.schemeVersion = 0;
-    this.schemeURL = 0;
-}
+        var lengthSizeMinusOne = view.getUint8(headerOffset) & 0x3;
+        ++headerOffset;
+
+        var numOfSequenceParameterSets = view.getUint8(headerOffset) & 0x1f;
+        ++headerOffset;
+
+        for (var index = 0; index < numOfSequenceParameterSets; ++index) {
+            var sequenceParameterSetLength = view.getUint16(headerOffset);
+            headerOffset += 2;
+
+            this.sequenceParameterSets.push(new Uint8Array(buffer, offset + headerOffset, sequenceParameterSetLength));
+            headerOffset += sequenceParameterSetLength;
+        }
+
+        var numOfPictureParameterSets = view.getUint8(headerOffset) & 0x1f;
+        ++headerOffset;
+
+        for (index = 0; index < numOfPictureParameterSets; ++index) {
+            var pictureParameterSetLength = view.getUint16(headerOffset);
+            headerOffset += 2;
+
+            this.pictureParameterSets.push(new Uint8Array(buffer, offset + headerOffset, pictureParameterSetLength));
+            headerOffset += pictureParameterSetLength;
+        }
+
+        if ([100, 110, 122, 144].indexOf(this.AVCProfileIndication) >= 0) {
+
+            // bit(6) reserved = '111111'b
+            this.chromaFormat = view.getUint8(headerOffset) & 0x3;
+            ++headerOffset;
+
+            // bit(6) reserved = '111111'b
+            this.bitDepthLumaMinus8 = view.getUint8(headerOffset) & 0x3;
+            ++headerOffset;
+
+            // bit(5) reserved = '11111'b;

+            this.bitDepthChromaMinus8 = view.getUint8(headerOffset) & 0x7;
+            ++headerOffset;
+            
+            if (headerOffset >= this.size)
+                return headerOffset;
+
+            var numOfSequenceParameterSetExt = view.getUint8(headerOffset);
+            this.sequenceParameterSets = [];
+            ++headerOffset;
+
+            for (index = 0; index < numOfSequenceParameterSetExt; ++index) {
+                var sequenceParameterSetLength = view.getUint16(headerOffset);
+                headerOffset += 2;
+
+                this.sequenceParameterSets.push(new Uint8Array(buffer, offset + headerOffset, sequenceParameterSetLength));
+                headerOffset += sequenceParameterSetLength;
+            }
+        }
+
+        return headerOffset;
+    };
+};
+
+Atom.constructorMap['avcC'] = AVCConfigurationBox.bind(null);
+
+class HEVCConfigurationBox extends Atom {
+    constructor(parent) {
+        super(parent);
+
+        this.description = 'HEVC Configuration Box';
+        this.configuration_version = 0;
+        this.general_profile_space = 0;
+        this.general_tier_flag = 0;
+        this.general_profile_idc = 0;
+        this.general_profile_compatibility_flags = 0;
+        this.general_constraint_indicator_flags = 0;
+        this.general_level_idc = 0;
+        this.min_spatial_segmentation_idc = 0;
+        this.parallelismType = 0;
+        this.chromaFormat = 0;
+        this.bitDepthLumaMinus8 = 0;
+        this.bitDepthChromaMinus8 = 0;
+        this.avgFrameRate = 0;
+        this.constantFrameRate = 0;
+        this.numTemporalLayers = 0;
+        this.temporalIdNested = 0;
+    };
+
+    parse(buffer, offset)
+    {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        this.configuration_version = view.getUint8(headerOffset);
+        ++headerOffset;
+
+        var byte = view.getUint8(headerOffset);
+        ++headerOffset;
+
+        this.general_profile_space = (byte & 0x00C0) >> 6;
+        this.general_tier_flag = (byte & 0x0020) >> 5;
+        this.general_profile_idc = byte & 0x001F;
+
+        this.general_profile_compatibility_flags = view.getUint32(headerOffset);
+        headerOffset += 4;
+
+        this.general_constraint_indicator_flags = view.getUint16(headerOffset) << 32
+            + view.getUint32(headerOffset + 2);
+        headerOffset += 6;
+
+        this.general_level_idc = view.getUint8(headerOffset);
+        ++headerOffset;
+
+        // bit(4) reserved = â€˜1111’b;
+        this.min_spatial_segmentation_idc = view.getUint16(headerOffset) & 0x0FFF;
+        headerOffset += 2;
+
+        // bit(6) reserved = â€˜111111’b;
+        this.parallelismType = view.getUint8(headerOffset) & 0x03;
+        ++headerOffset;
+
+        // bit(6) reserved = â€˜111111’b;
+        this.chromaFormat = view.getUint8(headerOffset) & 0x03;
+        ++headerOffset;
+
+        // bit(5) reserved = â€˜11111’b;
+        this.bitDepthLumaMinus8 = view.getUint8(headerOffset) & 0x07;
+        ++headerOffset;
+
+        // bit(5) reserved = â€˜11111’b;
+        this.bitDepthChromaMinus8 = view.getUint8(headerOffset) & 0x07;
+        ++headerOffset;
+
+        this.avgFrameRate = view.getUint16(headerOffset);
+        headerOffset += 2;
+
+        byte = view.getUint8(headerOffset);
+        ++headerOffset;
+
+        this.constantFrameRate = (byte & 0xC0) >> 6;
+        this.numTemporalLayers = (byte & 0x38) >> 3;
+        this.temporalIdNested = (byte & 0x04) >> 2;
+        this.lengthSizeMinusOne = byte & 0x02;
+
+        return this.size;
+    };
+};
+
+Atom.constructorMap['hvcC'] = HEVCConfigurationBox.bind(null);
+
+class CleanApertureBox extends Atom {
+    constructor(parent) {
+        super(parent);
+
+        this.description = 'Clean Aperture Box';
+        this.cleanApertureWidthN = 0;
+        this.cleanApertureWidthD = 0;
+        this.cleanApertureHeightN = 0;
+        this.cleanApertureHeightD = 0;
+        this.horizOffN = 0;
+        this.horizOffD = 0;
+        this.vertOffN = 0;
+        this.vertOffD = 0;
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        this.cleanApertureWidthN = view.getUint32(headerOffset);
+        headerOffset += 4;
+        
+        this.cleanApertureWidthD = view.getUint32(headerOffset);
+        headerOffset += 4;
+        
+        this.cleanApertureHeightN = view.getUint32(headerOffset);
+        headerOffset += 4;
+        
+        this.cleanApertureHeightD = view.getUint32(headerOffset);
+        headerOffset += 4;
+        
+        this.horizOffN = view.getUint32(headerOffset);
+        headerOffset += 4;
+        
+        this.horizOffD = view.getUint32(headerOffset);
+        headerOffset += 4;
+        
+        this.vertOffN = view.getUint32(headerOffset);
+        headerOffset += 4;
+        
+        this.vertOffD = view.getUint32(headerOffset);
+        headerOffset += 4;
 
 
-SchemeTypeBox.prototype.parse = function (buffer, offset) {
-    var headerOffset = this.super(SchemeTypeBox).parse.call(this, buffer, offset);
-    var view = new DataView(buffer, offset);
-
-    this.schemeType = view.getUint32(headerOffset);
-    headerOffset += 4;
-    this.schemeVersion = view.getUint32(headerOffset);
-    headerOffset += 4;
-    if (this.flags & 0x1) {
-        var array = new Uint8Array(buffer, headerOffset, this.size - headerOffset);
-        this.schemeURL = String.fromCharCode.apply(null, array);
+        return headerOffset;
+    };
+};
+
+Atom.constructorMap['clap'] = CleanApertureBox.bind(null);
+
+class TrackExtendsAtom extends FullBox {
+    constructor(parent) {
+        super(parent);
+        this.description = "Track Extends Atom";
+        this.trackID = 0;
+        this.default_sample_description_index = 0;
+        this.default_sample_duration = 0;
+        this.default_sample_size = 0;
+        this.default_sample_flags = 0;
     }
     }
-}
 
 
-var SchemeInfoBox = function (buffer, offset) {
-    this.super(SchemeInfoBox).constructor.call(this, buffer, offset);
-}
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        this.trackID = view.getUint32(headerOffset);
+        headerOffset += 4;
 
 
-SchemeInfoBox.prototype = Object.create(VersionFlagsAtom.prototype);
+        this.default_sample_description_index = view.getUint32(headerOffset);
+        headerOffset += 4;
 
 
-SchemeInfoBox.prototype.setDefaults = function () {
-    this.super(SchemeInfoBox).setDefaults.call(this);
+        this.default_sample_duration = view.getUint32(headerOffset);
+        headerOffset += 4;
 
 
-    this.description = "Scheme Information Box";
-    this.schemeSpecificData = 0;
-}
+        this.default_sample_size = view.getUint32(headerOffset);
+        headerOffset += 4;
+
+        this.default_sample_flags = view.getUint32(headerOffset);
+        headerOffset += 4;
+
+        return headerOffset;
+    };
+};
+
+Atom.constructorMap['trex'] = TrackExtendsAtom.bind(null);
+
+class OriginalFormatBox extends Atom {
+    constructor(parent) {
+        super(parent);
+        this.description = "Original Format Box";
+        this.dataFormat = 0;
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+
+        var array = new Uint8Array(buffer, offset + headerOffset, 4);
+        this.dataFormat = String.fromCharCode.apply(null, array);
+        headerOffset += 4;
+
+        return headerOffset;
+    };
+};
+
+Atom.constructorMap['frma'] = OriginalFormatBox.bind(null);
+
+class SchemeTypeBox extends FullBox {
+    constructor(parent) {
+        super(parent);
+        this.description = "Scheme Type Box";
+        this.schemeType = 0;
+        this.schemeVersion = 0;
+        this.schemeURL = 0;
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        var array = new Uint8Array(buffer, offset + headerOffset, 4);
+        this.schemeType = String.fromCharCode.apply(null, array);
+        headerOffset += 4;
+
+        this.schemeVersion = view.getUint32(headerOffset);
+        headerOffset += 4;
+
+        if (this.flags & 0x1) {
+            var remaining = this.size - headerOffset;
+            array = new Uint8Array(buffer, offset + headerOffset, remaining);
+            headerOffset += remaining;
+            this.schemeURL = String.fromCharCode.apply(null, array);
+        }
+
+        return headerOffset;
+    };
+};
+
+Atom.constructorMap['schm'] = SchemeTypeBox.bind(null);
+
+class TrackEncryptionBox extends FullBox {
+    constructor(parent) {
+        super(parent);
+
+        this.description = "Track Encryption Box";
+        this.defaultCryptByteBlock = 0;
+        this.defaultSkipByteBlock = 0;
+        this.defaultIsProtected = 0;
+        this.defaultPerSampleIVSize = 0;
+        this.defaultKID = '';
+        this.defaultConstantIV = null;
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        // unsigned int(8)      reserved = 0
+        ++headerOffset;
+
+        if (!this.version) {
+            // unsigned int(8)  reserved = 0
+            ++headerOffset
+        } else {
+            this.defaultCryptByteBlock = (view.getUint8(headerOffset) >> 4) & 0xF;
+            this.defaultSkipByteBlock = view.getUint8(headerOffset) & 0xF;
+            ++headerOffset;
+        }
+
+        this.defaultIsProtected = view.getUint8(headerOffset);
+        ++headerOffset;
+
+        this.defaultPerSampleIVSize = view.getUint8(headerOffset);
+        ++headerOffset;
+
+        var KIDArrayView = new Uint8Array(buffer, offset + headerOffset, 16);
+        this.defaultKID = String.prototype.concat.apply("0x", Array.prototype.map.call(KIDArrayView, function(value){ return value.toString(16); }));
+        headerOffset += 16;
+
+        if (this.defaultIsProtected && !this.defaultPerSampleIVSize) {
+            var size = view.getUint8(headerOffset);
+            ++headerOffset;
+
+            this.defaultConstantIV = new Uint8Array(buffer, offset + headerOffset, size);
+            headerOffset += size;
+        }
+
+        return headerOffset;
+    };
+};
+
+Atom.constructorMap['tenc'] = TrackEncryptionBox.bind(null);
+
+class SegmentIndexBox extends FullBox {
+    constructor(parent) {
+        super(parent);
+
+        this.description = "Segment Index Box";
+        this.referenceID = 0;
+        this.timeScale = 0;
+        this.earliestPresentationTime = 0;
+        this.firstOffset = 0;
+        this.references = [];
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        this.referenceID = view.getUint32(headerOffset);
+        headerOffset += 4;
+
+        this.timeScale = view.getUint32(headerOffset);
+        headerOffset += 4;
+
+        if (this.version == 1) {
+            var upper = view.getUint32(headerOffset);
+            headerOffset += 4;
+            var lower = view.getUint32(headerOffset);
+            headerOffset += 4;
+
+            this.earliestPresentationTime = (upper << 32)  + lower;
 
 
-SchemeInfoBox.prototype.parse = function (buffer, offset) {
-    var headerOffset = this.super(SchemeInfoBox).parse.call(this, buffer, offset);
-    this.schemeSpecificData = buffer.slice(headerOffset, this.size - headerOffset);
+            upper = view.getUint32(headerOffset);
+            headerOffset += 4;
+            lower = view.getUint32(headerOffset);
+            headerOffset += 4;
+
+            this.firstOffset = (upper << 32)  + lower;
+        } else {
+            this.earliestPresentationTime = view.getUint32(headerOffset);
+            headerOffset += 4;
+
+            this.firstOffset = view.getUint32(headerOffset);
+            headerOffset += 4;
+        }
+
+        headerOffset += 2; // Reserved uint(16)
+
+        this.referenceCount = view.getUint16(headerOffset);
+        headerOffset += 2;
+
+        this.references = [];
+
+        for (var i = 0; i < this.referenceCount; ++i) {
+            var value = view.getUint32(headerOffset);
+            headerOffset += 4;
+
+            var reference = {};
+            reference.type = (value & 0x80000000) == 0x80000000;
+            reference.size = value & ~0x80000000;
+
+            reference.subsegmentDuration = view.getUint32(headerOffset);
+            headerOffset += 4;
+
+            value = view.getUint32(headerOffset);
+            headerOffset += 4;
+
+            reference.startsWithSAP = (value & 0x80000000) == 0x80000000;
+            reference.SAPType = (value & 0x70000000) >> 28;
+            reference.SAPDeltaTime = value & ~0xF0000000;
+            this.references.push(reference);
+        }
+
+        this.totalDuration = this.references.reduce(function(previousValue, reference) {
+            return previousValue + reference.subsegmentDuration;
+        }, 0);
+
+        return headerOffset;
+    };
+};
+
+Atom.constructorMap['sidx'] = SegmentIndexBox.bind(null);
+
+class ProtectionSystemBox extends FullBox {
+    constructor(parent) {
+        super(parent)
+        this.description = "Protection System Box";
+        this.systemID = 0;
+        this.KIDs = [];
+        this.data = null;
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        var UUIDArrayView = new Uint8Array(buffer, offset + headerOffset, 16);
+        this.systemID = String.prototype.concat.apply("0x", Array.prototype.map.call(UUIDArrayView, function(value){
+            return value.toString(16);
+        }));
+        headerOffset += 16;
+
+        if (this.version > 0) {
+            var kidCount = view.getUint32(headerOffset);
+            headerOffset += 4;
+
+            for (var index = 0; index < kidCount; ++index) {
+                var KIDArrayView = new Uint8Array(buffer, offset + headerOffset, 16);
+                var KIDString = String.prototype.concat.apply("0x", Array.prototype.map.call(KIDArrayView, function(value){ return value.toString(16); }));
+                this.KIDs.push(KIDString);
+                headerOffset += 16;
+            }
+        }
+
+        var dataSize = view.getUint32(headerOffset);
+        this.data = new Uint8Array(buffer, offset + headerOffset, dataSize);
+        headerOffset += dataSize;
+
+        return headerOffset;
+    };
+};
+
+Atom.constructorMap['pssh'] = ProtectionSystemBox.bind(null);
+
+class MovieExtendsHeaderBox extends FullBox {
+    constructor(parent) {
+        super(parent);
+        this.description = 'Movie Extends Header Box';
+        this.duration = 0;
+    }
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        this.duration = view.getUint32(headerOffset);
+    }
 }
 
 }
 
+Atom.constructorMap['mehd'] = MovieExtendsHeaderBox.bind(null);
+
+
+class MovieFragmentHeaderBox extends FullBox {
+    constructor(parent) {
+        super(parent);
+        this.description = 'Movie Fragment Header Box';
+        this.sequenceNumber = 0;
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        this.sequenceNumber = view.getUint32(headerOffset);
+        headerOffset += 4;
+    };
+};
+
+Atom.constructorMap['mfhd'] = MovieFragmentHeaderBox.bind(null);
+
+class TrackFragmentHeaderBox extends FullBox {
+    constructor(parent) {
+        super(parent);
+        this.description = 'Track Fragment Header Box';
+        this.baseDataOffsetPresent = false;
+        this.sampleDescriptionIndexPresent = false;
+        this.defaultSampleDurationPresent = false;
+        this.defaultSampleSizePresent = false;
+        this.defaultSampleFlagsPresent = false;
+        this.durationIsEmpty = false;
+        this.defaultBaseIsMoof = false;
+        this.trackID = 0;
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+
+        this.baseDataOffsetPresent         = this.flags & 0x00001 ? true : false;
+        this.sampleDescriptionIndexPresent = this.flags & 0x00002 ? true : false;
+        this.defaultSampleDurationPresent  = this.flags & 0x00008 ? true : false;
+        this.defaultSampleSizePresent      = this.flags & 0x00010 ? true : false;
+        this.defaultSampleFlagsPresent     = this.flags & 0x00020 ? true : false;
+        this.durationIsEmpty               = this.flags & 0x10000 ? true : false;
+        this.defaultBaseIsMoof             = this.flags & 0x20000 ? true : false;
+
+        var view = new DataView(buffer, offset);
+
+        this.trackID = view.getUint32(headerOffset);
+        headerOffset += 4;
+
+        if (this.baseDataOffsetPresent) {
+            var upper = view.getUint32(headerOffset);
+            var lower = view.getUint32(headerOffset + 4);
+            headerOffset += 8;
+
+            this.baseDataOffset = (upper << 32)  + lower;
+        }
+
+        if (this.sampleDescriptionIndexPresent) {
+            this.sampleDescriptionIndex = view.getUint32(headerOffset);
+            headerOffset += 4;
+        }
+
+        if (this.defaultSampleDurationPresent) {
+            this.defaultSampleDuration = view.getUint32(headerOffset);
+            headerOffset += 4;
+        }
+
+        if (this.defaultSampleSizePresent) {
+            this.defaultSampleSize = view.getUint32(headerOffset);
+            headerOffset += 4;
+        }
+
+        if (this.defaultSampleFlagsPresent) {
+            this.defaultSampleFlags = view.getUint32(headerOffset);
+            headerOffset += 4;
+        }
+
+        return headerOffset;
+    };
+};
+
+Atom.constructorMap['tfhd'] = TrackFragmentHeaderBox.bind(null);
+
+class TrackFragmentRunBox extends FullBox {
+    constructor(parent) {
+        super(parent);
+        this.description = 'Track Fragment Run Box';
+        this.dataOffsetPresent = false;
+        this.firstSampleFlagsPresent = false;
+        this.sampleDurationPresent = false;
+        this.sampleSizePresent = false;
+        this.sampleFlagsPresent = false;
+        this.sampleCompositionTimeOffsetsPresent = false;
+        this.dataOffset;
+        this.samples = [];
+        this.duration = 0;
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        this.dataOffsetPresent                   = this.flags & 0x00001 ? true : false;
+        this.firstSampleFlagsPresent             = this.flags & 0x00004 ? true : false;
+        this.sampleDurationPresent               = this.flags & 0x00100 ? true : false;
+        this.sampleSizePresent                   = this.flags & 0x00200 ? true : false;
+        this.sampleFlagsPresent                  = this.flags & 0x00400 ? true : false;
+        this.sampleCompositionTimeOffsetsPresent = this.flags & 0x00800 ? true : false;
+
+        var view = new DataView(buffer, offset);
+
+        var sampleCount = view.getUint32(headerOffset);
+        headerOffset += 4;
+
+        if (this.dataOffsetPresent) {
+            this.dataOffset = view.getUint32(headerOffset);
+            headerOffset += 4;
+        }
+
+        if (this.firstSampleFlagsPresent) {
+            this.firstSampleFlags = view.getUint32(headerOffset);
+            headerOffset += 4;
+        }
+
+        for (var index = 0; index < sampleCount; ++index) {
+            var sample = {}
+            if (this.sampleDurationPresent) {
+                sample.sampleDuration = view.getUint32(headerOffset);
+                this.duration += sample.sampleDuration;
+                headerOffset += 4;
+            }
+
+            if (this.sampleSizePresent) {
+                sample.sampleSize = view.getUint32(headerOffset);
+                headerOffset += 4;
+            }
+
+            if (this.sampleFlagsPresent) {
+                var sampleFlags = view.getUint32(headerOffset);
+                this.sampleFlags = {
+                    isLeading:                 (sampleFlags & 0x0030) >> 4,
+                    sampleDependsOn:           (sampleFlags & 0x00C0) >> 6,
+                    sampleIsDependedOn:        (sampleFlags & 0x0300) >> 8,
+                    sampleHasRedundency:       (sampleFlags & 0x0C00) >> 10,
+                    samplePaddingValue:        (sampleFlags & 0x7000) >> 12,
+                    sampleIsNonSyncSample:     (sampleFlags & 0x8000) >> 15,
+                    sampleDegredationPriority: (sampleFlags & 0xFFFF0000) >> 16,
+                }
+                headerOffset += 4;
+            }
+
+            if (this.sampleCompositionTimeOffsetsPresent) {
+                sample.sampleCompositionTimeOffsets = !this.version ? view.getUint32(headerOffset) : view.getInt32(headerOffset);
+                headerOffset += 4;
+            }
+            this.samples.push(sample);
+        }
+
+        return headerOffset;
+    };
+};
+
+Atom.constructorMap['trun'] = TrackFragmentRunBox.bind(null);
+
+class TrackFragmentBaseMediaDecodeTimeBox extends FullBox {
+    constructor(parent) {
+        super(parent);
+        this.description = "Track Fragment Decode Time";
+        this.baseMediaDecodeTime = 0;
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        if (this.version === 1) {
+            var upper = view.getUint32(headerOffset);
+            var lower = view.getUint32(headerOffset + 4);
+            var sign = 1;
+            if (upper & (1 << 32)) {
+                sign = -1
+                upper = ~upper;
+                lower = ~lower + 1;
+            }
+
+            this.baseMediaDecodeTime = sign * ((upper << 32)  + lower);
+            headerOffset += 8;
+        } else {
+            this.baseMediaDecodeTime = view.getUint32(headerOffset);
+            headerOffset += 4;
+        }
+
+        return headerOffset;
+    };
+};
 
 
+Atom.constructorMap['tfdt'] = TrackFragmentBaseMediaDecodeTimeBox.bind(null);
index 78587ca5ae7d9660dd4cde017e421e910ddadee1..5d1d4dea0ebe90c73a8f3198f025a4040a32e1b5 100644 (file)
             
             function checkForAtom(offset) {
                 reader.onload = function(e) {
             
             function checkForAtom(offset) {
                 reader.onload = function(e) {
-                    var atom = new Atom(e.target.result);
-                    if (atom)
+                    var atom = new Atom();
+                    if (atom) {
+                        atom.parse(e.target.result, 0);
                         readAtom(offset, atom.size);
                         readAtom(offset, atom.size);
+                    }
                 };
                 };
-                var subset = file.slice(offset, offset + 16);
+                var subset = file.slice(offset, offset + 36);
                 reader.readAsArrayBuffer(subset);
             }
 
                 reader.readAsArrayBuffer(subset);
             }
 
@@ -39,7 +41,6 @@
             
             checkForAtom(0);
         }
             
             checkForAtom(0);
         }
-        
         function toDOMRepresentation(object)
         {
             if (object instanceof Atom)
         function toDOMRepresentation(object)
         {
             if (object instanceof Atom)
                 return document.createTextNode('"' + object + '"');
             else if (object instanceof Date)
                 return document.createTextNode(object.toLocaleString());
                 return document.createTextNode('"' + object + '"');
             else if (object instanceof Date)
                 return document.createTextNode(object.toLocaleString());
-            else
+            else if (object instanceof Uint8Array) {
+                return Uint8ArrayNode(object);
+            } else if (object instanceof Object)
+                return toDOMNode(object);
+            else if (object === null)
+                               return document.createTextNode('[null]');
+                       else
                 return document.createTextNode(object.toString());
         }
         
                 return document.createTextNode(object.toString());
         }
         
             }
             return output;
         }
             }
             return output;
         }
+
+        function Uint8ArrayNode(array)
+        {
+            var table = document.createElement('table');
+            var width = 16;
+            for (var offset = 0; offset <= array.length; offset += width) {
+                var tr = document.createElement('tr');
+                table.appendChild(tr);
+
+                var th = document.createElement('th');
+                tr.appendChild(th);
+                th.innerText = ('0000000' + offset.toString(16)).substr(-8) + ':';
+
+                for (var column = 0; column < width && offset + column < array.length; column += 2) {
+                    var td = document.createElement('td');
+                    tr.appendChild(td);
+                    td.innerText = ('00' + array[offset + column].toString(16)).substr(-2);
+                    if (offset + column + 1 < array.length)
+                        td.innerText += ('00' + array[offset + column + 1].toString(16)).substr(-2);
+                }
+            }
+            return table;
+        }
         
         function setup() {
             document.getElementById('file').addEventListener('change', onFileSelect, false);
         
         function setup() {
             document.getElementById('file').addEventListener('change', onFileSelect, false);
         dd dl { margin: 0; vertical-align: top }
         dd span { display: inline-block; vertical-align: top; }
 
         dd dl { margin: 0; vertical-align: top }
         dd span { display: inline-block; vertical-align: top; }
 
-        dt, dd { display: inline-block; min-width: 8em; }
+        dt, dd { display: inline-block; min-width: 14em; }
         dd { margin-left: 1em; }
         dd { margin-left: 1em; }
+        dd th { font-weight: normal; text-align: right; }
         .description { text-align: center; font-weight: bold; }
         
         /* make the output display as a table */
         .description { text-align: center; font-weight: bold; }
         
         /* make the output display as a table */