livekit-client 2.15.4 → 2.15.6
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 +373 -164
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +982 -643
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +0 -47
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts +27 -0
- package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -0
- package/dist/src/e2ee/worker/sifPayload.d.ts +22 -0
- package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +6 -10
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +20 -0
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -0
- package/dist/{ts4.2/src/room → src/room/data-stream/incoming}/StreamReader.d.ts +82 -56
- package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -0
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +27 -0
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -0
- package/dist/src/room/{StreamWriter.d.ts → data-stream/outgoing/StreamWriter.d.ts} +1 -1
- package/dist/src/room/data-stream/outgoing/StreamWriter.d.ts.map +1 -0
- package/dist/src/room/errors.d.ts +13 -0
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +32 -19
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +7 -2
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteVideoTrack.d.ts +1 -0
- package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +4 -1
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +17 -1
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +8 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +0 -47
- package/dist/ts4.2/src/e2ee/worker/naluUtils.d.ts +27 -0
- package/dist/ts4.2/src/e2ee/worker/sifPayload.d.ts +22 -0
- package/dist/ts4.2/src/index.d.ts +2 -2
- package/dist/ts4.2/src/room/Room.d.ts +6 -10
- package/dist/ts4.2/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +20 -0
- package/dist/{src/room → ts4.2/src/room/data-stream/incoming}/StreamReader.d.ts +82 -56
- package/dist/ts4.2/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +27 -0
- package/dist/ts4.2/src/room/{StreamWriter.d.ts → data-stream/outgoing/StreamWriter.d.ts} +1 -1
- package/dist/ts4.2/src/room/errors.d.ts +13 -0
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +32 -19
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +7 -2
- package/dist/ts4.2/src/room/track/RemoteVideoTrack.d.ts +1 -0
- package/dist/ts4.2/src/room/track/Track.d.ts +4 -1
- package/dist/ts4.2/src/room/types.d.ts +17 -1
- package/dist/ts4.2/src/room/utils.d.ts +8 -0
- package/package.json +7 -7
- package/src/e2ee/E2eeManager.ts +18 -1
- package/src/e2ee/worker/FrameCryptor.ts +56 -157
- package/src/e2ee/worker/e2ee.worker.ts +6 -1
- package/src/e2ee/worker/naluUtils.ts +328 -0
- package/src/e2ee/worker/sifPayload.ts +75 -0
- package/src/index.ts +2 -2
- package/src/room/Room.ts +104 -208
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +247 -0
- package/src/room/data-stream/incoming/StreamReader.ts +317 -0
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +316 -0
- package/src/room/{StreamWriter.ts → data-stream/outgoing/StreamWriter.ts} +1 -1
- package/src/room/errors.ts +34 -0
- package/src/room/participant/LocalParticipant.ts +39 -295
- package/src/room/track/LocalAudioTrack.ts +2 -2
- package/src/room/track/LocalTrack.ts +70 -50
- package/src/room/track/RemoteVideoTrack.ts +12 -2
- package/src/room/track/Track.ts +10 -1
- package/src/room/types.ts +22 -1
- package/src/room/utils.ts +14 -5
- package/dist/src/e2ee/worker/SifGuard.d.ts +0 -11
- package/dist/src/e2ee/worker/SifGuard.d.ts.map +0 -1
- package/dist/src/room/StreamReader.d.ts.map +0 -1
- package/dist/src/room/StreamWriter.d.ts.map +0 -1
- package/dist/ts4.2/src/e2ee/worker/SifGuard.d.ts +0 -11
- package/src/e2ee/worker/SifGuard.ts +0 -47
- package/src/room/StreamReader.ts +0 -170
package/src/room/Room.ts
CHANGED
@@ -4,9 +4,6 @@ import {
|
|
4
4
|
ConnectionQualityUpdate,
|
5
5
|
type DataPacket,
|
6
6
|
DataPacket_Kind,
|
7
|
-
DataStream_Chunk,
|
8
|
-
DataStream_Header,
|
9
|
-
DataStream_Trailer,
|
10
7
|
DisconnectReason,
|
11
8
|
JoinResponse,
|
12
9
|
LeaveRequest,
|
@@ -48,12 +45,12 @@ import { getBrowser } from '../utils/browserParser';
|
|
48
45
|
import DeviceManager from './DeviceManager';
|
49
46
|
import RTCEngine from './RTCEngine';
|
50
47
|
import { RegionUrlProvider } from './RegionUrlProvider';
|
48
|
+
import IncomingDataStreamManager from './data-stream/incoming/IncomingDataStreamManager';
|
51
49
|
import {
|
52
50
|
type ByteStreamHandler,
|
53
|
-
ByteStreamReader,
|
54
51
|
type TextStreamHandler,
|
55
|
-
|
56
|
-
|
52
|
+
} from './data-stream/incoming/StreamReader';
|
53
|
+
import OutgoingDataStreamManager from './data-stream/outgoing/OutgoingDataStreamManager';
|
57
54
|
import {
|
58
55
|
audioDefaults,
|
59
56
|
publishDefaults,
|
@@ -81,17 +78,13 @@ import type { TrackProcessor } from './track/processor/types';
|
|
81
78
|
import type { AdaptiveStreamSettings } from './track/types';
|
82
79
|
import { getNewAudioContext, kindToSource, sourceToKind } from './track/utils';
|
83
80
|
import {
|
84
|
-
type ByteStreamInfo,
|
85
81
|
type ChatMessage,
|
86
82
|
type SimulationOptions,
|
87
83
|
type SimulationScenario,
|
88
|
-
type StreamController,
|
89
|
-
type TextStreamInfo,
|
90
84
|
type TranscriptionSegment,
|
91
85
|
} from './types';
|
92
86
|
import {
|
93
87
|
Future,
|
94
|
-
bigIntToNumber,
|
95
88
|
createDummyVideoStreamTrack,
|
96
89
|
extractChatMessage,
|
97
90
|
extractTranscriptionSegments,
|
@@ -199,13 +192,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
199
192
|
*/
|
200
193
|
private transcriptionReceivedTimes: Map<string, number>;
|
201
194
|
|
202
|
-
private
|
195
|
+
private incomingDataStreamManager: IncomingDataStreamManager;
|
203
196
|
|
204
|
-
private
|
205
|
-
|
206
|
-
private byteStreamHandlers = new Map<string, ByteStreamHandler>();
|
207
|
-
|
208
|
-
private textStreamHandlers = new Map<string, TextStreamHandler>();
|
197
|
+
private outgoingDataStreamManager: OutgoingDataStreamManager;
|
209
198
|
|
210
199
|
private rpcHandlers: Map<string, (data: RpcInvocationData) => Promise<string>> = new Map();
|
211
200
|
|
@@ -238,6 +227,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
238
227
|
|
239
228
|
this.maybeCreateEngine();
|
240
229
|
|
230
|
+
this.incomingDataStreamManager = new IncomingDataStreamManager();
|
231
|
+
this.outgoingDataStreamManager = new OutgoingDataStreamManager(this.engine, this.log);
|
232
|
+
|
241
233
|
this.disconnectLock = new Mutex();
|
242
234
|
|
243
235
|
this.localParticipant = new LocalParticipant(
|
@@ -246,6 +238,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
246
238
|
this.engine,
|
247
239
|
this.options,
|
248
240
|
this.rpcHandlers,
|
241
|
+
this.outgoingDataStreamManager,
|
249
242
|
);
|
250
243
|
|
251
244
|
if (this.options.videoCaptureDefaults.deviceId) {
|
@@ -288,25 +281,19 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
288
281
|
}
|
289
282
|
|
290
283
|
registerTextStreamHandler(topic: string, callback: TextStreamHandler) {
|
291
|
-
|
292
|
-
throw new TypeError(`A text stream handler for topic "${topic}" has already been set.`);
|
293
|
-
}
|
294
|
-
this.textStreamHandlers.set(topic, callback);
|
284
|
+
return this.incomingDataStreamManager.registerTextStreamHandler(topic, callback);
|
295
285
|
}
|
296
286
|
|
297
287
|
unregisterTextStreamHandler(topic: string) {
|
298
|
-
this.
|
288
|
+
return this.incomingDataStreamManager.unregisterTextStreamHandler(topic);
|
299
289
|
}
|
300
290
|
|
301
291
|
registerByteStreamHandler(topic: string, callback: ByteStreamHandler) {
|
302
|
-
|
303
|
-
throw new TypeError(`A byte stream handler for topic "${topic}" has already been set.`);
|
304
|
-
}
|
305
|
-
this.byteStreamHandlers.set(topic, callback);
|
292
|
+
return this.incomingDataStreamManager.registerByteStreamHandler(topic, callback);
|
306
293
|
}
|
307
294
|
|
308
295
|
unregisterByteStreamHandler(topic: string) {
|
309
|
-
this.
|
296
|
+
return this.incomingDataStreamManager.unregisterByteStreamHandler(topic);
|
310
297
|
}
|
311
298
|
|
312
299
|
/**
|
@@ -353,68 +340,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
353
340
|
this.rpcHandlers.delete(method);
|
354
341
|
}
|
355
342
|
|
356
|
-
private async handleIncomingRpcRequest(
|
357
|
-
callerIdentity: string,
|
358
|
-
requestId: string,
|
359
|
-
method: string,
|
360
|
-
payload: string,
|
361
|
-
responseTimeout: number,
|
362
|
-
version: number,
|
363
|
-
) {
|
364
|
-
await this.engine.publishRpcAck(callerIdentity, requestId);
|
365
|
-
|
366
|
-
if (version !== 1) {
|
367
|
-
await this.engine.publishRpcResponse(
|
368
|
-
callerIdentity,
|
369
|
-
requestId,
|
370
|
-
null,
|
371
|
-
RpcError.builtIn('UNSUPPORTED_VERSION'),
|
372
|
-
);
|
373
|
-
return;
|
374
|
-
}
|
375
|
-
|
376
|
-
const handler = this.rpcHandlers.get(method);
|
377
|
-
|
378
|
-
if (!handler) {
|
379
|
-
await this.engine.publishRpcResponse(
|
380
|
-
callerIdentity,
|
381
|
-
requestId,
|
382
|
-
null,
|
383
|
-
RpcError.builtIn('UNSUPPORTED_METHOD'),
|
384
|
-
);
|
385
|
-
return;
|
386
|
-
}
|
387
|
-
|
388
|
-
let responseError: RpcError | null = null;
|
389
|
-
let responsePayload: string | null = null;
|
390
|
-
|
391
|
-
try {
|
392
|
-
const response = await handler({
|
393
|
-
requestId,
|
394
|
-
callerIdentity,
|
395
|
-
payload,
|
396
|
-
responseTimeout,
|
397
|
-
});
|
398
|
-
if (byteLength(response) > MAX_PAYLOAD_BYTES) {
|
399
|
-
responseError = RpcError.builtIn('RESPONSE_PAYLOAD_TOO_LARGE');
|
400
|
-
console.warn(`RPC Response payload too large for ${method}`);
|
401
|
-
} else {
|
402
|
-
responsePayload = response;
|
403
|
-
}
|
404
|
-
} catch (error) {
|
405
|
-
if (error instanceof RpcError) {
|
406
|
-
responseError = error;
|
407
|
-
} else {
|
408
|
-
console.warn(
|
409
|
-
`Uncaught error returned by RPC handler for ${method}. Returning APPLICATION_ERROR instead.`,
|
410
|
-
error,
|
411
|
-
);
|
412
|
-
responseError = RpcError.builtIn('APPLICATION_ERROR');
|
413
|
-
}
|
414
|
-
}
|
415
|
-
await this.engine.publishRpcResponse(callerIdentity, requestId, responsePayload, responseError);
|
416
|
-
}
|
417
|
-
|
418
343
|
/**
|
419
344
|
* @experimental
|
420
345
|
*/
|
@@ -616,6 +541,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
616
541
|
if (this.e2eeManager) {
|
617
542
|
this.e2eeManager.setupEngine(this.engine);
|
618
543
|
}
|
544
|
+
if (this.outgoingDataStreamManager) {
|
545
|
+
this.outgoingDataStreamManager.setupEngine(this.engine);
|
546
|
+
}
|
619
547
|
}
|
620
548
|
|
621
549
|
/**
|
@@ -1481,13 +1409,22 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1481
1409
|
}
|
1482
1410
|
}
|
1483
1411
|
|
1484
|
-
participant.addSubscribedMediaTrack(
|
1412
|
+
const publication = participant.addSubscribedMediaTrack(
|
1485
1413
|
mediaTrack,
|
1486
1414
|
trackId,
|
1487
1415
|
stream,
|
1488
1416
|
receiver,
|
1489
1417
|
adaptiveStreamSettings,
|
1490
1418
|
);
|
1419
|
+
|
1420
|
+
if (publication?.isEncrypted && !this.e2eeManager) {
|
1421
|
+
this.emit(
|
1422
|
+
RoomEvent.EncryptionError,
|
1423
|
+
new Error(
|
1424
|
+
`Encrypted ${publication.source} track received from participant ${participant.sid}, but room does not have encryption enabled!`,
|
1425
|
+
),
|
1426
|
+
);
|
1427
|
+
}
|
1491
1428
|
}
|
1492
1429
|
|
1493
1430
|
private handleRestarting = () => {
|
@@ -1545,6 +1482,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1545
1482
|
this.isResuming = false;
|
1546
1483
|
this.bufferedEvents = [];
|
1547
1484
|
this.transcriptionReceivedTimes.clear();
|
1485
|
+
this.incomingDataStreamManager.clearHandlersAndControllers();
|
1548
1486
|
if (this.state === ConnectionState.Disconnected) {
|
1549
1487
|
return;
|
1550
1488
|
}
|
@@ -1643,6 +1581,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1643
1581
|
return;
|
1644
1582
|
}
|
1645
1583
|
|
1584
|
+
this.incomingDataStreamManager.validateParticipantHasNoActiveDataStreams(identity);
|
1585
|
+
|
1646
1586
|
participant.trackPublications.forEach((publication) => {
|
1647
1587
|
participant.unpublishTrack(publication.trackSid, true);
|
1648
1588
|
});
|
@@ -1730,8 +1670,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1730
1670
|
return;
|
1731
1671
|
}
|
1732
1672
|
const newStreamState = Track.streamStateFromProto(streamState.state);
|
1673
|
+
pub.track.setStreamState(newStreamState);
|
1733
1674
|
if (newStreamState !== pub.track.streamState) {
|
1734
|
-
pub.track.streamState = newStreamState;
|
1735
1675
|
participant.emit(ParticipantEvent.TrackStreamStateChanged, pub, pub.track.streamState);
|
1736
1676
|
this.emitWhenConnected(
|
1737
1677
|
RoomEvent.TrackStreamStateChanged,
|
@@ -1784,12 +1724,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1784
1724
|
this.handleChatMessage(participant, packet.value.value);
|
1785
1725
|
} else if (packet.value.case === 'metrics') {
|
1786
1726
|
this.handleMetrics(packet.value.value, participant);
|
1787
|
-
} else if (
|
1788
|
-
|
1789
|
-
|
1790
|
-
|
1791
|
-
|
1792
|
-
this.
|
1727
|
+
} else if (
|
1728
|
+
packet.value.case === 'streamHeader' ||
|
1729
|
+
packet.value.case === 'streamChunk' ||
|
1730
|
+
packet.value.case === 'streamTrailer'
|
1731
|
+
) {
|
1732
|
+
this.handleDataStream(packet);
|
1793
1733
|
} else if (packet.value.case === 'rpcRequest') {
|
1794
1734
|
const rpc = packet.value.value;
|
1795
1735
|
this.handleIncomingRpcRequest(
|
@@ -1803,116 +1743,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1803
1743
|
}
|
1804
1744
|
};
|
1805
1745
|
|
1806
|
-
private async handleStreamHeader(streamHeader: DataStream_Header, participantIdentity: string) {
|
1807
|
-
if (streamHeader.contentHeader.case === 'byteHeader') {
|
1808
|
-
const streamHandlerCallback = this.byteStreamHandlers.get(streamHeader.topic);
|
1809
|
-
|
1810
|
-
if (!streamHandlerCallback) {
|
1811
|
-
this.log.debug(
|
1812
|
-
'ignoring incoming byte stream due to no handler for topic',
|
1813
|
-
streamHeader.topic,
|
1814
|
-
);
|
1815
|
-
return;
|
1816
|
-
}
|
1817
|
-
let streamController: ReadableStreamDefaultController<DataStream_Chunk>;
|
1818
|
-
const info: ByteStreamInfo = {
|
1819
|
-
id: streamHeader.streamId,
|
1820
|
-
name: streamHeader.contentHeader.value.name ?? 'unknown',
|
1821
|
-
mimeType: streamHeader.mimeType,
|
1822
|
-
size: streamHeader.totalLength ? Number(streamHeader.totalLength) : undefined,
|
1823
|
-
topic: streamHeader.topic,
|
1824
|
-
timestamp: bigIntToNumber(streamHeader.timestamp),
|
1825
|
-
attributes: streamHeader.attributes,
|
1826
|
-
};
|
1827
|
-
const stream = new ReadableStream({
|
1828
|
-
start: (controller) => {
|
1829
|
-
streamController = controller;
|
1830
|
-
this.byteStreamControllers.set(streamHeader.streamId, {
|
1831
|
-
info,
|
1832
|
-
controller: streamController,
|
1833
|
-
startTime: Date.now(),
|
1834
|
-
});
|
1835
|
-
},
|
1836
|
-
});
|
1837
|
-
streamHandlerCallback(
|
1838
|
-
new ByteStreamReader(info, stream, bigIntToNumber(streamHeader.totalLength)),
|
1839
|
-
{
|
1840
|
-
identity: participantIdentity,
|
1841
|
-
},
|
1842
|
-
);
|
1843
|
-
} else if (streamHeader.contentHeader.case === 'textHeader') {
|
1844
|
-
const streamHandlerCallback = this.textStreamHandlers.get(streamHeader.topic);
|
1845
|
-
|
1846
|
-
if (!streamHandlerCallback) {
|
1847
|
-
this.log.debug(
|
1848
|
-
'ignoring incoming text stream due to no handler for topic',
|
1849
|
-
streamHeader.topic,
|
1850
|
-
);
|
1851
|
-
return;
|
1852
|
-
}
|
1853
|
-
let streamController: ReadableStreamDefaultController<DataStream_Chunk>;
|
1854
|
-
const info: TextStreamInfo = {
|
1855
|
-
id: streamHeader.streamId,
|
1856
|
-
mimeType: streamHeader.mimeType,
|
1857
|
-
size: streamHeader.totalLength ? Number(streamHeader.totalLength) : undefined,
|
1858
|
-
topic: streamHeader.topic,
|
1859
|
-
timestamp: Number(streamHeader.timestamp),
|
1860
|
-
attributes: streamHeader.attributes,
|
1861
|
-
};
|
1862
|
-
|
1863
|
-
const stream = new ReadableStream<DataStream_Chunk>({
|
1864
|
-
start: (controller) => {
|
1865
|
-
streamController = controller;
|
1866
|
-
this.textStreamControllers.set(streamHeader.streamId, {
|
1867
|
-
info,
|
1868
|
-
controller: streamController,
|
1869
|
-
startTime: Date.now(),
|
1870
|
-
});
|
1871
|
-
},
|
1872
|
-
});
|
1873
|
-
streamHandlerCallback(
|
1874
|
-
new TextStreamReader(info, stream, bigIntToNumber(streamHeader.totalLength)),
|
1875
|
-
{ identity: participantIdentity },
|
1876
|
-
);
|
1877
|
-
}
|
1878
|
-
}
|
1879
|
-
|
1880
|
-
private handleStreamChunk(chunk: DataStream_Chunk) {
|
1881
|
-
const fileBuffer = this.byteStreamControllers.get(chunk.streamId);
|
1882
|
-
if (fileBuffer) {
|
1883
|
-
if (chunk.content.length > 0) {
|
1884
|
-
fileBuffer.controller.enqueue(chunk);
|
1885
|
-
}
|
1886
|
-
}
|
1887
|
-
const textBuffer = this.textStreamControllers.get(chunk.streamId);
|
1888
|
-
if (textBuffer) {
|
1889
|
-
if (chunk.content.length > 0) {
|
1890
|
-
textBuffer.controller.enqueue(chunk);
|
1891
|
-
}
|
1892
|
-
}
|
1893
|
-
}
|
1894
|
-
|
1895
|
-
private handleStreamTrailer(trailer: DataStream_Trailer) {
|
1896
|
-
const textBuffer = this.textStreamControllers.get(trailer.streamId);
|
1897
|
-
if (textBuffer) {
|
1898
|
-
textBuffer.info.attributes = {
|
1899
|
-
...textBuffer.info.attributes,
|
1900
|
-
...trailer.attributes,
|
1901
|
-
};
|
1902
|
-
textBuffer.controller.close();
|
1903
|
-
this.textStreamControllers.delete(trailer.streamId);
|
1904
|
-
}
|
1905
|
-
|
1906
|
-
const fileBuffer = this.byteStreamControllers.get(trailer.streamId);
|
1907
|
-
if (fileBuffer) {
|
1908
|
-
{
|
1909
|
-
fileBuffer.info.attributes = { ...fileBuffer.info.attributes, ...trailer.attributes };
|
1910
|
-
fileBuffer.controller.close();
|
1911
|
-
this.byteStreamControllers.delete(trailer.streamId);
|
1912
|
-
}
|
1913
|
-
}
|
1914
|
-
}
|
1915
|
-
|
1916
1746
|
private handleUserPacket = (
|
1917
1747
|
participant: RemoteParticipant | undefined,
|
1918
1748
|
userPacket: UserPacket,
|
@@ -1931,8 +1761,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1931
1761
|
participant?.emit(ParticipantEvent.SipDTMFReceived, dtmf);
|
1932
1762
|
};
|
1933
1763
|
|
1934
|
-
bufferedSegments: Map<string, TranscriptionSegmentModel> = new Map();
|
1935
|
-
|
1936
1764
|
private handleTranscription = (
|
1937
1765
|
_remoteParticipant: RemoteParticipant | undefined,
|
1938
1766
|
transcription: TranscriptionModel,
|
@@ -1963,6 +1791,74 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1963
1791
|
this.emit(RoomEvent.MetricsReceived, metrics, participant);
|
1964
1792
|
};
|
1965
1793
|
|
1794
|
+
private handleDataStream = (packet: DataPacket) => {
|
1795
|
+
this.incomingDataStreamManager.handleDataStreamPacket(packet);
|
1796
|
+
};
|
1797
|
+
|
1798
|
+
private async handleIncomingRpcRequest(
|
1799
|
+
callerIdentity: string,
|
1800
|
+
requestId: string,
|
1801
|
+
method: string,
|
1802
|
+
payload: string,
|
1803
|
+
responseTimeout: number,
|
1804
|
+
version: number,
|
1805
|
+
) {
|
1806
|
+
await this.engine.publishRpcAck(callerIdentity, requestId);
|
1807
|
+
|
1808
|
+
if (version !== 1) {
|
1809
|
+
await this.engine.publishRpcResponse(
|
1810
|
+
callerIdentity,
|
1811
|
+
requestId,
|
1812
|
+
null,
|
1813
|
+
RpcError.builtIn('UNSUPPORTED_VERSION'),
|
1814
|
+
);
|
1815
|
+
return;
|
1816
|
+
}
|
1817
|
+
|
1818
|
+
const handler = this.rpcHandlers.get(method);
|
1819
|
+
|
1820
|
+
if (!handler) {
|
1821
|
+
await this.engine.publishRpcResponse(
|
1822
|
+
callerIdentity,
|
1823
|
+
requestId,
|
1824
|
+
null,
|
1825
|
+
RpcError.builtIn('UNSUPPORTED_METHOD'),
|
1826
|
+
);
|
1827
|
+
return;
|
1828
|
+
}
|
1829
|
+
|
1830
|
+
let responseError: RpcError | null = null;
|
1831
|
+
let responsePayload: string | null = null;
|
1832
|
+
|
1833
|
+
try {
|
1834
|
+
const response = await handler({
|
1835
|
+
requestId,
|
1836
|
+
callerIdentity,
|
1837
|
+
payload,
|
1838
|
+
responseTimeout,
|
1839
|
+
});
|
1840
|
+
if (byteLength(response) > MAX_PAYLOAD_BYTES) {
|
1841
|
+
responseError = RpcError.builtIn('RESPONSE_PAYLOAD_TOO_LARGE');
|
1842
|
+
console.warn(`RPC Response payload too large for ${method}`);
|
1843
|
+
} else {
|
1844
|
+
responsePayload = response;
|
1845
|
+
}
|
1846
|
+
} catch (error) {
|
1847
|
+
if (error instanceof RpcError) {
|
1848
|
+
responseError = error;
|
1849
|
+
} else {
|
1850
|
+
console.warn(
|
1851
|
+
`Uncaught error returned by RPC handler for ${method}. Returning APPLICATION_ERROR instead.`,
|
1852
|
+
error,
|
1853
|
+
);
|
1854
|
+
responseError = RpcError.builtIn('APPLICATION_ERROR');
|
1855
|
+
}
|
1856
|
+
}
|
1857
|
+
await this.engine.publishRpcResponse(callerIdentity, requestId, responsePayload, responseError);
|
1858
|
+
}
|
1859
|
+
|
1860
|
+
bufferedSegments: Map<string, TranscriptionSegmentModel> = new Map();
|
1861
|
+
|
1966
1862
|
private handleAudioPlaybackStarted = () => {
|
1967
1863
|
if (this.canPlaybackAudio) {
|
1968
1864
|
return;
|
@@ -0,0 +1,247 @@
|
|
1
|
+
import {
|
2
|
+
type DataPacket,
|
3
|
+
DataStream_Chunk,
|
4
|
+
DataStream_Header,
|
5
|
+
DataStream_Trailer,
|
6
|
+
} from '@livekit/protocol';
|
7
|
+
import log from '../../../logger';
|
8
|
+
import { DataStreamError, DataStreamErrorReason } from '../../errors';
|
9
|
+
import { type ByteStreamInfo, type StreamController, type TextStreamInfo } from '../../types';
|
10
|
+
import { Future, bigIntToNumber } from '../../utils';
|
11
|
+
import {
|
12
|
+
type ByteStreamHandler,
|
13
|
+
ByteStreamReader,
|
14
|
+
type TextStreamHandler,
|
15
|
+
TextStreamReader,
|
16
|
+
} from './StreamReader';
|
17
|
+
|
18
|
+
export default class IncomingDataStreamManager {
|
19
|
+
private log = log;
|
20
|
+
|
21
|
+
private byteStreamControllers = new Map<string, StreamController<DataStream_Chunk>>();
|
22
|
+
|
23
|
+
private textStreamControllers = new Map<string, StreamController<DataStream_Chunk>>();
|
24
|
+
|
25
|
+
private byteStreamHandlers = new Map<string, ByteStreamHandler>();
|
26
|
+
|
27
|
+
private textStreamHandlers = new Map<string, TextStreamHandler>();
|
28
|
+
|
29
|
+
registerTextStreamHandler(topic: string, callback: TextStreamHandler) {
|
30
|
+
if (this.textStreamHandlers.has(topic)) {
|
31
|
+
throw new DataStreamError(
|
32
|
+
`A text stream handler for topic "${topic}" has already been set.`,
|
33
|
+
DataStreamErrorReason.HandlerAlreadyRegistered,
|
34
|
+
);
|
35
|
+
}
|
36
|
+
this.textStreamHandlers.set(topic, callback);
|
37
|
+
}
|
38
|
+
|
39
|
+
unregisterTextStreamHandler(topic: string) {
|
40
|
+
this.textStreamHandlers.delete(topic);
|
41
|
+
}
|
42
|
+
|
43
|
+
registerByteStreamHandler(topic: string, callback: ByteStreamHandler) {
|
44
|
+
if (this.byteStreamHandlers.has(topic)) {
|
45
|
+
throw new DataStreamError(
|
46
|
+
`A byte stream handler for topic "${topic}" has already been set.`,
|
47
|
+
DataStreamErrorReason.HandlerAlreadyRegistered,
|
48
|
+
);
|
49
|
+
}
|
50
|
+
this.byteStreamHandlers.set(topic, callback);
|
51
|
+
}
|
52
|
+
|
53
|
+
unregisterByteStreamHandler(topic: string) {
|
54
|
+
this.byteStreamHandlers.delete(topic);
|
55
|
+
}
|
56
|
+
|
57
|
+
clearHandlersAndControllers() {
|
58
|
+
this.byteStreamControllers.clear();
|
59
|
+
this.textStreamControllers.clear();
|
60
|
+
this.byteStreamHandlers.clear();
|
61
|
+
this.textStreamHandlers.clear();
|
62
|
+
}
|
63
|
+
|
64
|
+
validateParticipantHasNoActiveDataStreams(participantIdentity: string) {
|
65
|
+
// Terminate any in flight data stream receives from the given participant
|
66
|
+
const textStreamsBeingSentByDisconnectingParticipant = Array.from(
|
67
|
+
this.textStreamControllers.entries(),
|
68
|
+
).filter((entry) => entry[1].sendingParticipantIdentity === participantIdentity);
|
69
|
+
const byteStreamsBeingSentByDisconnectingParticipant = Array.from(
|
70
|
+
this.byteStreamControllers.entries(),
|
71
|
+
).filter((entry) => entry[1].sendingParticipantIdentity === participantIdentity);
|
72
|
+
|
73
|
+
if (
|
74
|
+
textStreamsBeingSentByDisconnectingParticipant.length > 0 ||
|
75
|
+
byteStreamsBeingSentByDisconnectingParticipant.length > 0
|
76
|
+
) {
|
77
|
+
const abnormalEndError = new DataStreamError(
|
78
|
+
`Participant ${participantIdentity} unexpectedly disconnected in the middle of sending data`,
|
79
|
+
DataStreamErrorReason.AbnormalEnd,
|
80
|
+
);
|
81
|
+
for (const [id, controller] of byteStreamsBeingSentByDisconnectingParticipant) {
|
82
|
+
controller.outOfBandFailureRejectingFuture.reject?.(abnormalEndError);
|
83
|
+
this.byteStreamControllers.delete(id);
|
84
|
+
}
|
85
|
+
for (const [id, controller] of textStreamsBeingSentByDisconnectingParticipant) {
|
86
|
+
controller.outOfBandFailureRejectingFuture.reject?.(abnormalEndError);
|
87
|
+
this.textStreamControllers.delete(id);
|
88
|
+
}
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
async handleDataStreamPacket(packet: DataPacket) {
|
93
|
+
switch (packet.value.case) {
|
94
|
+
case 'streamHeader':
|
95
|
+
return this.handleStreamHeader(packet.value.value, packet.participantIdentity);
|
96
|
+
case 'streamChunk':
|
97
|
+
return this.handleStreamChunk(packet.value.value);
|
98
|
+
case 'streamTrailer':
|
99
|
+
return this.handleStreamTrailer(packet.value.value);
|
100
|
+
default:
|
101
|
+
throw new Error(`DataPacket of value "${packet.value.case}" is not data stream related!`);
|
102
|
+
}
|
103
|
+
}
|
104
|
+
|
105
|
+
private async handleStreamHeader(streamHeader: DataStream_Header, participantIdentity: string) {
|
106
|
+
if (streamHeader.contentHeader.case === 'byteHeader') {
|
107
|
+
const streamHandlerCallback = this.byteStreamHandlers.get(streamHeader.topic);
|
108
|
+
if (!streamHandlerCallback) {
|
109
|
+
this.log.debug(
|
110
|
+
'ignoring incoming byte stream due to no handler for topic',
|
111
|
+
streamHeader.topic,
|
112
|
+
);
|
113
|
+
return;
|
114
|
+
}
|
115
|
+
|
116
|
+
let streamController: ReadableStreamDefaultController<DataStream_Chunk>;
|
117
|
+
const outOfBandFailureRejectingFuture = new Future<never>();
|
118
|
+
|
119
|
+
const info: ByteStreamInfo = {
|
120
|
+
id: streamHeader.streamId,
|
121
|
+
name: streamHeader.contentHeader.value.name ?? 'unknown',
|
122
|
+
mimeType: streamHeader.mimeType,
|
123
|
+
size: streamHeader.totalLength ? Number(streamHeader.totalLength) : undefined,
|
124
|
+
topic: streamHeader.topic,
|
125
|
+
timestamp: bigIntToNumber(streamHeader.timestamp),
|
126
|
+
attributes: streamHeader.attributes,
|
127
|
+
};
|
128
|
+
const stream = new ReadableStream({
|
129
|
+
start: (controller) => {
|
130
|
+
streamController = controller;
|
131
|
+
|
132
|
+
if (this.textStreamControllers.has(streamHeader.streamId)) {
|
133
|
+
throw new DataStreamError(
|
134
|
+
`A data stream read is already in progress for a stream with id ${streamHeader.streamId}.`,
|
135
|
+
DataStreamErrorReason.AlreadyOpened,
|
136
|
+
);
|
137
|
+
}
|
138
|
+
|
139
|
+
this.byteStreamControllers.set(streamHeader.streamId, {
|
140
|
+
info,
|
141
|
+
controller: streamController,
|
142
|
+
startTime: Date.now(),
|
143
|
+
sendingParticipantIdentity: participantIdentity,
|
144
|
+
outOfBandFailureRejectingFuture,
|
145
|
+
});
|
146
|
+
},
|
147
|
+
});
|
148
|
+
streamHandlerCallback(
|
149
|
+
new ByteStreamReader(
|
150
|
+
info,
|
151
|
+
stream,
|
152
|
+
bigIntToNumber(streamHeader.totalLength),
|
153
|
+
outOfBandFailureRejectingFuture,
|
154
|
+
),
|
155
|
+
{
|
156
|
+
identity: participantIdentity,
|
157
|
+
},
|
158
|
+
);
|
159
|
+
} else if (streamHeader.contentHeader.case === 'textHeader') {
|
160
|
+
const streamHandlerCallback = this.textStreamHandlers.get(streamHeader.topic);
|
161
|
+
if (!streamHandlerCallback) {
|
162
|
+
this.log.debug(
|
163
|
+
'ignoring incoming text stream due to no handler for topic',
|
164
|
+
streamHeader.topic,
|
165
|
+
);
|
166
|
+
return;
|
167
|
+
}
|
168
|
+
|
169
|
+
let streamController: ReadableStreamDefaultController<DataStream_Chunk>;
|
170
|
+
const outOfBandFailureRejectingFuture = new Future<never>();
|
171
|
+
const info: TextStreamInfo = {
|
172
|
+
id: streamHeader.streamId,
|
173
|
+
mimeType: streamHeader.mimeType,
|
174
|
+
size: streamHeader.totalLength ? Number(streamHeader.totalLength) : undefined,
|
175
|
+
topic: streamHeader.topic,
|
176
|
+
timestamp: Number(streamHeader.timestamp),
|
177
|
+
attributes: streamHeader.attributes,
|
178
|
+
};
|
179
|
+
|
180
|
+
const stream = new ReadableStream<DataStream_Chunk>({
|
181
|
+
start: (controller) => {
|
182
|
+
streamController = controller;
|
183
|
+
|
184
|
+
if (this.textStreamControllers.has(streamHeader.streamId)) {
|
185
|
+
throw new DataStreamError(
|
186
|
+
`A data stream read is already in progress for a stream with id ${streamHeader.streamId}.`,
|
187
|
+
DataStreamErrorReason.AlreadyOpened,
|
188
|
+
);
|
189
|
+
}
|
190
|
+
|
191
|
+
this.textStreamControllers.set(streamHeader.streamId, {
|
192
|
+
info,
|
193
|
+
controller: streamController,
|
194
|
+
startTime: Date.now(),
|
195
|
+
sendingParticipantIdentity: participantIdentity,
|
196
|
+
outOfBandFailureRejectingFuture,
|
197
|
+
});
|
198
|
+
},
|
199
|
+
});
|
200
|
+
streamHandlerCallback(
|
201
|
+
new TextStreamReader(
|
202
|
+
info,
|
203
|
+
stream,
|
204
|
+
bigIntToNumber(streamHeader.totalLength),
|
205
|
+
outOfBandFailureRejectingFuture,
|
206
|
+
),
|
207
|
+
{ identity: participantIdentity },
|
208
|
+
);
|
209
|
+
}
|
210
|
+
}
|
211
|
+
|
212
|
+
private handleStreamChunk(chunk: DataStream_Chunk) {
|
213
|
+
const fileBuffer = this.byteStreamControllers.get(chunk.streamId);
|
214
|
+
if (fileBuffer) {
|
215
|
+
if (chunk.content.length > 0) {
|
216
|
+
fileBuffer.controller.enqueue(chunk);
|
217
|
+
}
|
218
|
+
}
|
219
|
+
const textBuffer = this.textStreamControllers.get(chunk.streamId);
|
220
|
+
if (textBuffer) {
|
221
|
+
if (chunk.content.length > 0) {
|
222
|
+
textBuffer.controller.enqueue(chunk);
|
223
|
+
}
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
private handleStreamTrailer(trailer: DataStream_Trailer) {
|
228
|
+
const textBuffer = this.textStreamControllers.get(trailer.streamId);
|
229
|
+
if (textBuffer) {
|
230
|
+
textBuffer.info.attributes = {
|
231
|
+
...textBuffer.info.attributes,
|
232
|
+
...trailer.attributes,
|
233
|
+
};
|
234
|
+
textBuffer.controller.close();
|
235
|
+
this.textStreamControllers.delete(trailer.streamId);
|
236
|
+
}
|
237
|
+
|
238
|
+
const fileBuffer = this.byteStreamControllers.get(trailer.streamId);
|
239
|
+
if (fileBuffer) {
|
240
|
+
{
|
241
|
+
fileBuffer.info.attributes = { ...fileBuffer.info.attributes, ...trailer.attributes };
|
242
|
+
fileBuffer.controller.close();
|
243
|
+
this.byteStreamControllers.delete(trailer.streamId);
|
244
|
+
}
|
245
|
+
}
|
246
|
+
}
|
247
|
+
}
|