Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Binary serialization for p2p messages. #312

Merged
merged 138 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
138 commits
Select commit Hold shift + click to select a range
c78c60c
Fix issue with wrong segment endTime.
i-zolotarenko Aug 21, 2023
eb21613
Fix issue with hls wrong segment local id.
i-zolotarenko Aug 21, 2023
99c234d
Add linked-map, load queue and http-loader.
i-zolotarenko Aug 23, 2023
40881d6
Add load queue class.
i-zolotarenko Aug 27, 2023
02a07fe
Add segments storage.
i-zolotarenko Aug 28, 2023
30ed915
Add segments load logic.
i-zolotarenko Aug 30, 2023
2dc9029
Add plugin requests promises map.
i-zolotarenko Aug 31, 2023
3ea3362
Rewrite load queue.
i-zolotarenko Aug 31, 2023
2ff4aa6
Move segment queue status determination to queue class.
i-zolotarenko Aug 31, 2023
77f6804
Separate main and secondary stream to different contexts.
i-zolotarenko Sep 1, 2023
d933e7a
Remove unnecessary loader layer.
i-zolotarenko Sep 1, 2023
72d33eb
Fix issue with not loading after moving playhead to random position.
i-zolotarenko Sep 4, 2023
f6713b7
Rename hybrid loader file.
i-zolotarenko Sep 4, 2023
ff79e37
Initialize playback on first segment request by plugin.
i-zolotarenko Sep 4, 2023
fe3ea0e
Make storage async. Add core destroy logic.
i-zolotarenko Sep 4, 2023
e13d358
Use StreamWithReadonlySegments type in plugins.
i-zolotarenko Sep 5, 2023
7c4197d
Add bandwidth approximator.
i-zolotarenko Sep 5, 2023
72e426a
Refactor load queue code.
i-zolotarenko Sep 5, 2023
03a388b
Load controlling: pure functions style. (#302)
i-zolotarenko Sep 7, 2023
9ac874f
Install bittorrent-tracker and ripemd160 to core.
i-zolotarenko Sep 12, 2023
81e8521
Create type declaration for bittorrent tracker.
i-zolotarenko Sep 12, 2023
5f899c7
Create P2PLoader and Peer classes.
i-zolotarenko Sep 12, 2023
9d7d6f3
Move enums to separate file.
i-zolotarenko Sep 12, 2023
08f4c23
Remove unused position core property.
i-zolotarenko Sep 13, 2023
23e9abc
Create secondary hybrid loader only if necessary.
i-zolotarenko Sep 13, 2023
fb5b0ea
Add force parameter to process queue hybrid loader method.
i-zolotarenko Sep 13, 2023
ffab215
Merge remote-tracking branch 'origin/load-controlling' into p2p-manager
i-zolotarenko Sep 13, 2023
d0da2ae
Add sendCommand method to Peer.
i-zolotarenko Sep 13, 2023
e294cad
Add request type.
i-zolotarenko Sep 13, 2023
d67e4c8
Create and apply RequestContainer class.
i-zolotarenko Sep 14, 2023
7b61d44
Rename settings time window properties.
i-zolotarenko Sep 15, 2023
90f5d1d
Add AbortError.
i-zolotarenko Sep 15, 2023
db389b0
Merge with v1
i-zolotarenko Sep 15, 2023
3e05601
Merge with v1
i-zolotarenko Sep 15, 2023
1cb7353
Add streams map to hybrid loader.
i-zolotarenko Sep 15, 2023
a18826e
Add peer self segments map generation logic.
i-zolotarenko Sep 18, 2023
70bff75
Notify peers on segment loadings update.
i-zolotarenko Sep 18, 2023
f61b852
Fix types issues.
i-zolotarenko Sep 18, 2023
3305c19
Add peer events.
i-zolotarenko Sep 18, 2023
676ddae
Merge with requests-container.
i-zolotarenko Sep 18, 2023
a2cee74
Add downloadSegment method to p2p-loader. Use p2p request type in Pee…
i-zolotarenko Sep 18, 2023
b93677a
Use request container in p2p-loader.
i-zolotarenko Sep 19, 2023
098b10a
Add receiving segment chunks logic.
i-zolotarenko Sep 19, 2023
7cf3524
Reject promise with errors in peer class.
i-zolotarenko Sep 19, 2023
bb22472
Rewrite fetch segment with async await.
i-zolotarenko Sep 19, 2023
486058f
Use object with booleans instead of set for segment queue statuses.
i-zolotarenko Sep 19, 2023
8ef7dae
Rename fetch segment function.
i-zolotarenko Sep 19, 2023
920db7d
Rename hybrid loader public method.
i-zolotarenko Sep 19, 2023
7783df1
Use engine callbacks instead of promise creation in core.
i-zolotarenko Sep 20, 2023
34e6881
Merge pull request #306 from Novage/requests-container-p2p-integration
i-zolotarenko Sep 20, 2023
4ad6e5e
Remove unnecessary getControlledPromise function from engines.
i-zolotarenko Sep 20, 2023
aa369ab
Merge branch 'requests-container-p2p-integration' into p2p-manager
i-zolotarenko Sep 20, 2023
2bb356e
Create hybrid loaders when necessary data is already set.
i-zolotarenko Sep 20, 2023
695deca
Add progress to http loading.
i-zolotarenko Sep 20, 2023
ddf2265
Add progress functionality to Peer class.
i-zolotarenko Sep 20, 2023
7bd2267
Fix type issues.
i-zolotarenko Sep 20, 2023
6fb3fbe
Use only callbacks instead of promise creation in hls engine.
i-zolotarenko Sep 21, 2023
ffc4424
Add segment storage initialization.
i-zolotarenko Sep 21, 2023
513b361
Add peer settings options.
i-zolotarenko Sep 21, 2023
6e6e108
Move storage cleanup logic into storage class.
i-zolotarenko Sep 21, 2023
541d78b
Add p2p loaders container.
i-zolotarenko Sep 22, 2023
7893230
Add p2p loaders destroy logic.
i-zolotarenko Sep 22, 2023
83d067f
Remove unnecessary toString on string variable.
i-zolotarenko Sep 22, 2023
c6419ae
Don't use response.arrayBuffer() if getting data from stream.
i-zolotarenko Sep 25, 2023
b443b03
Change peer announcement type and corresponding logic.
i-zolotarenko Sep 25, 2023
0c8af4f
Use single PeerRequestError with different types instead of lot of Er…
i-zolotarenko Sep 25, 2023
1938c55
Change peer announcement type and corresponding logic.
i-zolotarenko Sep 25, 2023
f966ca4
Remove memory request container counters.
i-zolotarenko Sep 26, 2023
a0864cf
Remove unnecessary segmentsToDelete from memory segment storage.
i-zolotarenko Sep 26, 2023
093f87b
Fix issue with typo in "chunk"
i-zolotarenko Sep 27, 2023
7363354
Add ability to subscribe to segments store update.
i-zolotarenko Sep 27, 2023
e5e24c9
Merge with origin/p2p-manager.
i-zolotarenko Sep 28, 2023
ff29655
Add stale p2p loader destroy timeout.
i-zolotarenko Sep 28, 2023
fe58ea0
Add request container http requests subscriptions.
i-zolotarenko Sep 28, 2023
978d95d
Add vite-plugin-node-polyfills to demo.
i-zolotarenko Sep 28, 2023
9671b0c
Move utils to separate folder.
i-zolotarenko Sep 28, 2023
083d593
Fix issues with abort looping, not handling requests in container.
i-zolotarenko Sep 28, 2023
420ffcd
Force queue process if playback position was significantly changed.
i-zolotarenko Sep 28, 2023
7622bdf
Add stream property to segment type.
i-zolotarenko Sep 29, 2023
44ed56b
Fix issue with fetch result data wrong promise error handling.
i-zolotarenko Oct 1, 2023
2b59ee2
Fix issue with not clearing aborted engine request.
i-zolotarenko Oct 2, 2023
15cfdda
Add random http loading.
i-zolotarenko Oct 2, 2023
6a607a3
Change JsonSegmentAnnouncement type.
i-zolotarenko Oct 2, 2023
7b6330a
Add loading through p2p to process queue.
i-zolotarenko Oct 2, 2023
7599820
Change load method parameter to queue item.
i-zolotarenko Oct 3, 2023
3cc30fe
Add loggers to hybrid loader, p2p-manager.
i-zolotarenko Oct 5, 2023
e564a76
Add load probability to http random load.
i-zolotarenko Oct 5, 2023
67886a9
Show stat in Demo. Destroy p2p manager if there is no uploadable data.
i-zolotarenko Oct 5, 2023
7f11315
Refactor code related to stat components.
i-zolotarenko Oct 6, 2023
52d88d0
Add loggers select to demo.
i-zolotarenko Oct 6, 2023
220b440
And onPeerClose event to Peer class.
i-zolotarenko Oct 8, 2023
a480959
Fix issue with wrong array buffer slicing. Modify choosing peer conne…
i-zolotarenko Oct 10, 2023
08203df
Add peer logger. Use utf-8 string for peer ids instead of hashes.
i-zolotarenko Oct 10, 2023
321cc6b
Remove ripemd160 package.
i-zolotarenko Oct 10, 2023
9a14429
Install node types. Patch bittorrent-tracker. Remove modules ignoring…
i-zolotarenko Oct 11, 2023
548320d
Sent ArrayBuffer to peer instead of Buffer. Use custom function to ch…
i-zolotarenko Oct 11, 2023
0bd2822
Add remain time predicting logic.
i-zolotarenko Oct 11, 2023
4c01697
Create universal bandwidth-approximator.
i-zolotarenko Oct 15, 2023
1315281
Add bitrate adaptation logic.
i-zolotarenko Oct 16, 2023
8bc001f
Rename p2p-loader logger.
i-zolotarenko Oct 17, 2023
11cbfa3
Use simple number variable instead of object creation.
i-zolotarenko Oct 17, 2023
62e312a
Modify uploading cancellation logic.
i-zolotarenko Oct 18, 2023
ebb843b
Broadcast announcement not more than 1 time for macrotask.
i-zolotarenko Oct 18, 2023
a033a7f
Generate user-friendly stream hash.
i-zolotarenko Oct 24, 2023
1f3b760
Configure tsconfig module, target options.
i-zolotarenko Oct 25, 2023
f3bf527
Change event handler name to onSegmentLoaded.
i-zolotarenko Oct 25, 2023
1297e87
Add core event handlers to shaka.
i-zolotarenko Oct 30, 2023
2e78864
Fix issues with shaka streams playback.
i-zolotarenko Oct 30, 2023
0932063
Revise and refactor code.
i-zolotarenko Oct 31, 2023
4162ab8
Move p2p files to separate p2p directory.
i-zolotarenko Oct 31, 2023
19ec6af
Rename request container file and p2p loader and container files.
i-zolotarenko Oct 31, 2023
bcec5be
Add request-container, storage loggers. Use queueMicrotask instead of…
i-zolotarenko Oct 31, 2023
589eda6
Fix issue with skipping first segment in queue generation function.
i-zolotarenko Oct 31, 2023
0389b57
Add binary serialization functions.
i-zolotarenko Nov 1, 2023
a2f44b8
Remove bits file.
i-zolotarenko Nov 2, 2023
8af127c
Merge branch 'v1' into p2p-manager
mrlika Nov 2, 2023
e96befe
Merge remote-tracking branch 'origin/v1' into p2p-manager
i-zolotarenko Nov 2, 2023
fb56f65
Add number and array binary serialization functions.
i-zolotarenko Nov 2, 2023
e07c1d8
Add array compression function.
i-zolotarenko Nov 16, 2023
be77b2e
Rewrite using bigint.
i-zolotarenko Nov 20, 2023
f4f725a
Fix issue with wrong bitwise operation.
i-zolotarenko Nov 22, 2023
481e6b0
Change similar int serialization algorithm.
i-zolotarenko Nov 23, 2023
bc32531
Merge with origin branch.
i-zolotarenko Nov 23, 2023
d1dec32
Complete similar int array serialization algorithm.
i-zolotarenko Nov 24, 2023
ab63b07
Refactor code and apply serialization.
i-zolotarenko Nov 24, 2023
b42d4c2
Fix errors.
i-zolotarenko Nov 24, 2023
969ed0e
Change removing/creating new p2p loader order.
i-zolotarenko Nov 24, 2023
d8a0612
Use md5 algorithm. Generate 15 bytes hash (1 byte truncate).
i-zolotarenko Dec 13, 2023
92362b2
Use one decimal in segment id. Other truncate.
i-zolotarenko Dec 13, 2023
071c8d2
Merge with v1
i-zolotarenko Dec 15, 2023
c45736b
Structure binary commands code. Fix bugs.
i-zolotarenko Dec 15, 2023
ad4a13a
Add PeerRequestSegmentCommand.
i-zolotarenko Dec 15, 2023
963c9f4
Don't add empty arrays to binary command representation.
i-zolotarenko Dec 15, 2023
b6c1632
Remove inner types file.
i-zolotarenko Dec 15, 2023
6e0c909
Split too large command buffer into chunks.
i-zolotarenko Dec 17, 2023
d1cfd74
Send split command.
i-zolotarenko Dec 18, 2023
63a22c5
Receive splited to chunks command.
i-zolotarenko Dec 18, 2023
038d7b9
Add string serialization.
i-zolotarenko Dec 18, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/p2p-media-loader-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@
},
"dependencies": {
"bittorrent-tracker": "10.0.12",
"ripemd160": "^2.0.2"
"nano-md5": "^1.0.5"
},
"devDependencies": {
"@types/ripemd160": "^2.0.2"
}
}
14 changes: 12 additions & 2 deletions packages/p2p-media-loader-core/src/declarations.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
declare module "bittorrent-tracker" {
export default class Client {
constructor(options: {
infoHash: string;
peerId: string;
infoHash: Uint8Array;
peerId: Uint8Array;
announce: string[];
port: number;
rtcConfig?: RTCConfiguration;
Expand Down Expand Up @@ -47,3 +47,13 @@ declare module "bittorrent-tracker" {
destroy(): void;
};
}

declare module "nano-md5" {
type BinaryStringObject = string & { toHex: () => string };
const md5: {
(utf8String: string): string; // returns hex string interpretation of binary data
fromUtf8(utf8String: string): BinaryStringObject;
};

export default md5;
}
12 changes: 0 additions & 12 deletions packages/p2p-media-loader-core/src/enums.ts

This file was deleted.

7 changes: 3 additions & 4 deletions packages/p2p-media-loader-core/src/hybrid-loader.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Segment, StreamWithSegments } from "./index";
import { fulfillHttpSegmentRequest } from "./http-loader";
import { SegmentsMemoryStorage } from "./segments-storage";
import { Settings, CoreEventHandlers } from "./types";
import { Settings, CoreEventHandlers, Playback } from "./types";
import { BandwidthApproximator } from "./bandwidth-approximator";
import { P2PLoadersContainer } from "./p2p/loaders-container";
import { Playback, QueueItem } from "./internal-types";
import { RequestsContainer } from "./request-container";
import { EngineCallbacks } from "./request";
import * as QueueUtils from "./utils/queue";
Expand Down Expand Up @@ -283,7 +282,7 @@ export class HybridLoader {
}

private abortLastHttpLoadingInQueueAfterItem(
queue: QueueItem[],
queue: QueueUtils.QueueItem[],
segment: Segment
): boolean {
for (const { segment: itemSegment } of arrayBackwards(queue)) {
Expand All @@ -298,7 +297,7 @@ export class HybridLoader {
}

private abortLastP2PLoadingInQueueAfterItem(
queue: QueueItem[],
queue: QueueUtils.QueueItem[],
segment: Segment
): boolean {
for (const { segment: itemSegment } of arrayBackwards(queue)) {
Expand Down
50 changes: 0 additions & 50 deletions packages/p2p-media-loader-core/src/internal-types.d.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import * as Serialization from "./binary-serialization";
import { PeerCommandType, PeerCommand } from "./types";

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: Uint8Array) {
const length = commandFrameStart.length;
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: Uint8Array) {
return areBuffersEqual(buffer, commandFrameStart, FRAME_PART_LENGTH);
}

function isLastCommandChunk(buffer: Uint8Array) {
return areBuffersEqual(
buffer.slice(-FRAME_PART_LENGTH),
commandFrameEnd,
FRAME_PART_LENGTH
);
}

export class BinaryCommandJoiningError extends Error {
constructor(readonly type: "incomplete-joining" | "no-first-chunk") {
super();
}
}

export class BinaryCommandChunksJoiner {
private readonly chunks = new Serialization.ResizableUint8Array();
private status: "joining" | "completed" = "joining";

constructor(
private readonly onComplete: (commandBuffer: Uint8Array) => void
) {}

addCommandChunk(chunk: Uint8Array) {
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());
}

private unframeCommandChunk(chunk: Uint8Array) {
return chunk.slice(FRAME_PART_LENGTH, chunk.length - FRAME_PART_LENGTH);
}
}

export class BinaryCommandCreator {
private readonly bytes = new Serialization.ResizableUint8Array();
private resultBuffers: Uint8Array[] = [];
private status: "creating" | "completed" = "creating";

constructor(
commandType: PeerCommandType,
private readonly maxChunkLength: number
) {
this.bytes.push(commandType);
}

addInteger(name: string, value: number) {
this.bytes.push(name.charCodeAt(0));
const bytes = Serialization.serializeInt(BigInt(value));
this.bytes.push(bytes);
}

addSimilarIntArr(name: string, arr: number[]) {
this.bytes.push(name.charCodeAt(0));
const bytes = Serialization.serializeSimilarIntArray(
arr.map((num) => BigInt(num))
);
this.bytes.push(bytes);
}

addString(name: string, string: 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 chunksAmount = Math.ceil(unframedBuffer.length / this.maxChunkLength);
if (
Math.ceil(unframedBuffer.length / chunksAmount) + commandFramesLength >
this.maxChunkLength
) {
chunksAmount++;
}

for (const [i, chunk] of splitBufferToEqualChunks(
unframedBuffer,
chunksAmount
)) {
if (i === 0) {
this.resultBuffers.push(
frameBuffer(chunk, commandFrameStart, commandDivFrameEnd)
);
} else if (i === chunksAmount - 1) {
this.resultBuffers.push(
frameBuffer(chunk, commandDivFrameStart, commandFrameEnd)
);
} else {
this.resultBuffers.push(
frameBuffer(chunk, commandDivFrameStart, commandDivFrameEnd)
);
}
}
}

getResultBuffers(): Uint8Array[] {
if (this.status === "creating" || !this.resultBuffers.length) {
throw new Error("Command is not complete.");
}
return this.resultBuffers;
}
}

export function deserializeCommand(bytes: Uint8Array): PeerCommand {
const [commandCode] = bytes;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const deserializedCommand: { [key: string]: any } = {
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 as unknown as PeerCommand;
}

function getDataTypeFromByte(byte: number): Serialization.SerializedItem {
const typeCode = byte >> 4;
if (!Serialization.serializedItemTypes.includes(typeCode)) {
throw new Error("Not existing type");
}

return typeCode as Serialization.SerializedItem;
}

function stringToUtf8CodesBuffer(string: string, length?: number): Uint8Array {
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: Uint8Array,
chunksAmount: number
): Generator<[number, Uint8Array], void> {
const chunkLength = Math.ceil(buffer.length / chunksAmount);
for (let i = 0; i < chunksAmount; i++) {
yield [i, buffer.slice(i * chunkLength, (i + 1) * chunkLength)];
}
}

function frameBuffer(
buffer: Uint8Array,
frameStart: Uint8Array,
frameEnd: Uint8Array
) {
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: Uint8Array,
buffer2: Uint8Array,
length: number
) {
for (let i = 0; i < length; i++) {
if (buffer1[i] !== buffer2[i]) return false;
}
return true;
}
Loading