webm-muxer
Advanced tools
Comparing version 2.2.3 to 3.0.0
/** | ||
* Describes the properties used to configure an instance of `WebMMuxer`. | ||
* Describes the properties used to configure an instance of `Muxer`. | ||
*/ | ||
declare interface WebMMuxerOptions { | ||
declare interface MuxerOptions<T extends Target> { | ||
/** | ||
* Specifies where the muxed WebM file is written to. | ||
* | ||
* When using `'buffer'`, the muxed file is simply written to a buffer in memory, which is then returned by the | ||
* muxer's `finalize` method. | ||
* | ||
* 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 | ||
* muxed. The benefit of this target is the ability to write out very large files, easily exceeding the RAM of the | ||
* machine being used. | ||
* Specifies what happens with the data created by the muxer. | ||
*/ | ||
target: 'buffer' | ((data: Uint8Array, offset: number, done: boolean) => void) | FileSystemWritableFileStream, | ||
target: T, | ||
@@ -105,71 +92,113 @@ /** | ||
declare global { | ||
declare type Target = ArrayBufferTarget | StreamTarget | FileSystemWritableFileStreamTarget; | ||
/** The file data will be written into a single large buffer, which is then stored in `buffer`. */ | ||
declare class ArrayBufferTarget { | ||
buffer: ArrayBuffer; | ||
} | ||
/** | ||
* This target defines callbacks that will get called whenever there is new data available - this is useful if | ||
* you want to stream the data, e.g. pipe it somewhere else. | ||
* | ||
* When using `chunked: true` in the options, data created by the muxer will first be accumulated and only written out | ||
* once it has reached sufficient size (~16 MB). This is useful for reducing the total amount of writes, at the cost of | ||
* latency. | ||
*/ | ||
declare class StreamTarget { | ||
constructor( | ||
onData: (data: Uint8Array, position: number) => void, | ||
onDone?: () => void, | ||
options?: { chunked?: true } | ||
); | ||
} | ||
/** | ||
* This is essentially a wrapper around `StreamTarget` with the intention of simplifying the use of this library with | ||
* the File System Access API. Writing the file directly to disk as it's being created comes with many benefits, such as | ||
* creating files way larger than the available RAM. | ||
*/ | ||
declare class FileSystemWritableFileStreamTarget { | ||
constructor(stream: FileSystemWritableFileStream); | ||
} | ||
/** | ||
* Used to multiplex video and audio chunks into a single WebM file. For each WebM file you want to create, create | ||
* one instance of `Muxer`. | ||
*/ | ||
declare class Muxer<T extends Target> { | ||
target: T; | ||
/** | ||
* Used to multiplex video and audio chunks into a single WebM file. For each WebM file you want to create, create | ||
* one instance of `WebMMuxer`. | ||
* Creates a new instance of `Muxer`. | ||
* @param options Specifies configuration and metadata for the WebM file. | ||
*/ | ||
class WebMMuxer { | ||
/** | ||
* Creates a new instance of `WebMMuxer`. | ||
* @param options Specifies configuration and metadata for the WebM file. | ||
*/ | ||
constructor(options: WebMMuxerOptions); | ||
constructor(options: MuxerOptions<T>); | ||
/** | ||
* Adds a new, encoded video chunk to the WebM file. | ||
* @param chunk The encoded video chunk. Can be obtained through a `VideoEncoder`. | ||
* @param meta The metadata about the encoded video, also provided by `VideoEncoder`. | ||
* @param timestamp Optionally, the timestamp to use for the video chunk. When not provided, it will use the one | ||
* specified in `chunk`. | ||
*/ | ||
addVideoChunk(chunk: EncodedVideoChunk, meta: EncodedVideoChunkMetadata, timestamp?: number): void; | ||
/** | ||
* Adds a new, encoded audio chunk to the WebM file. | ||
* @param chunk The encoded audio chunk. Can be obtained through an `AudioEncoder`. | ||
* @param meta The metadata about the encoded audio, also provided by `AudioEncoder`. | ||
* @param timestamp Optionally, the timestamp to use for the audio chunk. When not provided, it will use the one | ||
* specified in `chunk`. | ||
*/ | ||
addAudioChunk(chunk: EncodedAudioChunk, meta: EncodedAudioChunkMetadata, timestamp?: number): void; | ||
/** | ||
* Adds a new, encoded video chunk to the WebM file. | ||
* @param chunk The encoded video chunk. Can be obtained through a `VideoEncoder`. | ||
* @param meta The metadata about the encoded video, also provided by `VideoEncoder`. | ||
* @param timestamp Optionally, the timestamp to use for the video chunk. When not provided, it will use the one | ||
* specified in `chunk`. | ||
*/ | ||
addVideoChunk(chunk: EncodedVideoChunk, meta: EncodedVideoChunkMetadata, timestamp?: number): void; | ||
/** | ||
* Adds a new, encoded audio chunk to the WebM file. | ||
* @param chunk The encoded audio chunk. Can be obtained through an `AudioEncoder`. | ||
* @param meta The metadata about the encoded audio, also provided by `AudioEncoder`. | ||
* @param timestamp Optionally, the timestamp to use for the audio chunk. When not provided, it will use the one | ||
* specified in `chunk`. | ||
*/ | ||
addAudioChunk(chunk: EncodedAudioChunk, meta: EncodedAudioChunkMetadata, timestamp?: number): void; | ||
/** | ||
* Adds a raw video chunk to the WebM file. This method should be used when the encoded video is not obtained | ||
* through a `VideoEncoder` but through some other means, where no instance of `EncodedVideoChunk`is available. | ||
* @param data The raw data of the video chunk. | ||
* @param type Whether the video chunk is a keyframe or delta frame. | ||
* @param timestamp The timestamp of the video chunk. | ||
* @param meta Optionally, any encoder metadata. | ||
*/ | ||
addVideoChunkRaw( | ||
data: Uint8Array, | ||
type: 'key' | 'delta', | ||
timestamp: number, | ||
meta?: EncodedVideoChunkMetadata | ||
): void; | ||
/** | ||
* Adds a raw audio chunk to the WebM file. This method should be used when the encoded audio is not obtained | ||
* through an `AudioEncoder` but through some other means, where no instance of `EncodedAudioChunk`is available. | ||
* @param data The raw data of the audio chunk. | ||
* @param type Whether the audio chunk is a keyframe or delta frame. | ||
* @param timestamp The timestamp of the audio chunk. | ||
* @param meta Optionally, any encoder metadata. | ||
*/ | ||
addAudioChunkRaw( | ||
data: Uint8Array, | ||
type: 'key' | 'delta', | ||
timestamp: number, | ||
meta?: EncodedAudioChunkMetadata | ||
): void; | ||
/** | ||
* Adds a raw video chunk to the WebM file. This method should be used when the encoded video is not obtained | ||
* through a `VideoEncoder` but through some other means, where no instance of `EncodedVideoChunk`is available. | ||
* @param data The raw data of the video chunk. | ||
* @param type Whether the video chunk is a keyframe or delta frame. | ||
* @param timestamp The timestamp of the video chunk. | ||
* @param meta Optionally, any encoder metadata. | ||
*/ | ||
addVideoChunkRaw( | ||
data: Uint8Array, | ||
type: 'key' | 'delta', | ||
timestamp: number, | ||
meta?: EncodedVideoChunkMetadata | ||
): void; | ||
/** | ||
* Adds a raw audio chunk to the WebM file. This method should be used when the encoded audio is not obtained | ||
* through an `AudioEncoder` but through some other means, where no instance of `EncodedAudioChunk`is available. | ||
* @param data The raw data of the audio chunk. | ||
* @param type Whether the audio chunk is a keyframe or delta frame. | ||
* @param timestamp The timestamp of the audio chunk. | ||
* @param meta Optionally, any encoder metadata. | ||
*/ | ||
addAudioChunkRaw( | ||
data: Uint8Array, | ||
type: 'key' | 'delta', | ||
timestamp: number, | ||
meta?: EncodedAudioChunkMetadata | ||
): void; | ||
/** | ||
* Is to be called after all media chunks have been added to the muxer. Make sure to call and await the `flush` | ||
* method on your `VideoEncoder` and/or `AudioEncoder` before calling this method to ensure all encoding has | ||
* finished. This method will then finish up the writing process of the WebM file. | ||
* @returns Should you have used `target: 'buffer'` in the configuration options, this method will return the | ||
* buffer containing the final WebM file. | ||
*/ | ||
finalize(): ArrayBuffer | null; | ||
} | ||
/** | ||
* Is to be called after all media chunks have been added to the muxer. Make sure to call and await the `flush` | ||
* method on your `VideoEncoder` and/or `AudioEncoder` before calling this method to ensure all encoding has | ||
* finished. This method will then finish up the writing process of the WebM file. | ||
* @returns Should you have used `target: 'buffer'` in the configuration options, this method will return the | ||
* buffer containing the final WebM file. | ||
*/ | ||
finalize(): ArrayBuffer | null; | ||
} | ||
export = WebMMuxer; | ||
declare namespace WebMMuxer { | ||
export { Muxer, ArrayBufferTarget, StreamTarget, FileSystemWritableFileStreamTarget }; | ||
} | ||
declare global { | ||
let WebMMuxer: typeof WebMMuxer; | ||
} | ||
export { Muxer, ArrayBufferTarget, StreamTarget, FileSystemWritableFileStreamTarget }; | ||
export as namespace WebMMuxer; | ||
export default WebMMuxer; |
@@ -58,6 +58,9 @@ "use strict"; | ||
// src/main.ts | ||
var main_exports = {}; | ||
__export(main_exports, { | ||
default: () => main_default | ||
// src/index.ts | ||
var src_exports = {}; | ||
__export(src_exports, { | ||
ArrayBufferTarget: () => ArrayBufferTarget, | ||
FileSystemWritableFileStreamTarget: () => FileSystemWritableFileStreamTarget, | ||
Muxer: () => Muxer, | ||
StreamTarget: () => StreamTarget | ||
}); | ||
@@ -109,5 +112,48 @@ | ||
// src/write_target.ts | ||
// src/misc.ts | ||
var readBits = (bytes, start, end) => { | ||
let result = 0; | ||
for (let i = start; i < end; i++) { | ||
let byteIndex = Math.floor(i / 8); | ||
let byte = bytes[byteIndex]; | ||
let bitIndex = 7 - (i & 7); | ||
let bit = (byte & 1 << bitIndex) >> bitIndex; | ||
result <<= 1; | ||
result |= bit; | ||
} | ||
return result; | ||
}; | ||
var writeBits = (bytes, start, end, value) => { | ||
for (let i = start; i < end; i++) { | ||
let byteIndex = Math.floor(i / 8); | ||
let byte = bytes[byteIndex]; | ||
let bitIndex = 7 - (i & 7); | ||
byte &= ~(1 << bitIndex); | ||
byte |= (value & 1 << end - i - 1) >> end - i - 1 << bitIndex; | ||
bytes[byteIndex] = byte; | ||
} | ||
}; | ||
// src/target.ts | ||
var ArrayBufferTarget = class { | ||
constructor() { | ||
this.buffer = null; | ||
} | ||
}; | ||
var StreamTarget = class { | ||
constructor(onData, onDone, options) { | ||
this.onData = onData; | ||
this.onDone = onDone; | ||
this.options = options; | ||
} | ||
}; | ||
var FileSystemWritableFileStreamTarget = class { | ||
constructor(stream) { | ||
this.stream = stream; | ||
} | ||
}; | ||
// src/writer.ts | ||
var _helper, _helperView, _writeByte, writeByte_fn, _writeFloat32, writeFloat32_fn, _writeFloat64, writeFloat64_fn, _writeUnsignedInt, writeUnsignedInt_fn, _writeString, writeString_fn; | ||
var WriteTarget = class { | ||
var Writer = class { | ||
constructor() { | ||
@@ -120,6 +166,6 @@ __privateAdd(this, _writeByte); | ||
this.pos = 0; | ||
__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(); | ||
__privateAdd(this, _helper, new Uint8Array(8)); | ||
__privateAdd(this, _helperView, new DataView(__privateGet(this, _helper).buffer)); | ||
} | ||
@@ -264,23 +310,14 @@ seek(newPos) { | ||
}; | ||
var _buffer, _bytes; | ||
var ArrayBufferWriteTarget = class extends WriteTarget { | ||
constructor() { | ||
var _target, _buffer, _bytes, _ensureSize, ensureSize_fn; | ||
var ArrayBufferTargetWriter = class extends Writer { | ||
constructor(target) { | ||
super(); | ||
__privateAdd(this, _ensureSize); | ||
__privateAdd(this, _target, void 0); | ||
__privateAdd(this, _buffer, new ArrayBuffer(__pow(2, 16))); | ||
__privateAdd(this, _bytes, new Uint8Array(__privateGet(this, _buffer))); | ||
__privateSet(this, _target, target); | ||
} | ||
ensureSize(size) { | ||
let newLength = __privateGet(this, _buffer).byteLength; | ||
while (newLength < size) | ||
newLength *= 2; | ||
if (newLength === __privateGet(this, _buffer).byteLength) | ||
return; | ||
let newBuffer = new ArrayBuffer(newLength); | ||
let newBytes = new Uint8Array(newBuffer); | ||
newBytes.set(__privateGet(this, _bytes), 0); | ||
__privateSet(this, _buffer, newBuffer); | ||
__privateSet(this, _bytes, newBytes); | ||
} | ||
write(data) { | ||
this.ensureSize(this.pos + data.byteLength); | ||
__privateMethod(this, _ensureSize, ensureSize_fn).call(this, this.pos + data.byteLength); | ||
__privateGet(this, _bytes).set(data, this.pos); | ||
@@ -290,112 +327,31 @@ this.pos += data.byteLength; | ||
finalize() { | ||
this.ensureSize(this.pos); | ||
return __privateGet(this, _buffer).slice(0, this.pos); | ||
__privateMethod(this, _ensureSize, ensureSize_fn).call(this, this.pos); | ||
__privateGet(this, _target).buffer = __privateGet(this, _buffer).slice(0, this.pos); | ||
} | ||
}; | ||
_target = new WeakMap(); | ||
_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(); | ||
__privateAdd(this, _stream, void 0); | ||
__privateAdd(this, _chunks, []); | ||
__privateSet(this, _stream, stream); | ||
} | ||
write(data) { | ||
this.writeDataIntoChunks(data, this.pos); | ||
this.flushChunks(); | ||
this.pos += data.byteLength; | ||
} | ||
writeDataIntoChunks(data, position) { | ||
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 = __privateGet(this, _chunks)[chunkIndex]; | ||
let relativePosition = position - chunk.start; | ||
let toWrite = data.subarray(0, Math.min(FILE_CHUNK_SIZE - relativePosition, data.byteLength)); | ||
chunk.data.set(toWrite, relativePosition); | ||
let section = { | ||
start: relativePosition, | ||
end: relativePosition + toWrite.byteLength | ||
}; | ||
insertSectionIntoFileChunk(chunk, section); | ||
if (chunk.written[0].start === 0 && chunk.written[0].end === FILE_CHUNK_SIZE) { | ||
chunk.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; | ||
} | ||
this.flushChunks(); | ||
} | ||
if (toWrite.byteLength < data.byteLength) { | ||
this.writeDataIntoChunks(data.subarray(toWrite.byteLength), position + toWrite.byteLength); | ||
} | ||
} | ||
createChunk(includesPosition) { | ||
let start = Math.floor(includesPosition / FILE_CHUNK_SIZE) * FILE_CHUNK_SIZE; | ||
let chunk = { | ||
start, | ||
data: new Uint8Array(FILE_CHUNK_SIZE), | ||
written: [], | ||
shouldFlush: false | ||
}; | ||
__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 < __privateGet(this, _chunks).length; i++) { | ||
let chunk = __privateGet(this, _chunks)[i]; | ||
if (!chunk.shouldFlush && !force) | ||
continue; | ||
for (let section of chunk.written) { | ||
__privateGet(this, _stream).write({ | ||
type: "write", | ||
data: chunk.data.subarray(section.start, section.end), | ||
position: chunk.start + section.start | ||
}); | ||
} | ||
__privateGet(this, _chunks).splice(i--, 1); | ||
} | ||
} | ||
finalize() { | ||
this.flushChunks(true); | ||
} | ||
_ensureSize = new WeakSet(); | ||
ensureSize_fn = function(size) { | ||
let newLength = __privateGet(this, _buffer).byteLength; | ||
while (newLength < size) | ||
newLength *= 2; | ||
if (newLength === __privateGet(this, _buffer).byteLength) | ||
return; | ||
let newBuffer = new ArrayBuffer(newLength); | ||
let newBytes = new Uint8Array(newBuffer); | ||
newBytes.set(__privateGet(this, _bytes), 0); | ||
__privateSet(this, _buffer, newBuffer); | ||
__privateSet(this, _bytes, newBytes); | ||
}; | ||
_stream = new WeakMap(); | ||
_chunks = new WeakMap(); | ||
var insertSectionIntoFileChunk = (chunk, section) => { | ||
let low = 0; | ||
let high = chunk.written.length - 1; | ||
let index = -1; | ||
while (low <= high) { | ||
let mid = Math.floor(low + (high - low + 1) / 2); | ||
if (chunk.written[mid].start <= section.start) { | ||
low = mid + 1; | ||
index = mid; | ||
} else { | ||
high = mid - 1; | ||
} | ||
} | ||
chunk.written.splice(index + 1, 0, section); | ||
if (index === -1 || chunk.written[index].end < section.start) | ||
index++; | ||
while (index < chunk.written.length - 1 && chunk.written[index].end >= chunk.written[index + 1].start) { | ||
chunk.written[index].end = Math.max(chunk.written[index].end, chunk.written[index + 1].end); | ||
chunk.written.splice(index + 1, 1); | ||
} | ||
}; | ||
var _sections, _onFlush, _lastFlushEnd, _ensureMonotonicity; | ||
var StreamingWriteTarget = class extends WriteTarget { | ||
constructor(onFlush, ensureMonotonicity) { | ||
var _target2, _sections, _lastFlushEnd, _ensureMonotonicity; | ||
var StreamTargetWriter = class extends Writer { | ||
constructor(target, ensureMonotonicity) { | ||
super(); | ||
__privateAdd(this, _target2, void 0); | ||
__privateAdd(this, _sections, []); | ||
__privateAdd(this, _onFlush, void 0); | ||
__privateAdd(this, _lastFlushEnd, 0); | ||
__privateAdd(this, _ensureMonotonicity, void 0); | ||
__privateSet(this, _onFlush, onFlush); | ||
__privateSet(this, _target2, target); | ||
__privateSet(this, _ensureMonotonicity, ensureMonotonicity); | ||
@@ -410,3 +366,3 @@ } | ||
} | ||
flush(done) { | ||
flush() { | ||
if (__privateGet(this, _sections).length === 0) | ||
@@ -442,4 +398,3 @@ return; | ||
} | ||
let isLastFlush = done && chunk === chunks[chunks.length - 1]; | ||
__privateGet(this, _onFlush).call(this, chunk.data, chunk.start, isLastFlush); | ||
__privateGet(this, _target2).onData(chunk.data, chunk.start); | ||
__privateSet(this, _lastFlushEnd, chunk.start + chunk.data.byteLength); | ||
@@ -449,9 +404,137 @@ } | ||
} | ||
finalize() { | ||
var _a, _b; | ||
(_b = (_a = __privateGet(this, _target2)).onDone) == null ? void 0 : _b.call(_a); | ||
} | ||
}; | ||
_target2 = new WeakMap(); | ||
_sections = new WeakMap(); | ||
_onFlush = new WeakMap(); | ||
_lastFlushEnd = new WeakMap(); | ||
_ensureMonotonicity = new WeakMap(); | ||
var CHUNK_SIZE = __pow(2, 24); | ||
var MAX_CHUNKS_AT_ONCE = 2; | ||
var _target3, _chunks, _lastFlushEnd2, _ensureMonotonicity2, _writeDataIntoChunks, writeDataIntoChunks_fn, _insertSectionIntoChunk, insertSectionIntoChunk_fn, _createChunk, createChunk_fn, _flushChunks, flushChunks_fn; | ||
var ChunkedStreamTargetWriter = class extends Writer { | ||
constructor(target, ensureMonotonicity) { | ||
super(); | ||
__privateAdd(this, _writeDataIntoChunks); | ||
__privateAdd(this, _insertSectionIntoChunk); | ||
__privateAdd(this, _createChunk); | ||
__privateAdd(this, _flushChunks); | ||
__privateAdd(this, _target3, void 0); | ||
__privateAdd(this, _chunks, []); | ||
__privateAdd(this, _lastFlushEnd2, 0); | ||
__privateAdd(this, _ensureMonotonicity2, void 0); | ||
__privateSet(this, _target3, target); | ||
__privateSet(this, _ensureMonotonicity2, ensureMonotonicity); | ||
} | ||
write(data) { | ||
__privateMethod(this, _writeDataIntoChunks, writeDataIntoChunks_fn).call(this, data, this.pos); | ||
__privateMethod(this, _flushChunks, flushChunks_fn).call(this); | ||
this.pos += data.byteLength; | ||
} | ||
finalize() { | ||
var _a, _b; | ||
__privateMethod(this, _flushChunks, flushChunks_fn).call(this, true); | ||
(_b = (_a = __privateGet(this, _target3)).onDone) == null ? void 0 : _b.call(_a); | ||
} | ||
}; | ||
_target3 = new WeakMap(); | ||
_chunks = new WeakMap(); | ||
_lastFlushEnd2 = new WeakMap(); | ||
_ensureMonotonicity2 = new WeakMap(); | ||
_writeDataIntoChunks = new WeakSet(); | ||
writeDataIntoChunks_fn = function(data, position) { | ||
let chunkIndex = __privateGet(this, _chunks).findIndex((x) => x.start <= position && position < x.start + CHUNK_SIZE); | ||
if (chunkIndex === -1) | ||
chunkIndex = __privateMethod(this, _createChunk, createChunk_fn).call(this, position); | ||
let chunk = __privateGet(this, _chunks)[chunkIndex]; | ||
let relativePosition = position - chunk.start; | ||
let toWrite = data.subarray(0, Math.min(CHUNK_SIZE - relativePosition, data.byteLength)); | ||
chunk.data.set(toWrite, relativePosition); | ||
let section = { | ||
start: relativePosition, | ||
end: relativePosition + toWrite.byteLength | ||
}; | ||
__privateMethod(this, _insertSectionIntoChunk, insertSectionIntoChunk_fn).call(this, chunk, section); | ||
if (chunk.written[0].start === 0 && chunk.written[0].end === CHUNK_SIZE) { | ||
chunk.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; | ||
} | ||
__privateMethod(this, _flushChunks, flushChunks_fn).call(this); | ||
} | ||
if (toWrite.byteLength < data.byteLength) { | ||
__privateMethod(this, _writeDataIntoChunks, writeDataIntoChunks_fn).call(this, data.subarray(toWrite.byteLength), position + toWrite.byteLength); | ||
} | ||
}; | ||
_insertSectionIntoChunk = new WeakSet(); | ||
insertSectionIntoChunk_fn = function(chunk, section) { | ||
let low = 0; | ||
let high = chunk.written.length - 1; | ||
let index = -1; | ||
while (low <= high) { | ||
let mid = Math.floor(low + (high - low + 1) / 2); | ||
if (chunk.written[mid].start <= section.start) { | ||
low = mid + 1; | ||
index = mid; | ||
} else { | ||
high = mid - 1; | ||
} | ||
} | ||
chunk.written.splice(index + 1, 0, section); | ||
if (index === -1 || chunk.written[index].end < section.start) | ||
index++; | ||
while (index < chunk.written.length - 1 && chunk.written[index].end >= chunk.written[index + 1].start) { | ||
chunk.written[index].end = Math.max(chunk.written[index].end, chunk.written[index + 1].end); | ||
chunk.written.splice(index + 1, 1); | ||
} | ||
}; | ||
_createChunk = new WeakSet(); | ||
createChunk_fn = function(includesPosition) { | ||
let start = Math.floor(includesPosition / CHUNK_SIZE) * CHUNK_SIZE; | ||
let chunk = { | ||
start, | ||
data: new Uint8Array(CHUNK_SIZE), | ||
written: [], | ||
shouldFlush: false | ||
}; | ||
__privateGet(this, _chunks).push(chunk); | ||
__privateGet(this, _chunks).sort((a, b) => a.start - b.start); | ||
return __privateGet(this, _chunks).indexOf(chunk); | ||
}; | ||
_flushChunks = new WeakSet(); | ||
flushChunks_fn = function(force = false) { | ||
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) { | ||
if (__privateGet(this, _ensureMonotonicity2) && chunk.start + section.start < __privateGet(this, _lastFlushEnd2)) { | ||
throw new Error("Internal error: Monotonicity violation."); | ||
} | ||
__privateGet(this, _target3).onData( | ||
chunk.data.subarray(section.start, section.end), | ||
chunk.start + section.start | ||
); | ||
__privateSet(this, _lastFlushEnd2, chunk.start + section.end); | ||
} | ||
__privateGet(this, _chunks).splice(i--, 1); | ||
} | ||
}; | ||
var FileSystemWritableFileStreamTargetWriter = class extends ChunkedStreamTargetWriter { | ||
constructor(target, ensureMonotonicity) { | ||
super(new StreamTarget( | ||
(data, position) => target.stream.write({ | ||
type: "write", | ||
data, | ||
position | ||
}) | ||
), ensureMonotonicity); | ||
} | ||
}; | ||
// src/main.ts | ||
// src/muxer.ts | ||
var VIDEO_TRACK_NUMBER = 1; | ||
@@ -467,4 +550,4 @@ var AUDIO_TRACK_NUMBER = 2; | ||
var FIRST_TIMESTAMP_BEHAVIORS = ["strict", "offset", "permissive"]; | ||
var _target, _options, _segment, _segmentInfo, _seekHead, _tracksElement, _segmentDuration, _colourElement, _videoCodecPrivate, _audioCodecPrivate, _cues, _currentCluster, _currentClusterTimestamp, _duration, _videoChunkQueue, _audioChunkQueue, _firstVideoTimestamp, _firstAudioTimestamp, _lastVideoTimestamp, _lastAudioTimestamp, _colorSpace, _finalized, _validateOptions, validateOptions_fn, _createFileHeader, createFileHeader_fn, _writeEBMLHeader, writeEBMLHeader_fn, _createCodecPrivatePlaceholders, createCodecPrivatePlaceholders_fn, _createColourElement, createColourElement_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, _validateTimestamp, validateTimestamp_fn, _writeSimpleBlock, writeSimpleBlock_fn, _createCodecPrivateElement, createCodecPrivateElement_fn, _writeCodecPrivate, writeCodecPrivate_fn, _createNewCluster, createNewCluster_fn, _finalizeCurrentCluster, finalizeCurrentCluster_fn, _ensureNotFinalized, ensureNotFinalized_fn; | ||
var WebMMuxer = class { | ||
var _options, _writer, _segment, _segmentInfo, _seekHead, _tracksElement, _segmentDuration, _colourElement, _videoCodecPrivate, _audioCodecPrivate, _cues, _currentCluster, _currentClusterTimestamp, _duration, _videoChunkQueue, _audioChunkQueue, _firstVideoTimestamp, _firstAudioTimestamp, _lastVideoTimestamp, _lastAudioTimestamp, _colorSpace, _finalized, _validateOptions, validateOptions_fn, _createFileHeader, createFileHeader_fn, _writeEBMLHeader, writeEBMLHeader_fn, _createCodecPrivatePlaceholders, createCodecPrivatePlaceholders_fn, _createColourElement, createColourElement_fn, _createSeekHead, createSeekHead_fn, _createSegmentInfo, createSegmentInfo_fn, _createTracks, createTracks_fn, _createSegment, createSegment_fn, _createCues, createCues_fn, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn, _segmentDataOffset, segmentDataOffset_get, _writeVideoDecoderConfig, writeVideoDecoderConfig_fn, _fixVP9ColorSpace, fixVP9ColorSpace_fn, _createInternalChunk, createInternalChunk_fn, _validateTimestamp, validateTimestamp_fn, _writeSimpleBlock, writeSimpleBlock_fn, _createCodecPrivateElement, createCodecPrivateElement_fn, _writeCodecPrivate, writeCodecPrivate_fn, _createNewCluster, createNewCluster_fn, _finalizeCurrentCluster, finalizeCurrentCluster_fn, _ensureNotFinalized, ensureNotFinalized_fn; | ||
var Muxer = class { | ||
constructor(options) { | ||
@@ -481,3 +564,3 @@ __privateAdd(this, _validateOptions); | ||
__privateAdd(this, _createCues); | ||
__privateAdd(this, _maybeFlushStreamingTarget); | ||
__privateAdd(this, _maybeFlushStreamingTargetWriter); | ||
__privateAdd(this, _segmentDataOffset); | ||
@@ -494,4 +577,4 @@ __privateAdd(this, _writeVideoDecoderConfig); | ||
__privateAdd(this, _ensureNotFinalized); | ||
__privateAdd(this, _target, void 0); | ||
__privateAdd(this, _options, void 0); | ||
__privateAdd(this, _writer, void 0); | ||
__privateAdd(this, _segment, void 0); | ||
@@ -517,2 +600,3 @@ __privateAdd(this, _segmentInfo, void 0); | ||
__privateAdd(this, _finalized, false); | ||
var _a; | ||
__privateMethod(this, _validateOptions, validateOptions_fn).call(this, options); | ||
@@ -523,8 +607,10 @@ __privateSet(this, _options, __spreadValues({ | ||
}, options)); | ||
if (options.target === "buffer") { | ||
__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, !!options.streaming)); | ||
this.target = options.target; | ||
let ensureMonotonicity = !!__privateGet(this, _options).streaming; | ||
if (options.target instanceof ArrayBufferTarget) { | ||
__privateSet(this, _writer, new ArrayBufferTargetWriter(options.target)); | ||
} else if (options.target instanceof StreamTarget) { | ||
__privateSet(this, _writer, ((_a = options.target.options) == null ? void 0 : _a.chunked) ? new ChunkedStreamTargetWriter(options.target, ensureMonotonicity) : new StreamTargetWriter(options.target, ensureMonotonicity)); | ||
} else if (options.target instanceof FileSystemWritableFileStreamTarget) { | ||
__privateSet(this, _writer, new FileSystemWritableFileStreamTargetWriter(options.target, ensureMonotonicity)); | ||
} else { | ||
@@ -561,3 +647,3 @@ throw new Error(`Invalid target: ${options.target}`); | ||
} | ||
__privateMethod(this, _maybeFlushStreamingTarget, maybeFlushStreamingTarget_fn).call(this); | ||
__privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this); | ||
} | ||
@@ -593,3 +679,3 @@ addAudioChunk(chunk, meta, timestamp) { | ||
} | ||
__privateMethod(this, _maybeFlushStreamingTarget, maybeFlushStreamingTarget_fn).call(this); | ||
__privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this); | ||
} | ||
@@ -604,31 +690,25 @@ finalize() { | ||
} | ||
__privateGet(this, _target).writeEBML(__privateGet(this, _cues)); | ||
__privateGet(this, _writer).writeEBML(__privateGet(this, _cues)); | ||
if (!__privateGet(this, _options).streaming) { | ||
let endPos = __privateGet(this, _target).pos; | ||
let segmentSize = __privateGet(this, _target).pos - __privateGet(this, _segmentDataOffset, segmentDataOffset_get); | ||
__privateGet(this, _target).seek(__privateGet(this, _target).offsets.get(__privateGet(this, _segment)) + 4); | ||
__privateGet(this, _target).writeEBMLVarInt(segmentSize, SEGMENT_SIZE_BYTES); | ||
let endPos = __privateGet(this, _writer).pos; | ||
let segmentSize = __privateGet(this, _writer).pos - __privateGet(this, _segmentDataOffset, segmentDataOffset_get); | ||
__privateGet(this, _writer).seek(__privateGet(this, _writer).offsets.get(__privateGet(this, _segment)) + 4); | ||
__privateGet(this, _writer).writeEBMLVarInt(segmentSize, SEGMENT_SIZE_BYTES); | ||
__privateGet(this, _segmentDuration).data = new EBMLFloat64(__privateGet(this, _duration)); | ||
__privateGet(this, _target).seek(__privateGet(this, _target).offsets.get(__privateGet(this, _segmentDuration))); | ||
__privateGet(this, _target).writeEBML(__privateGet(this, _segmentDuration)); | ||
__privateGet(this, _seekHead).data[0].data[1].data = __privateGet(this, _target).offsets.get(__privateGet(this, _cues)) - __privateGet(this, _segmentDataOffset, segmentDataOffset_get); | ||
__privateGet(this, _seekHead).data[1].data[1].data = __privateGet(this, _target).offsets.get(__privateGet(this, _segmentInfo)) - __privateGet(this, _segmentDataOffset, segmentDataOffset_get); | ||
__privateGet(this, _seekHead).data[2].data[1].data = __privateGet(this, _target).offsets.get(__privateGet(this, _tracksElement)) - __privateGet(this, _segmentDataOffset, segmentDataOffset_get); | ||
__privateGet(this, _target).seek(__privateGet(this, _target).offsets.get(__privateGet(this, _seekHead))); | ||
__privateGet(this, _target).writeEBML(__privateGet(this, _seekHead)); | ||
__privateGet(this, _target).seek(endPos); | ||
__privateGet(this, _writer).seek(__privateGet(this, _writer).offsets.get(__privateGet(this, _segmentDuration))); | ||
__privateGet(this, _writer).writeEBML(__privateGet(this, _segmentDuration)); | ||
__privateGet(this, _seekHead).data[0].data[1].data = __privateGet(this, _writer).offsets.get(__privateGet(this, _cues)) - __privateGet(this, _segmentDataOffset, segmentDataOffset_get); | ||
__privateGet(this, _seekHead).data[1].data[1].data = __privateGet(this, _writer).offsets.get(__privateGet(this, _segmentInfo)) - __privateGet(this, _segmentDataOffset, segmentDataOffset_get); | ||
__privateGet(this, _seekHead).data[2].data[1].data = __privateGet(this, _writer).offsets.get(__privateGet(this, _tracksElement)) - __privateGet(this, _segmentDataOffset, segmentDataOffset_get); | ||
__privateGet(this, _writer).seek(__privateGet(this, _writer).offsets.get(__privateGet(this, _seekHead))); | ||
__privateGet(this, _writer).writeEBML(__privateGet(this, _seekHead)); | ||
__privateGet(this, _writer).seek(endPos); | ||
} | ||
__privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this); | ||
__privateGet(this, _writer).finalize(); | ||
__privateSet(this, _finalized, true); | ||
if (__privateGet(this, _target) instanceof ArrayBufferWriteTarget) { | ||
return __privateGet(this, _target).finalize(); | ||
} else if (__privateGet(this, _target) instanceof FileSystemWritableFileStreamWriteTarget) { | ||
__privateGet(this, _target).finalize(); | ||
} else if (__privateGet(this, _target) instanceof StreamingWriteTarget) { | ||
__privateGet(this, _target).flush(true); | ||
} | ||
return null; | ||
} | ||
}; | ||
_target = new WeakMap(); | ||
_options = new WeakMap(); | ||
_writer = new WeakMap(); | ||
_segment = new WeakMap(); | ||
@@ -678,3 +758,3 @@ _segmentInfo = new WeakMap(); | ||
__privateMethod(this, _createCues, createCues_fn).call(this); | ||
__privateMethod(this, _maybeFlushStreamingTarget, maybeFlushStreamingTarget_fn).call(this); | ||
__privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this); | ||
}; | ||
@@ -693,3 +773,3 @@ _writeEBMLHeader = new WeakSet(); | ||
] }; | ||
__privateGet(this, _target).writeEBML(ebmlHeader); | ||
__privateGet(this, _writer).writeEBML(ebmlHeader); | ||
}; | ||
@@ -791,3 +871,3 @@ _createCodecPrivatePlaceholders = new WeakSet(); | ||
__privateSet(this, _segment, segment); | ||
__privateGet(this, _target).writeEBML(segment); | ||
__privateGet(this, _writer).writeEBML(segment); | ||
}; | ||
@@ -798,6 +878,6 @@ _createCues = new WeakSet(); | ||
}; | ||
_maybeFlushStreamingTarget = new WeakSet(); | ||
maybeFlushStreamingTarget_fn = function() { | ||
if (__privateGet(this, _target) instanceof StreamingWriteTarget) { | ||
__privateGet(this, _target).flush(false); | ||
_maybeFlushStreamingTargetWriter = new WeakSet(); | ||
maybeFlushStreamingTargetWriter_fn = function() { | ||
if (__privateGet(this, _writer) instanceof StreamTargetWriter) { | ||
__privateGet(this, _writer).flush(); | ||
} | ||
@@ -807,3 +887,3 @@ }; | ||
segmentDataOffset_get = function() { | ||
return __privateGet(this, _target).dataOffsets.get(__privateGet(this, _segment)); | ||
return __privateGet(this, _writer).dataOffsets.get(__privateGet(this, _segment)); | ||
}; | ||
@@ -837,6 +917,6 @@ _writeVideoDecoderConfig = new WeakSet(); | ||
if (!__privateGet(this, _options).streaming) { | ||
let endPos = __privateGet(this, _target).pos; | ||
__privateGet(this, _target).seek(__privateGet(this, _target).offsets.get(__privateGet(this, _colourElement))); | ||
__privateGet(this, _target).writeEBML(__privateGet(this, _colourElement)); | ||
__privateGet(this, _target).seek(endPos); | ||
let endPos = __privateGet(this, _writer).pos; | ||
__privateGet(this, _writer).seek(__privateGet(this, _writer).offsets.get(__privateGet(this, _colourElement))); | ||
__privateGet(this, _writer).writeEBML(__privateGet(this, _colourElement)); | ||
__privateGet(this, _writer).seek(endPos); | ||
} | ||
@@ -948,3 +1028,3 @@ } | ||
] }; | ||
__privateGet(this, _target).writeEBML(simpleBlock); | ||
__privateGet(this, _writer).writeEBML(simpleBlock); | ||
__privateSet(this, _duration, Math.max(__privateGet(this, _duration), msTime)); | ||
@@ -958,4 +1038,4 @@ }; | ||
writeCodecPrivate_fn = function(element, data) { | ||
let endPos = __privateGet(this, _target).pos; | ||
__privateGet(this, _target).seek(__privateGet(this, _target).offsets.get(element)); | ||
let endPos = __privateGet(this, _writer).pos; | ||
__privateGet(this, _writer).seek(__privateGet(this, _writer).offsets.get(element)); | ||
element = [ | ||
@@ -965,4 +1045,4 @@ __privateMethod(this, _createCodecPrivateElement, createCodecPrivateElement_fn).call(this, data), | ||
]; | ||
__privateGet(this, _target).writeEBML(element); | ||
__privateGet(this, _target).seek(endPos); | ||
__privateGet(this, _writer).writeEBML(element); | ||
__privateGet(this, _writer).seek(endPos); | ||
}; | ||
@@ -981,5 +1061,5 @@ _createNewCluster = new WeakSet(); | ||
}); | ||
__privateGet(this, _target).writeEBML(__privateGet(this, _currentCluster)); | ||
__privateGet(this, _writer).writeEBML(__privateGet(this, _currentCluster)); | ||
__privateSet(this, _currentClusterTimestamp, timestamp); | ||
let clusterOffsetFromSegment = __privateGet(this, _target).offsets.get(__privateGet(this, _currentCluster)) - __privateGet(this, _segmentDataOffset, segmentDataOffset_get); | ||
let clusterOffsetFromSegment = __privateGet(this, _writer).offsets.get(__privateGet(this, _currentCluster)) - __privateGet(this, _segmentDataOffset, segmentDataOffset_get); | ||
__privateGet(this, _cues).data.push({ id: 187 /* CuePoint */, data: [ | ||
@@ -999,7 +1079,7 @@ { id: 179 /* CueTime */, data: timestamp }, | ||
finalizeCurrentCluster_fn = function() { | ||
let clusterSize = __privateGet(this, _target).pos - __privateGet(this, _target).dataOffsets.get(__privateGet(this, _currentCluster)); | ||
let endPos = __privateGet(this, _target).pos; | ||
__privateGet(this, _target).seek(__privateGet(this, _target).offsets.get(__privateGet(this, _currentCluster)) + 4); | ||
__privateGet(this, _target).writeEBMLVarInt(clusterSize, CLUSTER_SIZE_BYTES); | ||
__privateGet(this, _target).seek(endPos); | ||
let clusterSize = __privateGet(this, _writer).pos - __privateGet(this, _writer).dataOffsets.get(__privateGet(this, _currentCluster)); | ||
let endPos = __privateGet(this, _writer).pos; | ||
__privateGet(this, _writer).seek(__privateGet(this, _writer).offsets.get(__privateGet(this, _currentCluster)) + 4); | ||
__privateGet(this, _writer).writeEBMLVarInt(clusterSize, CLUSTER_SIZE_BYTES); | ||
__privateGet(this, _writer).seek(endPos); | ||
}; | ||
@@ -1012,28 +1092,9 @@ _ensureNotFinalized = new WeakSet(); | ||
}; | ||
var main_default = WebMMuxer; | ||
var readBits = (bytes, start, end) => { | ||
let result = 0; | ||
for (let i = start; i < end; i++) { | ||
let byteIndex = Math.floor(i / 8); | ||
let byte = bytes[byteIndex]; | ||
let bitIndex = 7 - (i & 7); | ||
let bit = (byte & 1 << bitIndex) >> bitIndex; | ||
result <<= 1; | ||
result |= bit; | ||
} | ||
return result; | ||
}; | ||
var writeBits = (bytes, start, end, value) => { | ||
for (let i = start; i < end; i++) { | ||
let byteIndex = Math.floor(i / 8); | ||
let byte = bytes[byteIndex]; | ||
let bitIndex = 7 - (i & 7); | ||
byte &= ~(1 << bitIndex); | ||
byte |= (value & 1 << end - i - 1) >> end - i - 1 << bitIndex; | ||
bytes[byteIndex] = byte; | ||
} | ||
}; | ||
return __toCommonJS(main_exports); | ||
return __toCommonJS(src_exports); | ||
})(); | ||
WebMMuxer = WebMMuxer.default; | ||
if (typeof module === "object" && typeof module.exports === "object") module.exports = WebMMuxer; | ||
if (typeof module === "object" && typeof module.exports === "object") { | ||
module.exports.Muxer = WebMMuxer.Muxer; | ||
module.exports.ArrayBufferTarget = WebMMuxer.ArrayBufferTarget; | ||
module.exports.StreamTarget = WebMMuxer.StreamTarget; | ||
module.exports.FileSystemWritableFileStreamTarget = WebMMuxer.FileSystemWritableFileStreamTarget; | ||
} |
{ | ||
"name": "webm-muxer", | ||
"version": "2.2.3", | ||
"version": "3.0.0", | ||
"description": "WebM multiplexer in pure TypeScript with support for WebCodecs API, video & audio.", | ||
@@ -5,0 +5,0 @@ "main": "./build/webm-muxer.js", |
159
README.md
@@ -19,6 +19,6 @@ # webm-muxer - JavaScript WebM multiplexer | ||
```js | ||
import WebMMuxer from 'webm-muxer'; | ||
import { Muxer, ArrayBufferTarget } from 'webm-muxer'; | ||
let muxer = new WebMMuxer({ | ||
target: 'buffer', | ||
let muxer = new Muxer({ | ||
target: new ArrayBufferTarget(), | ||
video: { | ||
@@ -45,3 +45,5 @@ codec: 'V_VP9', | ||
await videoEncoder.flush(); | ||
let buffer = muxer.finalize(); // Buffer contains final WebM file | ||
muxer.finalize(); | ||
let { buffer } = muxer.target; // Buffer contains final WebM file | ||
``` | ||
@@ -62,10 +64,10 @@ | ||
``` | ||
The package has a single, default export, `WebMMuxer`: | ||
You can import all exported classes like so: | ||
```js | ||
import WebMMuxer from 'webm-muxer'; | ||
import * as WebMMuxer from 'webm-muxer'; | ||
// Or, using CommonJS: | ||
const WebMMuxer = require('webm-muxer'); | ||
``` | ||
Alternatively, you can simply include the library as a script in your HTML, which will add `WebMMuxer` to the global | ||
object, like so: | ||
Alternatively, you can simply include the library as a script in your HTML, which will add a `WebMMuxer` object, | ||
containing all the exported classes, to the global object, like so: | ||
```html | ||
@@ -77,12 +79,15 @@ <script src="build/webm-muxer.js"></script> | ||
### Initialization | ||
For each WebM file you wish to create, create an instance of `WebMMuxer` like so: | ||
For each WebM file you wish to create, create an instance of `Muxer` like so: | ||
```js | ||
let muxer = new WebMMuxer(options); | ||
import { Muxer } from 'webm-muxer'; | ||
let muxer = new Muxer(options); | ||
``` | ||
The available options are defined by the following interface: | ||
```ts | ||
interface WebMMuxerOptions { | ||
target: 'buffer' | ||
| ((data: Uint8Array, offset: number, done: boolean) => void) | ||
| FileSystemWritableFileStream | ||
interface MuxerOptions { | ||
target: | ||
| ArrayBufferTarget | ||
| StreamTarget | ||
| FileSystemWritableFileStreamTarget, | ||
@@ -113,13 +118,11 @@ video?: { | ||
#### `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`. | ||
This option specifies where the data created by the muxer will be written. The options are: | ||
- `ArrayBufferTarget`: The file data will be written into a single large buffer, which is then stored in the target. | ||
```js | ||
let muxer = new WebMMuxer({ | ||
target: 'buffer', | ||
video: { | ||
codec: 'V_VP9', | ||
width: 1280, | ||
height: 720 | ||
} | ||
import { Muxer, ArrayBufferTarget } from 'webm-muxer'; | ||
let muxer = new Muxer({ | ||
target: new ArrayBufferTarget(), | ||
// ... | ||
}); | ||
@@ -129,28 +132,40 @@ | ||
let buffer = muxer.finalize(); | ||
muxer.finalize(); | ||
let { buffer } = muxer.target; | ||
``` | ||
- `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. If you don't want this, set `streaming` to `true`. | ||
- `StreamTarget`: This target defines callbacks that will get called whenever there is new data available - this is useful if | ||
you want to stream the data, e.g. pipe it somewhere else. The constructor has the following signature: | ||
```ts | ||
constructor( | ||
onData: (data: Uint8Array, position: number) => void, | ||
onDone?: () => void, | ||
options?: { chunked?: true } | ||
); | ||
``` | ||
The `position` parameter specifies the offset in bytes at which the data should be written. When using | ||
`chunked: true` in the options, data created by the muxer will first be accumulated and only written out once it has | ||
reached sufficient size (~16 MB). This is useful for reducing the total amount of writes, at the cost of latency. | ||
Note that this target is **not** intended for *live-streaming*, i.e. playback before muxing has finished. | ||
```js | ||
let muxer = new WebMMuxer({ | ||
target: (data, offset, done) => { | ||
// Do something with the data | ||
}, | ||
audio: { | ||
codec: 'A_OPUS', | ||
numberOfChannels: 1, | ||
sampleRate: 44100 | ||
} | ||
import { Muxer, StreamTarget } from 'webm-muxer'; | ||
let muxer = new Muxer({ | ||
target: new StreamTarget( | ||
(data, position) => { /* Do something with the data */ }, | ||
() => { /* Muxing has finished */ } | ||
), | ||
// ... | ||
}); | ||
``` | ||
- `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. | ||
- `FileSystemWritableFileStreamTarget`: This is essentially a wrapper around `StreamTarget` with the intention of | ||
simplifying the use of this library with the File System Access API. Writing the file directly to disk as it's being | ||
created comes with many benefits, such as creating files way larger than the available RAM. | ||
```js | ||
import { Muxer, FileSystemWritableFileStreamTarget } from 'webm-muxer'; | ||
let fileHandle = await window.showSaveFilePicker({ | ||
@@ -163,17 +178,12 @@ suggestedName: `video.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 | ||
} | ||
let fileStream = await fileHandle.createWritable(); | ||
let muxer = new Muxer({ | ||
target: new FileSystemWritableFileStreamTarget(fileStream), | ||
// ... | ||
}); | ||
// ... | ||
muxer.finalize(); | ||
await fileStream.close(); // Make sure to close the stream | ||
``` | ||
@@ -202,11 +212,20 @@ #### `streaming` (optional) | ||
### Muxing media chunks | ||
Then, with VideoEncoder and AudioEncoder set up, send encoded chunks to the muxer like so: | ||
```js | ||
muxer.addVideoChunk(encodedVideoChunk, encodedVideoChunkMetadata); | ||
muxer.addAudioChunk(encodedAudioChunk, encodedAudioChunkMetadata); | ||
Then, with VideoEncoder and AudioEncoder set up, send encoded chunks to the muxer using the following methods: | ||
```ts | ||
addVideoChunk( | ||
chunk: EncodedVideoChunk, | ||
meta: EncodedVideoChunkMetadata, | ||
timestamp?: number | ||
): void; | ||
addAudioChunk( | ||
chunk: EncodedAudioChunk, | ||
meta: EncodedAudioChunkMetadata, | ||
timestamp?: number | ||
): void; | ||
``` | ||
In addition, both methods accept an optional, third argument `timestamp` (microseconds) which, if specified, overrides | ||
the `timestamp` property of the passed-in chunk. This is useful when getting chunks from a MediaStreamTrackProcessor | ||
from live media, which usually come with huge timestamp values and don't start at 0, which we want. | ||
Both methods accept an optional, third argument `timestamp` (microseconds) which, if specified, overrides | ||
the `timestamp` property of the passed-in chunk. | ||
The metadata comes from the second parameter of the `output` callback given to the | ||
@@ -241,11 +260,15 @@ VideoEncoder or AudioEncoder's constructor and needs to be passed into the muxer, like so: | ||
### Finishing up | ||
When encoding is finished, call `finalize` on the `WebMMuxer` instance to finalize the WebM file. When using | ||
`target: 'buffer'`, the resulting file's buffer is returned by this method: | ||
When encoding is finished and all the encoders have been flushed, call `finalize` on the `Muxer` instance to finalize | ||
the WebM file: | ||
```js | ||
let buffer = muxer.finalize(); | ||
muxer.finalize(); | ||
``` | ||
When using a FileSystemWritableFileStream, make sure to close the stream after calling `finalize`: | ||
When using an ArrayBufferTarget, the final buffer will be accessible through it: | ||
```js | ||
await fileWritableStream.close(); | ||
let { buffer } = muxer.target; | ||
``` | ||
When using a FileSystemWritableFileStreamTarget, make sure to close the stream after calling `finalize`: | ||
```js | ||
await fileStream.close(); | ||
``` | ||
@@ -252,0 +275,0 @@ ## Details |
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
71305
1255
296