@livekit/rtc-node 0.13.28 → 0.13.30
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_stream-B4hGVcKz.d.cts +498 -0
- package/dist/audio_stream-DEG1JKge.d.ts +498 -0
- package/dist/audio_stream.cjs +21 -6
- package/dist/audio_stream.cjs.map +1 -1
- package/dist/audio_stream.d.cts +12 -22
- package/dist/audio_stream.d.ts +12 -22
- package/dist/audio_stream.d.ts.map +1 -1
- package/dist/audio_stream.js +19 -5
- package/dist/audio_stream.js.map +1 -1
- package/dist/data_streams/index.d.cts +1 -1
- package/dist/data_streams/index.d.ts +1 -1
- package/dist/data_streams/stream_reader.d.cts +1 -1
- package/dist/data_streams/stream_reader.d.ts +1 -1
- package/dist/data_streams/stream_writer.d.cts +1 -1
- package/dist/data_streams/stream_writer.d.ts +1 -1
- package/dist/data_streams/types.d.cts +1 -1
- package/dist/data_streams/types.d.ts +1 -1
- package/dist/ffi_client.cjs +11 -7
- package/dist/ffi_client.cjs.map +1 -1
- package/dist/ffi_client.d.cts +2 -0
- package/dist/ffi_client.d.ts +2 -0
- package/dist/ffi_client.d.ts.map +1 -1
- package/dist/ffi_client.js +11 -7
- package/dist/ffi_client.js.map +1 -1
- package/dist/frame_processor.cjs +6 -0
- package/dist/frame_processor.cjs.map +1 -1
- package/dist/frame_processor.d.cts +2 -0
- package/dist/frame_processor.d.ts +2 -0
- package/dist/frame_processor.d.ts.map +1 -1
- package/dist/frame_processor.js +6 -0
- package/dist/frame_processor.js.map +1 -1
- package/dist/index.d.cts +4 -7
- package/dist/index.d.ts +4 -7
- package/dist/participant.cjs +6 -0
- package/dist/participant.cjs.map +1 -1
- package/dist/participant.d.cts +14 -153
- package/dist/participant.d.ts +14 -153
- package/dist/participant.d.ts.map +1 -1
- package/dist/participant.js +6 -0
- package/dist/participant.js.map +1 -1
- package/dist/room.cjs +63 -0
- package/dist/room.cjs.map +1 -1
- package/dist/room.d.cts +12 -219
- package/dist/room.d.ts +12 -219
- package/dist/room.d.ts.map +1 -1
- package/dist/room.js +63 -0
- package/dist/room.js.map +1 -1
- package/dist/track.cjs +123 -0
- package/dist/track.cjs.map +1 -1
- package/dist/track.d.cts +15 -42
- package/dist/track.d.ts +15 -42
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +123 -0
- package/dist/track.js.map +1 -1
- package/dist/track_publication.d.cts +14 -47
- package/dist/track_publication.d.ts +14 -47
- package/dist/{stream_reader-CuG4b8Y-.d.cts → types-_PdPVish.d.cts} +40 -40
- package/dist/{stream_reader-CuG4b8Y-.d.ts → types-_PdPVish.d.ts} +40 -40
- 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_stream.d.cts +12 -2
- package/dist/video_stream.d.ts +12 -2
- package/package.json +4 -4
- package/src/audio_stream.ts +28 -4
- package/src/audio_stream_room_lifecycle.test.ts +753 -0
- package/src/bun_runtime.test.ts +27 -0
- package/src/ffi_client.ts +6 -0
- package/src/frame_processor.ts +4 -0
- package/src/participant.ts +17 -0
- package/src/room.ts +89 -1
- package/src/track.ts +141 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 LiveKit, Inc.
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
// Guard: the "Bun" CI job runs vitest via `bun --bun run test` so test bodies
|
|
7
|
+
// execute on the bun runtime (with vitest still providing module mocking). The
|
|
8
|
+
// `--bun` flag is load-bearing — without it, bun honors vitest's
|
|
9
|
+
// `#!/usr/bin/env node` shebang and silently runs everything under node, making
|
|
10
|
+
// the bun job a meaningless duplicate of the node job.
|
|
11
|
+
//
|
|
12
|
+
// When EXPECT_BUN_RUNTIME=1 (set only by the bun CI step), assert that this test
|
|
13
|
+
// body is actually executing under bun. It runs through the exact same vitest
|
|
14
|
+
// path as the real suite, so it catches a regression where the runtime falls
|
|
15
|
+
// back to node. In every other run (the node CI job, local dev) the var is
|
|
16
|
+
// unset and this is skipped.
|
|
17
|
+
describe('bun runtime guard', () => {
|
|
18
|
+
const enforced = process.env.EXPECT_BUN_RUNTIME === '1';
|
|
19
|
+
|
|
20
|
+
it.skipIf(!enforced)('test bodies execute on the bun runtime', () => {
|
|
21
|
+
expect(
|
|
22
|
+
process.versions.bun,
|
|
23
|
+
'EXPECT_BUN_RUNTIME=1 but tests are not running under bun — `bun --bun run test` ' +
|
|
24
|
+
'fell back to node (check the --bun flag / invocation).',
|
|
25
|
+
).toBeTruthy();
|
|
26
|
+
});
|
|
27
|
+
});
|
package/src/ffi_client.ts
CHANGED
|
@@ -41,6 +41,8 @@ export class FfiClient extends (EventEmitter as new () => TypedEmitter<FfiClient
|
|
|
41
41
|
return globalThis._ffiClientInstance;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
private _nextRequestAsyncId = BigInt(0);
|
|
45
|
+
|
|
44
46
|
constructor() {
|
|
45
47
|
super();
|
|
46
48
|
this.setMaxListeners(0);
|
|
@@ -70,6 +72,10 @@ export class FfiClient extends (EventEmitter as new () => TypedEmitter<FfiClient
|
|
|
70
72
|
return livekitRetrievePtr(data);
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
getNextRequestAsyncId() {
|
|
76
|
+
return ++this._nextRequestAsyncId;
|
|
77
|
+
}
|
|
78
|
+
|
|
73
79
|
async waitFor<T>(
|
|
74
80
|
predicate: (ev: FfiEvent) => boolean,
|
|
75
81
|
options?: { signal?: AbortSignal },
|
package/src/frame_processor.ts
CHANGED
|
@@ -43,7 +43,11 @@ export abstract class FrameProcessor<Frame extends VideoFrame | AudioFrame> {
|
|
|
43
43
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
44
44
|
onStreamInfoUpdated(_info: FrameProcessorStreamInfo): void {}
|
|
45
45
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
46
|
+
onStreamInfoCleared(): void {}
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
46
48
|
onCredentialsUpdated(_credentials: FrameProcessorCredentials): void {}
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
50
|
+
onCredentialsCleared(): void {}
|
|
47
51
|
|
|
48
52
|
abstract process(frame: Frame): Frame;
|
|
49
53
|
abstract close(): void;
|
package/src/participant.ts
CHANGED
|
@@ -731,6 +731,14 @@ export class LocalParticipant extends Participant {
|
|
|
731
731
|
case 'publication':
|
|
732
732
|
const track_publication = new LocalTrackPublication(cb.message.value!);
|
|
733
733
|
track_publication.track = track;
|
|
734
|
+
// Stamp the server-assigned publication
|
|
735
|
+
// SID onto the track so track.sid == publication.sid. Both
|
|
736
|
+
// Track.pushProcessorMetadataToStream and the localTrackRepublished
|
|
737
|
+
// handler look up the local publication by this SID; without it they
|
|
738
|
+
// depend on createAudioTrack's provisional SID happening to match.
|
|
739
|
+
if (track.info && track_publication.sid) {
|
|
740
|
+
track.info.sid = track_publication.sid;
|
|
741
|
+
}
|
|
734
742
|
this.trackPublications.set(track_publication.sid!, track_publication);
|
|
735
743
|
|
|
736
744
|
return track_publication;
|
|
@@ -767,6 +775,15 @@ export class LocalParticipant extends Participant {
|
|
|
767
775
|
|
|
768
776
|
const pub = this.trackPublications.get(trackSid);
|
|
769
777
|
if (pub) {
|
|
778
|
+
// Clear the processor's room context here too: this path races the
|
|
779
|
+
// localTrackUnpublished room event, and whichever loses finds the
|
|
780
|
+
// publication already gone and skips its own setRoom(null). Calling it
|
|
781
|
+
// from both paths guarantees the processor is cleared (and the
|
|
782
|
+
// tokenRefreshed listener detached); setRoom(null) is idempotent, so a
|
|
783
|
+
// double-clear when this path wins is safe.
|
|
784
|
+
if (pub.track) {
|
|
785
|
+
pub.track.setRoom(null);
|
|
786
|
+
}
|
|
770
787
|
pub.track = undefined;
|
|
771
788
|
}
|
|
772
789
|
this.trackPublications.delete(trackSid);
|
package/src/room.ts
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
import { Mutex } from '@livekit/mutex';
|
|
5
5
|
import { EncryptionState, type EncryptionType } from '@livekit/rtc-ffi-bindings';
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
FfiEvent,
|
|
8
|
+
GetSessionStatsCallback,
|
|
9
|
+
GetSessionStatsResponse,
|
|
10
|
+
} from '@livekit/rtc-ffi-bindings';
|
|
7
11
|
import { DisconnectReason, type OwnedParticipant } from '@livekit/rtc-ffi-bindings';
|
|
8
12
|
import type {
|
|
9
13
|
DataStream_Trailer,
|
|
@@ -24,6 +28,7 @@ import {
|
|
|
24
28
|
RoomOptions as FfiRoomOptions,
|
|
25
29
|
type IceServer,
|
|
26
30
|
IceTransportType,
|
|
31
|
+
type ReadyForRoomEventResponse,
|
|
27
32
|
type RoomInfo,
|
|
28
33
|
type SimulateScenarioCallback,
|
|
29
34
|
type SimulateScenarioKind,
|
|
@@ -191,6 +196,36 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
|
|
|
191
196
|
return this.sidPromise;
|
|
192
197
|
}
|
|
193
198
|
|
|
199
|
+
async getRtcStats() {
|
|
200
|
+
if (!this.isConnected || !this.ffiHandle) {
|
|
201
|
+
throw new Error('getRtcStats requires a connected room');
|
|
202
|
+
}
|
|
203
|
+
// subscribe before issuing the request
|
|
204
|
+
const requestId = FfiClient.instance.getNextRequestAsyncId();
|
|
205
|
+
const cbPromise = FfiClient.instance.waitFor<GetSessionStatsCallback>(
|
|
206
|
+
(ev: FfiEvent) =>
|
|
207
|
+
ev.message.case === 'getSessionStats' && ev.message.value.asyncId === requestId,
|
|
208
|
+
{ signal: this.disconnectController.signal },
|
|
209
|
+
);
|
|
210
|
+
FfiClient.instance.request<GetSessionStatsResponse>({
|
|
211
|
+
message: {
|
|
212
|
+
case: 'getSessionStats',
|
|
213
|
+
value: {
|
|
214
|
+
roomHandle: this.ffiHandle.handle,
|
|
215
|
+
requestAsyncId: requestId,
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
const cb = await cbPromise;
|
|
220
|
+
if (cb.message.case === 'error') {
|
|
221
|
+
throw new Error(cb.message.value);
|
|
222
|
+
} else if (cb.message.case === 'result') {
|
|
223
|
+
return cb.message.value;
|
|
224
|
+
} else {
|
|
225
|
+
throw new Error('could not retrieve rtc stats');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
194
229
|
get numParticipants(): number {
|
|
195
230
|
return this.info?.numParticipants ?? 0;
|
|
196
231
|
}
|
|
@@ -292,6 +327,15 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
|
|
|
292
327
|
}
|
|
293
328
|
}
|
|
294
329
|
this.updateConnectionState(ConnectionState.CONN_CONNECTED);
|
|
330
|
+
// Release the FFI room event gate after our listener and local state are ready.
|
|
331
|
+
FfiClient.instance.request<ReadyForRoomEventResponse>({
|
|
332
|
+
message: {
|
|
333
|
+
case: 'readyForRoomEvent',
|
|
334
|
+
value: {
|
|
335
|
+
roomHandle: this.ffiHandle.handle,
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
});
|
|
295
339
|
break;
|
|
296
340
|
case 'error':
|
|
297
341
|
default:
|
|
@@ -400,6 +444,23 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
|
|
|
400
444
|
}
|
|
401
445
|
this.textStreamControllers.clear();
|
|
402
446
|
|
|
447
|
+
// Detach every track from this room so attached FrameProcessors receive
|
|
448
|
+
// onStreamInfoCleared/onCredentialsCleared and the tokenRefreshed listener
|
|
449
|
+
// is removed. Otherwise a server-initiated disconnect leaves processors with
|
|
450
|
+
// stale room context and the Room holding a strong ref to each Track's bound
|
|
451
|
+
// listener until GC. setRoom(null) is idempotent, so this is safe even if a
|
|
452
|
+
// track was already detached (e.g. via unsubscribe/unpublish).
|
|
453
|
+
if (this.localParticipant) {
|
|
454
|
+
for (const pub of this.localParticipant.trackPublications.values()) {
|
|
455
|
+
pub.track?.setRoom(null);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
for (const participant of this.remoteParticipants.values()) {
|
|
459
|
+
for (const pub of participant.trackPublications.values()) {
|
|
460
|
+
pub.track?.setRoom(null);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
403
464
|
// Clear sidPromise before removing listeners so that a reconnect
|
|
404
465
|
// doesn't return a stale, permanently-pending promise.
|
|
405
466
|
this.sidPromise = undefined;
|
|
@@ -513,11 +574,27 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
|
|
|
513
574
|
}
|
|
514
575
|
} else if (ev.case == 'localTrackPublished') {
|
|
515
576
|
const publication = this.localParticipant.trackPublications.get(ev.value.trackSid!);
|
|
577
|
+
if (publication?.track) {
|
|
578
|
+
publication.track.setRoom(this);
|
|
579
|
+
}
|
|
516
580
|
this.emit(RoomEvent.LocalTrackPublished, publication!, this.localParticipant);
|
|
517
581
|
} else if (ev.case == 'localTrackUnpublished') {
|
|
518
582
|
const publication = this.localParticipant.trackPublications.get(ev.value.publicationSid!);
|
|
583
|
+
const track = publication?.track;
|
|
584
|
+
if (track) {
|
|
585
|
+
track.setRoom(null);
|
|
586
|
+
}
|
|
519
587
|
this.localParticipant.trackPublications.delete(ev.value.publicationSid!);
|
|
588
|
+
// Emit while `publication.track` is still set, preserving the pre-existing
|
|
589
|
+
// payload for callbacks. The handler is synchronous, so nulling the track
|
|
590
|
+
// right after still completes before any other turn can observe it.
|
|
520
591
|
this.emit(RoomEvent.LocalTrackUnpublished, publication!, this.localParticipant!);
|
|
592
|
+
// Mirror trackUnsubscribed: drop the publication's track reference. This
|
|
593
|
+
// also makes unpublishTrack's own setRoom(null) a no-op when it loses the
|
|
594
|
+
// race (its `pub.track` guard short-circuits), avoiding a redundant clear.
|
|
595
|
+
if (track && publication) {
|
|
596
|
+
publication.track = undefined;
|
|
597
|
+
}
|
|
521
598
|
} else if ((ev.case as string) == 'localTrackRepublished') {
|
|
522
599
|
const value = (ev as any).value;
|
|
523
600
|
const previousSid: string = value.previousSid!;
|
|
@@ -527,6 +604,15 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
|
|
|
527
604
|
publication.updateInfo(newInfo);
|
|
528
605
|
this.localParticipant.trackPublications.delete(previousSid);
|
|
529
606
|
this.localParticipant.trackPublications.set(publication.sid!, publication);
|
|
607
|
+
if (publication.track?.info) {
|
|
608
|
+
// Keep the local-track invariant (track.sid == publication.sid, set at
|
|
609
|
+
// publishTrack) intact across republish, then re-push metadata so any
|
|
610
|
+
// attached FrameProcessor learns the new publication SID / credentials.
|
|
611
|
+
// setRoom with the same room is a no-op for the tokenRefreshed listener
|
|
612
|
+
// but re-fans the metadata to every registered AudioStream.
|
|
613
|
+
publication.track.info.sid = publication.sid;
|
|
614
|
+
publication.track.setRoom(this);
|
|
615
|
+
}
|
|
530
616
|
this.emit(RoomEvent.LocalTrackRepublished, publication, previousSid, this.localParticipant);
|
|
531
617
|
} else {
|
|
532
618
|
log.warn(`RoomEvent.LocalTrackRepublished: previous publication not found: ${previousSid}`);
|
|
@@ -576,6 +662,7 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
|
|
|
576
662
|
} else if (trackInfo.kind == TrackKind.KIND_AUDIO) {
|
|
577
663
|
publication.track = new RemoteAudioTrack(ownedTrack);
|
|
578
664
|
}
|
|
665
|
+
publication.track?.setRoom(this);
|
|
579
666
|
|
|
580
667
|
this.emit(RoomEvent.TrackSubscribed, publication.track!, publication, participant);
|
|
581
668
|
} catch (e: unknown) {
|
|
@@ -588,6 +675,7 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
|
|
|
588
675
|
ev.value.trackSid!,
|
|
589
676
|
);
|
|
590
677
|
const track = publication.track!;
|
|
678
|
+
track.setRoom(null);
|
|
591
679
|
publication.track = undefined;
|
|
592
680
|
publication.subscribed = false;
|
|
593
681
|
this.emit(RoomEvent.TrackUnsubscribed, track, publication, participant);
|
package/src/track.ts
CHANGED
|
@@ -11,7 +11,9 @@ import type {
|
|
|
11
11
|
} from '@livekit/rtc-ffi-bindings';
|
|
12
12
|
import { CreateAudioTrackRequest, CreateVideoTrackRequest } from '@livekit/rtc-ffi-bindings';
|
|
13
13
|
import type { AudioSource } from './audio_source.js';
|
|
14
|
+
import type { AudioStreamSource } from './audio_stream.js';
|
|
14
15
|
import { FfiClient, FfiHandle } from './ffi_client.js';
|
|
16
|
+
import type { Room } from './room.js';
|
|
15
17
|
import type { VideoSource } from './video_source.js';
|
|
16
18
|
|
|
17
19
|
export abstract class Track {
|
|
@@ -21,9 +23,148 @@ export abstract class Track {
|
|
|
21
23
|
/** @internal */
|
|
22
24
|
ffi_handle: FfiHandle;
|
|
23
25
|
|
|
26
|
+
private roomRef: WeakRef<Room> | null = null;
|
|
27
|
+
private audioStreams: Set<WeakRef<AudioStreamSource>> = new Set();
|
|
28
|
+
private streamFinalizationRegistry: FinalizationRegistry<WeakRef<AudioStreamSource>>;
|
|
29
|
+
private onRoomTokenRefreshed = () => {
|
|
30
|
+
const room = this.resolveRoom();
|
|
31
|
+
if (!room || !room.token || !room.serverUrl) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
for (const stream of this.iterateStreams()) {
|
|
35
|
+
const processor = stream.processor;
|
|
36
|
+
if (!processor) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
processor.onCredentialsUpdated({ token: room.token, url: room.serverUrl });
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
24
43
|
constructor(owned: OwnedTrack) {
|
|
25
44
|
this.info = owned.info;
|
|
26
45
|
this.ffi_handle = new FfiHandle(owned.handle!.id!);
|
|
46
|
+
this.streamFinalizationRegistry = new FinalizationRegistry<WeakRef<AudioStreamSource>>(
|
|
47
|
+
(ref) => {
|
|
48
|
+
this.audioStreams.delete(ref);
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** @internal */
|
|
54
|
+
resolveRoom(): Room | null {
|
|
55
|
+
return this.roomRef?.deref() ?? null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** @internal */
|
|
59
|
+
setRoom(room: Room | null): void {
|
|
60
|
+
const oldRoom = this.resolveRoom();
|
|
61
|
+
if (!oldRoom && !room) {
|
|
62
|
+
// Already roomless — nothing to detach and nothing to re-clear. Without
|
|
63
|
+
// this guard a second setRoom(null) (e.g. the unpublishTrack /
|
|
64
|
+
// localTrackUnpublished race calling it from both paths) would re-fire
|
|
65
|
+
// onStreamInfoCleared / onCredentialsCleared on every registered processor.
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (oldRoom && oldRoom !== room) {
|
|
69
|
+
oldRoom.off('tokenRefreshed', this.onRoomTokenRefreshed);
|
|
70
|
+
}
|
|
71
|
+
if (room) {
|
|
72
|
+
room.off('tokenRefreshed', this.onRoomTokenRefreshed);
|
|
73
|
+
room.on('tokenRefreshed', this.onRoomTokenRefreshed);
|
|
74
|
+
}
|
|
75
|
+
this.roomRef = room ? new WeakRef(room) : null;
|
|
76
|
+
for (const stream of this.iterateStreams()) {
|
|
77
|
+
this.pushProcessorMetadataToStream(stream, room);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** @internal */
|
|
82
|
+
registerAudioStream(stream: AudioStreamSource): void {
|
|
83
|
+
const ref = new WeakRef(stream);
|
|
84
|
+
this.audioStreams.add(ref);
|
|
85
|
+
this.streamFinalizationRegistry.register(stream, ref);
|
|
86
|
+
const room = this.resolveRoom();
|
|
87
|
+
if (room) {
|
|
88
|
+
this.pushProcessorMetadataToStream(stream, room);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** @internal */
|
|
93
|
+
unregisterAudioStream(stream: AudioStreamSource): void {
|
|
94
|
+
for (const ref of this.audioStreams) {
|
|
95
|
+
if (ref.deref() === stream) {
|
|
96
|
+
this.audioStreams.delete(ref);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private *iterateStreams(): Generator<AudioStreamSource> {
|
|
103
|
+
const dead: Array<WeakRef<AudioStreamSource>> = [];
|
|
104
|
+
for (const ref of this.audioStreams) {
|
|
105
|
+
const stream = ref.deref();
|
|
106
|
+
if (stream) {
|
|
107
|
+
yield stream;
|
|
108
|
+
} else {
|
|
109
|
+
dead.push(ref);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const ref of dead) {
|
|
113
|
+
this.audioStreams.delete(ref);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private pushProcessorMetadataToStream(stream: AudioStreamSource, room: Room | null): void {
|
|
118
|
+
const processor = stream.processor;
|
|
119
|
+
if (!processor) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!room) {
|
|
124
|
+
// Guard with optional-call: plugins built against an older @livekit/rtc-node
|
|
125
|
+
// inherit a FrameProcessor base class that doesn't define these methods,
|
|
126
|
+
// so they could be undefined on the prototype chain.
|
|
127
|
+
processor.onStreamInfoCleared?.();
|
|
128
|
+
processor.onCredentialsCleared?.();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let identity = '';
|
|
133
|
+
let publicationSid = '';
|
|
134
|
+
const trackSid = this.sid;
|
|
135
|
+
if (trackSid) {
|
|
136
|
+
let found = false;
|
|
137
|
+
for (const participant of room.remoteParticipants.values()) {
|
|
138
|
+
const publication = participant.trackPublications.get(trackSid);
|
|
139
|
+
if (publication) {
|
|
140
|
+
identity = participant.identity;
|
|
141
|
+
publicationSid = publication.sid ?? '';
|
|
142
|
+
found = true;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!found) {
|
|
147
|
+
const local = room.localParticipant;
|
|
148
|
+
if (local) {
|
|
149
|
+
for (const publication of local.trackPublications.values()) {
|
|
150
|
+
if (publication.sid === trackSid) {
|
|
151
|
+
identity = local.identity;
|
|
152
|
+
publicationSid = publication.sid ?? '';
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
processor.onStreamInfoUpdated({
|
|
161
|
+
roomName: room.name ?? '',
|
|
162
|
+
participantIdentity: identity,
|
|
163
|
+
publicationSid,
|
|
164
|
+
});
|
|
165
|
+
if (room.token && room.serverUrl) {
|
|
166
|
+
processor.onCredentialsUpdated({ token: room.token, url: room.serverUrl });
|
|
167
|
+
}
|
|
27
168
|
}
|
|
28
169
|
|
|
29
170
|
get sid(): string | undefined {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = "0.13.
|
|
1
|
+
export const SDK_VERSION = "0.13.30";
|