+ parse(buffer, offset) {
+ var headerOffset = super.parse(buffer, offset);
+ var view = new DataView(buffer, offset, this.size);
+
+ 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;
+ };
+};
+
+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;
+
+ return headerOffset;
+ };
+};
+
+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;
+ };
+};
+
+Atom.constructorMap['stss'] = SyncSampleAtom.bind(null);
+
+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,
+ });
+ };
+
+ 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;
+ };
+};
+
+Atom.constructorMap['stts'] = TimeToSampleAtom.bind(null);
+
+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,
+ });
+ };
+
+ parse(buffer, offset) {
+ var headerOffset = super.parse(buffer, offset);
+ var view = new DataView(buffer, offset);
+
+ this.sampleSize = view.getUint32(headerOffset);
+ headerOffset += 4;
+
+ this.entries = view.getUint32(headerOffset);
+ headerOffset += 4;
+
+ this.sampleSizes = new Uint32Array(this.entries);
+ var i = 0;
+
+ while (headerOffset < this.size) {
+ this.sampleSizes[i] = view.getUint32(headerOffset);
+ headerOffset += 4;
+ ++i;
+ }
+
+ return headerOffset;
+ };
+};
+
+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;
+ };
+};
+
+Atom.constructorMap['stsd'] = SampleDescriptionBox.bind(null);
+
+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;
+