@livekit/agents 1.0.43 → 1.0.45
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/inference/stt.cjs +19 -2
- package/dist/inference/stt.cjs.map +1 -1
- package/dist/inference/stt.d.cts +1 -1
- package/dist/inference/stt.d.ts +1 -1
- package/dist/inference/stt.d.ts.map +1 -1
- package/dist/inference/stt.js +19 -2
- package/dist/inference/stt.js.map +1 -1
- package/dist/ipc/job_proc_lazy_main.cjs +6 -0
- package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
- package/dist/ipc/job_proc_lazy_main.js +7 -1
- package/dist/ipc/job_proc_lazy_main.js.map +1 -1
- package/dist/ipc/supervised_proc.cjs +1 -1
- package/dist/ipc/supervised_proc.cjs.map +1 -1
- package/dist/ipc/supervised_proc.js +1 -1
- package/dist/ipc/supervised_proc.js.map +1 -1
- package/dist/llm/llm.cjs +1 -1
- package/dist/llm/llm.cjs.map +1 -1
- package/dist/llm/llm.js +1 -1
- package/dist/llm/llm.js.map +1 -1
- package/dist/log.cjs +13 -9
- package/dist/log.cjs.map +1 -1
- package/dist/log.d.cts +1 -1
- package/dist/log.d.ts +1 -1
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +13 -9
- package/dist/log.js.map +1 -1
- package/dist/stt/stt.cjs +6 -2
- package/dist/stt/stt.cjs.map +1 -1
- package/dist/stt/stt.d.ts.map +1 -1
- package/dist/stt/stt.js +6 -2
- package/dist/stt/stt.js.map +1 -1
- package/dist/tts/fallback_adapter.cjs +466 -0
- package/dist/tts/fallback_adapter.cjs.map +1 -0
- package/dist/tts/fallback_adapter.d.cts +110 -0
- package/dist/tts/fallback_adapter.d.ts +110 -0
- package/dist/tts/fallback_adapter.d.ts.map +1 -0
- package/dist/tts/fallback_adapter.js +442 -0
- package/dist/tts/fallback_adapter.js.map +1 -0
- package/dist/tts/index.cjs +3 -0
- package/dist/tts/index.cjs.map +1 -1
- package/dist/tts/index.d.cts +1 -0
- package/dist/tts/index.d.ts +1 -0
- package/dist/tts/index.d.ts.map +1 -1
- package/dist/tts/index.js +2 -0
- package/dist/tts/index.js.map +1 -1
- package/dist/tts/tts.cjs +2 -2
- package/dist/tts/tts.cjs.map +1 -1
- package/dist/tts/tts.js +2 -2
- package/dist/tts/tts.js.map +1 -1
- package/dist/utils.cjs +10 -2
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +10 -2
- package/dist/utils.js.map +1 -1
- package/dist/vad.cjs +11 -10
- package/dist/vad.cjs.map +1 -1
- package/dist/vad.d.cts +5 -3
- package/dist/vad.d.ts +5 -3
- package/dist/vad.d.ts.map +1 -1
- package/dist/vad.js +11 -10
- package/dist/vad.js.map +1 -1
- package/dist/voice/room_io/_input.cjs +6 -3
- package/dist/voice/room_io/_input.cjs.map +1 -1
- package/dist/voice/room_io/_input.d.ts.map +1 -1
- package/dist/voice/room_io/_input.js +6 -3
- package/dist/voice/room_io/_input.js.map +1 -1
- package/package.json +1 -1
- package/src/inference/stt.ts +21 -3
- package/src/ipc/job_proc_lazy_main.ts +13 -1
- package/src/ipc/supervised_proc.ts +1 -1
- package/src/llm/llm.ts +1 -1
- package/src/log.ts +22 -11
- package/src/stt/stt.ts +7 -2
- package/src/tts/fallback_adapter.ts +579 -0
- package/src/tts/index.ts +1 -0
- package/src/tts/tts.ts +2 -2
- package/src/utils.ts +10 -2
- package/src/vad.ts +12 -11
- package/src/voice/room_io/_input.ts +5 -3
package/dist/vad.js
CHANGED
|
@@ -32,13 +32,14 @@ class VADStream {
|
|
|
32
32
|
outputReader;
|
|
33
33
|
closed = false;
|
|
34
34
|
inputClosed = false;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
logger
|
|
35
|
+
vad;
|
|
36
|
+
lastActivityTime = BigInt(0);
|
|
37
|
+
logger;
|
|
38
38
|
deferredInputStream;
|
|
39
39
|
metricsStream;
|
|
40
40
|
constructor(vad) {
|
|
41
|
-
this
|
|
41
|
+
this.logger = log();
|
|
42
|
+
this.vad = vad;
|
|
42
43
|
this.deferredInputStream = new DeferredReadableStream();
|
|
43
44
|
this.inputWriter = this.input.writable.getWriter();
|
|
44
45
|
this.inputReader = this.input.readable.getReader();
|
|
@@ -83,16 +84,16 @@ class VADStream {
|
|
|
83
84
|
switch (value.type) {
|
|
84
85
|
case 0 /* START_OF_SPEECH */:
|
|
85
86
|
inferenceCount++;
|
|
86
|
-
if (inferenceCount >= 1e3 / this
|
|
87
|
-
this
|
|
87
|
+
if (inferenceCount >= 1e3 / this.vad.capabilities.updateInterval) {
|
|
88
|
+
this.vad.emit("metrics_collected", {
|
|
88
89
|
type: "vad_metrics",
|
|
89
90
|
timestamp: Date.now(),
|
|
90
91
|
idleTimeMs: Math.trunc(
|
|
91
|
-
Number((process.hrtime.bigint() - this
|
|
92
|
+
Number((process.hrtime.bigint() - this.lastActivityTime) / BigInt(1e6))
|
|
92
93
|
),
|
|
93
94
|
inferenceDurationTotalMs,
|
|
94
95
|
inferenceCount,
|
|
95
|
-
label: this
|
|
96
|
+
label: this.vad.label
|
|
96
97
|
});
|
|
97
98
|
inferenceCount = 0;
|
|
98
99
|
inferenceDurationTotalMs = 0;
|
|
@@ -100,10 +101,10 @@ class VADStream {
|
|
|
100
101
|
break;
|
|
101
102
|
case 1 /* INFERENCE_DONE */:
|
|
102
103
|
inferenceDurationTotalMs += Math.round(value.inferenceDuration);
|
|
103
|
-
this
|
|
104
|
+
this.lastActivityTime = process.hrtime.bigint();
|
|
104
105
|
break;
|
|
105
106
|
case 2 /* END_OF_SPEECH */:
|
|
106
|
-
this
|
|
107
|
+
this.lastActivityTime = process.hrtime.bigint();
|
|
107
108
|
break;
|
|
108
109
|
}
|
|
109
110
|
}
|
package/dist/vad.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vad.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';\nimport { EventEmitter } from 'node:events';\nimport type {\n ReadableStream,\n ReadableStreamDefaultReader,\n WritableStreamDefaultWriter,\n} from 'node:stream/web';\nimport { log } from './log.js';\nimport type { VADMetrics } from './metrics/base.js';\nimport { DeferredReadableStream } from './stream/deferred_stream.js';\nimport { IdentityTransform } from './stream/identity_transform.js';\n\nexport enum VADEventType {\n START_OF_SPEECH,\n INFERENCE_DONE,\n END_OF_SPEECH,\n METRICS_COLLECTED,\n}\n\nexport interface VADEvent {\n /** Type of the VAD event (e.g., start of speech, end of speech, inference done). */\n type: VADEventType;\n /**\n * Index of the audio sample where the event occurred, relative to the inference sample rate.\n */\n samplesIndex: number;\n /** Timestamp when the event was fired. */\n timestamp: number;\n /** Duration of the speech segment in seconds. */\n speechDuration: number;\n /** Duration of the silence segment in seconds. */\n silenceDuration: number;\n /**\n * List of audio frames associated with the speech.\n *\n * @remarks\n * - For `start_of_speech` events, this contains the audio chunks that triggered the detection.\n * - For `inference_done` events, this contains the audio chunks that were processed.\n * - For `end_of_speech` events, this contains the complete user speech.\n */\n frames: AudioFrame[];\n /** Probability that speech is present (only for `INFERENCE_DONE` events). */\n probability: number;\n /** Time taken to perform the inference, in seconds (only for `INFERENCE_DONE` events). */\n inferenceDuration: number;\n /** Indicates whether speech was detected in the frames. */\n speaking: boolean;\n /** Threshold used to detect silence. */\n rawAccumulatedSilence: number;\n /** Threshold used to detect speech. */\n rawAccumulatedSpeech: number;\n}\n\nexport interface VADCapabilities {\n /** Duration of each VAD inference window in milliseconds. Used to batch metrics emissions to roughly once per second. */\n updateInterval: number;\n}\n\nexport type VADCallbacks = {\n ['metrics_collected']: (metrics: VADMetrics) => void;\n};\n\nexport abstract class VAD extends (EventEmitter as new () => TypedEmitter<VADCallbacks>) {\n #capabilities: VADCapabilities;\n abstract label: string;\n\n constructor(capabilities: VADCapabilities) {\n super();\n this.#capabilities = capabilities;\n }\n\n get capabilities(): VADCapabilities {\n return this.#capabilities;\n }\n\n /**\n * Returns a {@link VADStream} that can be used to push audio frames and receive VAD events.\n */\n abstract stream(): VADStream;\n\n async close(): Promise<void> {\n return;\n }\n}\n\nexport abstract class VADStream implements AsyncIterableIterator<VADEvent> {\n protected static readonly FLUSH_SENTINEL = Symbol('FLUSH_SENTINEL');\n protected input = new IdentityTransform<AudioFrame | typeof VADStream.FLUSH_SENTINEL>();\n protected output = new IdentityTransform<VADEvent>();\n protected inputWriter: WritableStreamDefaultWriter<AudioFrame | typeof VADStream.FLUSH_SENTINEL>;\n protected inputReader: ReadableStreamDefaultReader<AudioFrame | typeof VADStream.FLUSH_SENTINEL>;\n protected outputWriter: WritableStreamDefaultWriter<VADEvent>;\n protected outputReader: ReadableStreamDefaultReader<VADEvent>;\n protected closed = false;\n protected inputClosed = false;\n\n #vad: VAD;\n #lastActivityTime = BigInt(0);\n private logger = log();\n private deferredInputStream: DeferredReadableStream<AudioFrame>;\n\n private metricsStream: ReadableStream<VADEvent>;\n constructor(vad: VAD) {\n this.#vad = vad;\n this.deferredInputStream = new DeferredReadableStream<AudioFrame>();\n\n this.inputWriter = this.input.writable.getWriter();\n this.inputReader = this.input.readable.getReader();\n this.outputWriter = this.output.writable.getWriter();\n\n const [outputStream, metricsStream] = this.output.readable.tee();\n this.metricsStream = metricsStream;\n this.outputReader = outputStream.getReader();\n\n this.pumpDeferredStream();\n this.monitorMetrics();\n }\n\n /**\n * Reads from the deferred input stream and forwards chunks to the input writer.\n *\n * Note: we can't just do this.deferredInputStream.stream.pipeTo(this.input.writable)\n * because the inputWriter locks the this.input.writable stream. All writes must go through\n * the inputWriter.\n */\n private async pumpDeferredStream() {\n const reader = this.deferredInputStream.stream.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n await this.inputWriter.write(value);\n }\n } catch (e) {\n this.logger.error(`Error pumping deferred stream: ${e}`);\n throw e;\n } finally {\n reader.releaseLock();\n }\n }\n\n protected async monitorMetrics() {\n let inferenceDurationTotalMs = 0;\n let inferenceCount = 0;\n const metricsReader = this.metricsStream.getReader();\n while (true) {\n const { done, value } = await metricsReader.read();\n if (done) {\n break;\n }\n switch (value.type) {\n case VADEventType.START_OF_SPEECH:\n inferenceCount++;\n if (inferenceCount >= 1000 / this.#vad.capabilities.updateInterval) {\n this.#vad.emit('metrics_collected', {\n type: 'vad_metrics',\n timestamp: Date.now(),\n idleTimeMs: Math.trunc(\n Number((process.hrtime.bigint() - this.#lastActivityTime) / BigInt(1000000)),\n ),\n inferenceDurationTotalMs,\n inferenceCount,\n label: this.#vad.label,\n });\n\n inferenceCount = 0;\n inferenceDurationTotalMs = 0;\n }\n break;\n case VADEventType.INFERENCE_DONE:\n inferenceDurationTotalMs += Math.round(value.inferenceDuration);\n this.#lastActivityTime = process.hrtime.bigint();\n break;\n case VADEventType.END_OF_SPEECH:\n this.#lastActivityTime = process.hrtime.bigint();\n break;\n }\n }\n }\n\n /**\n * Safely send a VAD event to the output stream, handling writer release errors during shutdown.\n * @returns true if the event was sent, false if the stream is closing\n * @throws Error if an unexpected error occurs\n */\n protected sendVADEvent(event: VADEvent): boolean {\n if (this.closed) {\n return false;\n }\n\n try {\n this.outputWriter.write(event);\n return true;\n } catch (e) {\n throw e;\n }\n }\n\n updateInputStream(audioStream: ReadableStream<AudioFrame>) {\n this.deferredInputStream.setSource(audioStream);\n }\n\n detachInputStream() {\n this.deferredInputStream.detachSource();\n }\n\n /** @deprecated Use `updateInputStream` instead */\n pushFrame(frame: AudioFrame) {\n // TODO(AJS-395): remove this method\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputWriter.write(frame);\n }\n\n flush() {\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputWriter.write(VADStream.FLUSH_SENTINEL);\n }\n\n endInput() {\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputClosed = true;\n this.input.writable.close();\n }\n\n async next(): Promise<IteratorResult<VADEvent>> {\n return this.outputReader.read().then(({ done, value }) => {\n if (done) {\n return { done: true, value: undefined };\n }\n return { done: false, value };\n });\n }\n\n close() {\n this.outputWriter.releaseLock();\n this.outputReader.cancel();\n this.output.writable.close();\n this.closed = true;\n }\n\n [Symbol.asyncIterator](): VADStream {\n return this;\n }\n}\n"],"mappings":"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","names":["VADEventType"]}
|
|
1
|
+
{"version":3,"sources":["../src/vad.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';\nimport { EventEmitter } from 'node:events';\nimport type {\n ReadableStream,\n ReadableStreamDefaultReader,\n WritableStreamDefaultWriter,\n} from 'node:stream/web';\nimport { log } from './log.js';\nimport type { VADMetrics } from './metrics/base.js';\nimport { DeferredReadableStream } from './stream/deferred_stream.js';\nimport { IdentityTransform } from './stream/identity_transform.js';\n\nexport enum VADEventType {\n START_OF_SPEECH,\n INFERENCE_DONE,\n END_OF_SPEECH,\n METRICS_COLLECTED,\n}\n\nexport interface VADEvent {\n /** Type of the VAD event (e.g., start of speech, end of speech, inference done). */\n type: VADEventType;\n /**\n * Index of the audio sample where the event occurred, relative to the inference sample rate.\n */\n samplesIndex: number;\n /** Timestamp when the event was fired. */\n timestamp: number;\n /** Duration of the speech segment in seconds. */\n speechDuration: number;\n /** Duration of the silence segment in seconds. */\n silenceDuration: number;\n /**\n * List of audio frames associated with the speech.\n *\n * @remarks\n * - For `start_of_speech` events, this contains the audio chunks that triggered the detection.\n * - For `inference_done` events, this contains the audio chunks that were processed.\n * - For `end_of_speech` events, this contains the complete user speech.\n */\n frames: AudioFrame[];\n /** Probability that speech is present (only for `INFERENCE_DONE` events). */\n probability: number;\n /** Time taken to perform the inference, in seconds (only for `INFERENCE_DONE` events). */\n inferenceDuration: number;\n /** Indicates whether speech was detected in the frames. */\n speaking: boolean;\n /** Threshold used to detect silence. */\n rawAccumulatedSilence: number;\n /** Threshold used to detect speech. */\n rawAccumulatedSpeech: number;\n}\n\nexport interface VADCapabilities {\n /** Duration of each VAD inference window in milliseconds. Used to batch metrics emissions to roughly once per second. */\n updateInterval: number;\n}\n\nexport type VADCallbacks = {\n ['metrics_collected']: (metrics: VADMetrics) => void;\n};\n\nexport abstract class VAD extends (EventEmitter as new () => TypedEmitter<VADCallbacks>) {\n #capabilities: VADCapabilities;\n abstract label: string;\n\n constructor(capabilities: VADCapabilities) {\n super();\n this.#capabilities = capabilities;\n }\n\n get capabilities(): VADCapabilities {\n return this.#capabilities;\n }\n\n /**\n * Returns a {@link VADStream} that can be used to push audio frames and receive VAD events.\n */\n abstract stream(): VADStream;\n\n async close(): Promise<void> {\n return;\n }\n}\n\nexport abstract class VADStream implements AsyncIterableIterator<VADEvent> {\n protected static readonly FLUSH_SENTINEL = Symbol('FLUSH_SENTINEL');\n protected input = new IdentityTransform<AudioFrame | typeof VADStream.FLUSH_SENTINEL>();\n protected output = new IdentityTransform<VADEvent>();\n protected inputWriter: WritableStreamDefaultWriter<AudioFrame | typeof VADStream.FLUSH_SENTINEL>;\n protected inputReader: ReadableStreamDefaultReader<AudioFrame | typeof VADStream.FLUSH_SENTINEL>;\n protected outputWriter: WritableStreamDefaultWriter<VADEvent>;\n protected outputReader: ReadableStreamDefaultReader<VADEvent>;\n protected closed = false;\n protected inputClosed = false;\n\n protected vad: VAD;\n protected lastActivityTime = BigInt(0);\n protected logger;\n protected deferredInputStream: DeferredReadableStream<AudioFrame>;\n\n private metricsStream: ReadableStream<VADEvent>;\n constructor(vad: VAD) {\n this.logger = log();\n this.vad = vad;\n this.deferredInputStream = new DeferredReadableStream<AudioFrame>();\n\n this.inputWriter = this.input.writable.getWriter();\n this.inputReader = this.input.readable.getReader();\n this.outputWriter = this.output.writable.getWriter();\n\n const [outputStream, metricsStream] = this.output.readable.tee();\n this.metricsStream = metricsStream;\n this.outputReader = outputStream.getReader();\n\n this.pumpDeferredStream();\n this.monitorMetrics();\n }\n\n /**\n * Reads from the deferred input stream and forwards chunks to the input writer.\n *\n * Note: we can't just do this.deferredInputStream.stream.pipeTo(this.input.writable)\n * because the inputWriter locks the this.input.writable stream. All writes must go through\n * the inputWriter.\n */\n private async pumpDeferredStream() {\n const reader = this.deferredInputStream.stream.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n await this.inputWriter.write(value);\n }\n } catch (e) {\n this.logger.error(`Error pumping deferred stream: ${e}`);\n throw e;\n } finally {\n reader.releaseLock();\n }\n }\n\n protected async monitorMetrics() {\n let inferenceDurationTotalMs = 0;\n let inferenceCount = 0;\n const metricsReader = this.metricsStream.getReader();\n while (true) {\n const { done, value } = await metricsReader.read();\n if (done) {\n break;\n }\n switch (value.type) {\n case VADEventType.START_OF_SPEECH:\n inferenceCount++;\n if (inferenceCount >= 1000 / this.vad.capabilities.updateInterval) {\n this.vad.emit('metrics_collected', {\n type: 'vad_metrics',\n timestamp: Date.now(),\n idleTimeMs: Math.trunc(\n Number((process.hrtime.bigint() - this.lastActivityTime) / BigInt(1000000)),\n ),\n inferenceDurationTotalMs,\n inferenceCount,\n label: this.vad.label,\n });\n\n inferenceCount = 0;\n inferenceDurationTotalMs = 0;\n }\n break;\n case VADEventType.INFERENCE_DONE:\n inferenceDurationTotalMs += Math.round(value.inferenceDuration);\n this.lastActivityTime = process.hrtime.bigint();\n break;\n case VADEventType.END_OF_SPEECH:\n this.lastActivityTime = process.hrtime.bigint();\n break;\n }\n }\n }\n\n /**\n * Safely send a VAD event to the output stream, handling writer release errors during shutdown.\n * @returns true if the event was sent, false if the stream is closing\n * @throws Error if an unexpected error occurs\n */\n protected sendVADEvent(event: VADEvent): boolean {\n if (this.closed) {\n return false;\n }\n\n try {\n this.outputWriter.write(event);\n return true;\n } catch (e) {\n throw e;\n }\n }\n\n updateInputStream(audioStream: ReadableStream<AudioFrame>) {\n this.deferredInputStream.setSource(audioStream);\n }\n\n detachInputStream() {\n this.deferredInputStream.detachSource();\n }\n\n /** @deprecated Use `updateInputStream` instead */\n pushFrame(frame: AudioFrame) {\n // TODO(AJS-395): remove this method\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputWriter.write(frame);\n }\n\n flush() {\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputWriter.write(VADStream.FLUSH_SENTINEL);\n }\n\n endInput() {\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputClosed = true;\n this.input.writable.close();\n }\n\n async next(): Promise<IteratorResult<VADEvent>> {\n return this.outputReader.read().then(({ done, value }) => {\n if (done) {\n return { done: true, value: undefined };\n }\n return { done: false, value };\n });\n }\n\n close() {\n this.outputWriter.releaseLock();\n this.outputReader.cancel();\n this.output.writable.close();\n this.closed = true;\n }\n\n [Symbol.asyncIterator](): VADStream {\n return this;\n }\n}\n"],"mappings":"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","names":["VADEventType"]}
|
|
@@ -60,8 +60,10 @@ class ParticipantAudioInputStream extends import_io.AudioInput {
|
|
|
60
60
|
if (this.participantIdentity === participantIdentity) {
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
|
+
if (this.participantIdentity) {
|
|
64
|
+
this.closeStream();
|
|
65
|
+
}
|
|
63
66
|
this.participantIdentity = participantIdentity;
|
|
64
|
-
this.closeStream();
|
|
65
67
|
if (!participantIdentity) {
|
|
66
68
|
return;
|
|
67
69
|
}
|
|
@@ -97,11 +99,9 @@ class ParticipantAudioInputStream extends import_io.AudioInput {
|
|
|
97
99
|
}
|
|
98
100
|
};
|
|
99
101
|
closeStream() {
|
|
100
|
-
var _a;
|
|
101
102
|
if (this.deferredStream.isSourceSet) {
|
|
102
103
|
this.deferredStream.detachSource();
|
|
103
104
|
}
|
|
104
|
-
(_a = this.frameProcessor) == null ? void 0 : _a.close();
|
|
105
105
|
this.publication = null;
|
|
106
106
|
}
|
|
107
107
|
onTrackSubscribed = (track, publication, participant) => {
|
|
@@ -147,10 +147,13 @@ class ParticipantAudioInputStream extends import_io.AudioInput {
|
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
149
|
async close() {
|
|
150
|
+
var _a;
|
|
150
151
|
this.room.off(import_rtc_node2.RoomEvent.TrackSubscribed, this.onTrackSubscribed);
|
|
151
152
|
this.room.off(import_rtc_node2.RoomEvent.TrackUnpublished, this.onTrackUnpublished);
|
|
152
153
|
this.room.off(import_rtc_node2.RoomEvent.TokenRefreshed, this.onTokenRefreshed);
|
|
153
154
|
this.closeStream();
|
|
155
|
+
(_a = this.frameProcessor) == null ? void 0 : _a.close();
|
|
156
|
+
this.frameProcessor = void 0;
|
|
154
157
|
await this.deferredStream.stream.cancel().catch(() => {
|
|
155
158
|
});
|
|
156
159
|
}
|
|
@@ -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 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
|
|
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 if (this.participantIdentity) {\n this.closeStream();\n }\n this.participantIdentity = participantIdentity;\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.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 this.frameProcessor?.close();\n this.frameProcessor = undefined;\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":["import_rtc_node","publication"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_input.d.ts","sourceRoot":"","sources":["../../../src/voice/room_io/_input.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAEL,KAAK,wBAAwB,EAC7B,iBAAiB,EAGjB,KAAK,IAAI,EAGV,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,qBAAa,2BAA4B,SAAQ,UAAU;IACzD,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,iBAAiB,CAAC,CAA2B;IACrD,OAAO,CAAC,cAAc,CAAC,CAA6B;IACpD,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,MAAM,CAAS;gBACX,EACV,IAAI,EACJ,UAAU,EACV,WAAW,EACX,iBAAiB,GAClB,EAAE;QACD,IAAI,EAAE,IAAI,CAAC;QACX,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,iBAAiB,CAAC,EAAE,wBAAwB,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;KAC3E;IAgBD,cAAc,CAAC,WAAW,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"_input.d.ts","sourceRoot":"","sources":["../../../src/voice/room_io/_input.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAEL,KAAK,wBAAwB,EAC7B,iBAAiB,EAGjB,KAAK,IAAI,EAGV,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,qBAAa,2BAA4B,SAAQ,UAAU;IACzD,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,iBAAiB,CAAC,CAA2B;IACrD,OAAO,CAAC,cAAc,CAAC,CAA6B;IACpD,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,MAAM,CAAS;gBACX,EACV,IAAI,EACJ,UAAU,EACV,WAAW,EACX,iBAAiB,GAClB,EAAE;QACD,IAAI,EAAE,IAAI,CAAC;QACX,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,iBAAiB,CAAC,EAAE,wBAAwB,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;KAC3E;IAgBD,cAAc,CAAC,WAAW,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI;IA6C7D,OAAO,CAAC,kBAAkB,CAqBxB;IAEF,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,iBAAiB,CA+BvB;IAEF,OAAO,CAAC,gBAAgB,CAOtB;IAEF,OAAO,CAAC,YAAY;IASd,KAAK;CAUZ"}
|
|
@@ -42,8 +42,10 @@ class ParticipantAudioInputStream extends AudioInput {
|
|
|
42
42
|
if (this.participantIdentity === participantIdentity) {
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
|
+
if (this.participantIdentity) {
|
|
46
|
+
this.closeStream();
|
|
47
|
+
}
|
|
45
48
|
this.participantIdentity = participantIdentity;
|
|
46
|
-
this.closeStream();
|
|
47
49
|
if (!participantIdentity) {
|
|
48
50
|
return;
|
|
49
51
|
}
|
|
@@ -79,11 +81,9 @@ class ParticipantAudioInputStream extends AudioInput {
|
|
|
79
81
|
}
|
|
80
82
|
};
|
|
81
83
|
closeStream() {
|
|
82
|
-
var _a;
|
|
83
84
|
if (this.deferredStream.isSourceSet) {
|
|
84
85
|
this.deferredStream.detachSource();
|
|
85
86
|
}
|
|
86
|
-
(_a = this.frameProcessor) == null ? void 0 : _a.close();
|
|
87
87
|
this.publication = null;
|
|
88
88
|
}
|
|
89
89
|
onTrackSubscribed = (track, publication, participant) => {
|
|
@@ -129,10 +129,13 @@ class ParticipantAudioInputStream extends AudioInput {
|
|
|
129
129
|
});
|
|
130
130
|
}
|
|
131
131
|
async close() {
|
|
132
|
+
var _a;
|
|
132
133
|
this.room.off(RoomEvent.TrackSubscribed, this.onTrackSubscribed);
|
|
133
134
|
this.room.off(RoomEvent.TrackUnpublished, this.onTrackUnpublished);
|
|
134
135
|
this.room.off(RoomEvent.TokenRefreshed, this.onTokenRefreshed);
|
|
135
136
|
this.closeStream();
|
|
137
|
+
(_a = this.frameProcessor) == null ? void 0 : _a.close();
|
|
138
|
+
this.frameProcessor = void 0;
|
|
136
139
|
await this.deferredStream.stream.cancel().catch(() => {
|
|
137
140
|
});
|
|
138
141
|
}
|
|
@@ -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 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
|
|
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 if (this.participantIdentity) {\n this.closeStream();\n }\n this.participantIdentity = participantIdentity;\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.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 this.frameProcessor?.close();\n this.frameProcessor = undefined;\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"]}
|
package/package.json
CHANGED
package/src/inference/stt.ts
CHANGED
|
@@ -177,7 +177,7 @@ export class STT<TModel extends STTModels> extends BaseSTT {
|
|
|
177
177
|
#logger = log();
|
|
178
178
|
|
|
179
179
|
constructor(opts?: {
|
|
180
|
-
model?:
|
|
180
|
+
model?: ModelWithLanguage;
|
|
181
181
|
language?: STTLanguages;
|
|
182
182
|
baseURL?: string;
|
|
183
183
|
encoding?: STTEncoding;
|
|
@@ -215,11 +215,29 @@ export class STT<TModel extends STTModels> extends BaseSTT {
|
|
|
215
215
|
throw new Error('apiSecret is required: pass apiSecret or set LIVEKIT_API_SECRET');
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
// Parse language from model string if provided: "provider/model:language"
|
|
219
|
+
let nextModel = model;
|
|
220
|
+
let nextLanguage = language;
|
|
221
|
+
if (typeof nextModel === 'string') {
|
|
222
|
+
const idx = nextModel.lastIndexOf(':');
|
|
223
|
+
if (idx !== -1) {
|
|
224
|
+
const languageFromModel = nextModel.slice(idx + 1) as STTLanguages;
|
|
225
|
+
if (nextLanguage && nextLanguage !== languageFromModel) {
|
|
226
|
+
this.#logger.warn(
|
|
227
|
+
'`language` is provided via both argument and model, using the one from the argument',
|
|
228
|
+
{ language: nextLanguage, model: nextModel },
|
|
229
|
+
);
|
|
230
|
+
} else {
|
|
231
|
+
nextLanguage = languageFromModel;
|
|
232
|
+
}
|
|
233
|
+
nextModel = nextModel.slice(0, idx) as TModel;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
218
236
|
const normalizedFallback = fallback ? normalizeSTTFallback(fallback) : undefined;
|
|
219
237
|
|
|
220
238
|
this.opts = {
|
|
221
|
-
model,
|
|
222
|
-
language,
|
|
239
|
+
model: nextModel as TModel,
|
|
240
|
+
language: nextLanguage,
|
|
223
241
|
encoding,
|
|
224
242
|
sampleRate,
|
|
225
243
|
baseURL: lkBaseURL,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2024 LiveKit, Inc.
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
import { Room, RoomEvent } from '@livekit/rtc-node';
|
|
4
|
+
import { Room, RoomEvent, dispose } from '@livekit/rtc-node';
|
|
5
5
|
import { EventEmitter, once } from 'node:events';
|
|
6
6
|
import { pathToFileURL } from 'node:url';
|
|
7
7
|
import type { Logger } from 'pino';
|
|
@@ -245,6 +245,18 @@ const startJob = (
|
|
|
245
245
|
|
|
246
246
|
await join.await;
|
|
247
247
|
|
|
248
|
+
// Dispose native FFI resources (Rust FfiServer, tokio runtimes, libwebrtc)
|
|
249
|
+
// before process.exit() to prevent libc++abi mutex crash during teardown.
|
|
250
|
+
// Without this, process.exit() can kill the process while native threads are
|
|
251
|
+
// still running, causing: "mutex lock failed: Invalid argument"
|
|
252
|
+
// See: https://github.com/livekit/node-sdks/issues/564
|
|
253
|
+
try {
|
|
254
|
+
await dispose();
|
|
255
|
+
logger.debug('native resources disposed');
|
|
256
|
+
} catch (error) {
|
|
257
|
+
logger.warn({ error }, 'failed to dispose native resources');
|
|
258
|
+
}
|
|
259
|
+
|
|
248
260
|
logger.debug('Job process shutdown');
|
|
249
261
|
process.exit(0);
|
|
250
262
|
}
|
|
@@ -182,7 +182,7 @@ export abstract class SupervisedProc {
|
|
|
182
182
|
this.proc.send({
|
|
183
183
|
case: 'initializeRequest',
|
|
184
184
|
value: {
|
|
185
|
-
loggerOptions,
|
|
185
|
+
loggerOptions: loggerOptions(),
|
|
186
186
|
pingInterval: this.#opts.pingInterval,
|
|
187
187
|
pingTimeout: this.#opts.pingTimeout,
|
|
188
188
|
highPingThreshold: this.#opts.highPingThreshold,
|
package/src/llm/llm.ts
CHANGED
|
@@ -174,7 +174,7 @@ export abstract class LLMStream implements AsyncIterableIterator<ChatChunk> {
|
|
|
174
174
|
this.emitError({ error, recoverable: true });
|
|
175
175
|
this.logger.warn(
|
|
176
176
|
{ llm: this.#llm.label(), attempt: i + 1, error },
|
|
177
|
-
`failed to generate LLM completion, retrying in ${retryInterval}
|
|
177
|
+
`failed to generate LLM completion, retrying in ${retryInterval}ms`,
|
|
178
178
|
);
|
|
179
179
|
}
|
|
180
180
|
|
package/src/log.ts
CHANGED
|
@@ -13,17 +13,27 @@ export type LoggerOptions = {
|
|
|
13
13
|
level?: string;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// Use Symbol.for() + globalThis to create process-wide singletons.
|
|
17
|
+
// This avoids the "dual package hazard". Symbol.for() returns the same Symbol
|
|
18
|
+
// across all module instances, and globalThis is shared process-wide.
|
|
19
|
+
const LOGGER_KEY = Symbol.for('@livekit/agents:logger');
|
|
20
|
+
const LOGGER_OPTIONS_KEY = Symbol.for('@livekit/agents:loggerOptions');
|
|
21
|
+
const OTEL_ENABLED_KEY = Symbol.for('@livekit/agents:otelEnabled');
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
type GlobalState = {
|
|
24
|
+
[LOGGER_KEY]?: Logger;
|
|
25
|
+
[LOGGER_OPTIONS_KEY]?: LoggerOptions;
|
|
26
|
+
[OTEL_ENABLED_KEY]?: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const globals = globalThis as typeof globalThis & GlobalState;
|
|
21
30
|
|
|
22
31
|
/** @internal */
|
|
23
|
-
|
|
32
|
+
export const loggerOptions = (): LoggerOptions | undefined => globals[LOGGER_OPTIONS_KEY];
|
|
24
33
|
|
|
25
34
|
/** @internal */
|
|
26
35
|
export const log = () => {
|
|
36
|
+
const logger = globals[LOGGER_KEY];
|
|
27
37
|
if (!logger) {
|
|
28
38
|
throw new TypeError('logger not initialized. did you forget to run initializeLogger()?');
|
|
29
39
|
}
|
|
@@ -32,8 +42,8 @@ export const log = () => {
|
|
|
32
42
|
|
|
33
43
|
/** @internal */
|
|
34
44
|
export const initializeLogger = ({ pretty, level }: LoggerOptions) => {
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
globals[LOGGER_OPTIONS_KEY] = { pretty, level };
|
|
46
|
+
globals[LOGGER_KEY] = pino(
|
|
37
47
|
{ level: level || 'info' },
|
|
38
48
|
pretty ? pinoPretty({ colorize: true }) : process.stdout,
|
|
39
49
|
);
|
|
@@ -65,13 +75,14 @@ class OtelDestination extends Writable {
|
|
|
65
75
|
* @internal
|
|
66
76
|
*/
|
|
67
77
|
export const enableOtelLogging = () => {
|
|
68
|
-
if (
|
|
78
|
+
if (globals[OTEL_ENABLED_KEY] || !globals[LOGGER_KEY]) {
|
|
69
79
|
console.warn('OTEL logging already enabled or logger not initialized');
|
|
70
80
|
return;
|
|
71
81
|
}
|
|
72
|
-
|
|
82
|
+
globals[OTEL_ENABLED_KEY] = true;
|
|
73
83
|
|
|
74
|
-
const
|
|
84
|
+
const opts = globals[LOGGER_OPTIONS_KEY]!;
|
|
85
|
+
const { pretty, level } = opts;
|
|
75
86
|
|
|
76
87
|
const logLevel = level || 'info';
|
|
77
88
|
const streams: { stream: DestinationStream; level: string }[] = [
|
|
@@ -79,5 +90,5 @@ export const enableOtelLogging = () => {
|
|
|
79
90
|
{ stream: new OtelDestination(), level: 'debug' },
|
|
80
91
|
];
|
|
81
92
|
|
|
82
|
-
|
|
93
|
+
globals[LOGGER_KEY] = pino({ level: logLevel }, multistream(streams));
|
|
83
94
|
};
|
package/src/stt/stt.ts
CHANGED
|
@@ -236,8 +236,8 @@ export abstract class SpeechStream implements AsyncIterableIterator<SpeechEvent>
|
|
|
236
236
|
// Don't emit error event for recoverable errors during retry loop
|
|
237
237
|
// to avoid ERR_UNHANDLED_ERROR or premature session termination
|
|
238
238
|
this.logger.warn(
|
|
239
|
-
{
|
|
240
|
-
`failed to recognize speech, retrying in ${retryInterval}
|
|
239
|
+
{ stt: this.#stt.label, attempt: i + 1, error },
|
|
240
|
+
`failed to recognize speech, retrying in ${retryInterval}ms`,
|
|
241
241
|
);
|
|
242
242
|
}
|
|
243
243
|
|
|
@@ -351,6 +351,11 @@ export abstract class SpeechStream implements AsyncIterableIterator<SpeechEvent>
|
|
|
351
351
|
}
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
+
if (frame.samplesPerChannel === 0) {
|
|
355
|
+
this.input.put(frame);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
354
359
|
if (this.resampler) {
|
|
355
360
|
const frames = this.resampler.push(frame);
|
|
356
361
|
for (const frame of frames) {
|