@livekit/rtc-node 0.13.3 → 0.13.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.
Files changed (115) 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 +141 -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 +116 -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 +249 -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 +252 -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 +121 -0
  73. package/dist/room.cjs.map +1 -1
  74. package/dist/room.d.cts +17 -2
  75. package/dist/room.d.ts +17 -2
  76. package/dist/room.d.ts.map +1 -1
  77. package/dist/room.js +122 -0
  78. package/dist/room.js.map +1 -1
  79. package/dist/types-B28ZM-Ci.d.cts +88 -0
  80. package/dist/types-BDnbvXeD.d.ts +88 -0
  81. package/dist/utils.cjs +61 -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 +35 -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 +159 -0
  102. package/src/data_streams/stream_writer.ts +36 -0
  103. package/src/data_streams/types.ts +55 -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 +337 -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 +141 -0
  112. package/src/utils.test.ts +51 -0
  113. package/src/utils.ts +42 -0
  114. package/src/version.ts +1 -1
  115. 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
 
@@ -175,6 +194,28 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
175
194
  this.removeAllListeners();
176
195
  }
177
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
+
178
219
  private onFfiEvent = (ffiEvent: FfiEvent) => {
179
220
  if (!this.localParticipant || !this.ffiHandle || !this.info) {
180
221
  throw TypeError('cannot handle ffi events before connectCallback');
@@ -373,6 +414,12 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
373
414
  this.emit(RoomEvent.Reconnected);
374
415
  } else if (ev.case == 'roomSidChanged') {
375
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);
376
423
  }
377
424
  };
378
425
 
@@ -429,6 +476,100 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
429
476
  this.remoteParticipants.set(ownedInfo.info!.identity!, participant);
430
477
  return participant;
431
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
+ }
432
573
  }
433
574
 
434
575
  export class ConnectError extends Error {
@@ -0,0 +1,51 @@
1
+ // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ import { describe, expect, it } from 'vitest';
5
+ import { splitUtf8 } from './utils.js';
6
+
7
+ describe('splitUtf8', () => {
8
+ it('splits a string into chunks of the given size', () => {
9
+ expect(splitUtf8('hello world', 5)).toEqual([
10
+ new TextEncoder().encode('hello'),
11
+ new TextEncoder().encode(' worl'),
12
+ new TextEncoder().encode('d'),
13
+ ]);
14
+ });
15
+
16
+ it('splits a string with special characters into chunks of the given size', () => {
17
+ expect(splitUtf8('héllo wörld', 5)).toEqual([
18
+ new TextEncoder().encode('héll'),
19
+ new TextEncoder().encode('o wö'),
20
+ new TextEncoder().encode('rld'),
21
+ ]);
22
+ });
23
+
24
+ it('splits a string with multi-byte utf8 characters correctly', () => {
25
+ expect(splitUtf8('こんにちは世界', 5)).toEqual([
26
+ new TextEncoder().encode('こ'),
27
+ new TextEncoder().encode('ん'),
28
+ new TextEncoder().encode('に'),
29
+ new TextEncoder().encode('ち'),
30
+ new TextEncoder().encode('は'),
31
+ new TextEncoder().encode('世'),
32
+ new TextEncoder().encode('界'),
33
+ ]);
34
+ });
35
+
36
+ it('handles a string with a single multi-byte utf8 character', () => {
37
+ expect(splitUtf8('😊', 5)).toEqual([new TextEncoder().encode('😊')]);
38
+ });
39
+
40
+ it('handles a string with mixed single and multi-byte utf8 characters', () => {
41
+ expect(splitUtf8('a😊b', 4)).toEqual([
42
+ new TextEncoder().encode('a'),
43
+ new TextEncoder().encode('😊'),
44
+ new TextEncoder().encode('b'),
45
+ ]);
46
+ });
47
+
48
+ it('handles an empty string', () => {
49
+ expect(splitUtf8('', 5)).toEqual([]);
50
+ });
51
+ });
package/src/utils.ts ADDED
@@ -0,0 +1,42 @@
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): Uint8Array[] {
20
+ if (n < 4) {
21
+ throw new Error('n must be at least 4 due to utf8 encoding rules');
22
+ }
23
+ // adapted from https://stackoverflow.com/a/6043797
24
+ const result: Uint8Array[] = [];
25
+ let encoded = new TextEncoder().encode(s);
26
+ while (encoded.length > n) {
27
+ let k = n;
28
+ while (k > 0) {
29
+ const byte = encoded[k];
30
+ if (byte !== undefined && (byte & 0xc0) !== 0x80) {
31
+ break;
32
+ }
33
+ k--;
34
+ }
35
+ result.push(encoded.slice(0, k));
36
+ encoded = encoded.slice(k);
37
+ }
38
+ if (encoded.length > 0) {
39
+ result.push(encoded);
40
+ }
41
+ return result;
42
+ }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const SDK_VERSION = "0.13.3";
1
+ export const SDK_VERSION = "0.13.5";
@@ -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));