@livekit/agents 1.0.36 → 1.0.38
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/cli.cjs.map +1 -1
- package/dist/inference/api_protos.cjs +68 -0
- package/dist/inference/api_protos.cjs.map +1 -1
- package/dist/inference/api_protos.d.cts +345 -4
- package/dist/inference/api_protos.d.ts +345 -4
- package/dist/inference/api_protos.d.ts.map +1 -1
- package/dist/inference/api_protos.js +60 -0
- package/dist/inference/api_protos.js.map +1 -1
- package/dist/inference/stt.cjs +32 -21
- package/dist/inference/stt.cjs.map +1 -1
- package/dist/inference/stt.d.ts.map +1 -1
- package/dist/inference/stt.js +34 -21
- package/dist/inference/stt.js.map +1 -1
- package/dist/ipc/inference_proc_executor.cjs.map +1 -1
- package/dist/ipc/job_proc_executor.cjs.map +1 -1
- package/dist/stt/stt.cjs +10 -0
- package/dist/stt/stt.cjs.map +1 -1
- package/dist/stt/stt.d.cts +12 -0
- package/dist/stt/stt.d.ts +12 -0
- package/dist/stt/stt.d.ts.map +1 -1
- package/dist/stt/stt.js +10 -0
- package/dist/stt/stt.js.map +1 -1
- package/dist/telemetry/traces.cjs +4 -3
- package/dist/telemetry/traces.cjs.map +1 -1
- package/dist/telemetry/traces.d.cts +2 -0
- package/dist/telemetry/traces.d.ts +2 -0
- package/dist/telemetry/traces.d.ts.map +1 -1
- package/dist/telemetry/traces.js +4 -3
- package/dist/telemetry/traces.js.map +1 -1
- package/dist/utils.cjs +6 -0
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +2 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +6 -0
- package/dist/utils.js.map +1 -1
- package/dist/voice/agent.cjs +5 -0
- package/dist/voice/agent.cjs.map +1 -1
- package/dist/voice/agent.d.ts.map +1 -1
- package/dist/voice/agent.js +5 -0
- package/dist/voice/agent.js.map +1 -1
- package/dist/voice/agent_activity.cjs +49 -23
- package/dist/voice/agent_activity.cjs.map +1 -1
- package/dist/voice/agent_activity.d.cts +1 -1
- package/dist/voice/agent_activity.d.ts +1 -1
- package/dist/voice/agent_activity.d.ts.map +1 -1
- package/dist/voice/agent_activity.js +50 -24
- package/dist/voice/agent_activity.js.map +1 -1
- package/dist/voice/agent_session.cjs +7 -5
- package/dist/voice/agent_session.cjs.map +1 -1
- package/dist/voice/agent_session.d.cts +5 -2
- package/dist/voice/agent_session.d.ts +5 -2
- package/dist/voice/agent_session.d.ts.map +1 -1
- package/dist/voice/agent_session.js +7 -5
- package/dist/voice/agent_session.js.map +1 -1
- package/dist/voice/audio_recognition.cjs +3 -1
- package/dist/voice/audio_recognition.cjs.map +1 -1
- package/dist/voice/audio_recognition.d.ts.map +1 -1
- package/dist/voice/audio_recognition.js +3 -1
- package/dist/voice/audio_recognition.js.map +1 -1
- package/dist/voice/avatar/datastream_io.cjs +6 -0
- package/dist/voice/avatar/datastream_io.cjs.map +1 -1
- package/dist/voice/avatar/datastream_io.d.cts +1 -0
- package/dist/voice/avatar/datastream_io.d.ts +1 -0
- package/dist/voice/avatar/datastream_io.d.ts.map +1 -1
- package/dist/voice/avatar/datastream_io.js +6 -0
- package/dist/voice/avatar/datastream_io.js.map +1 -1
- package/dist/voice/background_audio.cjs.map +1 -1
- package/dist/voice/generation.cjs +14 -5
- package/dist/voice/generation.cjs.map +1 -1
- package/dist/voice/generation.d.cts +3 -2
- package/dist/voice/generation.d.ts +3 -2
- package/dist/voice/generation.d.ts.map +1 -1
- package/dist/voice/generation.js +14 -5
- package/dist/voice/generation.js.map +1 -1
- package/dist/voice/io.cjs +12 -0
- package/dist/voice/io.cjs.map +1 -1
- package/dist/voice/io.d.cts +19 -1
- package/dist/voice/io.d.ts +19 -1
- package/dist/voice/io.d.ts.map +1 -1
- package/dist/voice/io.js +12 -0
- package/dist/voice/io.js.map +1 -1
- package/dist/voice/recorder_io/recorder_io.cjs +91 -28
- package/dist/voice/recorder_io/recorder_io.cjs.map +1 -1
- package/dist/voice/recorder_io/recorder_io.d.cts +7 -1
- package/dist/voice/recorder_io/recorder_io.d.ts +7 -1
- package/dist/voice/recorder_io/recorder_io.d.ts.map +1 -1
- package/dist/voice/recorder_io/recorder_io.js +91 -28
- package/dist/voice/recorder_io/recorder_io.js.map +1 -1
- package/dist/voice/room_io/_input.cjs +40 -11
- package/dist/voice/room_io/_input.cjs.map +1 -1
- package/dist/voice/room_io/_input.d.cts +4 -1
- package/dist/voice/room_io/_input.d.ts +4 -1
- package/dist/voice/room_io/_input.d.ts.map +1 -1
- package/dist/voice/room_io/_input.js +31 -2
- package/dist/voice/room_io/_input.js.map +1 -1
- package/dist/voice/room_io/_output.cjs +6 -0
- package/dist/voice/room_io/_output.cjs.map +1 -1
- package/dist/voice/room_io/_output.d.cts +1 -0
- package/dist/voice/room_io/_output.d.ts +1 -0
- package/dist/voice/room_io/_output.d.ts.map +1 -1
- package/dist/voice/room_io/_output.js +6 -0
- package/dist/voice/room_io/_output.js.map +1 -1
- package/dist/voice/room_io/room_io.cjs.map +1 -1
- package/dist/voice/room_io/room_io.d.cts +2 -2
- package/dist/voice/room_io/room_io.d.ts +2 -2
- package/dist/voice/room_io/room_io.d.ts.map +1 -1
- package/dist/voice/room_io/room_io.js.map +1 -1
- package/dist/voice/speech_handle.cjs +2 -0
- package/dist/voice/speech_handle.cjs.map +1 -1
- package/dist/voice/speech_handle.d.cts +3 -0
- package/dist/voice/speech_handle.d.ts +3 -0
- package/dist/voice/speech_handle.d.ts.map +1 -1
- package/dist/voice/speech_handle.js +2 -0
- package/dist/voice/speech_handle.js.map +1 -1
- package/dist/voice/testing/index.cjs +2 -0
- package/dist/voice/testing/index.cjs.map +1 -1
- package/dist/voice/testing/index.d.cts +1 -1
- package/dist/voice/testing/index.d.ts +1 -1
- package/dist/voice/testing/index.d.ts.map +1 -1
- package/dist/voice/testing/index.js +2 -0
- package/dist/voice/testing/index.js.map +1 -1
- package/dist/voice/testing/run_result.cjs +294 -5
- package/dist/voice/testing/run_result.cjs.map +1 -1
- package/dist/voice/testing/run_result.d.cts +149 -1
- package/dist/voice/testing/run_result.d.ts +149 -1
- package/dist/voice/testing/run_result.d.ts.map +1 -1
- package/dist/voice/testing/run_result.js +293 -5
- package/dist/voice/testing/run_result.js.map +1 -1
- package/package.json +1 -1
- package/src/inference/api_protos.ts +83 -0
- package/src/inference/stt.ts +39 -22
- package/src/stt/stt.ts +21 -0
- package/src/telemetry/traces.ts +6 -2
- package/src/utils.ts +7 -0
- package/src/voice/agent.ts +9 -0
- package/src/voice/agent_activity.ts +72 -26
- package/src/voice/agent_session.ts +6 -5
- package/src/voice/audio_recognition.ts +2 -0
- package/src/voice/avatar/datastream_io.ts +8 -0
- package/src/voice/generation.ts +24 -12
- package/src/voice/io.ts +27 -5
- package/src/voice/recorder_io/recorder_io.ts +123 -31
- package/src/voice/room_io/_input.ts +32 -4
- package/src/voice/room_io/_output.ts +8 -0
- package/src/voice/room_io/room_io.ts +3 -1
- package/src/voice/speech_handle.ts +4 -0
- package/src/voice/testing/index.ts +1 -0
- package/src/voice/testing/run_result.ts +373 -12
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FrameProcessor } from "@livekit/rtc-node";
|
|
1
2
|
import {
|
|
2
3
|
AudioStream,
|
|
3
4
|
RemoteParticipant,
|
|
@@ -12,6 +13,7 @@ class ParticipantAudioInputStream extends AudioInput {
|
|
|
12
13
|
sampleRate;
|
|
13
14
|
numChannels;
|
|
14
15
|
noiseCancellation;
|
|
16
|
+
frameProcessor;
|
|
15
17
|
publication = null;
|
|
16
18
|
participantIdentity = null;
|
|
17
19
|
logger = log();
|
|
@@ -25,9 +27,14 @@ class ParticipantAudioInputStream extends AudioInput {
|
|
|
25
27
|
this.room = room;
|
|
26
28
|
this.sampleRate = sampleRate;
|
|
27
29
|
this.numChannels = numChannels;
|
|
28
|
-
|
|
30
|
+
if (noiseCancellation instanceof FrameProcessor) {
|
|
31
|
+
this.frameProcessor = noiseCancellation;
|
|
32
|
+
} else {
|
|
33
|
+
this.noiseCancellation = noiseCancellation;
|
|
34
|
+
}
|
|
29
35
|
this.room.on(RoomEvent.TrackSubscribed, this.onTrackSubscribed);
|
|
30
36
|
this.room.on(RoomEvent.TrackUnpublished, this.onTrackUnpublished);
|
|
37
|
+
this.room.on(RoomEvent.TokenRefreshed, this.onTokenRefreshed);
|
|
31
38
|
}
|
|
32
39
|
setParticipant(participant) {
|
|
33
40
|
this.logger.debug({ participant }, "setting participant audio input");
|
|
@@ -72,12 +79,15 @@ class ParticipantAudioInputStream extends AudioInput {
|
|
|
72
79
|
}
|
|
73
80
|
};
|
|
74
81
|
closeStream() {
|
|
82
|
+
var _a;
|
|
75
83
|
if (this.deferredStream.isSourceSet) {
|
|
76
84
|
this.deferredStream.detachSource();
|
|
77
85
|
}
|
|
86
|
+
(_a = this.frameProcessor) == null ? void 0 : _a.close();
|
|
78
87
|
this.publication = null;
|
|
79
88
|
}
|
|
80
89
|
onTrackSubscribed = (track, publication, participant) => {
|
|
90
|
+
var _a, _b;
|
|
81
91
|
this.logger.debug({ participant: participant.identity }, "onTrackSubscribed in _input");
|
|
82
92
|
if (this.participantIdentity !== participant.identity || publication.source !== TrackSource.SOURCE_MICROPHONE || this.publication && this.publication.sid === publication.sid) {
|
|
83
93
|
return false;
|
|
@@ -90,19 +100,38 @@ class ParticipantAudioInputStream extends AudioInput {
|
|
|
90
100
|
outputRate: this.sampleRate
|
|
91
101
|
})
|
|
92
102
|
);
|
|
103
|
+
(_a = this.frameProcessor) == null ? void 0 : _a.onStreamInfoUpdated({
|
|
104
|
+
participantIdentity: participant.identity,
|
|
105
|
+
roomName: this.room.name,
|
|
106
|
+
publicationSid: publication.sid
|
|
107
|
+
});
|
|
108
|
+
(_b = this.frameProcessor) == null ? void 0 : _b.onCredentialsUpdated({
|
|
109
|
+
token: this.room.token,
|
|
110
|
+
url: this.room.serverUrl
|
|
111
|
+
});
|
|
93
112
|
return true;
|
|
94
113
|
};
|
|
114
|
+
onTokenRefreshed = () => {
|
|
115
|
+
var _a;
|
|
116
|
+
if (this.room.token && this.room.serverUrl) {
|
|
117
|
+
(_a = this.frameProcessor) == null ? void 0 : _a.onCredentialsUpdated({
|
|
118
|
+
token: this.room.token,
|
|
119
|
+
url: this.room.serverUrl
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
};
|
|
95
123
|
createStream(track) {
|
|
96
124
|
return new AudioStream(track, {
|
|
97
125
|
sampleRate: this.sampleRate,
|
|
98
126
|
numChannels: this.numChannels,
|
|
99
|
-
noiseCancellation: this.noiseCancellation
|
|
127
|
+
noiseCancellation: this.frameProcessor || this.noiseCancellation
|
|
100
128
|
// TODO(AJS-269): resolve compatibility issue with node-sdk to remove the forced type casting
|
|
101
129
|
});
|
|
102
130
|
}
|
|
103
131
|
async close() {
|
|
104
132
|
this.room.off(RoomEvent.TrackSubscribed, this.onTrackSubscribed);
|
|
105
133
|
this.room.off(RoomEvent.TrackUnpublished, this.onTrackUnpublished);
|
|
134
|
+
this.room.off(RoomEvent.TokenRefreshed, this.onTokenRefreshed);
|
|
106
135
|
this.closeStream();
|
|
107
136
|
await this.deferredStream.stream.cancel().catch(() => {
|
|
108
137
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/voice/room_io/_input.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type
|
|
1
|
+
{"version":3,"sources":["../../../src/voice/room_io/_input.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { type AudioFrame, FrameProcessor } from '@livekit/rtc-node';\nimport {\n AudioStream,\n type NoiseCancellationOptions,\n RemoteParticipant,\n type RemoteTrack,\n type RemoteTrackPublication,\n type Room,\n RoomEvent,\n TrackSource,\n} from '@livekit/rtc-node';\nimport type { ReadableStream } from 'node:stream/web';\nimport { log } from '../../log.js';\nimport { resampleStream } from '../../utils.js';\nimport { AudioInput } from '../io.js';\n\nexport class ParticipantAudioInputStream extends AudioInput {\n private room: Room;\n private sampleRate: number;\n private numChannels: number;\n private noiseCancellation?: NoiseCancellationOptions;\n private frameProcessor?: FrameProcessor<AudioFrame>;\n private publication: RemoteTrackPublication | null = null;\n private participantIdentity: string | null = null;\n private logger = log();\n constructor({\n room,\n sampleRate,\n numChannels,\n noiseCancellation,\n }: {\n room: Room;\n sampleRate: number;\n numChannels: number;\n noiseCancellation?: NoiseCancellationOptions | FrameProcessor<AudioFrame>;\n }) {\n super();\n this.room = room;\n this.sampleRate = sampleRate;\n this.numChannels = numChannels;\n if (noiseCancellation instanceof FrameProcessor) {\n this.frameProcessor = noiseCancellation;\n } else {\n this.noiseCancellation = noiseCancellation;\n }\n\n this.room.on(RoomEvent.TrackSubscribed, this.onTrackSubscribed);\n this.room.on(RoomEvent.TrackUnpublished, this.onTrackUnpublished);\n this.room.on(RoomEvent.TokenRefreshed, this.onTokenRefreshed);\n }\n\n setParticipant(participant: RemoteParticipant | string | null) {\n this.logger.debug({ participant }, 'setting participant audio input');\n const participantIdentity =\n participant instanceof RemoteParticipant ? participant.identity : participant;\n\n if (this.participantIdentity === participantIdentity) {\n return;\n }\n this.participantIdentity = participantIdentity;\n this.closeStream();\n\n if (!participantIdentity) {\n return;\n }\n\n const participantValue =\n participant instanceof RemoteParticipant\n ? participant\n : this.room.remoteParticipants.get(participantIdentity);\n\n // Convert Map iterator to array for Pino serialization\n const trackPublicationsArray = Array.from(participantValue?.trackPublications.values() ?? []);\n\n this.logger.info(\n {\n participantValue: participantValue?.identity,\n trackPublications: trackPublicationsArray,\n lengthOfTrackPublications: trackPublicationsArray.length,\n },\n 'participantValue.trackPublications',\n );\n // We need to check if the participant has a microphone track and subscribe to it\n // in case we miss the tracksubscribed event\n if (participantValue) {\n for (const publication of participantValue.trackPublications.values()) {\n if (publication.track && publication.source === TrackSource.SOURCE_MICROPHONE) {\n this.onTrackSubscribed(publication.track, publication, participantValue);\n break;\n }\n }\n }\n }\n\n private onTrackUnpublished = (\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (\n this.publication?.sid !== publication.sid ||\n participant.identity !== this.participantIdentity\n ) {\n return;\n }\n this.closeStream();\n\n // subscribe to the first available track\n for (const publication of participant.trackPublications.values()) {\n if (\n publication.track &&\n this.onTrackSubscribed(publication.track, publication, participant)\n ) {\n return;\n }\n }\n };\n\n private closeStream() {\n if (this.deferredStream.isSourceSet) {\n this.deferredStream.detachSource();\n }\n\n this.frameProcessor?.close();\n\n this.publication = null;\n }\n\n private onTrackSubscribed = (\n track: RemoteTrack,\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ): boolean => {\n this.logger.debug({ participant: participant.identity }, 'onTrackSubscribed in _input');\n if (\n this.participantIdentity !== participant.identity ||\n publication.source !== TrackSource.SOURCE_MICROPHONE ||\n (this.publication && this.publication.sid === publication.sid)\n ) {\n return false;\n }\n this.closeStream();\n this.publication = publication;\n this.deferredStream.setSource(\n resampleStream({\n stream: this.createStream(track),\n outputRate: this.sampleRate,\n }),\n );\n this.frameProcessor?.onStreamInfoUpdated({\n participantIdentity: participant.identity,\n roomName: this.room.name!,\n publicationSid: publication.sid!,\n });\n this.frameProcessor?.onCredentialsUpdated({\n token: this.room.token!,\n url: this.room.serverUrl!,\n });\n return true;\n };\n\n private onTokenRefreshed = () => {\n if (this.room.token && this.room.serverUrl) {\n this.frameProcessor?.onCredentialsUpdated({\n token: this.room.token,\n url: this.room.serverUrl,\n });\n }\n };\n\n private createStream(track: RemoteTrack): ReadableStream<AudioFrame> {\n return new AudioStream(track, {\n sampleRate: this.sampleRate,\n numChannels: this.numChannels,\n noiseCancellation: this.frameProcessor || this.noiseCancellation,\n // TODO(AJS-269): resolve compatibility issue with node-sdk to remove the forced type casting\n }) as unknown as ReadableStream<AudioFrame>;\n }\n\n async close() {\n this.room.off(RoomEvent.TrackSubscribed, this.onTrackSubscribed);\n this.room.off(RoomEvent.TrackUnpublished, this.onTrackUnpublished);\n this.room.off(RoomEvent.TokenRefreshed, this.onTokenRefreshed);\n this.closeStream();\n // Ignore errors - stream may be locked by RecorderIO or already cancelled\n await this.deferredStream.stream.cancel().catch(() => {});\n }\n}\n"],"mappings":"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","names":["publication"]}
|
|
@@ -266,6 +266,7 @@ class ParticipantAudioOutput extends import_io.AudioOutput {
|
|
|
266
266
|
pushedDuration = 0;
|
|
267
267
|
startedFuture = new import_utils.Future();
|
|
268
268
|
interruptedFuture = new import_utils.Future();
|
|
269
|
+
firstFrameEmitted = false;
|
|
269
270
|
constructor(room, options) {
|
|
270
271
|
super(options.sampleRate, void 0, { pause: true });
|
|
271
272
|
this.room = room;
|
|
@@ -281,6 +282,10 @@ class ParticipantAudioOutput extends import_io.AudioOutput {
|
|
|
281
282
|
async captureFrame(frame) {
|
|
282
283
|
await this.startedFuture.await;
|
|
283
284
|
super.captureFrame(frame);
|
|
285
|
+
if (!this.firstFrameEmitted) {
|
|
286
|
+
this.firstFrameEmitted = true;
|
|
287
|
+
this.onPlaybackStarted(Date.now());
|
|
288
|
+
}
|
|
284
289
|
this.pushedDuration += frame.samplesPerChannel / frame.sampleRate;
|
|
285
290
|
await this.audioSource.captureFrame(frame);
|
|
286
291
|
}
|
|
@@ -305,6 +310,7 @@ class ParticipantAudioOutput extends import_io.AudioOutput {
|
|
|
305
310
|
}
|
|
306
311
|
this.pushedDuration = 0;
|
|
307
312
|
this.interruptedFuture = new import_utils.Future();
|
|
313
|
+
this.firstFrameEmitted = false;
|
|
308
314
|
this.onPlaybackFinished({
|
|
309
315
|
playbackPosition: pushedDuration,
|
|
310
316
|
interrupted
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/voice/room_io/_output.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { RemoteParticipant } from '@livekit/rtc-node';\nimport {\n type AudioFrame,\n AudioSource,\n LocalAudioTrack,\n type LocalTrackPublication,\n type Participant,\n type RemoteTrackPublication,\n type Room,\n RoomEvent,\n type TextStreamWriter,\n TrackPublishOptions,\n TrackSource,\n} from '@livekit/rtc-node';\nimport {\n ATTRIBUTE_TRANSCRIPTION_FINAL,\n ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID,\n ATTRIBUTE_TRANSCRIPTION_TRACK_ID,\n TOPIC_TRANSCRIPTION,\n} from '../../constants.js';\nimport { log } from '../../log.js';\nimport { Future, Task, shortuuid } from '../../utils.js';\nimport { AudioOutput, TextOutput } from '../io.js';\nimport { findMicrophoneTrackId } from '../transcription/index.js';\n\nabstract class BaseParticipantTranscriptionOutput extends TextOutput {\n protected room: Room;\n protected isDeltaStream: boolean;\n protected participantIdentity: string | null = null;\n protected trackId?: string;\n protected capturing: boolean = false;\n protected latestText: string = '';\n protected currentId: string = this.generateCurrentId();\n protected logger = log();\n\n constructor(room: Room, isDeltaStream: boolean, participant: Participant | string | null) {\n super();\n this.room = room;\n this.isDeltaStream = isDeltaStream;\n\n this.room.on(RoomEvent.TrackPublished, this.onTrackPublished);\n this.room.on(RoomEvent.LocalTrackPublished, this.onLocalTrackPublished);\n\n this.setParticipant(participant);\n }\n\n setParticipant(participant: Participant | string | null) {\n if (typeof participant === 'string' || participant === null) {\n this.participantIdentity = participant;\n } else {\n this.participantIdentity = participant.identity;\n }\n\n if (!this.participantIdentity) {\n return;\n }\n\n try {\n this.trackId = findMicrophoneTrackId(this.room, this.participantIdentity);\n } catch (error) {\n // track id is optional for TextStream when audio is not published\n }\n\n this.flush();\n this.resetState();\n }\n\n protected onTrackPublished = (track: RemoteTrackPublication, participant: RemoteParticipant) => {\n if (\n !this.participantIdentity ||\n participant.identity !== this.participantIdentity ||\n track.source !== TrackSource.SOURCE_MICROPHONE\n ) {\n return;\n }\n\n this.trackId = track.sid;\n };\n\n protected onLocalTrackPublished = (track: LocalTrackPublication) => {\n if (\n !this.participantIdentity ||\n this.participantIdentity !== this.room.localParticipant?.identity ||\n track.source !== TrackSource.SOURCE_MICROPHONE\n ) {\n return;\n }\n\n this.trackId = track.sid;\n };\n\n protected generateCurrentId(): string {\n return shortuuid('SG_');\n }\n\n protected resetState() {\n this.currentId = this.generateCurrentId();\n this.capturing = false;\n this.latestText = '';\n }\n\n async captureText(text: string) {\n if (!this.participantIdentity) {\n return;\n }\n\n this.latestText = text;\n await this.handleCaptureText(text);\n }\n\n flush() {\n if (!this.participantIdentity || !this.capturing) {\n return;\n }\n\n this.capturing = false;\n this.handleFlush();\n }\n\n protected abstract handleCaptureText(text: string): Promise<void>;\n protected abstract handleFlush(): void;\n}\n\nexport class ParticipantTranscriptionOutput extends BaseParticipantTranscriptionOutput {\n private writer: TextStreamWriter | null = null;\n private flushTask: Task<void> | null = null;\n\n protected async handleCaptureText(text: string): Promise<void> {\n if (this.flushTask && !this.flushTask.done) {\n await this.flushTask.result;\n }\n\n if (!this.capturing) {\n this.resetState();\n this.capturing = true;\n }\n\n try {\n if (this.room.isConnected) {\n if (this.isDeltaStream) {\n // reuse the existing writer\n if (this.writer === null) {\n this.writer = await this.createTextWriter();\n }\n await this.writer.write(text);\n } else {\n const tmpWriter = await this.createTextWriter();\n await tmpWriter.write(text);\n await tmpWriter.close();\n }\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n\n protected handleFlush() {\n const currWriter = this.writer;\n this.writer = null;\n this.flushTask = Task.from((controller) => this.flushTaskImpl(currWriter, controller.signal));\n }\n\n private async createTextWriter(attributes?: Record<string, string>): Promise<TextStreamWriter> {\n if (!this.participantIdentity) {\n throw new Error('participantIdentity not found');\n }\n\n if (!this.room.localParticipant) {\n throw new Error('localParticipant not found');\n }\n\n if (!attributes) {\n attributes = {\n [ATTRIBUTE_TRANSCRIPTION_FINAL]: 'false',\n };\n if (this.trackId) {\n attributes[ATTRIBUTE_TRANSCRIPTION_TRACK_ID] = this.trackId;\n }\n }\n attributes[ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID] = this.currentId;\n\n return await this.room.localParticipant.streamText({\n topic: TOPIC_TRANSCRIPTION,\n senderIdentity: this.participantIdentity,\n attributes,\n });\n }\n\n private async flushTaskImpl(writer: TextStreamWriter | null, signal: AbortSignal): Promise<void> {\n const attributes: Record<string, string> = {\n [ATTRIBUTE_TRANSCRIPTION_FINAL]: 'true',\n };\n if (this.trackId) {\n attributes[ATTRIBUTE_TRANSCRIPTION_TRACK_ID] = this.trackId;\n }\n\n const abortPromise = new Promise<void>((resolve) => {\n signal.addEventListener('abort', () => resolve());\n });\n\n try {\n if (this.room.isConnected) {\n if (this.isDeltaStream) {\n if (writer) {\n await Promise.race([writer.close(), abortPromise]);\n }\n } else {\n const tmpWriter = await Promise.race([this.createTextWriter(attributes), abortPromise]);\n if (signal.aborted || !tmpWriter) {\n return;\n }\n await Promise.race([tmpWriter.write(this.latestText), abortPromise]);\n if (signal.aborted) {\n return;\n }\n await Promise.race([tmpWriter.close(), abortPromise]);\n }\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n}\n\nexport class ParticipantLegacyTranscriptionOutput extends BaseParticipantTranscriptionOutput {\n private pushedText: string = '';\n private flushTask: Promise<void> | null = null;\n\n protected async handleCaptureText(text: string): Promise<void> {\n if (!this.trackId) {\n return;\n }\n\n if (this.flushTask) {\n await this.flushTask;\n }\n\n if (!this.capturing) {\n this.resetState();\n this.capturing = true;\n }\n\n if (this.isDeltaStream) {\n this.pushedText += text;\n } else {\n this.pushedText = text;\n }\n\n await this.publishTranscription(this.currentId, this.pushedText, false);\n }\n\n protected handleFlush() {\n if (!this.trackId) {\n return;\n }\n\n this.flushTask = this.publishTranscription(this.currentId, this.pushedText, true);\n this.resetState();\n }\n\n async publishTranscription(id: string, text: string, final: boolean, signal?: AbortSignal) {\n if (!this.participantIdentity || !this.trackId) {\n return;\n }\n\n try {\n if (this.room.isConnected) {\n if (signal?.aborted) {\n return;\n }\n\n await this.room.localParticipant?.publishTranscription({\n participantIdentity: this.participantIdentity,\n trackSid: this.trackId,\n segments: [{ id, text, final, startTime: BigInt(0), endTime: BigInt(0), language: '' }],\n });\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n\n protected resetState() {\n super.resetState();\n this.pushedText = '';\n }\n}\n\nexport class ParalellTextOutput extends TextOutput {\n /** @internal */\n _sinks: TextOutput[];\n\n constructor(sinks: TextOutput[], nextInChain?: TextOutput) {\n super(nextInChain);\n this._sinks = sinks;\n }\n\n async captureText(text: string) {\n await Promise.all(this._sinks.map((sink) => sink.captureText(text)));\n }\n\n flush() {\n for (const sink of this._sinks) {\n sink.flush();\n }\n }\n}\n\nexport interface AudioOutputOptions {\n sampleRate: number;\n numChannels: number;\n trackPublishOptions: TrackPublishOptions;\n queueSizeMs?: number;\n}\nexport class ParticipantAudioOutput extends AudioOutput {\n private room: Room;\n private options: AudioOutputOptions;\n private audioSource: AudioSource;\n private publication?: LocalTrackPublication;\n private flushTask?: Task<void>;\n\n /** Duration of audio pushed to the source, in seconds */\n private pushedDuration: number = 0;\n private startedFuture: Future<void> = new Future();\n private interruptedFuture: Future<void> = new Future();\n\n constructor(room: Room, options: AudioOutputOptions) {\n super(options.sampleRate, undefined, { pause: true });\n this.room = room;\n this.options = options;\n this.audioSource = new AudioSource(options.sampleRate, options.numChannels);\n }\n\n get subscribed(): boolean {\n return this.startedFuture.done;\n }\n\n async start(signal: AbortSignal): Promise<void> {\n await this.publishTrack(signal);\n }\n\n async captureFrame(frame: AudioFrame): Promise<void> {\n await this.startedFuture.await;\n\n super.captureFrame(frame);\n\n // TODO(AJS-102): use frame.durationMs once available in rtc-node\n this.pushedDuration += frame.samplesPerChannel / frame.sampleRate;\n await this.audioSource.captureFrame(frame);\n }\n\n private async waitForPlayoutTask(abortController: AbortController): Promise<void> {\n const abortFuture = new Future<boolean>();\n\n const resolveAbort = () => {\n if (!abortFuture.done) abortFuture.resolve(true);\n };\n\n abortController.signal.addEventListener('abort', resolveAbort);\n\n this.audioSource.waitForPlayout().finally(() => {\n abortController.signal.removeEventListener('abort', resolveAbort);\n if (!abortFuture.done) abortFuture.resolve(false);\n });\n\n const interrupted = await Promise.race([\n abortFuture.await,\n this.interruptedFuture.await.then(() => true),\n ]);\n\n let pushedDuration = this.pushedDuration;\n\n if (interrupted) {\n // Calculate actual played duration accounting for queued audio\n // Note: queuedDuration is in milliseconds, pushedDuration is in seconds\n pushedDuration = Math.max(this.pushedDuration - this.audioSource.queuedDuration / 1000, 0);\n this.audioSource.clearQueue();\n }\n\n this.pushedDuration = 0;\n this.interruptedFuture = new Future();\n this.onPlaybackFinished({\n playbackPosition: pushedDuration,\n interrupted,\n });\n }\n\n /**\n * Flush any buffered audio, marking the current playback/segment as complete\n */\n flush(): void {\n super.flush();\n\n if (!this.pushedDuration) {\n return;\n }\n\n if (this.flushTask && !this.flushTask.done) {\n this.logger.error('flush called while playback is in progress');\n this.flushTask.cancel();\n }\n\n this.flushTask = Task.from((controller) => this.waitForPlayoutTask(controller));\n }\n\n clearBuffer(): void {\n if (!this.pushedDuration) {\n return;\n }\n\n this.interruptedFuture.resolve();\n }\n\n private async publishTrack(signal: AbortSignal) {\n const track = LocalAudioTrack.createAudioTrack('roomio_audio', this.audioSource);\n this.publication = await this.room.localParticipant?.publishTrack(\n track,\n new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE }),\n );\n\n if (signal.aborted) {\n return;\n }\n\n await this.publication?.waitForSubscription();\n\n if (!this.startedFuture.done) {\n this.startedFuture.resolve();\n }\n }\n\n async close() {\n // TODO(AJS-106): add republish track\n await this.audioSource.close();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,sBAYO;AACP,uBAKO;AACP,iBAAoB;AACpB,mBAAwC;AACxC,gBAAwC;AACxC,2BAAsC;AAEtC,MAAe,2CAA2C,qBAAW;AAAA,EACzD;AAAA,EACA;AAAA,EACA,sBAAqC;AAAA,EACrC;AAAA,EACA,YAAqB;AAAA,EACrB,aAAqB;AAAA,EACrB,YAAoB,KAAK,kBAAkB;AAAA,EAC3C,aAAS,gBAAI;AAAA,EAEvB,YAAY,MAAY,eAAwB,aAA0C;AACxF,UAAM;AACN,SAAK,OAAO;AACZ,SAAK,gBAAgB;AAErB,SAAK,KAAK,GAAG,0BAAU,gBAAgB,KAAK,gBAAgB;AAC5D,SAAK,KAAK,GAAG,0BAAU,qBAAqB,KAAK,qBAAqB;AAEtE,SAAK,eAAe,WAAW;AAAA,EACjC;AAAA,EAEA,eAAe,aAA0C;AACvD,QAAI,OAAO,gBAAgB,YAAY,gBAAgB,MAAM;AAC3D,WAAK,sBAAsB;AAAA,IAC7B,OAAO;AACL,WAAK,sBAAsB,YAAY;AAAA,IACzC;AAEA,QAAI,CAAC,KAAK,qBAAqB;AAC7B;AAAA,IACF;AAEA,QAAI;AACF,WAAK,cAAU,4CAAsB,KAAK,MAAM,KAAK,mBAAmB;AAAA,IAC1E,SAAS,OAAO;AAAA,IAEhB;AAEA,SAAK,MAAM;AACX,SAAK,WAAW;AAAA,EAClB;AAAA,EAEU,mBAAmB,CAAC,OAA+B,gBAAmC;AAC9F,QACE,CAAC,KAAK,uBACN,YAAY,aAAa,KAAK,uBAC9B,MAAM,WAAW,4BAAY,mBAC7B;AACA;AAAA,IACF;AAEA,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA,EAEU,wBAAwB,CAAC,UAAiC;AAlFtE;AAmFI,QACE,CAAC,KAAK,uBACN,KAAK,0BAAwB,UAAK,KAAK,qBAAV,mBAA4B,aACzD,MAAM,WAAW,4BAAY,mBAC7B;AACA;AAAA,IACF;AAEA,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA,EAEU,oBAA4B;AACpC,eAAO,wBAAU,KAAK;AAAA,EACxB;AAAA,EAEU,aAAa;AACrB,SAAK,YAAY,KAAK,kBAAkB;AACxC,SAAK,YAAY;AACjB,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,MAAM,YAAY,MAAc;AAC9B,QAAI,CAAC,KAAK,qBAAqB;AAC7B;AAAA,IACF;AAEA,SAAK,aAAa;AAClB,UAAM,KAAK,kBAAkB,IAAI;AAAA,EACnC;AAAA,EAEA,QAAQ;AACN,QAAI,CAAC,KAAK,uBAAuB,CAAC,KAAK,WAAW;AAChD;AAAA,IACF;AAEA,SAAK,YAAY;AACjB,SAAK,YAAY;AAAA,EACnB;AAIF;AAEO,MAAM,uCAAuC,mCAAmC;AAAA,EAC7E,SAAkC;AAAA,EAClC,YAA+B;AAAA,EAEvC,MAAgB,kBAAkB,MAA6B;AAC7D,QAAI,KAAK,aAAa,CAAC,KAAK,UAAU,MAAM;AAC1C,YAAM,KAAK,UAAU;AAAA,IACvB;AAEA,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,WAAW;AAChB,WAAK,YAAY;AAAA,IACnB;AAEA,QAAI;AACF,UAAI,KAAK,KAAK,aAAa;AACzB,YAAI,KAAK,eAAe;AAEtB,cAAI,KAAK,WAAW,MAAM;AACxB,iBAAK,SAAS,MAAM,KAAK,iBAAiB;AAAA,UAC5C;AACA,gBAAM,KAAK,OAAO,MAAM,IAAI;AAAA,QAC9B,OAAO;AACL,gBAAM,YAAY,MAAM,KAAK,iBAAiB;AAC9C,gBAAM,UAAU,MAAM,IAAI;AAC1B,gBAAM,UAAU,MAAM;AAAA,QACxB;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,OAAO,iCAAiC;AAAA,IAC5D;AAAA,EACF;AAAA,EAEU,cAAc;AACtB,UAAM,aAAa,KAAK;AACxB,SAAK,SAAS;AACd,SAAK,YAAY,kBAAK,KAAK,CAAC,eAAe,KAAK,cAAc,YAAY,WAAW,MAAM,CAAC;AAAA,EAC9F;AAAA,EAEA,MAAc,iBAAiB,YAAgE;AAC7F,QAAI,CAAC,KAAK,qBAAqB;AAC7B,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AAEA,QAAI,CAAC,KAAK,KAAK,kBAAkB;AAC/B,YAAM,IAAI,MAAM,4BAA4B;AAAA,IAC9C;AAEA,QAAI,CAAC,YAAY;AACf,mBAAa;AAAA,QACX,CAAC,8CAA6B,GAAG;AAAA,MACnC;AACA,UAAI,KAAK,SAAS;AAChB,mBAAW,iDAAgC,IAAI,KAAK;AAAA,MACtD;AAAA,IACF;AACA,eAAW,mDAAkC,IAAI,KAAK;AAEtD,WAAO,MAAM,KAAK,KAAK,iBAAiB,WAAW;AAAA,MACjD,OAAO;AAAA,MACP,gBAAgB,KAAK;AAAA,MACrB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,cAAc,QAAiC,QAAoC;AAC/F,UAAM,aAAqC;AAAA,MACzC,CAAC,8CAA6B,GAAG;AAAA,IACnC;AACA,QAAI,KAAK,SAAS;AAChB,iBAAW,iDAAgC,IAAI,KAAK;AAAA,IACtD;AAEA,UAAM,eAAe,IAAI,QAAc,CAAC,YAAY;AAClD,aAAO,iBAAiB,SAAS,MAAM,QAAQ,CAAC;AAAA,IAClD,CAAC;AAED,QAAI;AACF,UAAI,KAAK,KAAK,aAAa;AACzB,YAAI,KAAK,eAAe;AACtB,cAAI,QAAQ;AACV,kBAAM,QAAQ,KAAK,CAAC,OAAO,MAAM,GAAG,YAAY,CAAC;AAAA,UACnD;AAAA,QACF,OAAO;AACL,gBAAM,YAAY,MAAM,QAAQ,KAAK,CAAC,KAAK,iBAAiB,UAAU,GAAG,YAAY,CAAC;AACtF,cAAI,OAAO,WAAW,CAAC,WAAW;AAChC;AAAA,UACF;AACA,gBAAM,QAAQ,KAAK,CAAC,UAAU,MAAM,KAAK,UAAU,GAAG,YAAY,CAAC;AACnE,cAAI,OAAO,SAAS;AAClB;AAAA,UACF;AACA,gBAAM,QAAQ,KAAK,CAAC,UAAU,MAAM,GAAG,YAAY,CAAC;AAAA,QACtD;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,OAAO,iCAAiC;AAAA,IAC5D;AAAA,EACF;AACF;AAEO,MAAM,6CAA6C,mCAAmC;AAAA,EACnF,aAAqB;AAAA,EACrB,YAAkC;AAAA,EAE1C,MAAgB,kBAAkB,MAA6B;AAC7D,QAAI,CAAC,KAAK,SAAS;AACjB;AAAA,IACF;AAEA,QAAI,KAAK,WAAW;AAClB,YAAM,KAAK;AAAA,IACb;AAEA,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,WAAW;AAChB,WAAK,YAAY;AAAA,IACnB;AAEA,QAAI,KAAK,eAAe;AACtB,WAAK,cAAc;AAAA,IACrB,OAAO;AACL,WAAK,aAAa;AAAA,IACpB;AAEA,UAAM,KAAK,qBAAqB,KAAK,WAAW,KAAK,YAAY,KAAK;AAAA,EACxE;AAAA,EAEU,cAAc;AACtB,QAAI,CAAC,KAAK,SAAS;AACjB;AAAA,IACF;AAEA,SAAK,YAAY,KAAK,qBAAqB,KAAK,WAAW,KAAK,YAAY,IAAI;AAChF,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAM,qBAAqB,IAAY,MAAc,OAAgB,QAAsB;AAvQ7F;AAwQI,QAAI,CAAC,KAAK,uBAAuB,CAAC,KAAK,SAAS;AAC9C;AAAA,IACF;AAEA,QAAI;AACF,UAAI,KAAK,KAAK,aAAa;AACzB,YAAI,iCAAQ,SAAS;AACnB;AAAA,QACF;AAEA,gBAAM,UAAK,KAAK,qBAAV,mBAA4B,qBAAqB;AAAA,UACrD,qBAAqB,KAAK;AAAA,UAC1B,UAAU,KAAK;AAAA,UACf,UAAU,CAAC,EAAE,IAAI,MAAM,OAAO,WAAW,OAAO,CAAC,GAAG,SAAS,OAAO,CAAC,GAAG,UAAU,GAAG,CAAC;AAAA,QACxF;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,OAAO,iCAAiC;AAAA,IAC5D;AAAA,EACF;AAAA,EAEU,aAAa;AACrB,UAAM,WAAW;AACjB,SAAK,aAAa;AAAA,EACpB;AACF;AAEO,MAAM,2BAA2B,qBAAW;AAAA;AAAA,EAEjD;AAAA,EAEA,YAAY,OAAqB,aAA0B;AACzD,UAAM,WAAW;AACjB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,YAAY,MAAc;AAC9B,UAAM,QAAQ,IAAI,KAAK,OAAO,IAAI,CAAC,SAAS,KAAK,YAAY,IAAI,CAAC,CAAC;AAAA,EACrE;AAAA,EAEA,QAAQ;AACN,eAAW,QAAQ,KAAK,QAAQ;AAC9B,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AACF;AAQO,MAAM,+BAA+B,sBAAY;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA,iBAAyB;AAAA,EACzB,gBAA8B,IAAI,oBAAO;AAAA,EACzC,oBAAkC,IAAI,oBAAO;AAAA,EAErD,YAAY,MAAY,SAA6B;AACnD,UAAM,QAAQ,YAAY,QAAW,EAAE,OAAO,KAAK,CAAC;AACpD,SAAK,OAAO;AACZ,SAAK,UAAU;AACf,SAAK,cAAc,IAAI,4BAAY,QAAQ,YAAY,QAAQ,WAAW;AAAA,EAC5E;AAAA,EAEA,IAAI,aAAsB;AACxB,WAAO,KAAK,cAAc;AAAA,EAC5B;AAAA,EAEA,MAAM,MAAM,QAAoC;AAC9C,UAAM,KAAK,aAAa,MAAM;AAAA,EAChC;AAAA,EAEA,MAAM,aAAa,OAAkC;AACnD,UAAM,KAAK,cAAc;AAEzB,UAAM,aAAa,KAAK;AAGxB,SAAK,kBAAkB,MAAM,oBAAoB,MAAM;AACvD,UAAM,KAAK,YAAY,aAAa,KAAK;AAAA,EAC3C;AAAA,EAEA,MAAc,mBAAmB,iBAAiD;AAChF,UAAM,cAAc,IAAI,oBAAgB;AAExC,UAAM,eAAe,MAAM;AACzB,UAAI,CAAC,YAAY,KAAM,aAAY,QAAQ,IAAI;AAAA,IACjD;AAEA,oBAAgB,OAAO,iBAAiB,SAAS,YAAY;AAE7D,SAAK,YAAY,eAAe,EAAE,QAAQ,MAAM;AAC9C,sBAAgB,OAAO,oBAAoB,SAAS,YAAY;AAChE,UAAI,CAAC,YAAY,KAAM,aAAY,QAAQ,KAAK;AAAA,IAClD,CAAC;AAED,UAAM,cAAc,MAAM,QAAQ,KAAK;AAAA,MACrC,YAAY;AAAA,MACZ,KAAK,kBAAkB,MAAM,KAAK,MAAM,IAAI;AAAA,IAC9C,CAAC;AAED,QAAI,iBAAiB,KAAK;AAE1B,QAAI,aAAa;AAGf,uBAAiB,KAAK,IAAI,KAAK,iBAAiB,KAAK,YAAY,iBAAiB,KAAM,CAAC;AACzF,WAAK,YAAY,WAAW;AAAA,IAC9B;AAEA,SAAK,iBAAiB;AACtB,SAAK,oBAAoB,IAAI,oBAAO;AACpC,SAAK,mBAAmB;AAAA,MACtB,kBAAkB;AAAA,MAClB;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,UAAM,MAAM;AAEZ,QAAI,CAAC,KAAK,gBAAgB;AACxB;AAAA,IACF;AAEA,QAAI,KAAK,aAAa,CAAC,KAAK,UAAU,MAAM;AAC1C,WAAK,OAAO,MAAM,4CAA4C;AAC9D,WAAK,UAAU,OAAO;AAAA,IACxB;AAEA,SAAK,YAAY,kBAAK,KAAK,CAAC,eAAe,KAAK,mBAAmB,UAAU,CAAC;AAAA,EAChF;AAAA,EAEA,cAAoB;AAClB,QAAI,CAAC,KAAK,gBAAgB;AACxB;AAAA,IACF;AAEA,SAAK,kBAAkB,QAAQ;AAAA,EACjC;AAAA,EAEA,MAAc,aAAa,QAAqB;AAhalD;AAiaI,UAAM,QAAQ,gCAAgB,iBAAiB,gBAAgB,KAAK,WAAW;AAC/E,SAAK,cAAc,QAAM,UAAK,KAAK,qBAAV,mBAA4B;AAAA,MACnD;AAAA,MACA,IAAI,oCAAoB,EAAE,QAAQ,4BAAY,kBAAkB,CAAC;AAAA;AAGnE,QAAI,OAAO,SAAS;AAClB;AAAA,IACF;AAEA,YAAM,UAAK,gBAAL,mBAAkB;AAExB,QAAI,CAAC,KAAK,cAAc,MAAM;AAC5B,WAAK,cAAc,QAAQ;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ;AAEZ,UAAM,KAAK,YAAY,MAAM;AAAA,EAC/B;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/voice/room_io/_output.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { RemoteParticipant } from '@livekit/rtc-node';\nimport {\n type AudioFrame,\n AudioSource,\n LocalAudioTrack,\n type LocalTrackPublication,\n type Participant,\n type RemoteTrackPublication,\n type Room,\n RoomEvent,\n type TextStreamWriter,\n TrackPublishOptions,\n TrackSource,\n} from '@livekit/rtc-node';\nimport {\n ATTRIBUTE_TRANSCRIPTION_FINAL,\n ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID,\n ATTRIBUTE_TRANSCRIPTION_TRACK_ID,\n TOPIC_TRANSCRIPTION,\n} from '../../constants.js';\nimport { log } from '../../log.js';\nimport { Future, Task, shortuuid } from '../../utils.js';\nimport { AudioOutput, TextOutput } from '../io.js';\nimport { findMicrophoneTrackId } from '../transcription/index.js';\n\nabstract class BaseParticipantTranscriptionOutput extends TextOutput {\n protected room: Room;\n protected isDeltaStream: boolean;\n protected participantIdentity: string | null = null;\n protected trackId?: string;\n protected capturing: boolean = false;\n protected latestText: string = '';\n protected currentId: string = this.generateCurrentId();\n protected logger = log();\n\n constructor(room: Room, isDeltaStream: boolean, participant: Participant | string | null) {\n super();\n this.room = room;\n this.isDeltaStream = isDeltaStream;\n\n this.room.on(RoomEvent.TrackPublished, this.onTrackPublished);\n this.room.on(RoomEvent.LocalTrackPublished, this.onLocalTrackPublished);\n\n this.setParticipant(participant);\n }\n\n setParticipant(participant: Participant | string | null) {\n if (typeof participant === 'string' || participant === null) {\n this.participantIdentity = participant;\n } else {\n this.participantIdentity = participant.identity;\n }\n\n if (!this.participantIdentity) {\n return;\n }\n\n try {\n this.trackId = findMicrophoneTrackId(this.room, this.participantIdentity);\n } catch (error) {\n // track id is optional for TextStream when audio is not published\n }\n\n this.flush();\n this.resetState();\n }\n\n protected onTrackPublished = (track: RemoteTrackPublication, participant: RemoteParticipant) => {\n if (\n !this.participantIdentity ||\n participant.identity !== this.participantIdentity ||\n track.source !== TrackSource.SOURCE_MICROPHONE\n ) {\n return;\n }\n\n this.trackId = track.sid;\n };\n\n protected onLocalTrackPublished = (track: LocalTrackPublication) => {\n if (\n !this.participantIdentity ||\n this.participantIdentity !== this.room.localParticipant?.identity ||\n track.source !== TrackSource.SOURCE_MICROPHONE\n ) {\n return;\n }\n\n this.trackId = track.sid;\n };\n\n protected generateCurrentId(): string {\n return shortuuid('SG_');\n }\n\n protected resetState() {\n this.currentId = this.generateCurrentId();\n this.capturing = false;\n this.latestText = '';\n }\n\n async captureText(text: string) {\n if (!this.participantIdentity) {\n return;\n }\n\n this.latestText = text;\n await this.handleCaptureText(text);\n }\n\n flush() {\n if (!this.participantIdentity || !this.capturing) {\n return;\n }\n\n this.capturing = false;\n this.handleFlush();\n }\n\n protected abstract handleCaptureText(text: string): Promise<void>;\n protected abstract handleFlush(): void;\n}\n\nexport class ParticipantTranscriptionOutput extends BaseParticipantTranscriptionOutput {\n private writer: TextStreamWriter | null = null;\n private flushTask: Task<void> | null = null;\n\n protected async handleCaptureText(text: string): Promise<void> {\n if (this.flushTask && !this.flushTask.done) {\n await this.flushTask.result;\n }\n\n if (!this.capturing) {\n this.resetState();\n this.capturing = true;\n }\n\n try {\n if (this.room.isConnected) {\n if (this.isDeltaStream) {\n // reuse the existing writer\n if (this.writer === null) {\n this.writer = await this.createTextWriter();\n }\n await this.writer.write(text);\n } else {\n const tmpWriter = await this.createTextWriter();\n await tmpWriter.write(text);\n await tmpWriter.close();\n }\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n\n protected handleFlush() {\n const currWriter = this.writer;\n this.writer = null;\n this.flushTask = Task.from((controller) => this.flushTaskImpl(currWriter, controller.signal));\n }\n\n private async createTextWriter(attributes?: Record<string, string>): Promise<TextStreamWriter> {\n if (!this.participantIdentity) {\n throw new Error('participantIdentity not found');\n }\n\n if (!this.room.localParticipant) {\n throw new Error('localParticipant not found');\n }\n\n if (!attributes) {\n attributes = {\n [ATTRIBUTE_TRANSCRIPTION_FINAL]: 'false',\n };\n if (this.trackId) {\n attributes[ATTRIBUTE_TRANSCRIPTION_TRACK_ID] = this.trackId;\n }\n }\n attributes[ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID] = this.currentId;\n\n return await this.room.localParticipant.streamText({\n topic: TOPIC_TRANSCRIPTION,\n senderIdentity: this.participantIdentity,\n attributes,\n });\n }\n\n private async flushTaskImpl(writer: TextStreamWriter | null, signal: AbortSignal): Promise<void> {\n const attributes: Record<string, string> = {\n [ATTRIBUTE_TRANSCRIPTION_FINAL]: 'true',\n };\n if (this.trackId) {\n attributes[ATTRIBUTE_TRANSCRIPTION_TRACK_ID] = this.trackId;\n }\n\n const abortPromise = new Promise<void>((resolve) => {\n signal.addEventListener('abort', () => resolve());\n });\n\n try {\n if (this.room.isConnected) {\n if (this.isDeltaStream) {\n if (writer) {\n await Promise.race([writer.close(), abortPromise]);\n }\n } else {\n const tmpWriter = await Promise.race([this.createTextWriter(attributes), abortPromise]);\n if (signal.aborted || !tmpWriter) {\n return;\n }\n await Promise.race([tmpWriter.write(this.latestText), abortPromise]);\n if (signal.aborted) {\n return;\n }\n await Promise.race([tmpWriter.close(), abortPromise]);\n }\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n}\n\nexport class ParticipantLegacyTranscriptionOutput extends BaseParticipantTranscriptionOutput {\n private pushedText: string = '';\n private flushTask: Promise<void> | null = null;\n\n protected async handleCaptureText(text: string): Promise<void> {\n if (!this.trackId) {\n return;\n }\n\n if (this.flushTask) {\n await this.flushTask;\n }\n\n if (!this.capturing) {\n this.resetState();\n this.capturing = true;\n }\n\n if (this.isDeltaStream) {\n this.pushedText += text;\n } else {\n this.pushedText = text;\n }\n\n await this.publishTranscription(this.currentId, this.pushedText, false);\n }\n\n protected handleFlush() {\n if (!this.trackId) {\n return;\n }\n\n this.flushTask = this.publishTranscription(this.currentId, this.pushedText, true);\n this.resetState();\n }\n\n async publishTranscription(id: string, text: string, final: boolean, signal?: AbortSignal) {\n if (!this.participantIdentity || !this.trackId) {\n return;\n }\n\n try {\n if (this.room.isConnected) {\n if (signal?.aborted) {\n return;\n }\n\n await this.room.localParticipant?.publishTranscription({\n participantIdentity: this.participantIdentity,\n trackSid: this.trackId,\n segments: [{ id, text, final, startTime: BigInt(0), endTime: BigInt(0), language: '' }],\n });\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n\n protected resetState() {\n super.resetState();\n this.pushedText = '';\n }\n}\n\nexport class ParalellTextOutput extends TextOutput {\n /** @internal */\n _sinks: TextOutput[];\n\n constructor(sinks: TextOutput[], nextInChain?: TextOutput) {\n super(nextInChain);\n this._sinks = sinks;\n }\n\n async captureText(text: string) {\n await Promise.all(this._sinks.map((sink) => sink.captureText(text)));\n }\n\n flush() {\n for (const sink of this._sinks) {\n sink.flush();\n }\n }\n}\n\nexport interface AudioOutputOptions {\n sampleRate: number;\n numChannels: number;\n trackPublishOptions: TrackPublishOptions;\n queueSizeMs?: number;\n}\nexport class ParticipantAudioOutput extends AudioOutput {\n private room: Room;\n private options: AudioOutputOptions;\n private audioSource: AudioSource;\n private publication?: LocalTrackPublication;\n private flushTask?: Task<void>;\n\n /** Duration of audio pushed to the source, in seconds */\n private pushedDuration: number = 0;\n private startedFuture: Future<void> = new Future();\n private interruptedFuture: Future<void> = new Future();\n private firstFrameEmitted: boolean = false;\n\n constructor(room: Room, options: AudioOutputOptions) {\n super(options.sampleRate, undefined, { pause: true });\n this.room = room;\n this.options = options;\n this.audioSource = new AudioSource(options.sampleRate, options.numChannels);\n }\n\n get subscribed(): boolean {\n return this.startedFuture.done;\n }\n\n async start(signal: AbortSignal): Promise<void> {\n await this.publishTrack(signal);\n }\n\n async captureFrame(frame: AudioFrame): Promise<void> {\n await this.startedFuture.await;\n\n super.captureFrame(frame);\n\n if (!this.firstFrameEmitted) {\n this.firstFrameEmitted = true;\n this.onPlaybackStarted(Date.now());\n }\n\n // TODO(AJS-102): use frame.durationMs once available in rtc-node\n this.pushedDuration += frame.samplesPerChannel / frame.sampleRate;\n await this.audioSource.captureFrame(frame);\n }\n\n private async waitForPlayoutTask(abortController: AbortController): Promise<void> {\n const abortFuture = new Future<boolean>();\n\n const resolveAbort = () => {\n if (!abortFuture.done) abortFuture.resolve(true);\n };\n\n abortController.signal.addEventListener('abort', resolveAbort);\n\n this.audioSource.waitForPlayout().finally(() => {\n abortController.signal.removeEventListener('abort', resolveAbort);\n if (!abortFuture.done) abortFuture.resolve(false);\n });\n\n const interrupted = await Promise.race([\n abortFuture.await,\n this.interruptedFuture.await.then(() => true),\n ]);\n\n let pushedDuration = this.pushedDuration;\n\n if (interrupted) {\n // Calculate actual played duration accounting for queued audio\n // Note: queuedDuration is in milliseconds, pushedDuration is in seconds\n pushedDuration = Math.max(this.pushedDuration - this.audioSource.queuedDuration / 1000, 0);\n this.audioSource.clearQueue();\n }\n\n this.pushedDuration = 0;\n this.interruptedFuture = new Future();\n this.firstFrameEmitted = false;\n\n this.onPlaybackFinished({\n playbackPosition: pushedDuration,\n interrupted,\n });\n }\n\n /**\n * Flush any buffered audio, marking the current playback/segment as complete\n */\n flush(): void {\n super.flush();\n\n if (!this.pushedDuration) {\n return;\n }\n\n if (this.flushTask && !this.flushTask.done) {\n this.logger.error('flush called while playback is in progress');\n this.flushTask.cancel();\n }\n\n this.flushTask = Task.from((controller) => this.waitForPlayoutTask(controller));\n }\n\n clearBuffer(): void {\n if (!this.pushedDuration) {\n return;\n }\n\n this.interruptedFuture.resolve();\n }\n\n private async publishTrack(signal: AbortSignal) {\n const track = LocalAudioTrack.createAudioTrack('roomio_audio', this.audioSource);\n this.publication = await this.room.localParticipant?.publishTrack(\n track,\n new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE }),\n );\n\n if (signal.aborted) {\n return;\n }\n\n await this.publication?.waitForSubscription();\n\n if (!this.startedFuture.done) {\n this.startedFuture.resolve();\n }\n }\n\n async close() {\n // TODO(AJS-106): add republish track\n await this.audioSource.close();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,sBAYO;AACP,uBAKO;AACP,iBAAoB;AACpB,mBAAwC;AACxC,gBAAwC;AACxC,2BAAsC;AAEtC,MAAe,2CAA2C,qBAAW;AAAA,EACzD;AAAA,EACA;AAAA,EACA,sBAAqC;AAAA,EACrC;AAAA,EACA,YAAqB;AAAA,EACrB,aAAqB;AAAA,EACrB,YAAoB,KAAK,kBAAkB;AAAA,EAC3C,aAAS,gBAAI;AAAA,EAEvB,YAAY,MAAY,eAAwB,aAA0C;AACxF,UAAM;AACN,SAAK,OAAO;AACZ,SAAK,gBAAgB;AAErB,SAAK,KAAK,GAAG,0BAAU,gBAAgB,KAAK,gBAAgB;AAC5D,SAAK,KAAK,GAAG,0BAAU,qBAAqB,KAAK,qBAAqB;AAEtE,SAAK,eAAe,WAAW;AAAA,EACjC;AAAA,EAEA,eAAe,aAA0C;AACvD,QAAI,OAAO,gBAAgB,YAAY,gBAAgB,MAAM;AAC3D,WAAK,sBAAsB;AAAA,IAC7B,OAAO;AACL,WAAK,sBAAsB,YAAY;AAAA,IACzC;AAEA,QAAI,CAAC,KAAK,qBAAqB;AAC7B;AAAA,IACF;AAEA,QAAI;AACF,WAAK,cAAU,4CAAsB,KAAK,MAAM,KAAK,mBAAmB;AAAA,IAC1E,SAAS,OAAO;AAAA,IAEhB;AAEA,SAAK,MAAM;AACX,SAAK,WAAW;AAAA,EAClB;AAAA,EAEU,mBAAmB,CAAC,OAA+B,gBAAmC;AAC9F,QACE,CAAC,KAAK,uBACN,YAAY,aAAa,KAAK,uBAC9B,MAAM,WAAW,4BAAY,mBAC7B;AACA;AAAA,IACF;AAEA,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA,EAEU,wBAAwB,CAAC,UAAiC;AAlFtE;AAmFI,QACE,CAAC,KAAK,uBACN,KAAK,0BAAwB,UAAK,KAAK,qBAAV,mBAA4B,aACzD,MAAM,WAAW,4BAAY,mBAC7B;AACA;AAAA,IACF;AAEA,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA,EAEU,oBAA4B;AACpC,eAAO,wBAAU,KAAK;AAAA,EACxB;AAAA,EAEU,aAAa;AACrB,SAAK,YAAY,KAAK,kBAAkB;AACxC,SAAK,YAAY;AACjB,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,MAAM,YAAY,MAAc;AAC9B,QAAI,CAAC,KAAK,qBAAqB;AAC7B;AAAA,IACF;AAEA,SAAK,aAAa;AAClB,UAAM,KAAK,kBAAkB,IAAI;AAAA,EACnC;AAAA,EAEA,QAAQ;AACN,QAAI,CAAC,KAAK,uBAAuB,CAAC,KAAK,WAAW;AAChD;AAAA,IACF;AAEA,SAAK,YAAY;AACjB,SAAK,YAAY;AAAA,EACnB;AAIF;AAEO,MAAM,uCAAuC,mCAAmC;AAAA,EAC7E,SAAkC;AAAA,EAClC,YAA+B;AAAA,EAEvC,MAAgB,kBAAkB,MAA6B;AAC7D,QAAI,KAAK,aAAa,CAAC,KAAK,UAAU,MAAM;AAC1C,YAAM,KAAK,UAAU;AAAA,IACvB;AAEA,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,WAAW;AAChB,WAAK,YAAY;AAAA,IACnB;AAEA,QAAI;AACF,UAAI,KAAK,KAAK,aAAa;AACzB,YAAI,KAAK,eAAe;AAEtB,cAAI,KAAK,WAAW,MAAM;AACxB,iBAAK,SAAS,MAAM,KAAK,iBAAiB;AAAA,UAC5C;AACA,gBAAM,KAAK,OAAO,MAAM,IAAI;AAAA,QAC9B,OAAO;AACL,gBAAM,YAAY,MAAM,KAAK,iBAAiB;AAC9C,gBAAM,UAAU,MAAM,IAAI;AAC1B,gBAAM,UAAU,MAAM;AAAA,QACxB;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,OAAO,iCAAiC;AAAA,IAC5D;AAAA,EACF;AAAA,EAEU,cAAc;AACtB,UAAM,aAAa,KAAK;AACxB,SAAK,SAAS;AACd,SAAK,YAAY,kBAAK,KAAK,CAAC,eAAe,KAAK,cAAc,YAAY,WAAW,MAAM,CAAC;AAAA,EAC9F;AAAA,EAEA,MAAc,iBAAiB,YAAgE;AAC7F,QAAI,CAAC,KAAK,qBAAqB;AAC7B,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AAEA,QAAI,CAAC,KAAK,KAAK,kBAAkB;AAC/B,YAAM,IAAI,MAAM,4BAA4B;AAAA,IAC9C;AAEA,QAAI,CAAC,YAAY;AACf,mBAAa;AAAA,QACX,CAAC,8CAA6B,GAAG;AAAA,MACnC;AACA,UAAI,KAAK,SAAS;AAChB,mBAAW,iDAAgC,IAAI,KAAK;AAAA,MACtD;AAAA,IACF;AACA,eAAW,mDAAkC,IAAI,KAAK;AAEtD,WAAO,MAAM,KAAK,KAAK,iBAAiB,WAAW;AAAA,MACjD,OAAO;AAAA,MACP,gBAAgB,KAAK;AAAA,MACrB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,cAAc,QAAiC,QAAoC;AAC/F,UAAM,aAAqC;AAAA,MACzC,CAAC,8CAA6B,GAAG;AAAA,IACnC;AACA,QAAI,KAAK,SAAS;AAChB,iBAAW,iDAAgC,IAAI,KAAK;AAAA,IACtD;AAEA,UAAM,eAAe,IAAI,QAAc,CAAC,YAAY;AAClD,aAAO,iBAAiB,SAAS,MAAM,QAAQ,CAAC;AAAA,IAClD,CAAC;AAED,QAAI;AACF,UAAI,KAAK,KAAK,aAAa;AACzB,YAAI,KAAK,eAAe;AACtB,cAAI,QAAQ;AACV,kBAAM,QAAQ,KAAK,CAAC,OAAO,MAAM,GAAG,YAAY,CAAC;AAAA,UACnD;AAAA,QACF,OAAO;AACL,gBAAM,YAAY,MAAM,QAAQ,KAAK,CAAC,KAAK,iBAAiB,UAAU,GAAG,YAAY,CAAC;AACtF,cAAI,OAAO,WAAW,CAAC,WAAW;AAChC;AAAA,UACF;AACA,gBAAM,QAAQ,KAAK,CAAC,UAAU,MAAM,KAAK,UAAU,GAAG,YAAY,CAAC;AACnE,cAAI,OAAO,SAAS;AAClB;AAAA,UACF;AACA,gBAAM,QAAQ,KAAK,CAAC,UAAU,MAAM,GAAG,YAAY,CAAC;AAAA,QACtD;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,OAAO,iCAAiC;AAAA,IAC5D;AAAA,EACF;AACF;AAEO,MAAM,6CAA6C,mCAAmC;AAAA,EACnF,aAAqB;AAAA,EACrB,YAAkC;AAAA,EAE1C,MAAgB,kBAAkB,MAA6B;AAC7D,QAAI,CAAC,KAAK,SAAS;AACjB;AAAA,IACF;AAEA,QAAI,KAAK,WAAW;AAClB,YAAM,KAAK;AAAA,IACb;AAEA,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,WAAW;AAChB,WAAK,YAAY;AAAA,IACnB;AAEA,QAAI,KAAK,eAAe;AACtB,WAAK,cAAc;AAAA,IACrB,OAAO;AACL,WAAK,aAAa;AAAA,IACpB;AAEA,UAAM,KAAK,qBAAqB,KAAK,WAAW,KAAK,YAAY,KAAK;AAAA,EACxE;AAAA,EAEU,cAAc;AACtB,QAAI,CAAC,KAAK,SAAS;AACjB;AAAA,IACF;AAEA,SAAK,YAAY,KAAK,qBAAqB,KAAK,WAAW,KAAK,YAAY,IAAI;AAChF,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAM,qBAAqB,IAAY,MAAc,OAAgB,QAAsB;AAvQ7F;AAwQI,QAAI,CAAC,KAAK,uBAAuB,CAAC,KAAK,SAAS;AAC9C;AAAA,IACF;AAEA,QAAI;AACF,UAAI,KAAK,KAAK,aAAa;AACzB,YAAI,iCAAQ,SAAS;AACnB;AAAA,QACF;AAEA,gBAAM,UAAK,KAAK,qBAAV,mBAA4B,qBAAqB;AAAA,UACrD,qBAAqB,KAAK;AAAA,UAC1B,UAAU,KAAK;AAAA,UACf,UAAU,CAAC,EAAE,IAAI,MAAM,OAAO,WAAW,OAAO,CAAC,GAAG,SAAS,OAAO,CAAC,GAAG,UAAU,GAAG,CAAC;AAAA,QACxF;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,OAAO,iCAAiC;AAAA,IAC5D;AAAA,EACF;AAAA,EAEU,aAAa;AACrB,UAAM,WAAW;AACjB,SAAK,aAAa;AAAA,EACpB;AACF;AAEO,MAAM,2BAA2B,qBAAW;AAAA;AAAA,EAEjD;AAAA,EAEA,YAAY,OAAqB,aAA0B;AACzD,UAAM,WAAW;AACjB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,YAAY,MAAc;AAC9B,UAAM,QAAQ,IAAI,KAAK,OAAO,IAAI,CAAC,SAAS,KAAK,YAAY,IAAI,CAAC,CAAC;AAAA,EACrE;AAAA,EAEA,QAAQ;AACN,eAAW,QAAQ,KAAK,QAAQ;AAC9B,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AACF;AAQO,MAAM,+BAA+B,sBAAY;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA,iBAAyB;AAAA,EACzB,gBAA8B,IAAI,oBAAO;AAAA,EACzC,oBAAkC,IAAI,oBAAO;AAAA,EAC7C,oBAA6B;AAAA,EAErC,YAAY,MAAY,SAA6B;AACnD,UAAM,QAAQ,YAAY,QAAW,EAAE,OAAO,KAAK,CAAC;AACpD,SAAK,OAAO;AACZ,SAAK,UAAU;AACf,SAAK,cAAc,IAAI,4BAAY,QAAQ,YAAY,QAAQ,WAAW;AAAA,EAC5E;AAAA,EAEA,IAAI,aAAsB;AACxB,WAAO,KAAK,cAAc;AAAA,EAC5B;AAAA,EAEA,MAAM,MAAM,QAAoC;AAC9C,UAAM,KAAK,aAAa,MAAM;AAAA,EAChC;AAAA,EAEA,MAAM,aAAa,OAAkC;AACnD,UAAM,KAAK,cAAc;AAEzB,UAAM,aAAa,KAAK;AAExB,QAAI,CAAC,KAAK,mBAAmB;AAC3B,WAAK,oBAAoB;AACzB,WAAK,kBAAkB,KAAK,IAAI,CAAC;AAAA,IACnC;AAGA,SAAK,kBAAkB,MAAM,oBAAoB,MAAM;AACvD,UAAM,KAAK,YAAY,aAAa,KAAK;AAAA,EAC3C;AAAA,EAEA,MAAc,mBAAmB,iBAAiD;AAChF,UAAM,cAAc,IAAI,oBAAgB;AAExC,UAAM,eAAe,MAAM;AACzB,UAAI,CAAC,YAAY,KAAM,aAAY,QAAQ,IAAI;AAAA,IACjD;AAEA,oBAAgB,OAAO,iBAAiB,SAAS,YAAY;AAE7D,SAAK,YAAY,eAAe,EAAE,QAAQ,MAAM;AAC9C,sBAAgB,OAAO,oBAAoB,SAAS,YAAY;AAChE,UAAI,CAAC,YAAY,KAAM,aAAY,QAAQ,KAAK;AAAA,IAClD,CAAC;AAED,UAAM,cAAc,MAAM,QAAQ,KAAK;AAAA,MACrC,YAAY;AAAA,MACZ,KAAK,kBAAkB,MAAM,KAAK,MAAM,IAAI;AAAA,IAC9C,CAAC;AAED,QAAI,iBAAiB,KAAK;AAE1B,QAAI,aAAa;AAGf,uBAAiB,KAAK,IAAI,KAAK,iBAAiB,KAAK,YAAY,iBAAiB,KAAM,CAAC;AACzF,WAAK,YAAY,WAAW;AAAA,IAC9B;AAEA,SAAK,iBAAiB;AACtB,SAAK,oBAAoB,IAAI,oBAAO;AACpC,SAAK,oBAAoB;AAEzB,SAAK,mBAAmB;AAAA,MACtB,kBAAkB;AAAA,MAClB;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,UAAM,MAAM;AAEZ,QAAI,CAAC,KAAK,gBAAgB;AACxB;AAAA,IACF;AAEA,QAAI,KAAK,aAAa,CAAC,KAAK,UAAU,MAAM;AAC1C,WAAK,OAAO,MAAM,4CAA4C;AAC9D,WAAK,UAAU,OAAO;AAAA,IACxB;AAEA,SAAK,YAAY,kBAAK,KAAK,CAAC,eAAe,KAAK,mBAAmB,UAAU,CAAC;AAAA,EAChF;AAAA,EAEA,cAAoB;AAClB,QAAI,CAAC,KAAK,gBAAgB;AACxB;AAAA,IACF;AAEA,SAAK,kBAAkB,QAAQ;AAAA,EACjC;AAAA,EAEA,MAAc,aAAa,QAAqB;AAxalD;AAyaI,UAAM,QAAQ,gCAAgB,iBAAiB,gBAAgB,KAAK,WAAW;AAC/E,SAAK,cAAc,QAAM,UAAK,KAAK,qBAAV,mBAA4B;AAAA,MACnD;AAAA,MACA,IAAI,oCAAoB,EAAE,QAAQ,4BAAY,kBAAkB,CAAC;AAAA;AAGnE,QAAI,OAAO,SAAS;AAClB;AAAA,IACF;AAEA,YAAM,UAAK,gBAAL,mBAAkB;AAExB,QAAI,CAAC,KAAK,cAAc,MAAM;AAC5B,WAAK,cAAc,QAAQ;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ;AAEZ,UAAM,KAAK,YAAY,MAAM;AAAA,EAC/B;AACF;","names":[]}
|
|
@@ -60,6 +60,7 @@ export declare class ParticipantAudioOutput extends AudioOutput {
|
|
|
60
60
|
private pushedDuration;
|
|
61
61
|
private startedFuture;
|
|
62
62
|
private interruptedFuture;
|
|
63
|
+
private firstFrameEmitted;
|
|
63
64
|
constructor(room: Room, options: AudioOutputOptions);
|
|
64
65
|
get subscribed(): boolean;
|
|
65
66
|
start(signal: AbortSignal): Promise<void>;
|
|
@@ -60,6 +60,7 @@ export declare class ParticipantAudioOutput extends AudioOutput {
|
|
|
60
60
|
private pushedDuration;
|
|
61
61
|
private startedFuture;
|
|
62
62
|
private interruptedFuture;
|
|
63
|
+
private firstFrameEmitted;
|
|
63
64
|
constructor(room: Room, options: AudioOutputOptions);
|
|
64
65
|
get subscribed(): boolean;
|
|
65
66
|
start(signal: AbortSignal): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_output.d.ts","sourceRoot":"","sources":["../../../src/voice/room_io/_output.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"_output.d.ts","sourceRoot":"","sources":["../../../src/voice/room_io/_output.ts"],"names":[],"mappings":"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"}
|
|
@@ -251,6 +251,7 @@ class ParticipantAudioOutput extends AudioOutput {
|
|
|
251
251
|
pushedDuration = 0;
|
|
252
252
|
startedFuture = new Future();
|
|
253
253
|
interruptedFuture = new Future();
|
|
254
|
+
firstFrameEmitted = false;
|
|
254
255
|
constructor(room, options) {
|
|
255
256
|
super(options.sampleRate, void 0, { pause: true });
|
|
256
257
|
this.room = room;
|
|
@@ -266,6 +267,10 @@ class ParticipantAudioOutput extends AudioOutput {
|
|
|
266
267
|
async captureFrame(frame) {
|
|
267
268
|
await this.startedFuture.await;
|
|
268
269
|
super.captureFrame(frame);
|
|
270
|
+
if (!this.firstFrameEmitted) {
|
|
271
|
+
this.firstFrameEmitted = true;
|
|
272
|
+
this.onPlaybackStarted(Date.now());
|
|
273
|
+
}
|
|
269
274
|
this.pushedDuration += frame.samplesPerChannel / frame.sampleRate;
|
|
270
275
|
await this.audioSource.captureFrame(frame);
|
|
271
276
|
}
|
|
@@ -290,6 +295,7 @@ class ParticipantAudioOutput extends AudioOutput {
|
|
|
290
295
|
}
|
|
291
296
|
this.pushedDuration = 0;
|
|
292
297
|
this.interruptedFuture = new Future();
|
|
298
|
+
this.firstFrameEmitted = false;
|
|
293
299
|
this.onPlaybackFinished({
|
|
294
300
|
playbackPosition: pushedDuration,
|
|
295
301
|
interrupted
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/voice/room_io/_output.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { RemoteParticipant } from '@livekit/rtc-node';\nimport {\n type AudioFrame,\n AudioSource,\n LocalAudioTrack,\n type LocalTrackPublication,\n type Participant,\n type RemoteTrackPublication,\n type Room,\n RoomEvent,\n type TextStreamWriter,\n TrackPublishOptions,\n TrackSource,\n} from '@livekit/rtc-node';\nimport {\n ATTRIBUTE_TRANSCRIPTION_FINAL,\n ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID,\n ATTRIBUTE_TRANSCRIPTION_TRACK_ID,\n TOPIC_TRANSCRIPTION,\n} from '../../constants.js';\nimport { log } from '../../log.js';\nimport { Future, Task, shortuuid } from '../../utils.js';\nimport { AudioOutput, TextOutput } from '../io.js';\nimport { findMicrophoneTrackId } from '../transcription/index.js';\n\nabstract class BaseParticipantTranscriptionOutput extends TextOutput {\n protected room: Room;\n protected isDeltaStream: boolean;\n protected participantIdentity: string | null = null;\n protected trackId?: string;\n protected capturing: boolean = false;\n protected latestText: string = '';\n protected currentId: string = this.generateCurrentId();\n protected logger = log();\n\n constructor(room: Room, isDeltaStream: boolean, participant: Participant | string | null) {\n super();\n this.room = room;\n this.isDeltaStream = isDeltaStream;\n\n this.room.on(RoomEvent.TrackPublished, this.onTrackPublished);\n this.room.on(RoomEvent.LocalTrackPublished, this.onLocalTrackPublished);\n\n this.setParticipant(participant);\n }\n\n setParticipant(participant: Participant | string | null) {\n if (typeof participant === 'string' || participant === null) {\n this.participantIdentity = participant;\n } else {\n this.participantIdentity = participant.identity;\n }\n\n if (!this.participantIdentity) {\n return;\n }\n\n try {\n this.trackId = findMicrophoneTrackId(this.room, this.participantIdentity);\n } catch (error) {\n // track id is optional for TextStream when audio is not published\n }\n\n this.flush();\n this.resetState();\n }\n\n protected onTrackPublished = (track: RemoteTrackPublication, participant: RemoteParticipant) => {\n if (\n !this.participantIdentity ||\n participant.identity !== this.participantIdentity ||\n track.source !== TrackSource.SOURCE_MICROPHONE\n ) {\n return;\n }\n\n this.trackId = track.sid;\n };\n\n protected onLocalTrackPublished = (track: LocalTrackPublication) => {\n if (\n !this.participantIdentity ||\n this.participantIdentity !== this.room.localParticipant?.identity ||\n track.source !== TrackSource.SOURCE_MICROPHONE\n ) {\n return;\n }\n\n this.trackId = track.sid;\n };\n\n protected generateCurrentId(): string {\n return shortuuid('SG_');\n }\n\n protected resetState() {\n this.currentId = this.generateCurrentId();\n this.capturing = false;\n this.latestText = '';\n }\n\n async captureText(text: string) {\n if (!this.participantIdentity) {\n return;\n }\n\n this.latestText = text;\n await this.handleCaptureText(text);\n }\n\n flush() {\n if (!this.participantIdentity || !this.capturing) {\n return;\n }\n\n this.capturing = false;\n this.handleFlush();\n }\n\n protected abstract handleCaptureText(text: string): Promise<void>;\n protected abstract handleFlush(): void;\n}\n\nexport class ParticipantTranscriptionOutput extends BaseParticipantTranscriptionOutput {\n private writer: TextStreamWriter | null = null;\n private flushTask: Task<void> | null = null;\n\n protected async handleCaptureText(text: string): Promise<void> {\n if (this.flushTask && !this.flushTask.done) {\n await this.flushTask.result;\n }\n\n if (!this.capturing) {\n this.resetState();\n this.capturing = true;\n }\n\n try {\n if (this.room.isConnected) {\n if (this.isDeltaStream) {\n // reuse the existing writer\n if (this.writer === null) {\n this.writer = await this.createTextWriter();\n }\n await this.writer.write(text);\n } else {\n const tmpWriter = await this.createTextWriter();\n await tmpWriter.write(text);\n await tmpWriter.close();\n }\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n\n protected handleFlush() {\n const currWriter = this.writer;\n this.writer = null;\n this.flushTask = Task.from((controller) => this.flushTaskImpl(currWriter, controller.signal));\n }\n\n private async createTextWriter(attributes?: Record<string, string>): Promise<TextStreamWriter> {\n if (!this.participantIdentity) {\n throw new Error('participantIdentity not found');\n }\n\n if (!this.room.localParticipant) {\n throw new Error('localParticipant not found');\n }\n\n if (!attributes) {\n attributes = {\n [ATTRIBUTE_TRANSCRIPTION_FINAL]: 'false',\n };\n if (this.trackId) {\n attributes[ATTRIBUTE_TRANSCRIPTION_TRACK_ID] = this.trackId;\n }\n }\n attributes[ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID] = this.currentId;\n\n return await this.room.localParticipant.streamText({\n topic: TOPIC_TRANSCRIPTION,\n senderIdentity: this.participantIdentity,\n attributes,\n });\n }\n\n private async flushTaskImpl(writer: TextStreamWriter | null, signal: AbortSignal): Promise<void> {\n const attributes: Record<string, string> = {\n [ATTRIBUTE_TRANSCRIPTION_FINAL]: 'true',\n };\n if (this.trackId) {\n attributes[ATTRIBUTE_TRANSCRIPTION_TRACK_ID] = this.trackId;\n }\n\n const abortPromise = new Promise<void>((resolve) => {\n signal.addEventListener('abort', () => resolve());\n });\n\n try {\n if (this.room.isConnected) {\n if (this.isDeltaStream) {\n if (writer) {\n await Promise.race([writer.close(), abortPromise]);\n }\n } else {\n const tmpWriter = await Promise.race([this.createTextWriter(attributes), abortPromise]);\n if (signal.aborted || !tmpWriter) {\n return;\n }\n await Promise.race([tmpWriter.write(this.latestText), abortPromise]);\n if (signal.aborted) {\n return;\n }\n await Promise.race([tmpWriter.close(), abortPromise]);\n }\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n}\n\nexport class ParticipantLegacyTranscriptionOutput extends BaseParticipantTranscriptionOutput {\n private pushedText: string = '';\n private flushTask: Promise<void> | null = null;\n\n protected async handleCaptureText(text: string): Promise<void> {\n if (!this.trackId) {\n return;\n }\n\n if (this.flushTask) {\n await this.flushTask;\n }\n\n if (!this.capturing) {\n this.resetState();\n this.capturing = true;\n }\n\n if (this.isDeltaStream) {\n this.pushedText += text;\n } else {\n this.pushedText = text;\n }\n\n await this.publishTranscription(this.currentId, this.pushedText, false);\n }\n\n protected handleFlush() {\n if (!this.trackId) {\n return;\n }\n\n this.flushTask = this.publishTranscription(this.currentId, this.pushedText, true);\n this.resetState();\n }\n\n async publishTranscription(id: string, text: string, final: boolean, signal?: AbortSignal) {\n if (!this.participantIdentity || !this.trackId) {\n return;\n }\n\n try {\n if (this.room.isConnected) {\n if (signal?.aborted) {\n return;\n }\n\n await this.room.localParticipant?.publishTranscription({\n participantIdentity: this.participantIdentity,\n trackSid: this.trackId,\n segments: [{ id, text, final, startTime: BigInt(0), endTime: BigInt(0), language: '' }],\n });\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n\n protected resetState() {\n super.resetState();\n this.pushedText = '';\n }\n}\n\nexport class ParalellTextOutput extends TextOutput {\n /** @internal */\n _sinks: TextOutput[];\n\n constructor(sinks: TextOutput[], nextInChain?: TextOutput) {\n super(nextInChain);\n this._sinks = sinks;\n }\n\n async captureText(text: string) {\n await Promise.all(this._sinks.map((sink) => sink.captureText(text)));\n }\n\n flush() {\n for (const sink of this._sinks) {\n sink.flush();\n }\n }\n}\n\nexport interface AudioOutputOptions {\n sampleRate: number;\n numChannels: number;\n trackPublishOptions: TrackPublishOptions;\n queueSizeMs?: number;\n}\nexport class ParticipantAudioOutput extends AudioOutput {\n private room: Room;\n private options: AudioOutputOptions;\n private audioSource: AudioSource;\n private publication?: LocalTrackPublication;\n private flushTask?: Task<void>;\n\n /** Duration of audio pushed to the source, in seconds */\n private pushedDuration: number = 0;\n private startedFuture: Future<void> = new Future();\n private interruptedFuture: Future<void> = new Future();\n\n constructor(room: Room, options: AudioOutputOptions) {\n super(options.sampleRate, undefined, { pause: true });\n this.room = room;\n this.options = options;\n this.audioSource = new AudioSource(options.sampleRate, options.numChannels);\n }\n\n get subscribed(): boolean {\n return this.startedFuture.done;\n }\n\n async start(signal: AbortSignal): Promise<void> {\n await this.publishTrack(signal);\n }\n\n async captureFrame(frame: AudioFrame): Promise<void> {\n await this.startedFuture.await;\n\n super.captureFrame(frame);\n\n // TODO(AJS-102): use frame.durationMs once available in rtc-node\n this.pushedDuration += frame.samplesPerChannel / frame.sampleRate;\n await this.audioSource.captureFrame(frame);\n }\n\n private async waitForPlayoutTask(abortController: AbortController): Promise<void> {\n const abortFuture = new Future<boolean>();\n\n const resolveAbort = () => {\n if (!abortFuture.done) abortFuture.resolve(true);\n };\n\n abortController.signal.addEventListener('abort', resolveAbort);\n\n this.audioSource.waitForPlayout().finally(() => {\n abortController.signal.removeEventListener('abort', resolveAbort);\n if (!abortFuture.done) abortFuture.resolve(false);\n });\n\n const interrupted = await Promise.race([\n abortFuture.await,\n this.interruptedFuture.await.then(() => true),\n ]);\n\n let pushedDuration = this.pushedDuration;\n\n if (interrupted) {\n // Calculate actual played duration accounting for queued audio\n // Note: queuedDuration is in milliseconds, pushedDuration is in seconds\n pushedDuration = Math.max(this.pushedDuration - this.audioSource.queuedDuration / 1000, 0);\n this.audioSource.clearQueue();\n }\n\n this.pushedDuration = 0;\n this.interruptedFuture = new Future();\n this.onPlaybackFinished({\n playbackPosition: pushedDuration,\n interrupted,\n });\n }\n\n /**\n * Flush any buffered audio, marking the current playback/segment as complete\n */\n flush(): void {\n super.flush();\n\n if (!this.pushedDuration) {\n return;\n }\n\n if (this.flushTask && !this.flushTask.done) {\n this.logger.error('flush called while playback is in progress');\n this.flushTask.cancel();\n }\n\n this.flushTask = Task.from((controller) => this.waitForPlayoutTask(controller));\n }\n\n clearBuffer(): void {\n if (!this.pushedDuration) {\n return;\n }\n\n this.interruptedFuture.resolve();\n }\n\n private async publishTrack(signal: AbortSignal) {\n const track = LocalAudioTrack.createAudioTrack('roomio_audio', this.audioSource);\n this.publication = await this.room.localParticipant?.publishTrack(\n track,\n new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE }),\n );\n\n if (signal.aborted) {\n return;\n }\n\n await this.publication?.waitForSubscription();\n\n if (!this.startedFuture.done) {\n this.startedFuture.resolve();\n }\n }\n\n async close() {\n // TODO(AJS-106): add republish track\n await this.audioSource.close();\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/voice/room_io/_output.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { RemoteParticipant } from '@livekit/rtc-node';\nimport {\n type AudioFrame,\n AudioSource,\n LocalAudioTrack,\n type LocalTrackPublication,\n type Participant,\n type RemoteTrackPublication,\n type Room,\n RoomEvent,\n type TextStreamWriter,\n TrackPublishOptions,\n TrackSource,\n} from '@livekit/rtc-node';\nimport {\n ATTRIBUTE_TRANSCRIPTION_FINAL,\n ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID,\n ATTRIBUTE_TRANSCRIPTION_TRACK_ID,\n TOPIC_TRANSCRIPTION,\n} from '../../constants.js';\nimport { log } from '../../log.js';\nimport { Future, Task, shortuuid } from '../../utils.js';\nimport { AudioOutput, TextOutput } from '../io.js';\nimport { findMicrophoneTrackId } from '../transcription/index.js';\n\nabstract class BaseParticipantTranscriptionOutput extends TextOutput {\n protected room: Room;\n protected isDeltaStream: boolean;\n protected participantIdentity: string | null = null;\n protected trackId?: string;\n protected capturing: boolean = false;\n protected latestText: string = '';\n protected currentId: string = this.generateCurrentId();\n protected logger = log();\n\n constructor(room: Room, isDeltaStream: boolean, participant: Participant | string | null) {\n super();\n this.room = room;\n this.isDeltaStream = isDeltaStream;\n\n this.room.on(RoomEvent.TrackPublished, this.onTrackPublished);\n this.room.on(RoomEvent.LocalTrackPublished, this.onLocalTrackPublished);\n\n this.setParticipant(participant);\n }\n\n setParticipant(participant: Participant | string | null) {\n if (typeof participant === 'string' || participant === null) {\n this.participantIdentity = participant;\n } else {\n this.participantIdentity = participant.identity;\n }\n\n if (!this.participantIdentity) {\n return;\n }\n\n try {\n this.trackId = findMicrophoneTrackId(this.room, this.participantIdentity);\n } catch (error) {\n // track id is optional for TextStream when audio is not published\n }\n\n this.flush();\n this.resetState();\n }\n\n protected onTrackPublished = (track: RemoteTrackPublication, participant: RemoteParticipant) => {\n if (\n !this.participantIdentity ||\n participant.identity !== this.participantIdentity ||\n track.source !== TrackSource.SOURCE_MICROPHONE\n ) {\n return;\n }\n\n this.trackId = track.sid;\n };\n\n protected onLocalTrackPublished = (track: LocalTrackPublication) => {\n if (\n !this.participantIdentity ||\n this.participantIdentity !== this.room.localParticipant?.identity ||\n track.source !== TrackSource.SOURCE_MICROPHONE\n ) {\n return;\n }\n\n this.trackId = track.sid;\n };\n\n protected generateCurrentId(): string {\n return shortuuid('SG_');\n }\n\n protected resetState() {\n this.currentId = this.generateCurrentId();\n this.capturing = false;\n this.latestText = '';\n }\n\n async captureText(text: string) {\n if (!this.participantIdentity) {\n return;\n }\n\n this.latestText = text;\n await this.handleCaptureText(text);\n }\n\n flush() {\n if (!this.participantIdentity || !this.capturing) {\n return;\n }\n\n this.capturing = false;\n this.handleFlush();\n }\n\n protected abstract handleCaptureText(text: string): Promise<void>;\n protected abstract handleFlush(): void;\n}\n\nexport class ParticipantTranscriptionOutput extends BaseParticipantTranscriptionOutput {\n private writer: TextStreamWriter | null = null;\n private flushTask: Task<void> | null = null;\n\n protected async handleCaptureText(text: string): Promise<void> {\n if (this.flushTask && !this.flushTask.done) {\n await this.flushTask.result;\n }\n\n if (!this.capturing) {\n this.resetState();\n this.capturing = true;\n }\n\n try {\n if (this.room.isConnected) {\n if (this.isDeltaStream) {\n // reuse the existing writer\n if (this.writer === null) {\n this.writer = await this.createTextWriter();\n }\n await this.writer.write(text);\n } else {\n const tmpWriter = await this.createTextWriter();\n await tmpWriter.write(text);\n await tmpWriter.close();\n }\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n\n protected handleFlush() {\n const currWriter = this.writer;\n this.writer = null;\n this.flushTask = Task.from((controller) => this.flushTaskImpl(currWriter, controller.signal));\n }\n\n private async createTextWriter(attributes?: Record<string, string>): Promise<TextStreamWriter> {\n if (!this.participantIdentity) {\n throw new Error('participantIdentity not found');\n }\n\n if (!this.room.localParticipant) {\n throw new Error('localParticipant not found');\n }\n\n if (!attributes) {\n attributes = {\n [ATTRIBUTE_TRANSCRIPTION_FINAL]: 'false',\n };\n if (this.trackId) {\n attributes[ATTRIBUTE_TRANSCRIPTION_TRACK_ID] = this.trackId;\n }\n }\n attributes[ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID] = this.currentId;\n\n return await this.room.localParticipant.streamText({\n topic: TOPIC_TRANSCRIPTION,\n senderIdentity: this.participantIdentity,\n attributes,\n });\n }\n\n private async flushTaskImpl(writer: TextStreamWriter | null, signal: AbortSignal): Promise<void> {\n const attributes: Record<string, string> = {\n [ATTRIBUTE_TRANSCRIPTION_FINAL]: 'true',\n };\n if (this.trackId) {\n attributes[ATTRIBUTE_TRANSCRIPTION_TRACK_ID] = this.trackId;\n }\n\n const abortPromise = new Promise<void>((resolve) => {\n signal.addEventListener('abort', () => resolve());\n });\n\n try {\n if (this.room.isConnected) {\n if (this.isDeltaStream) {\n if (writer) {\n await Promise.race([writer.close(), abortPromise]);\n }\n } else {\n const tmpWriter = await Promise.race([this.createTextWriter(attributes), abortPromise]);\n if (signal.aborted || !tmpWriter) {\n return;\n }\n await Promise.race([tmpWriter.write(this.latestText), abortPromise]);\n if (signal.aborted) {\n return;\n }\n await Promise.race([tmpWriter.close(), abortPromise]);\n }\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n}\n\nexport class ParticipantLegacyTranscriptionOutput extends BaseParticipantTranscriptionOutput {\n private pushedText: string = '';\n private flushTask: Promise<void> | null = null;\n\n protected async handleCaptureText(text: string): Promise<void> {\n if (!this.trackId) {\n return;\n }\n\n if (this.flushTask) {\n await this.flushTask;\n }\n\n if (!this.capturing) {\n this.resetState();\n this.capturing = true;\n }\n\n if (this.isDeltaStream) {\n this.pushedText += text;\n } else {\n this.pushedText = text;\n }\n\n await this.publishTranscription(this.currentId, this.pushedText, false);\n }\n\n protected handleFlush() {\n if (!this.trackId) {\n return;\n }\n\n this.flushTask = this.publishTranscription(this.currentId, this.pushedText, true);\n this.resetState();\n }\n\n async publishTranscription(id: string, text: string, final: boolean, signal?: AbortSignal) {\n if (!this.participantIdentity || !this.trackId) {\n return;\n }\n\n try {\n if (this.room.isConnected) {\n if (signal?.aborted) {\n return;\n }\n\n await this.room.localParticipant?.publishTranscription({\n participantIdentity: this.participantIdentity,\n trackSid: this.trackId,\n segments: [{ id, text, final, startTime: BigInt(0), endTime: BigInt(0), language: '' }],\n });\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n\n protected resetState() {\n super.resetState();\n this.pushedText = '';\n }\n}\n\nexport class ParalellTextOutput extends TextOutput {\n /** @internal */\n _sinks: TextOutput[];\n\n constructor(sinks: TextOutput[], nextInChain?: TextOutput) {\n super(nextInChain);\n this._sinks = sinks;\n }\n\n async captureText(text: string) {\n await Promise.all(this._sinks.map((sink) => sink.captureText(text)));\n }\n\n flush() {\n for (const sink of this._sinks) {\n sink.flush();\n }\n }\n}\n\nexport interface AudioOutputOptions {\n sampleRate: number;\n numChannels: number;\n trackPublishOptions: TrackPublishOptions;\n queueSizeMs?: number;\n}\nexport class ParticipantAudioOutput extends AudioOutput {\n private room: Room;\n private options: AudioOutputOptions;\n private audioSource: AudioSource;\n private publication?: LocalTrackPublication;\n private flushTask?: Task<void>;\n\n /** Duration of audio pushed to the source, in seconds */\n private pushedDuration: number = 0;\n private startedFuture: Future<void> = new Future();\n private interruptedFuture: Future<void> = new Future();\n private firstFrameEmitted: boolean = false;\n\n constructor(room: Room, options: AudioOutputOptions) {\n super(options.sampleRate, undefined, { pause: true });\n this.room = room;\n this.options = options;\n this.audioSource = new AudioSource(options.sampleRate, options.numChannels);\n }\n\n get subscribed(): boolean {\n return this.startedFuture.done;\n }\n\n async start(signal: AbortSignal): Promise<void> {\n await this.publishTrack(signal);\n }\n\n async captureFrame(frame: AudioFrame): Promise<void> {\n await this.startedFuture.await;\n\n super.captureFrame(frame);\n\n if (!this.firstFrameEmitted) {\n this.firstFrameEmitted = true;\n this.onPlaybackStarted(Date.now());\n }\n\n // TODO(AJS-102): use frame.durationMs once available in rtc-node\n this.pushedDuration += frame.samplesPerChannel / frame.sampleRate;\n await this.audioSource.captureFrame(frame);\n }\n\n private async waitForPlayoutTask(abortController: AbortController): Promise<void> {\n const abortFuture = new Future<boolean>();\n\n const resolveAbort = () => {\n if (!abortFuture.done) abortFuture.resolve(true);\n };\n\n abortController.signal.addEventListener('abort', resolveAbort);\n\n this.audioSource.waitForPlayout().finally(() => {\n abortController.signal.removeEventListener('abort', resolveAbort);\n if (!abortFuture.done) abortFuture.resolve(false);\n });\n\n const interrupted = await Promise.race([\n abortFuture.await,\n this.interruptedFuture.await.then(() => true),\n ]);\n\n let pushedDuration = this.pushedDuration;\n\n if (interrupted) {\n // Calculate actual played duration accounting for queued audio\n // Note: queuedDuration is in milliseconds, pushedDuration is in seconds\n pushedDuration = Math.max(this.pushedDuration - this.audioSource.queuedDuration / 1000, 0);\n this.audioSource.clearQueue();\n }\n\n this.pushedDuration = 0;\n this.interruptedFuture = new Future();\n this.firstFrameEmitted = false;\n\n this.onPlaybackFinished({\n playbackPosition: pushedDuration,\n interrupted,\n });\n }\n\n /**\n * Flush any buffered audio, marking the current playback/segment as complete\n */\n flush(): void {\n super.flush();\n\n if (!this.pushedDuration) {\n return;\n }\n\n if (this.flushTask && !this.flushTask.done) {\n this.logger.error('flush called while playback is in progress');\n this.flushTask.cancel();\n }\n\n this.flushTask = Task.from((controller) => this.waitForPlayoutTask(controller));\n }\n\n clearBuffer(): void {\n if (!this.pushedDuration) {\n return;\n }\n\n this.interruptedFuture.resolve();\n }\n\n private async publishTrack(signal: AbortSignal) {\n const track = LocalAudioTrack.createAudioTrack('roomio_audio', this.audioSource);\n this.publication = await this.room.localParticipant?.publishTrack(\n track,\n new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE }),\n );\n\n if (signal.aborted) {\n return;\n }\n\n await this.publication?.waitForSubscription();\n\n if (!this.startedFuture.done) {\n this.startedFuture.resolve();\n }\n }\n\n async close() {\n // TODO(AJS-106): add republish track\n await this.audioSource.close();\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/voice/room_io/room_io.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n ConnectionState,\n DisconnectReason,\n type NoiseCancellationOptions,\n type Participant,\n ParticipantKind,\n type RemoteParticipant,\n type Room,\n RoomEvent,\n type TextStreamInfo,\n type TextStreamReader,\n TrackPublishOptions,\n TrackSource,\n} from '@livekit/rtc-node';\nimport type { WritableStreamDefaultWriter } from 'node:stream/web';\nimport { ATTRIBUTE_PUBLISH_ON_BEHALF, TOPIC_CHAT } from '../../constants.js';\nimport { log } from '../../log.js';\nimport { IdentityTransform } from '../../stream/identity_transform.js';\nimport { Future, Task } from '../../utils.js';\nimport { type AgentSession } from '../agent_session.js';\nimport {\n AgentSessionEventTypes,\n type AgentStateChangedEvent,\n CloseReason,\n type UserInputTranscribedEvent,\n} from '../events.js';\nimport type { AudioOutput, TextOutput } from '../io.js';\nimport { TranscriptionSynchronizer } from '../transcription/synchronizer.js';\nimport { ParticipantAudioInputStream } from './_input.js';\nimport {\n ParalellTextOutput,\n ParticipantAudioOutput,\n ParticipantLegacyTranscriptionOutput,\n ParticipantTranscriptionOutput,\n} from './_output.js';\n\nexport interface TextInputEvent {\n text: string;\n info: TextStreamInfo;\n participant: RemoteParticipant;\n}\n\nexport type TextInputCallback = (sess: AgentSession, ev: TextInputEvent) => void | Promise<void>;\n\nconst DEFAULT_TEXT_INPUT_CALLBACK: TextInputCallback = (sess: AgentSession, ev: TextInputEvent) => {\n sess.interrupt();\n sess.generateReply({ userInput: ev.text });\n};\n\nconst DEFAULT_PARTICIPANT_KINDS: ParticipantKind[] = [\n ParticipantKind.CONNECTOR,\n ParticipantKind.SIP,\n ParticipantKind.STANDARD,\n];\n\nconst CLOSE_ON_DISCONNECT_REASONS: DisconnectReason[] = [\n DisconnectReason.CLIENT_INITIATED,\n DisconnectReason.ROOM_DELETED,\n DisconnectReason.USER_REJECTED,\n];\n\nexport interface RoomInputOptions {\n audioSampleRate: number;\n audioNumChannels: number;\n /** If not given, default to True. */\n textEnabled: boolean;\n /** If not given, default to True. */\n audioEnabled: boolean;\n /** If not given, default to False. */\n videoEnabled: boolean;\n /** The participant to link to. If not provided, link to the first participant.\n Can be overridden by the `participant` argument of RoomIO constructor or `set_participant`.\n */\n participantIdentity?: string;\n noiseCancellation?: NoiseCancellationOptions;\n textInputCallback?: TextInputCallback;\n /** Participant kinds accepted for auto subscription. If not provided,\n accept `DEFAULT_PARTICIPANT_KINDS`\n */\n participantKinds?: ParticipantKind[];\n /** Close the AgentSession if the linked participant disconnects with reasons in\n CLIENT_INITIATED, ROOM_DELETED, or USER_REJECTED.\n */\n closeOnDisconnect: boolean;\n}\n\nexport interface RoomOutputOptions {\n /** If not given, default to True. */\n transcriptionEnabled: boolean;\n /** If not given, default to True. */\n audioEnabled: boolean;\n audioSampleRate: number;\n audioNumChannels: number;\n /** False to disable transcription synchronization with audio output.\n Otherwise, transcription is emitted as quickly as available.\n */\n syncTranscription: boolean;\n /** The name of the audio track to publish. If not provided, default to \"roomio_audio\".\n */\n audioPublishOptions: TrackPublishOptions;\n}\n\nconst DEFAULT_ROOM_INPUT_OPTIONS: RoomInputOptions = {\n audioSampleRate: 24000,\n audioNumChannels: 1,\n textEnabled: true,\n audioEnabled: true,\n videoEnabled: false,\n textInputCallback: DEFAULT_TEXT_INPUT_CALLBACK,\n closeOnDisconnect: true,\n};\n\nconst DEFAULT_ROOM_OUTPUT_OPTIONS: RoomOutputOptions = {\n audioSampleRate: 24000,\n audioNumChannels: 1,\n transcriptionEnabled: true,\n audioEnabled: true,\n syncTranscription: true,\n audioPublishOptions: new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE }),\n};\n\nexport class RoomIO {\n private agentSession: AgentSession;\n private room: Room;\n private inputOptions: RoomInputOptions;\n private outputOptions: RoomOutputOptions;\n\n private audioInput?: ParticipantAudioInputStream;\n private participantAudioOutput?: ParticipantAudioOutput;\n private userTranscriptOutput?: ParalellTextOutput;\n private agentTranscriptOutput?: ParalellTextOutput;\n private transcriptionSynchronizer?: TranscriptionSynchronizer;\n private participantIdentity: string | null = null;\n\n private participantAvailableFuture: Future<RemoteParticipant> = new Future();\n private roomConnectedFuture: Future<void> = new Future();\n\n // Use stream API for transcript queue\n private userTranscriptStream = new IdentityTransform<UserInputTranscribedEvent>();\n private userTranscriptWriter: WritableStreamDefaultWriter<UserInputTranscribedEvent>;\n private forwardUserTranscriptTask?: Task<void>;\n private initTask?: Task<void>;\n\n private textStreamHandlerRegistered = false;\n\n private logger = log();\n\n constructor({\n agentSession,\n room,\n participant = null,\n inputOptions,\n outputOptions,\n }: {\n agentSession: AgentSession;\n room: Room;\n participant?: RemoteParticipant | string | null;\n inputOptions?: Partial<RoomInputOptions>;\n outputOptions?: Partial<RoomOutputOptions>;\n }) {\n this.agentSession = agentSession;\n this.room = room;\n this.inputOptions = { ...DEFAULT_ROOM_INPUT_OPTIONS, ...inputOptions };\n this.outputOptions = { ...DEFAULT_ROOM_OUTPUT_OPTIONS, ...outputOptions };\n\n this.userTranscriptWriter = this.userTranscriptStream.writable.getWriter();\n\n this.participantIdentity = participant\n ? typeof participant === 'string'\n ? participant\n : participant.identity\n : this.inputOptions.participantIdentity ?? null;\n }\n private async init(signal: AbortSignal): Promise<void> {\n await this.roomConnectedFuture.await;\n\n for (const participant of this.room.remoteParticipants.values()) {\n this.onParticipantConnected(participant);\n }\n if (signal.aborted) {\n return;\n }\n\n const participant = await this.participantAvailableFuture.await;\n this.setParticipant(participant.identity);\n\n // init agent outputs\n this.updateTranscriptionOutput({\n output: this.agentTranscriptOutput,\n participant: this.room.localParticipant?.identity ?? null,\n });\n\n await this.participantAudioOutput?.start(signal);\n }\n\n private onConnectionStateChanged = (state: ConnectionState) => {\n this.logger.debug({ state }, 'connection state changed');\n if (\n state === ConnectionState.CONN_CONNECTED &&\n this.room.isConnected &&\n !this.roomConnectedFuture.done\n ) {\n this.roomConnectedFuture.resolve();\n }\n };\n\n private onParticipantConnected = (participant: RemoteParticipant) => {\n if (this.participantAvailableFuture.done) {\n return;\n }\n\n if (this.participantIdentity) {\n if (participant.identity !== this.participantIdentity) {\n return;\n }\n } else if (\n // otherwise, skip participants that are marked as publishing for this agent\n participant.attributes?.[ATTRIBUTE_PUBLISH_ON_BEHALF] === this.room.localParticipant?.identity\n ) {\n return;\n }\n\n const acceptedKinds = this.inputOptions.participantKinds ?? DEFAULT_PARTICIPANT_KINDS;\n if (participant.info.kind !== undefined && !acceptedKinds.includes(participant.info.kind)) {\n return;\n }\n\n this.participantAvailableFuture.resolve(participant);\n };\n\n private onParticipantDisconnected = (participant: RemoteParticipant) => {\n if (participant.identity !== this.participantIdentity) {\n return;\n }\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n if (\n this.inputOptions.closeOnDisconnect &&\n participant.disconnectReason &&\n CLOSE_ON_DISCONNECT_REASONS.includes(participant.disconnectReason)\n ) {\n this.logger.info(\n {\n participant: participant.identity,\n reason: DisconnectReason[participant.disconnectReason],\n },\n 'closing agent session due to participant disconnect ' +\n '(disable via `RoomInputOptions.closeOnDisconnect=False`)',\n );\n this.agentSession._closeSoon({\n reason: CloseReason.PARTICIPANT_DISCONNECTED,\n });\n }\n };\n\n private onUserInputTranscribed = (ev: UserInputTranscribedEvent) => {\n this.userTranscriptWriter.write(ev).catch((error) => {\n this.logger.error({ error }, 'Failed to write transcript event to stream');\n });\n };\n\n private onAgentStateChanged = async (ev: AgentStateChangedEvent) => {\n if (this.room.isConnected && this.room.localParticipant) {\n await this.room.localParticipant.setAttributes({\n [`lk.agent.state`]: ev.newState,\n });\n }\n };\n\n private onUserTextInput = (reader: TextStreamReader, participantInfo: { identity: string }) => {\n if (participantInfo.identity !== this.participantIdentity) {\n return;\n }\n\n const participant = this.room.remoteParticipants.get(participantInfo.identity);\n if (!participant) {\n this.logger.warn('participant not found, ignoring text input');\n return;\n }\n\n const readText = async () => {\n const text = await reader.readAll();\n\n const textInputResult = this.inputOptions.textInputCallback!(this.agentSession, {\n text,\n info: reader.info,\n participant,\n });\n\n // check if callback is a Promise\n if (textInputResult instanceof Promise) {\n await textInputResult;\n }\n };\n\n readText().catch((error) => {\n this.logger.error({ error }, 'Error reading text input');\n });\n };\n\n private async forwardUserTranscript(signal: AbortSignal): Promise<void> {\n const reader = this.userTranscriptStream.readable.getReader();\n try {\n while (!signal.aborted) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const event = value;\n // IMPORTANT: need to await here to avoid race condition\n await this.userTranscriptOutput?.captureText(event.transcript);\n if (event.isFinal) {\n this.userTranscriptOutput?.flush();\n }\n }\n } catch (error) {\n this.logger.error({ error }, 'Error processing transcript stream');\n }\n }\n\n private createTranscriptionOutput(options: {\n isDeltaStream: boolean;\n participant: Participant | string | null;\n }) {\n return new ParalellTextOutput([\n new ParticipantLegacyTranscriptionOutput(\n this.room,\n options.isDeltaStream,\n options.participant,\n ),\n new ParticipantTranscriptionOutput(this.room, options.isDeltaStream, options.participant),\n ]);\n }\n\n private updateTranscriptionOutput({\n output,\n participant,\n }: {\n output?: ParalellTextOutput;\n participant: string | null;\n }) {\n if (!output) {\n return;\n }\n\n for (const sink of output._sinks) {\n if (\n sink instanceof ParticipantLegacyTranscriptionOutput ||\n sink instanceof ParticipantTranscriptionOutput\n ) {\n sink.setParticipant(participant);\n }\n }\n }\n\n get audioOutput(): AudioOutput | undefined {\n if (!this.transcriptionSynchronizer) {\n return this.participantAudioOutput;\n }\n\n return this.transcriptionSynchronizer.audioOutput;\n }\n\n get transcriptionOutput(): TextOutput | undefined {\n if (!this.transcriptionSynchronizer) {\n return this.agentTranscriptOutput;\n }\n\n return this.transcriptionSynchronizer.textOutput;\n }\n\n get isParticipantAvailable(): boolean {\n return this.participantAvailableFuture.done;\n }\n\n /** Switch to a different participant */\n setParticipant(participantIdentity: string | null) {\n this.logger.debug({ participantIdentity }, 'setting participant');\n if (participantIdentity === null) {\n this.unsetParticipant();\n return;\n }\n\n if (this.participantIdentity !== participantIdentity) {\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n\n // check if new participant is already connected\n for (const participant of this.room.remoteParticipants.values()) {\n if (participant.identity === participantIdentity) {\n this.participantAvailableFuture.resolve(participant);\n break;\n }\n }\n }\n\n // update participant identity and handlers\n this.participantIdentity = participantIdentity;\n this.audioInput?.setParticipant(participantIdentity);\n this.updateTranscriptionOutput({\n output: this.userTranscriptOutput,\n participant: participantIdentity,\n });\n }\n\n unsetParticipant() {\n this.participantIdentity = null;\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n this.audioInput?.setParticipant(null);\n this.updateTranscriptionOutput({\n output: this.userTranscriptOutput,\n participant: null,\n });\n }\n\n start() {\n if (this.inputOptions.textEnabled) {\n try {\n this.room.registerTextStreamHandler(TOPIC_CHAT, this.onUserTextInput);\n this.textStreamHandlerRegistered = true;\n } catch (error) {\n if (this.inputOptions.textEnabled) {\n this.logger.warn(`text stream handler for topic \"${TOPIC_CHAT}\" already set, ignoring`);\n }\n }\n }\n\n // -- create inputs --\n if (this.inputOptions.audioEnabled) {\n this.audioInput = new ParticipantAudioInputStream({\n room: this.room,\n sampleRate: this.inputOptions.audioSampleRate,\n numChannels: this.inputOptions.audioNumChannels,\n noiseCancellation: this.inputOptions.noiseCancellation,\n });\n }\n\n // -- create outputs --\n if (this.outputOptions.audioEnabled) {\n this.participantAudioOutput = new ParticipantAudioOutput(this.room, {\n sampleRate: this.outputOptions.audioSampleRate,\n numChannels: this.outputOptions.audioNumChannels,\n trackPublishOptions: this.outputOptions.audioPublishOptions,\n });\n }\n if (this.outputOptions.transcriptionEnabled) {\n this.userTranscriptOutput = this.createTranscriptionOutput({\n isDeltaStream: false,\n participant: this.participantIdentity,\n });\n // Start the transcript forwarding\n this.forwardUserTranscriptTask = Task.from((controller) =>\n this.forwardUserTranscript(controller.signal),\n );\n this.agentTranscriptOutput = this.createTranscriptionOutput({\n isDeltaStream: true,\n participant: null,\n });\n\n // use the RoomIO's audio output if available, otherwise use the agent's audio output\n // TODO(AJS-176): check for agent output\n const audioOutput = this.participantAudioOutput;\n if (this.outputOptions.syncTranscription && audioOutput) {\n this.transcriptionSynchronizer = new TranscriptionSynchronizer(\n audioOutput,\n this.agentTranscriptOutput,\n );\n }\n }\n\n // -- set the room event handlers --\n this.room.on(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.room.on(RoomEvent.ConnectionStateChanged, this.onConnectionStateChanged);\n this.room.on(RoomEvent.ParticipantDisconnected, this.onParticipantDisconnected);\n if (this.room.isConnected) {\n this.onConnectionStateChanged(ConnectionState.CONN_CONNECTED);\n }\n\n this.initTask = Task.from((controller) => this.init(controller.signal));\n\n // attach the agent to the session\n if (this.audioInput) {\n this.agentSession.input.audio = this.audioInput;\n }\n if (this.audioOutput) {\n this.agentSession.output.audio = this.audioOutput;\n }\n if (this.transcriptionOutput) {\n this.agentSession.output.transcription = this.transcriptionOutput;\n }\n\n this.agentSession.on(AgentSessionEventTypes.AgentStateChanged, this.onAgentStateChanged);\n this.agentSession.on(AgentSessionEventTypes.UserInputTranscribed, this.onUserInputTranscribed);\n }\n\n async close() {\n this.room.off(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.room.off(RoomEvent.ConnectionStateChanged, this.onConnectionStateChanged);\n this.room.off(RoomEvent.ParticipantDisconnected, this.onParticipantDisconnected);\n this.agentSession.off(AgentSessionEventTypes.UserInputTranscribed, this.onUserInputTranscribed);\n this.agentSession.off(AgentSessionEventTypes.AgentStateChanged, this.onAgentStateChanged);\n\n if (this.textStreamHandlerRegistered) {\n this.room.unregisterTextStreamHandler(TOPIC_CHAT);\n this.textStreamHandlerRegistered = false;\n }\n\n await this.initTask?.cancelAndWait();\n\n // Close stream FIRST so reader.read() in forwardUserTranscript can exit.\n // This is a workaround for a race condition in the stream API.\n this.userTranscriptWriter.close();\n await this.forwardUserTranscriptTask?.cancelAndWait();\n\n await this.audioInput?.close();\n await this.participantAudioOutput?.close();\n await this.transcriptionSynchronizer?.close();\n }\n}\n"],"mappings":"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","names":["participant"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/voice/room_io/room_io.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n type AudioFrame,\n ConnectionState,\n DisconnectReason,\n type FrameProcessor,\n type NoiseCancellationOptions,\n type Participant,\n ParticipantKind,\n type RemoteParticipant,\n type Room,\n RoomEvent,\n type TextStreamInfo,\n type TextStreamReader,\n TrackPublishOptions,\n TrackSource,\n} from '@livekit/rtc-node';\nimport type { WritableStreamDefaultWriter } from 'node:stream/web';\nimport { ATTRIBUTE_PUBLISH_ON_BEHALF, TOPIC_CHAT } from '../../constants.js';\nimport { log } from '../../log.js';\nimport { IdentityTransform } from '../../stream/identity_transform.js';\nimport { Future, Task } from '../../utils.js';\nimport { type AgentSession } from '../agent_session.js';\nimport {\n AgentSessionEventTypes,\n type AgentStateChangedEvent,\n CloseReason,\n type UserInputTranscribedEvent,\n} from '../events.js';\nimport type { AudioOutput, TextOutput } from '../io.js';\nimport { TranscriptionSynchronizer } from '../transcription/synchronizer.js';\nimport { ParticipantAudioInputStream } from './_input.js';\nimport {\n ParalellTextOutput,\n ParticipantAudioOutput,\n ParticipantLegacyTranscriptionOutput,\n ParticipantTranscriptionOutput,\n} from './_output.js';\n\nexport interface TextInputEvent {\n text: string;\n info: TextStreamInfo;\n participant: RemoteParticipant;\n}\n\nexport type TextInputCallback = (sess: AgentSession, ev: TextInputEvent) => void | Promise<void>;\n\nconst DEFAULT_TEXT_INPUT_CALLBACK: TextInputCallback = (sess: AgentSession, ev: TextInputEvent) => {\n sess.interrupt();\n sess.generateReply({ userInput: ev.text });\n};\n\nconst DEFAULT_PARTICIPANT_KINDS: ParticipantKind[] = [\n ParticipantKind.CONNECTOR,\n ParticipantKind.SIP,\n ParticipantKind.STANDARD,\n];\n\nconst CLOSE_ON_DISCONNECT_REASONS: DisconnectReason[] = [\n DisconnectReason.CLIENT_INITIATED,\n DisconnectReason.ROOM_DELETED,\n DisconnectReason.USER_REJECTED,\n];\n\nexport interface RoomInputOptions {\n audioSampleRate: number;\n audioNumChannels: number;\n /** If not given, default to True. */\n textEnabled: boolean;\n /** If not given, default to True. */\n audioEnabled: boolean;\n /** If not given, default to False. */\n videoEnabled: boolean;\n /** The participant to link to. If not provided, link to the first participant.\n Can be overridden by the `participant` argument of RoomIO constructor or `set_participant`.\n */\n participantIdentity?: string;\n noiseCancellation?: NoiseCancellationOptions | FrameProcessor<AudioFrame>;\n textInputCallback?: TextInputCallback;\n /** Participant kinds accepted for auto subscription. If not provided,\n accept `DEFAULT_PARTICIPANT_KINDS`\n */\n participantKinds?: ParticipantKind[];\n /** Close the AgentSession if the linked participant disconnects with reasons in\n CLIENT_INITIATED, ROOM_DELETED, or USER_REJECTED.\n */\n closeOnDisconnect: boolean;\n}\n\nexport interface RoomOutputOptions {\n /** If not given, default to True. */\n transcriptionEnabled: boolean;\n /** If not given, default to True. */\n audioEnabled: boolean;\n audioSampleRate: number;\n audioNumChannels: number;\n /** False to disable transcription synchronization with audio output.\n Otherwise, transcription is emitted as quickly as available.\n */\n syncTranscription: boolean;\n /** The name of the audio track to publish. If not provided, default to \"roomio_audio\".\n */\n audioPublishOptions: TrackPublishOptions;\n}\n\nconst DEFAULT_ROOM_INPUT_OPTIONS: RoomInputOptions = {\n audioSampleRate: 24000,\n audioNumChannels: 1,\n textEnabled: true,\n audioEnabled: true,\n videoEnabled: false,\n textInputCallback: DEFAULT_TEXT_INPUT_CALLBACK,\n closeOnDisconnect: true,\n};\n\nconst DEFAULT_ROOM_OUTPUT_OPTIONS: RoomOutputOptions = {\n audioSampleRate: 24000,\n audioNumChannels: 1,\n transcriptionEnabled: true,\n audioEnabled: true,\n syncTranscription: true,\n audioPublishOptions: new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE }),\n};\n\nexport class RoomIO {\n private agentSession: AgentSession;\n private room: Room;\n private inputOptions: RoomInputOptions;\n private outputOptions: RoomOutputOptions;\n\n private audioInput?: ParticipantAudioInputStream;\n private participantAudioOutput?: ParticipantAudioOutput;\n private userTranscriptOutput?: ParalellTextOutput;\n private agentTranscriptOutput?: ParalellTextOutput;\n private transcriptionSynchronizer?: TranscriptionSynchronizer;\n private participantIdentity: string | null = null;\n\n private participantAvailableFuture: Future<RemoteParticipant> = new Future();\n private roomConnectedFuture: Future<void> = new Future();\n\n // Use stream API for transcript queue\n private userTranscriptStream = new IdentityTransform<UserInputTranscribedEvent>();\n private userTranscriptWriter: WritableStreamDefaultWriter<UserInputTranscribedEvent>;\n private forwardUserTranscriptTask?: Task<void>;\n private initTask?: Task<void>;\n\n private textStreamHandlerRegistered = false;\n\n private logger = log();\n\n constructor({\n agentSession,\n room,\n participant = null,\n inputOptions,\n outputOptions,\n }: {\n agentSession: AgentSession;\n room: Room;\n participant?: RemoteParticipant | string | null;\n inputOptions?: Partial<RoomInputOptions>;\n outputOptions?: Partial<RoomOutputOptions>;\n }) {\n this.agentSession = agentSession;\n this.room = room;\n this.inputOptions = { ...DEFAULT_ROOM_INPUT_OPTIONS, ...inputOptions };\n this.outputOptions = { ...DEFAULT_ROOM_OUTPUT_OPTIONS, ...outputOptions };\n\n this.userTranscriptWriter = this.userTranscriptStream.writable.getWriter();\n\n this.participantIdentity = participant\n ? typeof participant === 'string'\n ? participant\n : participant.identity\n : this.inputOptions.participantIdentity ?? null;\n }\n private async init(signal: AbortSignal): Promise<void> {\n await this.roomConnectedFuture.await;\n\n for (const participant of this.room.remoteParticipants.values()) {\n this.onParticipantConnected(participant);\n }\n if (signal.aborted) {\n return;\n }\n\n const participant = await this.participantAvailableFuture.await;\n this.setParticipant(participant.identity);\n\n // init agent outputs\n this.updateTranscriptionOutput({\n output: this.agentTranscriptOutput,\n participant: this.room.localParticipant?.identity ?? null,\n });\n\n await this.participantAudioOutput?.start(signal);\n }\n\n private onConnectionStateChanged = (state: ConnectionState) => {\n this.logger.debug({ state }, 'connection state changed');\n if (\n state === ConnectionState.CONN_CONNECTED &&\n this.room.isConnected &&\n !this.roomConnectedFuture.done\n ) {\n this.roomConnectedFuture.resolve();\n }\n };\n\n private onParticipantConnected = (participant: RemoteParticipant) => {\n if (this.participantAvailableFuture.done) {\n return;\n }\n\n if (this.participantIdentity) {\n if (participant.identity !== this.participantIdentity) {\n return;\n }\n } else if (\n // otherwise, skip participants that are marked as publishing for this agent\n participant.attributes?.[ATTRIBUTE_PUBLISH_ON_BEHALF] === this.room.localParticipant?.identity\n ) {\n return;\n }\n\n const acceptedKinds = this.inputOptions.participantKinds ?? DEFAULT_PARTICIPANT_KINDS;\n if (participant.info.kind !== undefined && !acceptedKinds.includes(participant.info.kind)) {\n return;\n }\n\n this.participantAvailableFuture.resolve(participant);\n };\n\n private onParticipantDisconnected = (participant: RemoteParticipant) => {\n if (participant.identity !== this.participantIdentity) {\n return;\n }\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n if (\n this.inputOptions.closeOnDisconnect &&\n participant.disconnectReason &&\n CLOSE_ON_DISCONNECT_REASONS.includes(participant.disconnectReason)\n ) {\n this.logger.info(\n {\n participant: participant.identity,\n reason: DisconnectReason[participant.disconnectReason],\n },\n 'closing agent session due to participant disconnect ' +\n '(disable via `RoomInputOptions.closeOnDisconnect=False`)',\n );\n this.agentSession._closeSoon({\n reason: CloseReason.PARTICIPANT_DISCONNECTED,\n });\n }\n };\n\n private onUserInputTranscribed = (ev: UserInputTranscribedEvent) => {\n this.userTranscriptWriter.write(ev).catch((error) => {\n this.logger.error({ error }, 'Failed to write transcript event to stream');\n });\n };\n\n private onAgentStateChanged = async (ev: AgentStateChangedEvent) => {\n if (this.room.isConnected && this.room.localParticipant) {\n await this.room.localParticipant.setAttributes({\n [`lk.agent.state`]: ev.newState,\n });\n }\n };\n\n private onUserTextInput = (reader: TextStreamReader, participantInfo: { identity: string }) => {\n if (participantInfo.identity !== this.participantIdentity) {\n return;\n }\n\n const participant = this.room.remoteParticipants.get(participantInfo.identity);\n if (!participant) {\n this.logger.warn('participant not found, ignoring text input');\n return;\n }\n\n const readText = async () => {\n const text = await reader.readAll();\n\n const textInputResult = this.inputOptions.textInputCallback!(this.agentSession, {\n text,\n info: reader.info,\n participant,\n });\n\n // check if callback is a Promise\n if (textInputResult instanceof Promise) {\n await textInputResult;\n }\n };\n\n readText().catch((error) => {\n this.logger.error({ error }, 'Error reading text input');\n });\n };\n\n private async forwardUserTranscript(signal: AbortSignal): Promise<void> {\n const reader = this.userTranscriptStream.readable.getReader();\n try {\n while (!signal.aborted) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const event = value;\n // IMPORTANT: need to await here to avoid race condition\n await this.userTranscriptOutput?.captureText(event.transcript);\n if (event.isFinal) {\n this.userTranscriptOutput?.flush();\n }\n }\n } catch (error) {\n this.logger.error({ error }, 'Error processing transcript stream');\n }\n }\n\n private createTranscriptionOutput(options: {\n isDeltaStream: boolean;\n participant: Participant | string | null;\n }) {\n return new ParalellTextOutput([\n new ParticipantLegacyTranscriptionOutput(\n this.room,\n options.isDeltaStream,\n options.participant,\n ),\n new ParticipantTranscriptionOutput(this.room, options.isDeltaStream, options.participant),\n ]);\n }\n\n private updateTranscriptionOutput({\n output,\n participant,\n }: {\n output?: ParalellTextOutput;\n participant: string | null;\n }) {\n if (!output) {\n return;\n }\n\n for (const sink of output._sinks) {\n if (\n sink instanceof ParticipantLegacyTranscriptionOutput ||\n sink instanceof ParticipantTranscriptionOutput\n ) {\n sink.setParticipant(participant);\n }\n }\n }\n\n get audioOutput(): AudioOutput | undefined {\n if (!this.transcriptionSynchronizer) {\n return this.participantAudioOutput;\n }\n\n return this.transcriptionSynchronizer.audioOutput;\n }\n\n get transcriptionOutput(): TextOutput | undefined {\n if (!this.transcriptionSynchronizer) {\n return this.agentTranscriptOutput;\n }\n\n return this.transcriptionSynchronizer.textOutput;\n }\n\n get isParticipantAvailable(): boolean {\n return this.participantAvailableFuture.done;\n }\n\n /** Switch to a different participant */\n setParticipant(participantIdentity: string | null) {\n this.logger.debug({ participantIdentity }, 'setting participant');\n if (participantIdentity === null) {\n this.unsetParticipant();\n return;\n }\n\n if (this.participantIdentity !== participantIdentity) {\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n\n // check if new participant is already connected\n for (const participant of this.room.remoteParticipants.values()) {\n if (participant.identity === participantIdentity) {\n this.participantAvailableFuture.resolve(participant);\n break;\n }\n }\n }\n\n // update participant identity and handlers\n this.participantIdentity = participantIdentity;\n this.audioInput?.setParticipant(participantIdentity);\n this.updateTranscriptionOutput({\n output: this.userTranscriptOutput,\n participant: participantIdentity,\n });\n }\n\n unsetParticipant() {\n this.participantIdentity = null;\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n this.audioInput?.setParticipant(null);\n this.updateTranscriptionOutput({\n output: this.userTranscriptOutput,\n participant: null,\n });\n }\n\n start() {\n if (this.inputOptions.textEnabled) {\n try {\n this.room.registerTextStreamHandler(TOPIC_CHAT, this.onUserTextInput);\n this.textStreamHandlerRegistered = true;\n } catch (error) {\n if (this.inputOptions.textEnabled) {\n this.logger.warn(`text stream handler for topic \"${TOPIC_CHAT}\" already set, ignoring`);\n }\n }\n }\n\n // -- create inputs --\n if (this.inputOptions.audioEnabled) {\n this.audioInput = new ParticipantAudioInputStream({\n room: this.room,\n sampleRate: this.inputOptions.audioSampleRate,\n numChannels: this.inputOptions.audioNumChannels,\n noiseCancellation: this.inputOptions.noiseCancellation,\n });\n }\n\n // -- create outputs --\n if (this.outputOptions.audioEnabled) {\n this.participantAudioOutput = new ParticipantAudioOutput(this.room, {\n sampleRate: this.outputOptions.audioSampleRate,\n numChannels: this.outputOptions.audioNumChannels,\n trackPublishOptions: this.outputOptions.audioPublishOptions,\n });\n }\n if (this.outputOptions.transcriptionEnabled) {\n this.userTranscriptOutput = this.createTranscriptionOutput({\n isDeltaStream: false,\n participant: this.participantIdentity,\n });\n // Start the transcript forwarding\n this.forwardUserTranscriptTask = Task.from((controller) =>\n this.forwardUserTranscript(controller.signal),\n );\n this.agentTranscriptOutput = this.createTranscriptionOutput({\n isDeltaStream: true,\n participant: null,\n });\n\n // use the RoomIO's audio output if available, otherwise use the agent's audio output\n // TODO(AJS-176): check for agent output\n const audioOutput = this.participantAudioOutput;\n if (this.outputOptions.syncTranscription && audioOutput) {\n this.transcriptionSynchronizer = new TranscriptionSynchronizer(\n audioOutput,\n this.agentTranscriptOutput,\n );\n }\n }\n\n // -- set the room event handlers --\n this.room.on(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.room.on(RoomEvent.ConnectionStateChanged, this.onConnectionStateChanged);\n this.room.on(RoomEvent.ParticipantDisconnected, this.onParticipantDisconnected);\n if (this.room.isConnected) {\n this.onConnectionStateChanged(ConnectionState.CONN_CONNECTED);\n }\n\n this.initTask = Task.from((controller) => this.init(controller.signal));\n\n // attach the agent to the session\n if (this.audioInput) {\n this.agentSession.input.audio = this.audioInput;\n }\n if (this.audioOutput) {\n this.agentSession.output.audio = this.audioOutput;\n }\n if (this.transcriptionOutput) {\n this.agentSession.output.transcription = this.transcriptionOutput;\n }\n\n this.agentSession.on(AgentSessionEventTypes.AgentStateChanged, this.onAgentStateChanged);\n this.agentSession.on(AgentSessionEventTypes.UserInputTranscribed, this.onUserInputTranscribed);\n }\n\n async close() {\n this.room.off(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.room.off(RoomEvent.ConnectionStateChanged, this.onConnectionStateChanged);\n this.room.off(RoomEvent.ParticipantDisconnected, this.onParticipantDisconnected);\n this.agentSession.off(AgentSessionEventTypes.UserInputTranscribed, this.onUserInputTranscribed);\n this.agentSession.off(AgentSessionEventTypes.AgentStateChanged, this.onAgentStateChanged);\n\n if (this.textStreamHandlerRegistered) {\n this.room.unregisterTextStreamHandler(TOPIC_CHAT);\n this.textStreamHandlerRegistered = false;\n }\n\n await this.initTask?.cancelAndWait();\n\n // Close stream FIRST so reader.read() in forwardUserTranscript can exit.\n // This is a workaround for a race condition in the stream API.\n this.userTranscriptWriter.close();\n await this.forwardUserTranscriptTask?.cancelAndWait();\n\n await this.audioInput?.close();\n await this.participantAudioOutput?.close();\n await this.transcriptionSynchronizer?.close();\n }\n}\n"],"mappings":"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","names":["participant"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type NoiseCancellationOptions, ParticipantKind, type RemoteParticipant, type Room, type TextStreamInfo, TrackPublishOptions } from '@livekit/rtc-node';
|
|
1
|
+
import { type AudioFrame, type FrameProcessor, type NoiseCancellationOptions, ParticipantKind, type RemoteParticipant, type Room, type TextStreamInfo, TrackPublishOptions } from '@livekit/rtc-node';
|
|
2
2
|
import { type AgentSession } from '../agent_session.js';
|
|
3
3
|
import type { AudioOutput, TextOutput } from '../io.js';
|
|
4
4
|
export interface TextInputEvent {
|
|
@@ -20,7 +20,7 @@ export interface RoomInputOptions {
|
|
|
20
20
|
Can be overridden by the `participant` argument of RoomIO constructor or `set_participant`.
|
|
21
21
|
*/
|
|
22
22
|
participantIdentity?: string;
|
|
23
|
-
noiseCancellation?: NoiseCancellationOptions
|
|
23
|
+
noiseCancellation?: NoiseCancellationOptions | FrameProcessor<AudioFrame>;
|
|
24
24
|
textInputCallback?: TextInputCallback;
|
|
25
25
|
/** Participant kinds accepted for auto subscription. If not provided,
|
|
26
26
|
accept `DEFAULT_PARTICIPANT_KINDS`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type NoiseCancellationOptions, ParticipantKind, type RemoteParticipant, type Room, type TextStreamInfo, TrackPublishOptions } from '@livekit/rtc-node';
|
|
1
|
+
import { type AudioFrame, type FrameProcessor, type NoiseCancellationOptions, ParticipantKind, type RemoteParticipant, type Room, type TextStreamInfo, TrackPublishOptions } from '@livekit/rtc-node';
|
|
2
2
|
import { type AgentSession } from '../agent_session.js';
|
|
3
3
|
import type { AudioOutput, TextOutput } from '../io.js';
|
|
4
4
|
export interface TextInputEvent {
|
|
@@ -20,7 +20,7 @@ export interface RoomInputOptions {
|
|
|
20
20
|
Can be overridden by the `participant` argument of RoomIO constructor or `set_participant`.
|
|
21
21
|
*/
|
|
22
22
|
participantIdentity?: string;
|
|
23
|
-
noiseCancellation?: NoiseCancellationOptions
|
|
23
|
+
noiseCancellation?: NoiseCancellationOptions | FrameProcessor<AudioFrame>;
|
|
24
24
|
textInputCallback?: TextInputCallback;
|
|
25
25
|
/** Participant kinds accepted for auto subscription. If not provided,
|
|
26
26
|
accept `DEFAULT_PARTICIPANT_KINDS`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"room_io.d.ts","sourceRoot":"","sources":["../../../src/voice/room_io/room_io.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"room_io.d.ts","sourceRoot":"","sources":["../../../src/voice/room_io/room_io.ts"],"names":[],"mappings":"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"}
|