livekit-client 2.15.4 → 2.15.5
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 +329 -124
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +834 -541
- 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/worker/FrameCryptor.d.ts +0 -46
- 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/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/src/room/{StreamReader.d.ts → data-stream/incoming/StreamReader.d.ts} +32 -6
- 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/types.d.ts +17 -1
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +0 -46
- package/dist/ts4.2/src/e2ee/worker/naluUtils.d.ts +27 -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/ts4.2/src/room/{StreamReader.d.ts → data-stream/incoming/StreamReader.d.ts} +32 -6
- 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/types.d.ts +17 -1
- package/package.json +1 -1
- package/src/e2ee/worker/FrameCryptor.ts +48 -139
- package/src/e2ee/worker/naluUtils.ts +328 -0
- package/src/index.ts +2 -2
- package/src/room/Room.ts +93 -206
- 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 +65 -48
- package/src/room/types.ts +22 -1
- package/src/room/utils.ts +2 -2
- package/dist/src/room/StreamReader.d.ts.map +0 -1
- package/dist/src/room/StreamWriter.d.ts.map +0 -1
- 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
|
/**
|
@@ -1545,6 +1473,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1545
1473
|
this.isResuming = false;
|
1546
1474
|
this.bufferedEvents = [];
|
1547
1475
|
this.transcriptionReceivedTimes.clear();
|
1476
|
+
this.incomingDataStreamManager.clearHandlersAndControllers();
|
1548
1477
|
if (this.state === ConnectionState.Disconnected) {
|
1549
1478
|
return;
|
1550
1479
|
}
|
@@ -1643,6 +1572,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1643
1572
|
return;
|
1644
1573
|
}
|
1645
1574
|
|
1575
|
+
this.incomingDataStreamManager.validateParticipantHasNoActiveDataStreams(identity);
|
1576
|
+
|
1646
1577
|
participant.trackPublications.forEach((publication) => {
|
1647
1578
|
participant.unpublishTrack(publication.trackSid, true);
|
1648
1579
|
});
|
@@ -1784,12 +1715,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1784
1715
|
this.handleChatMessage(participant, packet.value.value);
|
1785
1716
|
} else if (packet.value.case === 'metrics') {
|
1786
1717
|
this.handleMetrics(packet.value.value, participant);
|
1787
|
-
} else if (
|
1788
|
-
|
1789
|
-
|
1790
|
-
|
1791
|
-
|
1792
|
-
this.
|
1718
|
+
} else if (
|
1719
|
+
packet.value.case === 'streamHeader' ||
|
1720
|
+
packet.value.case === 'streamChunk' ||
|
1721
|
+
packet.value.case === 'streamTrailer'
|
1722
|
+
) {
|
1723
|
+
this.handleDataStream(packet);
|
1793
1724
|
} else if (packet.value.case === 'rpcRequest') {
|
1794
1725
|
const rpc = packet.value.value;
|
1795
1726
|
this.handleIncomingRpcRequest(
|
@@ -1803,116 +1734,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1803
1734
|
}
|
1804
1735
|
};
|
1805
1736
|
|
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
1737
|
private handleUserPacket = (
|
1917
1738
|
participant: RemoteParticipant | undefined,
|
1918
1739
|
userPacket: UserPacket,
|
@@ -1931,8 +1752,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1931
1752
|
participant?.emit(ParticipantEvent.SipDTMFReceived, dtmf);
|
1932
1753
|
};
|
1933
1754
|
|
1934
|
-
bufferedSegments: Map<string, TranscriptionSegmentModel> = new Map();
|
1935
|
-
|
1936
1755
|
private handleTranscription = (
|
1937
1756
|
_remoteParticipant: RemoteParticipant | undefined,
|
1938
1757
|
transcription: TranscriptionModel,
|
@@ -1963,6 +1782,74 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1963
1782
|
this.emit(RoomEvent.MetricsReceived, metrics, participant);
|
1964
1783
|
};
|
1965
1784
|
|
1785
|
+
private handleDataStream = (packet: DataPacket) => {
|
1786
|
+
this.incomingDataStreamManager.handleDataStreamPacket(packet);
|
1787
|
+
};
|
1788
|
+
|
1789
|
+
private async handleIncomingRpcRequest(
|
1790
|
+
callerIdentity: string,
|
1791
|
+
requestId: string,
|
1792
|
+
method: string,
|
1793
|
+
payload: string,
|
1794
|
+
responseTimeout: number,
|
1795
|
+
version: number,
|
1796
|
+
) {
|
1797
|
+
await this.engine.publishRpcAck(callerIdentity, requestId);
|
1798
|
+
|
1799
|
+
if (version !== 1) {
|
1800
|
+
await this.engine.publishRpcResponse(
|
1801
|
+
callerIdentity,
|
1802
|
+
requestId,
|
1803
|
+
null,
|
1804
|
+
RpcError.builtIn('UNSUPPORTED_VERSION'),
|
1805
|
+
);
|
1806
|
+
return;
|
1807
|
+
}
|
1808
|
+
|
1809
|
+
const handler = this.rpcHandlers.get(method);
|
1810
|
+
|
1811
|
+
if (!handler) {
|
1812
|
+
await this.engine.publishRpcResponse(
|
1813
|
+
callerIdentity,
|
1814
|
+
requestId,
|
1815
|
+
null,
|
1816
|
+
RpcError.builtIn('UNSUPPORTED_METHOD'),
|
1817
|
+
);
|
1818
|
+
return;
|
1819
|
+
}
|
1820
|
+
|
1821
|
+
let responseError: RpcError | null = null;
|
1822
|
+
let responsePayload: string | null = null;
|
1823
|
+
|
1824
|
+
try {
|
1825
|
+
const response = await handler({
|
1826
|
+
requestId,
|
1827
|
+
callerIdentity,
|
1828
|
+
payload,
|
1829
|
+
responseTimeout,
|
1830
|
+
});
|
1831
|
+
if (byteLength(response) > MAX_PAYLOAD_BYTES) {
|
1832
|
+
responseError = RpcError.builtIn('RESPONSE_PAYLOAD_TOO_LARGE');
|
1833
|
+
console.warn(`RPC Response payload too large for ${method}`);
|
1834
|
+
} else {
|
1835
|
+
responsePayload = response;
|
1836
|
+
}
|
1837
|
+
} catch (error) {
|
1838
|
+
if (error instanceof RpcError) {
|
1839
|
+
responseError = error;
|
1840
|
+
} else {
|
1841
|
+
console.warn(
|
1842
|
+
`Uncaught error returned by RPC handler for ${method}. Returning APPLICATION_ERROR instead.`,
|
1843
|
+
error,
|
1844
|
+
);
|
1845
|
+
responseError = RpcError.builtIn('APPLICATION_ERROR');
|
1846
|
+
}
|
1847
|
+
}
|
1848
|
+
await this.engine.publishRpcResponse(callerIdentity, requestId, responsePayload, responseError);
|
1849
|
+
}
|
1850
|
+
|
1851
|
+
bufferedSegments: Map<string, TranscriptionSegmentModel> = new Map();
|
1852
|
+
|
1966
1853
|
private handleAudioPlaybackStarted = () => {
|
1967
1854
|
if (this.canPlaybackAudio) {
|
1968
1855
|
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
|
+
}
|