livekit-client 2.17.1 → 2.17.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +21 -14
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +2087 -1920
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts +2 -0
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/KeyProvider.d.ts +2 -0
- package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
- package/dist/src/e2ee/events.d.ts +1 -1
- package/dist/src/e2ee/events.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +1 -0
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +2 -2
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
- package/dist/src/index.d.ts +7 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/logger.d.ts +2 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +1 -4
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/data-stream/incoming/StreamReader.d.ts +2 -4
- package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -1
- package/dist/src/room/data-track/depacketizer.d.ts +51 -0
- package/dist/src/room/data-track/depacketizer.d.ts.map +1 -0
- package/dist/src/room/data-track/e2ee.d.ts +12 -0
- package/dist/src/room/data-track/e2ee.d.ts.map +1 -0
- package/dist/src/room/data-track/frame.d.ts +7 -0
- package/dist/src/room/data-track/frame.d.ts.map +1 -0
- package/dist/src/room/data-track/handle.d.ts +6 -7
- package/dist/src/room/data-track/handle.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +76 -0
- package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -0
- package/dist/src/room/data-track/outgoing/errors.d.ts +64 -0
- package/dist/src/room/data-track/outgoing/errors.d.ts.map +1 -0
- package/dist/src/room/data-track/outgoing/pipeline.d.ts +22 -0
- package/dist/src/room/data-track/outgoing/pipeline.d.ts.map +1 -0
- package/dist/src/room/data-track/outgoing/types.d.ts +31 -0
- package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -0
- package/dist/src/room/data-track/packet/index.d.ts +3 -3
- package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
- package/dist/src/room/data-track/packetizer.d.ts +43 -0
- package/dist/src/room/data-track/packetizer.d.ts.map +1 -0
- package/dist/src/room/data-track/track.d.ts +30 -0
- package/dist/src/room/data-track/track.d.ts.map +1 -0
- package/dist/src/room/data-track/utils.d.ts +34 -2
- package/dist/src/room/data-track/utils.d.ts.map +1 -1
- package/dist/src/room/debounce.d.ts +11 -0
- package/dist/src/room/debounce.d.ts.map +1 -0
- package/dist/src/room/events.d.ts +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +2 -1
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +0 -2
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +6 -1
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/utils/subscribeToEvents.d.ts +12 -0
- package/dist/src/utils/subscribeToEvents.d.ts.map +1 -0
- package/dist/src/utils/throws.d.ts +4 -2
- package/dist/src/utils/throws.d.ts.map +1 -1
- package/dist/ts4.2/e2ee/E2eeManager.d.ts +2 -0
- package/dist/ts4.2/e2ee/KeyProvider.d.ts +2 -0
- package/dist/ts4.2/e2ee/events.d.ts +1 -1
- package/dist/ts4.2/e2ee/types.d.ts +1 -0
- package/dist/ts4.2/e2ee/worker/ParticipantKeyHandler.d.ts +2 -2
- package/dist/ts4.2/index.d.ts +7 -3
- package/dist/ts4.2/logger.d.ts +2 -1
- package/dist/ts4.2/room/PCTransport.d.ts +1 -6
- package/dist/ts4.2/room/data-stream/incoming/StreamReader.d.ts +2 -4
- package/dist/ts4.2/room/data-track/depacketizer.d.ts +51 -0
- package/dist/ts4.2/room/data-track/e2ee.d.ts +12 -0
- package/dist/ts4.2/room/data-track/frame.d.ts +7 -0
- package/dist/ts4.2/room/data-track/handle.d.ts +6 -7
- package/dist/ts4.2/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +77 -0
- package/dist/ts4.2/room/data-track/outgoing/errors.d.ts +64 -0
- package/dist/ts4.2/room/data-track/outgoing/pipeline.d.ts +22 -0
- package/dist/ts4.2/room/data-track/outgoing/types.d.ts +31 -0
- package/dist/ts4.2/room/data-track/packet/index.d.ts +3 -3
- package/dist/ts4.2/room/data-track/packetizer.d.ts +43 -0
- package/dist/ts4.2/room/data-track/track.d.ts +30 -0
- package/dist/ts4.2/room/data-track/utils.d.ts +34 -2
- package/dist/ts4.2/room/debounce.d.ts +11 -0
- package/dist/ts4.2/room/events.d.ts +1 -1
- package/dist/ts4.2/room/track/LocalAudioTrack.d.ts +1 -1
- package/dist/ts4.2/room/track/LocalTrack.d.ts +2 -1
- package/dist/ts4.2/room/types.d.ts +0 -2
- package/dist/ts4.2/room/utils.d.ts +6 -1
- package/dist/ts4.2/utils/subscribeToEvents.d.ts +12 -0
- package/dist/ts4.2/utils/throws.d.ts +4 -2
- package/package.json +4 -5
- package/src/e2ee/E2eeManager.ts +9 -5
- package/src/e2ee/KeyProvider.ts +10 -1
- package/src/e2ee/events.ts +1 -1
- package/src/e2ee/types.ts +1 -0
- package/src/e2ee/worker/ParticipantKeyHandler.ts +7 -4
- package/src/e2ee/worker/e2ee.worker.ts +20 -10
- package/src/index.ts +15 -5
- package/src/logger.ts +1 -0
- package/src/room/PCTransport.ts +2 -1
- package/src/room/PCTransportManager.ts +27 -9
- package/src/room/RTCEngine.ts +13 -2
- package/src/room/Room.ts +11 -5
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +5 -25
- package/src/room/data-stream/incoming/StreamReader.ts +56 -73
- package/src/room/data-track/depacketizer.test.ts +442 -0
- package/src/room/data-track/depacketizer.ts +298 -0
- package/src/room/data-track/e2ee.ts +14 -0
- package/src/room/data-track/frame.ts +8 -0
- package/src/room/data-track/handle.test.ts +1 -1
- package/src/room/data-track/handle.ts +9 -14
- package/src/room/data-track/outgoing/OutgoingDataTrackManager.test.ts +392 -0
- package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +302 -0
- package/src/room/data-track/outgoing/errors.ts +157 -0
- package/src/room/data-track/outgoing/pipeline.ts +76 -0
- package/src/room/data-track/outgoing/types.ts +37 -0
- package/src/room/data-track/packet/index.test.ts +9 -9
- package/src/room/data-track/packet/index.ts +11 -9
- package/src/room/data-track/packet/serializable.ts +1 -1
- package/src/room/data-track/packetizer.test.ts +131 -0
- package/src/room/data-track/packetizer.ts +132 -0
- package/src/room/data-track/track.ts +50 -0
- package/src/room/data-track/utils.test.ts +27 -1
- package/src/room/data-track/utils.ts +125 -5
- package/src/room/debounce.ts +115 -0
- package/src/room/events.ts +1 -1
- package/src/room/participant/LocalParticipant.ts +2 -0
- package/src/room/track/LocalAudioTrack.ts +10 -10
- package/src/room/track/LocalTrack.ts +14 -5
- package/src/room/track/LocalVideoTrack.ts +1 -1
- package/src/room/track/RemoteVideoTrack.ts +1 -1
- package/src/room/types.ts +0 -2
- package/src/room/utils.ts +7 -2
- package/src/utils/subscribeToEvents.ts +63 -0
- package/src/utils/throws.ts +3 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DataStream_Chunk } from '@livekit/protocol';
|
|
2
2
|
import { DataStreamError, DataStreamErrorReason } from '../../errors';
|
|
3
3
|
import type { BaseStreamInfo, ByteStreamInfo, TextStreamInfo } from '../../types';
|
|
4
|
-
import {
|
|
4
|
+
import { bigIntToNumber } from '../../utils';
|
|
5
5
|
|
|
6
6
|
export type BaseStreamReaderReadAllOpts = {
|
|
7
7
|
/** An AbortSignal can be used to terminate reads early. */
|
|
@@ -17,8 +17,6 @@ abstract class BaseStreamReader<T extends BaseStreamInfo> {
|
|
|
17
17
|
|
|
18
18
|
protected bytesReceived: number;
|
|
19
19
|
|
|
20
|
-
protected outOfBandFailureRejectingFuture?: Future<never, Error>;
|
|
21
|
-
|
|
22
20
|
get info() {
|
|
23
21
|
return this._info;
|
|
24
22
|
}
|
|
@@ -42,17 +40,11 @@ abstract class BaseStreamReader<T extends BaseStreamInfo> {
|
|
|
42
40
|
}
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
constructor(
|
|
46
|
-
info: T,
|
|
47
|
-
stream: ReadableStream<DataStream_Chunk>,
|
|
48
|
-
totalByteSize?: number,
|
|
49
|
-
outOfBandFailureRejectingFuture?: Future<never, Error>,
|
|
50
|
-
) {
|
|
43
|
+
constructor(info: T, stream: ReadableStream<DataStream_Chunk>, totalByteSize?: number) {
|
|
51
44
|
this.reader = stream;
|
|
52
45
|
this.totalByteSize = totalByteSize;
|
|
53
46
|
this._info = info;
|
|
54
47
|
this.bytesReceived = 0;
|
|
55
|
-
this.outOfBandFailureRejectingFuture = outOfBandFailureRejectingFuture;
|
|
56
48
|
}
|
|
57
49
|
|
|
58
50
|
protected abstract handleChunkReceived(chunk: DataStream_Chunk): void;
|
|
@@ -79,48 +71,44 @@ export class ByteStreamReader extends BaseStreamReader<ByteStreamInfo> {
|
|
|
79
71
|
|
|
80
72
|
[Symbol.asyncIterator]() {
|
|
81
73
|
const reader = this.reader.getReader();
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
let onAbort: (() => void) | null = null;
|
|
86
|
-
if (this.signal) {
|
|
87
|
-
const signal = this.signal;
|
|
88
|
-
onAbort = () => {
|
|
89
|
-
rejectingSignalFuture.reject?.(signal.reason);
|
|
90
|
-
};
|
|
91
|
-
signal.addEventListener('abort', onAbort);
|
|
92
|
-
activeSignal = signal;
|
|
93
|
-
}
|
|
74
|
+
// Suppress unhandled rejection on reader.closed — errors are
|
|
75
|
+
// already propagated through reader.read() to the consumer.
|
|
76
|
+
reader.closed.catch(() => {});
|
|
94
77
|
|
|
95
78
|
const cleanup = () => {
|
|
96
79
|
reader.releaseLock();
|
|
97
|
-
|
|
98
|
-
if (activeSignal && onAbort) {
|
|
99
|
-
activeSignal.removeEventListener('abort', onAbort);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
80
|
this.signal = undefined;
|
|
103
81
|
};
|
|
104
82
|
|
|
105
83
|
return {
|
|
106
84
|
next: async (): Promise<IteratorResult<Uint8Array>> => {
|
|
107
85
|
try {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
86
|
+
const signal = this.signal;
|
|
87
|
+
if (signal?.aborted) {
|
|
88
|
+
throw signal.reason;
|
|
89
|
+
}
|
|
90
|
+
const result = await new Promise<ReadableStreamReadResult<DataStream_Chunk>>(
|
|
91
|
+
(resolve, reject) => {
|
|
92
|
+
if (signal) {
|
|
93
|
+
const onAbort = () => reject(signal.reason);
|
|
94
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
95
|
+
reader
|
|
96
|
+
.read()
|
|
97
|
+
.then(resolve, reject)
|
|
98
|
+
.finally(() => {
|
|
99
|
+
signal.removeEventListener('abort', onAbort);
|
|
100
|
+
});
|
|
101
|
+
} else {
|
|
102
|
+
reader.read().then(resolve, reject);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
if (result.done) {
|
|
119
107
|
this.validateBytesReceived(true);
|
|
120
108
|
return { done: true, value: undefined as any };
|
|
121
109
|
} else {
|
|
122
|
-
this.handleChunkReceived(value);
|
|
123
|
-
return { done: false, value: value.content };
|
|
110
|
+
this.handleChunkReceived(result.value);
|
|
111
|
+
return { done: false, value: result.value.content };
|
|
124
112
|
}
|
|
125
113
|
} catch (err) {
|
|
126
114
|
cleanup();
|
|
@@ -175,9 +163,8 @@ export class TextStreamReader extends BaseStreamReader<TextStreamInfo> {
|
|
|
175
163
|
info: TextStreamInfo,
|
|
176
164
|
stream: ReadableStream<DataStream_Chunk>,
|
|
177
165
|
totalChunkCount?: number,
|
|
178
|
-
outOfBandFailureRejectingFuture?: Future<never, Error>,
|
|
179
166
|
) {
|
|
180
|
-
super(info, stream, totalChunkCount
|
|
167
|
+
super(info, stream, totalChunkCount);
|
|
181
168
|
this.receivedChunks = new Map();
|
|
182
169
|
}
|
|
183
170
|
|
|
@@ -211,55 +198,51 @@ export class TextStreamReader extends BaseStreamReader<TextStreamInfo> {
|
|
|
211
198
|
*/
|
|
212
199
|
[Symbol.asyncIterator]() {
|
|
213
200
|
const reader = this.reader.getReader();
|
|
201
|
+
// Suppress unhandled rejection on reader.closed — errors are
|
|
202
|
+
// already propagated through reader.read() to the consumer.
|
|
203
|
+
reader.closed.catch(() => {});
|
|
214
204
|
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
215
|
-
|
|
216
|
-
let rejectingSignalFuture = new Future<never, Error>();
|
|
217
|
-
let activeSignal: AbortSignal | null = null;
|
|
218
|
-
let onAbort: (() => void) | null = null;
|
|
219
|
-
if (this.signal) {
|
|
220
|
-
const signal = this.signal;
|
|
221
|
-
onAbort = () => {
|
|
222
|
-
rejectingSignalFuture.reject?.(signal.reason);
|
|
223
|
-
};
|
|
224
|
-
signal.addEventListener('abort', onAbort);
|
|
225
|
-
activeSignal = signal;
|
|
226
|
-
}
|
|
205
|
+
const signal = this.signal;
|
|
227
206
|
|
|
228
207
|
const cleanup = () => {
|
|
229
208
|
reader.releaseLock();
|
|
230
|
-
|
|
231
|
-
if (activeSignal && onAbort) {
|
|
232
|
-
activeSignal.removeEventListener('abort', onAbort);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
209
|
this.signal = undefined;
|
|
236
210
|
};
|
|
237
211
|
|
|
238
212
|
return {
|
|
239
213
|
next: async (): Promise<IteratorResult<string>> => {
|
|
240
214
|
try {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
215
|
+
if (signal?.aborted) {
|
|
216
|
+
throw signal.reason;
|
|
217
|
+
}
|
|
218
|
+
const result = await new Promise<ReadableStreamReadResult<DataStream_Chunk>>(
|
|
219
|
+
(resolve, reject) => {
|
|
220
|
+
if (signal) {
|
|
221
|
+
const onAbort = () => reject(signal.reason);
|
|
222
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
223
|
+
reader
|
|
224
|
+
.read()
|
|
225
|
+
.then(resolve, reject)
|
|
226
|
+
.finally(() => {
|
|
227
|
+
signal.removeEventListener('abort', onAbort);
|
|
228
|
+
});
|
|
229
|
+
} else {
|
|
230
|
+
reader.read().then(resolve, reject);
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
);
|
|
234
|
+
if (result.done) {
|
|
252
235
|
this.validateBytesReceived(true);
|
|
253
236
|
return { done: true, value: undefined };
|
|
254
237
|
} else {
|
|
255
|
-
this.handleChunkReceived(value);
|
|
238
|
+
this.handleChunkReceived(result.value);
|
|
256
239
|
|
|
257
240
|
let decodedResult;
|
|
258
241
|
try {
|
|
259
|
-
decodedResult = decoder.decode(value.content);
|
|
242
|
+
decodedResult = decoder.decode(result.value.content);
|
|
260
243
|
} catch (err) {
|
|
261
244
|
throw new DataStreamError(
|
|
262
|
-
`Cannot decode datastream chunk ${value.chunkIndex} as text: ${err}`,
|
|
245
|
+
`Cannot decode datastream chunk ${result.value.chunkIndex} as text: ${err}`,
|
|
263
246
|
DataStreamErrorReason.DecodeFailed,
|
|
264
247
|
);
|
|
265
248
|
}
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import DataTrackDepacketizer from './depacketizer';
|
|
4
|
+
import { DataTrackHandle } from './handle';
|
|
5
|
+
import { DataTrackPacket, DataTrackPacketHeader, FrameMarker } from './packet';
|
|
6
|
+
import { DataTrackTimestamp, WrapAroundUnsignedInt } from './utils';
|
|
7
|
+
|
|
8
|
+
const EMPTY_EXTENSIONS_JSON = { userTimestamp: null, e2ee: null };
|
|
9
|
+
|
|
10
|
+
describe('DataTrackDepacketizer', () => {
|
|
11
|
+
it('should depacketize a single packet', () => {
|
|
12
|
+
const depacketizer = new DataTrackDepacketizer();
|
|
13
|
+
const packet = new DataTrackPacket(
|
|
14
|
+
new DataTrackPacketHeader({
|
|
15
|
+
marker: FrameMarker.Single,
|
|
16
|
+
trackHandle: DataTrackHandle.fromNumber(101),
|
|
17
|
+
sequence: WrapAroundUnsignedInt.u16(0),
|
|
18
|
+
frameNumber: WrapAroundUnsignedInt.u16(103),
|
|
19
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(104),
|
|
20
|
+
}),
|
|
21
|
+
new Uint8Array(8),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const frame = depacketizer.push(packet);
|
|
25
|
+
expect(frame!.payload).toStrictEqual(new Uint8Array(8));
|
|
26
|
+
expect(frame!.extensions.toJSON()).toStrictEqual(EMPTY_EXTENSIONS_JSON);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it.each([0, 8, DataTrackDepacketizer.MAX_BUFFER_PACKETS - 2])(
|
|
30
|
+
'should depacketize a multi packet message',
|
|
31
|
+
(interPacketCount) => {
|
|
32
|
+
const depacketizer = new DataTrackDepacketizer();
|
|
33
|
+
|
|
34
|
+
const packetPayload = new Uint8Array(8);
|
|
35
|
+
const packetHeaderParams = {
|
|
36
|
+
/* no marker */
|
|
37
|
+
trackHandle: DataTrackHandle.fromNumber(101),
|
|
38
|
+
sequence: WrapAroundUnsignedInt.u16(0),
|
|
39
|
+
frameNumber: WrapAroundUnsignedInt.u16(103),
|
|
40
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(104),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const startPacket = new DataTrackPacket(
|
|
44
|
+
new DataTrackPacketHeader({ ...packetHeaderParams, marker: FrameMarker.Start }),
|
|
45
|
+
packetPayload,
|
|
46
|
+
);
|
|
47
|
+
const startPacketFrame = depacketizer.push(startPacket);
|
|
48
|
+
expect(startPacketFrame).toBeNull();
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < interPacketCount; i += 1) {
|
|
51
|
+
const interPacket = new DataTrackPacket(
|
|
52
|
+
new DataTrackPacketHeader({
|
|
53
|
+
...packetHeaderParams,
|
|
54
|
+
marker: FrameMarker.Inter,
|
|
55
|
+
sequence: WrapAroundUnsignedInt.u16(i + 1),
|
|
56
|
+
}),
|
|
57
|
+
packetPayload,
|
|
58
|
+
);
|
|
59
|
+
const interPacketFrame = depacketizer.push(interPacket);
|
|
60
|
+
expect(interPacketFrame).toBeNull();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const finalPacket = new DataTrackPacket(
|
|
64
|
+
new DataTrackPacketHeader({
|
|
65
|
+
...packetHeaderParams,
|
|
66
|
+
marker: FrameMarker.Final,
|
|
67
|
+
sequence: WrapAroundUnsignedInt.u16(interPacketCount + 1),
|
|
68
|
+
}),
|
|
69
|
+
packetPayload,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const finalPacketFrame = depacketizer.push(finalPacket);
|
|
73
|
+
expect(finalPacketFrame!.extensions.toJSON()).toStrictEqual(EMPTY_EXTENSIONS_JSON);
|
|
74
|
+
expect(finalPacketFrame!.payload.byteLength).toStrictEqual(
|
|
75
|
+
packetPayload.byteLength * (1 /* start */ + interPacketCount + 1) /* final */,
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
it('should throw "interrupted" when frame number changes midway through depacketizing', () => {
|
|
81
|
+
const depacketizer = new DataTrackDepacketizer();
|
|
82
|
+
const packetA = new DataTrackPacket(
|
|
83
|
+
new DataTrackPacketHeader({
|
|
84
|
+
marker: FrameMarker.Start,
|
|
85
|
+
trackHandle: DataTrackHandle.fromNumber(1),
|
|
86
|
+
sequence: WrapAroundUnsignedInt.u16(0),
|
|
87
|
+
frameNumber: WrapAroundUnsignedInt.u16(5 /* starts on frame 5 */),
|
|
88
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(0),
|
|
89
|
+
}),
|
|
90
|
+
new Uint8Array(8),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const frameA = depacketizer.push(packetA);
|
|
94
|
+
expect(frameA).toBeNull();
|
|
95
|
+
|
|
96
|
+
// Now feed in a packet with a different frame number:
|
|
97
|
+
const nextFrameNumber = packetA.header.frameNumber.value + 1;
|
|
98
|
+
const packetB = new DataTrackPacket(
|
|
99
|
+
new DataTrackPacketHeader({
|
|
100
|
+
marker: FrameMarker.Start,
|
|
101
|
+
trackHandle: DataTrackHandle.fromNumber(1),
|
|
102
|
+
sequence: WrapAroundUnsignedInt.u16(0),
|
|
103
|
+
frameNumber: WrapAroundUnsignedInt.u16(nextFrameNumber),
|
|
104
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(0),
|
|
105
|
+
}),
|
|
106
|
+
new Uint8Array(8),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(() => depacketizer.push(packetB, { errorOnPartialFrames: true })).toThrowError(
|
|
110
|
+
'Frame 5 dropped: Interrupted by the start of a new frame',
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should throw "incomplete" when final packet comes too early', () => {
|
|
115
|
+
const depacketizer = new DataTrackDepacketizer();
|
|
116
|
+
|
|
117
|
+
const packetPayload = new Uint8Array(8);
|
|
118
|
+
const packetHeaderParams = {
|
|
119
|
+
/* no marker */
|
|
120
|
+
trackHandle: DataTrackHandle.fromNumber(101),
|
|
121
|
+
sequence: WrapAroundUnsignedInt.u16(0),
|
|
122
|
+
frameNumber: WrapAroundUnsignedInt.u16(103),
|
|
123
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(104),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const startPacket = new DataTrackPacket(
|
|
127
|
+
new DataTrackPacketHeader({ ...packetHeaderParams, marker: FrameMarker.Start }),
|
|
128
|
+
packetPayload,
|
|
129
|
+
);
|
|
130
|
+
const startPacketFrame = depacketizer.push(startPacket);
|
|
131
|
+
expect(startPacketFrame).toBeNull();
|
|
132
|
+
|
|
133
|
+
const finalPacket = new DataTrackPacket(
|
|
134
|
+
new DataTrackPacketHeader({
|
|
135
|
+
...packetHeaderParams,
|
|
136
|
+
marker: FrameMarker.Final,
|
|
137
|
+
sequence: WrapAroundUnsignedInt.u16(3),
|
|
138
|
+
}),
|
|
139
|
+
packetPayload,
|
|
140
|
+
);
|
|
141
|
+
expect(() => depacketizer.push(finalPacket)).toThrowError(
|
|
142
|
+
'Frame 103 dropped: Not all packets received before final packet. Received 2 packets, expected 4 packets.',
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should throw "unknownFrame" when a non single packet frame does not start with a "start" packet', () => {
|
|
147
|
+
const depacketizer = new DataTrackDepacketizer();
|
|
148
|
+
|
|
149
|
+
const packet = new DataTrackPacket(
|
|
150
|
+
new DataTrackPacketHeader({
|
|
151
|
+
marker: FrameMarker.Inter,
|
|
152
|
+
trackHandle: DataTrackHandle.fromNumber(101),
|
|
153
|
+
sequence: WrapAroundUnsignedInt.u16(0),
|
|
154
|
+
frameNumber: WrapAroundUnsignedInt.u16(103),
|
|
155
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(104),
|
|
156
|
+
}),
|
|
157
|
+
new Uint8Array(8),
|
|
158
|
+
);
|
|
159
|
+
expect(() => depacketizer.push(packet)).toThrowError(
|
|
160
|
+
'Frame 103 dropped: Initial packet was never received.',
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should throw "bufferFull" when too many packets have been sent', () => {
|
|
165
|
+
const depacketizer = new DataTrackDepacketizer();
|
|
166
|
+
|
|
167
|
+
const packetPayload = new Uint8Array(8);
|
|
168
|
+
const packetHeaderParams = {
|
|
169
|
+
/* no marker */
|
|
170
|
+
trackHandle: DataTrackHandle.fromNumber(101),
|
|
171
|
+
sequence: WrapAroundUnsignedInt.u16(0),
|
|
172
|
+
frameNumber: WrapAroundUnsignedInt.u16(103),
|
|
173
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(104),
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const startPacket = new DataTrackPacket(
|
|
177
|
+
new DataTrackPacketHeader({ ...packetHeaderParams, marker: FrameMarker.Start }),
|
|
178
|
+
packetPayload,
|
|
179
|
+
);
|
|
180
|
+
const startPacketFrame = depacketizer.push(startPacket);
|
|
181
|
+
expect(startPacketFrame).toBeNull();
|
|
182
|
+
|
|
183
|
+
for (let i = 0; i < DataTrackDepacketizer.MAX_BUFFER_PACKETS - 1; i += 1) {
|
|
184
|
+
const interPacket = new DataTrackPacket(
|
|
185
|
+
new DataTrackPacketHeader({
|
|
186
|
+
...packetHeaderParams,
|
|
187
|
+
marker: FrameMarker.Inter,
|
|
188
|
+
sequence: WrapAroundUnsignedInt.u16(i + 1),
|
|
189
|
+
}),
|
|
190
|
+
packetPayload,
|
|
191
|
+
);
|
|
192
|
+
const interPacketFrame = depacketizer.push(interPacket);
|
|
193
|
+
expect(interPacketFrame).toBeNull();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Send one final inter packet (so one more than the max), and make sure the error case gets hit
|
|
197
|
+
const extraInterPacket = new DataTrackPacket(
|
|
198
|
+
new DataTrackPacketHeader({
|
|
199
|
+
...packetHeaderParams,
|
|
200
|
+
marker: FrameMarker.Final,
|
|
201
|
+
sequence: WrapAroundUnsignedInt.u16(DataTrackDepacketizer.MAX_BUFFER_PACKETS),
|
|
202
|
+
}),
|
|
203
|
+
packetPayload,
|
|
204
|
+
);
|
|
205
|
+
expect(() => depacketizer.push(extraInterPacket)).toThrowError(
|
|
206
|
+
'Frame 103 dropped: Reorder buffer is full.',
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should depacketize two frames worth of packets', () => {
|
|
211
|
+
const depacketizer = new DataTrackDepacketizer();
|
|
212
|
+
|
|
213
|
+
const packetPayload = new Uint8Array(8);
|
|
214
|
+
const packetHeaderParams = {
|
|
215
|
+
/* no marker */
|
|
216
|
+
trackHandle: DataTrackHandle.fromNumber(101),
|
|
217
|
+
sequence: WrapAroundUnsignedInt.u16(0),
|
|
218
|
+
frameNumber: WrapAroundUnsignedInt.u16(103),
|
|
219
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(104),
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// Process first frame successfully
|
|
223
|
+
const startPacketA = new DataTrackPacket(
|
|
224
|
+
new DataTrackPacketHeader({ ...packetHeaderParams, marker: FrameMarker.Start }),
|
|
225
|
+
packetPayload,
|
|
226
|
+
);
|
|
227
|
+
expect(depacketizer.push(startPacketA)).toBeNull();
|
|
228
|
+
|
|
229
|
+
const interPacketA = new DataTrackPacket(
|
|
230
|
+
new DataTrackPacketHeader({
|
|
231
|
+
...packetHeaderParams,
|
|
232
|
+
marker: FrameMarker.Inter,
|
|
233
|
+
sequence: WrapAroundUnsignedInt.u16(1),
|
|
234
|
+
}),
|
|
235
|
+
packetPayload,
|
|
236
|
+
);
|
|
237
|
+
expect(depacketizer.push(interPacketA)).toBeNull();
|
|
238
|
+
|
|
239
|
+
const finalPacketA = new DataTrackPacket(
|
|
240
|
+
new DataTrackPacketHeader({
|
|
241
|
+
...packetHeaderParams,
|
|
242
|
+
marker: FrameMarker.Final,
|
|
243
|
+
sequence: WrapAroundUnsignedInt.u16(2),
|
|
244
|
+
}),
|
|
245
|
+
packetPayload,
|
|
246
|
+
);
|
|
247
|
+
expect(depacketizer.push(finalPacketA)).not.toBeNull();
|
|
248
|
+
|
|
249
|
+
// Now process another frame successfully
|
|
250
|
+
const startPacketB = new DataTrackPacket(
|
|
251
|
+
new DataTrackPacketHeader({
|
|
252
|
+
...packetHeaderParams,
|
|
253
|
+
marker: FrameMarker.Start,
|
|
254
|
+
sequence: WrapAroundUnsignedInt.u16(3),
|
|
255
|
+
frameNumber: WrapAroundUnsignedInt.u16(999),
|
|
256
|
+
}),
|
|
257
|
+
packetPayload,
|
|
258
|
+
);
|
|
259
|
+
expect(depacketizer.push(startPacketB)).toBeNull();
|
|
260
|
+
|
|
261
|
+
const interPacketB = new DataTrackPacket(
|
|
262
|
+
new DataTrackPacketHeader({
|
|
263
|
+
...packetHeaderParams,
|
|
264
|
+
marker: FrameMarker.Inter,
|
|
265
|
+
sequence: WrapAroundUnsignedInt.u16(4),
|
|
266
|
+
frameNumber: WrapAroundUnsignedInt.u16(999),
|
|
267
|
+
}),
|
|
268
|
+
packetPayload,
|
|
269
|
+
);
|
|
270
|
+
expect(depacketizer.push(interPacketB)).toBeNull();
|
|
271
|
+
|
|
272
|
+
const finalPacketB = new DataTrackPacket(
|
|
273
|
+
new DataTrackPacketHeader({
|
|
274
|
+
...packetHeaderParams,
|
|
275
|
+
marker: FrameMarker.Final,
|
|
276
|
+
sequence: WrapAroundUnsignedInt.u16(5),
|
|
277
|
+
frameNumber: WrapAroundUnsignedInt.u16(999),
|
|
278
|
+
}),
|
|
279
|
+
packetPayload,
|
|
280
|
+
);
|
|
281
|
+
expect(depacketizer.push(finalPacketB)).not.toBeNull();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should ensure that if duplicate packets with the same sequence number are received, the last packet wins', () => {
|
|
285
|
+
const depacketizer = new DataTrackDepacketizer();
|
|
286
|
+
|
|
287
|
+
const packetHeaderParams = {
|
|
288
|
+
/* no marker */
|
|
289
|
+
trackHandle: DataTrackHandle.fromNumber(101),
|
|
290
|
+
sequence: WrapAroundUnsignedInt.u16(0),
|
|
291
|
+
frameNumber: WrapAroundUnsignedInt.u16(103),
|
|
292
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(104),
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const startPacket = new DataTrackPacket(
|
|
296
|
+
new DataTrackPacketHeader({ ...packetHeaderParams, marker: FrameMarker.Start }),
|
|
297
|
+
new Uint8Array(0),
|
|
298
|
+
);
|
|
299
|
+
expect(depacketizer.push(startPacket)).toBeNull();
|
|
300
|
+
|
|
301
|
+
// First version of the inter packet
|
|
302
|
+
const interPacketA = new DataTrackPacket(
|
|
303
|
+
new DataTrackPacketHeader({
|
|
304
|
+
...packetHeaderParams,
|
|
305
|
+
marker: FrameMarker.Inter,
|
|
306
|
+
sequence: WrapAroundUnsignedInt.u16(1),
|
|
307
|
+
}),
|
|
308
|
+
new Uint8Array([0x01, 0x02, 0x03]),
|
|
309
|
+
);
|
|
310
|
+
expect(depacketizer.push(interPacketA)).toBeNull();
|
|
311
|
+
|
|
312
|
+
// Second version of the inter packet
|
|
313
|
+
const interPacketB = new DataTrackPacket(
|
|
314
|
+
new DataTrackPacketHeader({
|
|
315
|
+
...packetHeaderParams,
|
|
316
|
+
marker: FrameMarker.Inter,
|
|
317
|
+
sequence: WrapAroundUnsignedInt.u16(1),
|
|
318
|
+
}),
|
|
319
|
+
new Uint8Array([0x04, 0x05, 0x06]),
|
|
320
|
+
);
|
|
321
|
+
expect(depacketizer.push(interPacketB)).toBeNull();
|
|
322
|
+
|
|
323
|
+
const finalPacket = new DataTrackPacket(
|
|
324
|
+
new DataTrackPacketHeader({
|
|
325
|
+
...packetHeaderParams,
|
|
326
|
+
marker: FrameMarker.Final,
|
|
327
|
+
sequence: WrapAroundUnsignedInt.u16(2),
|
|
328
|
+
}),
|
|
329
|
+
new Uint8Array(0),
|
|
330
|
+
);
|
|
331
|
+
expect(depacketizer.push(finalPacket)!.payload).toStrictEqual(
|
|
332
|
+
new Uint8Array([0x04, 0x05, 0x06]),
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('should ensure packets can be received out of order', () => {
|
|
337
|
+
const depacketizer = new DataTrackDepacketizer();
|
|
338
|
+
|
|
339
|
+
const packetHeaderParams = {
|
|
340
|
+
/* no marker */
|
|
341
|
+
trackHandle: DataTrackHandle.fromNumber(101),
|
|
342
|
+
sequence: WrapAroundUnsignedInt.u16(0),
|
|
343
|
+
frameNumber: WrapAroundUnsignedInt.u16(103),
|
|
344
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(104),
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const startPacket = new DataTrackPacket(
|
|
348
|
+
new DataTrackPacketHeader({ ...packetHeaderParams, marker: FrameMarker.Start }),
|
|
349
|
+
new Uint8Array(0),
|
|
350
|
+
);
|
|
351
|
+
expect(depacketizer.push(startPacket)).toBeNull();
|
|
352
|
+
|
|
353
|
+
// Second inter packet comes first
|
|
354
|
+
const interPacketB = new DataTrackPacket(
|
|
355
|
+
new DataTrackPacketHeader({
|
|
356
|
+
...packetHeaderParams,
|
|
357
|
+
marker: FrameMarker.Inter,
|
|
358
|
+
sequence: WrapAroundUnsignedInt.u16(2),
|
|
359
|
+
}),
|
|
360
|
+
new Uint8Array([0x04, 0x05, 0x06]),
|
|
361
|
+
);
|
|
362
|
+
expect(depacketizer.push(interPacketB)).toBeNull();
|
|
363
|
+
|
|
364
|
+
// First inter packet comes second
|
|
365
|
+
const interPacketA = new DataTrackPacket(
|
|
366
|
+
new DataTrackPacketHeader({
|
|
367
|
+
...packetHeaderParams,
|
|
368
|
+
marker: FrameMarker.Inter,
|
|
369
|
+
sequence: WrapAroundUnsignedInt.u16(1),
|
|
370
|
+
}),
|
|
371
|
+
new Uint8Array([0x01, 0x02, 0x03]),
|
|
372
|
+
);
|
|
373
|
+
expect(depacketizer.push(interPacketA)).toBeNull();
|
|
374
|
+
|
|
375
|
+
const finalPacket = new DataTrackPacket(
|
|
376
|
+
new DataTrackPacketHeader({
|
|
377
|
+
...packetHeaderParams,
|
|
378
|
+
marker: FrameMarker.Final,
|
|
379
|
+
sequence: WrapAroundUnsignedInt.u16(3),
|
|
380
|
+
}),
|
|
381
|
+
new Uint8Array(0),
|
|
382
|
+
);
|
|
383
|
+
expect(depacketizer.push(finalPacket)!.payload).toStrictEqual(
|
|
384
|
+
new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]),
|
|
385
|
+
);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('should be able to continue to depacketize if a bad packet puts the depacketizer into a bad state', () => {
|
|
389
|
+
const depacketizer = new DataTrackDepacketizer();
|
|
390
|
+
|
|
391
|
+
const packetHeaderParams = {
|
|
392
|
+
/* no marker */
|
|
393
|
+
trackHandle: DataTrackHandle.fromNumber(101),
|
|
394
|
+
sequence: WrapAroundUnsignedInt.u16(0),
|
|
395
|
+
frameNumber: WrapAroundUnsignedInt.u16(103),
|
|
396
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(104),
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// First, put the depacketizer into a bad state by feeding in a final packet
|
|
400
|
+
const preemptiveFinalPacket = new DataTrackPacket(
|
|
401
|
+
new DataTrackPacketHeader({
|
|
402
|
+
...packetHeaderParams,
|
|
403
|
+
marker: FrameMarker.Final,
|
|
404
|
+
frameNumber: WrapAroundUnsignedInt.u16(102),
|
|
405
|
+
sequence: WrapAroundUnsignedInt.u16(0),
|
|
406
|
+
}),
|
|
407
|
+
new Uint8Array(0),
|
|
408
|
+
);
|
|
409
|
+
expect(() => depacketizer.push(preemptiveFinalPacket)).toThrowError(
|
|
410
|
+
'Frame 102 dropped: Initial packet was never received.',
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
// Then, try to parse a valid multi byte frame made up of three packets
|
|
414
|
+
const startPacket = new DataTrackPacket(
|
|
415
|
+
new DataTrackPacketHeader({ ...packetHeaderParams, marker: FrameMarker.Start }),
|
|
416
|
+
new Uint8Array(0),
|
|
417
|
+
);
|
|
418
|
+
expect(depacketizer.push(startPacket)).toBeNull();
|
|
419
|
+
|
|
420
|
+
const interPacket = new DataTrackPacket(
|
|
421
|
+
new DataTrackPacketHeader({
|
|
422
|
+
...packetHeaderParams,
|
|
423
|
+
marker: FrameMarker.Inter,
|
|
424
|
+
sequence: WrapAroundUnsignedInt.u16(1),
|
|
425
|
+
}),
|
|
426
|
+
new Uint8Array([0x01, 0x02, 0x03]),
|
|
427
|
+
);
|
|
428
|
+
expect(depacketizer.push(interPacket)).toBeNull();
|
|
429
|
+
|
|
430
|
+
const finalPacket = new DataTrackPacket(
|
|
431
|
+
new DataTrackPacketHeader({
|
|
432
|
+
...packetHeaderParams,
|
|
433
|
+
marker: FrameMarker.Final,
|
|
434
|
+
sequence: WrapAroundUnsignedInt.u16(2),
|
|
435
|
+
}),
|
|
436
|
+
new Uint8Array(0),
|
|
437
|
+
);
|
|
438
|
+
expect(depacketizer.push(finalPacket)!.payload).toStrictEqual(
|
|
439
|
+
new Uint8Array([0x01, 0x02, 0x03]),
|
|
440
|
+
);
|
|
441
|
+
});
|
|
442
|
+
});
|