p2p-media-loader-core
Version:
P2P Media Loader core functionality
225 lines • 8.51 kB
JavaScript
import * as Serialization from "./binary-serialization.js";
const FRAME_PART_LENGTH = 4;
const commandFrameStart = stringToUtf8CodesBuffer("cstr", FRAME_PART_LENGTH);
const commandFrameEnd = stringToUtf8CodesBuffer("cend", FRAME_PART_LENGTH);
const commandDivFrameStart = stringToUtf8CodesBuffer("dstr", FRAME_PART_LENGTH);
const commandDivFrameEnd = stringToUtf8CodesBuffer("dend", FRAME_PART_LENGTH);
const startFrames = [commandFrameStart, commandDivFrameStart];
const endFrames = [commandFrameEnd, commandDivFrameEnd];
const commandFramesLength = commandFrameStart.length + commandFrameEnd.length;
export function isCommandChunk(buffer) {
const { length } = commandFrameStart;
const bufferEndingToCompare = buffer.slice(-length);
return (startFrames.some((frame) => areBuffersEqual(buffer, frame, FRAME_PART_LENGTH)) &&
endFrames.some((frame) => areBuffersEqual(bufferEndingToCompare, frame, FRAME_PART_LENGTH)));
}
function isFirstCommandChunk(buffer) {
return areBuffersEqual(buffer, commandFrameStart, FRAME_PART_LENGTH);
}
function isLastCommandChunk(buffer) {
return areBuffersEqual(buffer.slice(-FRAME_PART_LENGTH), commandFrameEnd, FRAME_PART_LENGTH);
}
export class BinaryCommandJoiningError extends Error {
constructor(type) {
super();
Object.defineProperty(this, "type", {
enumerable: true,
configurable: true,
writable: true,
value: type
});
}
}
export class BinaryCommandChunksJoiner {
constructor(onComplete) {
Object.defineProperty(this, "onComplete", {
enumerable: true,
configurable: true,
writable: true,
value: onComplete
});
Object.defineProperty(this, "chunks", {
enumerable: true,
configurable: true,
writable: true,
value: new Serialization.ResizableUint8Array()
});
Object.defineProperty(this, "status", {
enumerable: true,
configurable: true,
writable: true,
value: "joining"
});
}
addCommandChunk(chunk) {
if (this.status === "completed")
return;
const isFirstChunk = isFirstCommandChunk(chunk);
if (!this.chunks.length && !isFirstChunk) {
throw new BinaryCommandJoiningError("no-first-chunk");
}
if (this.chunks.length && isFirstChunk) {
throw new BinaryCommandJoiningError("incomplete-joining");
}
this.chunks.push(this.unframeCommandChunk(chunk));
if (!isLastCommandChunk(chunk))
return;
this.status = "completed";
this.onComplete(this.chunks.getBuffer());
}
unframeCommandChunk(chunk) {
return chunk.slice(FRAME_PART_LENGTH, chunk.length - FRAME_PART_LENGTH);
}
}
export class BinaryCommandCreator {
constructor(commandType, maxChunkLength) {
Object.defineProperty(this, "maxChunkLength", {
enumerable: true,
configurable: true,
writable: true,
value: maxChunkLength
});
Object.defineProperty(this, "bytes", {
enumerable: true,
configurable: true,
writable: true,
value: new Serialization.ResizableUint8Array()
});
Object.defineProperty(this, "resultBuffers", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "status", {
enumerable: true,
configurable: true,
writable: true,
value: "creating"
});
this.bytes.push(commandType);
}
addInteger(name, value) {
this.bytes.push(name.charCodeAt(0));
const bytes = Serialization.serializeInt(BigInt(value));
this.bytes.push(bytes);
}
addSimilarIntArr(name, arr) {
this.bytes.push(name.charCodeAt(0));
const bytes = Serialization.serializeSimilarIntArray(arr.map((num) => BigInt(num)));
this.bytes.push(bytes);
}
addString(name, string) {
this.bytes.push(name.charCodeAt(0));
const bytes = Serialization.serializeString(string);
this.bytes.push(bytes);
}
complete() {
if (!this.bytes.length)
throw new Error("Buffer is empty");
if (this.status === "completed")
return;
this.status = "completed";
const unframedBuffer = this.bytes.getBuffer();
if (unframedBuffer.length + commandFramesLength <= this.maxChunkLength) {
this.resultBuffers.push(frameBuffer(unframedBuffer, commandFrameStart, commandFrameEnd));
return;
}
let chunksCount = Math.ceil(unframedBuffer.length / this.maxChunkLength);
if (Math.ceil(unframedBuffer.length / chunksCount) + commandFramesLength >
this.maxChunkLength) {
chunksCount++;
}
for (const [i, chunk] of splitBufferToEqualChunks(unframedBuffer, chunksCount)) {
if (i === 0) {
this.resultBuffers.push(frameBuffer(chunk, commandFrameStart, commandDivFrameEnd));
}
else if (i === chunksCount - 1) {
this.resultBuffers.push(frameBuffer(chunk, commandDivFrameStart, commandFrameEnd));
}
else {
this.resultBuffers.push(frameBuffer(chunk, commandDivFrameStart, commandDivFrameEnd));
}
}
}
getResultBuffers() {
if (this.status === "creating" || !this.resultBuffers.length) {
throw new Error("Command is not complete.");
}
return this.resultBuffers;
}
}
export function deserializeCommand(bytes) {
const [commandCode] = bytes;
const deserializedCommand = {
c: commandCode,
};
let offset = 1;
while (offset < bytes.length) {
const name = String.fromCharCode(bytes[offset]);
offset++;
const dataType = getDataTypeFromByte(bytes[offset]);
switch (dataType) {
case Serialization.SerializedItem.Int:
{
const { number, byteLength } = Serialization.deserializeInt(bytes.slice(offset));
deserializedCommand[name] = Number(number);
offset += byteLength;
}
break;
case Serialization.SerializedItem.SimilarIntArray:
{
const { numbers, byteLength } = Serialization.deserializeSimilarIntArray(bytes.slice(offset));
deserializedCommand[name] = numbers.map((n) => Number(n));
offset += byteLength;
}
break;
case Serialization.SerializedItem.String:
{
const { string, byteLength } = Serialization.deserializeString(bytes.slice(offset));
deserializedCommand[name] = string;
offset += byteLength;
}
break;
}
}
return deserializedCommand;
}
function getDataTypeFromByte(byte) {
const typeCode = byte >> 4;
if (typeCode <= Serialization.SerializedItem.Min ||
typeCode >= Serialization.SerializedItem.Max) {
throw new Error("Not existing type");
}
return typeCode;
}
function stringToUtf8CodesBuffer(string, length) {
if (length && string.length !== length) {
throw new Error("Wrong string length");
}
const buffer = new Uint8Array(length ?? string.length);
for (let i = 0; i < string.length; i++)
buffer[i] = string.charCodeAt(i);
return buffer;
}
function* splitBufferToEqualChunks(buffer, chunksCount) {
const chunkLength = Math.ceil(buffer.length / chunksCount);
for (let i = 0; i < chunksCount; i++) {
yield [i, buffer.slice(i * chunkLength, (i + 1) * chunkLength)];
}
}
function frameBuffer(buffer, frameStart, frameEnd) {
const result = new Uint8Array(buffer.length + frameStart.length + frameEnd.length);
result.set(frameStart);
result.set(buffer, frameStart.length);
result.set(frameEnd, frameStart.length + buffer.length);
return result;
}
function areBuffersEqual(buffer1, buffer2, length) {
for (let i = 0; i < length; i++) {
if (buffer1[i] !== buffer2[i])
return false;
}
return true;
}
//# sourceMappingURL=binary-command-creator.js.map