@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.
- package/dist/data_streams/index.cjs +25 -0
- package/dist/data_streams/index.cjs.map +1 -0
- package/dist/data_streams/index.d.cts +11 -0
- package/dist/data_streams/index.d.ts +11 -0
- package/dist/data_streams/index.d.ts.map +1 -0
- package/dist/data_streams/index.js +3 -0
- package/dist/data_streams/index.js.map +1 -0
- package/dist/data_streams/stream_reader.cjs +141 -0
- package/dist/data_streams/stream_reader.cjs.map +1 -0
- package/dist/data_streams/stream_reader.d.cts +10 -0
- package/dist/data_streams/stream_reader.d.ts +10 -0
- package/dist/data_streams/stream_reader.d.ts.map +1 -0
- package/dist/data_streams/stream_reader.js +116 -0
- package/dist/data_streams/stream_reader.js.map +1 -0
- package/dist/data_streams/stream_writer.cjs +51 -0
- package/dist/data_streams/stream_writer.cjs.map +1 -0
- package/dist/data_streams/stream_writer.d.cts +26 -0
- package/dist/data_streams/stream_writer.d.ts +26 -0
- package/dist/data_streams/stream_writer.d.ts.map +1 -0
- package/dist/data_streams/stream_writer.js +26 -0
- package/dist/data_streams/stream_writer.js.map +1 -0
- package/dist/data_streams/types.cjs +17 -0
- package/dist/data_streams/types.cjs.map +1 -0
- package/dist/data_streams/types.d.cts +10 -0
- package/dist/data_streams/types.d.ts +10 -0
- package/dist/data_streams/types.d.ts.map +1 -0
- package/dist/data_streams/types.js +1 -0
- package/dist/data_streams/types.js.map +1 -0
- package/dist/index.cjs +7 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/log.cjs +42 -0
- package/dist/log.cjs.map +1 -0
- package/dist/log.d.cts +7 -0
- package/dist/log.d.ts +7 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +18 -0
- package/dist/log.js.map +1 -0
- package/dist/napi/native.d.cjs.map +1 -1
- package/dist/napi/native.d.ts +5 -5
- package/dist/native.d.ts +5 -5
- package/dist/participant.cjs +249 -16
- package/dist/participant.cjs.map +1 -1
- package/dist/participant.d.cts +40 -6
- package/dist/participant.d.ts +40 -6
- package/dist/participant.d.ts.map +1 -1
- package/dist/participant.js +252 -5
- package/dist/participant.js.map +1 -1
- package/dist/proto/ffi_pb.cjs +14 -3
- package/dist/proto/ffi_pb.cjs.map +1 -1
- package/dist/proto/ffi_pb.d.cts +81 -7
- package/dist/proto/ffi_pb.d.ts +81 -7
- package/dist/proto/ffi_pb.d.ts.map +1 -1
- package/dist/proto/ffi_pb.js +15 -4
- package/dist/proto/ffi_pb.js.map +1 -1
- package/dist/proto/room_pb.cjs +488 -30
- package/dist/proto/room_pb.cjs.map +1 -1
- package/dist/proto/room_pb.d.cts +412 -50
- package/dist/proto/room_pb.d.ts +412 -50
- package/dist/proto/room_pb.d.ts.map +1 -1
- package/dist/proto/room_pb.js +471 -29
- package/dist/proto/room_pb.js.map +1 -1
- package/dist/proto/video_frame_pb.cjs.map +1 -1
- package/dist/proto/video_frame_pb.d.cts +3 -0
- package/dist/proto/video_frame_pb.d.ts +3 -0
- package/dist/proto/video_frame_pb.d.ts.map +1 -1
- package/dist/proto/video_frame_pb.js.map +1 -1
- package/dist/room.cjs +121 -0
- package/dist/room.cjs.map +1 -1
- package/dist/room.d.cts +17 -2
- package/dist/room.d.ts +17 -2
- package/dist/room.d.ts.map +1 -1
- package/dist/room.js +122 -0
- package/dist/room.js.map +1 -1
- package/dist/types-B28ZM-Ci.d.cts +88 -0
- package/dist/types-BDnbvXeD.d.ts +88 -0
- package/dist/utils.cjs +61 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.cts +7 -0
- package/dist/utils.d.ts +7 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +35 -0
- package/dist/utils.js.map +1 -0
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.cts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/video_frame.cjs +2 -0
- package/dist/video_frame.cjs.map +1 -1
- package/dist/video_frame.d.ts.map +1 -1
- package/dist/video_frame.js +2 -0
- package/dist/video_frame.js.map +1 -1
- package/package.json +9 -7
- package/src/data_streams/index.ts +7 -0
- package/src/data_streams/stream_reader.ts +159 -0
- package/src/data_streams/stream_writer.ts +36 -0
- package/src/data_streams/types.ts +55 -0
- package/src/index.ts +1 -0
- package/src/log.ts +25 -0
- package/src/napi/native.d.ts +5 -5
- package/src/participant.ts +337 -29
- package/src/proto/ffi_pb.ts +86 -1
- package/src/proto/room_pb.ts +788 -68
- package/src/proto/video_frame_pb.ts +3 -0
- package/src/room.ts +141 -0
- package/src/utils.test.ts +51 -0
- package/src/utils.ts +42 -0
- package/src/version.ts +1 -1
- 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.
|
|
1
|
+
export const SDK_VERSION = "0.13.5";
|
package/src/video_frame.ts
CHANGED