mse-audio-wrapper
Advanced tools
Comparing version 1.0.0 to 1.0.1
{ | ||
"name": "mse-audio-wrapper", | ||
"version": "1.0.0", | ||
"version": "1.0.1", | ||
"description": "Library to enable Media Source Extensions API playback for unsupported audio containers and raw codecs", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -40,2 +40,6 @@ /* Copyright 2020-2021 Ethan Halsall | ||
get bitDepth() { | ||
return this._bitDepth; | ||
} | ||
get channels() { | ||
@@ -42,0 +46,0 @@ return this._channels; |
@@ -181,2 +181,3 @@ /* Copyright 2020-2021 Ethan Halsall | ||
this._coupledStreamCount = header.coupledStreamCount; | ||
this._bitDepth = header.bitDepth; | ||
this._bytes = header.bytes; | ||
@@ -183,0 +184,0 @@ this._inputSampleRate = header.inputSampleRate; |
@@ -131,2 +131,3 @@ /* Copyright 2020-2021 Ethan Halsall | ||
this._bitrateMinimum = header.bitrateMinimum; | ||
this._bitDepth = header.bitDepth; | ||
this._blocksize0 = header.blocksize0; | ||
@@ -133,0 +134,0 @@ this._blocksize1 = header.blocksize1; |
@@ -22,13 +22,11 @@ /* Copyright 2020-2021 Ethan Halsall | ||
* @abstract | ||
* @description ISO Base Media File Format Object structure Abstract Class | ||
* @description Container Object structure Abstract Class | ||
* @param {any} name Name of the object | ||
* @param {Array<Uint8>} [contents] Array of bytes to insert into this box | ||
* @param {Array<Uint8>} [contents] Array of arrays or typed arrays, or a single number or typed array | ||
* @param {Array<ContainerElement>} [objects] Array of objects to insert into this object | ||
*/ | ||
constructor(name, contents, objects) { | ||
constructor({ name, contents = [], children = [] }) { | ||
this._name = name; | ||
this._contents = contents; | ||
this._objects = objects; | ||
this.MIN_SIZE = 0; | ||
this._children = children; | ||
} | ||
@@ -100,41 +98,66 @@ | ||
get contents() { | ||
return this._contents.concat( | ||
this._objects.reduce((acc, obj) => acc.concat(obj.contents), []) | ||
); | ||
static *flatten(array) { | ||
for (const item of array) { | ||
if (Array.isArray(item)) { | ||
yield* ContainerElement.flatten(item); | ||
} else { | ||
yield item; | ||
} | ||
} | ||
} | ||
/** | ||
* @returns {number} Total length of this object and all contents | ||
* @returns {Uint8Array} Contents of this container element | ||
*/ | ||
get length() { | ||
return this._objects.reduce( | ||
(acc, obj) => acc + obj.length, | ||
this._contents.length + this.MIN_SIZE | ||
); | ||
get contents() { | ||
const buffer = new Uint8Array(this.length); | ||
const contents = this._buildContents(); | ||
let offset = 0; | ||
for (const element of ContainerElement.flatten(contents)) { | ||
if (typeof element !== "object") { | ||
buffer[offset] = element; | ||
offset++; | ||
} else { | ||
buffer.set(element, offset); | ||
offset += element.length; | ||
} | ||
} | ||
return buffer; | ||
} | ||
/** | ||
* @description Inserts bytes into the contents of this object | ||
* @param {Array<Uint>} data Bytes to insert | ||
* @param {number} index Position to insert bytes | ||
* @returns {number} Length of this container element | ||
*/ | ||
insertBytes(data, index) { | ||
this._contents = this._contents | ||
.slice(0, index) | ||
.concat(data) | ||
.concat(this._contents.slice(index)); | ||
get length() { | ||
return this._buildLength(); | ||
} | ||
/** | ||
* @description Appends data to the end of the contents of this box | ||
* @param {Array<Uint>} data Bytes to append | ||
*/ | ||
appendBytes(data) { | ||
this._contents = this._contents.concat(data); | ||
_buildContents() { | ||
return [ | ||
this._contents, | ||
...this._children.map((obj) => obj._buildContents()), | ||
]; | ||
} | ||
_buildLength() { | ||
let length; | ||
if (Array.isArray(this._contents)) { | ||
length = this._contents.reduce( | ||
(acc, val) => acc + (val.length === undefined ? 1 : val.length), | ||
0 | ||
); | ||
} else { | ||
length = this._contents.length === undefined ? 1 : this._contents.length; | ||
} | ||
return length + this._children.reduce((acc, obj) => acc + obj.length, 0); | ||
} | ||
addChild(object) { | ||
this._objects.push(object); | ||
this._children.push(object); | ||
} | ||
} |
@@ -29,18 +29,23 @@ /* Copyright 2020-2021 Ethan Halsall | ||
*/ | ||
constructor(name, { contents = [], children = [] } = {}) { | ||
super(name, contents, children); | ||
constructor(name, { contents, children } = {}) { | ||
super({ name, contents, children }); | ||
} | ||
this.MIN_SIZE = 4 + name.length; | ||
_buildContents() { | ||
return [ | ||
...this._lengthBytes, | ||
...ContainerElement.stringToByteArray(this._name), | ||
...super._buildContents(), | ||
]; | ||
} | ||
/** | ||
* @returns {Array<Uint8>} Contents of this box | ||
*/ | ||
get contents() { | ||
const contents = super.contents; | ||
_buildLength() { | ||
if (!this._length) { | ||
// length bytes + name length + content length | ||
this._length = 4 + this._name.length + super._buildLength(); | ||
this._lengthBytes = ContainerElement.getUint32(this._length); | ||
} | ||
return [...ContainerElement.getUint32(this.MIN_SIZE + contents.length)] | ||
.concat(ContainerElement.stringToByteArray(this._name)) | ||
.concat(contents); | ||
return this._length; | ||
} | ||
} |
@@ -22,9 +22,8 @@ /* Copyright 2020-2021 Ethan Halsall | ||
export default class ESTag extends ContainerElement { | ||
constructor(tagNumber, { contents = [], tags = [] } = {}) { | ||
super(tagNumber, contents, tags); | ||
this.MIN_SIZE = 5; | ||
constructor(tagNumber, { contents, tags } = {}) { | ||
super({ name: tagNumber, contents, children: tags }); | ||
} | ||
static getLength(length) { | ||
let bytes = ContainerElement.getUint32(length); | ||
const bytes = ContainerElement.getUint32(length); | ||
@@ -45,10 +44,14 @@ bytes.every((byte, i, array) => { | ||
*/ | ||
get contents() { | ||
const contents = super.contents; | ||
_buildContents() { | ||
return [this._name, ...this._lengthBytes, ...super._buildContents()]; | ||
} | ||
/* prettier-ignore */ | ||
return [ | ||
this._name, | ||
...ESTag.getLength(contents.length), | ||
].concat(contents); | ||
_buildLength() { | ||
if (!this._length) { | ||
const length = super._buildLength(); | ||
this._lengthBytes = ESTag.getLength(length); | ||
this._length = 1 + length + this._lengthBytes.length; | ||
} | ||
return this._length; | ||
} | ||
@@ -55,0 +58,0 @@ |
@@ -19,3 +19,3 @@ /* Copyright 2020-2021 Ethan Halsall | ||
import { concatBuffers } from "../../utilities"; | ||
import ContainerElement from "../ContainerElement"; | ||
import Box from "./Box"; | ||
@@ -67,3 +67,3 @@ import ESTag from "./ESTag"; | ||
0x00,0x00, // reserved | ||
...Box.getUint16(header.sampleRate),0x00,0x00, // sample rate 16.16 fixed-point | ||
Box.getUint16(header.sampleRate),0x00,0x00, // sample rate 16.16 fixed-point | ||
], | ||
@@ -75,10 +75,10 @@ children: [ | ||
header.channels, // output channel count | ||
...Box.getUint16(header.preSkip), // pre skip | ||
...Box.getUint32(header.inputSampleRate),// input sample rate | ||
...Box.getInt16(header.outputGain), // output gain | ||
Box.getUint16(header.preSkip), // pre skip | ||
Box.getUint32(header.inputSampleRate),// input sample rate | ||
Box.getInt16(header.outputGain), // output gain | ||
header.channelMappingFamily, // channel mapping family int(8) | ||
...(header.channelMappingFamily !== 0 ? [ | ||
(header.channelMappingFamily !== 0 ? [ | ||
header.streamCount, | ||
header.coupledStreamCount, | ||
...header.channelMappingTable // channel mapping table | ||
header.channelMappingTable // channel mapping table | ||
] : []) | ||
@@ -103,3 +103,3 @@ ], | ||
0x00,0x00, // reserved | ||
...Box.getUint16(header.sampleRate),0x00,0x00, // sample rate 16.16 fixed-point | ||
Box.getUint16(header.sampleRate),0x00,0x00, // sample rate 16.16 fixed-point | ||
/* | ||
@@ -126,7 +126,7 @@ When the bitstream's native sample rate is greater | ||
0x00,0x00,0x22, // Length | ||
...Box.getUint16(header.blockSize), // maximum block size | ||
...Box.getUint16(header.blockSize), // minimum block size | ||
Box.getUint16(header.blockSize), // maximum block size | ||
Box.getUint16(header.blockSize), // minimum block size | ||
0x00,0x00,0x00, // maximum frame size | ||
0x00,0x00,0x00, // minimum frame size | ||
...Box.getUint32((header.sampleRate << 12) | (header.channels << 8) | ((header.bitDepth - 1) << 4)), // 20bits sample rate, 3bits channels, 5bits bitDepth - 1 | ||
Box.getUint32((header.sampleRate << 12) | (header.channels << 8) | ((header.bitDepth - 1) << 4)), // 20bits sample rate, 3bits channels, 5bits bitDepth - 1 | ||
0x00,0x00,0x00,0x00, // total samples | ||
@@ -156,3 +156,3 @@ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 // md5 of stream | ||
new ESTag(5, { | ||
contents: [...header.audioSpecificConfig], | ||
contents: header.audioSpecificConfig, | ||
}) | ||
@@ -171,3 +171,3 @@ ); | ||
0x00,0x00, // Packet size | ||
...Box.getUint16(header.sampleRate),0x00,0x00], // sample rate unsigned floating point | ||
Box.getUint16(header.sampleRate),0x00,0x00], // sample rate unsigned floating point | ||
children: [ | ||
@@ -186,3 +186,3 @@ new Box("esds", { | ||
new ESTag(6, { | ||
contents: [0x02], | ||
contents: 0x02, | ||
}), | ||
@@ -202,140 +202,138 @@ ], | ||
getInitializationSegment(header) { | ||
const sampleRate = Box.getUint32(header.sampleRate); | ||
const boxes = [ | ||
new Box("ftyp", { | ||
/* prettier-ignore */ | ||
contents: [...Box.stringToByteArray("iso5"), // major brand | ||
0x00,0x00,0x02,0x00, // minor version | ||
...Box.stringToByteArray("iso6mp41")], // compatible brands | ||
}), | ||
new Box("moov", { | ||
children: [ | ||
new Box("mvhd", { | ||
/* prettier-ignore */ | ||
contents: [0x00, // version | ||
0x00,0x00,0x00, // flags | ||
0x00,0x00,0x00,0x00, // creation time | ||
0x00,0x00,0x00,0x00, // modification time | ||
0x00,0x00,0x03,0xe8, // timescale | ||
0x00,0x00,0x00,0x00, // duration | ||
0x00,0x01,0x00,0x00, // rate | ||
0x01,0x00, // volume | ||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // reserved | ||
0x00,0x01,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, // a b u (matrix structure) | ||
0x00,0x00,0x00,0x00, 0x00,0x01,0x00,0x00, 0x00,0x00,0x00,0x00, // c d v | ||
0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x40,0x00,0x00,0x00, // x y w | ||
0x00,0x00,0x00,0x00, // preview time | ||
0x00,0x00,0x00,0x00, // preview duration | ||
0x00,0x00,0x00,0x00, // poster time | ||
0x00,0x00,0x00,0x00, // selection time | ||
0x00,0x00,0x00,0x00, // selection duration | ||
0x00,0x00,0x00,0x00, // current time | ||
0x00,0x00,0x00,0x02], // next track | ||
}), | ||
new Box("trak", { | ||
children: [ | ||
new Box("tkhd", { | ||
/* prettier-ignore */ | ||
contents: [0x00, // version | ||
0x00,0x00,0x03, // flags (0x01 - track enabled, 0x02 - track in movie, 0x04 - track in preview, 0x08 - track in poster) | ||
0x00,0x00,0x00,0x00, // creation time | ||
0x00,0x00,0x00,0x00, // modification time | ||
0x00,0x00,0x00,0x01, // track id | ||
0x00,0x00,0x00,0x00, // reserved | ||
0x00,0x00,0x00,0x00, // duration | ||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // reserved | ||
0x00,0x00, // layer | ||
0x00,0x01, // alternate group | ||
0x01,0x00, // volume | ||
0x00,0x00, // reserved | ||
0x00,0x01,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, // a b u (matrix structure) | ||
0x00,0x00,0x00,0x00, 0x00,0x01,0x00,0x00, 0x00,0x00,0x00,0x00, // c d v | ||
0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x40,0x00,0x00,0x00, // x y w | ||
0x00,0x00,0x00,0x00, // track width | ||
0x00,0x00,0x00,0x00], // track height | ||
}), | ||
new Box("mdia", { | ||
children: [ | ||
new Box("mdhd", { | ||
/* prettier-ignore */ | ||
contents: [0x00, // version | ||
0x00,0x00,0x00, // flags | ||
0x00,0x00,0x00,0x00, // creation time (in seconds since midnight, January 1, 1904) | ||
0x00,0x00,0x00,0x00, // modification time | ||
...sampleRate, // time scale | ||
0x00,0x00,0x00,0x00, // duration | ||
0x55,0xc4, // language | ||
0x00,0x00], // quality | ||
}), | ||
new Box("hdlr", { | ||
/* prettier-ignore */ | ||
contents: [0x00, // version | ||
0x00,0x00,0x00, // flags | ||
...Box.stringToByteArray('mhlr'), // component type (mhlr, dhlr) | ||
...Box.stringToByteArray('soun'), // component subtype (vide' for video data, 'soun' for sound data or ‘subt’ for subtitles) | ||
0x00,0x00,0x00,0x00, // component manufacturer | ||
0x00,0x00,0x00,0x00, // component flags | ||
0x00,0x00,0x00,0x00, // component flags mask | ||
0x00], // String that specifies the name of the component, terminated by a null character | ||
}), | ||
new Box("minf", { | ||
children: [ | ||
new Box("stbl", { | ||
children: [ | ||
new Box("stsd", { | ||
// Sample description atom | ||
/* prettier-ignore */ | ||
contents: [0x00, // version | ||
0x00,0x00,0x00, // flags | ||
0x00,0x00,0x00,0x01], // entry count | ||
children: [this.getCodecBox(header)], | ||
}), | ||
new Box("stts", { | ||
// Time-to-sample atom | ||
/* prettier-ignore */ | ||
contents: [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], | ||
}), | ||
new Box("stsc", { | ||
// Sample-to-chunk atom | ||
/* prettier-ignore */ | ||
contents: [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], | ||
}), | ||
new Box("stsz", { | ||
// Sample Size atom | ||
/* prettier-ignore */ | ||
contents: [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | ||
0x00,0x00,0x00,0x00], | ||
}), | ||
new Box("stco", { | ||
// Chunk Offset atom | ||
/* prettier-ignore */ | ||
contents: [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], | ||
}), | ||
], | ||
}), | ||
], | ||
}), | ||
], | ||
}), | ||
], | ||
}), | ||
new Box("mvex", { | ||
children: [ | ||
new Box("trex", { | ||
/* prettier-ignore */ | ||
contents: [0x00,0x00,0x00,0x00, // flags | ||
0x00,0x00,0x00,0x01, // track id | ||
0x00,0x00,0x00,0x01, // default_sample_description_index | ||
...Box.getUint32(header.samplesPerFrame), // default_sample_duration | ||
0x00,0x00,0x00,0x00, // default_sample_size; | ||
0x00,0x00,0x00,0x00], // default_sample_flags; | ||
}), | ||
], | ||
}), | ||
], | ||
}), | ||
]; | ||
return concatBuffers(...boxes.map((box) => box.contents)); | ||
return new ContainerElement({ | ||
children: [ | ||
new Box("ftyp", { | ||
/* prettier-ignore */ | ||
contents: [Box.stringToByteArray("iso5"), // major brand | ||
0x00,0x00,0x02,0x00, // minor version | ||
Box.stringToByteArray("iso6mp41")], // compatible brands | ||
}), | ||
new Box("moov", { | ||
children: [ | ||
new Box("mvhd", { | ||
/* prettier-ignore */ | ||
contents: [0x00, // version | ||
0x00,0x00,0x00, // flags | ||
0x00,0x00,0x00,0x00, // creation time | ||
0x00,0x00,0x00,0x00, // modification time | ||
0x00,0x00,0x03,0xe8, // timescale | ||
0x00,0x00,0x00,0x00, // duration | ||
0x00,0x01,0x00,0x00, // rate | ||
0x01,0x00, // volume | ||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // reserved | ||
0x00,0x01,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, // a b u (matrix structure) | ||
0x00,0x00,0x00,0x00, 0x00,0x01,0x00,0x00, 0x00,0x00,0x00,0x00, // c d v | ||
0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x40,0x00,0x00,0x00, // x y w | ||
0x00,0x00,0x00,0x00, // preview time | ||
0x00,0x00,0x00,0x00, // preview duration | ||
0x00,0x00,0x00,0x00, // poster time | ||
0x00,0x00,0x00,0x00, // selection time | ||
0x00,0x00,0x00,0x00, // selection duration | ||
0x00,0x00,0x00,0x00, // current time | ||
0x00,0x00,0x00,0x02], // next track | ||
}), | ||
new Box("trak", { | ||
children: [ | ||
new Box("tkhd", { | ||
/* prettier-ignore */ | ||
contents: [0x00, // version | ||
0x00,0x00,0x03, // flags (0x01 - track enabled, 0x02 - track in movie, 0x04 - track in preview, 0x08 - track in poster) | ||
0x00,0x00,0x00,0x00, // creation time | ||
0x00,0x00,0x00,0x00, // modification time | ||
0x00,0x00,0x00,0x01, // track id | ||
0x00,0x00,0x00,0x00, // reserved | ||
0x00,0x00,0x00,0x00, // duration | ||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // reserved | ||
0x00,0x00, // layer | ||
0x00,0x01, // alternate group | ||
0x01,0x00, // volume | ||
0x00,0x00, // reserved | ||
0x00,0x01,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, // a b u (matrix structure) | ||
0x00,0x00,0x00,0x00, 0x00,0x01,0x00,0x00, 0x00,0x00,0x00,0x00, // c d v | ||
0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x40,0x00,0x00,0x00, // x y w | ||
0x00,0x00,0x00,0x00, // track width | ||
0x00,0x00,0x00,0x00], // track height | ||
}), | ||
new Box("mdia", { | ||
children: [ | ||
new Box("mdhd", { | ||
/* prettier-ignore */ | ||
contents: [0x00, // version | ||
0x00,0x00,0x00, // flags | ||
0x00,0x00,0x00,0x00, // creation time (in seconds since midnight, January 1, 1904) | ||
0x00,0x00,0x00,0x00, // modification time | ||
Box.getUint32(header.sampleRate), // time scale | ||
0x00,0x00,0x00,0x00, // duration | ||
0x55,0xc4, // language | ||
0x00,0x00], // quality | ||
}), | ||
new Box("hdlr", { | ||
/* prettier-ignore */ | ||
contents: [0x00, // version | ||
0x00,0x00,0x00, // flags | ||
Box.stringToByteArray('mhlr'), // component type (mhlr, dhlr) | ||
Box.stringToByteArray('soun'), // component subtype (vide' for video data, 'soun' for sound data or ‘subt’ for subtitles) | ||
0x00,0x00,0x00,0x00, // component manufacturer | ||
0x00,0x00,0x00,0x00, // component flags | ||
0x00,0x00,0x00,0x00, // component flags mask | ||
0x00], // String that specifies the name of the component, terminated by a null character | ||
}), | ||
new Box("minf", { | ||
children: [ | ||
new Box("stbl", { | ||
children: [ | ||
new Box("stsd", { | ||
// Sample description atom | ||
/* prettier-ignore */ | ||
contents: [0x00, // version | ||
0x00,0x00,0x00, // flags | ||
0x00,0x00,0x00,0x01], // entry count | ||
children: [this.getCodecBox(header)], | ||
}), | ||
new Box("stts", { | ||
// Time-to-sample atom | ||
/* prettier-ignore */ | ||
contents: [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], | ||
}), | ||
new Box("stsc", { | ||
// Sample-to-chunk atom | ||
/* prettier-ignore */ | ||
contents: [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], | ||
}), | ||
new Box("stsz", { | ||
// Sample Size atom | ||
/* prettier-ignore */ | ||
contents: [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | ||
0x00,0x00,0x00,0x00], | ||
}), | ||
new Box("stco", { | ||
// Chunk Offset atom | ||
/* prettier-ignore */ | ||
contents: [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], | ||
}), | ||
], | ||
}), | ||
], | ||
}), | ||
], | ||
}), | ||
], | ||
}), | ||
new Box("mvex", { | ||
children: [ | ||
new Box("trex", { | ||
/* prettier-ignore */ | ||
contents: [0x00,0x00,0x00,0x00, // flags | ||
0x00,0x00,0x00,0x01, // track id | ||
0x00,0x00,0x00,0x01, // default_sample_description_index | ||
Box.getUint32(header.samplesPerFrame), // default_sample_duration | ||
0x00,0x00,0x00,0x00, // default_sample_size; | ||
0x00,0x00,0x00,0x00], // default_sample_flags; | ||
}), | ||
], | ||
}), | ||
], | ||
}), | ||
], | ||
}).contents; | ||
} | ||
@@ -349,66 +347,57 @@ | ||
getMediaSegment(frames) { | ||
const trun = new Box("trun", { | ||
/* prettier-ignore */ | ||
contents: [0x00, // version | ||
0x00,0b0000010,0b00000001, // flags | ||
// * `ABCD|00000E0F` | ||
// * `A...|........` sample‐composition‐time‐offsets‐present | ||
// * `.B..|........` sample‐flags‐present | ||
// * `..C.|........` sample‐size‐present | ||
// * `...D|........` sample‐duration‐present | ||
// * `....|.....E..` first‐sample‐flags‐present | ||
// * `....|.......G` data-offset-present | ||
...Box.getUint32(frames.length), // number of samples | ||
...frames.flatMap(({data}) => [...Box.getUint32(data.length)]), // samples size per frame | ||
], | ||
}); | ||
const moof = new Box("moof", { | ||
return new ContainerElement({ | ||
children: [ | ||
new Box("mfhd", { | ||
/* prettier-ignore */ | ||
contents: [0x00,0x00,0x00,0x00, | ||
0x00,0x00,0x00,0x00], // sequence number | ||
}), | ||
new Box("traf", { | ||
new Box("moof", { | ||
children: [ | ||
new Box("tfhd", { | ||
new Box("mfhd", { | ||
/* prettier-ignore */ | ||
contents: [0x00, // version | ||
0b00000010,0x00,0b00000000, // flags | ||
// * `AB|00000000|00CDE0FG` | ||
// * `A.|........|........` default-base-is-moof | ||
// * `.B|........|........` duration-is-empty | ||
// * `..|........|..C.....` default-sample-flags-present | ||
// * `..|........|...D....` default-sample-size-present | ||
// * `..|........|....E...` default-sample-duration-present | ||
// * `..|........|......F.` sample-description-index-present | ||
// * `..|........|.......G` base-data-offset-present | ||
0x00,0x00,0x00,0x01, // track id | ||
// ...Box.getUint32(frames[0].header.samplesPerFrame), // default-sample-duration | ||
], | ||
contents: [0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00], // sequence number | ||
}), | ||
new Box("tfdt", { | ||
/* prettier-ignore */ | ||
contents: [0x00, // version | ||
0x00,0x00,0x00, // flags | ||
0x00,0x00,0x00,0x00], // base media decode time | ||
new Box("traf", { | ||
children: [ | ||
new Box("tfhd", { | ||
/* prettier-ignore */ | ||
contents: [0x00, // version | ||
0b00000010,0x00,0b00000000, // flags | ||
// * `AB|00000000|00CDE0FG` | ||
// * `A.|........|........` default-base-is-moof | ||
// * `.B|........|........` duration-is-empty | ||
// * `..|........|..C.....` default-sample-flags-present | ||
// * `..|........|...D....` default-sample-size-present | ||
// * `..|........|....E...` default-sample-duration-present | ||
// * `..|........|......F.` sample-description-index-present | ||
// * `..|........|.......G` base-data-offset-present | ||
0x00,0x00,0x00,0x01], // track id | ||
}), | ||
new Box("tfdt", { | ||
/* prettier-ignore */ | ||
contents: [0x00, // version | ||
0x00,0x00,0x00, // flags | ||
0x00,0x00,0x00,0x00], // base media decode time | ||
}), | ||
new Box("trun", { | ||
/* prettier-ignore */ | ||
contents: [0x00, // version | ||
0x00,0b0000010,0b00000001, // flags | ||
// * `ABCD|00000E0F` | ||
// * `A...|........` sample‐composition‐time‐offsets‐present | ||
// * `.B..|........` sample‐flags‐present | ||
// * `..C.|........` sample‐size‐present | ||
// * `...D|........` sample‐duration‐present | ||
// * `....|.....E..` first‐sample‐flags‐present | ||
// * `....|.......G` data-offset-present | ||
Box.getUint32(frames.length), // number of samples | ||
Box.getUint32(92 + frames.length * 4), // data offset | ||
...frames.map(({data}) => Box.getUint32(data.length))], // samples size per frame | ||
}), | ||
], | ||
}), | ||
trun, | ||
], | ||
}), | ||
new Box("mdat", { | ||
contents: frames.map(({ data }) => data), | ||
}), | ||
], | ||
}); | ||
trun.insertBytes([...Box.getUint32(moof.length + 12)], 8); // data offset (moof length + mdat length + mdat) | ||
const mdatContents = concatBuffers(...frames.map(({ data }) => data)); | ||
return concatBuffers( | ||
moof.contents, | ||
Box.getUint32(mdatContents.length + 8), | ||
Box.stringToByteArray("mdat"), | ||
mdatContents | ||
); | ||
}).contents; | ||
} | ||
} |
@@ -24,3 +24,3 @@ /* Copyright 2020-2021 Ethan Halsall | ||
/** | ||
* @description ISO/IEC 14496-12 Part 12 ISO Base Media File Format Box | ||
* @description Extensible Binary Meta Language element | ||
* @param {name} name ID of the EBML element | ||
@@ -32,7 +32,4 @@ * @param {object} params Object containing contents or children | ||
*/ | ||
constructor( | ||
name, | ||
{ contents = [], children = [], isUnknownLength = false } = {} | ||
) { | ||
super(name, contents, children); | ||
constructor(name, { contents, children, isUnknownLength = false } = {}) { | ||
super({ name, contents, children }); | ||
@@ -82,25 +79,17 @@ this._isUnknownLength = isUnknownLength; | ||
/** | ||
* @returns {number} Total length of this object and all contents | ||
*/ | ||
get length() { | ||
const length = super.length; | ||
return length + EBML.getUintVariable(length).length; | ||
_buildContents() { | ||
return [...this._name, ...this._lengthBytes, ...super._buildContents()]; | ||
} | ||
/** | ||
* @returns {Array<Uint8>} Contents of this EBML tag | ||
*/ | ||
get contents() { | ||
const contents = super.contents; | ||
_buildLength() { | ||
if (!this._length) { | ||
this._contentLength = super._buildLength(); | ||
this._lengthBytes = this._isUnknownLength | ||
? [0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff] // unknown length constant | ||
: EBML.getUintVariable(this._contentLength); | ||
this._length = | ||
this._name.length + this._lengthBytes.length + this._contentLength; | ||
} | ||
// prettier-ignore | ||
return this._name | ||
.concat( | ||
this._isUnknownLength | ||
? [0x01,0xff,0xff,0xff,0xff,0xff,0xff,0xff] // unknown length constant | ||
: [...EBML.getUintVariable(contents.length)] | ||
) | ||
.concat(contents); | ||
return this._length; | ||
} | ||
@@ -107,0 +96,0 @@ } |
@@ -19,17 +19,5 @@ /* Copyright 2020-2021 Ethan Halsall | ||
import { concatBuffers } from "../../utilities"; | ||
import ContainerElement from "../ContainerElement"; | ||
import EBML, { id } from "./EBML"; | ||
const EBML_HEADER = new EBML(id.EBML, { | ||
children: [ | ||
new EBML(id.EBMLVersion, { contents: [1] }), | ||
new EBML(id.EBMLReadVersion, { contents: [1] }), | ||
new EBML(id.EBMLMaxIDLength, { contents: [4] }), | ||
new EBML(id.EBMLMaxSizeLength, { contents: [8] }), | ||
new EBML(id.DocType, { contents: EBML.stringToByteArray("webm") }), | ||
new EBML(id.DocTypeVersion, { contents: [4] }), | ||
new EBML(id.DocTypeReadVersion, { contents: [2] }), | ||
], | ||
}).contents; | ||
export default class WEBMContainer { | ||
@@ -42,14 +30,12 @@ constructor(codec) { | ||
new EBML(id.CodecDelay, { | ||
contents: [ | ||
...EBML.getUint32( | ||
this._getTimecode(header.preSkip) * this._timecodeScale | ||
), | ||
], | ||
contents: EBML.getUint32( | ||
this._getTimecode(header.preSkip) * this._timecodeScale | ||
), | ||
}), // OPUS codec delay | ||
new EBML(id.SeekPreRoll, { | ||
contents: [ | ||
...EBML.getUint32(this._getTimecode(3840) * this._timecodeScale), | ||
], | ||
contents: EBML.getUint32( | ||
this._getTimecode(3840) * this._timecodeScale | ||
), | ||
}), // OPUS seek preroll 80ms | ||
new EBML(id.CodecPrivate, { contents: [...header.bytes] }), // OpusHead bytes | ||
new EBML(id.CodecPrivate, { contents: header.bytes }), // OpusHead bytes | ||
]; | ||
@@ -64,5 +50,5 @@ break; | ||
0x02, // number of packets | ||
...header.codecPrivate.lacing, | ||
...header.codecPrivate.vorbisHead, | ||
...header.codecPrivate.vorbisSetup, | ||
header.codecPrivate.lacing, | ||
header.codecPrivate.vorbisHead, | ||
header.codecPrivate.vorbisSetup, | ||
], | ||
@@ -85,39 +71,54 @@ }), | ||
const segment = new EBML(id.Segment, { | ||
isUnknownLength: true, | ||
return new ContainerElement({ | ||
children: [ | ||
new EBML(id.Info, { | ||
new EBML(id.EBML, { | ||
children: [ | ||
new EBML(id.TimecodeScale, { | ||
contents: [...EBML.getUint32(1000000)], | ||
}), | ||
new EBML(id.MuxingApp, { | ||
contents: EBML.stringToByteArray("mse-audio-wrapper"), | ||
}), | ||
new EBML(id.WritingApp, { | ||
contents: EBML.stringToByteArray("mse-audio-wrapper"), | ||
}), | ||
new EBML(id.EBMLVersion, { contents: 1 }), | ||
new EBML(id.EBMLReadVersion, { contents: 1 }), | ||
new EBML(id.EBMLMaxIDLength, { contents: 4 }), | ||
new EBML(id.EBMLMaxSizeLength, { contents: 8 }), | ||
new EBML(id.DocType, { contents: EBML.stringToByteArray("webm") }), | ||
new EBML(id.DocTypeVersion, { contents: 4 }), | ||
new EBML(id.DocTypeReadVersion, { contents: 2 }), | ||
], | ||
}), | ||
new EBML(id.Tracks, { | ||
new EBML(id.Segment, { | ||
isUnknownLength: true, | ||
children: [ | ||
new EBML(id.TrackEntry, { | ||
new EBML(id.Info, { | ||
children: [ | ||
new EBML(id.TrackNumber, { contents: [0x01] }), | ||
new EBML(id.TrackUID, { contents: [0x01] }), | ||
new EBML(id.FlagLacing, { contents: [0x00] }), | ||
new EBML(id.CodecID, { | ||
contents: EBML.stringToByteArray(this._codecId), | ||
new EBML(id.TimecodeScale, { | ||
contents: EBML.getUint32(1000000), | ||
}), | ||
new EBML(id.TrackType, { contents: [0x02] }), // audio | ||
new EBML(id.Audio, { | ||
new EBML(id.MuxingApp, { | ||
contents: EBML.stringToByteArray("mse-audio-wrapper"), | ||
}), | ||
new EBML(id.WritingApp, { | ||
contents: EBML.stringToByteArray("mse-audio-wrapper"), | ||
}), | ||
], | ||
}), | ||
new EBML(id.Tracks, { | ||
children: [ | ||
new EBML(id.TrackEntry, { | ||
children: [ | ||
new EBML(id.Channels, { contents: [header.channels] }), | ||
new EBML(id.SamplingFrequency, { | ||
contents: [...EBML.getFloat64(header.sampleRate)], | ||
new EBML(id.TrackNumber, { contents: 0x01 }), | ||
new EBML(id.TrackUID, { contents: 0x01 }), | ||
new EBML(id.FlagLacing, { contents: 0x00 }), | ||
new EBML(id.CodecID, { | ||
contents: EBML.stringToByteArray(this._codecId), | ||
}), | ||
new EBML(id.BitDepth, { contents: [header.bitDepth] }), | ||
new EBML(id.TrackType, { contents: 0x02 }), // audio | ||
new EBML(id.Audio, { | ||
children: [ | ||
new EBML(id.Channels, { contents: header.channels }), | ||
new EBML(id.SamplingFrequency, { | ||
contents: EBML.getFloat64(header.sampleRate), | ||
}), | ||
new EBML(id.BitDepth, { contents: header.bitDepth }), | ||
], | ||
}), | ||
...this._getCodecSpecificTrack(header), | ||
], | ||
}), | ||
...this._getCodecSpecificTrack(header), | ||
], | ||
@@ -129,4 +130,2 @@ }), | ||
}).contents; | ||
return concatBuffers(EBML_HEADER, segment); | ||
} | ||
@@ -140,7 +139,5 @@ | ||
new EBML(id.Timecode, { | ||
contents: [ | ||
...EBML.getUintVariable( | ||
Math.round(this._getTimecode(this._sampleNumber)) | ||
), | ||
], // Absolute timecode of the cluster | ||
contents: EBML.getUintVariable( | ||
Math.round(this._getTimecode(this._sampleNumber)) | ||
), // Absolute timecode of the cluster | ||
}), | ||
@@ -152,3 +149,3 @@ ...frames.map( | ||
0x81, // track number | ||
...EBML.getInt16( | ||
EBML.getInt16( | ||
Math.round( | ||
@@ -162,3 +159,3 @@ this._getTimecode( | ||
0x80, // No lacing | ||
...data, // ogg page contents | ||
data, // ogg page contents | ||
], | ||
@@ -168,11 +165,8 @@ }) | ||
], | ||
}).contents; | ||
}); | ||
this._sampleNumber += blockSamples; | ||
const data = new Uint8Array(cluster.length); | ||
data.set(cluster); | ||
return data; | ||
return cluster.contents; | ||
} | ||
} |
142874
3368