UNPKG

@framekit/webm-writer-esm

Version:
245 lines (214 loc) • 7.65 kB
/** * Allows a series of Blob-convertible objects (ArrayBuffer, Blob, String, etc) to be added to a buffer. Seeking and * overwriting of blobs is allowed. * * You can supply a FileWriter, in which case the BlobBuffer is just used as temporary storage before it writes it * through to the disk. * * By Nicholas Sherlock * */ export function BlobBufferFactory(fs) { return function (destination) { let buffer = [], writePromise = Promise.resolve(), fileWriter = null, fd = null; if (destination && destination.constructor.name === 'FileWriter') { fileWriter = destination; } else if (fs && destination) { fd = destination; } // Current seek offset this.pos = 0; // One more than the index of the highest byte ever written this.length = 0; // Returns a promise that converts the blob to an ArrayBuffer function readBlobAsBuffer(blob) { return new Promise(function (resolve) { let reader = new FileReader(); reader.addEventListener('loadend', function () { resolve(reader.result); }); reader.readAsArrayBuffer(blob); }); } function convertToUint8Array(thing) { return new Promise(function (resolve) { if (thing instanceof Uint8Array) { resolve(thing); } else if ( thing instanceof ArrayBuffer || ArrayBuffer.isView(thing) ) { resolve(new Uint8Array(thing)); } else if (thing instanceof Blob) { resolve( readBlobAsBuffer(thing).then(function (buffer) { return new Uint8Array(buffer); }) ); } else { //Assume that Blob will know how to read this thing resolve( readBlobAsBuffer(new Blob([thing])).then(function (buffer) { return new Uint8Array(buffer); }) ); } }); } function measureData(data) { let result = data.byteLength || data.length || data.size; if (!Number.isInteger(result)) { throw new Error('Failed to determine size of element'); } return result; } /** * Seek to the given absolute offset. * * You may not seek beyond the end of the file (this would create a hole and/or allow blocks to be written in non- * sequential order, which isn't currently supported by the memory buffer backend). */ this.seek = function (offset) { if (offset < 0) { throw new Error('Offset may not be negative'); } if (isNaN(offset)) { throw new Error('Offset may not be NaN'); } if (offset > this.length) { throw new Error('Seeking beyond the end of file is not allowed'); } this.pos = offset; }; /** * Write the Blob-convertible data to the buffer at the current seek position. * * Note: If overwriting existing data, the write must not cross preexisting block boundaries (written data must * be fully contained by the extent of a previous write). */ this.write = function (data) { let newEntry = { offset: this.pos, data: data, length: measureData(data), }, isAppend = newEntry.offset >= this.length; this.pos += newEntry.length; this.length = Math.max(this.length, this.pos); // After previous writes complete, perform our write writePromise = writePromise.then(function () { if (fd) { return new Promise(function (resolve) { convertToUint8Array(newEntry.data).then(function (dataArray) { let totalWritten = 0, buffer = Buffer.from(dataArray.buffer), handleWriteComplete = function (err, written, buffer) { totalWritten += written; if (totalWritten >= buffer.length) { resolve(); } else { // We still have more to write... fs.write( fd, buffer, totalWritten, buffer.length - totalWritten, newEntry.offset + totalWritten, handleWriteComplete ); } }; fs.write( fd, buffer, 0, buffer.length, newEntry.offset, handleWriteComplete ); }); }); } else if (fileWriter) { return new Promise(function (resolve) { fileWriter.onwriteend = resolve; fileWriter.seek(newEntry.offset); fileWriter.write(new Blob([newEntry.data])); }); } else if (!isAppend) { // We might be modifying a write that was already buffered in memory. // Slow linear search to find a block we might be overwriting for (let i = 0; i < buffer.length; i++) { let entry = buffer[i]; // If our new entry overlaps the old one in any way... if ( !( newEntry.offset + newEntry.length <= entry.offset || newEntry.offset >= entry.offset + entry.length ) ) { if ( newEntry.offset < entry.offset || newEntry.offset + newEntry.length > entry.offset + entry.length ) { throw new Error('Overwrite crosses blob boundaries'); } if ( newEntry.offset == entry.offset && newEntry.length == entry.length ) { // We overwrote the entire block entry.data = newEntry.data; // We're done return; } else { return convertToUint8Array(entry.data) .then(function (entryArray) { entry.data = entryArray; return convertToUint8Array(newEntry.data); }) .then(function (newEntryArray) { newEntry.data = newEntryArray; entry.data.set( newEntry.data, newEntry.offset - entry.offset ); }); } } } // Else fall through to do a simple append, as we didn't overwrite any pre-existing blocks } buffer.push(newEntry); }); }; /** * Finish all writes to the buffer, returning a promise that signals when that is complete. * * If a FileWriter was not provided, the promise is resolved with a Blob that represents the completed BlobBuffer * contents. You can optionally pass in a mimeType to be used for this blob. * * If a FileWriter was provided, the promise is resolved with null as the first argument. */ this.complete = function (mimeType) { if (fd || fileWriter) { writePromise = writePromise.then(function () { return null; }); } else { // After writes complete we need to merge the buffer to give to the caller writePromise = writePromise.then(function () { let result = []; for (let i = 0; i < buffer.length; i++) { result.push(buffer[i].data); } return new Blob(result, { type: mimeType }); }); } return writePromise; }; }; }