livekit-client 2.8.1 → 2.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. package/README.md +18 -7
  2. package/dist/livekit-client.e2ee.worker.js +1 -1
  3. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  4. package/dist/livekit-client.e2ee.worker.mjs +1 -0
  5. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  6. package/dist/livekit-client.esm.mjs +3681 -2961
  7. package/dist/livekit-client.esm.mjs.map +1 -1
  8. package/dist/livekit-client.umd.js +1 -1
  9. package/dist/livekit-client.umd.js.map +1 -1
  10. package/dist/src/api/SignalClient.d.ts.map +1 -1
  11. package/dist/src/index.d.ts +7 -5
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/room/RTCEngine.d.ts +6 -0
  14. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  15. package/dist/src/room/Room.d.ts +50 -1
  16. package/dist/src/room/Room.d.ts.map +1 -1
  17. package/dist/src/room/StreamReader.d.ts +56 -0
  18. package/dist/src/room/StreamReader.d.ts.map +1 -0
  19. package/dist/src/room/StreamWriter.d.ts +16 -0
  20. package/dist/src/room/StreamWriter.d.ts.map +1 -0
  21. package/dist/src/room/errors.d.ts +3 -1
  22. package/dist/src/room/errors.d.ts.map +1 -1
  23. package/dist/src/room/participant/LocalParticipant.d.ts +23 -36
  24. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  25. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  26. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  27. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  28. package/dist/src/room/track/LocalTrack.d.ts +1 -0
  29. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  30. package/dist/src/room/track/LocalTrackPublication.d.ts +1 -0
  31. package/dist/src/room/track/LocalTrackPublication.d.ts.map +1 -1
  32. package/dist/src/room/track/RemoteTrack.d.ts +1 -0
  33. package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
  34. package/dist/src/room/track/RemoteTrackPublication.d.ts +1 -0
  35. package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
  36. package/dist/src/room/track/Track.d.ts +1 -0
  37. package/dist/src/room/track/Track.d.ts.map +1 -1
  38. package/dist/src/room/track/TrackPublication.d.ts +2 -1
  39. package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
  40. package/dist/src/room/track/create.d.ts.map +1 -1
  41. package/dist/src/room/track/facingMode.d.ts.map +1 -1
  42. package/dist/src/room/track/options.d.ts +18 -2
  43. package/dist/src/room/track/options.d.ts.map +1 -1
  44. package/dist/src/room/types.d.ts +43 -0
  45. package/dist/src/room/types.d.ts.map +1 -1
  46. package/dist/src/room/utils.d.ts +26 -0
  47. package/dist/src/room/utils.d.ts.map +1 -1
  48. package/dist/ts4.2/src/index.d.ts +7 -5
  49. package/dist/ts4.2/src/room/RTCEngine.d.ts +6 -0
  50. package/dist/ts4.2/src/room/Room.d.ts +49 -0
  51. package/dist/ts4.2/src/room/StreamReader.d.ts +56 -0
  52. package/dist/ts4.2/src/room/StreamWriter.d.ts +25 -0
  53. package/dist/ts4.2/src/room/errors.d.ts +3 -1
  54. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +23 -36
  55. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +1 -0
  56. package/dist/ts4.2/src/room/track/LocalTrackPublication.d.ts +1 -0
  57. package/dist/ts4.2/src/room/track/RemoteTrack.d.ts +1 -0
  58. package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +1 -0
  59. package/dist/ts4.2/src/room/track/Track.d.ts +1 -0
  60. package/dist/ts4.2/src/room/track/TrackPublication.d.ts +2 -1
  61. package/dist/ts4.2/src/room/track/options.d.ts +18 -2
  62. package/dist/ts4.2/src/room/types.d.ts +43 -0
  63. package/dist/ts4.2/src/room/utils.d.ts +26 -0
  64. package/package.json +3 -3
  65. package/src/api/SignalClient.ts +5 -2
  66. package/src/e2ee/E2eeManager.ts +2 -2
  67. package/src/index.ts +17 -1
  68. package/src/room/RTCEngine.ts +69 -2
  69. package/src/room/Room.ts +311 -23
  70. package/src/room/StreamReader.ts +177 -0
  71. package/src/room/StreamWriter.ts +32 -0
  72. package/src/room/errors.ts +16 -2
  73. package/src/room/participant/LocalParticipant.ts +320 -165
  74. package/src/room/participant/Participant.ts +2 -5
  75. package/src/room/participant/RemoteParticipant.ts +3 -2
  76. package/src/room/{participant/LocalParticipant.test.ts → rpc.test.ts} +22 -29
  77. package/src/room/track/LocalAudioTrack.ts +4 -3
  78. package/src/room/track/LocalTrack.ts +12 -4
  79. package/src/room/track/LocalTrackPublication.ts +6 -1
  80. package/src/room/track/LocalVideoTrack.ts +1 -1
  81. package/src/room/track/RemoteTrack.ts +4 -0
  82. package/src/room/track/RemoteTrackPublication.ts +8 -4
  83. package/src/room/track/Track.ts +2 -0
  84. package/src/room/track/TrackPublication.ts +6 -3
  85. package/src/room/track/create.ts +4 -3
  86. package/src/room/track/facingMode.ts +2 -1
  87. package/src/room/track/options.ts +20 -2
  88. package/src/room/track/utils.ts +1 -1
  89. package/src/room/types.ts +50 -0
  90. package/src/room/utils.ts +77 -0
@@ -4,6 +4,12 @@ import {
4
4
  Codec,
5
5
  DataPacket,
6
6
  DataPacket_Kind,
7
+ DataStream_ByteHeader,
8
+ DataStream_Chunk,
9
+ DataStream_Header,
10
+ DataStream_OperationType,
11
+ DataStream_TextHeader,
12
+ DataStream_Trailer,
7
13
  Encryption_Type,
8
14
  ParticipantInfo,
9
15
  ParticipantPermission,
@@ -23,6 +29,7 @@ import {
23
29
  import type { InternalRoomOptions } from '../../options';
24
30
  import { PCTransportState } from '../PCTransportManager';
25
31
  import type RTCEngine from '../RTCEngine';
32
+ import { TextStreamWriter } from '../StreamWriter';
26
33
  import { defaultVideoCodec } from '../defaults';
27
34
  import {
28
35
  DeviceUnsupportedError,
@@ -61,15 +68,27 @@ import {
61
68
  mimeTypeToVideoCodecString,
62
69
  screenCaptureToDisplayMediaStreamOptions,
63
70
  } from '../track/utils';
64
- import type { ChatMessage, DataPublishOptions } from '../types';
71
+ import {
72
+ type ChatMessage,
73
+ type DataPublishOptions,
74
+ type SendTextOptions,
75
+ type StreamTextOptions,
76
+ type TextStreamInfo,
77
+ } from '../types';
65
78
  import {
66
79
  Future,
67
80
  compareVersions,
81
+ isAudioTrack,
68
82
  isE2EESimulcastSupported,
69
83
  isFireFox,
84
+ isLocalAudioTrack,
85
+ isLocalTrack,
86
+ isLocalVideoTrack,
70
87
  isSVCCodec,
71
88
  isSafari17,
89
+ isVideoTrack,
72
90
  isWeb,
91
+ numberToBigInt,
73
92
  sleep,
74
93
  supportsAV1,
75
94
  supportsVP9,
@@ -84,6 +103,8 @@ import {
84
103
  mediaTrackToLocalTrack,
85
104
  } from './publishUtils';
86
105
 
106
+ const STREAM_CHUNK_SIZE = 15_000;
107
+
87
108
  export default class LocalParticipant extends Participant {
88
109
  audioTrackPublications: Map<string, LocalTrackPublication>;
89
110
 
@@ -119,6 +140,8 @@ export default class LocalParticipant extends Participant {
119
140
 
120
141
  private reconnectFuture?: Future<void>;
121
142
 
143
+ private rpcHandlers: Map<string, (data: RpcInvocationData) => Promise<string>>;
144
+
122
145
  private pendingSignalRequests: Map<
123
146
  number,
124
147
  {
@@ -130,8 +153,6 @@ export default class LocalParticipant extends Participant {
130
153
 
131
154
  private enabledPublishVideoCodecs: Codec[] = [];
132
155
 
133
- private rpcHandlers: Map<string, (data: RpcInvocationData) => Promise<string>> = new Map();
134
-
135
156
  private pendingAcks = new Map<string, { resolve: () => void; participantIdentity: string }>();
136
157
 
137
158
  private pendingResponses = new Map<
@@ -143,7 +164,13 @@ export default class LocalParticipant extends Participant {
143
164
  >();
144
165
 
145
166
  /** @internal */
146
- constructor(sid: string, identity: string, engine: RTCEngine, options: InternalRoomOptions) {
167
+ constructor(
168
+ sid: string,
169
+ identity: string,
170
+ engine: RTCEngine,
171
+ options: InternalRoomOptions,
172
+ roomRpcHandlers: Map<string, (data: RpcInvocationData) => Promise<string>>,
173
+ ) {
147
174
  super(sid, identity, undefined, undefined, undefined, {
148
175
  loggerName: options.loggerName,
149
176
  loggerContextCb: () => this.engine.logContext,
@@ -160,6 +187,7 @@ export default class LocalParticipant extends Participant {
160
187
  ['audiooutput', 'default'],
161
188
  ]);
162
189
  this.pendingSignalRequests = new Map();
190
+ this.rpcHandlers = roomRpcHandlers;
163
191
  }
164
192
 
165
193
  get lastCameraError(): Error | undefined {
@@ -251,17 +279,6 @@ export default class LocalParticipant extends Participant {
251
279
 
252
280
  private handleDataPacket = (packet: DataPacket) => {
253
281
  switch (packet.value.case) {
254
- case 'rpcRequest':
255
- let rpcRequest = packet.value.value as RpcRequest;
256
- this.handleIncomingRpcRequest(
257
- packet.participantIdentity,
258
- rpcRequest.id,
259
- rpcRequest.method,
260
- rpcRequest.payload,
261
- rpcRequest.responseTimeoutMs,
262
- rpcRequest.version,
263
- );
264
- break;
265
282
  case 'rpcResponse':
266
283
  let rpcResponse = packet.value.value as RpcResponse;
267
284
  let payload: string | null = null;
@@ -497,6 +514,7 @@ export default class LocalParticipant extends Participant {
497
514
  if (e instanceof Error) {
498
515
  this.emit(ParticipantEvent.MediaDevicesError, e);
499
516
  }
517
+ this.pendingPublishing.delete(source);
500
518
  throw e;
501
519
  }
502
520
  try {
@@ -640,9 +658,9 @@ export default class LocalParticipant extends Participant {
640
658
  track.setAudioContext(this.audioContext);
641
659
  }
642
660
  track.mediaStream = stream;
643
- if (track instanceof LocalAudioTrack && audioProcessor) {
661
+ if (isAudioTrack(track) && audioProcessor) {
644
662
  await track.setProcessor(audioProcessor);
645
- } else if (track instanceof LocalVideoTrack && videoProcessor) {
663
+ } else if (isVideoTrack(track) && videoProcessor) {
646
664
  await track.setProcessor(videoProcessor);
647
665
  }
648
666
  return track;
@@ -717,7 +735,7 @@ export default class LocalParticipant extends Participant {
717
735
  options?: TrackPublishOptions,
718
736
  isRepublish = false,
719
737
  ): Promise<LocalTrackPublication> {
720
- if (track instanceof LocalAudioTrack) {
738
+ if (isLocalAudioTrack(track)) {
721
739
  track.setAudioContext(this.audioContext);
722
740
  }
723
741
 
@@ -725,7 +743,7 @@ export default class LocalParticipant extends Participant {
725
743
  if (this.republishPromise && !isRepublish) {
726
744
  await this.republishPromise;
727
745
  }
728
- if (track instanceof LocalTrack && this.pendingPublishPromises.has(track)) {
746
+ if (isLocalTrack(track) && this.pendingPublishPromises.has(track)) {
729
747
  await this.pendingPublishPromises.get(track);
730
748
  }
731
749
  let defaultConstraints: MediaTrackConstraints | undefined;
@@ -857,7 +875,7 @@ export default class LocalParticipant extends Participant {
857
875
 
858
876
  private async publish(track: LocalTrack, opts: TrackPublishOptions, isStereo: boolean) {
859
877
  const existingTrackOfSource = Array.from(this.trackPublications.values()).find(
860
- (publishedTrack) => track instanceof LocalTrack && publishedTrack.source === track.source,
878
+ (publishedTrack) => isLocalTrack(track) && publishedTrack.source === track.source,
861
879
  );
862
880
  if (existingTrackOfSource && track.source !== Track.Source.Unknown) {
863
881
  this.log.info(`publishing a second track with the same source: ${track.source}`, {
@@ -865,7 +883,7 @@ export default class LocalParticipant extends Participant {
865
883
  ...getLogContextFromTrack(track),
866
884
  });
867
885
  }
868
- if (opts.stopMicTrackOnMute && track instanceof LocalAudioTrack) {
886
+ if (opts.stopMicTrackOnMute && isAudioTrack(track)) {
869
887
  track.stopOnMute = true;
870
888
  }
871
889
 
@@ -919,6 +937,7 @@ export default class LocalParticipant extends Participant {
919
937
  stereo: isStereo,
920
938
  disableRed: this.isE2EEEnabled || !(opts.red ?? true),
921
939
  stream: opts?.stream,
940
+ backupCodecPolicy: opts?.backupCodecPolicy,
922
941
  });
923
942
 
924
943
  // compute encodings and layers for video
@@ -950,7 +969,7 @@ export default class LocalParticipant extends Participant {
950
969
  req.width = dims.width;
951
970
  req.height = dims.height;
952
971
  // for svc codecs, disable simulcast and use vp8 for backup codec
953
- if (track instanceof LocalVideoTrack) {
972
+ if (isLocalVideoTrack(track)) {
954
973
  if (isSVCCodec(videoCodec)) {
955
974
  if (track.source === Track.Source.ScreenShare) {
956
975
  // vp9 svc with screenshare cannot encode multiple spatial layers
@@ -1036,7 +1055,7 @@ export default class LocalParticipant extends Participant {
1036
1055
 
1037
1056
  track.sender = await this.engine.createSender(track, opts, encodings);
1038
1057
 
1039
- if (track instanceof LocalVideoTrack) {
1058
+ if (isLocalVideoTrack(track)) {
1040
1059
  opts.degradationPreference ??= getDefaultDegradationPreference(track);
1041
1060
  track.setDegradationPreference(opts.degradationPreference);
1042
1061
  }
@@ -1126,9 +1145,9 @@ export default class LocalParticipant extends Participant {
1126
1145
  trackInfo: ti,
1127
1146
  });
1128
1147
 
1129
- if (track instanceof LocalVideoTrack) {
1148
+ if (isLocalVideoTrack(track)) {
1130
1149
  track.startMonitor(this.engine.client);
1131
- } else if (track instanceof LocalAudioTrack) {
1150
+ } else if (isLocalAudioTrack(track)) {
1132
1151
  track.startMonitor();
1133
1152
  }
1134
1153
 
@@ -1169,7 +1188,7 @@ export default class LocalParticipant extends Participant {
1169
1188
  throw new TrackInvalidError('track is not published');
1170
1189
  }
1171
1190
 
1172
- if (!(track instanceof LocalVideoTrack)) {
1191
+ if (!isLocalVideoTrack(track)) {
1173
1192
  throw new TrackInvalidError('track is not a video track');
1174
1193
  }
1175
1194
 
@@ -1236,7 +1255,7 @@ export default class LocalParticipant extends Participant {
1236
1255
  track: LocalTrack | MediaStreamTrack,
1237
1256
  stopOnUnpublish?: boolean,
1238
1257
  ): Promise<LocalTrackPublication | undefined> {
1239
- if (track instanceof LocalTrack) {
1258
+ if (isLocalTrack(track)) {
1240
1259
  const publishPromise = this.pendingPublishPromises.get(track);
1241
1260
  if (publishPromise) {
1242
1261
  this.log.info('awaiting publish promise before attempting to unpublish', {
@@ -1303,7 +1322,7 @@ export default class LocalParticipant extends Participant {
1303
1322
  if (this.engine.removeTrack(trackSender)) {
1304
1323
  negotiationNeeded = true;
1305
1324
  }
1306
- if (track instanceof LocalVideoTrack) {
1325
+ if (isLocalVideoTrack(track)) {
1307
1326
  for (const [, trackInfo] of track.simulcastCodecs) {
1308
1327
  if (trackInfo.sender) {
1309
1328
  if (this.engine.removeTrack(trackInfo.sender)) {
@@ -1349,9 +1368,7 @@ export default class LocalParticipant extends Participant {
1349
1368
  tracks: LocalTrack[] | MediaStreamTrack[],
1350
1369
  ): Promise<LocalTrackPublication[]> {
1351
1370
  const results = await Promise.all(tracks.map((track) => this.unpublishTrack(track)));
1352
- return results.filter(
1353
- (track) => track instanceof LocalTrackPublication,
1354
- ) as LocalTrackPublication[];
1371
+ return results.filter((track) => !!track);
1355
1372
  }
1356
1373
 
1357
1374
  async republishAllTracks(options?: TrackPublishOptions, restartTracks: boolean = true) {
@@ -1379,7 +1396,7 @@ export default class LocalParticipant extends Participant {
1379
1396
  !track.isMuted &&
1380
1397
  track.source !== Track.Source.ScreenShare &&
1381
1398
  track.source !== Track.Source.ScreenShareAudio &&
1382
- (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) &&
1399
+ (isLocalAudioTrack(track) || isLocalVideoTrack(track)) &&
1383
1400
  !track.isUserProvided
1384
1401
  ) {
1385
1402
  // generally we need to restart the track before publishing, often a full reconnect
@@ -1453,11 +1470,12 @@ export default class LocalParticipant extends Participant {
1453
1470
  await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
1454
1471
  }
1455
1472
 
1456
- async sendChatMessage(text: string): Promise<ChatMessage> {
1473
+ async sendChatMessage(text: string, options?: SendTextOptions): Promise<ChatMessage> {
1457
1474
  const msg = {
1458
1475
  id: crypto.randomUUID(),
1459
1476
  message: text,
1460
1477
  timestamp: Date.now(),
1478
+ attachedFiles: options?.attachments,
1461
1479
  } as const satisfies ChatMessage;
1462
1480
  const packet = new DataPacket({
1463
1481
  value: {
@@ -1469,6 +1487,7 @@ export default class LocalParticipant extends Participant {
1469
1487
  },
1470
1488
  });
1471
1489
  await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
1490
+
1472
1491
  this.emit(ParticipantEvent.ChatMessage, msg);
1473
1492
  return msg;
1474
1493
  }
@@ -1494,6 +1513,263 @@ export default class LocalParticipant extends Participant {
1494
1513
  return msg;
1495
1514
  }
1496
1515
 
1516
+ async sendText(text: string, options?: SendTextOptions): Promise<TextStreamInfo> {
1517
+ const streamId = crypto.randomUUID();
1518
+ const textInBytes = new TextEncoder().encode(text);
1519
+ const totalTextLength = textInBytes.byteLength;
1520
+
1521
+ const fileIds = options?.attachments?.map(() => crypto.randomUUID());
1522
+
1523
+ const progresses = new Array<number>(fileIds ? fileIds.length + 1 : 1).fill(0);
1524
+
1525
+ const handleProgress = (progress: number, idx: number) => {
1526
+ progresses[idx] = progress;
1527
+ const totalProgress = progresses.reduce((acc, val) => acc + val, 0);
1528
+ options?.onProgress?.(totalProgress);
1529
+ };
1530
+
1531
+ const writer = await this.streamText({
1532
+ streamId,
1533
+ totalSize: totalTextLength,
1534
+ destinationIdentities: options?.destinationIdentities,
1535
+ topic: options?.topic,
1536
+ attachedStreamIds: fileIds,
1537
+ });
1538
+
1539
+ const textChunkSize = Math.floor(STREAM_CHUNK_SIZE / 4); // utf8 is at most 4 bytes long, so play it safe and take a quarter of the byte size to slice the string
1540
+ const totalTextChunks = Math.ceil(totalTextLength / textChunkSize);
1541
+
1542
+ for (let i = 0; i < totalTextChunks; i++) {
1543
+ const chunkData = text.slice(
1544
+ i * textChunkSize,
1545
+ Math.min((i + 1) * textChunkSize, totalTextLength),
1546
+ );
1547
+ await this.engine.waitForBufferStatusLow(DataPacket_Kind.RELIABLE);
1548
+ await writer.write(chunkData);
1549
+
1550
+ handleProgress(Math.ceil((i + 1) / totalTextChunks), 0);
1551
+ }
1552
+
1553
+ await writer.close();
1554
+
1555
+ if (options?.attachments && fileIds) {
1556
+ await Promise.all(
1557
+ options.attachments.map(async (file, idx) =>
1558
+ this._sendFile(fileIds[idx], file, {
1559
+ topic: options.topic,
1560
+ mimeType: file.type,
1561
+ onProgress: (progress) => {
1562
+ handleProgress(progress, idx + 1);
1563
+ },
1564
+ }),
1565
+ ),
1566
+ );
1567
+ }
1568
+ return writer.info;
1569
+ }
1570
+
1571
+ /**
1572
+ * @internal
1573
+ * @experimental CAUTION, might get removed in a minor release
1574
+ */
1575
+ async streamText(options?: StreamTextOptions): Promise<TextStreamWriter> {
1576
+ const streamId = options?.streamId ?? crypto.randomUUID();
1577
+
1578
+ const info: TextStreamInfo = {
1579
+ id: streamId,
1580
+ mimeType: 'text/plain',
1581
+ timestamp: Date.now(),
1582
+ topic: options?.topic ?? '',
1583
+ size: options?.totalSize,
1584
+ };
1585
+ const header = new DataStream_Header({
1586
+ streamId,
1587
+ mimeType: info.mimeType,
1588
+ topic: info.topic,
1589
+ timestamp: numberToBigInt(info.timestamp),
1590
+ totalLength: numberToBigInt(options?.totalSize),
1591
+ contentHeader: {
1592
+ case: 'textHeader',
1593
+ value: new DataStream_TextHeader({
1594
+ version: options?.version,
1595
+ attachedStreamIds: options?.attachedStreamIds,
1596
+ replyToStreamId: options?.replyToStreamId,
1597
+ operationType:
1598
+ options?.type === 'update'
1599
+ ? DataStream_OperationType.UPDATE
1600
+ : DataStream_OperationType.CREATE,
1601
+ }),
1602
+ },
1603
+ });
1604
+ const destinationIdentities = options?.destinationIdentities;
1605
+ const packet = new DataPacket({
1606
+ destinationIdentities,
1607
+ value: {
1608
+ case: 'streamHeader',
1609
+ value: header,
1610
+ },
1611
+ });
1612
+ await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
1613
+
1614
+ let chunkId = 0;
1615
+ const localP = this;
1616
+
1617
+ const writableStream = new WritableStream<[string, number?]>({
1618
+ // Implement the sink
1619
+ write([textChunk]) {
1620
+ const textInBytes = new TextEncoder().encode(textChunk);
1621
+
1622
+ if (textInBytes.byteLength > STREAM_CHUNK_SIZE) {
1623
+ this.abort?.();
1624
+ throw new Error('chunk size too large');
1625
+ }
1626
+
1627
+ return new Promise(async (resolve) => {
1628
+ await localP.engine.waitForBufferStatusLow(DataPacket_Kind.RELIABLE);
1629
+ const chunk = new DataStream_Chunk({
1630
+ content: textInBytes,
1631
+ streamId,
1632
+ chunkIndex: numberToBigInt(chunkId),
1633
+ });
1634
+ const chunkPacket = new DataPacket({
1635
+ destinationIdentities,
1636
+ value: {
1637
+ case: 'streamChunk',
1638
+ value: chunk,
1639
+ },
1640
+ });
1641
+ await localP.engine.sendDataPacket(chunkPacket, DataPacket_Kind.RELIABLE);
1642
+
1643
+ chunkId += 1;
1644
+ resolve();
1645
+ });
1646
+ },
1647
+ async close() {
1648
+ const trailer = new DataStream_Trailer({
1649
+ streamId,
1650
+ });
1651
+ const trailerPacket = new DataPacket({
1652
+ destinationIdentities,
1653
+ value: {
1654
+ case: 'streamTrailer',
1655
+ value: trailer,
1656
+ },
1657
+ });
1658
+ await localP.engine.sendDataPacket(trailerPacket, DataPacket_Kind.RELIABLE);
1659
+ },
1660
+ abort(err) {
1661
+ console.log('Sink error:', err);
1662
+ // TODO handle aborts to signal something to receiver side
1663
+ },
1664
+ });
1665
+
1666
+ let onEngineClose = async () => {
1667
+ await writer.close();
1668
+ };
1669
+
1670
+ localP.engine.once(EngineEvent.Closing, onEngineClose);
1671
+
1672
+ const writer = new TextStreamWriter(writableStream, info, () =>
1673
+ this.engine.off(EngineEvent.Closing, onEngineClose),
1674
+ );
1675
+
1676
+ return writer;
1677
+ }
1678
+
1679
+ async sendFile(
1680
+ file: File,
1681
+ options?: {
1682
+ mimeType?: string;
1683
+ topic?: string;
1684
+ destinationIdentities?: Array<string>;
1685
+ onProgress?: (progress: number) => void;
1686
+ },
1687
+ ): Promise<{ id: string }> {
1688
+ const streamId = crypto.randomUUID();
1689
+ await this._sendFile(streamId, file, options);
1690
+ return { id: streamId };
1691
+ }
1692
+
1693
+ private async _sendFile(
1694
+ streamId: string,
1695
+ file: File,
1696
+ options?: {
1697
+ mimeType?: string;
1698
+ topic?: string;
1699
+ encryptionType?: Encryption_Type.NONE;
1700
+ destinationIdentities?: Array<string>;
1701
+ onProgress?: (progress: number) => void;
1702
+ },
1703
+ ) {
1704
+ const totalLength = file.size;
1705
+ const header = new DataStream_Header({
1706
+ totalLength: numberToBigInt(totalLength),
1707
+ mimeType: options?.mimeType ?? file.type,
1708
+ streamId,
1709
+ topic: options?.topic,
1710
+ encryptionType: options?.encryptionType,
1711
+ timestamp: numberToBigInt(Date.now()),
1712
+ contentHeader: {
1713
+ case: 'byteHeader',
1714
+ value: new DataStream_ByteHeader({
1715
+ name: file.name,
1716
+ }),
1717
+ },
1718
+ });
1719
+
1720
+ const destinationIdentities = options?.destinationIdentities;
1721
+ const packet = new DataPacket({
1722
+ destinationIdentities,
1723
+ value: {
1724
+ case: 'streamHeader',
1725
+ value: header,
1726
+ },
1727
+ });
1728
+
1729
+ await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
1730
+ function read(b: Blob): Promise<Uint8Array> {
1731
+ return new Promise((resolve) => {
1732
+ const fr = new FileReader();
1733
+ fr.onload = () => {
1734
+ resolve(new Uint8Array(fr.result as ArrayBuffer));
1735
+ };
1736
+ fr.readAsArrayBuffer(b);
1737
+ });
1738
+ }
1739
+ const totalChunks = Math.ceil(totalLength / STREAM_CHUNK_SIZE);
1740
+ for (let i = 0; i < totalChunks; i++) {
1741
+ const chunkData = await read(
1742
+ file.slice(i * STREAM_CHUNK_SIZE, Math.min((i + 1) * STREAM_CHUNK_SIZE, totalLength)),
1743
+ );
1744
+ await this.engine.waitForBufferStatusLow(DataPacket_Kind.RELIABLE);
1745
+ const chunk = new DataStream_Chunk({
1746
+ content: chunkData,
1747
+ streamId,
1748
+ chunkIndex: numberToBigInt(i),
1749
+ });
1750
+ const chunkPacket = new DataPacket({
1751
+ destinationIdentities,
1752
+ value: {
1753
+ case: 'streamChunk',
1754
+ value: chunk,
1755
+ },
1756
+ });
1757
+ await this.engine.sendDataPacket(chunkPacket, DataPacket_Kind.RELIABLE);
1758
+ options?.onProgress?.((i + 1) / totalChunks);
1759
+ }
1760
+ const trailer = new DataStream_Trailer({
1761
+ streamId,
1762
+ });
1763
+ const trailerPacket = new DataPacket({
1764
+ destinationIdentities,
1765
+ value: {
1766
+ case: 'streamTrailer',
1767
+ value: trailer,
1768
+ },
1769
+ });
1770
+ await this.engine.sendDataPacket(trailerPacket, DataPacket_Kind.RELIABLE);
1771
+ }
1772
+
1497
1773
  /**
1498
1774
  * Initiate an RPC call to a remote participant
1499
1775
  * @param params - Parameters for initiating the RPC call, see {@link PerformRpcParams}
@@ -1571,39 +1847,20 @@ export default class LocalParticipant extends Participant {
1571
1847
  }
1572
1848
 
1573
1849
  /**
1574
- * Establishes the participant as a receiver for calls of the specified RPC method.
1575
- * Will overwrite any existing callback for the same method.
1576
- *
1577
- * @param method - The name of the indicated RPC method
1578
- * @param handler - Will be invoked when an RPC request for this method is received
1579
- * @returns A promise that resolves when the method is successfully registered
1580
- *
1581
- * @example
1582
- * ```typescript
1583
- * room.localParticipant?.registerRpcMethod(
1584
- * 'greet',
1585
- * async (data: RpcInvocationData) => {
1586
- * console.log(`Received greeting from ${data.callerIdentity}: ${data.payload}`);
1587
- * return `Hello, ${data.callerIdentity}!`;
1588
- * }
1589
- * );
1590
- * ```
1591
- *
1592
- * The handler should return a Promise that resolves to a string.
1593
- * If unable to respond within `responseTimeout`, the request will result in an error on the caller's side.
1594
- *
1595
- * You may throw errors of type `RpcError` with a string `message` in the handler,
1596
- * and they will be received on the caller's side with the message intact.
1597
- * Other errors thrown in your handler will not be transmitted as-is, and will instead arrive to the caller as `1500` ("Application Error").
1850
+ * @deprecated use `room.registerRpcMethod` instead
1598
1851
  */
1599
1852
  registerRpcMethod(method: string, handler: (data: RpcInvocationData) => Promise<string>) {
1853
+ if (this.rpcHandlers.has(method)) {
1854
+ this.log.warn(
1855
+ `you're overriding the RPC handler for method ${method}, in the future this will throw an error`,
1856
+ );
1857
+ }
1858
+
1600
1859
  this.rpcHandlers.set(method, handler);
1601
1860
  }
1602
1861
 
1603
1862
  /**
1604
- * Unregisters a previously registered RPC method.
1605
- *
1606
- * @param method - The name of the RPC method to unregister
1863
+ * @deprecated use `room.unregisterRpcMethod` instead
1607
1864
  */
1608
1865
  unregisterRpcMethod(method: string) {
1609
1866
  this.rpcHandlers.delete(method);
@@ -1661,68 +1918,6 @@ export default class LocalParticipant extends Participant {
1661
1918
  }
1662
1919
  }
1663
1920
 
1664
- private async handleIncomingRpcRequest(
1665
- callerIdentity: string,
1666
- requestId: string,
1667
- method: string,
1668
- payload: string,
1669
- responseTimeout: number,
1670
- version: number,
1671
- ) {
1672
- await this.publishRpcAck(callerIdentity, requestId);
1673
-
1674
- if (version !== 1) {
1675
- await this.publishRpcResponse(
1676
- callerIdentity,
1677
- requestId,
1678
- null,
1679
- RpcError.builtIn('UNSUPPORTED_VERSION'),
1680
- );
1681
- return;
1682
- }
1683
-
1684
- const handler = this.rpcHandlers.get(method);
1685
-
1686
- if (!handler) {
1687
- await this.publishRpcResponse(
1688
- callerIdentity,
1689
- requestId,
1690
- null,
1691
- RpcError.builtIn('UNSUPPORTED_METHOD'),
1692
- );
1693
- return;
1694
- }
1695
-
1696
- let responseError: RpcError | null = null;
1697
- let responsePayload: string | null = null;
1698
-
1699
- try {
1700
- const response = await handler({
1701
- requestId,
1702
- callerIdentity,
1703
- payload,
1704
- responseTimeout,
1705
- });
1706
- if (byteLength(response) > MAX_PAYLOAD_BYTES) {
1707
- responseError = RpcError.builtIn('RESPONSE_PAYLOAD_TOO_LARGE');
1708
- console.warn(`RPC Response payload too large for ${method}`);
1709
- } else {
1710
- responsePayload = response;
1711
- }
1712
- } catch (error) {
1713
- if (error instanceof RpcError) {
1714
- responseError = error;
1715
- } else {
1716
- console.warn(
1717
- `Uncaught error returned by RPC handler for ${method}. Returning APPLICATION_ERROR instead.`,
1718
- error,
1719
- );
1720
- responseError = RpcError.builtIn('APPLICATION_ERROR');
1721
- }
1722
- }
1723
- await this.publishRpcResponse(callerIdentity, requestId, responsePayload, responseError);
1724
- }
1725
-
1726
1921
  /** @internal */
1727
1922
  private async publishRpcRequest(
1728
1923
  destinationIdentity: string,
@@ -1749,46 +1944,6 @@ export default class LocalParticipant extends Participant {
1749
1944
  await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
1750
1945
  }
1751
1946
 
1752
- /** @internal */
1753
- private async publishRpcResponse(
1754
- destinationIdentity: string,
1755
- requestId: string,
1756
- payload: string | null,
1757
- error: RpcError | null,
1758
- ) {
1759
- const packet = new DataPacket({
1760
- destinationIdentities: [destinationIdentity],
1761
- kind: DataPacket_Kind.RELIABLE,
1762
- value: {
1763
- case: 'rpcResponse',
1764
- value: new RpcResponse({
1765
- requestId,
1766
- value: error
1767
- ? { case: 'error', value: error.toProto() }
1768
- : { case: 'payload', value: payload ?? '' },
1769
- }),
1770
- },
1771
- });
1772
-
1773
- await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
1774
- }
1775
-
1776
- /** @internal */
1777
- private async publishRpcAck(destinationIdentity: string, requestId: string) {
1778
- const packet = new DataPacket({
1779
- destinationIdentities: [destinationIdentity],
1780
- kind: DataPacket_Kind.RELIABLE,
1781
- value: {
1782
- case: 'rpcAck',
1783
- value: new RpcAck({
1784
- requestId,
1785
- }),
1786
- },
1787
- });
1788
-
1789
- await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
1790
- }
1791
-
1792
1947
  /** @internal */
1793
1948
  handleParticipantDisconnected(participantIdentity: string) {
1794
1949
  for (const [id, { participantIdentity: pendingIdentity }] of this.pendingAcks) {
@@ -1962,7 +2117,7 @@ export default class LocalParticipant extends Participant {
1962
2117
  this.unpublishTrack(track);
1963
2118
  } else if (track.isUserProvided) {
1964
2119
  await track.mute();
1965
- } else if (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) {
2120
+ } else if (isLocalAudioTrack(track) || isLocalVideoTrack(track)) {
1966
2121
  try {
1967
2122
  if (isWeb()) {
1968
2123
  try {
@@ -1997,7 +2152,7 @@ export default class LocalParticipant extends Participant {
1997
2152
  ...this.logContext,
1998
2153
  ...getLogContextFromTrack(track),
1999
2154
  });
2000
- if (track instanceof LocalAudioTrack) {
2155
+ if (isLocalAudioTrack(track)) {
2001
2156
  // fall back to default device if available
2002
2157
  await track.restartTrack({ deviceId: 'default' });
2003
2158
  } else {
@@ -2026,7 +2181,7 @@ export default class LocalParticipant extends Participant {
2026
2181
 
2027
2182
  // this looks overly complicated due to this object tree
2028
2183
  if (track instanceof MediaStreamTrack) {
2029
- if (localTrack instanceof LocalAudioTrack || localTrack instanceof LocalVideoTrack) {
2184
+ if (isLocalAudioTrack(localTrack) || isLocalVideoTrack(localTrack)) {
2030
2185
  if (localTrack.mediaStreamTrack === track) {
2031
2186
  publication = <LocalTrackPublication>pub;
2032
2187
  }