webm-muxer
Advanced tools
Comparing version 1.2.0 to 1.2.1
@@ -11,2 +11,8 @@ /** | ||
* | ||
* If the target is a function, it will be called each time data is output by the muxer - this is useful if you want | ||
* to stream the data. The function will be called with three arguments: the data to write, the offset in bytes at | ||
* which to write the data and a boolean indicating whether the muxer is done writing data. Note that the same | ||
* segment of bytes might be written to multiple times and therefore you need to write the data in the same order | ||
* the function gave it to you. | ||
* | ||
* If the target is of type `FileSystemWritableFileStream`, the file will be written directly to disk as it is being | ||
@@ -16,3 +22,3 @@ * muxed. The benefit of this target is the ability to write out very large files, easily exceeding the RAM of the | ||
*/ | ||
target: 'buffer' | FileSystemWritableFileStream, | ||
target: 'buffer' | ((data: Uint8Array, offset: number, done: boolean) => void) | FileSystemWritableFileStream, | ||
/** | ||
@@ -19,0 +25,0 @@ * Specifies the docType of the muxed multimedia file. This property is optional and defaults to `'webm'`, which is |
@@ -61,41 +61,49 @@ "use strict"; | ||
}; | ||
var measureUnsignedInt = (value) => { | ||
if (value < 1 << 8) { | ||
return 1; | ||
} else if (value < 1 << 16) { | ||
return 2; | ||
} else if (value < 1 << 24) { | ||
return 3; | ||
} else if (value < __pow(2, 32)) { | ||
return 4; | ||
} else if (value < __pow(2, 40)) { | ||
return 5; | ||
} else { | ||
return 6; | ||
} | ||
}; | ||
var measureEBMLVarInt = (value) => { | ||
if (value < (1 << 7) - 1) { | ||
return 1; | ||
} else if (value < (1 << 14) - 1) { | ||
return 2; | ||
} else if (value < (1 << 21) - 1) { | ||
return 3; | ||
} else if (value < (1 << 28) - 1) { | ||
return 4; | ||
} else if (value < __pow(2, 35) - 1) { | ||
return 5; | ||
} else if (value < __pow(2, 42) - 1) { | ||
return 6; | ||
} else { | ||
throw new Error("EBML VINT size not supported " + value); | ||
} | ||
}; | ||
// src/write_target.ts | ||
var _helper, _helperView, _writeFloat32, writeFloat32_fn, _writeFloat64, writeFloat64_fn, _writeUnsignedInt, writeUnsignedInt_fn, _writeString, writeString_fn; | ||
var WriteTarget = class { | ||
constructor() { | ||
__privateAdd(this, _writeFloat32); | ||
__privateAdd(this, _writeFloat64); | ||
__privateAdd(this, _writeUnsignedInt); | ||
__privateAdd(this, _writeString); | ||
this.pos = 0; | ||
this.helper = new Uint8Array(8); | ||
this.helperView = new DataView(this.helper.buffer); | ||
__privateAdd(this, _helper, new Uint8Array(8)); | ||
__privateAdd(this, _helperView, new DataView(__privateGet(this, _helper).buffer)); | ||
this.offsets = /* @__PURE__ */ new WeakMap(); | ||
this.dataOffsets = /* @__PURE__ */ new WeakMap(); | ||
} | ||
writeFloat32(value) { | ||
this.helperView.setFloat32(0, value, false); | ||
this.write(this.helper.subarray(0, 4)); | ||
} | ||
writeFloat64(value) { | ||
this.helperView.setFloat64(0, value, false); | ||
this.write(this.helper); | ||
} | ||
writeUnsignedInt(value, width = measureUnsignedInt(value)) { | ||
let pos = 0; | ||
switch (width) { | ||
case 6: | ||
this.helperView.setUint8(pos++, value / __pow(2, 40) | 0); | ||
case 5: | ||
this.helperView.setUint8(pos++, value / __pow(2, 32) | 0); | ||
case 4: | ||
this.helperView.setUint8(pos++, value >> 24); | ||
case 3: | ||
this.helperView.setUint8(pos++, value >> 16); | ||
case 2: | ||
this.helperView.setUint8(pos++, value >> 8); | ||
case 1: | ||
this.helperView.setUint8(pos++, value); | ||
break; | ||
default: | ||
throw new Error("Bad UINT size " + width); | ||
} | ||
this.write(this.helper.subarray(0, pos)); | ||
} | ||
writeEBMLVarInt(value, width = measureEBMLVarInt(value)) { | ||
@@ -105,33 +113,33 @@ let pos = 0; | ||
case 1: | ||
this.helperView.setUint8(pos++, 1 << 7 | value); | ||
__privateGet(this, _helperView).setUint8(pos++, 1 << 7 | value); | ||
break; | ||
case 2: | ||
this.helperView.setUint8(pos++, 1 << 6 | value >> 8); | ||
this.helperView.setUint8(pos++, value); | ||
__privateGet(this, _helperView).setUint8(pos++, 1 << 6 | value >> 8); | ||
__privateGet(this, _helperView).setUint8(pos++, value); | ||
break; | ||
case 3: | ||
this.helperView.setUint8(pos++, 1 << 5 | value >> 16); | ||
this.helperView.setUint8(pos++, value >> 8); | ||
this.helperView.setUint8(pos++, value); | ||
__privateGet(this, _helperView).setUint8(pos++, 1 << 5 | value >> 16); | ||
__privateGet(this, _helperView).setUint8(pos++, value >> 8); | ||
__privateGet(this, _helperView).setUint8(pos++, value); | ||
break; | ||
case 4: | ||
this.helperView.setUint8(pos++, 1 << 4 | value >> 24); | ||
this.helperView.setUint8(pos++, value >> 16); | ||
this.helperView.setUint8(pos++, value >> 8); | ||
this.helperView.setUint8(pos++, value); | ||
__privateGet(this, _helperView).setUint8(pos++, 1 << 4 | value >> 24); | ||
__privateGet(this, _helperView).setUint8(pos++, value >> 16); | ||
__privateGet(this, _helperView).setUint8(pos++, value >> 8); | ||
__privateGet(this, _helperView).setUint8(pos++, value); | ||
break; | ||
case 5: | ||
this.helperView.setUint8(pos++, 1 << 3 | value / __pow(2, 32) & 7); | ||
this.helperView.setUint8(pos++, value >> 24); | ||
this.helperView.setUint8(pos++, value >> 16); | ||
this.helperView.setUint8(pos++, value >> 8); | ||
this.helperView.setUint8(pos++, value); | ||
__privateGet(this, _helperView).setUint8(pos++, 1 << 3 | value / __pow(2, 32) & 7); | ||
__privateGet(this, _helperView).setUint8(pos++, value >> 24); | ||
__privateGet(this, _helperView).setUint8(pos++, value >> 16); | ||
__privateGet(this, _helperView).setUint8(pos++, value >> 8); | ||
__privateGet(this, _helperView).setUint8(pos++, value); | ||
break; | ||
case 6: | ||
this.helperView.setUint8(pos++, 1 << 2 | value / __pow(2, 40) & 3); | ||
this.helperView.setUint8(pos++, value / __pow(2, 32) | 0); | ||
this.helperView.setUint8(pos++, value >> 24); | ||
this.helperView.setUint8(pos++, value >> 16); | ||
this.helperView.setUint8(pos++, value >> 8); | ||
this.helperView.setUint8(pos++, value); | ||
__privateGet(this, _helperView).setUint8(pos++, 1 << 2 | value / __pow(2, 40) & 3); | ||
__privateGet(this, _helperView).setUint8(pos++, value / __pow(2, 32) | 0); | ||
__privateGet(this, _helperView).setUint8(pos++, value >> 24); | ||
__privateGet(this, _helperView).setUint8(pos++, value >> 16); | ||
__privateGet(this, _helperView).setUint8(pos++, value >> 8); | ||
__privateGet(this, _helperView).setUint8(pos++, value); | ||
break; | ||
@@ -141,7 +149,4 @@ default: | ||
} | ||
this.write(this.helper.subarray(0, pos)); | ||
this.write(__privateGet(this, _helper).subarray(0, pos)); | ||
} | ||
writeString(str) { | ||
this.write(new Uint8Array(str.split("").map((x) => x.charCodeAt(0)))); | ||
} | ||
writeEBML(data) { | ||
@@ -157,3 +162,3 @@ var _a, _b; | ||
this.offsets.set(data, this.pos); | ||
this.writeUnsignedInt(data.id); | ||
__privateMethod(this, _writeUnsignedInt, writeUnsignedInt_fn).call(this, data.id); | ||
if (Array.isArray(data.data)) { | ||
@@ -174,6 +179,6 @@ let sizePos = this.pos; | ||
this.writeEBMLVarInt(size); | ||
this.writeUnsignedInt(data.data, size); | ||
__privateMethod(this, _writeUnsignedInt, writeUnsignedInt_fn).call(this, data.data, size); | ||
} else if (typeof data.data === "string") { | ||
this.writeEBMLVarInt(data.data.length); | ||
this.writeString(data.data); | ||
__privateMethod(this, _writeString, writeString_fn).call(this, data.data); | ||
} else if (data.data instanceof Uint8Array) { | ||
@@ -184,6 +189,6 @@ this.writeEBMLVarInt(data.data.byteLength, data.size); | ||
this.writeEBMLVarInt(4); | ||
this.writeFloat32(data.data.value); | ||
__privateMethod(this, _writeFloat32, writeFloat32_fn).call(this, data.data.value); | ||
} else if (data.data instanceof EBMLFloat64) { | ||
this.writeEBMLVarInt(8); | ||
this.writeFloat64(data.data.value); | ||
__privateMethod(this, _writeFloat64, writeFloat64_fn).call(this, data.data.value); | ||
} | ||
@@ -193,55 +198,62 @@ } | ||
}; | ||
var measureUnsignedInt = (value) => { | ||
if (value < 1 << 8) { | ||
return 1; | ||
} else if (value < 1 << 16) { | ||
return 2; | ||
} else if (value < 1 << 24) { | ||
return 3; | ||
} else if (value < __pow(2, 32)) { | ||
return 4; | ||
} else if (value < __pow(2, 40)) { | ||
return 5; | ||
} else { | ||
return 6; | ||
} | ||
_helper = new WeakMap(); | ||
_helperView = new WeakMap(); | ||
_writeFloat32 = new WeakSet(); | ||
writeFloat32_fn = function(value) { | ||
__privateGet(this, _helperView).setFloat32(0, value, false); | ||
this.write(__privateGet(this, _helper).subarray(0, 4)); | ||
}; | ||
var measureEBMLVarInt = (value) => { | ||
if (value < (1 << 7) - 1) { | ||
return 1; | ||
} else if (value < (1 << 14) - 1) { | ||
return 2; | ||
} else if (value < (1 << 21) - 1) { | ||
return 3; | ||
} else if (value < (1 << 28) - 1) { | ||
return 4; | ||
} else if (value < __pow(2, 35) - 1) { | ||
return 5; | ||
} else if (value < __pow(2, 42) - 1) { | ||
return 6; | ||
} else { | ||
throw new Error("EBML VINT size not supported " + value); | ||
_writeFloat64 = new WeakSet(); | ||
writeFloat64_fn = function(value) { | ||
__privateGet(this, _helperView).setFloat64(0, value, false); | ||
this.write(__privateGet(this, _helper)); | ||
}; | ||
_writeUnsignedInt = new WeakSet(); | ||
writeUnsignedInt_fn = function(value, width = measureUnsignedInt(value)) { | ||
let pos = 0; | ||
switch (width) { | ||
case 6: | ||
__privateGet(this, _helperView).setUint8(pos++, value / __pow(2, 40) | 0); | ||
case 5: | ||
__privateGet(this, _helperView).setUint8(pos++, value / __pow(2, 32) | 0); | ||
case 4: | ||
__privateGet(this, _helperView).setUint8(pos++, value >> 24); | ||
case 3: | ||
__privateGet(this, _helperView).setUint8(pos++, value >> 16); | ||
case 2: | ||
__privateGet(this, _helperView).setUint8(pos++, value >> 8); | ||
case 1: | ||
__privateGet(this, _helperView).setUint8(pos++, value); | ||
break; | ||
default: | ||
throw new Error("Bad UINT size " + width); | ||
} | ||
this.write(__privateGet(this, _helper).subarray(0, pos)); | ||
}; | ||
_writeString = new WeakSet(); | ||
writeString_fn = function(str) { | ||
this.write(new Uint8Array(str.split("").map((x) => x.charCodeAt(0)))); | ||
}; | ||
var _buffer, _bytes; | ||
var ArrayBufferWriteTarget = class extends WriteTarget { | ||
constructor() { | ||
super(); | ||
this.buffer = new ArrayBuffer(__pow(2, 16)); | ||
this.bytes = new Uint8Array(this.buffer); | ||
__privateAdd(this, _buffer, new ArrayBuffer(__pow(2, 16))); | ||
__privateAdd(this, _bytes, new Uint8Array(__privateGet(this, _buffer))); | ||
} | ||
ensureSize(size) { | ||
let newLength = this.buffer.byteLength; | ||
let newLength = __privateGet(this, _buffer).byteLength; | ||
while (newLength < size) | ||
newLength *= 2; | ||
if (newLength === this.buffer.byteLength) | ||
if (newLength === __privateGet(this, _buffer).byteLength) | ||
return; | ||
let newBuffer = new ArrayBuffer(newLength); | ||
let newBytes = new Uint8Array(newBuffer); | ||
newBytes.set(this.bytes, 0); | ||
this.buffer = newBuffer; | ||
this.bytes = newBytes; | ||
newBytes.set(__privateGet(this, _bytes), 0); | ||
__privateSet(this, _buffer, newBuffer); | ||
__privateSet(this, _bytes, newBytes); | ||
} | ||
write(data) { | ||
this.ensureSize(this.pos + data.byteLength); | ||
this.bytes.set(data, this.pos); | ||
__privateGet(this, _bytes).set(data, this.pos); | ||
this.pos += data.byteLength; | ||
@@ -254,12 +266,16 @@ } | ||
this.ensureSize(this.pos); | ||
return this.buffer.slice(0, this.pos); | ||
return __privateGet(this, _buffer).slice(0, this.pos); | ||
} | ||
}; | ||
_buffer = new WeakMap(); | ||
_bytes = new WeakMap(); | ||
var FILE_CHUNK_SIZE = __pow(2, 24); | ||
var MAX_CHUNKS_AT_ONCE = 2; | ||
var _stream, _chunks; | ||
var FileSystemWritableFileStreamWriteTarget = class extends WriteTarget { | ||
constructor(stream) { | ||
super(); | ||
this.chunks = []; | ||
this.stream = stream; | ||
__privateAdd(this, _stream, void 0); | ||
__privateAdd(this, _chunks, []); | ||
__privateSet(this, _stream, stream); | ||
} | ||
@@ -272,6 +288,6 @@ write(data) { | ||
writeDataIntoChunks(data, position) { | ||
let chunkIndex = this.chunks.findIndex((x) => x.start <= position && position < x.start + FILE_CHUNK_SIZE); | ||
let chunkIndex = __privateGet(this, _chunks).findIndex((x) => x.start <= position && position < x.start + FILE_CHUNK_SIZE); | ||
if (chunkIndex === -1) | ||
chunkIndex = this.createChunk(position); | ||
let chunk = this.chunks[chunkIndex]; | ||
let chunk = __privateGet(this, _chunks)[chunkIndex]; | ||
let relativePosition = position - chunk.start; | ||
@@ -288,5 +304,5 @@ let toWrite = data.subarray(0, Math.min(FILE_CHUNK_SIZE - relativePosition, data.byteLength)); | ||
} | ||
if (this.chunks.length > MAX_CHUNKS_AT_ONCE) { | ||
for (let i = 0; i < this.chunks.length - 1; i++) { | ||
this.chunks[i].shouldFlush = true; | ||
if (__privateGet(this, _chunks).length > MAX_CHUNKS_AT_ONCE) { | ||
for (let i = 0; i < __privateGet(this, _chunks).length - 1; i++) { | ||
__privateGet(this, _chunks)[i].shouldFlush = true; | ||
} | ||
@@ -307,13 +323,13 @@ this.flushChunks(); | ||
}; | ||
this.chunks.push(chunk); | ||
this.chunks.sort((a, b) => a.start - b.start); | ||
return this.chunks.indexOf(chunk); | ||
__privateGet(this, _chunks).push(chunk); | ||
__privateGet(this, _chunks).sort((a, b) => a.start - b.start); | ||
return __privateGet(this, _chunks).indexOf(chunk); | ||
} | ||
flushChunks(force = false) { | ||
for (let i = 0; i < this.chunks.length; i++) { | ||
let chunk = this.chunks[i]; | ||
for (let i = 0; i < __privateGet(this, _chunks).length; i++) { | ||
let chunk = __privateGet(this, _chunks)[i]; | ||
if (!chunk.shouldFlush && !force) | ||
continue; | ||
for (let section of chunk.written) { | ||
this.stream.write({ | ||
__privateGet(this, _stream).write({ | ||
type: "write", | ||
@@ -324,3 +340,3 @@ data: chunk.data.subarray(section.start, section.end), | ||
} | ||
this.chunks.splice(i--, 1); | ||
__privateGet(this, _chunks).splice(i--, 1); | ||
} | ||
@@ -335,2 +351,4 @@ } | ||
}; | ||
_stream = new WeakMap(); | ||
_chunks = new WeakMap(); | ||
var insertSectionIntoFileChunk = (chunk, section) => { | ||
@@ -357,2 +375,56 @@ let low = 0; | ||
}; | ||
var _sections, _onFlush; | ||
var StreamingWriteTarget = class extends WriteTarget { | ||
constructor(onFlush) { | ||
super(); | ||
__privateAdd(this, _sections, []); | ||
__privateAdd(this, _onFlush, void 0); | ||
__privateSet(this, _onFlush, onFlush); | ||
} | ||
write(data) { | ||
__privateGet(this, _sections).push({ | ||
data: data.slice(), | ||
start: this.pos | ||
}); | ||
this.pos += data.byteLength; | ||
} | ||
seek(newPos) { | ||
this.pos = newPos; | ||
} | ||
flush(done) { | ||
if (__privateGet(this, _sections).length === 0) | ||
return; | ||
let chunks = []; | ||
let sorted = [...__privateGet(this, _sections)].sort((a, b) => a.start - b.start); | ||
chunks.push({ | ||
start: sorted[0].start, | ||
size: sorted[0].data.byteLength | ||
}); | ||
for (let i = 1; i < sorted.length; i++) { | ||
let lastChunk = chunks[chunks.length - 1]; | ||
let section = sorted[i]; | ||
if (section.start <= lastChunk.start + lastChunk.size) { | ||
lastChunk.size = Math.max(lastChunk.size, section.start + section.data.byteLength - lastChunk.start); | ||
} else { | ||
chunks.push({ | ||
start: section.start, | ||
size: section.data.byteLength | ||
}); | ||
} | ||
} | ||
for (let chunk of chunks) { | ||
chunk.data = new Uint8Array(chunk.size); | ||
for (let section of __privateGet(this, _sections)) { | ||
if (chunk.start <= section.start && section.start < chunk.start + chunk.size) { | ||
chunk.data.set(section.data, section.start - chunk.start); | ||
} | ||
} | ||
let isLastFlush = done && chunk === chunks[chunks.length - 1]; | ||
__privateGet(this, _onFlush).call(this, chunk.data, chunk.start, isLastFlush); | ||
} | ||
__privateGet(this, _sections).length = 0; | ||
} | ||
}; | ||
_sections = new WeakMap(); | ||
_onFlush = new WeakMap(); | ||
@@ -369,3 +441,3 @@ // src/main.ts | ||
var CLUSTER_SIZE_BYTES = 5; | ||
var _target, _options, _segment, _segmentInfo, _seekHead, _tracksElement, _segmentDuration, _colourElement, _videoCodecPrivate, _audioCodecPrivate, _cues, _currentCluster, _currentClusterTimestamp, _duration, _videoChunkQueue, _audioChunkQueue, _lastVideoTimestamp, _lastAudioTimestamp, _colorSpace, _finalized, _validateOptions, validateOptions_fn, _createFileHeader, createFileHeader_fn, _writeEBMLHeader, writeEBMLHeader_fn, _createSeekHead, createSeekHead_fn, _createSegmentInfo, createSegmentInfo_fn, _createTracks, createTracks_fn, _createSegment, createSegment_fn, _createCues, createCues_fn, _segmentDataOffset, segmentDataOffset_get, _writeVideoDecoderConfig, writeVideoDecoderConfig_fn, _fixVP9ColorSpace, fixVP9ColorSpace_fn, _createInternalChunk, createInternalChunk_fn, _writeSimpleBlock, writeSimpleBlock_fn, _writeCodecPrivate, writeCodecPrivate_fn, _createNewCluster, createNewCluster_fn, _finalizeCurrentCluster, finalizeCurrentCluster_fn, _ensureNotFinalized, ensureNotFinalized_fn; | ||
var _target, _options, _segment, _segmentInfo, _seekHead, _tracksElement, _segmentDuration, _colourElement, _videoCodecPrivate, _audioCodecPrivate, _cues, _currentCluster, _currentClusterTimestamp, _duration, _videoChunkQueue, _audioChunkQueue, _lastVideoTimestamp, _lastAudioTimestamp, _colorSpace, _finalized, _validateOptions, validateOptions_fn, _createFileHeader, createFileHeader_fn, _writeEBMLHeader, writeEBMLHeader_fn, _createSeekHead, createSeekHead_fn, _createSegmentInfo, createSegmentInfo_fn, _createTracks, createTracks_fn, _createSegment, createSegment_fn, _createCues, createCues_fn, _maybeFlushStreamingTarget, maybeFlushStreamingTarget_fn, _segmentDataOffset, segmentDataOffset_get, _writeVideoDecoderConfig, writeVideoDecoderConfig_fn, _fixVP9ColorSpace, fixVP9ColorSpace_fn, _createInternalChunk, createInternalChunk_fn, _writeSimpleBlock, writeSimpleBlock_fn, _writeCodecPrivate, writeCodecPrivate_fn, _createNewCluster, createNewCluster_fn, _finalizeCurrentCluster, finalizeCurrentCluster_fn, _ensureNotFinalized, ensureNotFinalized_fn; | ||
var WebMMuxer = class { | ||
@@ -381,2 +453,3 @@ constructor(options) { | ||
__privateAdd(this, _createCues); | ||
__privateAdd(this, _maybeFlushStreamingTarget); | ||
__privateAdd(this, _segmentDataOffset); | ||
@@ -415,4 +488,8 @@ __privateAdd(this, _writeVideoDecoderConfig); | ||
__privateSet(this, _target, new ArrayBufferWriteTarget()); | ||
} else if (options.target instanceof FileSystemWritableFileStream) { | ||
__privateSet(this, _target, new FileSystemWritableFileStreamWriteTarget(options.target)); | ||
} else if (typeof options.target === "function") { | ||
__privateSet(this, _target, new StreamingWriteTarget(options.target)); | ||
} else { | ||
__privateSet(this, _target, new FileSystemWritableFileStreamWriteTarget(options.target)); | ||
throw new Error(`Invalid target: ${options.target}`); | ||
} | ||
@@ -445,2 +522,3 @@ __privateMethod(this, _createFileHeader, createFileHeader_fn).call(this); | ||
} | ||
__privateMethod(this, _maybeFlushStreamingTarget, maybeFlushStreamingTarget_fn).call(this); | ||
} | ||
@@ -470,2 +548,3 @@ addAudioChunk(chunk, meta, timestamp) { | ||
} | ||
__privateMethod(this, _maybeFlushStreamingTarget, maybeFlushStreamingTarget_fn).call(this); | ||
} | ||
@@ -497,2 +576,4 @@ finalize() { | ||
__privateGet(this, _target).finalize(); | ||
} else if (__privateGet(this, _target) instanceof StreamingWriteTarget) { | ||
__privateGet(this, _target).flush(true); | ||
} | ||
@@ -536,2 +617,3 @@ return null; | ||
__privateMethod(this, _createCues, createCues_fn).call(this); | ||
__privateMethod(this, _maybeFlushStreamingTarget, maybeFlushStreamingTarget_fn).call(this); | ||
}; | ||
@@ -642,2 +724,8 @@ _writeEBMLHeader = new WeakSet(); | ||
}; | ||
_maybeFlushStreamingTarget = new WeakSet(); | ||
maybeFlushStreamingTarget_fn = function() { | ||
if (__privateGet(this, _target) instanceof StreamingWriteTarget) { | ||
__privateGet(this, _target).flush(false); | ||
} | ||
}; | ||
_segmentDataOffset = new WeakSet(); | ||
@@ -644,0 +732,0 @@ segmentDataOffset_get = function() { |
{ | ||
"name": "webm-muxer", | ||
"version": "1.2.0", | ||
"version": "1.2.1", | ||
"description": "WebM multiplexer in pure TypeScript with support for WebCodecs API, video & audio.", | ||
@@ -5,0 +5,0 @@ "main": "./build/webm-muxer.js", |
142
README.md
@@ -68,2 +68,3 @@ # webm-muxer - JavaScript WebM multiplexer | ||
## Usage | ||
### Initialization | ||
For each WebM file you wish to create, create an instance of `WebMMuxer` like so: | ||
@@ -76,9 +77,8 @@ ```js | ||
interface WebMMuxerOptions { | ||
// When 'buffer' is used, the muxed file is written to a buffer in | ||
// memory. When a FileSystemWritableFileStream acquired through | ||
// the File System Access API (see example below) is used, the | ||
// muxed file is written directly to disk, allowing for files way | ||
// larger than what would fit in RAM. | ||
target: 'buffer' | FileSystemWritableFileStream, | ||
type?: 'webm' | 'matroska', // Optional - see Details for more | ||
target: 'buffer' | ||
| ((data: Uint8Array, offset: number, done: boolean) => void) | ||
| FileSystemWritableFileStream | ||
type?: 'webm' | 'matroska', | ||
video?: { | ||
@@ -90,2 +90,3 @@ codec: string, | ||
}, | ||
audio?: { | ||
@@ -99,55 +100,75 @@ codec: string, | ||
``` | ||
Codecs supported by WebM are `V_VP8`, `V_VP9`, `V_AV1`, `A_OPUS` and `A_VORBIS`. | ||
Codecs officially supported by WebM are `V_VP8`, `V_VP9`, `V_AV1`, `A_OPUS` and `A_VORBIS`. | ||
#### `target` | ||
This option specifies what will happens with the data created by the muxer. The options are: | ||
- `'buffer'`: The file data will be written into a single, large buffer which is then returned by `finalize`. | ||
Some examples: | ||
```js | ||
// Create a muxer with a video track running the VP9 codec, and no | ||
// audio track. The muxed file is written to a buffer in memory. | ||
let muxer1 = new WebMMuxer({ | ||
target: 'buffer', | ||
video: { | ||
codec: 'V_VP9', | ||
width: 1280, | ||
height: 720 | ||
} | ||
}); | ||
```js | ||
let muxer = new WebMMuxer({ | ||
target: 'buffer', | ||
video: { | ||
codec: 'V_VP9', | ||
width: 1280, | ||
height: 720 | ||
} | ||
}); | ||
// Create a muxer with a video track running the VP9 codec, and an | ||
// audio track running the Opus codec. The muxed file is written | ||
// directly to a file on disk, using the File System Access API. | ||
let fileHandle = await window.showSaveFilePicker({ | ||
suggestedName: `video.webm`, | ||
types: [{ | ||
description: 'Video File', | ||
accept: { 'video/webm': ['.webm'] } | ||
}], | ||
}); | ||
let fileWritableStream = await fileHandle.createWritable(); | ||
let muxer2 = new WebMMuxer({ | ||
target: fileWritableStream, | ||
video: { | ||
codec: 'V_VP9', | ||
width: 1920, | ||
height: 1080, | ||
frameRate: 60 | ||
}, | ||
audio: { | ||
codec: 'A_OPUS', | ||
numberOfChannels: 2, | ||
sampleRate: 48000 | ||
} | ||
}); | ||
// ... | ||
// Create a muxer running only an Opus-coded audio track, and | ||
// no video. Writes to a buffer in memory. | ||
let muxer3 = new WebMMuxer({ | ||
target: 'buffer', | ||
audio: { | ||
codec: 'A_OPUS', | ||
numberOfChannels: 1, | ||
sampleRate: 44100 | ||
} | ||
}); | ||
``` | ||
let buffer = muxer.finalize(); | ||
``` | ||
- `function`: If the target is a function, it will be called each time data is output by the muxer - this is useful if | ||
you want to stream the data. The function will be called with three arguments: the data to write, the offset in | ||
bytes at which to write the data and a boolean indicating whether the muxer is done writing data. Note that the same | ||
segment of bytes might be written to multiple times and therefore you need to write the data in the same order the | ||
function gave it to you. | ||
```js | ||
let muxer = new WebMMuxer({ | ||
target: (data, offset, done) => { | ||
// Do something with the data | ||
}, | ||
audio: { | ||
codec: 'A_OPUS', | ||
numberOfChannels: 1, | ||
sampleRate: 44100 | ||
} | ||
}); | ||
``` | ||
- `FileSystemWritableFileStream`: When acquired through the File System Access API, the | ||
muxed file is written directly to disk, allowing for files way larger than what would fit in RAM. This functionality could also be manually emulated by passing a `function` instead, however, this library has some built-in write batching optimization which will be used when passing a FileSystemWritableFileStream. | ||
```js | ||
let fileHandle = await window.showSaveFilePicker({ | ||
suggestedName: `video.webm`, | ||
types: [{ | ||
description: 'Video File', | ||
accept: { 'video/webm': ['.webm'] } | ||
}], | ||
}); | ||
let fileWritableStream = await fileHandle.createWritable(); | ||
let muxer = new WebMMuxer({ | ||
target: fileWritableStream, | ||
video: { | ||
codec: 'V_VP9', | ||
width: 1920, | ||
height: 1080, | ||
frameRate: 60 | ||
}, | ||
audio: { | ||
codec: 'A_OPUS', | ||
numberOfChannels: 2, | ||
sampleRate: 48000 | ||
} | ||
}); | ||
``` | ||
#### `type` | ||
As WebM is a subset of the more general Matroska multimedia container format, this library muxes both WebM and Matroska | ||
files. WebM, according to the official specification, supports only a small subset of the codecs supported by Matroska. | ||
It is likely, however, that most players will successfully play back a WebM file with codecs other than the ones | ||
supported in the spec. To be on the safe side, however, you can set the `type` option to `'matroska'`, which | ||
will internally label the file as a general Matroska file. If you do this, your output file should also have the .mkv | ||
extension. | ||
### Muxing media chunks | ||
Then, with VideoEncoder and AudioEncoder set up, send encoded chunks to the muxer like so: | ||
@@ -190,2 +211,3 @@ ```js | ||
### Finishing up | ||
When encoding is finished, call `finalize` on the `WebMMuxer` instance to finalize the WebM file. When using | ||
@@ -219,10 +241,2 @@ `target: 'buffer'`, the resulting file's buffer is returned by this method: | ||
### WebM vs Matroska DocType | ||
As WebM is a subset of the more general Matroska multimedia container format, this library muxes both WebM and Matroska | ||
files. WebM, according to the official specification, supports only a small subset of the codecs supported by Matroska. | ||
It is likely, however, that most players will successfully play back a WebM file with codecs other than the ones | ||
supported in the spec. To be on the safe side, however, you can set the options' `type` property to `'matroska'`, which | ||
will internally label the file as a general Matroska file. If you do this, your output file should also have the .mkv | ||
extension. | ||
### Size "limits" | ||
@@ -229,0 +243,0 @@ This library can mux WebM files up to a total size of ~4398 GB and with a Matroska Cluster size of ~34 GB. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
57611
1028
248