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.
Files changed (84) hide show
  1. package/dist/livekit-client.e2ee.worker.js +1 -1
  2. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  3. package/dist/livekit-client.e2ee.worker.mjs +373 -164
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +982 -643
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.umd.js +1 -1
  8. package/dist/livekit-client.umd.js.map +1 -1
  9. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  10. package/dist/src/e2ee/worker/FrameCryptor.d.ts +0 -47
  11. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  12. package/dist/src/e2ee/worker/naluUtils.d.ts +27 -0
  13. package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -0
  14. package/dist/src/e2ee/worker/sifPayload.d.ts +22 -0
  15. package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -0
  16. package/dist/src/index.d.ts +2 -2
  17. package/dist/src/index.d.ts.map +1 -1
  18. package/dist/src/room/Room.d.ts +6 -10
  19. package/dist/src/room/Room.d.ts.map +1 -1
  20. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +20 -0
  21. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -0
  22. package/dist/{ts4.2/src/room → src/room/data-stream/incoming}/StreamReader.d.ts +82 -56
  23. package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -0
  24. package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +27 -0
  25. package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -0
  26. package/dist/src/room/{StreamWriter.d.ts → data-stream/outgoing/StreamWriter.d.ts} +1 -1
  27. package/dist/src/room/data-stream/outgoing/StreamWriter.d.ts.map +1 -0
  28. package/dist/src/room/errors.d.ts +13 -0
  29. package/dist/src/room/errors.d.ts.map +1 -1
  30. package/dist/src/room/participant/LocalParticipant.d.ts +32 -19
  31. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  32. package/dist/src/room/track/LocalTrack.d.ts +7 -2
  33. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  34. package/dist/src/room/track/RemoteVideoTrack.d.ts +1 -0
  35. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  36. package/dist/src/room/track/Track.d.ts +4 -1
  37. package/dist/src/room/track/Track.d.ts.map +1 -1
  38. package/dist/src/room/types.d.ts +17 -1
  39. package/dist/src/room/types.d.ts.map +1 -1
  40. package/dist/src/room/utils.d.ts +8 -0
  41. package/dist/src/room/utils.d.ts.map +1 -1
  42. package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +0 -47
  43. package/dist/ts4.2/src/e2ee/worker/naluUtils.d.ts +27 -0
  44. package/dist/ts4.2/src/e2ee/worker/sifPayload.d.ts +22 -0
  45. package/dist/ts4.2/src/index.d.ts +2 -2
  46. package/dist/ts4.2/src/room/Room.d.ts +6 -10
  47. package/dist/ts4.2/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +20 -0
  48. package/dist/{src/room → ts4.2/src/room/data-stream/incoming}/StreamReader.d.ts +82 -56
  49. package/dist/ts4.2/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +27 -0
  50. package/dist/ts4.2/src/room/{StreamWriter.d.ts → data-stream/outgoing/StreamWriter.d.ts} +1 -1
  51. package/dist/ts4.2/src/room/errors.d.ts +13 -0
  52. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +32 -19
  53. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +7 -2
  54. package/dist/ts4.2/src/room/track/RemoteVideoTrack.d.ts +1 -0
  55. package/dist/ts4.2/src/room/track/Track.d.ts +4 -1
  56. package/dist/ts4.2/src/room/types.d.ts +17 -1
  57. package/dist/ts4.2/src/room/utils.d.ts +8 -0
  58. package/package.json +7 -7
  59. package/src/e2ee/E2eeManager.ts +18 -1
  60. package/src/e2ee/worker/FrameCryptor.ts +56 -157
  61. package/src/e2ee/worker/e2ee.worker.ts +6 -1
  62. package/src/e2ee/worker/naluUtils.ts +328 -0
  63. package/src/e2ee/worker/sifPayload.ts +75 -0
  64. package/src/index.ts +2 -2
  65. package/src/room/Room.ts +104 -208
  66. package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +247 -0
  67. package/src/room/data-stream/incoming/StreamReader.ts +317 -0
  68. package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +316 -0
  69. package/src/room/{StreamWriter.ts → data-stream/outgoing/StreamWriter.ts} +1 -1
  70. package/src/room/errors.ts +34 -0
  71. package/src/room/participant/LocalParticipant.ts +39 -295
  72. package/src/room/track/LocalAudioTrack.ts +2 -2
  73. package/src/room/track/LocalTrack.ts +70 -50
  74. package/src/room/track/RemoteVideoTrack.ts +12 -2
  75. package/src/room/track/Track.ts +10 -1
  76. package/src/room/types.ts +22 -1
  77. package/src/room/utils.ts +14 -5
  78. package/dist/src/e2ee/worker/SifGuard.d.ts +0 -11
  79. package/dist/src/e2ee/worker/SifGuard.d.ts.map +0 -1
  80. package/dist/src/room/StreamReader.d.ts.map +0 -1
  81. package/dist/src/room/StreamWriter.d.ts.map +0 -1
  82. package/dist/ts4.2/src/e2ee/worker/SifGuard.d.ts +0 -11
  83. package/src/e2ee/worker/SifGuard.ts +0 -47
  84. 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
- TextStreamReader,
56
- } from './StreamReader';
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 byteStreamControllers = new Map<string, StreamController<DataStream_Chunk>>();
195
+ private incomingDataStreamManager: IncomingDataStreamManager;
203
196
 
204
- private textStreamControllers = new Map<string, StreamController<DataStream_Chunk>>();
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
- if (this.textStreamHandlers.has(topic)) {
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.textStreamHandlers.delete(topic);
288
+ return this.incomingDataStreamManager.unregisterTextStreamHandler(topic);
299
289
  }
300
290
 
301
291
  registerByteStreamHandler(topic: string, callback: ByteStreamHandler) {
302
- if (this.byteStreamHandlers.has(topic)) {
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.byteStreamHandlers.delete(topic);
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 (packet.value.case === 'streamHeader') {
1788
- this.handleStreamHeader(packet.value.value, packet.participantIdentity);
1789
- } else if (packet.value.case === 'streamChunk') {
1790
- this.handleStreamChunk(packet.value.value);
1791
- } else if (packet.value.case === 'streamTrailer') {
1792
- this.handleStreamTrailer(packet.value.value);
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
+ }