|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'use strict'; |
|
|
|
(function() { |
|
|
|
|
|
|
|
|
|
let ArrayBufferDataStream = function(length) { |
|
this.data = new Uint8Array(length); |
|
this.pos = 0; |
|
}; |
|
|
|
ArrayBufferDataStream.prototype.seek = function(toOffset) { |
|
this.pos = toOffset; |
|
}; |
|
|
|
ArrayBufferDataStream.prototype.writeBytes = function(arr) { |
|
for (let i = 0; i < arr.length; i++) { |
|
this.data[this.pos++] = arr[i]; |
|
} |
|
}; |
|
|
|
ArrayBufferDataStream.prototype.writeByte = function(b) { |
|
this.data[this.pos++] = b; |
|
}; |
|
|
|
|
|
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) { |
|
let bytes = new Uint8Array(new Float64Array([d]).buffer); |
|
|
|
for (let i = bytes.length - 1; i >= 0; i--) { |
|
this.writeByte(bytes[i]); |
|
} |
|
}; |
|
|
|
ArrayBufferDataStream.prototype.writeFloatBE = function(d) { |
|
let bytes = new Uint8Array(new Float32Array([d]).buffer); |
|
|
|
for (let i = bytes.length - 1; i >= 0; i--) { |
|
this.writeByte(bytes[i]); |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
ArrayBufferDataStream.prototype.writeString = function(s) { |
|
for (let i = 0; i < s.length; i++) { |
|
this.data[this.pos++] = s.charCodeAt(i); |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
|
|
|
|
|
|
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 Error('Bad EBML VINT size ' + width); |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
ArrayBufferDataStream.prototype.measureEBMLVarInt = function(val) { |
|
if (val < (1 << 7) - 1) { |
|
|
|
|
|
|
|
|
|
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) { |
|
return 5; |
|
} else { |
|
throw new Error('EBML VINT size not supported ' + val); |
|
} |
|
}; |
|
|
|
ArrayBufferDataStream.prototype.writeEBMLVarInt = function(i) { |
|
this.writeEBMLVarIntWidth(i, this.measureEBMLVarInt(i)); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ArrayBufferDataStream.prototype.writeUnsignedIntBE = function(u, width) { |
|
if (width === undefined) { |
|
width = this.measureUnsignedInt(u); |
|
} |
|
|
|
|
|
switch (width) { |
|
case 5: |
|
this.writeU8( |
|
Math.floor(u / 4294967296)); |
|
|
|
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 Error('Bad UINT size ' + width); |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
ArrayBufferDataStream.prototype.measureUnsignedInt = function(val) { |
|
|
|
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; |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
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 new Error('ArrayBufferDataStream\'s pos lies beyond end of buffer'); |
|
} |
|
}; |
|
|
|
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { |
|
module.exports = ArrayBufferDataStream; |
|
} else { |
|
self.ArrayBufferDataStream = ArrayBufferDataStream; |
|
} |
|
}()); |
|
'use strict'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function() { |
|
let BlobBuffer = function(fs) { |
|
return function(destination) { |
|
let buffer = [], writePromise = Promise.resolve(), fileWriter = null, |
|
fd = null; |
|
|
|
if (destination && |
|
destination.constructor.name === 'FileSystemWritableFileStream') { |
|
fileWriter = destination; |
|
} else if (fs && destination) { |
|
fd = destination; |
|
} |
|
|
|
|
|
this.pos = 0; |
|
|
|
|
|
this.length = 0; |
|
|
|
|
|
function readBlobAsBuffer(blob) { |
|
return new Promise(function(resolve, reject) { |
|
let 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 { |
|
|
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
writePromise = writePromise.then(async function() { |
|
if (fd) { |
|
return new Promise(function(resolve, reject) { |
|
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 { |
|
|
|
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.seek(newEntry.offset) |
|
.then(() => {fileWriter.write(new Blob([newEntry.data]))}) |
|
.then(() => {resolve(); |
|
}) |
|
}); |
|
} else if (!isAppend) { |
|
|
|
|
|
|
|
for (let i = 0; i < buffer.length; i++) { |
|
let entry = buffer[i]; |
|
|
|
|
|
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) { |
|
|
|
entry.data = newEntry.data; |
|
|
|
|
|
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); |
|
}); |
|
} |
|
} |
|
} |
|
|
|
|
|
} |
|
|
|
buffer.push(newEntry); |
|
}); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.complete = function(mimeType) { |
|
if (fd || fileWriter) { |
|
writePromise = writePromise.then(function() { |
|
return null; |
|
}); |
|
} else { |
|
|
|
|
|
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; |
|
}; |
|
}; |
|
}; |
|
|
|
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { |
|
module.exports = BlobBuffer(require('fs')); |
|
} else { |
|
self.BlobBuffer = BlobBuffer(null); |
|
} |
|
})(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'use strict'; |
|
|
|
(function() { |
|
function extend(base, top) { |
|
let target = {}; |
|
|
|
[base, top].forEach(function(obj) { |
|
for (let prop in obj) { |
|
if (Object.prototype.hasOwnProperty.call(obj, prop)) { |
|
target[prop] = obj[prop]; |
|
} |
|
} |
|
}); |
|
|
|
return target; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function byteStringToUint32LE(string) { |
|
let a = string.charCodeAt(0), b = string.charCodeAt(1), |
|
c = string.charCodeAt(2), d = string.charCodeAt(3); |
|
|
|
return (a | (b << 8) | (c << 16) | (d << 24)) >>> 0; |
|
} |
|
|
|
|
|
|
|
|
|
function EBMLFloat32(value) { |
|
this.value = value; |
|
} |
|
|
|
function EBMLFloat64(value) { |
|
this.value = value; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function writeEBML(buffer, bufferFileOffset, ebml) { |
|
|
|
if (Array.isArray(ebml)) { |
|
for (let i = 0; i < ebml.length; i++) { |
|
writeEBML(buffer, bufferFileOffset, ebml[i]); |
|
} |
|
|
|
} else if (typeof ebml === 'string') { |
|
buffer.writeString(ebml); |
|
} else if (ebml instanceof Uint8Array) { |
|
buffer.writeBytes(ebml); |
|
} else if (ebml.id) { |
|
|
|
ebml.offset = buffer.pos + bufferFileOffset; |
|
|
|
buffer.writeUnsignedIntBE(ebml.id); |
|
|
|
|
|
|
|
if (Array.isArray(ebml.data)) { |
|
|
|
|
|
|
|
let sizePos, dataBegin, dataEnd; |
|
|
|
if (ebml.size === -1) { |
|
|
|
|
|
buffer.writeByte(0xFF); |
|
} else { |
|
sizePos = buffer.pos; |
|
|
|
|
|
|
|
|
|
|
|
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); |
|
|
|
buffer.seek(dataEnd); |
|
} |
|
} else if (typeof ebml.data === 'string') { |
|
buffer.writeEBMLVarInt(ebml.data.length); |
|
ebml.dataOffset = buffer.pos + bufferFileOffset; |
|
buffer.writeString(ebml.data); |
|
} else if (typeof ebml.data === 'number') { |
|
|
|
|
|
if (!ebml.size) { |
|
ebml.size = buffer.measureUnsignedInt(ebml.data); |
|
} |
|
|
|
buffer.writeEBMLVarInt(ebml.size); |
|
ebml.dataOffset = buffer.pos + bufferFileOffset; |
|
buffer.writeUnsignedIntBE(ebml.data, ebml.size); |
|
} else if (ebml.data instanceof EBMLFloat64) { |
|
buffer.writeEBMLVarInt(8); |
|
ebml.dataOffset = buffer.pos + bufferFileOffset; |
|
buffer.writeDoubleBE(ebml.data.value); |
|
} else if (ebml.data instanceof EBMLFloat32) { |
|
buffer.writeEBMLVarInt(4); |
|
ebml.dataOffset = buffer.pos + bufferFileOffset; |
|
buffer.writeFloatBE(ebml.data.value); |
|
} else if (ebml.data instanceof Uint8Array) { |
|
buffer.writeEBMLVarInt(ebml.data.byteLength); |
|
ebml.dataOffset = buffer.pos + bufferFileOffset; |
|
buffer.writeBytes(ebml.data); |
|
} else { |
|
throw new Error('Bad EBML datatype ' + typeof ebml.data); |
|
} |
|
} else { |
|
throw new Error('Bad EBML datatype ' + typeof ebml.data); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let WebMWriter = function(ArrayBufferDataStream, BlobBuffer) { |
|
return function(options) { |
|
let MAX_CLUSTER_DURATION_MSEC = 5000000, DEFAULT_TRACK_NUMBER = 1, |
|
writtenHeader = false, videoWidth = 0, videoHeight = 0, |
|
firstTimestampEver = true, earliestTimestamp = 0, |
|
|
|
|
|
|
|
|
|
|
|
|
|
clusterFrameBuffer = [], clusterStartTime = 0, clusterDuration = 0, |
|
lastTimeCode = 0, |
|
|
|
optionDefaults = { |
|
fileWriter: null, |
|
|
|
fd: null, |
|
|
|
codec: 'VP8', |
|
|
|
}, |
|
|
|
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, |
|
'data': new EBMLFloat64(0) |
|
}, |
|
|
|
seekHead, |
|
|
|
cues = [], |
|
|
|
blobBuffer = new BlobBuffer(options.fileWriter || options.fd); |
|
|
|
function fileOffsetToSegmentRelative(fileOffset) { |
|
return fileOffset - ebmlSegment.dataOffset; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function createSeekHead() { |
|
let seekPositionEBMLTemplate = { |
|
'id': 0x53AC, |
|
'size': 5, |
|
'data': 0 |
|
}, |
|
|
|
result = { |
|
'id': 0x114D9B74, |
|
'data': [] |
|
}; |
|
|
|
for (let name in seekPoints) { |
|
let seekPoint = seekPoints[name]; |
|
|
|
seekPoint.positionEBML = Object.create(seekPositionEBMLTemplate); |
|
|
|
result.data.push({ |
|
'id': 0x4DBB, |
|
'data': [ |
|
{ |
|
'id': 0x53AB, |
|
'data': seekPoint.id |
|
}, |
|
seekPoint.positionEBML |
|
] |
|
}); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
|
|
|
|
|
|
function writeHeader() { |
|
seekHead = createSeekHead(); |
|
|
|
let ebmlHeader = { |
|
'id': 0x1a45dfa3, |
|
'data': [ |
|
{ |
|
'id': 0x4286, |
|
'data': 1 |
|
}, |
|
{ |
|
'id': 0x42f7, |
|
'data': 1 |
|
}, |
|
{ |
|
'id': 0x42f2, |
|
'data': 4 |
|
}, |
|
{ |
|
'id': 0x42f3, |
|
'data': 8 |
|
}, |
|
{ |
|
'id': 0x4282, |
|
'data': 'webm' |
|
}, |
|
{ |
|
'id': 0x4287, |
|
'data': 2 |
|
}, |
|
{ |
|
'id': 0x4285, |
|
'data': 2 |
|
} |
|
] |
|
}, |
|
|
|
segmentInfo = { |
|
'id': 0x1549a966, |
|
'data': [ |
|
{ |
|
'id': 0x2ad7b1, |
|
'data': 1e6 |
|
|
|
}, |
|
{ |
|
'id': 0x4d80, |
|
'data': 'webm-writer-js', |
|
}, |
|
{ |
|
'id': 0x5741, |
|
'data': 'webm-writer-js' |
|
}, |
|
segmentDuration |
|
] |
|
}, |
|
|
|
videoProperties = [ |
|
{ |
|
'id': 0xb0, |
|
'data': videoWidth |
|
}, |
|
{ |
|
'id': 0xba, |
|
'data': videoHeight |
|
} |
|
]; |
|
|
|
let tracks = { |
|
'id': 0x1654ae6b, |
|
'data': [{ |
|
'id': 0xae, |
|
'data': [ |
|
{ |
|
'id': 0xd7, |
|
'data': DEFAULT_TRACK_NUMBER |
|
}, |
|
{ |
|
'id': 0x73c5, |
|
'data': DEFAULT_TRACK_NUMBER |
|
}, |
|
{ |
|
'id': 0x83, |
|
'data': 1 |
|
}, |
|
{ |
|
'id': 0xe0, |
|
'data': videoProperties |
|
}, |
|
{ |
|
'id': 0x9c, |
|
'data': 0 |
|
}, |
|
{ |
|
'id': 0x22b59c, |
|
'data': 'und' |
|
}, |
|
{ |
|
'id': 0xb9, |
|
'data': 1 |
|
}, |
|
{ |
|
'id': 0x88, |
|
'data': 1 |
|
}, |
|
{ |
|
'id': 0x55aa, |
|
'data': 0 |
|
}, |
|
|
|
{ |
|
'id': 0x86, |
|
'data': 'V_' + options.codec |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
] |
|
}] |
|
}; |
|
|
|
ebmlSegment = { |
|
'id': 0x18538067, |
|
'size': -1, |
|
'data': [ |
|
seekHead, |
|
segmentInfo, |
|
tracks, |
|
] |
|
}; |
|
|
|
let bufferStream = new ArrayBufferDataStream(256); |
|
|
|
writeEBML(bufferStream, blobBuffer.pos, [ebmlHeader, ebmlSegment]); |
|
blobBuffer.write(bufferStream.getAsDataArray()); |
|
|
|
|
|
seekPoints.SegmentInfo.positionEBML.data = |
|
fileOffsetToSegmentRelative(segmentInfo.offset); |
|
seekPoints.Tracks.positionEBML.data = |
|
fileOffsetToSegmentRelative(tracks.offset); |
|
|
|
writtenHeader = true; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function createSimpleBlockForframe(frame) { |
|
let bufferStream = new ArrayBufferDataStream(1 + 2 + 1); |
|
|
|
if (!(frame.trackNumber > 0 && frame.trackNumber < 127)) { |
|
throw new Error('TrackNumber must be > 0 and < 127'); |
|
} |
|
|
|
bufferStream.writeEBMLVarInt( |
|
frame.trackNumber); |
|
|
|
bufferStream.writeU16BE(frame.timecode); |
|
|
|
|
|
bufferStream.writeByte( |
|
(frame.type == "key" ? 1 : 0) << 7 |
|
); |
|
|
|
return { |
|
'id': 0xA3, |
|
'data': [bufferStream.getAsDataArray(), frame.frame] |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function createCluster(cluster) { |
|
return { |
|
'id': 0x1f43b675, |
|
'data': [{ |
|
'id': 0xe7, |
|
'data': Math.round(cluster.timecode) |
|
}] |
|
}; |
|
} |
|
|
|
function addCuePoint(trackIndex, clusterTime, clusterFileOffset) { |
|
cues.push({ |
|
'id': 0xBB, |
|
'data': [ |
|
{ |
|
'id': 0xB3, |
|
'data': clusterTime |
|
}, |
|
{ |
|
'id': 0xB7, |
|
'data': [ |
|
{ |
|
'id': 0xF7, |
|
'data': trackIndex |
|
}, |
|
{ |
|
'id': 0xF1, |
|
'data': fileOffsetToSegmentRelative(clusterFileOffset) |
|
} |
|
] |
|
} |
|
] |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function writeCues() { |
|
let ebml = {'id': 0x1C53BB6B, 'data': cues}, |
|
|
|
cuesBuffer = new ArrayBufferDataStream( |
|
16 + |
|
cues.length * |
|
32); |
|
|
|
writeEBML(cuesBuffer, blobBuffer.pos, ebml); |
|
blobBuffer.write(cuesBuffer.getAsDataArray()); |
|
|
|
|
|
|
|
seekPoints.Cues.positionEBML.data = |
|
fileOffsetToSegmentRelative(ebml.offset); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function flushClusterFrameBuffer() { |
|
if (clusterFrameBuffer.length === 0) { |
|
return; |
|
} |
|
|
|
|
|
let rawImageSize = 0; |
|
|
|
for (let i = 0; i < clusterFrameBuffer.length; i++) { |
|
rawImageSize += clusterFrameBuffer[i].frame.byteLength; |
|
} |
|
|
|
let buffer = new ArrayBufferDataStream( |
|
rawImageSize + |
|
clusterFrameBuffer.length * |
|
64), |
|
|
|
cluster = createCluster({ |
|
timecode: Math.round(clusterStartTime), |
|
}); |
|
|
|
for (let i = 0; i < clusterFrameBuffer.length; i++) { |
|
cluster.data.push(createSimpleBlockForframe(clusterFrameBuffer[i])); |
|
} |
|
|
|
writeEBML(buffer, blobBuffer.pos, cluster); |
|
blobBuffer.write(buffer.getAsDataArray()); |
|
|
|
addCuePoint( |
|
DEFAULT_TRACK_NUMBER, Math.round(clusterStartTime), cluster.offset); |
|
|
|
clusterFrameBuffer = []; |
|
clusterDuration = 0; |
|
} |
|
|
|
function validateOptions() { |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function addFrameToCluster(frame) { |
|
frame.trackNumber = DEFAULT_TRACK_NUMBER; |
|
var time = frame.intime / 1000; |
|
if (firstTimestampEver) { |
|
earliestTimestamp = time; |
|
time = 0; |
|
firstTimestampEver = false; |
|
} else { |
|
time = time - earliestTimestamp; |
|
} |
|
lastTimeCode = time; |
|
if (clusterDuration == 0) clusterStartTime = time; |
|
|
|
|
|
|
|
frame.timecode = Math.round(time - clusterStartTime); |
|
|
|
clusterFrameBuffer.push(frame); |
|
clusterDuration = frame.timecode + 1; |
|
|
|
if (clusterDuration >= MAX_CLUSTER_DURATION_MSEC) { |
|
flushClusterFrameBuffer(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function rewriteSeekHead() { |
|
let seekHeadBuffer = new ArrayBufferDataStream(seekHead.size), |
|
oldPos = blobBuffer.pos; |
|
|
|
|
|
|
|
writeEBML(seekHeadBuffer, seekHead.dataOffset, seekHead.data); |
|
|
|
|
|
blobBuffer.seek(seekHead.dataOffset); |
|
blobBuffer.write(seekHeadBuffer.getAsDataArray()); |
|
blobBuffer.seek(oldPos); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function rewriteDuration() { |
|
let buffer = new ArrayBufferDataStream(8), oldPos = blobBuffer.pos; |
|
|
|
|
|
buffer.writeDoubleBE(lastTimeCode); |
|
|
|
|
|
blobBuffer.seek(segmentDuration.dataOffset); |
|
blobBuffer.write(buffer.getAsDataArray()); |
|
|
|
blobBuffer.seek(oldPos); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.addFrame = function(frame) { |
|
if (!writtenHeader) { |
|
videoWidth = options.width; |
|
videoHeight = options.height; |
|
writeHeader(); |
|
} |
|
if (frame.constructor.name == 'EncodedVideoChunk') { |
|
let frameData = new Uint8Array(frame.byteLength); |
|
frame.copyTo(frameData); |
|
addFrameToCluster({ |
|
frame: frameData, |
|
intime: frame.timestamp, |
|
type: frame.type, |
|
}); |
|
return; |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.complete = function() { |
|
if (!writtenHeader) { |
|
writeHeader(); |
|
} |
|
firstTimestampEver = true; |
|
|
|
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 { |
|
self.WebMWriter = |
|
WebMWriter(self.ArrayBufferDataStream, self.BlobBuffer); |
|
} |
|
})(); |
|
|