@livekit/agents 0.5.2 → 0.6.1
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/index.cjs +3 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/job.cjs.map +1 -1
- package/dist/job.js.map +1 -1
- package/dist/llm/index.cjs +2 -0
- package/dist/llm/index.cjs.map +1 -1
- package/dist/llm/index.d.ts +1 -1
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +2 -0
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/llm.cjs +47 -3
- package/dist/llm/llm.cjs.map +1 -1
- package/dist/llm/llm.d.ts +15 -2
- package/dist/llm/llm.d.ts.map +1 -1
- package/dist/llm/llm.js +46 -3
- package/dist/llm/llm.js.map +1 -1
- package/dist/metrics/base.cjs +44 -0
- package/dist/metrics/base.cjs.map +1 -0
- package/dist/metrics/base.d.ts +96 -0
- package/dist/metrics/base.d.ts.map +1 -0
- package/dist/metrics/base.js +20 -0
- package/dist/metrics/base.js.map +1 -0
- package/dist/metrics/index.cjs +35 -0
- package/dist/metrics/index.cjs.map +1 -0
- package/dist/metrics/index.d.ts +5 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +9 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/usage_collector.cjs +53 -0
- package/dist/metrics/usage_collector.cjs.map +1 -0
- package/dist/metrics/usage_collector.d.ts +14 -0
- package/dist/metrics/usage_collector.d.ts.map +1 -0
- package/dist/metrics/usage_collector.js +29 -0
- package/dist/metrics/usage_collector.js.map +1 -0
- package/dist/metrics/utils.cjs +104 -0
- package/dist/metrics/utils.cjs.map +1 -0
- package/dist/metrics/utils.d.ts +10 -0
- package/dist/metrics/utils.d.ts.map +1 -0
- package/dist/metrics/utils.js +73 -0
- package/dist/metrics/utils.js.map +1 -0
- package/dist/multimodal/multimodal_agent.cjs +34 -16
- package/dist/multimodal/multimodal_agent.cjs.map +1 -1
- package/dist/multimodal/multimodal_agent.d.ts +4 -5
- package/dist/multimodal/multimodal_agent.d.ts.map +1 -1
- package/dist/multimodal/multimodal_agent.js +34 -16
- package/dist/multimodal/multimodal_agent.js.map +1 -1
- package/dist/pipeline/index.cjs +2 -0
- package/dist/pipeline/index.cjs.map +1 -1
- package/dist/pipeline/index.d.ts +1 -1
- package/dist/pipeline/index.d.ts.map +1 -1
- package/dist/pipeline/index.js +3 -1
- package/dist/pipeline/index.js.map +1 -1
- package/dist/pipeline/pipeline_agent.cjs +166 -66
- package/dist/pipeline/pipeline_agent.cjs.map +1 -1
- package/dist/pipeline/pipeline_agent.d.ts +10 -4
- package/dist/pipeline/pipeline_agent.d.ts.map +1 -1
- package/dist/pipeline/pipeline_agent.js +169 -69
- package/dist/pipeline/pipeline_agent.js.map +1 -1
- package/dist/pipeline/speech_handle.cjs +49 -1
- package/dist/pipeline/speech_handle.cjs.map +1 -1
- package/dist/pipeline/speech_handle.d.ts +12 -2
- package/dist/pipeline/speech_handle.d.ts.map +1 -1
- package/dist/pipeline/speech_handle.js +50 -2
- package/dist/pipeline/speech_handle.js.map +1 -1
- package/dist/stt/index.cjs.map +1 -1
- package/dist/stt/index.d.ts +1 -1
- package/dist/stt/index.d.ts.map +1 -1
- package/dist/stt/index.js.map +1 -1
- package/dist/stt/stream_adapter.cjs +15 -5
- package/dist/stt/stream_adapter.cjs.map +1 -1
- package/dist/stt/stream_adapter.d.ts +4 -1
- package/dist/stt/stream_adapter.d.ts.map +1 -1
- package/dist/stt/stream_adapter.js +15 -5
- package/dist/stt/stream_adapter.js.map +1 -1
- package/dist/stt/stt.cjs +46 -2
- package/dist/stt/stt.cjs.map +1 -1
- package/dist/stt/stt.d.ts +25 -3
- package/dist/stt/stt.d.ts.map +1 -1
- package/dist/stt/stt.js +46 -2
- package/dist/stt/stt.js.map +1 -1
- package/dist/tts/index.cjs +4 -2
- package/dist/tts/index.cjs.map +1 -1
- package/dist/tts/index.d.ts +1 -1
- package/dist/tts/index.d.ts.map +1 -1
- package/dist/tts/index.js +3 -1
- package/dist/tts/index.js.map +1 -1
- package/dist/tts/stream_adapter.cjs +14 -3
- package/dist/tts/stream_adapter.cjs.map +1 -1
- package/dist/tts/stream_adapter.d.ts +3 -0
- package/dist/tts/stream_adapter.d.ts.map +1 -1
- package/dist/tts/stream_adapter.js +15 -4
- package/dist/tts/stream_adapter.js.map +1 -1
- package/dist/tts/tts.cjs +109 -6
- package/dist/tts/tts.cjs.map +1 -1
- package/dist/tts/tts.d.ts +24 -1
- package/dist/tts/tts.d.ts.map +1 -1
- package/dist/tts/tts.js +107 -5
- package/dist/tts/tts.js.map +1 -1
- package/dist/utils.cjs +11 -4
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +11 -4
- package/dist/utils.js.map +1 -1
- package/dist/vad.cjs +43 -2
- package/dist/vad.cjs.map +1 -1
- package/dist/vad.d.ts +21 -4
- package/dist/vad.d.ts.map +1 -1
- package/dist/vad.js +43 -2
- package/dist/vad.js.map +1 -1
- package/dist/worker.cjs +5 -2
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +5 -2
- package/dist/worker.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +2 -1
- package/src/job.ts +3 -3
- package/src/llm/index.ts +2 -0
- package/src/llm/llm.ts +55 -3
- package/src/metrics/base.ts +127 -0
- package/src/metrics/index.ts +20 -0
- package/src/metrics/usage_collector.ts +40 -0
- package/src/metrics/utils.ts +100 -0
- package/src/multimodal/multimodal_agent.ts +57 -23
- package/src/pipeline/index.ts +1 -1
- package/src/pipeline/pipeline_agent.ts +208 -89
- package/src/pipeline/speech_handle.ts +67 -2
- package/src/stt/index.ts +2 -0
- package/src/stt/stream_adapter.ts +17 -5
- package/src/stt/stt.ts +67 -3
- package/src/tts/index.ts +2 -0
- package/src/tts/stream_adapter.ts +17 -4
- package/src/tts/tts.ts +127 -4
- package/src/utils.ts +12 -4
- package/src/vad.ts +61 -4
- package/src/worker.ts +7 -3
package/dist/tts/tts.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tts/tts.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { AsyncIterableQueue, mergeFrames } from '../utils.js';\n\n/** SynthesizedAudio is a packet of speech synthesis as returned by the TTS. */\nexport interface SynthesizedAudio {\n /** Request ID (one segment could be made up of multiple requests) */\n requestId: string;\n /** Segment ID, each segment is separated by a flush */\n segmentId: string;\n /** Synthesized audio frame */\n frame: AudioFrame;\n /** Current segment of the synthesized audio */\n deltaText?: string;\n}\n\n/**\n * Describes the capabilities of the TTS provider.\n *\n * @remarks\n * At present, only `streaming` is supplied to this interface, and the framework only supports\n * providers that do have a streaming endpoint.\n */\nexport interface TTSCapabilities {\n streaming: boolean;\n}\n\n/**\n * An instance of a text-to-speech adapter.\n *\n * @remarks\n * This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that\n * exports its own child TTS class, which inherits this class's methods.\n */\nexport abstract class TTS {\n #capabilities: TTSCapabilities;\n #sampleRate: number;\n #numChannels: number;\n\n constructor(sampleRate: number, numChannels: number, capabilities: TTSCapabilities) {\n this.#capabilities = capabilities;\n this.#sampleRate = sampleRate;\n this.#numChannels = numChannels;\n }\n\n /** Returns this TTS's capabilities */\n get capabilities(): TTSCapabilities {\n return this.#capabilities;\n }\n\n /** Returns the sample rate of audio frames returned by this TTS */\n get sampleRate(): number {\n return this.#sampleRate;\n }\n\n /** Returns the channel count of audio frames returned by this TTS */\n get numChannels(): number {\n return this.#numChannels;\n }\n\n /**\n * Receives text and returns synthesis in the form of a {@link ChunkedStream}\n */\n abstract synthesize(text: string): ChunkedStream;\n\n /**\n * Returns a {@link SynthesizeStream} that can be used to push text and receive audio data\n */\n abstract stream(): SynthesizeStream;\n}\n\n/**\n * An instance of a text-to-speech stream, as an asynchronous iterable iterator.\n *\n * @example Looping through frames\n * ```ts\n * for await (const event of stream) {\n * await source.captureFrame(event.frame);\n * }\n * ```\n *\n * @remarks\n * This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that\n * exports its own child SynthesizeStream class, which inherits this class's methods.\n */\nexport abstract class SynthesizeStream\n implements AsyncIterableIterator<SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM>\n{\n protected static readonly FLUSH_SENTINEL = Symbol('FLUSH_SENTINEL');\n static readonly END_OF_STREAM = Symbol('END_OF_STREAM');\n protected input = new AsyncIterableQueue<string | typeof SynthesizeStream.FLUSH_SENTINEL>();\n protected queue = new AsyncIterableQueue<\n SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM\n >();\n protected closed = false;\n\n /** Push a string of text to the TTS */\n pushText(text: string) {\n if (this.input.closed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.input.put(text);\n }\n\n /** Flush the TTS, causing it to process all pending text */\n flush() {\n if (this.input.closed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.input.put(SynthesizeStream.FLUSH_SENTINEL);\n }\n\n /** Mark the input as ended and forbid additional pushes */\n endInput() {\n if (this.input.closed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.input.close();\n }\n\n next(): Promise<IteratorResult<SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM>> {\n return this.queue.next();\n }\n\n /** Close both the input and output of the TTS stream */\n close() {\n this.input.close();\n this.queue.close();\n this.closed = true;\n }\n\n [Symbol.asyncIterator](): SynthesizeStream {\n return this;\n }\n}\n\n/**\n * An instance of a text-to-speech response, as an asynchronous iterable iterator.\n *\n * @example Looping through frames\n * ```ts\n * for await (const event of stream) {\n * await source.captureFrame(event.frame);\n * }\n * ```\n *\n * @remarks\n * This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that\n * exports its own child ChunkedStream class, which inherits this class's methods.\n */\nexport abstract class ChunkedStream implements AsyncIterableIterator<SynthesizedAudio> {\n protected queue = new AsyncIterableQueue<SynthesizedAudio>();\n protected closed = false;\n\n /** Collect every frame into one in a single call */\n async collect(): Promise<AudioFrame> {\n const frames = [];\n for await (const event of this) {\n frames.push(event.frame);\n }\n return mergeFrames(frames);\n }\n\n next(): Promise<IteratorResult<SynthesizedAudio>> {\n return this.queue.next();\n }\n\n /** Close both the input and output of the TTS stream */\n close() {\n this.queue.close();\n this.closed = true;\n }\n\n [Symbol.asyncIterator](): ChunkedStream {\n return this;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,mBAAgD;AAgCzC,MAAe,IAAI;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YAAY,YAAoB,aAAqB,cAA+B;AAClF,SAAK,gBAAgB;AACrB,SAAK,cAAc;AACnB,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA,EAGA,IAAI,eAAgC;AAClC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,aAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,cAAsB;AACxB,WAAO,KAAK;AAAA,EACd;AAWF;AAgBO,MAAe,iBAEtB;AAAA,EACE,OAA0B,iBAAiB,OAAO,gBAAgB;AAAA,EAClE,OAAgB,gBAAgB,OAAO,eAAe;AAAA,EAC5C,QAAQ,IAAI,gCAAoE;AAAA,EAChF,QAAQ,IAAI,gCAEpB;AAAA,EACQ,SAAS;AAAA;AAAA,EAGnB,SAAS,MAAc;AACrB,QAAI,KAAK,MAAM,QAAQ;AACrB,YAAM,IAAI,MAAM,iBAAiB;AAAA,IACnC;AACA,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,MAAM,kBAAkB;AAAA,IACpC;AACA,SAAK,MAAM,IAAI,IAAI;AAAA,EACrB;AAAA;AAAA,EAGA,QAAQ;AACN,QAAI,KAAK,MAAM,QAAQ;AACrB,YAAM,IAAI,MAAM,iBAAiB;AAAA,IACnC;AACA,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,MAAM,kBAAkB;AAAA,IACpC;AACA,SAAK,MAAM,IAAI,iBAAiB,cAAc;AAAA,EAChD;AAAA;AAAA,EAGA,WAAW;AACT,QAAI,KAAK,MAAM,QAAQ;AACrB,YAAM,IAAI,MAAM,iBAAiB;AAAA,IACnC;AACA,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,MAAM,kBAAkB;AAAA,IACpC;AACA,SAAK,MAAM,MAAM;AAAA,EACnB;AAAA,EAEA,OAA0F;AACxF,WAAO,KAAK,MAAM,KAAK;AAAA,EACzB;AAAA;AAAA,EAGA,QAAQ;AACN,SAAK,MAAM,MAAM;AACjB,SAAK,MAAM,MAAM;AACjB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,CAAC,OAAO,aAAa,IAAsB;AACzC,WAAO;AAAA,EACT;AACF;AAgBO,MAAe,cAAiE;AAAA,EAC3E,QAAQ,IAAI,gCAAqC;AAAA,EACjD,SAAS;AAAA;AAAA,EAGnB,MAAM,UAA+B;AACnC,UAAM,SAAS,CAAC;AAChB,qBAAiB,SAAS,MAAM;AAC9B,aAAO,KAAK,MAAM,KAAK;AAAA,IACzB;AACA,eAAO,0BAAY,MAAM;AAAA,EAC3B;AAAA,EAEA,OAAkD;AAChD,WAAO,KAAK,MAAM,KAAK;AAAA,EACzB;AAAA;AAAA,EAGA,QAAQ;AACN,SAAK,MAAM,MAAM;AACjB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,CAAC,OAAO,aAAa,IAAmB;AACtC,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/tts/tts.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 { TTSMetrics } from '../metrics/base.js';\nimport { AsyncIterableQueue, mergeFrames } from '../utils.js';\n\n/** SynthesizedAudio is a packet of speech synthesis as returned by the TTS. */\nexport interface SynthesizedAudio {\n /** Request ID (one segment could be made up of multiple requests) */\n requestId: string;\n /** Segment ID, each segment is separated by a flush */\n segmentId: string;\n /** Synthesized audio frame */\n frame: AudioFrame;\n /** Current segment of the synthesized audio */\n deltaText?: string;\n /** Whether this is the last frame of the segment (streaming only) */\n final: boolean;\n}\n\n/**\n * Describes the capabilities of the TTS provider.\n *\n * @remarks\n * At present, only `streaming` is supplied to this interface, and the framework only supports\n * providers that do have a streaming endpoint.\n */\nexport interface TTSCapabilities {\n streaming: boolean;\n}\n\nexport enum TTSEvent {\n METRICS_COLLECTED,\n}\n\nexport type TTSCallbacks = {\n [TTSEvent.METRICS_COLLECTED]: (metrics: TTSMetrics) => void;\n};\n\n/**\n * An instance of a text-to-speech adapter.\n *\n * @remarks\n * This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that\n * exports its own child TTS class, which inherits this class's methods.\n */\nexport abstract class TTS extends (EventEmitter as new () => TypedEmitter<TTSCallbacks>) {\n #capabilities: TTSCapabilities;\n #sampleRate: number;\n #numChannels: number;\n abstract label: string;\n\n constructor(sampleRate: number, numChannels: number, capabilities: TTSCapabilities) {\n super();\n this.#capabilities = capabilities;\n this.#sampleRate = sampleRate;\n this.#numChannels = numChannels;\n }\n\n /** Returns this TTS's capabilities */\n get capabilities(): TTSCapabilities {\n return this.#capabilities;\n }\n\n /** Returns the sample rate of audio frames returned by this TTS */\n get sampleRate(): number {\n return this.#sampleRate;\n }\n\n /** Returns the channel count of audio frames returned by this TTS */\n get numChannels(): number {\n return this.#numChannels;\n }\n\n /**\n * Receives text and returns synthesis in the form of a {@link ChunkedStream}\n */\n abstract synthesize(text: string): ChunkedStream;\n\n /**\n * Returns a {@link SynthesizeStream} that can be used to push text and receive audio data\n */\n abstract stream(): SynthesizeStream;\n}\n\n/**\n * An instance of a text-to-speech stream, as an asynchronous iterable iterator.\n *\n * @example Looping through frames\n * ```ts\n * for await (const event of stream) {\n * await source.captureFrame(event.frame);\n * }\n * ```\n *\n * @remarks\n * This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that\n * exports its own child SynthesizeStream class, which inherits this class's methods.\n */\nexport abstract class SynthesizeStream\n implements AsyncIterableIterator<SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM>\n{\n protected static readonly FLUSH_SENTINEL = Symbol('FLUSH_SENTINEL');\n static readonly END_OF_STREAM = Symbol('END_OF_STREAM');\n protected input = new AsyncIterableQueue<string | typeof SynthesizeStream.FLUSH_SENTINEL>();\n protected queue = new AsyncIterableQueue<\n SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM\n >();\n protected output = new AsyncIterableQueue<\n SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM\n >();\n protected closed = false;\n abstract label: string;\n #tts: TTS;\n #metricsPendingTexts: string[] = [];\n #metricsText = '';\n #monitorMetricsTask?: Promise<void>;\n\n constructor(tts: TTS) {\n this.#tts = tts;\n }\n\n protected async monitorMetrics() {\n const startTime = process.hrtime.bigint();\n let audioDuration = 0;\n let ttfb: bigint | undefined;\n let requestId = '';\n\n const emit = () => {\n if (this.#metricsPendingTexts.length) {\n const text = this.#metricsPendingTexts.shift()!;\n const duration = process.hrtime.bigint() - startTime;\n const metrics: TTSMetrics = {\n timestamp: Date.now(),\n requestId,\n ttfb: Math.trunc(Number(ttfb! / BigInt(1000000))),\n duration: Math.trunc(Number(duration / BigInt(1000000))),\n charactersCount: text.length,\n audioDuration,\n cancelled: false, // XXX(nbsp)\n label: this.label,\n streamed: false,\n };\n this.#tts.emit(TTSEvent.METRICS_COLLECTED, metrics);\n }\n };\n\n for await (const audio of this.queue) {\n this.output.put(audio);\n if (audio === SynthesizeStream.END_OF_STREAM) continue;\n requestId = audio.requestId;\n if (!ttfb) {\n ttfb = process.hrtime.bigint() - startTime;\n }\n audioDuration += audio.frame.samplesPerChannel / audio.frame.sampleRate;\n if (audio.final) {\n emit();\n }\n }\n\n if (requestId) {\n emit();\n }\n this.output.close();\n }\n\n /** Push a string of text to the TTS */\n pushText(text: string) {\n if (!this.#monitorMetricsTask) {\n this.#monitorMetricsTask = this.monitorMetrics();\n }\n this.#metricsText += text;\n\n if (this.input.closed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.input.put(text);\n }\n\n /** Flush the TTS, causing it to process all pending text */\n flush() {\n if (this.#metricsText) {\n this.#metricsPendingTexts.push(this.#metricsText);\n this.#metricsText = '';\n }\n if (this.input.closed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.input.put(SynthesizeStream.FLUSH_SENTINEL);\n }\n\n /** Mark the input as ended and forbid additional pushes */\n endInput() {\n if (this.input.closed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.input.close();\n }\n\n next(): Promise<IteratorResult<SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM>> {\n return this.output.next();\n }\n\n /** Close both the input and output of the TTS stream */\n close() {\n this.input.close();\n this.output.close();\n this.closed = true;\n }\n\n [Symbol.asyncIterator](): SynthesizeStream {\n return this;\n }\n}\n\n/**\n * An instance of a text-to-speech response, as an asynchronous iterable iterator.\n *\n * @example Looping through frames\n * ```ts\n * for await (const event of stream) {\n * await source.captureFrame(event.frame);\n * }\n * ```\n *\n * @remarks\n * This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that\n * exports its own child ChunkedStream class, which inherits this class's methods.\n */\nexport abstract class ChunkedStream implements AsyncIterableIterator<SynthesizedAudio> {\n protected queue = new AsyncIterableQueue<SynthesizedAudio>();\n protected output = new AsyncIterableQueue<SynthesizedAudio>();\n protected closed = false;\n abstract label: string;\n #text: string;\n #tts: TTS;\n\n constructor(text: string, tts: TTS) {\n this.#text = text;\n this.#tts = tts;\n\n this.monitorMetrics();\n }\n\n protected async monitorMetrics() {\n const startTime = process.hrtime.bigint();\n let audioDuration = 0;\n let ttfb: bigint | undefined;\n let requestId = '';\n\n for await (const audio of this.queue) {\n this.output.put(audio);\n requestId = audio.requestId;\n if (!ttfb) {\n ttfb = process.hrtime.bigint() - startTime;\n }\n audioDuration += audio.frame.samplesPerChannel / audio.frame.sampleRate;\n }\n this.output.close();\n\n const duration = process.hrtime.bigint() - startTime;\n const metrics: TTSMetrics = {\n timestamp: Date.now(),\n requestId,\n ttfb: Math.trunc(Number(ttfb! / BigInt(1000000))),\n duration: Math.trunc(Number(duration / BigInt(1000000))),\n charactersCount: this.#text.length,\n audioDuration,\n cancelled: false, // XXX(nbsp)\n label: this.label,\n streamed: false,\n };\n this.#tts.emit(TTSEvent.METRICS_COLLECTED, metrics);\n }\n\n /** Collect every frame into one in a single call */\n async collect(): Promise<AudioFrame> {\n const frames = [];\n for await (const event of this) {\n frames.push(event.frame);\n }\n return mergeFrames(frames);\n }\n\n next(): Promise<IteratorResult<SynthesizedAudio>> {\n return this.output.next();\n }\n\n /** Close both the input and output of the TTS stream */\n close() {\n this.queue.close();\n this.output.close();\n this.closed = true;\n }\n\n [Symbol.asyncIterator](): ChunkedStream {\n return this;\n }\n}\n"],"mappings":"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","names":["TTSEvent"]}
|
package/dist/tts/tts.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { AudioFrame } from '@livekit/rtc-node';
|
|
2
|
+
import type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';
|
|
3
|
+
import type { TTSMetrics } from '../metrics/base.js';
|
|
2
4
|
import { AsyncIterableQueue } from '../utils.js';
|
|
3
5
|
/** SynthesizedAudio is a packet of speech synthesis as returned by the TTS. */
|
|
4
6
|
export interface SynthesizedAudio {
|
|
@@ -10,6 +12,8 @@ export interface SynthesizedAudio {
|
|
|
10
12
|
frame: AudioFrame;
|
|
11
13
|
/** Current segment of the synthesized audio */
|
|
12
14
|
deltaText?: string;
|
|
15
|
+
/** Whether this is the last frame of the segment (streaming only) */
|
|
16
|
+
final: boolean;
|
|
13
17
|
}
|
|
14
18
|
/**
|
|
15
19
|
* Describes the capabilities of the TTS provider.
|
|
@@ -21,6 +25,13 @@ export interface SynthesizedAudio {
|
|
|
21
25
|
export interface TTSCapabilities {
|
|
22
26
|
streaming: boolean;
|
|
23
27
|
}
|
|
28
|
+
export declare enum TTSEvent {
|
|
29
|
+
METRICS_COLLECTED = 0
|
|
30
|
+
}
|
|
31
|
+
export type TTSCallbacks = {
|
|
32
|
+
[TTSEvent.METRICS_COLLECTED]: (metrics: TTSMetrics) => void;
|
|
33
|
+
};
|
|
34
|
+
declare const TTS_base: new () => TypedEmitter<TTSCallbacks>;
|
|
24
35
|
/**
|
|
25
36
|
* An instance of a text-to-speech adapter.
|
|
26
37
|
*
|
|
@@ -28,8 +39,9 @@ export interface TTSCapabilities {
|
|
|
28
39
|
* This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that
|
|
29
40
|
* exports its own child TTS class, which inherits this class's methods.
|
|
30
41
|
*/
|
|
31
|
-
export declare abstract class TTS {
|
|
42
|
+
export declare abstract class TTS extends TTS_base {
|
|
32
43
|
#private;
|
|
44
|
+
abstract label: string;
|
|
33
45
|
constructor(sampleRate: number, numChannels: number, capabilities: TTSCapabilities);
|
|
34
46
|
/** Returns this TTS's capabilities */
|
|
35
47
|
get capabilities(): TTSCapabilities;
|
|
@@ -61,11 +73,16 @@ export declare abstract class TTS {
|
|
|
61
73
|
* exports its own child SynthesizeStream class, which inherits this class's methods.
|
|
62
74
|
*/
|
|
63
75
|
export declare abstract class SynthesizeStream implements AsyncIterableIterator<SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM> {
|
|
76
|
+
#private;
|
|
64
77
|
protected static readonly FLUSH_SENTINEL: unique symbol;
|
|
65
78
|
static readonly END_OF_STREAM: unique symbol;
|
|
66
79
|
protected input: AsyncIterableQueue<string | typeof SynthesizeStream.FLUSH_SENTINEL>;
|
|
67
80
|
protected queue: AsyncIterableQueue<SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM>;
|
|
81
|
+
protected output: AsyncIterableQueue<SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM>;
|
|
68
82
|
protected closed: boolean;
|
|
83
|
+
abstract label: string;
|
|
84
|
+
constructor(tts: TTS);
|
|
85
|
+
protected monitorMetrics(): Promise<void>;
|
|
69
86
|
/** Push a string of text to the TTS */
|
|
70
87
|
pushText(text: string): void;
|
|
71
88
|
/** Flush the TTS, causing it to process all pending text */
|
|
@@ -92,8 +109,13 @@ export declare abstract class SynthesizeStream implements AsyncIterableIterator<
|
|
|
92
109
|
* exports its own child ChunkedStream class, which inherits this class's methods.
|
|
93
110
|
*/
|
|
94
111
|
export declare abstract class ChunkedStream implements AsyncIterableIterator<SynthesizedAudio> {
|
|
112
|
+
#private;
|
|
95
113
|
protected queue: AsyncIterableQueue<SynthesizedAudio>;
|
|
114
|
+
protected output: AsyncIterableQueue<SynthesizedAudio>;
|
|
96
115
|
protected closed: boolean;
|
|
116
|
+
abstract label: string;
|
|
117
|
+
constructor(text: string, tts: TTS);
|
|
118
|
+
protected monitorMetrics(): Promise<void>;
|
|
97
119
|
/** Collect every frame into one in a single call */
|
|
98
120
|
collect(): Promise<AudioFrame>;
|
|
99
121
|
next(): Promise<IteratorResult<SynthesizedAudio>>;
|
|
@@ -101,4 +123,5 @@ export declare abstract class ChunkedStream implements AsyncIterableIterator<Syn
|
|
|
101
123
|
close(): void;
|
|
102
124
|
[Symbol.asyncIterator](): ChunkedStream;
|
|
103
125
|
}
|
|
126
|
+
export {};
|
|
104
127
|
//# sourceMappingURL=tts.d.ts.map
|
package/dist/tts/tts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tts.d.ts","sourceRoot":"","sources":["../../src/tts/tts.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAe,MAAM,aAAa,CAAC;AAE9D,+EAA+E;AAC/E,MAAM,WAAW,gBAAgB;IAC/B,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,KAAK,EAAE,UAAU,CAAC;IAClB,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"tts.d.ts","sourceRoot":"","sources":["../../src/tts/tts.ts"],"names":[],"mappings":"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"}
|
package/dist/tts/tts.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
1
2
|
import { AsyncIterableQueue, mergeFrames } from "../utils.js";
|
|
2
|
-
|
|
3
|
+
var TTSEvent = /* @__PURE__ */ ((TTSEvent2) => {
|
|
4
|
+
TTSEvent2[TTSEvent2["METRICS_COLLECTED"] = 0] = "METRICS_COLLECTED";
|
|
5
|
+
return TTSEvent2;
|
|
6
|
+
})(TTSEvent || {});
|
|
7
|
+
class TTS extends EventEmitter {
|
|
3
8
|
#capabilities;
|
|
4
9
|
#sampleRate;
|
|
5
10
|
#numChannels;
|
|
6
11
|
constructor(sampleRate, numChannels, capabilities) {
|
|
12
|
+
super();
|
|
7
13
|
this.#capabilities = capabilities;
|
|
8
14
|
this.#sampleRate = sampleRate;
|
|
9
15
|
this.#numChannels = numChannels;
|
|
@@ -26,9 +32,62 @@ class SynthesizeStream {
|
|
|
26
32
|
static END_OF_STREAM = Symbol("END_OF_STREAM");
|
|
27
33
|
input = new AsyncIterableQueue();
|
|
28
34
|
queue = new AsyncIterableQueue();
|
|
35
|
+
output = new AsyncIterableQueue();
|
|
29
36
|
closed = false;
|
|
37
|
+
#tts;
|
|
38
|
+
#metricsPendingTexts = [];
|
|
39
|
+
#metricsText = "";
|
|
40
|
+
#monitorMetricsTask;
|
|
41
|
+
constructor(tts) {
|
|
42
|
+
this.#tts = tts;
|
|
43
|
+
}
|
|
44
|
+
async monitorMetrics() {
|
|
45
|
+
const startTime = process.hrtime.bigint();
|
|
46
|
+
let audioDuration = 0;
|
|
47
|
+
let ttfb;
|
|
48
|
+
let requestId = "";
|
|
49
|
+
const emit = () => {
|
|
50
|
+
if (this.#metricsPendingTexts.length) {
|
|
51
|
+
const text = this.#metricsPendingTexts.shift();
|
|
52
|
+
const duration = process.hrtime.bigint() - startTime;
|
|
53
|
+
const metrics = {
|
|
54
|
+
timestamp: Date.now(),
|
|
55
|
+
requestId,
|
|
56
|
+
ttfb: Math.trunc(Number(ttfb / BigInt(1e6))),
|
|
57
|
+
duration: Math.trunc(Number(duration / BigInt(1e6))),
|
|
58
|
+
charactersCount: text.length,
|
|
59
|
+
audioDuration,
|
|
60
|
+
cancelled: false,
|
|
61
|
+
// XXX(nbsp)
|
|
62
|
+
label: this.label,
|
|
63
|
+
streamed: false
|
|
64
|
+
};
|
|
65
|
+
this.#tts.emit(0 /* METRICS_COLLECTED */, metrics);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
for await (const audio of this.queue) {
|
|
69
|
+
this.output.put(audio);
|
|
70
|
+
if (audio === SynthesizeStream.END_OF_STREAM) continue;
|
|
71
|
+
requestId = audio.requestId;
|
|
72
|
+
if (!ttfb) {
|
|
73
|
+
ttfb = process.hrtime.bigint() - startTime;
|
|
74
|
+
}
|
|
75
|
+
audioDuration += audio.frame.samplesPerChannel / audio.frame.sampleRate;
|
|
76
|
+
if (audio.final) {
|
|
77
|
+
emit();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (requestId) {
|
|
81
|
+
emit();
|
|
82
|
+
}
|
|
83
|
+
this.output.close();
|
|
84
|
+
}
|
|
30
85
|
/** Push a string of text to the TTS */
|
|
31
86
|
pushText(text) {
|
|
87
|
+
if (!this.#monitorMetricsTask) {
|
|
88
|
+
this.#monitorMetricsTask = this.monitorMetrics();
|
|
89
|
+
}
|
|
90
|
+
this.#metricsText += text;
|
|
32
91
|
if (this.input.closed) {
|
|
33
92
|
throw new Error("Input is closed");
|
|
34
93
|
}
|
|
@@ -39,6 +98,10 @@ class SynthesizeStream {
|
|
|
39
98
|
}
|
|
40
99
|
/** Flush the TTS, causing it to process all pending text */
|
|
41
100
|
flush() {
|
|
101
|
+
if (this.#metricsText) {
|
|
102
|
+
this.#metricsPendingTexts.push(this.#metricsText);
|
|
103
|
+
this.#metricsText = "";
|
|
104
|
+
}
|
|
42
105
|
if (this.input.closed) {
|
|
43
106
|
throw new Error("Input is closed");
|
|
44
107
|
}
|
|
@@ -58,12 +121,12 @@ class SynthesizeStream {
|
|
|
58
121
|
this.input.close();
|
|
59
122
|
}
|
|
60
123
|
next() {
|
|
61
|
-
return this.
|
|
124
|
+
return this.output.next();
|
|
62
125
|
}
|
|
63
126
|
/** Close both the input and output of the TTS stream */
|
|
64
127
|
close() {
|
|
65
128
|
this.input.close();
|
|
66
|
-
this.
|
|
129
|
+
this.output.close();
|
|
67
130
|
this.closed = true;
|
|
68
131
|
}
|
|
69
132
|
[Symbol.asyncIterator]() {
|
|
@@ -72,7 +135,44 @@ class SynthesizeStream {
|
|
|
72
135
|
}
|
|
73
136
|
class ChunkedStream {
|
|
74
137
|
queue = new AsyncIterableQueue();
|
|
138
|
+
output = new AsyncIterableQueue();
|
|
75
139
|
closed = false;
|
|
140
|
+
#text;
|
|
141
|
+
#tts;
|
|
142
|
+
constructor(text, tts) {
|
|
143
|
+
this.#text = text;
|
|
144
|
+
this.#tts = tts;
|
|
145
|
+
this.monitorMetrics();
|
|
146
|
+
}
|
|
147
|
+
async monitorMetrics() {
|
|
148
|
+
const startTime = process.hrtime.bigint();
|
|
149
|
+
let audioDuration = 0;
|
|
150
|
+
let ttfb;
|
|
151
|
+
let requestId = "";
|
|
152
|
+
for await (const audio of this.queue) {
|
|
153
|
+
this.output.put(audio);
|
|
154
|
+
requestId = audio.requestId;
|
|
155
|
+
if (!ttfb) {
|
|
156
|
+
ttfb = process.hrtime.bigint() - startTime;
|
|
157
|
+
}
|
|
158
|
+
audioDuration += audio.frame.samplesPerChannel / audio.frame.sampleRate;
|
|
159
|
+
}
|
|
160
|
+
this.output.close();
|
|
161
|
+
const duration = process.hrtime.bigint() - startTime;
|
|
162
|
+
const metrics = {
|
|
163
|
+
timestamp: Date.now(),
|
|
164
|
+
requestId,
|
|
165
|
+
ttfb: Math.trunc(Number(ttfb / BigInt(1e6))),
|
|
166
|
+
duration: Math.trunc(Number(duration / BigInt(1e6))),
|
|
167
|
+
charactersCount: this.#text.length,
|
|
168
|
+
audioDuration,
|
|
169
|
+
cancelled: false,
|
|
170
|
+
// XXX(nbsp)
|
|
171
|
+
label: this.label,
|
|
172
|
+
streamed: false
|
|
173
|
+
};
|
|
174
|
+
this.#tts.emit(0 /* METRICS_COLLECTED */, metrics);
|
|
175
|
+
}
|
|
76
176
|
/** Collect every frame into one in a single call */
|
|
77
177
|
async collect() {
|
|
78
178
|
const frames = [];
|
|
@@ -82,11 +182,12 @@ class ChunkedStream {
|
|
|
82
182
|
return mergeFrames(frames);
|
|
83
183
|
}
|
|
84
184
|
next() {
|
|
85
|
-
return this.
|
|
185
|
+
return this.output.next();
|
|
86
186
|
}
|
|
87
187
|
/** Close both the input and output of the TTS stream */
|
|
88
188
|
close() {
|
|
89
189
|
this.queue.close();
|
|
190
|
+
this.output.close();
|
|
90
191
|
this.closed = true;
|
|
91
192
|
}
|
|
92
193
|
[Symbol.asyncIterator]() {
|
|
@@ -96,6 +197,7 @@ class ChunkedStream {
|
|
|
96
197
|
export {
|
|
97
198
|
ChunkedStream,
|
|
98
199
|
SynthesizeStream,
|
|
99
|
-
TTS
|
|
200
|
+
TTS,
|
|
201
|
+
TTSEvent
|
|
100
202
|
};
|
|
101
203
|
//# sourceMappingURL=tts.js.map
|
package/dist/tts/tts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tts/tts.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { AsyncIterableQueue, mergeFrames } from '../utils.js';\n\n/** SynthesizedAudio is a packet of speech synthesis as returned by the TTS. */\nexport interface SynthesizedAudio {\n /** Request ID (one segment could be made up of multiple requests) */\n requestId: string;\n /** Segment ID, each segment is separated by a flush */\n segmentId: string;\n /** Synthesized audio frame */\n frame: AudioFrame;\n /** Current segment of the synthesized audio */\n deltaText?: string;\n}\n\n/**\n * Describes the capabilities of the TTS provider.\n *\n * @remarks\n * At present, only `streaming` is supplied to this interface, and the framework only supports\n * providers that do have a streaming endpoint.\n */\nexport interface TTSCapabilities {\n streaming: boolean;\n}\n\n/**\n * An instance of a text-to-speech adapter.\n *\n * @remarks\n * This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that\n * exports its own child TTS class, which inherits this class's methods.\n */\nexport abstract class TTS {\n #capabilities: TTSCapabilities;\n #sampleRate: number;\n #numChannels: number;\n\n constructor(sampleRate: number, numChannels: number, capabilities: TTSCapabilities) {\n this.#capabilities = capabilities;\n this.#sampleRate = sampleRate;\n this.#numChannels = numChannels;\n }\n\n /** Returns this TTS's capabilities */\n get capabilities(): TTSCapabilities {\n return this.#capabilities;\n }\n\n /** Returns the sample rate of audio frames returned by this TTS */\n get sampleRate(): number {\n return this.#sampleRate;\n }\n\n /** Returns the channel count of audio frames returned by this TTS */\n get numChannels(): number {\n return this.#numChannels;\n }\n\n /**\n * Receives text and returns synthesis in the form of a {@link ChunkedStream}\n */\n abstract synthesize(text: string): ChunkedStream;\n\n /**\n * Returns a {@link SynthesizeStream} that can be used to push text and receive audio data\n */\n abstract stream(): SynthesizeStream;\n}\n\n/**\n * An instance of a text-to-speech stream, as an asynchronous iterable iterator.\n *\n * @example Looping through frames\n * ```ts\n * for await (const event of stream) {\n * await source.captureFrame(event.frame);\n * }\n * ```\n *\n * @remarks\n * This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that\n * exports its own child SynthesizeStream class, which inherits this class's methods.\n */\nexport abstract class SynthesizeStream\n implements AsyncIterableIterator<SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM>\n{\n protected static readonly FLUSH_SENTINEL = Symbol('FLUSH_SENTINEL');\n static readonly END_OF_STREAM = Symbol('END_OF_STREAM');\n protected input = new AsyncIterableQueue<string | typeof SynthesizeStream.FLUSH_SENTINEL>();\n protected queue = new AsyncIterableQueue<\n SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM\n >();\n protected closed = false;\n\n /** Push a string of text to the TTS */\n pushText(text: string) {\n if (this.input.closed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.input.put(text);\n }\n\n /** Flush the TTS, causing it to process all pending text */\n flush() {\n if (this.input.closed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.input.put(SynthesizeStream.FLUSH_SENTINEL);\n }\n\n /** Mark the input as ended and forbid additional pushes */\n endInput() {\n if (this.input.closed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.input.close();\n }\n\n next(): Promise<IteratorResult<SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM>> {\n return this.queue.next();\n }\n\n /** Close both the input and output of the TTS stream */\n close() {\n this.input.close();\n this.queue.close();\n this.closed = true;\n }\n\n [Symbol.asyncIterator](): SynthesizeStream {\n return this;\n }\n}\n\n/**\n * An instance of a text-to-speech response, as an asynchronous iterable iterator.\n *\n * @example Looping through frames\n * ```ts\n * for await (const event of stream) {\n * await source.captureFrame(event.frame);\n * }\n * ```\n *\n * @remarks\n * This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that\n * exports its own child ChunkedStream class, which inherits this class's methods.\n */\nexport abstract class ChunkedStream implements AsyncIterableIterator<SynthesizedAudio> {\n protected queue = new AsyncIterableQueue<SynthesizedAudio>();\n protected closed = false;\n\n /** Collect every frame into one in a single call */\n async collect(): Promise<AudioFrame> {\n const frames = [];\n for await (const event of this) {\n frames.push(event.frame);\n }\n return mergeFrames(frames);\n }\n\n next(): Promise<IteratorResult<SynthesizedAudio>> {\n return this.queue.next();\n }\n\n /** Close both the input and output of the TTS stream */\n close() {\n this.queue.close();\n this.closed = true;\n }\n\n [Symbol.asyncIterator](): ChunkedStream {\n return this;\n }\n}\n"],"mappings":"AAIA,SAAS,oBAAoB,mBAAmB;AAgCzC,MAAe,IAAI;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YAAY,YAAoB,aAAqB,cAA+B;AAClF,SAAK,gBAAgB;AACrB,SAAK,cAAc;AACnB,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA,EAGA,IAAI,eAAgC;AAClC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,aAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,cAAsB;AACxB,WAAO,KAAK;AAAA,EACd;AAWF;AAgBO,MAAe,iBAEtB;AAAA,EACE,OAA0B,iBAAiB,OAAO,gBAAgB;AAAA,EAClE,OAAgB,gBAAgB,OAAO,eAAe;AAAA,EAC5C,QAAQ,IAAI,mBAAoE;AAAA,EAChF,QAAQ,IAAI,mBAEpB;AAAA,EACQ,SAAS;AAAA;AAAA,EAGnB,SAAS,MAAc;AACrB,QAAI,KAAK,MAAM,QAAQ;AACrB,YAAM,IAAI,MAAM,iBAAiB;AAAA,IACnC;AACA,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,MAAM,kBAAkB;AAAA,IACpC;AACA,SAAK,MAAM,IAAI,IAAI;AAAA,EACrB;AAAA;AAAA,EAGA,QAAQ;AACN,QAAI,KAAK,MAAM,QAAQ;AACrB,YAAM,IAAI,MAAM,iBAAiB;AAAA,IACnC;AACA,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,MAAM,kBAAkB;AAAA,IACpC;AACA,SAAK,MAAM,IAAI,iBAAiB,cAAc;AAAA,EAChD;AAAA;AAAA,EAGA,WAAW;AACT,QAAI,KAAK,MAAM,QAAQ;AACrB,YAAM,IAAI,MAAM,iBAAiB;AAAA,IACnC;AACA,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,MAAM,kBAAkB;AAAA,IACpC;AACA,SAAK,MAAM,MAAM;AAAA,EACnB;AAAA,EAEA,OAA0F;AACxF,WAAO,KAAK,MAAM,KAAK;AAAA,EACzB;AAAA;AAAA,EAGA,QAAQ;AACN,SAAK,MAAM,MAAM;AACjB,SAAK,MAAM,MAAM;AACjB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,CAAC,OAAO,aAAa,IAAsB;AACzC,WAAO;AAAA,EACT;AACF;AAgBO,MAAe,cAAiE;AAAA,EAC3E,QAAQ,IAAI,mBAAqC;AAAA,EACjD,SAAS;AAAA;AAAA,EAGnB,MAAM,UAA+B;AACnC,UAAM,SAAS,CAAC;AAChB,qBAAiB,SAAS,MAAM;AAC9B,aAAO,KAAK,MAAM,KAAK;AAAA,IACzB;AACA,WAAO,YAAY,MAAM;AAAA,EAC3B;AAAA,EAEA,OAAkD;AAChD,WAAO,KAAK,MAAM,KAAK;AAAA,EACzB;AAAA;AAAA,EAGA,QAAQ;AACN,SAAK,MAAM,MAAM;AACjB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,CAAC,OAAO,aAAa,IAAmB;AACtC,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/tts/tts.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 { TTSMetrics } from '../metrics/base.js';\nimport { AsyncIterableQueue, mergeFrames } from '../utils.js';\n\n/** SynthesizedAudio is a packet of speech synthesis as returned by the TTS. */\nexport interface SynthesizedAudio {\n /** Request ID (one segment could be made up of multiple requests) */\n requestId: string;\n /** Segment ID, each segment is separated by a flush */\n segmentId: string;\n /** Synthesized audio frame */\n frame: AudioFrame;\n /** Current segment of the synthesized audio */\n deltaText?: string;\n /** Whether this is the last frame of the segment (streaming only) */\n final: boolean;\n}\n\n/**\n * Describes the capabilities of the TTS provider.\n *\n * @remarks\n * At present, only `streaming` is supplied to this interface, and the framework only supports\n * providers that do have a streaming endpoint.\n */\nexport interface TTSCapabilities {\n streaming: boolean;\n}\n\nexport enum TTSEvent {\n METRICS_COLLECTED,\n}\n\nexport type TTSCallbacks = {\n [TTSEvent.METRICS_COLLECTED]: (metrics: TTSMetrics) => void;\n};\n\n/**\n * An instance of a text-to-speech adapter.\n *\n * @remarks\n * This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that\n * exports its own child TTS class, which inherits this class's methods.\n */\nexport abstract class TTS extends (EventEmitter as new () => TypedEmitter<TTSCallbacks>) {\n #capabilities: TTSCapabilities;\n #sampleRate: number;\n #numChannels: number;\n abstract label: string;\n\n constructor(sampleRate: number, numChannels: number, capabilities: TTSCapabilities) {\n super();\n this.#capabilities = capabilities;\n this.#sampleRate = sampleRate;\n this.#numChannels = numChannels;\n }\n\n /** Returns this TTS's capabilities */\n get capabilities(): TTSCapabilities {\n return this.#capabilities;\n }\n\n /** Returns the sample rate of audio frames returned by this TTS */\n get sampleRate(): number {\n return this.#sampleRate;\n }\n\n /** Returns the channel count of audio frames returned by this TTS */\n get numChannels(): number {\n return this.#numChannels;\n }\n\n /**\n * Receives text and returns synthesis in the form of a {@link ChunkedStream}\n */\n abstract synthesize(text: string): ChunkedStream;\n\n /**\n * Returns a {@link SynthesizeStream} that can be used to push text and receive audio data\n */\n abstract stream(): SynthesizeStream;\n}\n\n/**\n * An instance of a text-to-speech stream, as an asynchronous iterable iterator.\n *\n * @example Looping through frames\n * ```ts\n * for await (const event of stream) {\n * await source.captureFrame(event.frame);\n * }\n * ```\n *\n * @remarks\n * This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that\n * exports its own child SynthesizeStream class, which inherits this class's methods.\n */\nexport abstract class SynthesizeStream\n implements AsyncIterableIterator<SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM>\n{\n protected static readonly FLUSH_SENTINEL = Symbol('FLUSH_SENTINEL');\n static readonly END_OF_STREAM = Symbol('END_OF_STREAM');\n protected input = new AsyncIterableQueue<string | typeof SynthesizeStream.FLUSH_SENTINEL>();\n protected queue = new AsyncIterableQueue<\n SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM\n >();\n protected output = new AsyncIterableQueue<\n SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM\n >();\n protected closed = false;\n abstract label: string;\n #tts: TTS;\n #metricsPendingTexts: string[] = [];\n #metricsText = '';\n #monitorMetricsTask?: Promise<void>;\n\n constructor(tts: TTS) {\n this.#tts = tts;\n }\n\n protected async monitorMetrics() {\n const startTime = process.hrtime.bigint();\n let audioDuration = 0;\n let ttfb: bigint | undefined;\n let requestId = '';\n\n const emit = () => {\n if (this.#metricsPendingTexts.length) {\n const text = this.#metricsPendingTexts.shift()!;\n const duration = process.hrtime.bigint() - startTime;\n const metrics: TTSMetrics = {\n timestamp: Date.now(),\n requestId,\n ttfb: Math.trunc(Number(ttfb! / BigInt(1000000))),\n duration: Math.trunc(Number(duration / BigInt(1000000))),\n charactersCount: text.length,\n audioDuration,\n cancelled: false, // XXX(nbsp)\n label: this.label,\n streamed: false,\n };\n this.#tts.emit(TTSEvent.METRICS_COLLECTED, metrics);\n }\n };\n\n for await (const audio of this.queue) {\n this.output.put(audio);\n if (audio === SynthesizeStream.END_OF_STREAM) continue;\n requestId = audio.requestId;\n if (!ttfb) {\n ttfb = process.hrtime.bigint() - startTime;\n }\n audioDuration += audio.frame.samplesPerChannel / audio.frame.sampleRate;\n if (audio.final) {\n emit();\n }\n }\n\n if (requestId) {\n emit();\n }\n this.output.close();\n }\n\n /** Push a string of text to the TTS */\n pushText(text: string) {\n if (!this.#monitorMetricsTask) {\n this.#monitorMetricsTask = this.monitorMetrics();\n }\n this.#metricsText += text;\n\n if (this.input.closed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.input.put(text);\n }\n\n /** Flush the TTS, causing it to process all pending text */\n flush() {\n if (this.#metricsText) {\n this.#metricsPendingTexts.push(this.#metricsText);\n this.#metricsText = '';\n }\n if (this.input.closed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.input.put(SynthesizeStream.FLUSH_SENTINEL);\n }\n\n /** Mark the input as ended and forbid additional pushes */\n endInput() {\n if (this.input.closed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.input.close();\n }\n\n next(): Promise<IteratorResult<SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM>> {\n return this.output.next();\n }\n\n /** Close both the input and output of the TTS stream */\n close() {\n this.input.close();\n this.output.close();\n this.closed = true;\n }\n\n [Symbol.asyncIterator](): SynthesizeStream {\n return this;\n }\n}\n\n/**\n * An instance of a text-to-speech response, as an asynchronous iterable iterator.\n *\n * @example Looping through frames\n * ```ts\n * for await (const event of stream) {\n * await source.captureFrame(event.frame);\n * }\n * ```\n *\n * @remarks\n * This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that\n * exports its own child ChunkedStream class, which inherits this class's methods.\n */\nexport abstract class ChunkedStream implements AsyncIterableIterator<SynthesizedAudio> {\n protected queue = new AsyncIterableQueue<SynthesizedAudio>();\n protected output = new AsyncIterableQueue<SynthesizedAudio>();\n protected closed = false;\n abstract label: string;\n #text: string;\n #tts: TTS;\n\n constructor(text: string, tts: TTS) {\n this.#text = text;\n this.#tts = tts;\n\n this.monitorMetrics();\n }\n\n protected async monitorMetrics() {\n const startTime = process.hrtime.bigint();\n let audioDuration = 0;\n let ttfb: bigint | undefined;\n let requestId = '';\n\n for await (const audio of this.queue) {\n this.output.put(audio);\n requestId = audio.requestId;\n if (!ttfb) {\n ttfb = process.hrtime.bigint() - startTime;\n }\n audioDuration += audio.frame.samplesPerChannel / audio.frame.sampleRate;\n }\n this.output.close();\n\n const duration = process.hrtime.bigint() - startTime;\n const metrics: TTSMetrics = {\n timestamp: Date.now(),\n requestId,\n ttfb: Math.trunc(Number(ttfb! / BigInt(1000000))),\n duration: Math.trunc(Number(duration / BigInt(1000000))),\n charactersCount: this.#text.length,\n audioDuration,\n cancelled: false, // XXX(nbsp)\n label: this.label,\n streamed: false,\n };\n this.#tts.emit(TTSEvent.METRICS_COLLECTED, metrics);\n }\n\n /** Collect every frame into one in a single call */\n async collect(): Promise<AudioFrame> {\n const frames = [];\n for await (const event of this) {\n frames.push(event.frame);\n }\n return mergeFrames(frames);\n }\n\n next(): Promise<IteratorResult<SynthesizedAudio>> {\n return this.output.next();\n }\n\n /** Close both the input and output of the TTS stream */\n close() {\n this.queue.close();\n this.output.close();\n this.closed = true;\n }\n\n [Symbol.asyncIterator](): ChunkedStream {\n return this;\n }\n}\n"],"mappings":"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","names":["TTSEvent"]}
|
package/dist/utils.cjs
CHANGED
|
@@ -85,10 +85,17 @@ class Queue {
|
|
|
85
85
|
this.#limit = limit;
|
|
86
86
|
}
|
|
87
87
|
async get() {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
const _get = async () => {
|
|
89
|
+
if (this.items.length === 0) {
|
|
90
|
+
await (0, import_node_events.once)(this.#events, "put");
|
|
91
|
+
}
|
|
92
|
+
let item2 = this.items.shift();
|
|
93
|
+
if (!item2) {
|
|
94
|
+
item2 = await _get();
|
|
95
|
+
}
|
|
96
|
+
return item2;
|
|
97
|
+
};
|
|
98
|
+
const item = _get();
|
|
92
99
|
this.#events.emit("get");
|
|
93
100
|
return item;
|
|
94
101
|
}
|
package/dist/utils.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type {\n LocalParticipant,\n RemoteParticipant,\n Room,\n TrackPublication,\n} from '@livekit/rtc-node';\nimport { AudioFrame, TrackSource } from '@livekit/rtc-node';\nimport { EventEmitter, once } from 'node:events';\n\n/** Union of a single and a list of {@link AudioFrame}s */\nexport type AudioBuffer = AudioFrame[] | AudioFrame;\n\n/**\n * Merge one or more {@link AudioFrame}s into a single one.\n *\n * @param buffer Either an {@link AudioFrame} or a list thereof\n * @throws\n * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError\n * | TypeError} if sample rate or channel count are mismatched\n */\nexport const mergeFrames = (buffer: AudioBuffer): AudioFrame => {\n if (Array.isArray(buffer)) {\n buffer = buffer as AudioFrame[];\n if (buffer.length == 0) {\n throw new TypeError('buffer is empty');\n }\n\n const sampleRate = buffer[0]!.sampleRate;\n const channels = buffer[0]!.channels;\n let samplesPerChannel = 0;\n let data = new Int16Array();\n\n for (const frame of buffer) {\n if (frame.sampleRate !== sampleRate) {\n throw new TypeError('sample rate mismatch');\n }\n\n if (frame.channels !== channels) {\n throw new TypeError('channel count mismatch');\n }\n\n data = new Int16Array([...data, ...frame.data]);\n samplesPerChannel += frame.samplesPerChannel;\n }\n\n return new AudioFrame(data, sampleRate, channels, samplesPerChannel);\n }\n\n return buffer;\n};\n\nexport const findMicroTrackId = (room: Room, identity: string): string => {\n let p: RemoteParticipant | LocalParticipant | undefined = room.remoteParticipants.get(identity);\n\n if (identity === room.localParticipant?.identity) {\n p = room.localParticipant;\n }\n\n if (!p) {\n throw new Error(`participant ${identity} not found`);\n }\n\n // find first micro track\n let trackId: string | undefined;\n p.trackPublications.forEach((track: TrackPublication) => {\n if (track.source === TrackSource.SOURCE_MICROPHONE) {\n trackId = track.sid;\n return;\n }\n });\n\n if (!trackId) {\n throw new Error(`participant ${identity} does not have a microphone track`);\n }\n\n return trackId;\n};\n\n/** @internal */\nexport class Queue<T> {\n /** @internal */\n items: T[] = [];\n #limit?: number;\n #events = new EventEmitter();\n\n constructor(limit?: number) {\n this.#limit = limit;\n }\n\n async get(): Promise<T> {\n if (this.items.length === 0) {\n await once(this.#events, 'put');\n }\n const item = this.items.shift()!;\n this.#events.emit('get');\n return item;\n }\n\n async put(item: T) {\n if (this.#limit && this.items.length >= this.#limit) {\n await once(this.#events, 'get');\n }\n this.items.push(item);\n this.#events.emit('put');\n }\n}\n\n/** @internal */\nexport class Future {\n #await: Promise<void>;\n #resolvePromise!: () => void;\n #rejectPromise!: (error: Error) => void;\n #done: boolean = false;\n\n constructor() {\n this.#await = new Promise<void>((resolve, reject) => {\n this.#resolvePromise = resolve;\n this.#rejectPromise = reject;\n });\n }\n\n get await() {\n return this.#await;\n }\n\n get done() {\n return this.#done;\n }\n\n resolve() {\n this.#done = true;\n this.#resolvePromise();\n }\n\n reject(error: Error) {\n this.#done = true;\n this.#rejectPromise(error);\n }\n}\n\n/** @internal */\nexport class CancellablePromise<T> {\n #promise: Promise<T>;\n #cancelFn: () => void;\n #isCancelled: boolean = false;\n #error: Error | null = null;\n\n constructor(\n executor: (\n resolve: (value: T | PromiseLike<T>) => void,\n reject: (reason?: any) => void,\n onCancel: (cancelFn: () => void) => void,\n ) => void,\n ) {\n let cancel: () => void;\n\n this.#promise = new Promise<T>((resolve, reject) => {\n executor(\n resolve,\n (reason) => {\n this.#error = reason instanceof Error ? reason : new Error(String(reason));\n reject(reason);\n },\n (cancelFn) => {\n cancel = () => {\n this.#isCancelled = true;\n cancelFn();\n };\n },\n );\n });\n\n this.#cancelFn = cancel!;\n }\n\n get isCancelled(): boolean {\n return this.#isCancelled;\n }\n\n get error(): Error | null {\n return this.#error;\n }\n\n then<TResult1 = T, TResult2 = never>(\n onfulfilled?: ((value: T) => TResult1 | Promise<TResult1>) | null,\n onrejected?: ((reason: any) => TResult2 | Promise<TResult2>) | null,\n ): Promise<TResult1 | TResult2> {\n return this.#promise.then(onfulfilled, onrejected);\n }\n\n catch<TResult = never>(\n onrejected?: ((reason: any) => TResult | Promise<TResult>) | null,\n ): Promise<T | TResult> {\n return this.#promise.catch(onrejected);\n }\n\n finally(onfinally?: (() => void) | null): Promise<T> {\n return this.#promise.finally(onfinally);\n }\n\n cancel(): void {\n this.#cancelFn();\n }\n\n static from<T>(promise: Promise<T>): CancellablePromise<T> {\n return new CancellablePromise<T>((resolve, reject) => {\n promise.then(resolve).catch(reject);\n });\n }\n}\n\n/** @internal */\nexport async function gracefullyCancel<T>(promise: CancellablePromise<T>): Promise<void> {\n if (!promise.isCancelled) {\n promise.cancel();\n }\n try {\n await promise;\n } catch (error) {\n // Ignore the error, as it's expected due to cancellation\n }\n}\n\n/** @internal */\nexport class AsyncIterableQueue<T> implements AsyncIterableIterator<T> {\n private static readonly CLOSE_SENTINEL = Symbol('CLOSE_SENTINEL');\n #queue = new Queue<T | typeof AsyncIterableQueue.CLOSE_SENTINEL>();\n #closed = false;\n\n get closed(): boolean {\n return this.#closed;\n }\n\n put(item: T): void {\n if (this.#closed) {\n throw new Error('Queue is closed');\n }\n this.#queue.put(item);\n }\n\n close(): void {\n this.#closed = true;\n this.#queue.put(AsyncIterableQueue.CLOSE_SENTINEL);\n }\n\n async next(): Promise<IteratorResult<T>> {\n if (this.#closed && this.#queue.items.length === 0) {\n return { value: undefined, done: true };\n }\n const item = await this.#queue.get();\n if (item === AsyncIterableQueue.CLOSE_SENTINEL && this.#closed) {\n return { value: undefined, done: true };\n }\n return { value: item as T, done: false };\n }\n\n [Symbol.asyncIterator](): AsyncIterableQueue<T> {\n return this;\n }\n}\n\n/** @internal */\nexport class ExpFilter {\n #alpha: number;\n #max?: number;\n #filtered?: number = undefined;\n\n constructor(alpha: number, max?: number) {\n this.#alpha = alpha;\n this.#max = max;\n }\n\n reset(alpha?: number) {\n if (alpha) {\n this.#alpha = alpha;\n }\n this.#filtered = undefined;\n }\n\n apply(exp: number, sample: number): number {\n if (this.#filtered) {\n const a = this.#alpha ** exp;\n this.#filtered = a * this.#filtered + (1 - a) * sample;\n } else {\n this.#filtered = sample;\n }\n\n if (this.#max && this.#filtered > this.#max) {\n this.#filtered = this.#max;\n }\n\n return this.#filtered;\n }\n\n get filtered(): number | undefined {\n return this.#filtered;\n }\n\n set alpha(alpha: number) {\n this.#alpha = alpha;\n }\n}\n\n/** @internal */\nexport class AudioEnergyFilter {\n #cooldownSeconds: number;\n #cooldown: number;\n\n constructor(cooldownSeconds = 1) {\n this.#cooldownSeconds = cooldownSeconds;\n this.#cooldown = cooldownSeconds;\n }\n\n pushFrame(frame: AudioFrame): boolean {\n const arr = Float32Array.from(frame.data, (x) => x / 32768);\n const rms = (arr.map((x) => x ** 2).reduce((acc, x) => acc + x) / arr.length) ** 0.5;\n if (rms > 0.004) {\n this.#cooldown = this.#cooldownSeconds;\n return true;\n }\n\n const durationSeconds = frame.samplesPerChannel / frame.sampleRate;\n this.#cooldown -= durationSeconds;\n if (this.#cooldown > 0) {\n return true;\n }\n\n return false;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/utils.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type {\n LocalParticipant,\n RemoteParticipant,\n Room,\n TrackPublication,\n} from '@livekit/rtc-node';\nimport { AudioFrame, TrackSource } from '@livekit/rtc-node';\nimport { EventEmitter, once } from 'node:events';\n\n/** Union of a single and a list of {@link AudioFrame}s */\nexport type AudioBuffer = AudioFrame[] | AudioFrame;\n\n/**\n * Merge one or more {@link AudioFrame}s into a single one.\n *\n * @param buffer Either an {@link AudioFrame} or a list thereof\n * @throws\n * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError\n * | TypeError} if sample rate or channel count are mismatched\n */\nexport const mergeFrames = (buffer: AudioBuffer): AudioFrame => {\n if (Array.isArray(buffer)) {\n buffer = buffer as AudioFrame[];\n if (buffer.length == 0) {\n throw new TypeError('buffer is empty');\n }\n\n const sampleRate = buffer[0]!.sampleRate;\n const channels = buffer[0]!.channels;\n let samplesPerChannel = 0;\n let data = new Int16Array();\n\n for (const frame of buffer) {\n if (frame.sampleRate !== sampleRate) {\n throw new TypeError('sample rate mismatch');\n }\n\n if (frame.channels !== channels) {\n throw new TypeError('channel count mismatch');\n }\n\n data = new Int16Array([...data, ...frame.data]);\n samplesPerChannel += frame.samplesPerChannel;\n }\n\n return new AudioFrame(data, sampleRate, channels, samplesPerChannel);\n }\n\n return buffer;\n};\n\nexport const findMicroTrackId = (room: Room, identity: string): string => {\n let p: RemoteParticipant | LocalParticipant | undefined = room.remoteParticipants.get(identity);\n\n if (identity === room.localParticipant?.identity) {\n p = room.localParticipant;\n }\n\n if (!p) {\n throw new Error(`participant ${identity} not found`);\n }\n\n // find first micro track\n let trackId: string | undefined;\n p.trackPublications.forEach((track: TrackPublication) => {\n if (track.source === TrackSource.SOURCE_MICROPHONE) {\n trackId = track.sid;\n return;\n }\n });\n\n if (!trackId) {\n throw new Error(`participant ${identity} does not have a microphone track`);\n }\n\n return trackId;\n};\n\n/** @internal */\nexport class Queue<T> {\n /** @internal */\n items: T[] = [];\n #limit?: number;\n #events = new EventEmitter();\n\n constructor(limit?: number) {\n this.#limit = limit;\n }\n\n async get(): Promise<T> {\n const _get = async (): Promise<T> => {\n if (this.items.length === 0) {\n await once(this.#events, 'put');\n }\n let item = this.items.shift();\n if (!item) {\n item = await _get();\n }\n return item;\n };\n\n const item = _get();\n this.#events.emit('get');\n return item;\n }\n\n async put(item: T) {\n if (this.#limit && this.items.length >= this.#limit) {\n await once(this.#events, 'get');\n }\n this.items.push(item);\n this.#events.emit('put');\n }\n}\n\n/** @internal */\nexport class Future {\n #await: Promise<void>;\n #resolvePromise!: () => void;\n #rejectPromise!: (error: Error) => void;\n #done: boolean = false;\n\n constructor() {\n this.#await = new Promise<void>((resolve, reject) => {\n this.#resolvePromise = resolve;\n this.#rejectPromise = reject;\n });\n }\n\n get await() {\n return this.#await;\n }\n\n get done() {\n return this.#done;\n }\n\n resolve() {\n this.#done = true;\n this.#resolvePromise();\n }\n\n reject(error: Error) {\n this.#done = true;\n this.#rejectPromise(error);\n }\n}\n\n/** @internal */\nexport class CancellablePromise<T> {\n #promise: Promise<T>;\n #cancelFn: () => void;\n #isCancelled: boolean = false;\n #error: Error | null = null;\n\n constructor(\n executor: (\n resolve: (value: T | PromiseLike<T>) => void,\n reject: (reason?: any) => void,\n onCancel: (cancelFn: () => void) => void,\n ) => void,\n ) {\n let cancel: () => void;\n\n this.#promise = new Promise<T>((resolve, reject) => {\n executor(\n resolve,\n (reason) => {\n this.#error = reason instanceof Error ? reason : new Error(String(reason));\n reject(reason);\n },\n (cancelFn) => {\n cancel = () => {\n this.#isCancelled = true;\n cancelFn();\n };\n },\n );\n });\n\n this.#cancelFn = cancel!;\n }\n\n get isCancelled(): boolean {\n return this.#isCancelled;\n }\n\n get error(): Error | null {\n return this.#error;\n }\n\n then<TResult1 = T, TResult2 = never>(\n onfulfilled?: ((value: T) => TResult1 | Promise<TResult1>) | null,\n onrejected?: ((reason: any) => TResult2 | Promise<TResult2>) | null,\n ): Promise<TResult1 | TResult2> {\n return this.#promise.then(onfulfilled, onrejected);\n }\n\n catch<TResult = never>(\n onrejected?: ((reason: any) => TResult | Promise<TResult>) | null,\n ): Promise<T | TResult> {\n return this.#promise.catch(onrejected);\n }\n\n finally(onfinally?: (() => void) | null): Promise<T> {\n return this.#promise.finally(onfinally);\n }\n\n cancel(): void {\n this.#cancelFn();\n }\n\n static from<T>(promise: Promise<T>): CancellablePromise<T> {\n return new CancellablePromise<T>((resolve, reject) => {\n promise.then(resolve).catch(reject);\n });\n }\n}\n\n/** @internal */\nexport async function gracefullyCancel<T>(promise: CancellablePromise<T>): Promise<void> {\n if (!promise.isCancelled) {\n promise.cancel();\n }\n try {\n await promise;\n } catch (error) {\n // Ignore the error, as it's expected due to cancellation\n }\n}\n\n/** @internal */\nexport class AsyncIterableQueue<T> implements AsyncIterableIterator<T> {\n private static readonly CLOSE_SENTINEL = Symbol('CLOSE_SENTINEL');\n #queue = new Queue<T | typeof AsyncIterableQueue.CLOSE_SENTINEL>();\n #closed = false;\n\n get closed(): boolean {\n return this.#closed;\n }\n\n put(item: T): void {\n if (this.#closed) {\n throw new Error('Queue is closed');\n }\n this.#queue.put(item);\n }\n\n close(): void {\n this.#closed = true;\n this.#queue.put(AsyncIterableQueue.CLOSE_SENTINEL);\n }\n\n async next(): Promise<IteratorResult<T>> {\n if (this.#closed && this.#queue.items.length === 0) {\n return { value: undefined, done: true };\n }\n const item = await this.#queue.get();\n if (item === AsyncIterableQueue.CLOSE_SENTINEL && this.#closed) {\n return { value: undefined, done: true };\n }\n return { value: item as T, done: false };\n }\n\n [Symbol.asyncIterator](): AsyncIterableQueue<T> {\n return this;\n }\n}\n\n/** @internal */\nexport class ExpFilter {\n #alpha: number;\n #max?: number;\n #filtered?: number = undefined;\n\n constructor(alpha: number, max?: number) {\n this.#alpha = alpha;\n this.#max = max;\n }\n\n reset(alpha?: number) {\n if (alpha) {\n this.#alpha = alpha;\n }\n this.#filtered = undefined;\n }\n\n apply(exp: number, sample: number): number {\n if (this.#filtered) {\n const a = this.#alpha ** exp;\n this.#filtered = a * this.#filtered + (1 - a) * sample;\n } else {\n this.#filtered = sample;\n }\n\n if (this.#max && this.#filtered > this.#max) {\n this.#filtered = this.#max;\n }\n\n return this.#filtered;\n }\n\n get filtered(): number | undefined {\n return this.#filtered;\n }\n\n set alpha(alpha: number) {\n this.#alpha = alpha;\n }\n}\n\n/** @internal */\nexport class AudioEnergyFilter {\n #cooldownSeconds: number;\n #cooldown: number;\n\n constructor(cooldownSeconds = 1) {\n this.#cooldownSeconds = cooldownSeconds;\n this.#cooldown = cooldownSeconds;\n }\n\n pushFrame(frame: AudioFrame): boolean {\n const arr = Float32Array.from(frame.data, (x) => x / 32768);\n const rms = (arr.map((x) => x ** 2).reduce((acc, x) => acc + x) / arr.length) ** 0.5;\n if (rms > 0.004) {\n this.#cooldown = this.#cooldownSeconds;\n return true;\n }\n\n const durationSeconds = frame.samplesPerChannel / frame.sampleRate;\n this.#cooldown -= durationSeconds;\n if (this.#cooldown > 0) {\n return true;\n }\n\n return false;\n }\n}\n"],"mappings":"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","names":["item"]}
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAGV,IAAI,EAEL,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAe,MAAM,mBAAmB,CAAC;AAG5D,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GAAG,UAAU,EAAE,GAAG,UAAU,CAAC;AAEpD;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,WAAY,WAAW,KAAG,UA6BjD,CAAC;AAEF,eAAO,MAAM,gBAAgB,SAAU,IAAI,YAAY,MAAM,KAAG,MAyB/D,CAAC;AAEF,gBAAgB;AAChB,qBAAa,KAAK,CAAC,CAAC;;IAClB,gBAAgB;IAChB,KAAK,EAAE,CAAC,EAAE,CAAM;gBAIJ,KAAK,CAAC,EAAE,MAAM;IAIpB,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
package/dist/utils.js
CHANGED
|
@@ -54,10 +54,17 @@ class Queue {
|
|
|
54
54
|
this.#limit = limit;
|
|
55
55
|
}
|
|
56
56
|
async get() {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
const _get = async () => {
|
|
58
|
+
if (this.items.length === 0) {
|
|
59
|
+
await once(this.#events, "put");
|
|
60
|
+
}
|
|
61
|
+
let item2 = this.items.shift();
|
|
62
|
+
if (!item2) {
|
|
63
|
+
item2 = await _get();
|
|
64
|
+
}
|
|
65
|
+
return item2;
|
|
66
|
+
};
|
|
67
|
+
const item = _get();
|
|
61
68
|
this.#events.emit("get");
|
|
62
69
|
return item;
|
|
63
70
|
}
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type {\n LocalParticipant,\n RemoteParticipant,\n Room,\n TrackPublication,\n} from '@livekit/rtc-node';\nimport { AudioFrame, TrackSource } from '@livekit/rtc-node';\nimport { EventEmitter, once } from 'node:events';\n\n/** Union of a single and a list of {@link AudioFrame}s */\nexport type AudioBuffer = AudioFrame[] | AudioFrame;\n\n/**\n * Merge one or more {@link AudioFrame}s into a single one.\n *\n * @param buffer Either an {@link AudioFrame} or a list thereof\n * @throws\n * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError\n * | TypeError} if sample rate or channel count are mismatched\n */\nexport const mergeFrames = (buffer: AudioBuffer): AudioFrame => {\n if (Array.isArray(buffer)) {\n buffer = buffer as AudioFrame[];\n if (buffer.length == 0) {\n throw new TypeError('buffer is empty');\n }\n\n const sampleRate = buffer[0]!.sampleRate;\n const channels = buffer[0]!.channels;\n let samplesPerChannel = 0;\n let data = new Int16Array();\n\n for (const frame of buffer) {\n if (frame.sampleRate !== sampleRate) {\n throw new TypeError('sample rate mismatch');\n }\n\n if (frame.channels !== channels) {\n throw new TypeError('channel count mismatch');\n }\n\n data = new Int16Array([...data, ...frame.data]);\n samplesPerChannel += frame.samplesPerChannel;\n }\n\n return new AudioFrame(data, sampleRate, channels, samplesPerChannel);\n }\n\n return buffer;\n};\n\nexport const findMicroTrackId = (room: Room, identity: string): string => {\n let p: RemoteParticipant | LocalParticipant | undefined = room.remoteParticipants.get(identity);\n\n if (identity === room.localParticipant?.identity) {\n p = room.localParticipant;\n }\n\n if (!p) {\n throw new Error(`participant ${identity} not found`);\n }\n\n // find first micro track\n let trackId: string | undefined;\n p.trackPublications.forEach((track: TrackPublication) => {\n if (track.source === TrackSource.SOURCE_MICROPHONE) {\n trackId = track.sid;\n return;\n }\n });\n\n if (!trackId) {\n throw new Error(`participant ${identity} does not have a microphone track`);\n }\n\n return trackId;\n};\n\n/** @internal */\nexport class Queue<T> {\n /** @internal */\n items: T[] = [];\n #limit?: number;\n #events = new EventEmitter();\n\n constructor(limit?: number) {\n this.#limit = limit;\n }\n\n async get(): Promise<T> {\n if (this.items.length === 0) {\n await once(this.#events, 'put');\n }\n const item = this.items.shift()!;\n this.#events.emit('get');\n return item;\n }\n\n async put(item: T) {\n if (this.#limit && this.items.length >= this.#limit) {\n await once(this.#events, 'get');\n }\n this.items.push(item);\n this.#events.emit('put');\n }\n}\n\n/** @internal */\nexport class Future {\n #await: Promise<void>;\n #resolvePromise!: () => void;\n #rejectPromise!: (error: Error) => void;\n #done: boolean = false;\n\n constructor() {\n this.#await = new Promise<void>((resolve, reject) => {\n this.#resolvePromise = resolve;\n this.#rejectPromise = reject;\n });\n }\n\n get await() {\n return this.#await;\n }\n\n get done() {\n return this.#done;\n }\n\n resolve() {\n this.#done = true;\n this.#resolvePromise();\n }\n\n reject(error: Error) {\n this.#done = true;\n this.#rejectPromise(error);\n }\n}\n\n/** @internal */\nexport class CancellablePromise<T> {\n #promise: Promise<T>;\n #cancelFn: () => void;\n #isCancelled: boolean = false;\n #error: Error | null = null;\n\n constructor(\n executor: (\n resolve: (value: T | PromiseLike<T>) => void,\n reject: (reason?: any) => void,\n onCancel: (cancelFn: () => void) => void,\n ) => void,\n ) {\n let cancel: () => void;\n\n this.#promise = new Promise<T>((resolve, reject) => {\n executor(\n resolve,\n (reason) => {\n this.#error = reason instanceof Error ? reason : new Error(String(reason));\n reject(reason);\n },\n (cancelFn) => {\n cancel = () => {\n this.#isCancelled = true;\n cancelFn();\n };\n },\n );\n });\n\n this.#cancelFn = cancel!;\n }\n\n get isCancelled(): boolean {\n return this.#isCancelled;\n }\n\n get error(): Error | null {\n return this.#error;\n }\n\n then<TResult1 = T, TResult2 = never>(\n onfulfilled?: ((value: T) => TResult1 | Promise<TResult1>) | null,\n onrejected?: ((reason: any) => TResult2 | Promise<TResult2>) | null,\n ): Promise<TResult1 | TResult2> {\n return this.#promise.then(onfulfilled, onrejected);\n }\n\n catch<TResult = never>(\n onrejected?: ((reason: any) => TResult | Promise<TResult>) | null,\n ): Promise<T | TResult> {\n return this.#promise.catch(onrejected);\n }\n\n finally(onfinally?: (() => void) | null): Promise<T> {\n return this.#promise.finally(onfinally);\n }\n\n cancel(): void {\n this.#cancelFn();\n }\n\n static from<T>(promise: Promise<T>): CancellablePromise<T> {\n return new CancellablePromise<T>((resolve, reject) => {\n promise.then(resolve).catch(reject);\n });\n }\n}\n\n/** @internal */\nexport async function gracefullyCancel<T>(promise: CancellablePromise<T>): Promise<void> {\n if (!promise.isCancelled) {\n promise.cancel();\n }\n try {\n await promise;\n } catch (error) {\n // Ignore the error, as it's expected due to cancellation\n }\n}\n\n/** @internal */\nexport class AsyncIterableQueue<T> implements AsyncIterableIterator<T> {\n private static readonly CLOSE_SENTINEL = Symbol('CLOSE_SENTINEL');\n #queue = new Queue<T | typeof AsyncIterableQueue.CLOSE_SENTINEL>();\n #closed = false;\n\n get closed(): boolean {\n return this.#closed;\n }\n\n put(item: T): void {\n if (this.#closed) {\n throw new Error('Queue is closed');\n }\n this.#queue.put(item);\n }\n\n close(): void {\n this.#closed = true;\n this.#queue.put(AsyncIterableQueue.CLOSE_SENTINEL);\n }\n\n async next(): Promise<IteratorResult<T>> {\n if (this.#closed && this.#queue.items.length === 0) {\n return { value: undefined, done: true };\n }\n const item = await this.#queue.get();\n if (item === AsyncIterableQueue.CLOSE_SENTINEL && this.#closed) {\n return { value: undefined, done: true };\n }\n return { value: item as T, done: false };\n }\n\n [Symbol.asyncIterator](): AsyncIterableQueue<T> {\n return this;\n }\n}\n\n/** @internal */\nexport class ExpFilter {\n #alpha: number;\n #max?: number;\n #filtered?: number = undefined;\n\n constructor(alpha: number, max?: number) {\n this.#alpha = alpha;\n this.#max = max;\n }\n\n reset(alpha?: number) {\n if (alpha) {\n this.#alpha = alpha;\n }\n this.#filtered = undefined;\n }\n\n apply(exp: number, sample: number): number {\n if (this.#filtered) {\n const a = this.#alpha ** exp;\n this.#filtered = a * this.#filtered + (1 - a) * sample;\n } else {\n this.#filtered = sample;\n }\n\n if (this.#max && this.#filtered > this.#max) {\n this.#filtered = this.#max;\n }\n\n return this.#filtered;\n }\n\n get filtered(): number | undefined {\n return this.#filtered;\n }\n\n set alpha(alpha: number) {\n this.#alpha = alpha;\n }\n}\n\n/** @internal */\nexport class AudioEnergyFilter {\n #cooldownSeconds: number;\n #cooldown: number;\n\n constructor(cooldownSeconds = 1) {\n this.#cooldownSeconds = cooldownSeconds;\n this.#cooldown = cooldownSeconds;\n }\n\n pushFrame(frame: AudioFrame): boolean {\n const arr = Float32Array.from(frame.data, (x) => x / 32768);\n const rms = (arr.map((x) => x ** 2).reduce((acc, x) => acc + x) / arr.length) ** 0.5;\n if (rms > 0.004) {\n this.#cooldown = this.#cooldownSeconds;\n return true;\n }\n\n const durationSeconds = frame.samplesPerChannel / frame.sampleRate;\n this.#cooldown -= durationSeconds;\n if (this.#cooldown > 0) {\n return true;\n }\n\n return false;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/utils.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type {\n LocalParticipant,\n RemoteParticipant,\n Room,\n TrackPublication,\n} from '@livekit/rtc-node';\nimport { AudioFrame, TrackSource } from '@livekit/rtc-node';\nimport { EventEmitter, once } from 'node:events';\n\n/** Union of a single and a list of {@link AudioFrame}s */\nexport type AudioBuffer = AudioFrame[] | AudioFrame;\n\n/**\n * Merge one or more {@link AudioFrame}s into a single one.\n *\n * @param buffer Either an {@link AudioFrame} or a list thereof\n * @throws\n * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError\n * | TypeError} if sample rate or channel count are mismatched\n */\nexport const mergeFrames = (buffer: AudioBuffer): AudioFrame => {\n if (Array.isArray(buffer)) {\n buffer = buffer as AudioFrame[];\n if (buffer.length == 0) {\n throw new TypeError('buffer is empty');\n }\n\n const sampleRate = buffer[0]!.sampleRate;\n const channels = buffer[0]!.channels;\n let samplesPerChannel = 0;\n let data = new Int16Array();\n\n for (const frame of buffer) {\n if (frame.sampleRate !== sampleRate) {\n throw new TypeError('sample rate mismatch');\n }\n\n if (frame.channels !== channels) {\n throw new TypeError('channel count mismatch');\n }\n\n data = new Int16Array([...data, ...frame.data]);\n samplesPerChannel += frame.samplesPerChannel;\n }\n\n return new AudioFrame(data, sampleRate, channels, samplesPerChannel);\n }\n\n return buffer;\n};\n\nexport const findMicroTrackId = (room: Room, identity: string): string => {\n let p: RemoteParticipant | LocalParticipant | undefined = room.remoteParticipants.get(identity);\n\n if (identity === room.localParticipant?.identity) {\n p = room.localParticipant;\n }\n\n if (!p) {\n throw new Error(`participant ${identity} not found`);\n }\n\n // find first micro track\n let trackId: string | undefined;\n p.trackPublications.forEach((track: TrackPublication) => {\n if (track.source === TrackSource.SOURCE_MICROPHONE) {\n trackId = track.sid;\n return;\n }\n });\n\n if (!trackId) {\n throw new Error(`participant ${identity} does not have a microphone track`);\n }\n\n return trackId;\n};\n\n/** @internal */\nexport class Queue<T> {\n /** @internal */\n items: T[] = [];\n #limit?: number;\n #events = new EventEmitter();\n\n constructor(limit?: number) {\n this.#limit = limit;\n }\n\n async get(): Promise<T> {\n const _get = async (): Promise<T> => {\n if (this.items.length === 0) {\n await once(this.#events, 'put');\n }\n let item = this.items.shift();\n if (!item) {\n item = await _get();\n }\n return item;\n };\n\n const item = _get();\n this.#events.emit('get');\n return item;\n }\n\n async put(item: T) {\n if (this.#limit && this.items.length >= this.#limit) {\n await once(this.#events, 'get');\n }\n this.items.push(item);\n this.#events.emit('put');\n }\n}\n\n/** @internal */\nexport class Future {\n #await: Promise<void>;\n #resolvePromise!: () => void;\n #rejectPromise!: (error: Error) => void;\n #done: boolean = false;\n\n constructor() {\n this.#await = new Promise<void>((resolve, reject) => {\n this.#resolvePromise = resolve;\n this.#rejectPromise = reject;\n });\n }\n\n get await() {\n return this.#await;\n }\n\n get done() {\n return this.#done;\n }\n\n resolve() {\n this.#done = true;\n this.#resolvePromise();\n }\n\n reject(error: Error) {\n this.#done = true;\n this.#rejectPromise(error);\n }\n}\n\n/** @internal */\nexport class CancellablePromise<T> {\n #promise: Promise<T>;\n #cancelFn: () => void;\n #isCancelled: boolean = false;\n #error: Error | null = null;\n\n constructor(\n executor: (\n resolve: (value: T | PromiseLike<T>) => void,\n reject: (reason?: any) => void,\n onCancel: (cancelFn: () => void) => void,\n ) => void,\n ) {\n let cancel: () => void;\n\n this.#promise = new Promise<T>((resolve, reject) => {\n executor(\n resolve,\n (reason) => {\n this.#error = reason instanceof Error ? reason : new Error(String(reason));\n reject(reason);\n },\n (cancelFn) => {\n cancel = () => {\n this.#isCancelled = true;\n cancelFn();\n };\n },\n );\n });\n\n this.#cancelFn = cancel!;\n }\n\n get isCancelled(): boolean {\n return this.#isCancelled;\n }\n\n get error(): Error | null {\n return this.#error;\n }\n\n then<TResult1 = T, TResult2 = never>(\n onfulfilled?: ((value: T) => TResult1 | Promise<TResult1>) | null,\n onrejected?: ((reason: any) => TResult2 | Promise<TResult2>) | null,\n ): Promise<TResult1 | TResult2> {\n return this.#promise.then(onfulfilled, onrejected);\n }\n\n catch<TResult = never>(\n onrejected?: ((reason: any) => TResult | Promise<TResult>) | null,\n ): Promise<T | TResult> {\n return this.#promise.catch(onrejected);\n }\n\n finally(onfinally?: (() => void) | null): Promise<T> {\n return this.#promise.finally(onfinally);\n }\n\n cancel(): void {\n this.#cancelFn();\n }\n\n static from<T>(promise: Promise<T>): CancellablePromise<T> {\n return new CancellablePromise<T>((resolve, reject) => {\n promise.then(resolve).catch(reject);\n });\n }\n}\n\n/** @internal */\nexport async function gracefullyCancel<T>(promise: CancellablePromise<T>): Promise<void> {\n if (!promise.isCancelled) {\n promise.cancel();\n }\n try {\n await promise;\n } catch (error) {\n // Ignore the error, as it's expected due to cancellation\n }\n}\n\n/** @internal */\nexport class AsyncIterableQueue<T> implements AsyncIterableIterator<T> {\n private static readonly CLOSE_SENTINEL = Symbol('CLOSE_SENTINEL');\n #queue = new Queue<T | typeof AsyncIterableQueue.CLOSE_SENTINEL>();\n #closed = false;\n\n get closed(): boolean {\n return this.#closed;\n }\n\n put(item: T): void {\n if (this.#closed) {\n throw new Error('Queue is closed');\n }\n this.#queue.put(item);\n }\n\n close(): void {\n this.#closed = true;\n this.#queue.put(AsyncIterableQueue.CLOSE_SENTINEL);\n }\n\n async next(): Promise<IteratorResult<T>> {\n if (this.#closed && this.#queue.items.length === 0) {\n return { value: undefined, done: true };\n }\n const item = await this.#queue.get();\n if (item === AsyncIterableQueue.CLOSE_SENTINEL && this.#closed) {\n return { value: undefined, done: true };\n }\n return { value: item as T, done: false };\n }\n\n [Symbol.asyncIterator](): AsyncIterableQueue<T> {\n return this;\n }\n}\n\n/** @internal */\nexport class ExpFilter {\n #alpha: number;\n #max?: number;\n #filtered?: number = undefined;\n\n constructor(alpha: number, max?: number) {\n this.#alpha = alpha;\n this.#max = max;\n }\n\n reset(alpha?: number) {\n if (alpha) {\n this.#alpha = alpha;\n }\n this.#filtered = undefined;\n }\n\n apply(exp: number, sample: number): number {\n if (this.#filtered) {\n const a = this.#alpha ** exp;\n this.#filtered = a * this.#filtered + (1 - a) * sample;\n } else {\n this.#filtered = sample;\n }\n\n if (this.#max && this.#filtered > this.#max) {\n this.#filtered = this.#max;\n }\n\n return this.#filtered;\n }\n\n get filtered(): number | undefined {\n return this.#filtered;\n }\n\n set alpha(alpha: number) {\n this.#alpha = alpha;\n }\n}\n\n/** @internal */\nexport class AudioEnergyFilter {\n #cooldownSeconds: number;\n #cooldown: number;\n\n constructor(cooldownSeconds = 1) {\n this.#cooldownSeconds = cooldownSeconds;\n this.#cooldown = cooldownSeconds;\n }\n\n pushFrame(frame: AudioFrame): boolean {\n const arr = Float32Array.from(frame.data, (x) => x / 32768);\n const rms = (arr.map((x) => x ** 2).reduce((acc, x) => acc + x) / arr.length) ** 0.5;\n if (rms > 0.004) {\n this.#cooldown = this.#cooldownSeconds;\n return true;\n }\n\n const durationSeconds = frame.samplesPerChannel / frame.sampleRate;\n this.#cooldown -= durationSeconds;\n if (this.#cooldown > 0) {\n return true;\n }\n\n return false;\n }\n}\n"],"mappings":"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","names":["item"]}
|
package/dist/vad.cjs
CHANGED
|
@@ -23,16 +23,19 @@ __export(vad_exports, {
|
|
|
23
23
|
VADStream: () => VADStream
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(vad_exports);
|
|
26
|
+
var import_node_events = require("node:events");
|
|
26
27
|
var import_utils = require("./utils.cjs");
|
|
27
28
|
var VADEventType = /* @__PURE__ */ ((VADEventType2) => {
|
|
28
29
|
VADEventType2[VADEventType2["START_OF_SPEECH"] = 0] = "START_OF_SPEECH";
|
|
29
30
|
VADEventType2[VADEventType2["INFERENCE_DONE"] = 1] = "INFERENCE_DONE";
|
|
30
31
|
VADEventType2[VADEventType2["END_OF_SPEECH"] = 2] = "END_OF_SPEECH";
|
|
32
|
+
VADEventType2[VADEventType2["METRICS_COLLECTED"] = 3] = "METRICS_COLLECTED";
|
|
31
33
|
return VADEventType2;
|
|
32
34
|
})(VADEventType || {});
|
|
33
|
-
class VAD {
|
|
35
|
+
class VAD extends import_node_events.EventEmitter {
|
|
34
36
|
#capabilities;
|
|
35
37
|
constructor(capabilities) {
|
|
38
|
+
super();
|
|
36
39
|
this.#capabilities = capabilities;
|
|
37
40
|
}
|
|
38
41
|
get capabilities() {
|
|
@@ -43,7 +46,44 @@ class VADStream {
|
|
|
43
46
|
static FLUSH_SENTINEL = Symbol("FLUSH_SENTINEL");
|
|
44
47
|
input = new import_utils.AsyncIterableQueue();
|
|
45
48
|
queue = new import_utils.AsyncIterableQueue();
|
|
49
|
+
output = new import_utils.AsyncIterableQueue();
|
|
46
50
|
closed = false;
|
|
51
|
+
#vad;
|
|
52
|
+
#lastActivityTime = BigInt(0);
|
|
53
|
+
constructor(vad) {
|
|
54
|
+
this.#vad = vad;
|
|
55
|
+
this.monitorMetrics();
|
|
56
|
+
}
|
|
57
|
+
async monitorMetrics() {
|
|
58
|
+
let inferenceDurationTotal = 0;
|
|
59
|
+
let inferenceCount = 0;
|
|
60
|
+
for await (const event of this.queue) {
|
|
61
|
+
this.output.put(event);
|
|
62
|
+
switch (event.type) {
|
|
63
|
+
case 0 /* START_OF_SPEECH */:
|
|
64
|
+
inferenceCount++;
|
|
65
|
+
if (inferenceCount >= 1 / this.#vad.capabilities.updateInterval) {
|
|
66
|
+
this.#vad.emit(3 /* METRICS_COLLECTED */, {
|
|
67
|
+
timestamp: Date.now(),
|
|
68
|
+
idleTime: Math.trunc(
|
|
69
|
+
Number((process.hrtime.bigint() - this.#lastActivityTime) / BigInt(1e6))
|
|
70
|
+
),
|
|
71
|
+
inferenceDurationTotal,
|
|
72
|
+
inferenceCount,
|
|
73
|
+
label: this.#vad.label
|
|
74
|
+
});
|
|
75
|
+
inferenceCount = 0;
|
|
76
|
+
inferenceDurationTotal = 0;
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
case 1 /* INFERENCE_DONE */:
|
|
80
|
+
case 2 /* END_OF_SPEECH */:
|
|
81
|
+
this.#lastActivityTime = process.hrtime.bigint();
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
this.output.close();
|
|
86
|
+
}
|
|
47
87
|
pushFrame(frame) {
|
|
48
88
|
if (this.input.closed) {
|
|
49
89
|
throw new Error("Input is closed");
|
|
@@ -72,11 +112,12 @@ class VADStream {
|
|
|
72
112
|
this.input.close();
|
|
73
113
|
}
|
|
74
114
|
next() {
|
|
75
|
-
return this.
|
|
115
|
+
return this.output.next();
|
|
76
116
|
}
|
|
77
117
|
close() {
|
|
78
118
|
this.input.close();
|
|
79
119
|
this.queue.close();
|
|
120
|
+
this.output.close();
|
|
80
121
|
this.closed = true;
|
|
81
122
|
}
|
|
82
123
|
[Symbol.asyncIterator]() {
|