livekit-client 2.18.0 → 2.18.2

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/dist/livekit-client.e2ee.worker.js +1 -1
  2. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  3. package/dist/livekit-client.e2ee.worker.mjs +8 -7
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +8026 -5883
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.umd.js +1 -1
  8. package/dist/livekit-client.umd.js.map +1 -1
  9. package/dist/src/api/SignalClient.d.ts +12 -4
  10. package/dist/src/api/SignalClient.d.ts.map +1 -1
  11. package/dist/src/connectionHelper/ConnectionCheck.d.ts +1 -1
  12. package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
  13. package/dist/src/e2ee/constants.d.ts.map +1 -1
  14. package/dist/src/e2ee/types.d.ts +6 -0
  15. package/dist/src/e2ee/types.d.ts.map +1 -1
  16. package/dist/src/e2ee/utils.d.ts +2 -1
  17. package/dist/src/e2ee/utils.d.ts.map +1 -1
  18. package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
  19. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  20. package/dist/src/index.d.ts +6 -4
  21. package/dist/src/index.d.ts.map +1 -1
  22. package/dist/src/room/PCTransport.d.ts +5 -0
  23. package/dist/src/room/PCTransport.d.ts.map +1 -1
  24. package/dist/src/room/PCTransportManager.d.ts +1 -1
  25. package/dist/src/room/PCTransportManager.d.ts.map +1 -1
  26. package/dist/src/room/RTCEngine.d.ts +28 -11
  27. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  28. package/dist/src/room/Room.d.ts +14 -3
  29. package/dist/src/room/Room.d.ts.map +1 -1
  30. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +5 -1
  31. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
  32. package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -1
  33. package/dist/src/room/data-track/LocalDataTrack.d.ts +28 -5
  34. package/dist/src/room/data-track/LocalDataTrack.d.ts.map +1 -1
  35. package/dist/src/room/data-track/RemoteDataTrack.d.ts +5 -5
  36. package/dist/src/room/data-track/RemoteDataTrack.d.ts.map +1 -1
  37. package/dist/src/room/data-track/depacketizer.d.ts +4 -4
  38. package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
  39. package/dist/src/room/data-track/frame.d.ts +14 -0
  40. package/dist/src/room/data-track/frame.d.ts.map +1 -1
  41. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +19 -11
  42. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
  43. package/dist/src/room/data-track/incoming/pipeline.d.ts +6 -5
  44. package/dist/src/room/data-track/incoming/pipeline.d.ts.map +1 -1
  45. package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +57 -23
  46. package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -1
  47. package/dist/src/room/data-track/outgoing/errors.d.ts +16 -6
  48. package/dist/src/room/data-track/outgoing/errors.d.ts.map +1 -1
  49. package/dist/src/room/data-track/outgoing/pipeline.d.ts +7 -6
  50. package/dist/src/room/data-track/outgoing/pipeline.d.ts.map +1 -1
  51. package/dist/src/room/data-track/outgoing/types.d.ts +14 -4
  52. package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
  53. package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
  54. package/dist/src/room/data-track/packetizer.d.ts +4 -4
  55. package/dist/src/room/data-track/packetizer.d.ts.map +1 -1
  56. package/dist/src/room/data-track/track-interfaces.d.ts +1 -1
  57. package/dist/src/room/data-track/track-interfaces.d.ts.map +1 -1
  58. package/dist/src/room/data-track/types.d.ts +6 -1
  59. package/dist/src/room/data-track/types.d.ts.map +1 -1
  60. package/dist/src/room/events.d.ts +24 -3
  61. package/dist/src/room/events.d.ts.map +1 -1
  62. package/dist/src/room/participant/LocalParticipant.d.ts +12 -1
  63. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  64. package/dist/src/room/participant/RemoteParticipant.d.ts +13 -0
  65. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  66. package/dist/src/room/utils.d.ts +1 -0
  67. package/dist/src/room/utils.d.ts.map +1 -1
  68. package/dist/src/utils/deferrable-map.d.ts +32 -0
  69. package/dist/src/utils/deferrable-map.d.ts.map +1 -0
  70. package/dist/src/utils/serializer.d.ts +48 -0
  71. package/dist/src/utils/serializer.d.ts.map +1 -0
  72. package/dist/ts4.2/api/SignalClient.d.ts +12 -4
  73. package/dist/ts4.2/connectionHelper/ConnectionCheck.d.ts +2 -1
  74. package/dist/ts4.2/e2ee/types.d.ts +6 -0
  75. package/dist/ts4.2/e2ee/utils.d.ts +2 -1
  76. package/dist/ts4.2/index.d.ts +7 -4
  77. package/dist/ts4.2/room/PCTransport.d.ts +5 -0
  78. package/dist/ts4.2/room/PCTransportManager.d.ts +1 -1
  79. package/dist/ts4.2/room/RTCEngine.d.ts +28 -11
  80. package/dist/ts4.2/room/Room.d.ts +14 -3
  81. package/dist/ts4.2/room/data-stream/incoming/IncomingDataStreamManager.d.ts +5 -1
  82. package/dist/ts4.2/room/data-track/LocalDataTrack.d.ts +27 -4
  83. package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +4 -4
  84. package/dist/ts4.2/room/data-track/depacketizer.d.ts +4 -4
  85. package/dist/ts4.2/room/data-track/frame.d.ts +14 -0
  86. package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +21 -10
  87. package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +6 -5
  88. package/dist/ts4.2/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +57 -23
  89. package/dist/ts4.2/room/data-track/outgoing/errors.d.ts +16 -6
  90. package/dist/ts4.2/room/data-track/outgoing/pipeline.d.ts +7 -6
  91. package/dist/ts4.2/room/data-track/outgoing/types.d.ts +14 -4
  92. package/dist/ts4.2/room/data-track/packetizer.d.ts +4 -4
  93. package/dist/ts4.2/room/data-track/track-interfaces.d.ts +1 -1
  94. package/dist/ts4.2/room/data-track/types.d.ts +6 -1
  95. package/dist/ts4.2/room/events.d.ts +24 -3
  96. package/dist/ts4.2/room/participant/LocalParticipant.d.ts +12 -1
  97. package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +13 -0
  98. package/dist/ts4.2/room/utils.d.ts +1 -0
  99. package/dist/ts4.2/utils/deferrable-map.d.ts +32 -0
  100. package/dist/ts4.2/utils/serializer.d.ts +48 -0
  101. package/package.json +1 -1
  102. package/src/api/SignalClient.test.ts +9 -4
  103. package/src/api/SignalClient.ts +116 -9
  104. package/src/connectionHelper/ConnectionCheck.ts +1 -1
  105. package/src/e2ee/constants.ts +1 -0
  106. package/src/e2ee/types.ts +6 -0
  107. package/src/e2ee/utils.ts +4 -3
  108. package/src/e2ee/worker/DataCryptor.ts +1 -4
  109. package/src/e2ee/worker/FrameCryptor.ts +1 -4
  110. package/src/e2ee/worker/ParticipantKeyHandler.ts +1 -1
  111. package/src/index.ts +13 -4
  112. package/src/room/PCTransport.ts +41 -1
  113. package/src/room/PCTransportManager.ts +1 -1
  114. package/src/room/RTCEngine.ts +312 -125
  115. package/src/room/Room.ts +168 -35
  116. package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +26 -2
  117. package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +7 -7
  118. package/src/room/data-track/LocalDataTrack.ts +83 -10
  119. package/src/room/data-track/RemoteDataTrack.ts +7 -9
  120. package/src/room/data-track/depacketizer.ts +21 -12
  121. package/src/room/data-track/frame.ts +28 -2
  122. package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +58 -73
  123. package/src/room/data-track/incoming/IncomingDataTrackManager.ts +139 -80
  124. package/src/room/data-track/incoming/pipeline.ts +29 -24
  125. package/src/room/data-track/outgoing/OutgoingDataTrackManager.test.ts +225 -32
  126. package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +150 -75
  127. package/src/room/data-track/outgoing/errors.ts +36 -7
  128. package/src/room/data-track/outgoing/pipeline.ts +23 -17
  129. package/src/room/data-track/outgoing/types.ts +12 -3
  130. package/src/room/data-track/packet/extensions.ts +17 -22
  131. package/src/room/data-track/packet/index.test.ts +22 -33
  132. package/src/room/data-track/packetizer.test.ts +2 -2
  133. package/src/room/data-track/packetizer.ts +4 -4
  134. package/src/room/data-track/track-interfaces.ts +1 -1
  135. package/src/room/data-track/types.ts +21 -1
  136. package/src/room/events.ts +26 -1
  137. package/src/room/participant/LocalParticipant.ts +74 -8
  138. package/src/room/participant/RemoteParticipant.ts +25 -0
  139. package/src/room/utils.ts +4 -0
  140. package/src/utils/deferrable-map.ts +109 -0
  141. package/src/utils/serializer.ts +72 -0
  142. package/dist/src/room/data-track/e2ee.d.ts +0 -12
  143. package/dist/src/room/data-track/e2ee.d.ts.map +0 -1
  144. package/dist/ts4.2/room/data-track/e2ee.d.ts +0 -12
  145. package/src/room/data-track/e2ee.ts +0 -15
@@ -2,6 +2,7 @@ import { type JoinResponse, type ParticipantUpdate } from '@livekit/protocol';
2
2
  import { EventEmitter } from 'events';
3
3
  import type { Throws } from '@livekit/throws-transformer/throws';
4
4
  import type TypedEmitter from 'typed-emitter';
5
+ import type { BaseE2EEManager } from '../../../e2ee/E2eeManager';
5
6
  import { LoggerNames, getLogger } from '../../../logger';
6
7
  import { abortSignalAny, abortSignalTimeout } from '../../../utils/abort-signal-polyfill';
7
8
  import type Participant from '../../participant/Participant';
@@ -9,8 +10,7 @@ import type RemoteParticipant from '../../participant/RemoteParticipant';
9
10
  import { Future } from '../../utils';
10
11
  import RemoteDataTrack from '../RemoteDataTrack';
11
12
  import { DataTrackDepacketizerDropError } from '../depacketizer';
12
- import type { DecryptionProvider } from '../e2ee';
13
- import type { DataTrackFrame } from '../frame';
13
+ import { type DataTrackFrame, DataTrackFrameInternal } from '../frame';
14
14
  import { DataTrackHandle } from '../handle';
15
15
  import { DataTrackPacket } from '../packet';
16
16
  import { type DataTrackInfo, type DataTrackSid } from '../types';
@@ -30,10 +30,10 @@ export type DataTrackIncomingManagerCallbacks = {
30
30
 
31
31
  /** A track has been published by a remote participant and is available to be
32
32
  * subscribed to. */
33
- trackAvailable: (event: EventTrackAvailable) => void;
33
+ trackPublished: (event: EventTrackAvailable) => void;
34
34
 
35
35
  /** A track has been unpublished by a remote participant and can no longer be subscribed to. */
36
- trackUnavailable: (event: EventTrackUnavailable) => void;
36
+ trackUnpublished: (event: EventTrackUnavailable) => void;
37
37
  };
38
38
 
39
39
  /** Track is not subscribed to. */
@@ -69,7 +69,7 @@ type IncomingDataTrackManagerOptions = {
69
69
  * If none, remote tracks using end-to-end encryption will not be available
70
70
  * for subscription.
71
71
  */
72
- decryptionProvider: DecryptionProvider | null;
72
+ e2eeManager?: BaseE2EEManager;
73
73
  };
74
74
 
75
75
  /** How long to wait when attempting to subscribe before timing out. */
@@ -77,10 +77,10 @@ const SUBSCRIBE_TIMEOUT_MILLISECONDS = 10_000;
77
77
 
78
78
  /** Maximum number of {@link DataTrackFrame}s that are cached for each ReadableStream subscription.
79
79
  * If data comes in too fast and saturates this threshold, backpressure will be applied. */
80
- const READABLE_STREAM_DEFAULT_HIGH_WATER_MARK = 16;
80
+ const READABLE_STREAM_DEFAULT_BUFFER_SIZE = 16;
81
81
 
82
82
  export default class IncomingDataTrackManager extends (EventEmitter as new () => TypedEmitter<DataTrackIncomingManagerCallbacks>) {
83
- private decryptionProvider: DecryptionProvider | null;
83
+ private e2eeManager: BaseE2EEManager | null;
84
84
 
85
85
  /** Mapping between track SID and descriptor. */
86
86
  private descriptors = new Map<DataTrackSid, Descriptor<SubscriptionState>>();
@@ -95,7 +95,99 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
95
95
 
96
96
  constructor(options?: IncomingDataTrackManagerOptions) {
97
97
  super();
98
- this.decryptionProvider = options?.decryptionProvider ?? null;
98
+ this.e2eeManager = options?.e2eeManager ?? null;
99
+ }
100
+
101
+ /** @internal */
102
+ updateE2eeManager(e2eeManager: BaseE2EEManager | null) {
103
+ this.e2eeManager = e2eeManager;
104
+
105
+ // Propegate downwards to all pre-existing pipelines
106
+ for (const descriptor of this.descriptors.values()) {
107
+ if (descriptor.subscription.type === 'active') {
108
+ descriptor.subscription.pipeline.updateE2eeManager(e2eeManager);
109
+ }
110
+ }
111
+ }
112
+
113
+ /** Allocates a ReadableStream which emits when a new {@link DataTrackFrame} is received from the
114
+ * SFU. The SFU subscription is initiated lazily when the stream is created.
115
+ *
116
+ * @returns A tuple of the ReadableStream and a Promise that resolves once the SFU subscription
117
+ * is fully established / the stream is ready to receive frames.
118
+ *
119
+ * @internal
120
+ **/
121
+ openSubscriptionStream(
122
+ sid: DataTrackSid,
123
+ signal?: AbortSignal,
124
+ bufferSize = READABLE_STREAM_DEFAULT_BUFFER_SIZE,
125
+ ): [ReadableStream<DataTrackFrame>, Promise<Throws<void, DataTrackSubscribeError>>] {
126
+ let streamController: ReadableStreamDefaultController<DataTrackFrame> | null = null;
127
+ const sfuSubscriptionComplete = new Future<void, DataTrackSubscribeError>();
128
+
129
+ const stream = new ReadableStream<DataTrackFrame>(
130
+ {
131
+ start: (controller) => {
132
+ streamController = controller;
133
+
134
+ const onAbort = () => {
135
+ controller.error(DataTrackSubscribeError.cancelled());
136
+ sfuSubscriptionComplete.reject?.(DataTrackSubscribeError.cancelled());
137
+ };
138
+
139
+ this.subscribeRequest(sid, signal)
140
+ .then(async () => {
141
+ signal?.addEventListener('abort', onAbort);
142
+
143
+ const descriptor = this.descriptors.get(sid);
144
+ if (!descriptor) {
145
+ log.error(`Unknown track ${sid}`);
146
+ return;
147
+ }
148
+ if (descriptor.subscription.type !== 'active') {
149
+ log.error(`Subscription for track ${sid} is not active`);
150
+ return;
151
+ }
152
+
153
+ descriptor.subscription.streamControllers.add(controller);
154
+ sfuSubscriptionComplete.resolve?.();
155
+ })
156
+ .catch((err) => {
157
+ controller.error(err);
158
+ sfuSubscriptionComplete.reject?.(err);
159
+ })
160
+ .finally(() => {
161
+ signal?.removeEventListener('abort', onAbort);
162
+ });
163
+ },
164
+ cancel: () => {
165
+ if (!streamController) {
166
+ log.warn(`ReadableStream subscribed to ${sid} was not started.`);
167
+ return;
168
+ }
169
+ const descriptor = this.descriptors.get(sid);
170
+ if (!descriptor) {
171
+ log.warn(`Unknown track ${sid}, skipping cancel...`);
172
+ return;
173
+ }
174
+ if (descriptor.subscription.type !== 'active') {
175
+ log.warn(`Subscription for track ${sid} is not active, skipping cancel...`);
176
+ return;
177
+ }
178
+
179
+ descriptor.subscription.streamControllers.delete(streamController);
180
+
181
+ // If no active stream controllers are left, also unsubscribe on the SFU end.
182
+ if (descriptor.subscription.streamControllers.size === 0) {
183
+ this.unSubscribeRequest(descriptor.info.sid);
184
+ }
185
+ },
186
+ },
187
+ new CountQueuingStrategy({ highWaterMark: bufferSize }),
188
+ );
189
+
190
+ return [stream, sfuSubscriptionComplete.promise];
99
191
  }
100
192
 
101
193
  /** Client requested to subscribe to a data track.
@@ -109,8 +201,7 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
109
201
  async subscribeRequest(
110
202
  sid: DataTrackSid,
111
203
  signal?: AbortSignal,
112
- highWaterMark = READABLE_STREAM_DEFAULT_HIGH_WATER_MARK,
113
- ): Promise<Throws<ReadableStream<DataTrackFrame>, DataTrackSubscribeError>> {
204
+ ): Promise<Throws<void, DataTrackSubscribeError>> {
114
205
  const descriptor = this.descriptors.get(sid);
115
206
  if (!descriptor) {
116
207
  // @throws-transformer ignore - this should be treated as a "panic" and not be caught
@@ -122,6 +213,10 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
122
213
  userProvidedSignal?: AbortSignal,
123
214
  timeoutSignal?: AbortSignal,
124
215
  ) => {
216
+ if (currentDescriptor.subscription.type === 'active') {
217
+ // Subscription has already become active! So bail out early, there is nothing to wait for.
218
+ return;
219
+ }
125
220
  if (currentDescriptor.subscription.type !== 'pending') {
126
221
  // @throws-transformer ignore - this should be treated as a "panic" and not be caught
127
222
  throw new Error(
@@ -170,8 +265,6 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
170
265
  combinedSignal.addEventListener('abort', onAbort);
171
266
  await proxiedCompletionFuture.promise;
172
267
  combinedSignal.removeEventListener('abort', onAbort);
173
-
174
- return this.createReadableStream(sid, highWaterMark);
175
268
  };
176
269
 
177
270
  switch (descriptor.subscription.type) {
@@ -203,72 +296,22 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
203
296
  const timeoutSignal = abortSignalTimeout(SUBSCRIBE_TIMEOUT_MILLISECONDS);
204
297
 
205
298
  // Wait for the subscription to complete, or time out if it takes too long
206
- const reader = await waitForCompletionFuture(descriptor, signal, timeoutSignal);
207
- return reader;
299
+ await waitForCompletionFuture(descriptor, signal, timeoutSignal);
300
+ return;
208
301
  }
209
302
  case 'pending': {
210
303
  descriptor.subscription.pendingRequestCount += 1;
211
304
 
212
305
  // Wait for the subscription to complete
213
- const reader = await waitForCompletionFuture(descriptor, signal);
214
- return reader;
306
+ await waitForCompletionFuture(descriptor, signal);
307
+ return;
215
308
  }
216
309
  case 'active': {
217
- return this.createReadableStream(sid);
310
+ return;
218
311
  }
219
312
  }
220
313
  }
221
314
 
222
- /** Allocates a ReadableStream which emits when a new {@link DataTrackFrame} is received from the
223
- * SFU. */
224
- private createReadableStream(
225
- sid: DataTrackSid,
226
- highWaterMark = READABLE_STREAM_DEFAULT_HIGH_WATER_MARK,
227
- ) {
228
- let streamController: ReadableStreamDefaultController<DataTrackFrame> | null = null;
229
- return new ReadableStream<DataTrackFrame>(
230
- {
231
- start: (controller) => {
232
- streamController = controller;
233
- const descriptor = this.descriptors.get(sid);
234
- if (!descriptor) {
235
- log.error(`Unknown track ${sid}`);
236
- return;
237
- }
238
- if (descriptor.subscription.type !== 'active') {
239
- log.error(`Subscription for track ${sid} is not active`);
240
- return;
241
- }
242
-
243
- descriptor.subscription.streamControllers.add(controller);
244
- },
245
- cancel: () => {
246
- if (!streamController) {
247
- log.warn(`ReadableStream subscribed to ${sid} was not started.`);
248
- return;
249
- }
250
- const descriptor = this.descriptors.get(sid);
251
- if (!descriptor) {
252
- log.warn(`Unknown track ${sid}, skipping cancel...`);
253
- return;
254
- }
255
- if (descriptor.subscription.type !== 'active') {
256
- log.warn(`Subscription for track ${sid} is not active, skipping cancel...`);
257
- return;
258
- }
259
-
260
- descriptor.subscription.streamControllers.delete(streamController);
261
-
262
- // If no active stream controllers are left, also unsubscribe on the SFU end.
263
- if (descriptor.subscription.streamControllers.size === 0) {
264
- this.unSubscribeRequest(descriptor.info.sid);
265
- }
266
- },
267
- },
268
- new CountQueuingStrategy({ highWaterMark }),
269
- );
270
- }
271
-
272
315
  /**
273
316
  * Get information about all currently subscribed tracks.
274
317
  * @internal */
@@ -333,8 +376,12 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
333
376
  }
334
377
 
335
378
  // Detect published track
336
- const sidsInUpdate = new Set<DataTrackSid>();
379
+ const publisherParticipantToSidsInUpdate = new Map<
380
+ Participant['identity'],
381
+ Set<DataTrackSid>
382
+ >();
337
383
  for (const [publisherIdentity, infos] of updates.entries()) {
384
+ const sidsInUpdate = new Set<DataTrackSid>();
338
385
  for (const info of infos) {
339
386
  sidsInUpdate.add(info.sid);
340
387
  if (this.descriptors.has(info.sid)) {
@@ -342,14 +389,18 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
342
389
  }
343
390
  await this.handleTrackPublished(publisherIdentity, info);
344
391
  }
392
+ publisherParticipantToSidsInUpdate.set(publisherIdentity, sidsInUpdate);
345
393
  }
346
394
 
347
395
  // Detect unpublished tracks
348
- let unpublishedSids = Array.from(this.descriptors.keys()).filter(
349
- (sid) => !sidsInUpdate.has(sid),
350
- );
351
- for (const sid of unpublishedSids) {
352
- this.handleTrackUnpublished(sid);
396
+ for (const [publisherIdentity, sidsInUpdate] of publisherParticipantToSidsInUpdate.entries()) {
397
+ const descriptorsForPublisher = Array.from(this.descriptors.entries())
398
+ .filter(([_sid, descriptor]) => descriptor.publisherIdentity === publisherIdentity)
399
+ .map(([sid]) => sid);
400
+ let unpublishedSids = descriptorsForPublisher.filter((sid) => !sidsInUpdate.has(sid));
401
+ for (const sid of unpublishedSids) {
402
+ this.handleTrackUnpublished(sid);
403
+ }
353
404
  }
354
405
  }
355
406
 
@@ -373,7 +424,7 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
373
424
  this.descriptors.set(descriptor.info.sid, descriptor);
374
425
 
375
426
  const track = new RemoteDataTrack(descriptor.info, this, { publisherIdentity });
376
- this.emit('trackAvailable', { track });
427
+ this.emit('trackPublished', { track });
377
428
  }
378
429
 
379
430
  handleTrackUnpublished(sid: DataTrackSid) {
@@ -385,10 +436,13 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
385
436
  this.descriptors.delete(sid);
386
437
 
387
438
  if (descriptor.subscription.type === 'active') {
439
+ descriptor.subscription.streamControllers.forEach((controller) => {
440
+ controller.close();
441
+ });
388
442
  this.subscriptionHandles.delete(descriptor.subscription.subcriptionHandle);
389
443
  }
390
444
 
391
- this.emit('trackUnavailable', { sid, publisherIdentity: descriptor.publisherIdentity });
445
+ this.emit('trackUnpublished', { sid, publisherIdentity: descriptor.publisherIdentity });
392
446
  }
393
447
 
394
448
  /** SFU notification that handles have been assigned for requested subscriptions. */
@@ -424,7 +478,7 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
424
478
  const pipeline = new IncomingDataTrackPipeline({
425
479
  info: descriptor.info,
426
480
  publisherIdentity: descriptor.publisherIdentity,
427
- decryptionProvider: this.decryptionProvider,
481
+ e2eeManager: this.e2eeManager,
428
482
  });
429
483
 
430
484
  const previousDescriptorSubscription = descriptor.subscription;
@@ -442,7 +496,7 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
442
496
  }
443
497
 
444
498
  /** Packet has been received over the transport. */
445
- packetReceived(bytes: Uint8Array): Throws<void, DataTrackDepacketizerDropError> {
499
+ async packetReceived(bytes: Uint8Array): Promise<Throws<void, DataTrackDepacketizerDropError>> {
446
500
  let packet: DataTrackPacket;
447
501
  try {
448
502
  [packet] = DataTrackPacket.fromBinary(bytes);
@@ -468,8 +522,8 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
468
522
  return;
469
523
  }
470
524
 
471
- const frame = descriptor.subscription.pipeline.processPacket(packet);
472
- if (!frame) {
525
+ const internalFrame = await descriptor.subscription.pipeline.processPacket(packet);
526
+ if (!internalFrame) {
473
527
  // Not all packets have been received yet to form a complete frame
474
528
  return;
475
529
  }
@@ -482,6 +536,7 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
482
536
  );
483
537
  continue;
484
538
  }
539
+ const frame = DataTrackFrameInternal.lossyIntoFrame(internalFrame);
485
540
  controller.enqueue(frame);
486
541
  }
487
542
  }
@@ -523,7 +578,7 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
523
578
  /** Shutdown the manager, ending any subscriptions. */
524
579
  shutdown() {
525
580
  for (const descriptor of this.descriptors.values()) {
526
- this.emit('trackUnavailable', {
581
+ this.emit('trackUnpublished', {
527
582
  sid: descriptor.info.sid,
528
583
  publisherIdentity: descriptor.publisherIdentity,
529
584
  });
@@ -531,6 +586,10 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
531
586
  if (descriptor.subscription.type === 'pending') {
532
587
  descriptor.subscription.completionFuture.reject?.(DataTrackSubscribeError.disconnected());
533
588
  }
589
+
590
+ if (descriptor.subscription.type === 'active') {
591
+ descriptor.subscription.streamControllers.forEach((controller) => controller.close());
592
+ }
534
593
  }
535
594
  this.descriptors.clear();
536
595
  }
@@ -1,8 +1,8 @@
1
1
  import type { Throws } from '@livekit/throws-transformer/throws';
2
+ import type { BaseE2EEManager } from '../../../e2ee/E2eeManager';
2
3
  import { LoggerNames, getLogger } from '../../../logger';
3
4
  import DataTrackDepacketizer, { DataTrackDepacketizerDropError } from '../depacketizer';
4
- import type { DecryptionProvider, EncryptedPayload } from '../e2ee';
5
- import type { DataTrackFrame } from '../frame';
5
+ import type { DataTrackFrameInternal } from '../frame';
6
6
  import { DataTrackPacket } from '../packet';
7
7
  import { type DataTrackInfo } from '../types';
8
8
 
@@ -14,7 +14,7 @@ const log = getLogger(LoggerNames.DataTracks);
14
14
  type Options = {
15
15
  info: DataTrackInfo;
16
16
  publisherIdentity: string;
17
- decryptionProvider: DecryptionProvider | null;
17
+ e2eeManager: BaseE2EEManager | null;
18
18
  };
19
19
 
20
20
  /**
@@ -23,7 +23,7 @@ type Options = {
23
23
  export default class IncomingDataTrackPipeline {
24
24
  private publisherIdentity: string;
25
25
 
26
- private e2eeProvider: DecryptionProvider | null;
26
+ private e2eeManager: BaseE2EEManager | null;
27
27
 
28
28
  private depacketizer: DataTrackDepacketizer;
29
29
 
@@ -31,7 +31,7 @@ export default class IncomingDataTrackPipeline {
31
31
  * Creates a new pipeline with the given options.
32
32
  */
33
33
  constructor(options: Options) {
34
- const hasProvider = options.decryptionProvider !== null;
34
+ const hasProvider = options.e2eeManager !== null;
35
35
  if (options.info.usesE2ee !== hasProvider) {
36
36
  // @throws-transformer ignore - this should be treated as a "panic" and not be caught
37
37
  throw new Error(
@@ -42,19 +42,23 @@ export default class IncomingDataTrackPipeline {
42
42
  const depacketizer = new DataTrackDepacketizer();
43
43
 
44
44
  this.publisherIdentity = options.publisherIdentity;
45
- this.e2eeProvider = options.decryptionProvider ?? null;
45
+ this.e2eeManager = options.e2eeManager ?? null;
46
46
  this.depacketizer = depacketizer;
47
47
  }
48
48
 
49
- processPacket(
49
+ updateE2eeManager(e2eeManager: BaseE2EEManager | null) {
50
+ this.e2eeManager = e2eeManager;
51
+ }
52
+
53
+ async processPacket(
50
54
  packet: DataTrackPacket,
51
- ): Throws<DataTrackFrame | null, DataTrackDepacketizerDropError> {
55
+ ): Promise<Throws<DataTrackFrameInternal | null, DataTrackDepacketizerDropError>> {
52
56
  const frame = this.depacketize(packet);
53
57
  if (!frame) {
54
58
  return null;
55
59
  }
56
60
 
57
- const decrypted = this.decryptIfNeeded(frame);
61
+ const decrypted = await this.decryptIfNeeded(frame);
58
62
  if (!decrypted) {
59
63
  return null;
60
64
  }
@@ -67,14 +71,14 @@ export default class IncomingDataTrackPipeline {
67
71
  */
68
72
  private depacketize(
69
73
  packet: DataTrackPacket,
70
- ): Throws<DataTrackFrame | null, DataTrackDepacketizerDropError> {
71
- let frame: DataTrackFrame | null;
74
+ ): Throws<DataTrackFrameInternal | null, DataTrackDepacketizerDropError> {
75
+ let frame: DataTrackFrameInternal | null;
72
76
  try {
73
77
  frame = this.depacketizer.push(packet);
74
78
  } catch (err) {
75
79
  // In a future version, use this to maintain drop statistics.
76
80
  // FIXME: is this a good idea?
77
- log.debug(`Data frame depacketize error: ${err}`);
81
+ log.warn(`Data frame depacketize error: ${err}`);
78
82
  return null;
79
83
  }
80
84
  return frame;
@@ -83,10 +87,12 @@ export default class IncomingDataTrackPipeline {
83
87
  /**
84
88
  * Decrypt the frame's payload if E2EE is enabled for this track.
85
89
  */
86
- private decryptIfNeeded(frame: DataTrackFrame): DataTrackFrame | null {
87
- const decryption = this.e2eeProvider;
90
+ private async decryptIfNeeded(
91
+ frame: DataTrackFrameInternal,
92
+ ): Promise<DataTrackFrameInternal | null> {
93
+ const e2eeManager = this.e2eeManager;
88
94
 
89
- if (!decryption) {
95
+ if (!e2eeManager) {
90
96
  return frame;
91
97
  }
92
98
 
@@ -96,21 +102,20 @@ export default class IncomingDataTrackPipeline {
96
102
  return null;
97
103
  }
98
104
 
99
- const encrypted: EncryptedPayload = {
100
- payload: frame.payload,
101
- iv: e2ee.iv,
102
- keyIndex: e2ee.keyIndex,
103
- };
104
-
105
- let result: Uint8Array;
105
+ let result;
106
106
  try {
107
- result = decryption.decrypt(encrypted, this.publisherIdentity);
107
+ result = await e2eeManager.handleEncryptedData(
108
+ frame.payload,
109
+ e2ee.iv,
110
+ this.publisherIdentity,
111
+ e2ee.keyIndex,
112
+ );
108
113
  } catch (err) {
109
114
  log.error(`Error decrypting packet: ${err}`);
110
115
  return null;
111
116
  }
112
117
 
113
- frame.payload = result;
118
+ frame.payload = result.payload;
114
119
  return frame;
115
120
  }
116
121
  }