googlevideo
Version:
A set of utilities for working with Google Video APIs.
126 lines (125 loc) • 5.36 kB
JavaScript
export class UMP {
/**
* Creates a new UMP parser.
* @param chunkedDataBuffer - Buffer containing UMP format data.
*/
constructor(chunkedDataBuffer) {
this.chunkedDataBuffer = chunkedDataBuffer;
}
/**
* Parses parts from the buffer and calls the handler for each complete part.
* @param handlePart - Function called with each complete part.
* @returns Partial part if parsing is incomplete, undefined otherwise.
*/
parse(handlePart) {
while (true) {
let offset = 0;
const [partType, newOffset] = this.readVarInt(offset);
offset = newOffset;
const [partSize, finalOffset] = this.readVarInt(offset);
offset = finalOffset;
if (partType < 0 || partSize < 0)
break;
if (!this.chunkedDataBuffer.canReadBytes(offset, partSize)) {
if (!this.chunkedDataBuffer.canReadBytes(offset, 1))
break;
return {
type: partType,
size: partSize,
data: this.chunkedDataBuffer
};
}
const splitResult = this.chunkedDataBuffer.split(offset).remainingBuffer.split(partSize);
offset = 0;
handlePart({
type: partType,
size: partSize,
data: splitResult.extractedBuffer
});
this.chunkedDataBuffer = splitResult.remainingBuffer;
}
}
/**
* Reads a variable-length integer from the buffer.
* @param offset - Position to start reading from.
* @returns Tuple of [value, new offset] or [-1, offset] if incomplete.
*/
readVarInt(offset) {
let byteLength;
// Determine the length of the val
if (this.chunkedDataBuffer.canReadBytes(offset, 1)) {
const firstByte = this.chunkedDataBuffer.getUint8(offset);
byteLength = firstByte < 128 ? 1 : firstByte < 192 ? 2 : firstByte < 224 ? 3 : firstByte < 240 ? 4 : 5;
}
else {
byteLength = 0;
}
if (byteLength < 1 || !this.chunkedDataBuffer.canReadBytes(offset, byteLength)) {
return [-1, offset];
}
let value;
// Now read it based on the length
switch (byteLength) {
case 1:
value = this.chunkedDataBuffer.getUint8(offset++);
break;
case 2: {
const byte1 = this.chunkedDataBuffer.getUint8(offset++);
const byte2 = this.chunkedDataBuffer.getUint8(offset++);
value = (byte1 & 0x3f) + 64 * byte2;
break;
}
case 3: {
const byte1 = this.chunkedDataBuffer.getUint8(offset++);
const byte2 = this.chunkedDataBuffer.getUint8(offset++);
const byte3 = this.chunkedDataBuffer.getUint8(offset++);
value = (byte1 & 0x1f) + 32 * (byte2 + 256 * byte3);
break;
}
case 4: {
const byte1 = this.chunkedDataBuffer.getUint8(offset++);
const byte2 = this.chunkedDataBuffer.getUint8(offset++);
const byte3 = this.chunkedDataBuffer.getUint8(offset++);
const byte4 = this.chunkedDataBuffer.getUint8(offset++);
value = (byte1 & 0x0f) + 16 * (byte2 + 256 * (byte3 + 256 * byte4));
break;
}
default: {
const tempOffset = offset + 1;
this.chunkedDataBuffer.focus(tempOffset);
if (this.canReadFromCurrentChunk(tempOffset, 4)) {
value = this.getCurrentDataView().getUint32(tempOffset - this.chunkedDataBuffer.currentChunkOffset, true);
}
else {
const byte3 = this.chunkedDataBuffer.getUint8(tempOffset + 2) + 256 * this.chunkedDataBuffer.getUint8(tempOffset + 3);
value =
this.chunkedDataBuffer.getUint8(tempOffset) +
256 * (this.chunkedDataBuffer.getUint8(tempOffset + 1) + 256 * byte3);
}
offset += 5;
break;
}
}
return [value, offset];
}
/**
* Checks if the specified bytes can be read from the current chunk.
* @param offset - Position to start reading from.
* @param length - Number of bytes to read.
* @returns True if bytes can be read from current chunk, false otherwise.
*/
canReadFromCurrentChunk(offset, length) {
return offset - this.chunkedDataBuffer.currentChunkOffset + length <= this.chunkedDataBuffer.chunks[this.chunkedDataBuffer.currentChunkIndex].length;
}
/**
* Gets a DataView of the current chunk, creating it if necessary.
* @returns DataView for the current chunk.
*/
getCurrentDataView() {
if (!this.chunkedDataBuffer.currentDataView) {
const currentChunk = this.chunkedDataBuffer.chunks[this.chunkedDataBuffer.currentChunkIndex];
this.chunkedDataBuffer.currentDataView = new DataView(currentChunk.buffer, currentChunk.byteOffset, currentChunk.length);
}
return this.chunkedDataBuffer.currentDataView;
}
}