+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;
+ };
+};
+
+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;
+ };
+};
+
+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;
+
+ this.modificationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
+ headerOffset += 4;
+
+ this.timeScale = view.getUint32(headerOffset);
+ headerOffset += 4;
+
+ this.duration = view.getUint32(headerOffset);
+ headerOffset += 4;
+
+ this.preferredRate = view.getUint32(headerOffset) / (1 << 16);
+ headerOffset += 4;
+
+ this.preferredVolume = view.getUint16(headerOffset) / (1 << 8);
+ 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 headerOffset;
+ };
+};
+
+Atom.constructorMap['mvhd'] = MovieHeaderAtom.bind(null);
+
+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, mediaRateInteger, mediaRateFraction]);
+ }
+
+ return headerOffset;
+ };
+};
+
+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.modificationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
+ headerOffset += 4;
+
+ this.trackID = 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;
+
+ // Reserved
+ // An 8-byte value that is reserved for use by Apple. Set this field to 0.
+ headerOffset += 8;
+
+ this.layer = view.getUint16(headerOffset);
+ headerOffset += 2;
+
+ this.alternateGroup = view.getUint16(headerOffset);
+ headerOffset += 2;
+
+ this.volume = view.getUint16(headerOffset) / (1 << 8);
+ headerOffset += 2;
+
+ // Reserved
+ // A 16-bit integer that is reserved for use by Apple. Set this field to 0.
+ headerOffset += 2;
+
+ 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;
+
+ return headerOffset;
+ };
+};
+
+Atom.constructorMap['tkhd'] = TrackHeaderAtom.bind(null);
+
+class MediaHeaderAtom extends FullBox {
+ constructor(parent) {
+ super(parent);
+
+ this.description = "Media Header Atom";
+ this.creationTime = 0;
+ this.modificationTime = 0;
+ this.timeScale = 0;
+ this.duration = 0;
+ this.language = 0;
+ this.quality = 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.modificationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
+ headerOffset += 4;
+
+ this.timeScale = view.getUint32(headerOffset);
+ headerOffset += 4;
+
+ this.duration = view.getUint32(headerOffset);
+ headerOffset += 4;
+
+ this.language = view.getUint16(headerOffset);
+ headerOffset += 2;
+
+ this.quality = view.getUint16(headerOffset);
+ headerOffset += 2;
+
+ return headerOffset;
+ };
+};
+
+Atom.constructorMap['mdhd'] = MediaHeaderAtom.bind(null);
+
+class HandlerReferenceBox extends FullBox {
+ constructor(parent) {
+ super(parent);
+
+ this.description = 'Handler Reference Box';
+ this.handlerType = '';
+ this.name = '';
+ };
+
+ parse(buffer, offset) {
+ var headerOffset = super.parse(buffer, offset);
+ var view = new DataView(buffer, offset);
+
+ // unsigned int(32) predefined = 0;
+ headerOffset += 4;
+
+ 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;