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,6 +1,7 @@
1
1
  import { ChatMessage as ChatMessageModel, ClientInfo, DisconnectReason, Transcription as TranscriptionModel } from '@livekit/protocol';
2
2
  import TypedPromise from '../utils/TypedPromise';
3
3
  import type { BrowserDetails } from '../utils/browserParser';
4
+ import type { Throws } from '../utils/throws';
4
5
  import type { ConnectionError } from './errors';
5
6
  import type LocalParticipant from './participant/LocalParticipant';
6
7
  import type Participant from './participant/Participant';
@@ -71,8 +72,12 @@ export declare function getEmptyVideoStreamTrack(): MediaStreamTrack;
71
72
  export declare function createDummyVideoStreamTrack(width?: number, height?: number, enabled?: boolean, paintContent?: boolean): MediaStreamTrack;
72
73
  export declare function getEmptyAudioStreamTrack(): MediaStreamTrack;
73
74
  export declare function getStereoAudioStreamTrack(): MediaStreamTrack;
75
+ /** An object that represents a serialized version of a `new Promise((resolve, reject) => {})`
76
+ * constructor. Wait for a promise resolution with `await future.promise` and explicitly resolve or
77
+ * reject the inner promise with `future.resolve(...)` or `future.reject(...)`.
78
+ */
74
79
  export declare class Future<T, E extends Error> {
75
- promise: Promise<T>;
80
+ promise: Promise<Throws<T, E>>;
76
81
  resolve?: (arg: T) => void;
77
82
  reject?: (e: E) => void;
78
83
  onFinally?: () => void;
@@ -0,0 +1,12 @@
1
+ import type { EventMap } from 'typed-emitter';
2
+ import type TypedEventEmitter from 'typed-emitter';
3
+ /** A test helper to listen to events received by an event emitter and allow them to be imperatively
4
+ * queried after the fact. */
5
+ export declare function subscribeToEvents<Callbacks extends EventMap, EventNames extends keyof Callbacks = keyof Callbacks>(eventEmitter: TypedEventEmitter<Callbacks>, eventNames: Array<EventNames>): {
6
+ /** Listen for the next occurrance of an event to be emitted, or return the last event that was
7
+ * buffered (but hasn't been processed yet). */
8
+ waitFor<EventPayload extends Parameters<Callbacks[EventName]>[0], EventName extends EventNames = EventNames>(eventName: EventName): Promise<EventPayload>;
9
+ /** Cleanup any lingering subscriptions. */
10
+ unsubscribe: () => void;
11
+ };
12
+ //# sourceMappingURL=subscribeToEvents.d.ts.map
@@ -1,3 +1,4 @@
1
+ type Primitives = null | undefined | string | number | bigint | boolean | symbol;
1
2
  /**
2
3
  * Branded type that encodes possible thrown errors in the return type.
3
4
  *
@@ -11,9 +12,9 @@
11
12
  *
12
13
  * For more info about how this is checked, see ./throws-transformer at the root of this repo.
13
14
  */
14
- export type Throws<T, E extends Error> = T & {
15
+ export type Throws<T, E extends Error> = (T & {
15
16
  readonly __throws?: E;
16
- };
17
+ }) | Extract<T, Primitives>;
17
18
  /**
18
19
  * Extract the error types from a Throws type.
19
20
  */
@@ -34,4 +35,5 @@ export type CombineErrors<T extends any[]> = T extends [
34
35
  * throwing functions and wants to propagate their errors.
35
36
  */
36
37
  export type PropagatesErrors<T, AdditionalErrors extends Error = never> = Throws<ExtractSuccess<T>, ExtractErrors<T> | AdditionalErrors>;
38
+ export {};
37
39
  //# sourceMappingURL=throws.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livekit-client",
3
- "version": "2.17.1",
3
+ "version": "2.17.3",
4
4
  "description": "JavaScript/TypeScript client SDK for LiveKit",
5
5
  "main": "./dist/livekit-client.umd.js",
6
6
  "unpkg": "./dist/livekit-client.umd.js",
@@ -42,7 +42,6 @@
42
42
  "jose": "^6.1.0",
43
43
  "loglevel": "^1.9.2",
44
44
  "sdp-transform": "^2.15.0",
45
- "ts-debounce": "^4.0.0",
46
45
  "tslib": "2.8.1",
47
46
  "typed-emitter": "^2.1.0",
48
47
  "webrtc-adapter": "^9.0.1"
@@ -79,11 +78,11 @@
79
78
  "eslint-plugin-import-x": "^4.16.1",
80
79
  "eslint-plugin-prettier": "^5.5.4",
81
80
  "gh-pages": "6.3.0",
82
- "glob": "^13.0.0",
83
- "happy-dom": "^17.2.0",
81
+ "glob": "^13.0.6",
82
+ "happy-dom": "^20.0.0",
84
83
  "jsdom": "^26.1.0",
85
84
  "prettier": "^3.4.2",
86
- "rollup": "4.57.1",
85
+ "rollup": "4.59.0",
87
86
  "rollup-plugin-delete": "^2.1.0",
88
87
  "rollup-plugin-typescript2": "0.36.0",
89
88
  "size-limit": "^11.2.0",
@@ -169,7 +169,7 @@ export class E2EEManager
169
169
  case 'initAck':
170
170
  if (data.enabled) {
171
171
  this.keyProvider.getKeys().forEach((keyInfo) => {
172
- this.postKey(keyInfo);
172
+ this.postKey(keyInfo, false);
173
173
  });
174
174
  }
175
175
  break;
@@ -177,7 +177,7 @@ export class E2EEManager
177
177
  case 'enable':
178
178
  if (data.enabled) {
179
179
  this.keyProvider.getKeys().forEach((keyInfo) => {
180
- this.postKey(keyInfo);
180
+ this.postKey(keyInfo, false);
181
181
  });
182
182
  }
183
183
  if (
@@ -274,8 +274,9 @@ export class E2EEManager
274
274
  if (!this.room) {
275
275
  throw new TypeError(`expected room to be present on signal connect`);
276
276
  }
277
+ const latestKeyIndex = keyProvider.getLatestManuallySetKeyIndex();
277
278
  keyProvider.getKeys().forEach((keyInfo) => {
278
- this.postKey(keyInfo);
279
+ this.postKey(keyInfo, latestKeyIndex === (keyInfo.keyIndex ?? 0));
279
280
  });
280
281
  this.setParticipantCryptorEnabled(
281
282
  this.room.localParticipant.isE2EEEnabled,
@@ -305,7 +306,9 @@ export class E2EEManager
305
306
  });
306
307
 
307
308
  keyProvider
308
- .on(KeyProviderEvent.SetKey, (keyInfo) => this.postKey(keyInfo))
309
+ .on(KeyProviderEvent.SetKey, (keyInfo, updateCurrentKeyIndex) =>
310
+ this.postKey(keyInfo, updateCurrentKeyIndex ?? true),
311
+ )
309
312
  .on(KeyProviderEvent.RatchetRequest, (participantId, keyIndex) =>
310
313
  this.postRatchetRequest(participantId, keyIndex),
311
314
  );
@@ -376,7 +379,7 @@ export class E2EEManager
376
379
  this.worker.postMessage(msg);
377
380
  }
378
381
 
379
- private postKey({ key, participantIdentity, keyIndex }: KeyInfo) {
382
+ private postKey({ key, participantIdentity, keyIndex }: KeyInfo, updateCurrentKeyIndex: boolean) {
380
383
  if (!this.worker) {
381
384
  throw Error('could not set key, worker is missing');
382
385
  }
@@ -387,6 +390,7 @@ export class E2EEManager
387
390
  isPublisher: participantIdentity === this.room?.localParticipant.identity,
388
391
  key,
389
392
  keyIndex,
393
+ updateCurrentKeyIndex,
390
394
  },
391
395
  };
392
396
  this.worker.postMessage(msg);
@@ -14,6 +14,8 @@ export class BaseKeyProvider extends (EventEmitter as new () => TypedEventEmitte
14
14
 
15
15
  private readonly options: KeyProviderOptions;
16
16
 
17
+ private latestManuallySetKeyIndex = 0;
18
+
17
19
  constructor(options: Partial<KeyProviderOptions> = {}) {
18
20
  super();
19
21
  this.keyInfoMap = new Map();
@@ -35,7 +37,10 @@ export class BaseKeyProvider extends (EventEmitter as new () => TypedEventEmitte
35
37
  );
36
38
  }
37
39
  this.keyInfoMap.set(`${participantIdentity ?? 'shared'}-${keyIndex ?? 0}`, keyInfo);
38
- this.emit(KeyProviderEvent.SetKey, keyInfo);
40
+ if (keyIndex !== undefined) {
41
+ this.latestManuallySetKeyIndex = keyIndex;
42
+ }
43
+ this.emit(KeyProviderEvent.SetKey, keyInfo, keyIndex !== undefined);
39
44
  }
40
45
 
41
46
  /**
@@ -59,6 +64,10 @@ export class BaseKeyProvider extends (EventEmitter as new () => TypedEventEmitte
59
64
  return Array.from(this.keyInfoMap.values());
60
65
  }
61
66
 
67
+ getLatestManuallySetKeyIndex() {
68
+ return this.latestManuallySetKeyIndex;
69
+ }
70
+
62
71
  getOptions() {
63
72
  return this.options;
64
73
  }
@@ -12,7 +12,7 @@ export enum KeyProviderEvent {
12
12
  }
13
13
 
14
14
  export type KeyProviderCallbacks = {
15
- [KeyProviderEvent.SetKey]: (keyInfo: KeyInfo) => void;
15
+ [KeyProviderEvent.SetKey]: (keyInfo: KeyInfo, updateCurrentKeyIndex: boolean) => void;
16
16
  [KeyProviderEvent.RatchetRequest]: (participantIdentity?: string, keyIndex?: number) => void;
17
17
  [KeyProviderEvent.KeyRatcheted]: (
18
18
  ratchetedResult: RatchetResult,
package/src/e2ee/types.ts CHANGED
@@ -23,6 +23,7 @@ export interface SetKeyMessage extends BaseMessage {
23
23
  isPublisher: boolean;
24
24
  key: CryptoKey;
25
25
  keyIndex?: number;
26
+ updateCurrentKeyIndex: boolean;
26
27
  };
27
28
  }
28
29
 
@@ -154,9 +154,11 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
154
154
  * together with the material
155
155
  * also resets the valid key property and updates the currentKeyIndex
156
156
  */
157
- async setKey(material: CryptoKey, keyIndex = 0) {
158
- await this.setKeyFromMaterial(material, keyIndex);
159
- this.resetKeyStatus(keyIndex);
157
+ async setKey(material: CryptoKey, keyIndex = 0, updateCurrentKeyIndex = true) {
158
+ await this.setKeyFromMaterial(material, keyIndex, null, updateCurrentKeyIndex);
159
+ if (updateCurrentKeyIndex) {
160
+ this.resetKeyStatus(keyIndex);
161
+ }
160
162
  }
161
163
 
162
164
  /**
@@ -169,6 +171,7 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
169
171
  material: CryptoKey,
170
172
  keyIndex: number,
171
173
  ratchetedResult: RatchetResult | null = null,
174
+ updateCurrentKeyIndex = true,
172
175
  ) {
173
176
  const keySet = await deriveKeys(material, this.keyProviderOptions.ratchetSalt);
174
177
  const newIndex = keyIndex >= 0 ? keyIndex % this.cryptoKeyRing.length : this.currentKeyIndex;
@@ -178,7 +181,7 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
178
181
  ratchetSalt: this.keyProviderOptions.ratchetSalt,
179
182
  });
180
183
  this.setKeySet(keySet, newIndex, ratchetedResult);
181
- if (newIndex >= 0) this.currentKeyIndex = newIndex;
184
+ if (newIndex >= 0 && updateCurrentKeyIndex) this.currentKeyIndex = newIndex;
182
185
  }
183
186
 
184
187
  setKeySet(keySet: KeySet, keyIndex: number, ratchetedResult: RatchetResult | null = null) {
@@ -141,12 +141,16 @@ onmessage = (ev) => {
141
141
 
142
142
  case 'setKey':
143
143
  if (useSharedKey) {
144
- await setSharedKey(data.key, data.keyIndex);
144
+ await setSharedKey(data.key, data.keyIndex, data.updateCurrentKeyIndex);
145
145
  } else if (data.participantIdentity) {
146
146
  workerLogger.info(
147
147
  `set participant sender key ${data.participantIdentity} index ${data.keyIndex}`,
148
148
  );
149
- await getParticipantKeyHandler(data.participantIdentity).setKey(data.key, data.keyIndex);
149
+ await getParticipantKeyHandler(data.participantIdentity).setKey(
150
+ data.key,
151
+ data.keyIndex,
152
+ data.updateCurrentKeyIndex,
153
+ );
150
154
  } else {
151
155
  workerLogger.error('no participant Id was provided and shared key usage is disabled');
152
156
  }
@@ -281,9 +285,9 @@ function setEncryptionEnabled(enable: boolean, participantIdentity: string) {
281
285
  encryptionEnabledMap.set(participantIdentity, enable);
282
286
  }
283
287
 
284
- async function setSharedKey(key: CryptoKey, index?: number) {
288
+ async function setSharedKey(key: CryptoKey, index?: number, updateCurrentKeyIndex?: boolean) {
285
289
  workerLogger.info('set shared key', { index });
286
- await getSharedKeyHandler().setKey(key, index);
290
+ await getSharedKeyHandler().setKey(key, index, updateCurrentKeyIndex);
287
291
  }
288
292
 
289
293
  function setupCryptorErrorEvents(cryptor: FrameCryptor) {
@@ -325,17 +329,23 @@ function handleSifTrailer(trailer: Uint8Array) {
325
329
  // Operations using RTCRtpScriptTransform.
326
330
  // @ts-ignore
327
331
  if (self.RTCTransformEvent) {
328
- workerLogger.debug('setup transform event');
329
332
  // @ts-ignore
330
333
  self.onrtctransform = (event: RTCTransformEvent) => {
331
334
  // @ts-ignore
332
335
  const transformer = event.transformer;
333
- workerLogger.debug('transformer', transformer);
334
-
335
336
  const { kind, participantIdentity, trackId, codec } =
336
337
  transformer.options as ScriptTransformOptions;
337
- const cryptor = getTrackCryptor(participantIdentity, trackId);
338
- workerLogger.debug('transform', { codec });
339
- cryptor.setupTransform(kind, transformer.readable, transformer.writable, trackId, false, codec);
338
+ messageQueue.run(async () => {
339
+ const cryptor = getTrackCryptor(participantIdentity, trackId);
340
+ workerLogger.debug('onrtctransform setup', { participantIdentity, trackId, codec });
341
+ cryptor.setupTransform(
342
+ kind,
343
+ transformer.readable,
344
+ transformer.writable,
345
+ trackId,
346
+ false,
347
+ codec,
348
+ );
349
+ });
340
350
  };
341
351
  }
package/src/index.ts CHANGED
@@ -9,11 +9,18 @@ import {
9
9
  import { LogLevel, LoggerNames, getLogger, setLogExtension, setLogLevel } from './logger';
10
10
  import DefaultReconnectPolicy from './room/DefaultReconnectPolicy';
11
11
  import type { ReconnectContext, ReconnectPolicy } from './room/ReconnectPolicy';
12
- import Room, { ConnectionState } from './room/Room';
13
- import type { RoomEventCallbacks } from './room/Room';
12
+ import Room, { ConnectionState, type RoomEventCallbacks } from './room/Room';
14
13
  import * as attributes from './room/attribute-typings';
14
+ // FIXME: remove this import in a follow up data track pull request.
15
+ import './room/data-track/depacketizer';
16
+ // FIXME: remove this import in a follow up data track pull request.
17
+ import './room/data-track/outgoing/OutgoingDataTrackManager';
15
18
  import LocalParticipant from './room/participant/LocalParticipant';
16
- import Participant, { ConnectionQuality, ParticipantKind } from './room/participant/Participant';
19
+ import Participant, {
20
+ ConnectionQuality,
21
+ type ParticipantEventCallbacks,
22
+ ParticipantKind,
23
+ } from './room/participant/Participant';
17
24
  import type { ParticipantTrackPermission } from './room/participant/ParticipantTrackPermission';
18
25
  import RemoteParticipant from './room/participant/RemoteParticipant';
19
26
  import type {
@@ -32,8 +39,8 @@ import RemoteTrack from './room/track/RemoteTrack';
32
39
  import RemoteTrackPublication from './room/track/RemoteTrackPublication';
33
40
  import type { ElementInfo } from './room/track/RemoteVideoTrack';
34
41
  import RemoteVideoTrack from './room/track/RemoteVideoTrack';
35
- import { TrackPublication } from './room/track/TrackPublication';
36
- import type { LiveKitReactNativeInfo } from './room/types';
42
+ import { type PublicationEventCallbacks, TrackPublication } from './room/track/TrackPublication';
43
+ import type { LiveKitReactNativeInfo, TextStreamInfo } from './room/types';
37
44
  import type { AudioAnalyserOptions } from './room/utils';
38
45
  import {
39
46
  compareVersions,
@@ -140,6 +147,7 @@ export type {
140
147
  AudioAnalyserOptions,
141
148
  ElementInfo,
142
149
  LiveKitReactNativeInfo,
150
+ TextStreamInfo,
143
151
  ParticipantTrackPermission,
144
152
  AudioReceiverStats,
145
153
  AudioSenderStats,
@@ -148,6 +156,8 @@ export type {
148
156
  ReconnectContext,
149
157
  ReconnectPolicy,
150
158
  RoomEventCallbacks,
159
+ ParticipantEventCallbacks,
160
+ PublicationEventCallbacks,
151
161
  };
152
162
  export { DataTrackPacket, type DataTrackPacketHeader } from './room/data-track/packet';
153
163
  export {
package/src/logger.ts CHANGED
@@ -21,6 +21,7 @@ export enum LoggerNames {
21
21
  PCManager = 'livekit-pc-manager',
22
22
  PCTransport = 'livekit-pc-transport',
23
23
  E2EE = 'lk-e2ee',
24
+ DataTracks = 'livekit-data-tracks',
24
25
  }
25
26
 
26
27
  type LogLevelString = keyof typeof LogLevel;
@@ -1,9 +1,9 @@
1
1
  import { Mutex } from '@livekit/mutex';
2
2
  import { EventEmitter } from 'events';
3
3
  import { parse, write } from 'sdp-transform';
4
- import { debounce } from 'ts-debounce';
5
4
  import type { MediaDescription, SessionDescription } from 'sdp-transform';
6
5
  import log, { LoggerNames, getLogger } from '../logger';
6
+ import { debounce } from './debounce';
7
7
  import { NegotiationError, UnexpectedConnectionState } from './errors';
8
8
  import type { LoggerOptions } from './types';
9
9
  import { ddExtensionURI, isSVCCodec, isSafari } from './utils';
@@ -278,6 +278,7 @@ export default class PCTransport extends EventEmitter {
278
278
  await this._pc.setRemoteDescription(currentSD);
279
279
  } else {
280
280
  this.renegotiate = true;
281
+ this.log.debug('requesting renegotiation', { ...this.logContext });
281
282
  return;
282
283
  }
283
284
  } else if (!this._pc || this._pc.signalingState === 'closed') {
@@ -229,28 +229,46 @@ export class PCTransportManager {
229
229
 
230
230
  async negotiate(abortController: AbortController) {
231
231
  return new TypedPromise<void, NegotiationError | Error>(async (resolve, reject) => {
232
- const negotiationTimeout = setTimeout(() => {
232
+ let negotiationTimeout = setTimeout(() => {
233
233
  reject(new NegotiationError('negotiation timed out'));
234
234
  }, this.peerConnectionTimeout);
235
235
 
236
- const abortHandler = () => {
236
+ const cleanup = () => {
237
237
  clearTimeout(negotiationTimeout);
238
+ this.publisher.off(PCEvents.NegotiationStarted, onNegotiationStarted);
239
+ abortController.signal.removeEventListener('abort', abortHandler);
240
+ };
241
+
242
+ const abortHandler = () => {
243
+ cleanup();
238
244
  reject(new NegotiationError('negotiation aborted'));
239
245
  };
240
246
 
241
- abortController.signal.addEventListener('abort', abortHandler);
242
- this.publisher.once(PCEvents.NegotiationStarted, () => {
247
+ // Reset the timeout each time a renegotiation cycle starts. This
248
+ // prevents premature timeouts when the negotiation machinery is
249
+ // actively renegotiating (offers going out, answers coming back) but
250
+ // NegotiationComplete hasn't fired yet because new requirements keep
251
+ // arriving between offer/answer round-trips.
252
+ const onNegotiationStarted = () => {
243
253
  if (abortController.signal.aborted) {
244
254
  return;
245
255
  }
246
- this.publisher.once(PCEvents.NegotiationComplete, () => {
247
- clearTimeout(negotiationTimeout);
248
- resolve();
249
- });
256
+ clearTimeout(negotiationTimeout);
257
+ negotiationTimeout = setTimeout(() => {
258
+ cleanup();
259
+ reject(new NegotiationError('negotiation timed out'));
260
+ }, this.peerConnectionTimeout);
261
+ };
262
+
263
+ abortController.signal.addEventListener('abort', abortHandler);
264
+ this.publisher.on(PCEvents.NegotiationStarted, onNegotiationStarted);
265
+ this.publisher.once(PCEvents.NegotiationComplete, () => {
266
+ cleanup();
267
+ resolve();
250
268
  });
251
269
 
252
270
  await this.publisher.negotiate((e) => {
253
- clearTimeout(negotiationTimeout);
271
+ cleanup();
254
272
  if (e instanceof Error) {
255
273
  reject(e);
256
274
  } else {
@@ -1477,8 +1477,11 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1477
1477
  if (!this.pcManager) {
1478
1478
  return false;
1479
1479
  }
1480
- // primary connection
1481
- if (this.pcManager.currentState !== PCTransportState.CONNECTED) {
1480
+ const allowedConnectionStates: PCTransportState[] = [
1481
+ PCTransportState.CONNECTING,
1482
+ PCTransportState.CONNECTED,
1483
+ ];
1484
+ if (!allowedConnectionStates.includes(this.pcManager.currentState)) {
1482
1485
  return false;
1483
1486
  }
1484
1487
 
@@ -1521,6 +1524,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1521
1524
  reject(new NegotiationError('cannot negotiate on closed engine'));
1522
1525
  }
1523
1526
  this.on(EngineEvent.Closing, handleClosed);
1527
+ this.on(EngineEvent.Restarting, handleClosed);
1524
1528
 
1525
1529
  this.pcManager.publisher.once(
1526
1530
  PCEvents.RTPVideoPayloadTypes,
@@ -1540,6 +1544,12 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1540
1544
  await this.pcManager.negotiate(abortController);
1541
1545
  resolve();
1542
1546
  } catch (e: unknown) {
1547
+ if (abortController.signal.aborted) {
1548
+ // negotiation was aborted due to engine close or restart, resolve
1549
+ // cleanly to avoid triggering a cascading reconnect loop
1550
+ resolve();
1551
+ return;
1552
+ }
1543
1553
  if (e instanceof NegotiationError) {
1544
1554
  this.fullReconnectOnNext = true;
1545
1555
  }
@@ -1551,6 +1561,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1551
1561
  }
1552
1562
  } finally {
1553
1563
  this.off(EngineEvent.Closing, handleClosed);
1564
+ this.off(EngineEvent.Restarting, handleClosed);
1554
1565
  }
1555
1566
  });
1556
1567
  }
package/src/room/Room.ts CHANGED
@@ -740,7 +740,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
740
740
  `Initial connection failed with ConnectionError: ${error.message}. Retrying with another region: ${nextUrl}`,
741
741
  this.logContext,
742
742
  );
743
- this.recreateEngine();
743
+ this.recreateEngine(true);
744
744
  await connectFn(resolve, reject, nextUrl);
745
745
  } else {
746
746
  this.handleDisconnect(
@@ -866,7 +866,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
866
866
  ) {
867
867
  this.log.info('Reconnection attempt replaced by new connection attempt', this.logContext);
868
868
  // make sure we close and recreate the existing engine in order to get rid of any potentially ongoing reconnection attempts
869
- this.recreateEngine();
869
+ this.recreateEngine(true);
870
870
  } else {
871
871
  // create engine if previously disconnected
872
872
  this.maybeCreateEngine();
@@ -1380,8 +1380,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1380
1380
  );
1381
1381
  }
1382
1382
 
1383
- private recreateEngine() {
1384
- this.engine?.close();
1383
+ private recreateEngine(sendLeave?: boolean) {
1384
+ const oldEngine = this.engine;
1385
+
1386
+ if (sendLeave && oldEngine && !oldEngine.client.isDisconnected) {
1387
+ oldEngine.client.sendLeave().finally(() => oldEngine.close());
1388
+ } else {
1389
+ oldEngine?.close();
1390
+ }
1385
1391
  /* @ts-ignore */
1386
1392
  this.engine = undefined;
1387
1393
  this.isResuming = false;
@@ -2307,7 +2313,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
2307
2313
  engine: this.engine
2308
2314
  ? {
2309
2315
  closed: this.engine.isClosed,
2310
- transportsConnected: this.engine.verifyTransport(),
2316
+ transportsConnectedOrConnecting: this.engine.verifyTransport(),
2311
2317
  }
2312
2318
  : undefined,
2313
2319
  });
@@ -8,7 +8,7 @@ import {
8
8
  import log from '../../../logger';
9
9
  import { DataStreamError, DataStreamErrorReason } from '../../errors';
10
10
  import { type ByteStreamInfo, type StreamController, type TextStreamInfo } from '../../types';
11
- import { Future, bigIntToNumber } from '../../utils';
11
+ import { bigIntToNumber } from '../../utils';
12
12
  import {
13
13
  type ByteStreamHandler,
14
14
  ByteStreamReader,
@@ -78,11 +78,11 @@ export default class IncomingDataStreamManager {
78
78
  DataStreamErrorReason.AbnormalEnd,
79
79
  );
80
80
  for (const [id, controller] of byteStreamsBeingSentByDisconnectingParticipant) {
81
- controller.outOfBandFailureRejectingFuture.reject?.(abnormalEndError);
81
+ controller.controller.error(abnormalEndError);
82
82
  this.byteStreamControllers.delete(id);
83
83
  }
84
84
  for (const [id, controller] of textStreamsBeingSentByDisconnectingParticipant) {
85
- controller.outOfBandFailureRejectingFuture.reject?.(abnormalEndError);
85
+ controller.controller.error(abnormalEndError);
86
86
  this.textStreamControllers.delete(id);
87
87
  }
88
88
  }
@@ -121,10 +121,6 @@ export default class IncomingDataStreamManager {
121
121
  }
122
122
 
123
123
  let streamController: ReadableStreamDefaultController<DataStream_Chunk>;
124
- const outOfBandFailureRejectingFuture = new Future<never, Error>();
125
- outOfBandFailureRejectingFuture.promise.catch((err) => {
126
- this.log.error(err);
127
- });
128
124
 
129
125
  const info: ByteStreamInfo = {
130
126
  id: streamHeader.streamId,
@@ -152,17 +148,11 @@ export default class IncomingDataStreamManager {
152
148
  controller: streamController,
153
149
  startTime: Date.now(),
154
150
  sendingParticipantIdentity: participantIdentity,
155
- outOfBandFailureRejectingFuture,
156
151
  });
157
152
  },
158
153
  });
159
154
  streamHandlerCallback(
160
- new ByteStreamReader(
161
- info,
162
- stream,
163
- bigIntToNumber(streamHeader.totalLength),
164
- outOfBandFailureRejectingFuture,
165
- ),
155
+ new ByteStreamReader(info, stream, bigIntToNumber(streamHeader.totalLength)),
166
156
  {
167
157
  identity: participantIdentity,
168
158
  },
@@ -178,10 +168,6 @@ export default class IncomingDataStreamManager {
178
168
  }
179
169
 
180
170
  let streamController: ReadableStreamDefaultController<DataStream_Chunk>;
181
- const outOfBandFailureRejectingFuture = new Future<never, Error>();
182
- outOfBandFailureRejectingFuture.promise.catch((err) => {
183
- this.log.error(err);
184
- });
185
171
 
186
172
  const info: TextStreamInfo = {
187
173
  id: streamHeader.streamId,
@@ -210,17 +196,11 @@ export default class IncomingDataStreamManager {
210
196
  controller: streamController,
211
197
  startTime: Date.now(),
212
198
  sendingParticipantIdentity: participantIdentity,
213
- outOfBandFailureRejectingFuture,
214
199
  });
215
200
  },
216
201
  });
217
202
  streamHandlerCallback(
218
- new TextStreamReader(
219
- info,
220
- stream,
221
- bigIntToNumber(streamHeader.totalLength),
222
- outOfBandFailureRejectingFuture,
223
- ),
203
+ new TextStreamReader(info, stream, bigIntToNumber(streamHeader.totalLength)),
224
204
  { identity: participantIdentity },
225
205
  );
226
206
  }