@livekit/rtc-node 0.13.24 → 0.13.26
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/audio_filter.cjs +2 -2
- package/dist/audio_filter.cjs.map +1 -1
- package/dist/audio_filter.js +1 -1
- package/dist/audio_filter.js.map +1 -1
- package/dist/audio_frame.cjs +9 -5
- package/dist/audio_frame.cjs.map +1 -1
- package/dist/audio_frame.d.cts +2 -7
- package/dist/audio_frame.d.ts +2 -7
- package/dist/audio_frame.d.ts.map +1 -1
- package/dist/audio_frame.js +8 -4
- package/dist/audio_frame.js.map +1 -1
- package/dist/audio_mixer.cjs +10 -4
- package/dist/audio_mixer.cjs.map +1 -1
- package/dist/audio_mixer.d.cts +4 -7
- package/dist/audio_mixer.d.ts +4 -7
- package/dist/audio_mixer.d.ts.map +1 -1
- package/dist/audio_mixer.js +10 -4
- package/dist/audio_mixer.js.map +1 -1
- package/dist/audio_resampler.cjs +20 -12
- package/dist/audio_resampler.cjs.map +1 -1
- package/dist/audio_resampler.d.cts +6 -6
- package/dist/audio_resampler.d.ts +6 -6
- package/dist/audio_resampler.d.ts.map +1 -1
- package/dist/audio_resampler.js +12 -4
- package/dist/audio_resampler.js.map +1 -1
- package/dist/audio_source.cjs +11 -7
- package/dist/audio_source.cjs.map +1 -1
- package/dist/audio_source.d.cts +1 -7
- package/dist/audio_source.d.ts +1 -7
- package/dist/audio_source.d.ts.map +1 -1
- package/dist/audio_source.js +8 -3
- package/dist/audio_source.js.map +1 -1
- package/dist/audio_stream.cjs +16 -6
- package/dist/audio_stream.cjs.map +1 -1
- package/dist/audio_stream.d.cts +1 -14
- package/dist/audio_stream.d.ts +1 -14
- package/dist/audio_stream.d.ts.map +1 -1
- package/dist/audio_stream.js +15 -5
- package/dist/audio_stream.js.map +1 -1
- package/dist/data_streams/index.d.cts +2 -10
- package/dist/data_streams/index.d.ts +2 -10
- package/dist/data_streams/stream_reader.cjs +8 -0
- package/dist/data_streams/stream_reader.cjs.map +1 -1
- package/dist/data_streams/stream_reader.d.cts +2 -10
- package/dist/data_streams/stream_reader.d.ts +2 -10
- package/dist/data_streams/stream_reader.d.ts.map +1 -1
- package/dist/data_streams/stream_reader.js +8 -0
- package/dist/data_streams/stream_reader.js.map +1 -1
- package/dist/data_streams/stream_writer.d.cts +2 -10
- package/dist/data_streams/stream_writer.d.ts +2 -10
- package/dist/data_streams/types.cjs.map +1 -1
- package/dist/data_streams/types.d.cts +2 -10
- package/dist/data_streams/types.d.ts +2 -10
- package/dist/data_streams/types.d.ts.map +1 -1
- package/dist/e2ee.cjs +21 -21
- package/dist/e2ee.cjs.map +1 -1
- package/dist/e2ee.d.cts +1 -2
- package/dist/e2ee.d.ts +1 -2
- package/dist/e2ee.d.ts.map +1 -1
- package/dist/e2ee.js +2 -2
- package/dist/e2ee.js.map +1 -1
- package/dist/ffi_client.cjs +30 -15
- package/dist/ffi_client.cjs.map +1 -1
- package/dist/ffi_client.d.cts +6 -17
- package/dist/ffi_client.d.ts +6 -17
- package/dist/ffi_client.d.ts.map +1 -1
- package/dist/ffi_client.js +24 -6
- package/dist/ffi_client.js.map +1 -1
- package/dist/frame_processor.cjs +13 -2
- package/dist/frame_processor.cjs.map +1 -1
- package/dist/frame_processor.d.cts +5 -8
- package/dist/frame_processor.d.ts +5 -8
- package/dist/frame_processor.d.ts.map +1 -1
- package/dist/frame_processor.js +10 -1
- package/dist/frame_processor.js.map +1 -1
- package/dist/index.cjs +25 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -16
- package/dist/index.d.ts +3 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -7
- package/dist/index.js.map +1 -1
- package/dist/participant.cjs +146 -101
- package/dist/participant.cjs.map +1 -1
- package/dist/participant.d.cts +4 -16
- package/dist/participant.d.ts +4 -16
- package/dist/participant.d.ts.map +1 -1
- package/dist/participant.js +120 -75
- package/dist/participant.js.map +1 -1
- package/dist/room.cjs +116 -58
- package/dist/room.cjs.map +1 -1
- package/dist/room.d.cts +6 -15
- package/dist/room.d.ts +6 -15
- package/dist/room.d.ts.map +1 -1
- package/dist/room.js +113 -55
- package/dist/room.js.map +1 -1
- package/dist/rpc.cjs.map +1 -1
- package/dist/rpc.d.cts +1 -2
- package/dist/rpc.d.ts +1 -2
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js.map +1 -1
- package/dist/{stream_reader-Ch2zvjU3.d.cts → stream_reader-CuG4b8Y-.d.cts} +1 -1
- package/dist/{stream_reader-DRyR29vo.d.ts → stream_reader-CuG4b8Y-.d.ts} +1 -1
- package/dist/track.cjs +3 -3
- package/dist/track.cjs.map +1 -1
- package/dist/track.d.cts +1 -14
- package/dist/track.d.ts +1 -14
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +1 -1
- package/dist/track.js.map +1 -1
- package/dist/track_publication.cjs +4 -4
- package/dist/track_publication.cjs.map +1 -1
- package/dist/track_publication.d.cts +1 -14
- package/dist/track_publication.d.ts +1 -14
- package/dist/track_publication.d.ts.map +1 -1
- package/dist/track_publication.js +2 -2
- package/dist/track_publication.js.map +1 -1
- 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 +42 -42
- package/dist/video_frame.cjs.map +1 -1
- package/dist/video_frame.d.cts +1 -6
- package/dist/video_frame.d.ts +1 -6
- package/dist/video_frame.d.ts.map +1 -1
- package/dist/video_frame.js +2 -2
- package/dist/video_frame.js.map +1 -1
- package/dist/video_source.cjs +5 -5
- package/dist/video_source.cjs.map +1 -1
- package/dist/video_source.d.cts +1 -14
- package/dist/video_source.d.ts +1 -14
- package/dist/video_source.d.ts.map +1 -1
- package/dist/video_source.js +2 -2
- package/dist/video_source.js.map +1 -1
- package/dist/video_stream.cjs +12 -4
- package/dist/video_stream.cjs.map +1 -1
- package/dist/video_stream.d.cts +1 -14
- package/dist/video_stream.d.ts +1 -14
- package/dist/video_stream.d.ts.map +1 -1
- package/dist/video_stream.js +10 -2
- package/dist/video_stream.js.map +1 -1
- package/package.json +7 -37
- package/src/audio_filter.ts +2 -2
- package/src/audio_frame.ts +17 -5
- package/src/audio_mixer.test.ts +102 -0
- package/src/audio_mixer.ts +10 -4
- package/src/audio_resampler.ts +14 -4
- package/src/audio_source.ts +13 -5
- package/src/audio_stream.ts +21 -6
- package/src/data_streams/stream_reader.ts +19 -1
- package/src/data_streams/types.ts +1 -1
- package/src/e2ee.ts +3 -3
- package/src/ffi_client.ts +33 -8
- package/src/frame_processor.ts +21 -0
- package/src/index.ts +6 -5
- package/src/participant.ts +117 -66
- package/src/room.ts +161 -69
- package/src/rpc.ts +1 -1
- package/src/tests/e2e.test.ts +179 -8
- package/src/track.ts +4 -4
- package/src/track_publication.ts +6 -6
- package/src/version.ts +1 -1
- package/src/video_frame.ts +3 -3
- package/src/video_source.ts +3 -3
- package/src/video_stream.ts +15 -3
- package/Cargo.toml +0 -25
- package/build.rs +0 -9
- package/dist/napi/native.cjs +0 -301
- package/dist/napi/native.d.cjs +0 -17
- package/dist/napi/native.d.cjs.map +0 -1
- package/dist/napi/native.d.d.cts +0 -21
- package/dist/napi/native.d.d.ts +0 -21
- package/dist/napi/native.d.js +0 -1
- package/dist/napi/native.d.js.map +0 -1
- package/dist/napi/native.d.ts +0 -19
- package/dist/napi/native.js +0 -15
- package/dist/native.cjs +0 -301
- package/dist/native.d.ts +0 -19
- package/dist/native.js +0 -15
- package/dist/proto/audio_frame_pb.cjs +0 -1357
- package/dist/proto/audio_frame_pb.cjs.map +0 -1
- package/dist/proto/audio_frame_pb.d.cts +0 -1183
- package/dist/proto/audio_frame_pb.d.ts +0 -1183
- package/dist/proto/audio_frame_pb.d.ts.map +0 -1
- package/dist/proto/audio_frame_pb.js +0 -1283
- package/dist/proto/audio_frame_pb.js.map +0 -1
- package/dist/proto/data_stream_pb.cjs +0 -1638
- package/dist/proto/data_stream_pb.cjs.map +0 -1
- package/dist/proto/data_stream_pb.d.cts +0 -1449
- package/dist/proto/data_stream_pb.d.ts +0 -1449
- package/dist/proto/data_stream_pb.d.ts.map +0 -1
- package/dist/proto/data_stream_pb.js +0 -1559
- package/dist/proto/data_stream_pb.js.map +0 -1
- package/dist/proto/e2ee_pb.cjs +0 -746
- package/dist/proto/e2ee_pb.cjs.map +0 -1
- package/dist/proto/e2ee_pb.d.cts +0 -653
- package/dist/proto/e2ee_pb.d.ts +0 -653
- package/dist/proto/e2ee_pb.d.ts.map +0 -1
- package/dist/proto/e2ee_pb.js +0 -696
- package/dist/proto/e2ee_pb.js.map +0 -1
- package/dist/proto/ffi_pb.cjs +0 -477
- package/dist/proto/ffi_pb.cjs.map +0 -1
- package/dist/proto/ffi_pb.d.cts +0 -1321
- package/dist/proto/ffi_pb.d.ts +0 -1321
- package/dist/proto/ffi_pb.d.ts.map +0 -1
- package/dist/proto/ffi_pb.js +0 -444
- package/dist/proto/ffi_pb.js.map +0 -1
- package/dist/proto/handle_pb.cjs +0 -53
- package/dist/proto/handle_pb.cjs.map +0 -1
- package/dist/proto/handle_pb.d.cts +0 -31
- package/dist/proto/handle_pb.d.ts +0 -31
- package/dist/proto/handle_pb.d.ts.map +0 -1
- package/dist/proto/handle_pb.js +0 -29
- package/dist/proto/handle_pb.js.map +0 -1
- package/dist/proto/participant_pb.cjs +0 -173
- package/dist/proto/participant_pb.cjs.map +0 -1
- package/dist/proto/participant_pb.d.cts +0 -218
- package/dist/proto/participant_pb.d.ts +0 -218
- package/dist/proto/participant_pb.d.ts.map +0 -1
- package/dist/proto/participant_pb.js +0 -145
- package/dist/proto/participant_pb.js.map +0 -1
- package/dist/proto/room_pb.cjs +0 -3374
- package/dist/proto/room_pb.cjs.map +0 -1
- package/dist/proto/room_pb.d.cts +0 -3028
- package/dist/proto/room_pb.d.ts +0 -3028
- package/dist/proto/room_pb.d.ts.map +0 -1
- package/dist/proto/room_pb.js +0 -3232
- package/dist/proto/room_pb.js.map +0 -1
- package/dist/proto/rpc_pb.cjs +0 -328
- package/dist/proto/rpc_pb.cjs.map +0 -1
- package/dist/proto/rpc_pb.d.cts +0 -267
- package/dist/proto/rpc_pb.d.ts +0 -267
- package/dist/proto/rpc_pb.d.ts.map +0 -1
- package/dist/proto/rpc_pb.js +0 -294
- package/dist/proto/rpc_pb.js.map +0 -1
- package/dist/proto/stats_pb.cjs +0 -1370
- package/dist/proto/stats_pb.cjs.map +0 -1
- package/dist/proto/stats_pb.d.cts +0 -1764
- package/dist/proto/stats_pb.d.ts +0 -1764
- package/dist/proto/stats_pb.d.ts.map +0 -1
- package/dist/proto/stats_pb.js +0 -1300
- package/dist/proto/stats_pb.js.map +0 -1
- package/dist/proto/track_pb.cjs +0 -630
- package/dist/proto/track_pb.cjs.map +0 -1
- package/dist/proto/track_pb.d.cts +0 -542
- package/dist/proto/track_pb.d.ts +0 -542
- package/dist/proto/track_pb.d.ts.map +0 -1
- package/dist/proto/track_pb.js +0 -584
- package/dist/proto/track_pb.js.map +0 -1
- package/dist/proto/track_publication_pb.cjs +0 -194
- package/dist/proto/track_publication_pb.cjs.map +0 -1
- package/dist/proto/track_publication_pb.d.cts +0 -135
- package/dist/proto/track_publication_pb.d.ts +0 -135
- package/dist/proto/track_publication_pb.d.ts.map +0 -1
- package/dist/proto/track_publication_pb.js +0 -164
- package/dist/proto/track_publication_pb.js.map +0 -1
- package/dist/proto/video_frame_pb.cjs +0 -723
- package/dist/proto/video_frame_pb.cjs.map +0 -1
- package/dist/proto/video_frame_pb.d.cts +0 -667
- package/dist/proto/video_frame_pb.d.ts +0 -667
- package/dist/proto/video_frame_pb.d.ts.map +0 -1
- package/dist/proto/video_frame_pb.js +0 -673
- package/dist/proto/video_frame_pb.js.map +0 -1
- package/src/lib.rs +0 -9
- package/src/napi/native.cjs +0 -301
- package/src/napi/native.d.ts +0 -19
- package/src/napi/native.js +0 -15
- package/src/nodejs.rs +0 -134
- package/src/proto/audio_frame_pb.ts +0 -2270
- package/src/proto/data_stream_pb.ts +0 -2674
- package/src/proto/e2ee_pb.ts +0 -1231
- package/src/proto/ffi_pb.ts +0 -1700
- package/src/proto/handle_pb.ts +0 -69
- package/src/proto/participant_pb.ts +0 -351
- package/src/proto/room_pb.ts +0 -5661
- package/src/proto/rpc_pb.ts +0 -539
- package/src/proto/stats_pb.ts +0 -3050
- package/src/proto/track_pb.ts +0 -1040
- package/src/proto/track_publication_pb.ts +0 -284
- package/src/proto/video_frame_pb.ts +0 -1236
package/src/tests/e2e.test.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { AccessToken } from 'livekit-server-sdk';
|
|
5
5
|
import { randomUUID } from 'node:crypto';
|
|
6
6
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
7
|
-
import { afterAll, describe, expect, it } from 'vitest';
|
|
7
|
+
import { afterAll, describe, expect, it as itRaw } from 'vitest';
|
|
8
8
|
import {
|
|
9
9
|
AudioFrame,
|
|
10
10
|
AudioSource,
|
|
@@ -20,6 +20,9 @@ import {
|
|
|
20
20
|
dispose,
|
|
21
21
|
} from '../index.js';
|
|
22
22
|
|
|
23
|
+
// use concurrent testing if available on the runner (currently not supported by bun's api)
|
|
24
|
+
const it = typeof itRaw.concurrent === 'function' ? itRaw.concurrent : itRaw;
|
|
25
|
+
|
|
23
26
|
const hasE2EEnv =
|
|
24
27
|
!!process.env.LIVEKIT_URL && !!process.env.LIVEKIT_API_KEY && !!process.env.LIVEKIT_API_SECRET;
|
|
25
28
|
const describeE2E = hasE2EEnv ? describe : describe.skip;
|
|
@@ -202,7 +205,7 @@ describeE2E('livekit-rtc e2e', () => {
|
|
|
202
205
|
await dispose();
|
|
203
206
|
});
|
|
204
207
|
|
|
205
|
-
it
|
|
208
|
+
it(
|
|
206
209
|
'connects to a room',
|
|
207
210
|
async () => {
|
|
208
211
|
const { roomName, rooms } = await connectTestRooms(1);
|
|
@@ -225,7 +228,7 @@ describeE2E('livekit-rtc e2e', () => {
|
|
|
225
228
|
testTimeoutMs,
|
|
226
229
|
);
|
|
227
230
|
|
|
228
|
-
it
|
|
231
|
+
it(
|
|
229
232
|
'connects multiple participants to the same room',
|
|
230
233
|
async () => {
|
|
231
234
|
const { roomName, rooms } = await connectTestRooms(2);
|
|
@@ -241,7 +244,7 @@ describeE2E('livekit-rtc e2e', () => {
|
|
|
241
244
|
testTimeoutMs,
|
|
242
245
|
);
|
|
243
246
|
|
|
244
|
-
it
|
|
247
|
+
it(
|
|
245
248
|
'emits participantDisconnected when a participant leaves',
|
|
246
249
|
async () => {
|
|
247
250
|
const { rooms } = await connectTestRooms(2);
|
|
@@ -267,7 +270,7 @@ describeE2E('livekit-rtc e2e', () => {
|
|
|
267
270
|
testTimeoutMs,
|
|
268
271
|
);
|
|
269
272
|
|
|
270
|
-
it
|
|
273
|
+
it(
|
|
271
274
|
'transfers audio between two participants (sine detection)',
|
|
272
275
|
async () => {
|
|
273
276
|
const cases = [
|
|
@@ -367,7 +370,7 @@ describeE2E('livekit-rtc e2e', () => {
|
|
|
367
370
|
testTimeoutMs * 2,
|
|
368
371
|
);
|
|
369
372
|
|
|
370
|
-
it
|
|
373
|
+
it(
|
|
371
374
|
'publishes and receives reliable data packets',
|
|
372
375
|
async () => {
|
|
373
376
|
const { rooms } = await connectTestRooms(2);
|
|
@@ -407,7 +410,7 @@ describeE2E('livekit-rtc e2e', () => {
|
|
|
407
410
|
testTimeoutMs,
|
|
408
411
|
);
|
|
409
412
|
|
|
410
|
-
it
|
|
413
|
+
it(
|
|
411
414
|
'sends and receives text and byte streams',
|
|
412
415
|
async () => {
|
|
413
416
|
const { rooms } = await connectTestRooms(2);
|
|
@@ -469,7 +472,7 @@ describeE2E('livekit-rtc e2e', () => {
|
|
|
469
472
|
testTimeoutMs,
|
|
470
473
|
);
|
|
471
474
|
|
|
472
|
-
it
|
|
475
|
+
it(
|
|
473
476
|
'invokes RPC methods and returns structured errors',
|
|
474
477
|
async () => {
|
|
475
478
|
const { rooms } = await connectTestRooms(2);
|
|
@@ -511,4 +514,172 @@ describeE2E('livekit-rtc e2e', () => {
|
|
|
511
514
|
},
|
|
512
515
|
testTimeoutMs * 2,
|
|
513
516
|
);
|
|
517
|
+
|
|
518
|
+
it(
|
|
519
|
+
'cleans up stream controllers when disconnecting during an active stream',
|
|
520
|
+
async () => {
|
|
521
|
+
const { rooms } = await connectTestRooms(2);
|
|
522
|
+
const [receivingRoom, sendingRoom] = rooms;
|
|
523
|
+
const topic = 'cleanup-stream-topic';
|
|
524
|
+
|
|
525
|
+
// Register a handler on the receiving side that will intentionally
|
|
526
|
+
// NOT fully consume the stream — simulating an abandoned transfer.
|
|
527
|
+
let readerReceived = false;
|
|
528
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
529
|
+
receivingRoom!.registerTextStreamHandler(topic, async (_reader, _sender) => {
|
|
530
|
+
readerReceived = true;
|
|
531
|
+
// Deliberately do not call reader.readAll() so the stream stays open
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// Start sending a text stream but don't close it
|
|
535
|
+
const writer = await sendingRoom!.localParticipant!.streamText({ topic });
|
|
536
|
+
await writer.write('partial data');
|
|
537
|
+
|
|
538
|
+
// Wait for the receiving side to get the stream header
|
|
539
|
+
await waitFor(() => readerReceived, {
|
|
540
|
+
timeoutMs: 5000,
|
|
541
|
+
debugName: 'text stream header received',
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// Disconnect the receiving room while the stream is still open.
|
|
545
|
+
// This should close the stream controller without throwing.
|
|
546
|
+
await receivingRoom!.disconnect();
|
|
547
|
+
|
|
548
|
+
// Also close the writer and disconnect the sender
|
|
549
|
+
await writer.close();
|
|
550
|
+
await sendingRoom!.disconnect();
|
|
551
|
+
|
|
552
|
+
// If we got here without hanging or throwing, the stream controller
|
|
553
|
+
// was properly cleaned up on disconnect.
|
|
554
|
+
},
|
|
555
|
+
testTimeoutMs,
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
it(
|
|
559
|
+
'cleans up track publications when a remote participant disconnects',
|
|
560
|
+
async () => {
|
|
561
|
+
const { rooms } = await connectTestRooms(2);
|
|
562
|
+
const [stayingRoom, leavingRoom] = rooms;
|
|
563
|
+
|
|
564
|
+
// Publish a track from the leaving participant so its track publication
|
|
565
|
+
// will need to be cleaned up on disconnect.
|
|
566
|
+
const source = new AudioSource(48_000, 1);
|
|
567
|
+
const track = LocalAudioTrack.createAudioTrack('cleanup-test', source);
|
|
568
|
+
const options = new TrackPublishOptions();
|
|
569
|
+
options.source = TrackSource.SOURCE_MICROPHONE;
|
|
570
|
+
await leavingRoom!.localParticipant!.publishTrack(track, options);
|
|
571
|
+
|
|
572
|
+
// Wait for the staying room to see the track subscription
|
|
573
|
+
await waitFor(
|
|
574
|
+
() => {
|
|
575
|
+
const remote = stayingRoom!.remoteParticipants.get(
|
|
576
|
+
leavingRoom!.localParticipant!.identity,
|
|
577
|
+
);
|
|
578
|
+
return remote !== undefined && remote.trackPublications.size > 0;
|
|
579
|
+
},
|
|
580
|
+
{ timeoutMs: 5000, debugName: 'track publication visible' },
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
// Capture a reference to the remote participant before disconnect
|
|
584
|
+
const remoteParticipant = stayingRoom!.remoteParticipants.get(
|
|
585
|
+
leavingRoom!.localParticipant!.identity,
|
|
586
|
+
)!;
|
|
587
|
+
expect(remoteParticipant.trackPublications.size).toBeGreaterThan(0);
|
|
588
|
+
|
|
589
|
+
// Listen for the disconnect event
|
|
590
|
+
const disconnected = waitForRoomEvent(
|
|
591
|
+
stayingRoom!,
|
|
592
|
+
RoomEvent.ParticipantDisconnected,
|
|
593
|
+
testTimeoutMs,
|
|
594
|
+
(p: { identity: string }) => p.identity,
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
await leavingRoom!.disconnect();
|
|
598
|
+
await disconnected;
|
|
599
|
+
|
|
600
|
+
// trackUnpublished events fire before participantDisconnected, so
|
|
601
|
+
// by this point all publications should already be removed and disposed.
|
|
602
|
+
expect(remoteParticipant.trackPublications.size).toBe(0);
|
|
603
|
+
expect(stayingRoom!.remoteParticipants.has(remoteParticipant.identity)).toBe(false);
|
|
604
|
+
|
|
605
|
+
await source.close();
|
|
606
|
+
await stayingRoom!.disconnect();
|
|
607
|
+
},
|
|
608
|
+
testTimeoutMs,
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
it(
|
|
612
|
+
'cleans up resources when multiple participants disconnect simultaneously',
|
|
613
|
+
async () => {
|
|
614
|
+
// Connect 4 participants to stress-test concurrent disconnection cleanup
|
|
615
|
+
const { rooms } = await connectTestRooms(4);
|
|
616
|
+
|
|
617
|
+
// Publish a track from each participant to create track publications
|
|
618
|
+
const sources: AudioSource[] = [];
|
|
619
|
+
for (const room of rooms) {
|
|
620
|
+
const source = new AudioSource(48_000, 1);
|
|
621
|
+
sources.push(source);
|
|
622
|
+
const track = LocalAudioTrack.createAudioTrack('multi-cleanup', source);
|
|
623
|
+
const options = new TrackPublishOptions();
|
|
624
|
+
options.source = TrackSource.SOURCE_MICROPHONE;
|
|
625
|
+
await room.localParticipant!.publishTrack(track, options);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Wait for all participants to see each other's tracks
|
|
629
|
+
await waitFor(
|
|
630
|
+
() =>
|
|
631
|
+
rooms.every(
|
|
632
|
+
(r) =>
|
|
633
|
+
r.remoteParticipants.size === 3 &&
|
|
634
|
+
[...r.remoteParticipants.values()].every((p) => p.trackPublications.size > 0),
|
|
635
|
+
),
|
|
636
|
+
{ timeoutMs: 5000, debugName: 'all tracks visible' },
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
// Register listeners before disconnecting so we can verify both
|
|
640
|
+
// RoomEvent.Disconnected and RoomEvent.ConnectionStateChanged fire
|
|
641
|
+
// for every room, even when disconnects race.
|
|
642
|
+
const disconnectedEvents = rooms.map((r) =>
|
|
643
|
+
waitForRoomEvent(r, RoomEvent.Disconnected, 3_000, (reason) => reason),
|
|
644
|
+
);
|
|
645
|
+
const connectionStateEvents = rooms.map((r) =>
|
|
646
|
+
waitForRoomEvent(r, RoomEvent.ConnectionStateChanged, 3_000, (state) => state),
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
// Disconnect all participants simultaneously
|
|
650
|
+
await Promise.all([...rooms.map((r) => r.disconnect()), ...sources.map((s) => s.close())]);
|
|
651
|
+
|
|
652
|
+
await Promise.all(disconnectedEvents);
|
|
653
|
+
const observedStates = await Promise.all(connectionStateEvents);
|
|
654
|
+
|
|
655
|
+
// Verify all rooms are disconnected and remote participant maps are empty
|
|
656
|
+
for (const room of rooms) {
|
|
657
|
+
expect(room.isConnected).toBe(false);
|
|
658
|
+
}
|
|
659
|
+
for (const state of observedStates) {
|
|
660
|
+
expect(state).toBe(ConnectionState.CONN_DISCONNECTED);
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
testTimeoutMs * 2,
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
it(
|
|
667
|
+
'concurrent getSid() calls share a single listener and resolve consistently',
|
|
668
|
+
async () => {
|
|
669
|
+
const { rooms } = await connectTestRooms(1);
|
|
670
|
+
const room = rooms[0]!;
|
|
671
|
+
|
|
672
|
+
// Fire multiple concurrent getSid() calls — they should all resolve
|
|
673
|
+
// to the same SID without leaking event listeners.
|
|
674
|
+
const results = await Promise.all([room.getSid(), room.getSid(), room.getSid()]);
|
|
675
|
+
|
|
676
|
+
// All calls should return the same non-empty SID
|
|
677
|
+
expect(results[0]).toBeTruthy();
|
|
678
|
+
expect(results[1]).toBe(results[0]);
|
|
679
|
+
expect(results[2]).toBe(results[0]);
|
|
680
|
+
|
|
681
|
+
await room.disconnect();
|
|
682
|
+
},
|
|
683
|
+
testTimeoutMs,
|
|
684
|
+
);
|
|
514
685
|
});
|
package/src/track.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2024 LiveKit, Inc.
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
import type { AudioSource } from './audio_source.js';
|
|
5
|
-
import { FfiClient, FfiHandle } from './ffi_client.js';
|
|
6
4
|
import type {
|
|
7
5
|
CreateAudioTrackResponse,
|
|
8
6
|
CreateVideoTrackResponse,
|
|
@@ -10,8 +8,10 @@ import type {
|
|
|
10
8
|
StreamState,
|
|
11
9
|
TrackInfo,
|
|
12
10
|
TrackKind,
|
|
13
|
-
} from '
|
|
14
|
-
import { CreateAudioTrackRequest, CreateVideoTrackRequest } from '
|
|
11
|
+
} from '@livekit/rtc-ffi-bindings';
|
|
12
|
+
import { CreateAudioTrackRequest, CreateVideoTrackRequest } from '@livekit/rtc-ffi-bindings';
|
|
13
|
+
import type { AudioSource } from './audio_source.js';
|
|
14
|
+
import { FfiClient, FfiHandle } from './ffi_client.js';
|
|
15
15
|
import type { VideoSource } from './video_source.js';
|
|
16
16
|
|
|
17
17
|
export abstract class Track {
|
package/src/track_publication.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2024 LiveKit, Inc.
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import type { SetSubscribedResponse } from './proto/room_pb.js';
|
|
8
|
-
import { SetSubscribedRequest } from './proto/room_pb.js';
|
|
4
|
+
import type { EncryptionType } from '@livekit/rtc-ffi-bindings';
|
|
5
|
+
import type { SetSubscribedResponse } from '@livekit/rtc-ffi-bindings';
|
|
6
|
+
import { SetSubscribedRequest } from '@livekit/rtc-ffi-bindings';
|
|
9
7
|
import type {
|
|
10
8
|
OwnedTrackPublication,
|
|
11
9
|
TrackKind,
|
|
12
10
|
TrackPublicationInfo,
|
|
13
11
|
TrackSource,
|
|
14
|
-
} from '
|
|
12
|
+
} from '@livekit/rtc-ffi-bindings';
|
|
13
|
+
import { FfiHandle } from '@livekit/rtc-ffi-bindings';
|
|
14
|
+
import { FfiClient } from './ffi_client.js';
|
|
15
15
|
import type { Track } from './track.js';
|
|
16
16
|
|
|
17
17
|
export abstract class TrackPublication {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = "0.13.
|
|
1
|
+
export const SDK_VERSION = "0.13.26";
|
package/src/video_frame.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2024 LiveKit, Inc.
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
import {
|
|
5
|
-
import type { OwnedVideoBuffer, VideoConvertResponse } from './proto/video_frame_pb.js';
|
|
4
|
+
import type { OwnedVideoBuffer, VideoConvertResponse } from '@livekit/rtc-ffi-bindings';
|
|
6
5
|
import {
|
|
7
6
|
VideoBufferInfo,
|
|
8
7
|
VideoBufferInfo_ComponentInfo,
|
|
9
8
|
VideoBufferType,
|
|
10
|
-
} from '
|
|
9
|
+
} from '@livekit/rtc-ffi-bindings';
|
|
10
|
+
import { FfiClient, FfiHandle, FfiRequest } from './ffi_client.js';
|
|
11
11
|
|
|
12
12
|
export class VideoFrame {
|
|
13
13
|
data: Uint8Array;
|
package/src/video_source.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2024 LiveKit, Inc.
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
import { FfiClient, FfiHandle } from './ffi_client.js';
|
|
5
4
|
import type {
|
|
6
5
|
CaptureVideoFrameResponse,
|
|
7
6
|
NewVideoSourceResponse,
|
|
8
7
|
VideoSourceInfo,
|
|
9
|
-
} from '
|
|
8
|
+
} from '@livekit/rtc-ffi-bindings';
|
|
10
9
|
import {
|
|
11
10
|
CaptureVideoFrameRequest,
|
|
12
11
|
NewVideoSourceRequest,
|
|
13
12
|
VideoRotation,
|
|
14
13
|
VideoSourceType,
|
|
15
|
-
} from '
|
|
14
|
+
} from '@livekit/rtc-ffi-bindings';
|
|
15
|
+
import { FfiClient, FfiHandle } from './ffi_client.js';
|
|
16
16
|
import type { VideoFrame } from './video_frame.js';
|
|
17
17
|
|
|
18
18
|
export class VideoSource {
|
package/src/video_stream.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2024 LiveKit, Inc.
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import type { NewVideoStreamResponse, VideoRotation } from '@livekit/rtc-ffi-bindings';
|
|
5
|
+
import { NewVideoStreamRequest, VideoStreamType } from '@livekit/rtc-ffi-bindings';
|
|
4
6
|
import type { UnderlyingSource } from 'node:stream/web';
|
|
5
7
|
import type { FfiEvent } from './ffi_client.js';
|
|
6
8
|
import { FfiClient, FfiClientEvent, FfiHandle } from './ffi_client.js';
|
|
7
|
-
import type { NewVideoStreamResponse, VideoRotation } from './proto/video_frame_pb.js';
|
|
8
|
-
import { NewVideoStreamRequest, VideoStreamType } from './proto/video_frame_pb.js';
|
|
9
9
|
import type { Track } from './track.js';
|
|
10
10
|
import { VideoFrame } from './video_frame.js';
|
|
11
11
|
|
|
@@ -18,6 +18,7 @@ export type VideoFrameEvent = {
|
|
|
18
18
|
class VideoStreamSource implements UnderlyingSource<VideoFrameEvent> {
|
|
19
19
|
private controller?: ReadableStreamDefaultController<VideoFrameEvent>;
|
|
20
20
|
private ffiHandle: FfiHandle;
|
|
21
|
+
private disposed = false;
|
|
21
22
|
|
|
22
23
|
constructor(track: Track) {
|
|
23
24
|
const req = new NewVideoStreamRequest({
|
|
@@ -65,6 +66,14 @@ class VideoStreamSource implements UnderlyingSource<VideoFrameEvent> {
|
|
|
65
66
|
case 'eos':
|
|
66
67
|
FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);
|
|
67
68
|
this.controller.close();
|
|
69
|
+
// Dispose the native handle so the FD is released on stream end,
|
|
70
|
+
// not just when cancel() is called explicitly by the consumer.
|
|
71
|
+
// Guard against double-dispose if cancel() is called after EOS
|
|
72
|
+
// while buffered frames are still in the ReadableStream queue.
|
|
73
|
+
if (!this.disposed) {
|
|
74
|
+
this.disposed = true;
|
|
75
|
+
this.ffiHandle.dispose();
|
|
76
|
+
}
|
|
68
77
|
break;
|
|
69
78
|
}
|
|
70
79
|
};
|
|
@@ -75,7 +84,10 @@ class VideoStreamSource implements UnderlyingSource<VideoFrameEvent> {
|
|
|
75
84
|
|
|
76
85
|
cancel() {
|
|
77
86
|
FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);
|
|
78
|
-
this.
|
|
87
|
+
if (!this.disposed) {
|
|
88
|
+
this.disposed = true;
|
|
89
|
+
this.ffiHandle.dispose();
|
|
90
|
+
}
|
|
79
91
|
}
|
|
80
92
|
}
|
|
81
93
|
|
package/Cargo.toml
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
[package]
|
|
2
|
-
edition = "2021"
|
|
3
|
-
name = "rtc-node"
|
|
4
|
-
version = "0.0.1"
|
|
5
|
-
|
|
6
|
-
[lib]
|
|
7
|
-
crate-type = ["cdylib"]
|
|
8
|
-
|
|
9
|
-
[dependencies]
|
|
10
|
-
napi = { version = "2.12.2", default-features = false, features = [
|
|
11
|
-
"async",
|
|
12
|
-
"napi6",
|
|
13
|
-
] }
|
|
14
|
-
napi-derive = "2.12.2"
|
|
15
|
-
livekit-ffi = { path = "./rust-sdks/livekit-ffi" }
|
|
16
|
-
prost = "0.14"
|
|
17
|
-
prost-types = "0.14"
|
|
18
|
-
log = "0.4.20"
|
|
19
|
-
tokio = { version = "1.37.0", features = ["full"] }
|
|
20
|
-
|
|
21
|
-
[build-dependencies]
|
|
22
|
-
napi-build = "2.0.1"
|
|
23
|
-
|
|
24
|
-
[profile.release]
|
|
25
|
-
lto = true
|