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.
- package/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +8 -7
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +8026 -5883
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts +12 -4
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/connectionHelper/ConnectionCheck.d.ts +1 -1
- package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
- package/dist/src/e2ee/constants.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +6 -0
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/utils.d.ts +2 -1
- package/dist/src/e2ee/utils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/index.d.ts +6 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +5 -0
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts +1 -1
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +28 -11
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +14 -3
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +5 -1
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/data-track/LocalDataTrack.d.ts +28 -5
- package/dist/src/room/data-track/LocalDataTrack.d.ts.map +1 -1
- package/dist/src/room/data-track/RemoteDataTrack.d.ts +5 -5
- package/dist/src/room/data-track/RemoteDataTrack.d.ts.map +1 -1
- package/dist/src/room/data-track/depacketizer.d.ts +4 -4
- package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
- package/dist/src/room/data-track/frame.d.ts +14 -0
- package/dist/src/room/data-track/frame.d.ts.map +1 -1
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +19 -11
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/incoming/pipeline.d.ts +6 -5
- package/dist/src/room/data-track/incoming/pipeline.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +57 -23
- package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/errors.d.ts +16 -6
- package/dist/src/room/data-track/outgoing/errors.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/pipeline.d.ts +7 -6
- package/dist/src/room/data-track/outgoing/pipeline.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/types.d.ts +14 -4
- package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
- package/dist/src/room/data-track/packetizer.d.ts +4 -4
- package/dist/src/room/data-track/packetizer.d.ts.map +1 -1
- package/dist/src/room/data-track/track-interfaces.d.ts +1 -1
- package/dist/src/room/data-track/track-interfaces.d.ts.map +1 -1
- package/dist/src/room/data-track/types.d.ts +6 -1
- package/dist/src/room/data-track/types.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +24 -3
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +12 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts +13 -0
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +1 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/utils/deferrable-map.d.ts +32 -0
- package/dist/src/utils/deferrable-map.d.ts.map +1 -0
- package/dist/src/utils/serializer.d.ts +48 -0
- package/dist/src/utils/serializer.d.ts.map +1 -0
- package/dist/ts4.2/api/SignalClient.d.ts +12 -4
- package/dist/ts4.2/connectionHelper/ConnectionCheck.d.ts +2 -1
- package/dist/ts4.2/e2ee/types.d.ts +6 -0
- package/dist/ts4.2/e2ee/utils.d.ts +2 -1
- package/dist/ts4.2/index.d.ts +7 -4
- package/dist/ts4.2/room/PCTransport.d.ts +5 -0
- package/dist/ts4.2/room/PCTransportManager.d.ts +1 -1
- package/dist/ts4.2/room/RTCEngine.d.ts +28 -11
- package/dist/ts4.2/room/Room.d.ts +14 -3
- package/dist/ts4.2/room/data-stream/incoming/IncomingDataStreamManager.d.ts +5 -1
- package/dist/ts4.2/room/data-track/LocalDataTrack.d.ts +27 -4
- package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +4 -4
- package/dist/ts4.2/room/data-track/depacketizer.d.ts +4 -4
- package/dist/ts4.2/room/data-track/frame.d.ts +14 -0
- package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +21 -10
- package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +6 -5
- package/dist/ts4.2/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +57 -23
- package/dist/ts4.2/room/data-track/outgoing/errors.d.ts +16 -6
- package/dist/ts4.2/room/data-track/outgoing/pipeline.d.ts +7 -6
- package/dist/ts4.2/room/data-track/outgoing/types.d.ts +14 -4
- package/dist/ts4.2/room/data-track/packetizer.d.ts +4 -4
- package/dist/ts4.2/room/data-track/track-interfaces.d.ts +1 -1
- package/dist/ts4.2/room/data-track/types.d.ts +6 -1
- package/dist/ts4.2/room/events.d.ts +24 -3
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +12 -1
- package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +13 -0
- package/dist/ts4.2/room/utils.d.ts +1 -0
- package/dist/ts4.2/utils/deferrable-map.d.ts +32 -0
- package/dist/ts4.2/utils/serializer.d.ts +48 -0
- package/package.json +1 -1
- package/src/api/SignalClient.test.ts +9 -4
- package/src/api/SignalClient.ts +116 -9
- package/src/connectionHelper/ConnectionCheck.ts +1 -1
- package/src/e2ee/constants.ts +1 -0
- package/src/e2ee/types.ts +6 -0
- package/src/e2ee/utils.ts +4 -3
- package/src/e2ee/worker/DataCryptor.ts +1 -4
- package/src/e2ee/worker/FrameCryptor.ts +1 -4
- package/src/e2ee/worker/ParticipantKeyHandler.ts +1 -1
- package/src/index.ts +13 -4
- package/src/room/PCTransport.ts +41 -1
- package/src/room/PCTransportManager.ts +1 -1
- package/src/room/RTCEngine.ts +312 -125
- package/src/room/Room.ts +168 -35
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +26 -2
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +7 -7
- package/src/room/data-track/LocalDataTrack.ts +83 -10
- package/src/room/data-track/RemoteDataTrack.ts +7 -9
- package/src/room/data-track/depacketizer.ts +21 -12
- package/src/room/data-track/frame.ts +28 -2
- package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +58 -73
- package/src/room/data-track/incoming/IncomingDataTrackManager.ts +139 -80
- package/src/room/data-track/incoming/pipeline.ts +29 -24
- package/src/room/data-track/outgoing/OutgoingDataTrackManager.test.ts +225 -32
- package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +150 -75
- package/src/room/data-track/outgoing/errors.ts +36 -7
- package/src/room/data-track/outgoing/pipeline.ts +23 -17
- package/src/room/data-track/outgoing/types.ts +12 -3
- package/src/room/data-track/packet/extensions.ts +17 -22
- package/src/room/data-track/packet/index.test.ts +22 -33
- package/src/room/data-track/packetizer.test.ts +2 -2
- package/src/room/data-track/packetizer.ts +4 -4
- package/src/room/data-track/track-interfaces.ts +1 -1
- package/src/room/data-track/types.ts +21 -1
- package/src/room/events.ts +26 -1
- package/src/room/participant/LocalParticipant.ts +74 -8
- package/src/room/participant/RemoteParticipant.ts +25 -0
- package/src/room/utils.ts +4 -0
- package/src/utils/deferrable-map.ts +109 -0
- package/src/utils/serializer.ts +72 -0
- package/dist/src/room/data-track/e2ee.d.ts +0 -12
- package/dist/src/room/data-track/e2ee.d.ts.map +0 -1
- package/dist/ts4.2/room/data-track/e2ee.d.ts +0 -12
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
80
|
+
const READABLE_STREAM_DEFAULT_BUFFER_SIZE = 16;
|
|
81
81
|
|
|
82
82
|
export default class IncomingDataTrackManager extends (EventEmitter as new () => TypedEmitter<DataTrackIncomingManagerCallbacks>) {
|
|
83
|
-
private
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
207
|
-
return
|
|
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
|
-
|
|
214
|
-
return
|
|
306
|
+
await waitForCompletionFuture(descriptor, signal);
|
|
307
|
+
return;
|
|
215
308
|
}
|
|
216
309
|
case 'active': {
|
|
217
|
-
return
|
|
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
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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('
|
|
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('
|
|
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
|
-
|
|
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
|
|
472
|
-
if (!
|
|
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('
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
45
|
+
this.e2eeManager = options.e2eeManager ?? null;
|
|
46
46
|
this.depacketizer = depacketizer;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
updateE2eeManager(e2eeManager: BaseE2EEManager | null) {
|
|
50
|
+
this.e2eeManager = e2eeManager;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async processPacket(
|
|
50
54
|
packet: DataTrackPacket,
|
|
51
|
-
): Throws<
|
|
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<
|
|
71
|
-
let frame:
|
|
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.
|
|
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(
|
|
87
|
-
|
|
90
|
+
private async decryptIfNeeded(
|
|
91
|
+
frame: DataTrackFrameInternal,
|
|
92
|
+
): Promise<DataTrackFrameInternal | null> {
|
|
93
|
+
const e2eeManager = this.e2eeManager;
|
|
88
94
|
|
|
89
|
-
if (!
|
|
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
|
-
|
|
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 =
|
|
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
|
}
|