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.
Files changed (145) hide show
  1. package/README.md +7 -5
  2. package/dist/livekit-client.e2ee.worker.js +1 -1
  3. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  4. package/dist/livekit-client.e2ee.worker.mjs +21 -14
  5. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  6. package/dist/livekit-client.esm.mjs +2087 -1920
  7. package/dist/livekit-client.esm.mjs.map +1 -1
  8. package/dist/livekit-client.umd.js +1 -1
  9. package/dist/livekit-client.umd.js.map +1 -1
  10. package/dist/src/e2ee/E2eeManager.d.ts +2 -0
  11. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  12. package/dist/src/e2ee/KeyProvider.d.ts +2 -0
  13. package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
  14. package/dist/src/e2ee/events.d.ts +1 -1
  15. package/dist/src/e2ee/events.d.ts.map +1 -1
  16. package/dist/src/e2ee/types.d.ts +1 -0
  17. package/dist/src/e2ee/types.d.ts.map +1 -1
  18. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +2 -2
  19. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
  20. package/dist/src/index.d.ts +7 -6
  21. package/dist/src/index.d.ts.map +1 -1
  22. package/dist/src/logger.d.ts +2 -1
  23. package/dist/src/logger.d.ts.map +1 -1
  24. package/dist/src/room/PCTransport.d.ts +1 -4
  25. package/dist/src/room/PCTransport.d.ts.map +1 -1
  26. package/dist/src/room/PCTransportManager.d.ts.map +1 -1
  27. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  28. package/dist/src/room/Room.d.ts.map +1 -1
  29. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
  30. package/dist/src/room/data-stream/incoming/StreamReader.d.ts +2 -4
  31. package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -1
  32. package/dist/src/room/data-track/depacketizer.d.ts +51 -0
  33. package/dist/src/room/data-track/depacketizer.d.ts.map +1 -0
  34. package/dist/src/room/data-track/e2ee.d.ts +12 -0
  35. package/dist/src/room/data-track/e2ee.d.ts.map +1 -0
  36. package/dist/src/room/data-track/frame.d.ts +7 -0
  37. package/dist/src/room/data-track/frame.d.ts.map +1 -0
  38. package/dist/src/room/data-track/handle.d.ts +6 -7
  39. package/dist/src/room/data-track/handle.d.ts.map +1 -1
  40. package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +76 -0
  41. package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -0
  42. package/dist/src/room/data-track/outgoing/errors.d.ts +64 -0
  43. package/dist/src/room/data-track/outgoing/errors.d.ts.map +1 -0
  44. package/dist/src/room/data-track/outgoing/pipeline.d.ts +22 -0
  45. package/dist/src/room/data-track/outgoing/pipeline.d.ts.map +1 -0
  46. package/dist/src/room/data-track/outgoing/types.d.ts +31 -0
  47. package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -0
  48. package/dist/src/room/data-track/packet/index.d.ts +3 -3
  49. package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
  50. package/dist/src/room/data-track/packetizer.d.ts +43 -0
  51. package/dist/src/room/data-track/packetizer.d.ts.map +1 -0
  52. package/dist/src/room/data-track/track.d.ts +30 -0
  53. package/dist/src/room/data-track/track.d.ts.map +1 -0
  54. package/dist/src/room/data-track/utils.d.ts +34 -2
  55. package/dist/src/room/data-track/utils.d.ts.map +1 -1
  56. package/dist/src/room/debounce.d.ts +11 -0
  57. package/dist/src/room/debounce.d.ts.map +1 -0
  58. package/dist/src/room/events.d.ts +1 -1
  59. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  60. package/dist/src/room/track/LocalAudioTrack.d.ts +1 -1
  61. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  62. package/dist/src/room/track/LocalTrack.d.ts +2 -1
  63. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  64. package/dist/src/room/types.d.ts +0 -2
  65. package/dist/src/room/types.d.ts.map +1 -1
  66. package/dist/src/room/utils.d.ts +6 -1
  67. package/dist/src/room/utils.d.ts.map +1 -1
  68. package/dist/src/utils/subscribeToEvents.d.ts +12 -0
  69. package/dist/src/utils/subscribeToEvents.d.ts.map +1 -0
  70. package/dist/src/utils/throws.d.ts +4 -2
  71. package/dist/src/utils/throws.d.ts.map +1 -1
  72. package/dist/ts4.2/e2ee/E2eeManager.d.ts +2 -0
  73. package/dist/ts4.2/e2ee/KeyProvider.d.ts +2 -0
  74. package/dist/ts4.2/e2ee/events.d.ts +1 -1
  75. package/dist/ts4.2/e2ee/types.d.ts +1 -0
  76. package/dist/ts4.2/e2ee/worker/ParticipantKeyHandler.d.ts +2 -2
  77. package/dist/ts4.2/index.d.ts +7 -3
  78. package/dist/ts4.2/logger.d.ts +2 -1
  79. package/dist/ts4.2/room/PCTransport.d.ts +1 -6
  80. package/dist/ts4.2/room/data-stream/incoming/StreamReader.d.ts +2 -4
  81. package/dist/ts4.2/room/data-track/depacketizer.d.ts +51 -0
  82. package/dist/ts4.2/room/data-track/e2ee.d.ts +12 -0
  83. package/dist/ts4.2/room/data-track/frame.d.ts +7 -0
  84. package/dist/ts4.2/room/data-track/handle.d.ts +6 -7
  85. package/dist/ts4.2/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +77 -0
  86. package/dist/ts4.2/room/data-track/outgoing/errors.d.ts +64 -0
  87. package/dist/ts4.2/room/data-track/outgoing/pipeline.d.ts +22 -0
  88. package/dist/ts4.2/room/data-track/outgoing/types.d.ts +31 -0
  89. package/dist/ts4.2/room/data-track/packet/index.d.ts +3 -3
  90. package/dist/ts4.2/room/data-track/packetizer.d.ts +43 -0
  91. package/dist/ts4.2/room/data-track/track.d.ts +30 -0
  92. package/dist/ts4.2/room/data-track/utils.d.ts +34 -2
  93. package/dist/ts4.2/room/debounce.d.ts +11 -0
  94. package/dist/ts4.2/room/events.d.ts +1 -1
  95. package/dist/ts4.2/room/track/LocalAudioTrack.d.ts +1 -1
  96. package/dist/ts4.2/room/track/LocalTrack.d.ts +2 -1
  97. package/dist/ts4.2/room/types.d.ts +0 -2
  98. package/dist/ts4.2/room/utils.d.ts +6 -1
  99. package/dist/ts4.2/utils/subscribeToEvents.d.ts +12 -0
  100. package/dist/ts4.2/utils/throws.d.ts +4 -2
  101. package/package.json +4 -5
  102. package/src/e2ee/E2eeManager.ts +9 -5
  103. package/src/e2ee/KeyProvider.ts +10 -1
  104. package/src/e2ee/events.ts +1 -1
  105. package/src/e2ee/types.ts +1 -0
  106. package/src/e2ee/worker/ParticipantKeyHandler.ts +7 -4
  107. package/src/e2ee/worker/e2ee.worker.ts +20 -10
  108. package/src/index.ts +15 -5
  109. package/src/logger.ts +1 -0
  110. package/src/room/PCTransport.ts +2 -1
  111. package/src/room/PCTransportManager.ts +27 -9
  112. package/src/room/RTCEngine.ts +13 -2
  113. package/src/room/Room.ts +11 -5
  114. package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +5 -25
  115. package/src/room/data-stream/incoming/StreamReader.ts +56 -73
  116. package/src/room/data-track/depacketizer.test.ts +442 -0
  117. package/src/room/data-track/depacketizer.ts +298 -0
  118. package/src/room/data-track/e2ee.ts +14 -0
  119. package/src/room/data-track/frame.ts +8 -0
  120. package/src/room/data-track/handle.test.ts +1 -1
  121. package/src/room/data-track/handle.ts +9 -14
  122. package/src/room/data-track/outgoing/OutgoingDataTrackManager.test.ts +392 -0
  123. package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +302 -0
  124. package/src/room/data-track/outgoing/errors.ts +157 -0
  125. package/src/room/data-track/outgoing/pipeline.ts +76 -0
  126. package/src/room/data-track/outgoing/types.ts +37 -0
  127. package/src/room/data-track/packet/index.test.ts +9 -9
  128. package/src/room/data-track/packet/index.ts +11 -9
  129. package/src/room/data-track/packet/serializable.ts +1 -1
  130. package/src/room/data-track/packetizer.test.ts +131 -0
  131. package/src/room/data-track/packetizer.ts +132 -0
  132. package/src/room/data-track/track.ts +50 -0
  133. package/src/room/data-track/utils.test.ts +27 -1
  134. package/src/room/data-track/utils.ts +125 -5
  135. package/src/room/debounce.ts +115 -0
  136. package/src/room/events.ts +1 -1
  137. package/src/room/participant/LocalParticipant.ts +2 -0
  138. package/src/room/track/LocalAudioTrack.ts +10 -10
  139. package/src/room/track/LocalTrack.ts +14 -5
  140. package/src/room/track/LocalVideoTrack.ts +1 -1
  141. package/src/room/track/RemoteVideoTrack.ts +1 -1
  142. package/src/room/types.ts +0 -2
  143. package/src/room/utils.ts +7 -2
  144. package/src/utils/subscribeToEvents.ts +63 -0
  145. 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 { Future, bigIntToNumber } from '../../utils';
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
- let rejectingSignalFuture = new Future<never, Error>();
84
- let activeSignal: AbortSignal | null = null;
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 { done, value } = await Promise.race([
109
- reader.read(),
110
- // Rejects if this.signal is aborted
111
- rejectingSignalFuture.promise,
112
- // Rejects if something external says it should, like a participant disconnecting, etc
113
- this.outOfBandFailureRejectingFuture?.promise ??
114
- new Promise<never>(() => {
115
- /* never resolves */
116
- }),
117
- ]);
118
- if (done) {
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, outOfBandFailureRejectingFuture);
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
- const { done, value } = await Promise.race([
242
- reader.read(),
243
- // Rejects if this.signal is aborted
244
- rejectingSignalFuture.promise,
245
- // Rejects if something external says it should, like a participant disconnecting, etc
246
- this.outOfBandFailureRejectingFuture?.promise ??
247
- new Promise<never>(() => {
248
- /* never resolves */
249
- }),
250
- ]);
251
- if (done) {
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
+ });