-var Atom = function(buffer, offset) {
- this.setDefaults();
- return this.parse(buffer, offset) ? this : null;
-};
+class BitReader {
+ constructor(buffer, offset) {
+ this.buffer = buffer;
+ this.bitPos = offset * 8;
+ }
-Atom.create = function(buffer, offset) {
- // 'offset' is optional.
- if (arguments.length < 2)
- offset = 0;
+ readOneBit() {
+ var offset = Math.floor(this.bitPos / 8),
+ shift = 7 - this.bitPos % 8;
+ this.bitPos += 1;
+ return (this.buffer[offset] >> shift) & 1;
+ }
- if (buffer.byteLength - offset < this.minimumSize)
- return null;
+ readBits(n) {
+ var i, value = 0;
+ for (i = 0; i < n; i += 1) {
+ value = value << 1 | this.readOneBit();
+ }
+ return value;
+ }
- var typeArrayView = new Uint8Array(buffer, offset + 4, 4);
- var type = String.fromCharCode.apply(null, typeArrayView);
-
- switch (type) {
- case 'ftyp':
- return new FileTypeAtom(buffer, offset);
- case 'moov':
- case 'trak':
- case 'mdia':
- case 'minf':
- 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);
- default:
- return new Atom(buffer, offset);
+ 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,
});
- Object.defineProperty(this, "minimumSize", {
+ Object.defineProperty(this, "minimumSize", {
value: 8,
writable: true,
enumerable: false,
configurable: true,
});
- Object.defineProperty(this, "parent", {
+ Object.defineProperty(this, "parent", {
value: null,
writable: true,
enumerable: false,
configurable: true,
});
- this.size = 0;
- this.type = '';
-}
+ Object.defineProperty(this, "description", {
+ value: "Undifferentiated Atom",
+ writable: true,
+ enumerable: false,
+ configurable: true,
+ });
+
+ 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;
+ }
-Atom.prototype.parse = function(buffer, offset) {
- // 'offset' is optional.
- if (typeof(offset) == 'undefined')
- offset = 0;
+ var type = this.getType(buffer, offset);
+ var atom;
- if (buffer.byteLength - offset < this.minimumSize)
- return 0;
+ if (typeof(Atom.constructorMap[type]) == 'undefined')
+ atom = new Atom(parent);
+ else
+ atom = new Atom.constructorMap[type](parent);
+ atom.parse(buffer, offset);
+ return atom;
+ };
- var view = new DataView(buffer, offset, 4);
- headerOffset = 0;
+ static getType(buffer, offset) {
+ // 'offset' is optional.
+ if (arguments.length < 2) {
+ offset = 0;
+ }
- this.size = view.getUint32(0);
- headerOffset += 4;
+ if (buffer.byteLength - offset < this.minimumSize)
+ return null;
- var typeArrayView = new Uint8Array(buffer, offset + headerOffset, 4);
- this.type = String.fromCharCode.apply(null, typeArrayView);
- 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;
+ }
+
+ 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)
- return false;
-
- // 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';
+
+ 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;
+ }
+
+ return null;
+ };
+
+ getAtomsByType(type) {
+ if (typeof(this.childAtoms) == 'undefined')
+ return [];
+
+ // 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.constructorMap = { };
+
+class FileTypeAtom extends Atom {
+ constructor(parent) {
+ super(parent);
+
+ this.description = "File Type Atom";
+ this.minimumSize = 16;
+ this.brand = "";
+ this.version = 0;
+ this.compatible_brands = [];
}
+ 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;
+
return headerOffset;
+}
};
-Atom.prototype.getAtomByType = function(type) {
- if (typeof(this.childAtoms) == 'undefined')
- return null;
+class AudioSampleEntry extends SampleEntry {
+ constructor(parent) {
+ super(parent);
+ this.description = 'Audio Sample Entry';
+ this.channelCount = 0;
+ this.sampleSize = 0;
+ this.sampleRate = 0;
+ };
- // Bredth first
- for (index in this.childAtoms) {
- if (this.childAtoms[index].type == type)
- return this.childAtoms[index];
- }
+ parse(buffer, offset) {
+ var headerOffset = super.parse(buffer, offset);
+ var view = new DataView(buffer, offset);
- var result = null;
- for (index in this.childAtoms) {
- if (result = this.childAtoms[index].getAtomsByType(type))
- break;
- }
- return result;
+ // unsigned int(32)[2] reserved = 0
+ headerOffset += 8;
+
+ this.channelCount = view.getUint16(headerOffset);
+ headerOffset += 2;
+
+ this.sampleSize = view.getUint16(headerOffset);
+ headerOffset += 2;
+
+ // unsigned int(16) pre_defined = 0
+ // const unsigned int(16) reserved = 0
+ headerOffset += 4;
+
+ this.sampleRate = (view.getUint32(headerOffset) >> 16) & 0xFFFF;
+ headerOffset += 4;
+
+ 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'
+ }
-Atom.prototype.getAtomsByType = function(type) {
- if (typeof(this.childAtoms) == 'undefined')
- return [];
+ parse(buffer, offset) {
+ var headerOffset = super.parse(buffer, offset);
- var result = [];
+ this.descriptor = new ESDescriptor(this);
+ headerOffset += this.descriptor.parse(buffer, offset + headerOffset);
- // Bredth first
- for (index in this.childAtoms) {
- if (this.childAtoms[index].type == type)
- result.push(this.childAtoms[index]);
+ 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,
+ });
- for (index in this.childAtoms)
- result = result.concat(this.childAtoms[index].getAtomsByType(type));
+ 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;
+ };
+};
- return result;
+BaseDescriptor.TagMap = {
+ 3: { name: 'ES_DescrTag' },
+ 4: { name: 'DecoderConfigDescrTag' },
+ 5: { name: 'DecSpecificInfoTag' },
};
-var FileTypeAtom = function(buffer, offset) {
- Object.getPrototypeOf(FileTypeAtom.prototype).constructor.call(this, buffer, offset);
-}
+class ESDescriptor extends BaseDescriptor {
+ constructor(parent) {
+ super(parent);
+ this.description = "ES Descriptor"
+ this.ES_ID = 0;
+ };
-FileTypeAtom.prototype = Object.create(Atom.prototype);
+ parse(buffer, offset) {
+ var headerOffset = super.parse(buffer, offset);;
+ var view = new DataView(buffer, offset);
-FileTypeAtom.prototype.setDefaults = function() {
- Object.getPrototypeOf(FileTypeAtom.prototype).setDefaults.call(this);
- this.minimumSize = 16;
- this.brand = "";
- this.version = 0;
- this.compatible_brands = [];
-}
+ this.ES_ID = view.getUint16(headerOffset);
+ headerOffset += 2;
-FileTypeAtom.prototype.parse = function(buffer, offset) {
- var headerOffset = Object.getPrototypeOf(FileTypeAtom.prototype).parse.call(this, buffer, offset);
- if (!headerOffset)
- return 0;
+ var nextByte = view.getUint8(headerOffset);
+ headerOffset += 1;
- var view = new DataView(buffer, offset, this.size);
+ this.streamDependencyFlag = nextByte & (1 << 7);
+ this.urlFlag = nextByte & (1 << 6);
+ this.ocrStreamFlag = nextByte & (1 << 5);
+ this.streamPriority = nextByte & 0x1f;
- var brandArrayView = new Uint8Array(buffer, offset + headerOffset, 4);
- this.brand = String.fromCharCode.apply(null, brandArrayView);
- headerOffset += 4;
+ if (this.streamDependencyFlag) {
+ this.dependsOn_ES_Number = view.getUint16(headerOffset);
+ headerOffset += 2;
+ }
- this.version = view.getUint32(headerOffset);
- headerOffset += 4;
+ if (this.urlFlag) {
+ var urlLength = view.getUint8(headerOffset);
+ headerOffset += 1;
- while (headerOffset < this.size - 4) {
- var brandArrayView = new Uint8Array(buffer, offset + headerOffset, 4);
- this.compatible_brands.push(String.fromCharCode.apply(null, brandArrayView));
+ var array = new Uint8Array(buffer, offset + headerOffset, urlLength);
+ headerOffset += urlLength;
+ this.url = String.fromCharCode.apply(null, array);
+ }
+
+ 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;
+
+ 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;
}
+};
- return true;
-}
+class DecoderSpecificInfo extends BaseDescriptor {
+ constructor(parent) {
+ // 'Audio ISO/IEC 14496-3' && 'AudioStreamType'
+ if (parent.objectTypeIndication == 0x40 && parent.streamType == 0x5)
+ return new AudioSpecificConfig(parent);
-var ContainerAtom = function(buffer, offset) {
- Object.getPrototypeOf(ContainerAtom.prototype).constructor.call(this, buffer, offset);
+ super(parent);
+ this.description = 'Decoder Specific Info';
+ }
}
-ContainerAtom.prototype = Object.create(Atom.prototype);
+class AudioSpecificConfig extends BaseDescriptor {
+ constructor(parent) {
+ super(parent);
+ this.audioObjectType = 0;
+ this.samplingFrequencyIndex = 0;
+ this.channelConfiguration = 0;
+ }
-ContainerAtom.prototype.setDefaults = function() {
- Object.getPrototypeOf(ContainerAtom.prototype).setDefaults.call(this);
- this.childAtoms = [];
-}
+ parse(buffer, offset) {
+ var headerOffset = super.parse(buffer, offset);
+ if (this.size < headerOffset)
+ return;
-ContainerAtom.prototype.parse = function(buffer, offset) {
- var headerOffset = Object.getPrototypeOf(ContainerAtom.prototype).parse.call(this, buffer, offset);
- if (!headerOffset)
- return 0;
-
- while (headerOffset < this.size) {
- var childAtom = Atom.create(buffer, offset + headerOffset);
- if (!childAtom)
- break;
- headerOffset += childAtom.size;
- this.childAtoms.push(childAtom);
- childAtom.parent = this;
+ 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);
+
+ this.samplingFrequencyIndex = bitReader.readBits(4);
+ if (this.samplingFrequencyIndex === 0xf)
+ this.samplingFrequencyIndex += bitReader.readBits(24);
+
+ return headerOffset;
}
}
-var VersionFlagsAtom = function(buffer, offset) {
- Object.getPrototypeOf(VersionFlagsAtom.prototype).constructor.call(this, 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;
-VersionFlagsAtom.prototype = Object.create(Atom.prototype);
+ this.verticalResolution = view.getUint32(headerOffset) / (1 << 16);
+ headerOffset += 4;
-VersionFlagsAtom.prototype.setDefaults = function() {
- Object.getPrototypeOf(VersionFlagsAtom.prototype).setDefaults.call(this);
- this.version = 0;
- this.flags = 0;
-}
+ // const unsigned int(32) reserved = 0
+ headerOffset += 4;
-VersionFlagsAtom.prototype.parse = function(buffer, offset) {
- var headerOffset = Object.getPrototypeOf(VersionFlagsAtom.prototype).parse.call(this, buffer, offset);
- if (!headerOffset)
- return 0;
+ this.frameCount = view.getUint16(headerOffset);
+ headerOffset += 2;
- var view = new DataView(buffer, offset);
+ var array = new Uint8Array(buffer, offset + headerOffset, 32);
+ this.compressorName = String.fromCharCode.apply(null, array);
+ headerOffset += 32;
- this.version = view.getUint8(headerOffset);
- headerOffset += 1;
+ this.depth = view.getUint16(headerOffset);
+ headerOffset += 2;
- // '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;
+ // int(16) pre_defined = -1;
+ headerOffset += 2;
- return headerOffset;
-}
+ 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;
+ };
+};
-var MovieHeaderAtom = function(buffer, offset) {
- return Object.getPrototypeOf(MovieHeaderAtom.prototype).constructor.call(this, buffer, offset);
-}
+class AVCConfigurationBox extends Atom {
+ constructor(parent) {
+ super(parent);
-MovieHeaderAtom.prototype = Object.create(VersionFlagsAtom.prototype);
-
-MovieHeaderAtom.prototype.setDefaults = function() {
- Object.getPrototypeOf(MovieHeaderAtom.prototype).setDefaults.call(this);
- 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;
-}
+ this.description = 'AVC Configuration Box';
+ this.configurationVersion = 0;
+ this.AVCProfileIndication = 0;
+ this.profileCompatibility = 0;
+ this.AVCLevelIndication = 0;
+ this.sequenceParameterSets = [];
+ this.pictureParameterSets = [];
+ };
-MovieHeaderAtom.prototype.parse = function(buffer, offset) {
- var headerOffset = Object.getPrototypeOf(MovieHeaderAtom.prototype).parse.call(this, buffer, offset);
- if (!headerOffset)
- return 0;
+ parse(buffer, offset)
+ {
+ var headerOffset = super.parse(buffer, offset);
+ var view = new DataView(buffer, offset);
- var view = new DataView(buffer, offset);
+ this.configurationVersion = view.getUint8(headerOffset);
+ ++headerOffset;
- this.creationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
- headerOffset += 4;
+ this.AVCProfileIndication = view.getUint8(headerOffset);
+ ++headerOffset;
- this.modificationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
- headerOffset += 4;
+ this.profileCompatibility = view.getUint8(headerOffset);
+ ++headerOffset;
- this.timeScale = view.getUint32(headerOffset);
- headerOffset += 4;
+ this.AVCLevelIndication = view.getUint8(headerOffset);
+ ++headerOffset;
- this.duration = view.getUint32(headerOffset);
- headerOffset += 4;
+ var lengthSizeMinusOne = view.getUint8(headerOffset) & 0x3;
+ ++headerOffset;
- this.preferredRate = view.getUint32(headerOffset) / (1 << 16);
- headerOffset += 4;
+ var numOfSequenceParameterSets = view.getUint8(headerOffset) & 0x1f;
+ ++headerOffset;
- this.preferredVolume = view.getUint16(headerOffset) / (1 << 8);
- headerOffset += 2;
+ for (var index = 0; index < numOfSequenceParameterSets; ++index) {
+ var sequenceParameterSetLength = 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.sequenceParameterSets.push(new Uint8Array(buffer, offset + headerOffset, sequenceParameterSetLength));
+ headerOffset += sequenceParameterSetLength;
+ }
-var TrackHeaderAtom = function(buffer, offset) {
- Object.getPrototypeOf(TrackHeaderAtom.prototype).constructor.call(this, buffer, offset);
-}
+ var numOfPictureParameterSets = view.getUint8(headerOffset) & 0x1f;
+ ++headerOffset;
-TrackHeaderAtom.prototype = Object.create(Atom.prototype);
-
-TrackHeaderAtom.prototype.setDefaults = function() {
- Object.getPrototypeOf(TrackHeaderAtom.prototype).setDefaults.call(this);
-
- 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;
-}
+ for (index = 0; index < numOfPictureParameterSets; ++index) {
+ var pictureParameterSetLength = view.getUint16(headerOffset);
+ headerOffset += 2;
-TrackHeaderAtom.prototype.parse = function(buffer, offset) {
- var headerOffset = Object.getPrototypeOf(TrackHeaderAtom.prototype).parse.call(this, buffer, offset);
- if (!headerOffset)
- return 0;
+ this.pictureParameterSets.push(new Uint8Array(buffer, offset + headerOffset, pictureParameterSetLength));
+ headerOffset += pictureParameterSetLength;
+ }
- var view = new DataView(buffer, offset);
+ if ([100, 110, 122, 144].indexOf(this.AVCProfileIndication) >= 0) {
- this.creationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
- headerOffset += 4;
+ // bit(6) reserved = '111111'b
+ this.chromaFormat = view.getUint8(headerOffset) & 0x3;
+ ++headerOffset;
- this.modificationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
- headerOffset += 4;
+ // bit(6) reserved = '111111'b
+ this.bitDepthLumaMinus8 = view.getUint8(headerOffset) & 0x3;
+ ++headerOffset;
- this.trackID = view.getUint32(headerOffset);
- headerOffset += 4;
+ // bit(5) reserved = '11111'b;

+ this.bitDepthChromaMinus8 = view.getUint8(headerOffset) & 0x7;
+ ++headerOffset;
+
+ if (headerOffset >= this.size)
+ return headerOffset;
- // Reserved
- // A 32-bit integer that is reserved for use by Apple. Set this field to 0.
- headerOffset += 4;
+ var numOfSequenceParameterSetExt = view.getUint8(headerOffset);
+ this.sequenceParameterSets = [];
+ ++headerOffset;
- this.duration = view.getUint32(headerOffset);
- headerOffset += 4;
+ for (index = 0; index < numOfSequenceParameterSetExt; ++index) {
+ var sequenceParameterSetLength = view.getUint16(headerOffset);
+ headerOffset += 2;
- // Reserved
- // An 8-byte value that is reserved for use by Apple. Set this field to 0.
- headerOffset += 8;
+ this.sequenceParameterSets.push(new Uint8Array(buffer, offset + headerOffset, sequenceParameterSetLength));
+ headerOffset += sequenceParameterSetLength;
+ }
+ }
- this.layer = view.getUint16(headerOffset);
- headerOffset += 2;
+ return headerOffset;
+ };
+};
- this.alternateGroup = view.getUint16(headerOffset);
- headerOffset += 2;
+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.volume = view.getUint16(headerOffset) / (1 << 8);
- headerOffset += 2;
+ this.general_constraint_indicator_flags = view.getUint16(headerOffset) << 32
+ + view.getUint32(headerOffset + 2);
+ headerOffset += 6;
- // Reserved
- // A 16-bit integer that is reserved for use by Apple. Set this field to 0.
- headerOffset += 2;
+ this.general_level_idc = view.getUint8(headerOffset);
+ ++headerOffset;
- 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;
-}
+ // bit(4) reserved = ‘1111’b;
+ this.min_spatial_segmentation_idc = view.getUint16(headerOffset) & 0x0FFF;
+ headerOffset += 2;
-var MediaHeaderAtom = function(buffer, offset) {
- Object.getPrototypeOf(MediaHeaderAtom.prototype).constructor.call(this, buffer, offset);
-}
+ // bit(6) reserved = ‘111111’b;
+ this.parallelismType = view.getUint8(headerOffset) & 0x03;
+ ++headerOffset;
-MediaHeaderAtom.prototype = Object.create(VersionFlagsAtom.prototype);
+ // bit(6) reserved = ‘111111’b;
+ this.chromaFormat = view.getUint8(headerOffset) & 0x03;
+ ++headerOffset;
-MediaHeaderAtom.prototype.setDefaults = function() {
- Object.getPrototypeOf(MediaHeaderAtom.prototype).setDefaults.call(this);
+ // bit(5) reserved = ‘11111’b;
+ this.bitDepthLumaMinus8 = view.getUint8(headerOffset) & 0x07;
+ ++headerOffset;
- this.creationTime = 0;
- this.modificationTime = 0;
- this.timeScale = 0;
- this.duration = 0;
- this.language = 0;
- this.quality = 0;
-}
+ // bit(5) reserved = ‘11111’b;
+ this.bitDepthChromaMinus8 = view.getUint8(headerOffset) & 0x07;
+ ++headerOffset;
-MediaHeaderAtom.prototype.parse = function(buffer, offset) {
- var headerOffset = Object.getPrototypeOf(MediaHeaderAtom.prototype).parse.call(this, buffer, offset);
- if (!headerOffset)
- return 0;
+ this.avgFrameRate = view.getUint16(headerOffset);
+ headerOffset += 2;
- var view = new DataView(buffer, offset);
+ byte = view.getUint8(headerOffset);
+ ++headerOffset;
- this.creationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
- headerOffset += 4;
+ this.constantFrameRate = (byte & 0xC0) >> 6;
+ this.numTemporalLayers = (byte & 0x38) >> 3;
+ this.temporalIdNested = (byte & 0x04) >> 2;
+ this.lengthSizeMinusOne = byte & 0x02;
- this.modificationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
- headerOffset += 4;
+ return this.size;
+ };
+};
- this.timeScale = view.getUint32(headerOffset);
- headerOffset += 4;
+Atom.constructorMap['hvcC'] = HEVCConfigurationBox.bind(null);
- this.duration = view.getUint32(headerOffset);
- headerOffset += 4;
+class CleanApertureBox extends Atom {
+ constructor(parent) {
+ super(parent);
- this.language = view.getUint16(headerOffset);
- headerOffset += 2;
+ 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;
+ };
- this.quality = view.getUint16(headerOffset);
- headerOffset += 2;
-}
+ parse(buffer, offset) {
+ var headerOffset = super.parse(buffer, offset);
+ var view = new DataView(buffer, offset);
-var SyncSampleAtom = function(buffer, offset) {
- Object.getPrototypeOf(SyncSampleAtom.prototype).constructor.call(this, 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;
+
+ return headerOffset;
+ };
+};
-SyncSampleAtom.prototype = Object.create(Atom.prototype);
+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;
+ }
-SyncSampleAtom.prototype.setDefaults = function() {
- Object.getPrototypeOf(SyncSampleAtom.prototype).setDefaults.call(this);
+ parse(buffer, offset) {
+ var headerOffset = super.parse(buffer, offset);
+ var view = new DataView(buffer, offset);
- this.version = 0;
- this.flags = 0;
- this.entries = 0;
- this.syncSamples = [];
-}
+ this.trackID = view.getUint32(headerOffset);
+ headerOffset += 4;
+
+ this.default_sample_description_index = view.getUint32(headerOffset);
+ headerOffset += 4;
+
+ this.default_sample_duration = view.getUint32(headerOffset);
+ headerOffset += 4;
+
+ 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;
+ };
+};
-SyncSampleAtom.prototype.parse = function(buffer, offset) {
- var headerOffset = Object.getPrototypeOf(SyncSampleAtom.prototype).parse.call(this, buffer, offset);
- if (!headerOffset)
- return 0;
+Atom.constructorMap['tenc'] = TrackEncryptionBox.bind(null);
- var view = new DataView(buffer, offset);
+class SegmentIndexBox extends FullBox {
+ constructor(parent) {
+ super(parent);
- this.version = view.getUint8(headerOffset);
- headerOffset += 1;
+ this.description = "Segment Index Box";
+ this.referenceID = 0;
+ this.timeScale = 0;
+ this.earliestPresentationTime = 0;
+ this.firstOffset = 0;
+ this.references = [];
+ };
- // '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.referenceID = view.getUint32(headerOffset);
+ headerOffset += 4;
- while (headerOffset < this.size) {
- var sampleNumber = view.getUint32(headerOffset);
+ this.timeScale = view.getUint32(headerOffset);
headerOffset += 4;
- this.syncSamples.push(sampleNumber);
+
+ if (this.version == 1) {
+ var upper = view.getUint32(headerOffset);
+ headerOffset += 4;
+ var lower = view.getUint32(headerOffset);
+ headerOffset += 4;
+
+ this.earliestPresentationTime = (upper << 32) + lower;
+
+ 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;
}
-}
-var TimeToSampleAtom = function(buffer, offset) {
- Object.getPrototypeOf(TimeToSampleAtom.prototype).constructor.call(this, buffer, offset);
+ parse(buffer, offset) {
+ var headerOffset = super.parse(buffer, offset);
+ var view = new DataView(buffer, offset);
+
+ this.duration = view.getUint32(headerOffset);
+ }
}
-TimeToSampleAtom.prototype = Object.create(VersionFlagsAtom.prototype);
+Atom.constructorMap['mehd'] = MovieExtendsHeaderBox.bind(null);
-TimeToSampleAtom.prototype.setDefaults = function() {
- Object.getPrototypeOf(TimeToSampleAtom.prototype).setDefaults.call(this);
- this.entries = 0;
-
- Object.defineProperty(this, "timeToSamples", {
- value: [],
- writable: true,
- enumerable: false,
- configurable: true,
- });
-}
+class MovieFragmentHeaderBox extends FullBox {
+ constructor(parent) {
+ super(parent);
+ this.description = 'Movie Fragment Header Box';
+ this.sequenceNumber = 0;
+ };
-TimeToSampleAtom.prototype.parse = function(buffer, offset) {
- var headerOffset = Object.getPrototypeOf(TimeToSampleAtom.prototype).parse.call(this, buffer, offset);
- if (!headerOffset)
- return 0;
+ parse(buffer, offset) {
+ var headerOffset = super.parse(buffer, offset);
+ var view = new DataView(buffer, offset);
- var view = new DataView(buffer, offset);
+ this.sequenceNumber = view.getUint32(headerOffset);
+ headerOffset += 4;
+ };
+};
- this.entries = view.getUint32(headerOffset);
+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;
- while (headerOffset < this.size) {
+ 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;
- var sampleDuration = view.getUint32(headerOffset);
+ 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);
+
+
+class ColorBox extends Atom {
+ constructor(parent) {
+ super(parent);
+ this.description = "Color";
+ };
+
+ parse(buffer, offset) {
+ var headerOffset = super.parse(buffer, offset);
+ var view = new DataView(buffer, offset);
+
+ var typeArrayView = new Uint8Array(buffer, offset + headerOffset, 4);
+ this.colorType = String.fromCharCode.apply(null, typeArrayView);
headerOffset += 4;
- this.timeToSamples.push([sampleCount, sampleDuration]);
+ if (this.colorType == 'nclx') {
+ this.colorPrimaries = view.getUint16(headerOffset);
+ headerOffset += 2;
+
+ this.transferCharacteristics = view.getUint16(headerOffset);
+ headerOffset += 2;
+
+ this.matrixCoefficients = view.getUint16(headerOffset);
+ headerOffset += 2;
+
+ this.fullRangeFlag = view.getUint8(headerOffset) & 0xF
+ headerOffset++;
+ }
}
-}
\ No newline at end of file
+}
+
+Atom.constructorMap['colr'] = ColorBox.bind(null);
\ No newline at end of file