@livekit/rtc-node 0.13.2 → 0.13.4

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 (114) hide show
  1. package/dist/data_streams/index.cjs +25 -0
  2. package/dist/data_streams/index.cjs.map +1 -0
  3. package/dist/data_streams/index.d.cts +11 -0
  4. package/dist/data_streams/index.d.ts +11 -0
  5. package/dist/data_streams/index.d.ts.map +1 -0
  6. package/dist/data_streams/index.js +3 -0
  7. package/dist/data_streams/index.js.map +1 -0
  8. package/dist/data_streams/stream_reader.cjs +145 -0
  9. package/dist/data_streams/stream_reader.cjs.map +1 -0
  10. package/dist/data_streams/stream_reader.d.cts +10 -0
  11. package/dist/data_streams/stream_reader.d.ts +10 -0
  12. package/dist/data_streams/stream_reader.d.ts.map +1 -0
  13. package/dist/data_streams/stream_reader.js +120 -0
  14. package/dist/data_streams/stream_reader.js.map +1 -0
  15. package/dist/data_streams/stream_writer.cjs +51 -0
  16. package/dist/data_streams/stream_writer.cjs.map +1 -0
  17. package/dist/data_streams/stream_writer.d.cts +26 -0
  18. package/dist/data_streams/stream_writer.d.ts +26 -0
  19. package/dist/data_streams/stream_writer.d.ts.map +1 -0
  20. package/dist/data_streams/stream_writer.js +26 -0
  21. package/dist/data_streams/stream_writer.js.map +1 -0
  22. package/dist/data_streams/types.cjs +17 -0
  23. package/dist/data_streams/types.cjs.map +1 -0
  24. package/dist/data_streams/types.d.cts +10 -0
  25. package/dist/data_streams/types.d.ts +10 -0
  26. package/dist/data_streams/types.d.ts.map +1 -0
  27. package/dist/data_streams/types.js +1 -0
  28. package/dist/data_streams/types.js.map +1 -0
  29. package/dist/index.cjs +7 -4
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +4 -0
  32. package/dist/index.d.ts +4 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +1 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/log.cjs +42 -0
  37. package/dist/log.cjs.map +1 -0
  38. package/dist/log.d.cts +7 -0
  39. package/dist/log.d.ts +7 -0
  40. package/dist/log.d.ts.map +1 -0
  41. package/dist/log.js +18 -0
  42. package/dist/log.js.map +1 -0
  43. package/dist/napi/native.d.cjs.map +1 -1
  44. package/dist/napi/native.d.ts +5 -5
  45. package/dist/native.d.ts +5 -5
  46. package/dist/participant.cjs +260 -16
  47. package/dist/participant.cjs.map +1 -1
  48. package/dist/participant.d.cts +40 -6
  49. package/dist/participant.d.ts +40 -6
  50. package/dist/participant.d.ts.map +1 -1
  51. package/dist/participant.js +263 -5
  52. package/dist/participant.js.map +1 -1
  53. package/dist/proto/ffi_pb.cjs +14 -3
  54. package/dist/proto/ffi_pb.cjs.map +1 -1
  55. package/dist/proto/ffi_pb.d.cts +81 -7
  56. package/dist/proto/ffi_pb.d.ts +81 -7
  57. package/dist/proto/ffi_pb.d.ts.map +1 -1
  58. package/dist/proto/ffi_pb.js +15 -4
  59. package/dist/proto/ffi_pb.js.map +1 -1
  60. package/dist/proto/room_pb.cjs +488 -30
  61. package/dist/proto/room_pb.cjs.map +1 -1
  62. package/dist/proto/room_pb.d.cts +412 -50
  63. package/dist/proto/room_pb.d.ts +412 -50
  64. package/dist/proto/room_pb.d.ts.map +1 -1
  65. package/dist/proto/room_pb.js +471 -29
  66. package/dist/proto/room_pb.js.map +1 -1
  67. package/dist/proto/video_frame_pb.cjs.map +1 -1
  68. package/dist/proto/video_frame_pb.d.cts +3 -0
  69. package/dist/proto/video_frame_pb.d.ts +3 -0
  70. package/dist/proto/video_frame_pb.d.ts.map +1 -1
  71. package/dist/proto/video_frame_pb.js.map +1 -1
  72. package/dist/room.cjs +202 -45
  73. package/dist/room.cjs.map +1 -1
  74. package/dist/room.d.cts +21 -3
  75. package/dist/room.d.ts +21 -3
  76. package/dist/room.d.ts.map +1 -1
  77. package/dist/room.js +203 -45
  78. package/dist/room.js.map +1 -1
  79. package/dist/types-BWsKf9LP.d.cts +93 -0
  80. package/dist/types-Ca3IVRvo.d.ts +93 -0
  81. package/dist/utils.cjs +51 -0
  82. package/dist/utils.cjs.map +1 -0
  83. package/dist/utils.d.cts +7 -0
  84. package/dist/utils.d.ts +7 -0
  85. package/dist/utils.d.ts.map +1 -0
  86. package/dist/utils.js +25 -0
  87. package/dist/utils.js.map +1 -0
  88. package/dist/version.cjs +1 -1
  89. package/dist/version.cjs.map +1 -1
  90. package/dist/version.d.cts +1 -1
  91. package/dist/version.d.ts +1 -1
  92. package/dist/version.js +1 -1
  93. package/dist/version.js.map +1 -1
  94. package/dist/video_frame.cjs +2 -0
  95. package/dist/video_frame.cjs.map +1 -1
  96. package/dist/video_frame.d.ts.map +1 -1
  97. package/dist/video_frame.js +2 -0
  98. package/dist/video_frame.js.map +1 -1
  99. package/package.json +9 -7
  100. package/src/data_streams/index.ts +7 -0
  101. package/src/data_streams/stream_reader.ts +166 -0
  102. package/src/data_streams/stream_writer.ts +36 -0
  103. package/src/data_streams/types.ts +61 -0
  104. package/src/index.ts +1 -0
  105. package/src/log.ts +25 -0
  106. package/src/napi/native.d.ts +5 -5
  107. package/src/participant.ts +351 -29
  108. package/src/proto/ffi_pb.ts +86 -1
  109. package/src/proto/room_pb.ts +788 -68
  110. package/src/proto/video_frame_pb.ts +3 -0
  111. package/src/room.ts +251 -68
  112. package/src/utils.ts +33 -0
  113. package/src/version.ts +1 -1
  114. package/src/video_frame.ts +2 -0
@@ -87,6 +87,9 @@ proto2.util.setEnumType(VideoRotation, "livekit.proto.VideoRotation", [
87
87
  ]);
88
88
 
89
89
  /**
90
+ * Values of this enum must not be changed
91
+ * It is used to serialize a rtc.VideoFrame on Python
92
+ *
90
93
  * @generated from enum livekit.proto.VideoBufferType
91
94
  */
92
95
  export enum VideoBufferType {
package/src/room.ts CHANGED
@@ -3,9 +3,19 @@
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  import type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';
5
5
  import EventEmitter from 'events';
6
+ import { ReadableStream } from 'node:stream/web';
7
+ import { ByteStreamReader, TextStreamReader } from './data_streams/stream_reader.js';
8
+ import type {
9
+ ByteStreamHandler,
10
+ ByteStreamInfo,
11
+ StreamController,
12
+ TextStreamHandler,
13
+ TextStreamInfo,
14
+ } from './data_streams/types.js';
6
15
  import type { E2EEOptions } from './e2ee.js';
7
16
  import { E2EEManager, defaultE2EEOptions } from './e2ee.js';
8
17
  import { FfiClient, FfiClientEvent, FfiHandle } from './ffi_client.js';
18
+ import { log } from './log.js';
9
19
  import type { Participant } from './participant.js';
10
20
  import { LocalParticipant, RemoteParticipant } from './participant.js';
11
21
  import { EncryptionState } from './proto/e2ee_pb.js';
@@ -16,6 +26,9 @@ import {
16
26
  type ConnectResponse,
17
27
  type ConnectionQuality,
18
28
  type DataPacketKind,
29
+ type DataStream_Chunk,
30
+ type DataStream_Header,
31
+ DataStream_Trailer,
19
32
  type DisconnectResponse,
20
33
  RoomOptions as FfiRoomOptions,
21
34
  type IceServer,
@@ -33,6 +46,7 @@ import { RemoteAudioTrack, RemoteVideoTrack } from './track.js';
33
46
  import type { LocalTrackPublication, TrackPublication } from './track_publication.js';
34
47
  import { RemoteTrackPublication } from './track_publication.js';
35
48
  import type { ChatMessage } from './types.js';
49
+ import { bigIntToNumber } from './utils.js';
36
50
 
37
51
  export interface RtcConfiguration {
38
52
  iceTransportType: IceTransportType;
@@ -66,6 +80,11 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
66
80
  private info?: RoomInfo;
67
81
  private ffiHandle?: FfiHandle;
68
82
 
83
+ private byteStreamControllers = new Map<string, StreamController<DataStream_Chunk>>();
84
+ private textStreamControllers = new Map<string, StreamController<DataStream_Chunk>>();
85
+ private byteStreamHandlers = new Map<string, ByteStreamHandler>();
86
+ private textStreamHandlers = new Map<string, TextStreamHandler>();
87
+
69
88
  e2eeManager?: E2EEManager;
70
89
  connectionState: ConnectionState = ConnectionState.CONN_DISCONNECTED;
71
90
 
@@ -88,8 +107,26 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
88
107
  return this.ffiHandle != undefined && this.connectionState != ConnectionState.CONN_DISCONNECTED;
89
108
  }
90
109
 
91
- async getSid(): Promise<string | undefined> {
92
- return this.info?.sid; // TODO update this to handle async room updates once rust protocol has been updated
110
+ async getSid(): Promise<string> {
111
+ if (!this.isConnected) {
112
+ return '';
113
+ }
114
+ if (this.info?.sid && this.info.sid !== '') {
115
+ return this.info.sid;
116
+ }
117
+ return new Promise((resolve, reject) => {
118
+ const handleRoomUpdate = (sid: string) => {
119
+ if (sid !== '') {
120
+ this.off(RoomEvent.RoomSidChanged, handleRoomUpdate);
121
+ resolve(sid);
122
+ }
123
+ };
124
+ this.on(RoomEvent.RoomSidChanged, handleRoomUpdate);
125
+ this.once(RoomEvent.Disconnected, () => {
126
+ this.off(RoomEvent.RoomSidChanged, handleRoomUpdate);
127
+ reject('Room disconnected before room server id was available');
128
+ });
129
+ });
93
130
  }
94
131
 
95
132
  async connect(url: string, token: string, opts?: RoomOptions) {
@@ -157,10 +194,50 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
157
194
  this.removeAllListeners();
158
195
  }
159
196
 
197
+ registerTextStreamHandler(topic: string, callback: TextStreamHandler) {
198
+ if (this.textStreamHandlers.has(topic)) {
199
+ throw new Error(`A text stream handler for topic "${topic}" has already been set.`);
200
+ }
201
+ this.textStreamHandlers.set(topic, callback);
202
+ }
203
+
204
+ unregisterTextStreamHandler(topic: string) {
205
+ this.textStreamHandlers.delete(topic);
206
+ }
207
+
208
+ registerByteStreamHandler(topic: string, callback: ByteStreamHandler) {
209
+ if (this.byteStreamHandlers.has(topic)) {
210
+ throw new Error(`A byte stream handler for topic "${topic}" has already been set.`);
211
+ }
212
+ this.byteStreamHandlers.set(topic, callback);
213
+ }
214
+
215
+ unregisterByteStreamHandler(topic: string) {
216
+ this.byteStreamHandlers.delete(topic);
217
+ }
218
+
160
219
  private onFfiEvent = (ffiEvent: FfiEvent) => {
161
- if (
220
+ if (!this.localParticipant || !this.ffiHandle || !this.info) {
221
+ throw TypeError('cannot handle ffi events before connectCallback');
222
+ }
223
+
224
+ if (ffiEvent.message.case == 'rpcMethodInvocation') {
225
+ if (
226
+ ffiEvent.message.value.localParticipantHandle == this.localParticipant.ffi_handle.handle
227
+ ) {
228
+ this.localParticipant.handleRpcMethodInvocation(
229
+ ffiEvent.message.value.invocationId!,
230
+ ffiEvent.message.value.method!,
231
+ ffiEvent.message.value.requestId!,
232
+ ffiEvent.message.value.callerIdentity!,
233
+ ffiEvent.message.value.payload!,
234
+ ffiEvent.message.value.responseTimeoutMs!,
235
+ );
236
+ }
237
+ return;
238
+ } else if (
162
239
  ffiEvent.message.case != 'roomEvent' ||
163
- ffiEvent.message.value.roomHandle != this.ffiHandle?.handle
240
+ ffiEvent.message.value.roomHandle != this.ffiHandle.handle
164
241
  ) {
165
242
  return;
166
243
  }
@@ -191,96 +268,97 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
191
268
  participant!.trackPublications.set(publication.sid!, publication);
192
269
  this.emit(RoomEvent.TrackPublished, publication, participant!);
193
270
  } else if (ev.case == 'trackUnpublished') {
194
- const participant = this.remoteParticipants.get(ev.value.participantIdentity!);
195
- const publication = participant!.trackPublications.get(ev.value.publicationSid!);
196
- participant!.trackPublications.delete(ev.value.publicationSid!);
197
- this.emit(RoomEvent.TrackUnpublished, publication!, participant!);
271
+ const participant = this.requireRemoteParticipant(ev.value.participantIdentity!);
272
+ const publication = participant.trackPublications.get(ev.value.publicationSid!);
273
+ participant.trackPublications.delete(ev.value.publicationSid!);
274
+ if (publication) {
275
+ this.emit(RoomEvent.TrackUnpublished, publication, participant);
276
+ }
198
277
  } else if (ev.case == 'trackSubscribed') {
199
- const ownedTrack = ev.value.track;
200
- const participant = this.remoteParticipants.get(ev.value.participantIdentity!);
201
- const publication = participant!.trackPublications.get(ownedTrack!.info!.sid!);
202
- publication!.subscribed = true;
203
- if (ownedTrack!.info!.kind == TrackKind.KIND_VIDEO) {
204
- publication!.track = new RemoteVideoTrack(ownedTrack!);
205
- } else if (ownedTrack!.info!.kind == TrackKind.KIND_AUDIO) {
206
- publication!.track = new RemoteAudioTrack(ownedTrack!);
278
+ const ownedTrack = ev.value.track!;
279
+ const trackInfo = ownedTrack.info!;
280
+ const { participant, publication } = this.requirePublicationOfRemoteParticipant(
281
+ ev.value.participantIdentity!,
282
+ trackInfo.sid!,
283
+ );
284
+ publication.subscribed = true;
285
+ if (trackInfo.kind == TrackKind.KIND_VIDEO) {
286
+ publication.track = new RemoteVideoTrack(ownedTrack);
287
+ } else if (trackInfo.kind == TrackKind.KIND_AUDIO) {
288
+ publication.track = new RemoteAudioTrack(ownedTrack);
207
289
  }
208
290
 
209
- this.emit(RoomEvent.TrackSubscribed, publication!.track!, publication!, participant!);
291
+ this.emit(RoomEvent.TrackSubscribed, publication.track!, publication, participant);
210
292
  } else if (ev.case == 'trackUnsubscribed') {
211
- const participant = this.remoteParticipants.get(ev.value.participantIdentity!);
212
- const publication = participant!.trackPublications.get(ev.value.trackSid!);
213
- publication!.track = undefined;
214
- publication!.subscribed = false;
215
- this.emit(RoomEvent.TrackUnsubscribed, publication!.track!, publication!, participant!);
216
- } else if (ev.case == 'trackSubscriptionFailed') {
217
- const participant = this.remoteParticipants.get(ev.value.participantIdentity!);
218
- this.emit(
219
- RoomEvent.TrackSubscriptionFailed,
293
+ const { participant, publication } = this.requirePublicationOfRemoteParticipant(
294
+ ev.value.participantIdentity!,
220
295
  ev.value.trackSid!,
221
- participant!,
222
- ev.value.error,
223
296
  );
297
+ const track = publication.track!;
298
+ publication.track = undefined;
299
+ publication.subscribed = false;
300
+ this.emit(RoomEvent.TrackUnsubscribed, track, publication, participant);
301
+ } else if (ev.case == 'trackSubscriptionFailed') {
302
+ const participant = this.requireRemoteParticipant(ev.value.participantIdentity!);
303
+ this.emit(RoomEvent.TrackSubscriptionFailed, ev.value.trackSid!, participant, ev.value.error);
224
304
  } else if (ev.case == 'trackMuted') {
225
- const participant = this.remoteParticipants.get(ev.value.participantIdentity!);
226
- const publication = participant!.trackPublications.get(ev.value.trackSid!);
227
- publication!.info!.muted = true;
228
- if (publication!.track) {
229
- publication!.track.info!.muted = true;
305
+ const { participant, publication } = this.requirePublicationOfParticipant(
306
+ ev.value.participantIdentity!,
307
+ ev.value.trackSid!,
308
+ );
309
+ publication.info!.muted = true;
310
+ if (publication.track) {
311
+ publication.track.info!.muted = true;
230
312
  }
231
- this.emit(RoomEvent.TrackMuted, publication!, participant!);
313
+ this.emit(RoomEvent.TrackMuted, publication, participant);
232
314
  } else if (ev.case == 'trackUnmuted') {
233
- const participant = this.retrieveParticipantByIdentity(ev.value.participantIdentity!);
234
- const publication = participant!.trackPublications.get(ev.value.trackSid!);
235
- publication!.info!.muted = false;
236
- if (publication!.track) {
237
- publication!.track.info!.muted = false;
315
+ const { participant, publication } = this.requirePublicationOfParticipant(
316
+ ev.value.participantIdentity!,
317
+ ev.value.trackSid!,
318
+ );
319
+ publication.info!.muted = false;
320
+ if (publication.track) {
321
+ publication.track.info!.muted = false;
238
322
  }
239
- this.emit(RoomEvent.TrackUnmuted, publication!, participant!);
323
+ this.emit(RoomEvent.TrackUnmuted, publication, participant);
240
324
  } else if (ev.case == 'activeSpeakersChanged') {
241
325
  const activeSpeakers = ev.value.participantIdentities.map((identity) =>
242
- this.retrieveParticipantByIdentity(identity),
243
- );
244
- this.emit(
245
- RoomEvent.ActiveSpeakersChanged,
246
- activeSpeakers.map((s) => s!),
326
+ this.requireParticipantByIdentity(identity),
247
327
  );
328
+ this.emit(RoomEvent.ActiveSpeakersChanged, activeSpeakers);
248
329
  } else if (ev.case == 'roomMetadataChanged') {
249
330
  this.info!.metadata = ev.value.metadata;
250
331
  this.emit(RoomEvent.RoomMetadataChanged, this.info!.metadata!);
251
332
  } else if (ev.case == 'participantMetadataChanged') {
252
- const participant = this.retrieveParticipantByIdentity(ev.value.participantIdentity!);
253
- participant!.info!.metadata = ev.value.metadata;
254
- this.emit(RoomEvent.ParticipantMetadataChanged, participant!.metadata, participant!);
333
+ const participant = this.requireParticipantByIdentity(ev.value.participantIdentity!);
334
+ participant.info.metadata = ev.value.metadata;
335
+ this.emit(RoomEvent.ParticipantMetadataChanged, participant.metadata, participant);
255
336
  } else if (ev.case == 'participantNameChanged') {
256
- const participant = this.retrieveParticipantByIdentity(ev.value.participantIdentity!);
257
- participant!.info!.name = ev.value.name;
258
- this.emit(RoomEvent.ParticipantNameChanged, participant!.name!, participant!);
337
+ const participant = this.requireParticipantByIdentity(ev.value.participantIdentity!);
338
+ participant.info.name = ev.value.name;
339
+ this.emit(RoomEvent.ParticipantNameChanged, participant.name!, participant);
259
340
  } else if (ev.case == 'participantAttributesChanged') {
260
- const participant = this.retrieveParticipantByIdentity(ev.value.participantIdentity!);
261
- participant!.info!.attributes = ev.value.attributes.reduce(
262
- (obj, item) => {
263
- obj[item.key!] = item.value!;
264
- return obj;
341
+ const participant = this.requireParticipantByIdentity(ev.value.participantIdentity!);
342
+ participant.info.attributes = ev.value.attributes.reduce(
343
+ (acc, value) => {
344
+ acc[value.key!] = value.value!;
345
+ return acc;
265
346
  },
266
347
  {} as Record<string, string>,
267
348
  );
268
349
  if (Object.keys(ev.value.changedAttributes).length > 0) {
269
- this.emit(
270
- RoomEvent.ParticipantAttributesChanged,
271
- ev.value.changedAttributes.reduce(
272
- (obj, item) => {
273
- obj[item.key!] = item.value!;
274
- return obj;
275
- },
276
- {} as Record<string, string>,
277
- ),
278
- participant!,
350
+ const changedAttributes = ev.value.changedAttributes.reduce(
351
+ (acc, value) => {
352
+ acc[value.key!] = value.value!;
353
+ return acc;
354
+ },
355
+ {} as Record<string, string>,
279
356
  );
357
+ this.emit(RoomEvent.ParticipantAttributesChanged, changedAttributes, participant);
280
358
  }
281
359
  } else if (ev.case == 'connectionQualityChanged') {
282
- const participant = this.retrieveParticipantByIdentity(ev.value.participantIdentity!);
283
- this.emit(RoomEvent.ConnectionQualityChanged, ev.value.quality!, participant!);
360
+ const participant = this.requireParticipantByIdentity(ev.value.participantIdentity!);
361
+ this.emit(RoomEvent.ConnectionQualityChanged, ev.value.quality!, participant);
284
362
  } else if (ev.case == 'chatMessage') {
285
363
  const participant = this.retrieveParticipantByIdentity(ev.value.participantIdentity!);
286
364
  const { id, message: messageText, timestamp, editTimestamp, generated } = ev.value.message!;
@@ -334,6 +412,14 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
334
412
  this.emit(RoomEvent.Reconnecting);
335
413
  } else if (ev.case == 'reconnected') {
336
414
  this.emit(RoomEvent.Reconnected);
415
+ } else if (ev.case == 'roomSidChanged') {
416
+ this.emit(RoomEvent.RoomSidChanged, ev.value.sid!);
417
+ } else if (ev.case === 'streamHeaderReceived' && ev.value.header) {
418
+ this.handleStreamHeader(ev.value.header, ev.value.participantIdentity!);
419
+ } else if (ev.case === 'streamChunkReceived' && ev.value.chunk) {
420
+ this.handleStreamChunk(ev.value.chunk);
421
+ } else if (ev.case === 'streamTrailerReceived' && ev.value.trailer) {
422
+ this.handleStreamTrailer(ev.value.trailer);
337
423
  }
338
424
  };
339
425
 
@@ -390,6 +476,100 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
390
476
  this.remoteParticipants.set(ownedInfo.info!.identity!, participant);
391
477
  return participant;
392
478
  }
479
+
480
+ private handleStreamHeader(streamHeader: DataStream_Header, participantIdentity: string) {
481
+ if (streamHeader.contentHeader.case === 'byteHeader') {
482
+ const streamHandlerCallback = this.byteStreamHandlers.get(streamHeader.topic ?? '');
483
+
484
+ if (!streamHandlerCallback) {
485
+ log.debug('ignoring incoming byte stream due to no handler for topic', streamHeader.topic);
486
+ return;
487
+ }
488
+ let streamController: ReadableStreamDefaultController<DataStream_Chunk>;
489
+ const stream = new ReadableStream({
490
+ start: (controller) => {
491
+ streamController = controller;
492
+ this.byteStreamControllers.set(streamHeader.streamId!, {
493
+ header: streamHeader,
494
+ controller: streamController,
495
+ startTime: Date.now(),
496
+ });
497
+ },
498
+ });
499
+ const info: ByteStreamInfo = {
500
+ streamId: streamHeader.streamId!,
501
+ name: streamHeader.contentHeader.value.name ?? 'unknown',
502
+ mimeType: streamHeader.mimeType!,
503
+ totalSize: streamHeader.totalLength ? Number(streamHeader.totalLength) : undefined,
504
+ topic: streamHeader.topic!,
505
+ timestamp: bigIntToNumber(streamHeader.timestamp!),
506
+ attributes: streamHeader.attributes,
507
+ };
508
+ streamHandlerCallback(
509
+ new ByteStreamReader(info, stream, bigIntToNumber(streamHeader.totalLength)),
510
+ { identity: participantIdentity },
511
+ );
512
+ } else if (streamHeader.contentHeader.case === 'textHeader') {
513
+ const streamHandlerCallback = this.textStreamHandlers.get(streamHeader.topic ?? '');
514
+
515
+ if (!streamHandlerCallback) {
516
+ log.debug('ignoring incoming text stream due to no handler for topic', streamHeader.topic);
517
+ return;
518
+ }
519
+ let streamController: ReadableStreamDefaultController<DataStream_Chunk>;
520
+ const stream = new ReadableStream<DataStream_Chunk>({
521
+ start: (controller) => {
522
+ streamController = controller;
523
+ this.textStreamControllers.set(streamHeader.streamId!, {
524
+ header: streamHeader,
525
+ controller: streamController,
526
+ startTime: Date.now(),
527
+ });
528
+ },
529
+ });
530
+ const info: TextStreamInfo = {
531
+ streamId: streamHeader.streamId!,
532
+ mimeType: streamHeader.mimeType!,
533
+ totalSize: streamHeader.totalLength ? Number(streamHeader.totalLength) : undefined,
534
+ topic: streamHeader.topic!,
535
+ timestamp: Number(streamHeader.timestamp),
536
+ attributes: streamHeader.attributes,
537
+ };
538
+ streamHandlerCallback(
539
+ new TextStreamReader(info, stream, bigIntToNumber(streamHeader.totalLength)),
540
+ { identity: participantIdentity },
541
+ );
542
+ }
543
+ }
544
+
545
+ private handleStreamChunk(chunk: DataStream_Chunk) {
546
+ const fileBuffer = this.byteStreamControllers.get(chunk.streamId!);
547
+ if (fileBuffer) {
548
+ if (chunk.content!.length > 0) {
549
+ fileBuffer.controller.enqueue(chunk);
550
+ }
551
+ }
552
+ const textBuffer = this.textStreamControllers.get(chunk.streamId!);
553
+ if (textBuffer) {
554
+ if (chunk.content!.length > 0) {
555
+ textBuffer.controller.enqueue(chunk);
556
+ }
557
+ }
558
+ }
559
+
560
+ private handleStreamTrailer(trailer: DataStream_Trailer) {
561
+ const streamId = trailer.streamId!;
562
+ const fileBuffer = this.byteStreamControllers.get(streamId);
563
+ if (fileBuffer) {
564
+ fileBuffer.controller.close();
565
+ this.byteStreamControllers.delete(streamId);
566
+ }
567
+ const textBuffer = this.textStreamControllers.get(streamId);
568
+ if (textBuffer) {
569
+ textBuffer.controller.close();
570
+ this.byteStreamControllers.delete(streamId);
571
+ }
572
+ }
393
573
  }
394
574
 
395
575
  export class ConnectError extends Error {
@@ -428,6 +608,7 @@ export type RoomCallbacks = {
428
608
  trackUnmuted: (publication: TrackPublication, participant: Participant) => void;
429
609
  activeSpeakersChanged: (speakers: Participant[]) => void;
430
610
  roomMetadataChanged: (metadata: string) => void;
611
+ roomInfoUpdated: (info: RoomInfo) => void;
431
612
  participantMetadataChanged: (metadata: string | undefined, participant: Participant) => void;
432
613
  participantNameChanged: (name: string, participant: Participant) => void;
433
614
  participantAttributesChanged: (
@@ -449,6 +630,7 @@ export type RoomCallbacks = {
449
630
  disconnected: (reason: DisconnectReason) => void;
450
631
  reconnecting: () => void;
451
632
  reconnected: () => void;
633
+ roomSidChanged: (sid: string) => void;
452
634
  };
453
635
 
454
636
  export enum RoomEvent {
@@ -466,6 +648,7 @@ export enum RoomEvent {
466
648
  TrackUnmuted = 'trackUnmuted',
467
649
  ActiveSpeakersChanged = 'activeSpeakersChanged',
468
650
  RoomMetadataChanged = 'roomMetadataChanged',
651
+ RoomSidChanged = 'roomSidChanged',
469
652
  ParticipantMetadataChanged = 'participantMetadataChanged',
470
653
  ParticipantNameChanged = 'participantNameChanged',
471
654
  ParticipantAttributesChanged = 'participantAttributesChanged',
package/src/utils.ts ADDED
@@ -0,0 +1,33 @@
1
+ // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ /** convert bigints to numbers preserving undefined values */
6
+ export function bigIntToNumber<T extends bigint | undefined>(
7
+ value: T,
8
+ ): T extends bigint ? number : undefined {
9
+ return (value !== undefined ? Number(value) : undefined) as T extends bigint ? number : undefined;
10
+ }
11
+
12
+ /** convert numbers to bigints preserving undefined values */
13
+ export function numberToBigInt<T extends number | undefined>(
14
+ value: T,
15
+ ): T extends number ? bigint : undefined {
16
+ return (value !== undefined ? BigInt(value) : undefined) as T extends number ? bigint : undefined;
17
+ }
18
+
19
+ export function splitUtf8(s: string, n: number): string[] {
20
+ // adapted from https://stackoverflow.com/a/6043797
21
+ const result: string[] = [];
22
+ while (s.length > n) {
23
+ let k = n;
24
+ // Move back to find the start of a UTF-8 character
25
+ while ((s.charCodeAt(k) & 0xc0) === 0x80) {
26
+ k--;
27
+ }
28
+ result.push(s.slice(0, k));
29
+ s = s.slice(k);
30
+ }
31
+ result.push(s);
32
+ return result;
33
+ }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const SDK_VERSION = "0.13.2";
1
+ export const SDK_VERSION = "0.13.4";
@@ -46,6 +46,8 @@ export class VideoFrame {
46
46
  case VideoBufferType.RGB24:
47
47
  info.stride = this.width * 3;
48
48
  break;
49
+ default:
50
+ info.stride = 0;
49
51
  }
50
52
 
51
53
  info.components.push(...getPlaneInfos(this.dataPtr, this.type, this.width, this.height));