mirror of
				https://github.com/Ylianst/MeshCentral.git
				synced 2025-03-09 15:40:18 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1097 lines
		
	
	
		
			No EOL
		
	
	
		
			40 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1097 lines
		
	
	
		
			No EOL
		
	
	
		
			40 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						|
 * A tool for presenting an ArrayBuffer as a stream for writing some simple data types.
 | 
						|
 * 
 | 
						|
 * By Nicholas Sherlock
 | 
						|
 * 
 | 
						|
 * Released under the WTFPLv2 https://en.wikipedia.org/wiki/WTFPL
 | 
						|
 */
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
(function(){
 | 
						|
    /*
 | 
						|
     * Create an ArrayBuffer of the given length and present it as a writable stream with methods
 | 
						|
     * for writing data in different formats.
 | 
						|
     */
 | 
						|
    var ArrayBufferDataStream = function(length) {
 | 
						|
        this.data = new Uint8Array(length);
 | 
						|
        this.pos = 0;
 | 
						|
    };
 | 
						|
    
 | 
						|
    ArrayBufferDataStream.prototype.seek = function(offset) {
 | 
						|
        this.pos = offset;
 | 
						|
    };
 | 
						|
 | 
						|
    ArrayBufferDataStream.prototype.writeBytes = function(arr) {
 | 
						|
        for (var i = 0; i < arr.length; i++) {
 | 
						|
            this.data[this.pos++] = arr[i];
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    ArrayBufferDataStream.prototype.writeByte = function(b) {
 | 
						|
        this.data[this.pos++] = b;
 | 
						|
    };
 | 
						|
    
 | 
						|
    //Synonym:
 | 
						|
    ArrayBufferDataStream.prototype.writeU8 = ArrayBufferDataStream.prototype.writeByte;
 | 
						|
    
 | 
						|
    ArrayBufferDataStream.prototype.writeU16BE = function(u) {
 | 
						|
        this.data[this.pos++] = u >> 8;
 | 
						|
        this.data[this.pos++] = u;
 | 
						|
    };
 | 
						|
 | 
						|
    ArrayBufferDataStream.prototype.writeDoubleBE = function(d) {
 | 
						|
        var 
 | 
						|
            bytes = new Uint8Array(new Float64Array([d]).buffer);
 | 
						|
        
 | 
						|
        for (var i = bytes.length - 1; i >= 0; i--) {
 | 
						|
            this.writeByte(bytes[i]);
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    ArrayBufferDataStream.prototype.writeFloatBE = function(d) {
 | 
						|
        var 
 | 
						|
            bytes = new Uint8Array(new Float32Array([d]).buffer);
 | 
						|
        
 | 
						|
        for (var i = bytes.length - 1; i >= 0; i--) {
 | 
						|
            this.writeByte(bytes[i]);
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    /**
 | 
						|
     * Write an ASCII string to the stream
 | 
						|
     */
 | 
						|
    ArrayBufferDataStream.prototype.writeString = function(s) {
 | 
						|
        for (var i = 0; i < s.length; i++) {
 | 
						|
            this.data[this.pos++] = s.charCodeAt(i);
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    /**
 | 
						|
     * Write the given 32-bit integer to the stream as an EBML variable-length integer using the given byte width 
 | 
						|
     * (use measureEBMLVarInt).
 | 
						|
     * 
 | 
						|
     * No error checking is performed to ensure that the supplied width is correct for the integer.
 | 
						|
     * 
 | 
						|
     * @param i Integer to be written
 | 
						|
     * @param width Number of bytes to write to the stream
 | 
						|
     */
 | 
						|
    ArrayBufferDataStream.prototype.writeEBMLVarIntWidth = function(i, width) {
 | 
						|
        switch (width) {
 | 
						|
            case 1:
 | 
						|
                this.writeU8((1 << 7) | i);
 | 
						|
            break;
 | 
						|
            case 2:
 | 
						|
                this.writeU8((1 << 6) | (i >> 8));
 | 
						|
                this.writeU8(i);
 | 
						|
            break;
 | 
						|
            case 3:
 | 
						|
                this.writeU8((1 << 5) | (i >> 16));
 | 
						|
                this.writeU8(i >> 8);
 | 
						|
                this.writeU8(i);
 | 
						|
            break;
 | 
						|
            case 4:
 | 
						|
                this.writeU8((1 << 4) | (i >> 24));
 | 
						|
                this.writeU8(i >> 16);
 | 
						|
                this.writeU8(i >> 8);
 | 
						|
                this.writeU8(i);
 | 
						|
            break;
 | 
						|
            case 5:
 | 
						|
                /* 
 | 
						|
                 * JavaScript converts its doubles to 32-bit integers for bitwise operations, so we need to do a 
 | 
						|
                 * division by 2^32 instead of a right-shift of 32 to retain those top 3 bits
 | 
						|
                 */
 | 
						|
                this.writeU8((1 << 3) | ((i / 4294967296) & 0x7)); 
 | 
						|
                this.writeU8(i >> 24);
 | 
						|
                this.writeU8(i >> 16);
 | 
						|
                this.writeU8(i >> 8);
 | 
						|
                this.writeU8(i);
 | 
						|
            break;
 | 
						|
            default:
 | 
						|
                throw new RuntimeException("Bad EBML VINT size " + width);
 | 
						|
        }
 | 
						|
    };
 | 
						|
    
 | 
						|
    /**
 | 
						|
     * Return the number of bytes needed to encode the given integer as an EBML VINT.
 | 
						|
     */
 | 
						|
    ArrayBufferDataStream.prototype.measureEBMLVarInt = function(val) {
 | 
						|
        if (val < (1 << 7) - 1) { 
 | 
						|
            /* Top bit is set, leaving 7 bits to hold the integer, but we can't store 127 because
 | 
						|
             * "all bits set to one" is a reserved value. Same thing for the other cases below:
 | 
						|
             */
 | 
						|
            return 1;
 | 
						|
        } else if (val < (1 << 14) - 1) {
 | 
						|
            return 2;
 | 
						|
        } else if (val < (1 << 21) - 1) {
 | 
						|
            return 3;
 | 
						|
        } else if (val < (1 << 28) - 1) {
 | 
						|
            return 4;
 | 
						|
        } else if (val < 34359738367) { // 2 ^ 35 - 1 (can address 32GB)
 | 
						|
            return 5;
 | 
						|
        } else {
 | 
						|
            throw new RuntimeException("EBML VINT size not supported " + val);
 | 
						|
        }
 | 
						|
    };
 | 
						|
    
 | 
						|
    ArrayBufferDataStream.prototype.writeEBMLVarInt = function(i) {
 | 
						|
        this.writeEBMLVarIntWidth(i, this.measureEBMLVarInt(i));
 | 
						|
    };
 | 
						|
    
 | 
						|
    /**
 | 
						|
     * Write the given unsigned 32-bit integer to the stream in big-endian order using the given byte width.
 | 
						|
     * No error checking is performed to ensure that the supplied width is correct for the integer.
 | 
						|
     * 
 | 
						|
     * Omit the width parameter to have it determined automatically for you.
 | 
						|
     * 
 | 
						|
     * @param u Unsigned integer to be written
 | 
						|
     * @param width Number of bytes to write to the stream
 | 
						|
     */
 | 
						|
    ArrayBufferDataStream.prototype.writeUnsignedIntBE = function(u, width) {
 | 
						|
        if (width === undefined) {
 | 
						|
            width = this.measureUnsignedInt(u);
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Each case falls through:
 | 
						|
        switch (width) {
 | 
						|
            case 5:
 | 
						|
                this.writeU8(Math.floor(u / 4294967296)); // Need to use division to access >32 bits of floating point var
 | 
						|
            case 4:
 | 
						|
                this.writeU8(u >> 24);
 | 
						|
            case 3:
 | 
						|
                this.writeU8(u >> 16);
 | 
						|
            case 2:
 | 
						|
                this.writeU8(u >> 8);
 | 
						|
            case 1:
 | 
						|
                this.writeU8(u);
 | 
						|
            break;
 | 
						|
            default:
 | 
						|
                throw new RuntimeException("Bad UINT size " + width);
 | 
						|
        }
 | 
						|
    };
 | 
						|
    
 | 
						|
    /**
 | 
						|
     * Return the number of bytes needed to hold the non-zero bits of the given unsigned integer.
 | 
						|
     */
 | 
						|
    ArrayBufferDataStream.prototype.measureUnsignedInt = function(val) {
 | 
						|
        // Force to 32-bit unsigned integer
 | 
						|
        if (val < (1 << 8)) {
 | 
						|
            return 1;
 | 
						|
        } else if (val < (1 << 16)) {
 | 
						|
            return 2;
 | 
						|
        } else if (val < (1 << 24)) {
 | 
						|
            return 3;
 | 
						|
        } else if (val < 4294967296) {
 | 
						|
            return 4;
 | 
						|
        } else {
 | 
						|
            return 5;
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    /**
 | 
						|
     * Return a view on the portion of the buffer from the beginning to the current seek position as a Uint8Array.
 | 
						|
     */
 | 
						|
    ArrayBufferDataStream.prototype.getAsDataArray = function() {
 | 
						|
        if (this.pos < this.data.byteLength) {
 | 
						|
            return this.data.subarray(0, this.pos);
 | 
						|
        } else if (this.pos == this.data.byteLength) {
 | 
						|
            return this.data;
 | 
						|
        } else {
 | 
						|
            throw "ArrayBufferDataStream's pos lies beyond end of buffer";
 | 
						|
        }
 | 
						|
    };
 | 
						|
	
 | 
						|
	if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
 | 
						|
		module.exports = ArrayBufferDataStream;
 | 
						|
	} else {
 | 
						|
		window.ArrayBufferDataStream = ArrayBufferDataStream;
 | 
						|
	}
 | 
						|
}());"use strict";
 | 
						|
 | 
						|
/**
 | 
						|
 * 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
 | 
						|
 * 
 | 
						|
 * Released under the WTFPLv2 https://en.wikipedia.org/wiki/WTFPL
 | 
						|
 */
 | 
						|
(function() {
 | 
						|
	var BlobBuffer = function(fs) {
 | 
						|
		return function(destination) {
 | 
						|
			var
 | 
						|
				buffer = [],
 | 
						|
				writePromise = Promise.resolve(),
 | 
						|
				fileWriter = null,
 | 
						|
				fd = null;
 | 
						|
			
 | 
						|
			if (typeof FileWriter !== "undefined" && destination instanceof 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, reject) {
 | 
						|
					var
 | 
						|
						reader = new FileReader();
 | 
						|
					
 | 
						|
					reader.addEventListener("loadend", function () {
 | 
						|
						resolve(reader.result);
 | 
						|
					});
 | 
						|
					
 | 
						|
					reader.readAsArrayBuffer(blob);
 | 
						|
				});
 | 
						|
			}
 | 
						|
			
 | 
						|
			function convertToUint8Array(thing) {
 | 
						|
				return new Promise(function (resolve, reject) {
 | 
						|
					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) {
 | 
						|
				var
 | 
						|
					result = data.byteLength || data.length || data.size;
 | 
						|
				
 | 
						|
				if (!Number.isInteger(result)) {
 | 
						|
					throw "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 "Offset may not be negative";
 | 
						|
				}
 | 
						|
				
 | 
						|
				if (isNaN(offset)) {
 | 
						|
					throw "Offset may not be NaN";
 | 
						|
				}
 | 
						|
				
 | 
						|
				if (offset > this.length) {
 | 
						|
					throw "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) {
 | 
						|
				var
 | 
						|
					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, reject) {
 | 
						|
							convertToUint8Array(newEntry.data).then(function(dataArray) {
 | 
						|
								var
 | 
						|
									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, reject) {
 | 
						|
							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 (var i = 0; i < buffer.length; i++) {
 | 
						|
							var
 | 
						|
								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 () {
 | 
						|
						var
 | 
						|
							result = [];
 | 
						|
						
 | 
						|
						for (var i = 0; i < buffer.length; i++) {
 | 
						|
							result.push(buffer[i].data);
 | 
						|
						}
 | 
						|
						
 | 
						|
						return new Blob(result, {mimeType: mimeType});
 | 
						|
					});
 | 
						|
				}
 | 
						|
				
 | 
						|
				return writePromise;
 | 
						|
			};
 | 
						|
		};
 | 
						|
	};
 | 
						|
	
 | 
						|
	if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
 | 
						|
		module.exports = BlobBuffer(require('fs'));
 | 
						|
	} else {
 | 
						|
		window.BlobBuffer = BlobBuffer(null);
 | 
						|
	}
 | 
						|
})();/**
 | 
						|
 * WebM video encoder for Google Chrome. This implementation is suitable for creating very large video files, because
 | 
						|
 * it can stream Blobs directly to a FileWriter without buffering the entire video in memory.
 | 
						|
 * 
 | 
						|
 * When FileWriter is not available or not desired, it can buffer the video in memory as a series of Blobs which are 
 | 
						|
 * eventually returned as one composite Blob.
 | 
						|
 * 
 | 
						|
 * By Nicholas Sherlock.
 | 
						|
 * 
 | 
						|
 * Based on the ideas from Whammy: https://github.com/antimatter15/whammy
 | 
						|
 * 
 | 
						|
 * Released under the WTFPLv2 https://en.wikipedia.org/wiki/WTFPL
 | 
						|
 */
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
(function() {
 | 
						|
    var WebMWriter = function(ArrayBufferDataStream, BlobBuffer) {
 | 
						|
        function extend(base, top) {
 | 
						|
            var
 | 
						|
                target = {};
 | 
						|
            
 | 
						|
            [base, top].forEach(function(obj) {
 | 
						|
                for (var prop in obj) {
 | 
						|
                    if (Object.prototype.hasOwnProperty.call(obj, prop)) {
 | 
						|
                        target[prop] = obj[prop];
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            });
 | 
						|
            
 | 
						|
            return target;
 | 
						|
        }
 | 
						|
        
 | 
						|
        /**
 | 
						|
         * Decode a Base64 data URL into a binary string.
 | 
						|
         *
 | 
						|
         * Returns the binary string, or false if the URL could not be decoded.
 | 
						|
         */
 | 
						|
        function decodeBase64WebPDataURL(url) {
 | 
						|
            if (typeof url !== "string" || !url.match(/^data:image\/webp;base64,/i)) {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            
 | 
						|
            return window.atob(url.substring("data:image\/webp;base64,".length));
 | 
						|
        }
 | 
						|
        
 | 
						|
        /**
 | 
						|
         * Convert a raw binary string (one character = one output byte) to an ArrayBuffer
 | 
						|
         */
 | 
						|
        function stringToArrayBuffer(string) {
 | 
						|
            var
 | 
						|
                buffer = new ArrayBuffer(string.length),
 | 
						|
                int8Array = new Uint8Array(buffer);
 | 
						|
            
 | 
						|
            for (var i = 0; i < string.length; i++) {
 | 
						|
                int8Array[i] = string.charCodeAt(i);
 | 
						|
            }
 | 
						|
            
 | 
						|
            return buffer;
 | 
						|
        }
 | 
						|
        
 | 
						|
        /**
 | 
						|
         * Convert the given canvas to a WebP encoded image and return the image data as a string.
 | 
						|
         */
 | 
						|
        function renderAsWebP(canvas, quality) {
 | 
						|
            var
 | 
						|
                frame = canvas.toDataURL('image/webp', quality);
 | 
						|
            
 | 
						|
            return decodeBase64WebPDataURL(frame);
 | 
						|
        }
 | 
						|
        
 | 
						|
        function extractKeyframeFromWebP(webP) {
 | 
						|
            // Assume that Chrome will generate a Simple Lossy WebP which has this header:
 | 
						|
            var
 | 
						|
                keyframeStartIndex = webP.indexOf('VP8 ');
 | 
						|
            
 | 
						|
            if (keyframeStartIndex == -1) {
 | 
						|
                throw "Failed to identify beginning of keyframe in WebP image";
 | 
						|
            }
 | 
						|
            
 | 
						|
            // Skip the header and the 4 bytes that encode the length of the VP8 chunk
 | 
						|
            keyframeStartIndex += 'VP8 '.length + 4;
 | 
						|
            
 | 
						|
            return webP.substring(keyframeStartIndex);
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Just a little utility so we can tag values as floats for the EBML encoder's benefit
 | 
						|
        function EBMLFloat32(value) {
 | 
						|
            this.value = value;
 | 
						|
        }
 | 
						|
        
 | 
						|
        function EBMLFloat64(value) {
 | 
						|
            this.value = value;
 | 
						|
        }
 | 
						|
        
 | 
						|
        /**
 | 
						|
         * Write the given EBML object to the provided ArrayBufferStream.
 | 
						|
         *
 | 
						|
         * The buffer's first byte is at bufferFileOffset inside the video file. This is used to complete offset and
 | 
						|
         * dataOffset fields in each EBML structure, indicating the file offset of the first byte of the EBML element and
 | 
						|
         * its data payload.
 | 
						|
         */
 | 
						|
        function writeEBML(buffer, bufferFileOffset, ebml) {
 | 
						|
            // Is the ebml an array of sibling elements?
 | 
						|
            if (Array.isArray(ebml)) {
 | 
						|
                for (var i = 0; i < ebml.length; i++) {
 | 
						|
                    writeEBML(buffer, bufferFileOffset, ebml[i]);
 | 
						|
                }
 | 
						|
            // Is this some sort of raw data that we want to write directly?
 | 
						|
            } else if (typeof ebml === "string") {
 | 
						|
                buffer.writeString(ebml);
 | 
						|
            } else if (ebml instanceof Uint8Array) {
 | 
						|
                buffer.writeBytes(ebml);
 | 
						|
            } else if (ebml.id){
 | 
						|
                // We're writing an EBML element
 | 
						|
                ebml.offset = buffer.pos + bufferFileOffset;
 | 
						|
                
 | 
						|
                buffer.writeUnsignedIntBE(ebml.id); // ID field
 | 
						|
                
 | 
						|
                // Now we need to write the size field, so we must know the payload size:
 | 
						|
                
 | 
						|
                if (Array.isArray(ebml.data)) {
 | 
						|
                    // Writing an array of child elements. We won't try to measure the size of the children up-front
 | 
						|
                    
 | 
						|
                    var
 | 
						|
                        sizePos, dataBegin, dataEnd;
 | 
						|
                    
 | 
						|
                    if (ebml.size === -1) {
 | 
						|
                        // Write the reserved all-one-bits marker to note that the size of this element is unknown/unbounded
 | 
						|
                        buffer.writeByte(0xFF);
 | 
						|
                    } else {
 | 
						|
                        sizePos = buffer.pos;
 | 
						|
                        
 | 
						|
                        /* Write a dummy size field to overwrite later. 4 bytes allows an element maximum size of 256MB,
 | 
						|
                         * which should be plenty (we don't want to have to buffer that much data in memory at one time
 | 
						|
                         * anyway!)
 | 
						|
                         */
 | 
						|
                        buffer.writeBytes([0, 0, 0, 0]);
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    dataBegin = buffer.pos;
 | 
						|
                    
 | 
						|
                    ebml.dataOffset = dataBegin + bufferFileOffset;
 | 
						|
                    writeEBML(buffer, bufferFileOffset, ebml.data);
 | 
						|
                    
 | 
						|
                    if (ebml.size !== -1) {
 | 
						|
                        dataEnd = buffer.pos;
 | 
						|
                        
 | 
						|
                        ebml.size = dataEnd - dataBegin;
 | 
						|
                        
 | 
						|
                        buffer.seek(sizePos);
 | 
						|
                        buffer.writeEBMLVarIntWidth(ebml.size, 4); // Size field
 | 
						|
                        
 | 
						|
                        buffer.seek(dataEnd);
 | 
						|
                    }
 | 
						|
                } else if (typeof ebml.data === "string") {
 | 
						|
                    buffer.writeEBMLVarInt(ebml.data.length); // Size field
 | 
						|
                    ebml.dataOffset = buffer.pos + bufferFileOffset;
 | 
						|
                    buffer.writeString(ebml.data);
 | 
						|
                } else if (typeof ebml.data === "number") {
 | 
						|
                    // Allow the caller to explicitly choose the size if they wish by supplying a size field
 | 
						|
                    if (!ebml.size) {
 | 
						|
                        ebml.size = buffer.measureUnsignedInt(ebml.data);
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    buffer.writeEBMLVarInt(ebml.size); // Size field
 | 
						|
                    ebml.dataOffset = buffer.pos + bufferFileOffset;
 | 
						|
                    buffer.writeUnsignedIntBE(ebml.data, ebml.size);
 | 
						|
                } else if (ebml.data instanceof EBMLFloat64) {
 | 
						|
                    buffer.writeEBMLVarInt(8); // Size field
 | 
						|
                    ebml.dataOffset = buffer.pos + bufferFileOffset;
 | 
						|
                    buffer.writeDoubleBE(ebml.data.value);
 | 
						|
                } else if (ebml.data instanceof EBMLFloat32) {
 | 
						|
                    buffer.writeEBMLVarInt(4); // Size field
 | 
						|
                    ebml.dataOffset = buffer.pos + bufferFileOffset;
 | 
						|
                    buffer.writeFloatBE(ebml.data.value);
 | 
						|
                } else if (ebml.data instanceof Uint8Array) {
 | 
						|
                    buffer.writeEBMLVarInt(ebml.data.byteLength); // Size field
 | 
						|
                    ebml.dataOffset = buffer.pos + bufferFileOffset;
 | 
						|
                    buffer.writeBytes(ebml.data);
 | 
						|
                } else {
 | 
						|
                    throw "Bad EBML datatype " + typeof ebml.data;
 | 
						|
                }
 | 
						|
            } else {
 | 
						|
                throw "Bad EBML datatype " + typeof ebml.data;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        
 | 
						|
        return function(options) {
 | 
						|
            var
 | 
						|
                MAX_CLUSTER_DURATION_MSEC = 5000,
 | 
						|
                DEFAULT_TRACK_NUMBER = 1,
 | 
						|
            
 | 
						|
                writtenHeader = false,
 | 
						|
                videoWidth, videoHeight,
 | 
						|
                
 | 
						|
                clusterFrameBuffer = [],
 | 
						|
                clusterStartTime = 0,
 | 
						|
                clusterDuration = 0,
 | 
						|
                
 | 
						|
                optionDefaults = {
 | 
						|
                    quality: 0.95,       // WebM image quality from 0.0 (worst) to 1.0 (best)
 | 
						|
                    fileWriter: null,    // Chrome FileWriter in order to stream to a file instead of buffering to memory (optional)
 | 
						|
                    fd: null,            // Node.JS file descriptor to write to instead of buffering (optional)
 | 
						|
                    
 | 
						|
                    // You must supply one of:
 | 
						|
                    frameDuration: null, // Duration of frames in milliseconds
 | 
						|
                    frameRate: null,     // Number of frames per second
 | 
						|
                },
 | 
						|
                
 | 
						|
                seekPoints = {
 | 
						|
                    Cues: {id: new Uint8Array([0x1C, 0x53, 0xBB, 0x6B]), positionEBML: null},
 | 
						|
                    SegmentInfo: {id: new Uint8Array([0x15, 0x49, 0xA9, 0x66]), positionEBML: null},
 | 
						|
                    Tracks: {id: new Uint8Array([0x16, 0x54, 0xAE, 0x6B]), positionEBML: null},
 | 
						|
                },
 | 
						|
                
 | 
						|
                ebmlSegment,
 | 
						|
                segmentDuration = {
 | 
						|
                    "id": 0x4489, // Duration
 | 
						|
                    "data": new EBMLFloat64(0)
 | 
						|
                },
 | 
						|
                
 | 
						|
                seekHead,
 | 
						|
                
 | 
						|
                cues = [],
 | 
						|
                
 | 
						|
                blobBuffer = new BlobBuffer(options.fileWriter || options.fd);
 | 
						|
    
 | 
						|
            function fileOffsetToSegmentRelative(fileOffset) {
 | 
						|
                return fileOffset - ebmlSegment.dataOffset;
 | 
						|
            }
 | 
						|
            
 | 
						|
            /**
 | 
						|
             * Create a SeekHead element with descriptors for the points in the global seekPoints array.
 | 
						|
             *
 | 
						|
             * 5 bytes of position values are reserved for each node, which lie at the offset point.positionEBML.dataOffset,
 | 
						|
             * to be overwritten later.
 | 
						|
             */
 | 
						|
            function createSeekHead() {
 | 
						|
                var
 | 
						|
                    seekPositionEBMLTemplate = {
 | 
						|
                        "id": 0x53AC, // SeekPosition
 | 
						|
                        "size": 5, // Allows for 32GB video files
 | 
						|
                        "data": 0 // We'll overwrite this when the file is complete
 | 
						|
                    },
 | 
						|
                    
 | 
						|
                    result = {
 | 
						|
                        "id": 0x114D9B74, // SeekHead
 | 
						|
                        "data": []
 | 
						|
                    };
 | 
						|
                
 | 
						|
                for (var name in seekPoints) {
 | 
						|
                    var
 | 
						|
                        seekPoint = seekPoints[name];
 | 
						|
                
 | 
						|
                    seekPoint.positionEBML = Object.create(seekPositionEBMLTemplate);
 | 
						|
                    
 | 
						|
                    result.data.push({
 | 
						|
                         "id": 0x4DBB, // Seek
 | 
						|
                         "data": [
 | 
						|
                              {
 | 
						|
                                  "id": 0x53AB, // SeekID
 | 
						|
                                  "data": seekPoint.id
 | 
						|
                              },
 | 
						|
                              seekPoint.positionEBML
 | 
						|
                         ]
 | 
						|
                    });
 | 
						|
                }
 | 
						|
                
 | 
						|
                return result;
 | 
						|
            }
 | 
						|
            
 | 
						|
            /**
 | 
						|
             * Write the WebM file header to the stream.
 | 
						|
             */
 | 
						|
            function writeHeader() {
 | 
						|
                seekHead = createSeekHead();
 | 
						|
                
 | 
						|
                var
 | 
						|
                    ebmlHeader = {
 | 
						|
                        "id": 0x1a45dfa3, // EBML
 | 
						|
                        "data": [
 | 
						|
                            {
 | 
						|
                                "id": 0x4286, // EBMLVersion
 | 
						|
                                "data": 1
 | 
						|
                            },
 | 
						|
                            {
 | 
						|
                                "id": 0x42f7, // EBMLReadVersion
 | 
						|
                                "data": 1
 | 
						|
                            },
 | 
						|
                            {
 | 
						|
                                "id": 0x42f2, // EBMLMaxIDLength
 | 
						|
                                "data": 4
 | 
						|
                            },
 | 
						|
                            {
 | 
						|
                                "id": 0x42f3, // EBMLMaxSizeLength
 | 
						|
                                "data": 8
 | 
						|
                            },
 | 
						|
                            {
 | 
						|
                                "id": 0x4282, // DocType
 | 
						|
                                "data": "webm"
 | 
						|
                            },
 | 
						|
                            {
 | 
						|
                                "id": 0x4287, // DocTypeVersion
 | 
						|
                                "data": 2
 | 
						|
                            },
 | 
						|
                            {
 | 
						|
                                "id": 0x4285, // DocTypeReadVersion
 | 
						|
                                "data": 2
 | 
						|
                            }
 | 
						|
                        ]
 | 
						|
                    },
 | 
						|
                    
 | 
						|
                    segmentInfo = {
 | 
						|
                        "id": 0x1549a966, // Info
 | 
						|
                        "data": [
 | 
						|
                            {
 | 
						|
                                "id": 0x2ad7b1, // TimecodeScale
 | 
						|
                                "data": 1e6 // Times will be in miliseconds (1e6 nanoseconds per step = 1ms)
 | 
						|
                            },
 | 
						|
                            {
 | 
						|
                                "id": 0x4d80, // MuxingApp
 | 
						|
                                "data": "webm-writer-js",
 | 
						|
                            },
 | 
						|
                            {
 | 
						|
                                "id": 0x5741, // WritingApp
 | 
						|
                                "data": "webm-writer-js"
 | 
						|
                            },
 | 
						|
                            segmentDuration // To be filled in later
 | 
						|
                        ]
 | 
						|
                    },
 | 
						|
                    
 | 
						|
                    tracks = {
 | 
						|
                        "id": 0x1654ae6b, // Tracks
 | 
						|
                        "data": [
 | 
						|
                            {
 | 
						|
                                "id": 0xae, // TrackEntry
 | 
						|
                                "data": [
 | 
						|
                                    {
 | 
						|
                                        "id": 0xd7, // TrackNumber
 | 
						|
                                        "data": DEFAULT_TRACK_NUMBER
 | 
						|
                                    },
 | 
						|
                                    {
 | 
						|
                                        "id": 0x73c5, // TrackUID
 | 
						|
                                        "data": DEFAULT_TRACK_NUMBER
 | 
						|
                                    },
 | 
						|
                                    {
 | 
						|
                                        "id": 0x9c, // FlagLacing
 | 
						|
                                        "data": 0
 | 
						|
                                    },
 | 
						|
                                    {
 | 
						|
                                        "id": 0x22b59c, // Language
 | 
						|
                                        "data": "und"
 | 
						|
                                    },
 | 
						|
                                    {
 | 
						|
                                        "id": 0x86, // CodecID
 | 
						|
                                        "data": "V_VP8"
 | 
						|
                                    },
 | 
						|
                                    {
 | 
						|
                                        "id": 0x258688, // CodecName
 | 
						|
                                        "data": "VP8"
 | 
						|
                                    },
 | 
						|
                                    {
 | 
						|
                                        "id": 0x83, // TrackType
 | 
						|
                                        "data": 1
 | 
						|
                                    },
 | 
						|
                                    {
 | 
						|
                                        "id": 0xe0,  // Video
 | 
						|
                                        "data": [
 | 
						|
                                            {
 | 
						|
                                                "id": 0xb0, // PixelWidth
 | 
						|
                                                "data": videoWidth
 | 
						|
                                            },
 | 
						|
                                            {
 | 
						|
                                                "id": 0xba, // PixelHeight
 | 
						|
                                                "data": videoHeight
 | 
						|
                                            }
 | 
						|
                                        ]
 | 
						|
                                    }
 | 
						|
                                ]
 | 
						|
                            }
 | 
						|
                        ]
 | 
						|
                    };
 | 
						|
                
 | 
						|
                ebmlSegment = {
 | 
						|
                    "id": 0x18538067, // Segment
 | 
						|
                    "size": -1, // Unbounded size
 | 
						|
                    "data": [
 | 
						|
                        seekHead,
 | 
						|
                        segmentInfo,
 | 
						|
                        tracks,
 | 
						|
                    ]
 | 
						|
                };
 | 
						|
                
 | 
						|
                var
 | 
						|
                    bufferStream = new ArrayBufferDataStream(256);
 | 
						|
                    
 | 
						|
                writeEBML(bufferStream, blobBuffer.pos, [ebmlHeader, ebmlSegment]);
 | 
						|
                blobBuffer.write(bufferStream.getAsDataArray());
 | 
						|
                
 | 
						|
                // Now we know where these top-level elements lie in the file:
 | 
						|
                seekPoints.SegmentInfo.positionEBML.data = fileOffsetToSegmentRelative(segmentInfo.offset);
 | 
						|
                seekPoints.Tracks.positionEBML.data = fileOffsetToSegmentRelative(tracks.offset);
 | 
						|
            };
 | 
						|
            
 | 
						|
            /**
 | 
						|
             * Create a SimpleBlock keyframe header using these fields:
 | 
						|
             *     timecode    - Time of this keyframe
 | 
						|
             *     trackNumber - Track number from 1 to 126 (inclusive)
 | 
						|
             *     frame       - Raw frame data payload string
 | 
						|
             *
 | 
						|
             * Returns an EBML element.
 | 
						|
             */
 | 
						|
            function createKeyframeBlock(keyframe) {
 | 
						|
                var
 | 
						|
                    bufferStream = new ArrayBufferDataStream(1 + 2 + 1);
 | 
						|
                
 | 
						|
                if (!(keyframe.trackNumber > 0 && keyframe.trackNumber < 127)) {
 | 
						|
                    throw "TrackNumber must be > 0 and < 127";
 | 
						|
                }
 | 
						|
    
 | 
						|
                bufferStream.writeEBMLVarInt(keyframe.trackNumber); // Always 1 byte since we limit the range of trackNumber
 | 
						|
                bufferStream.writeU16BE(keyframe.timecode);
 | 
						|
                
 | 
						|
                // Flags byte
 | 
						|
                bufferStream.writeByte(
 | 
						|
                    1 << 7 // Keyframe
 | 
						|
                );
 | 
						|
                
 | 
						|
                return {
 | 
						|
                    "id": 0xA3, // SimpleBlock
 | 
						|
                    "data": [
 | 
						|
                         bufferStream.getAsDataArray(),
 | 
						|
                         keyframe.frame
 | 
						|
                    ]
 | 
						|
                };
 | 
						|
            }
 | 
						|
            
 | 
						|
            /**
 | 
						|
             * Create a Cluster node using these fields:
 | 
						|
             *
 | 
						|
             *    timecode    - Start time for the cluster
 | 
						|
             *
 | 
						|
             * Returns an EBML element.
 | 
						|
             */
 | 
						|
            function createCluster(cluster) {
 | 
						|
                return {
 | 
						|
                    "id": 0x1f43b675,
 | 
						|
                    "data": [
 | 
						|
                         {
 | 
						|
                            "id": 0xe7, // Timecode
 | 
						|
                            "data": Math.round(cluster.timecode)
 | 
						|
                         }
 | 
						|
                    ]
 | 
						|
                };
 | 
						|
            }
 | 
						|
            
 | 
						|
            function addCuePoint(trackIndex, clusterTime, clusterFileOffset) {
 | 
						|
                cues.push({
 | 
						|
                    "id": 0xBB, // Cue
 | 
						|
                    "data": [
 | 
						|
                         {
 | 
						|
                             "id": 0xB3, // CueTime
 | 
						|
                             "data": clusterTime
 | 
						|
                         },
 | 
						|
                         {
 | 
						|
                             "id": 0xB7, // CueTrackPositions
 | 
						|
                             "data": [
 | 
						|
                                  {
 | 
						|
                                      "id": 0xF7, // CueTrack
 | 
						|
                                      "data": trackIndex
 | 
						|
                                  },
 | 
						|
                                  {
 | 
						|
                                      "id": 0xF1, // CueClusterPosition
 | 
						|
                                      "data": fileOffsetToSegmentRelative(clusterFileOffset)
 | 
						|
                                  }
 | 
						|
                             ]
 | 
						|
                         }
 | 
						|
                    ]
 | 
						|
                });
 | 
						|
            }
 | 
						|
            
 | 
						|
            /**
 | 
						|
             * Write a Cues element to the blobStream using the global `cues` array of CuePoints (use addCuePoint()).
 | 
						|
             * The seek entry for the Cues in the SeekHead is updated.
 | 
						|
             */
 | 
						|
            function writeCues() {
 | 
						|
                var
 | 
						|
                    ebml = {
 | 
						|
                        "id": 0x1C53BB6B,
 | 
						|
                        "data": cues
 | 
						|
                    },
 | 
						|
                    
 | 
						|
                    cuesBuffer = new ArrayBufferDataStream(16 + cues.length * 32); // Pretty crude estimate of the buffer size we'll need
 | 
						|
                
 | 
						|
                writeEBML(cuesBuffer, blobBuffer.pos, ebml);
 | 
						|
                blobBuffer.write(cuesBuffer.getAsDataArray());
 | 
						|
                
 | 
						|
                // Now we know where the Cues element has ended up, we can update the SeekHead
 | 
						|
                seekPoints.Cues.positionEBML.data = fileOffsetToSegmentRelative(ebml.offset);
 | 
						|
            }
 | 
						|
            
 | 
						|
            /**
 | 
						|
             * Flush the frames in the current clusterFrameBuffer out to the stream as a Cluster.
 | 
						|
             */
 | 
						|
            function flushClusterFrameBuffer() {
 | 
						|
                if (clusterFrameBuffer.length == 0) {
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
    
 | 
						|
                // First work out how large of a buffer we need to hold the cluster data
 | 
						|
                var
 | 
						|
                    rawImageSize = 0;
 | 
						|
                
 | 
						|
                for (var i = 0; i < clusterFrameBuffer.length; i++) {
 | 
						|
                    rawImageSize += clusterFrameBuffer[i].frame.length;
 | 
						|
                }
 | 
						|
                
 | 
						|
                var
 | 
						|
                    buffer = new ArrayBufferDataStream(rawImageSize + clusterFrameBuffer.length * 32), // Estimate 32 bytes per SimpleBlock header
 | 
						|
    
 | 
						|
                    cluster = createCluster({
 | 
						|
                        timecode: Math.round(clusterStartTime),
 | 
						|
                    });
 | 
						|
                    
 | 
						|
                for (var i = 0; i < clusterFrameBuffer.length; i++) {
 | 
						|
                    cluster.data.push(createKeyframeBlock(clusterFrameBuffer[i]));
 | 
						|
                }
 | 
						|
                
 | 
						|
                writeEBML(buffer, blobBuffer.pos, cluster);
 | 
						|
                blobBuffer.write(buffer.getAsDataArray());
 | 
						|
                
 | 
						|
                addCuePoint(DEFAULT_TRACK_NUMBER, Math.round(clusterStartTime), cluster.offset);
 | 
						|
                
 | 
						|
                clusterFrameBuffer = [];
 | 
						|
                clusterStartTime += clusterDuration;
 | 
						|
                clusterDuration = 0;
 | 
						|
            }
 | 
						|
            
 | 
						|
            function validateOptions() {
 | 
						|
                // Derive frameDuration setting if not already supplied
 | 
						|
                if (!options.frameDuration) {
 | 
						|
                    if (options.frameRate) {
 | 
						|
                        options.frameDuration = 1000 / options.frameRate;
 | 
						|
                    } else {
 | 
						|
                        throw "Missing required frameDuration or frameRate setting";
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            
 | 
						|
            function addFrameToCluster(frame) {
 | 
						|
                frame.trackNumber = DEFAULT_TRACK_NUMBER;
 | 
						|
                
 | 
						|
                // Frame timecodes are relative to the start of their cluster:
 | 
						|
                frame.timecode = Math.round(clusterDuration);
 | 
						|
    
 | 
						|
                clusterFrameBuffer.push(frame);
 | 
						|
                
 | 
						|
                clusterDuration += frame.duration;
 | 
						|
                
 | 
						|
                if (clusterDuration >= MAX_CLUSTER_DURATION_MSEC) {
 | 
						|
                    flushClusterFrameBuffer();
 | 
						|
                }
 | 
						|
            }
 | 
						|
            
 | 
						|
            /**
 | 
						|
             * Rewrites the SeekHead element that was initially written to the stream with the offsets of top level elements.
 | 
						|
             *
 | 
						|
             * Call once writing is complete (so the offset of all top level elements is known).
 | 
						|
             */
 | 
						|
            function rewriteSeekHead() {
 | 
						|
                var
 | 
						|
                    seekHeadBuffer = new ArrayBufferDataStream(seekHead.size),
 | 
						|
                    oldPos = blobBuffer.pos;
 | 
						|
                
 | 
						|
                // Write the rewritten SeekHead element's data payload to the stream (don't need to update the id or size)
 | 
						|
                writeEBML(seekHeadBuffer, seekHead.dataOffset, seekHead.data);
 | 
						|
                
 | 
						|
                // And write that through to the file
 | 
						|
                blobBuffer.seek(seekHead.dataOffset);
 | 
						|
                blobBuffer.write(seekHeadBuffer.getAsDataArray());
 | 
						|
    
 | 
						|
                blobBuffer.seek(oldPos);
 | 
						|
            }
 | 
						|
            
 | 
						|
            /**
 | 
						|
             * Rewrite the Duration field of the Segment with the newly-discovered video duration.
 | 
						|
             */
 | 
						|
            function rewriteDuration() {
 | 
						|
                var
 | 
						|
                    buffer = new ArrayBufferDataStream(8),
 | 
						|
                    oldPos = blobBuffer.pos;
 | 
						|
                
 | 
						|
                // Rewrite the data payload (don't need to update the id or size)
 | 
						|
                buffer.writeDoubleBE(clusterStartTime);
 | 
						|
                
 | 
						|
                // And write that through to the file
 | 
						|
                blobBuffer.seek(segmentDuration.dataOffset);
 | 
						|
                blobBuffer.write(buffer.getAsDataArray());
 | 
						|
        
 | 
						|
                blobBuffer.seek(oldPos);
 | 
						|
            }
 | 
						|
            
 | 
						|
            /**
 | 
						|
             * Add a frame to the video. Currently the frame must be a Canvas element.
 | 
						|
             */
 | 
						|
            this.addFrame = function(canvas, duration) {
 | 
						|
                //if (writtenHeader) {
 | 
						|
                //    if (canvas.width != videoWidth || canvas.height != videoHeight) {
 | 
						|
                //        throw "Frame size differs from previous frames";
 | 
						|
                //    }
 | 
						|
                //} else {
 | 
						|
                    videoWidth = canvas.width;
 | 
						|
                    videoHeight = canvas.height;
 | 
						|
    
 | 
						|
                    writeHeader();
 | 
						|
                    writtenHeader = true;
 | 
						|
                //}
 | 
						|
    
 | 
						|
                var
 | 
						|
                    webP = renderAsWebP(canvas, options.quality);
 | 
						|
                
 | 
						|
                if (!webP) {
 | 
						|
                    throw "Couldn't decode WebP frame, does the browser support WebP?";
 | 
						|
                }
 | 
						|
                
 | 
						|
                addFrameToCluster({
 | 
						|
                    frame: extractKeyframeFromWebP(webP),
 | 
						|
                    duration: ((typeof duration == 'number')?duration:options.frameDuration)
 | 
						|
                });
 | 
						|
            };
 | 
						|
            
 | 
						|
            /**
 | 
						|
             * Finish writing the video and return a Promise to signal completion.
 | 
						|
             *
 | 
						|
             * If the destination device was memory (i.e. options.fileWriter was not supplied), the Promise is resolved with
 | 
						|
             * a Blob with the contents of the entire video.
 | 
						|
             */
 | 
						|
            this.complete = function() {
 | 
						|
                flushClusterFrameBuffer();
 | 
						|
                
 | 
						|
                writeCues();
 | 
						|
                rewriteSeekHead();
 | 
						|
                rewriteDuration();
 | 
						|
                
 | 
						|
                return blobBuffer.complete('video/webm');
 | 
						|
            };
 | 
						|
            
 | 
						|
            this.getWrittenSize = function() {
 | 
						|
                return blobBuffer.length;
 | 
						|
            };
 | 
						|
    
 | 
						|
            options = extend(optionDefaults, options || {});
 | 
						|
            validateOptions();
 | 
						|
        };
 | 
						|
    };
 | 
						|
 | 
						|
    if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
 | 
						|
	    module.exports = WebMWriter(require("./ArrayBufferDataStream"), require("./BlobBuffer"));
 | 
						|
    } else {
 | 
						|
	    window.WebMWriter = WebMWriter(ArrayBufferDataStream, BlobBuffer);
 | 
						|
    }
 | 
						|
})(); |