UNPKG

@convo-lang/convo-lang

Version:
124 lines 4.31 kB
import { aryRemoveItem, CancelToken, getErrorMessage } from "@iyio/common"; import { BehaviorSubject } from "rxjs"; import { convoTtsService } from "./convo.deps.js"; export class TtsCtrl { constructor(ttsService) { this._isDisposed = false; this._convertingTextToSpeech = new BehaviorSubject(false); this._readingText = new BehaviorSubject(false); this._provider = new BehaviorSubject(null); this._model = new BehaviorSubject(null); this._voice = new BehaviorSubject(null); this.queue = []; this.isFlushing = false; this.ttsService = ttsService; } get isDisposed() { return this._isDisposed; } dispose() { if (this._isDisposed) { return; } this._isDisposed = true; } get convertingTextToSpeechSubject() { return this._convertingTextToSpeech; } get convertingTextToSpeech() { return this._convertingTextToSpeech.value; } get readingTextSubject() { return this._readingText; } get readingText() { return this._readingText.value; } get providerSubject() { return this._provider; } get provider() { return this._provider.value; } set provider(value) { if (value == this._provider.value) { return; } this._provider.next(value); } get modelSubject() { return this._model; } get model() { return this._model.value; } set model(value) { if (value == this._model.value) { return; } this._model.next(value); } get voiceSubject() { return this._voice; } get voice() { return this._voice.value; } set voice(value) { if (value == this._voice.value) { return; } this._voice.next(value); } textToSpeech(request, cancel) { const service = this.ttsService || convoTtsService.get(); if (!service) { return null; } const item = { request: { provider: request.provider ?? this.provider ?? undefined, model: request.model ?? this.model ?? undefined, voice: request.voice ?? this.voice ?? undefined, text: request.text, }, cancel: cancel ?? new CancelToken(), }; this.queue.push(item); this.convertToAudioAsync(item, service); return item; } async convertToAudioAsync(item, service) { try { const canConvert = await service.canConvertToSpeech(item.request); if (!canConvert || this.isDisposed || item.cancel.isCanceled) { aryRemoveItem(this.queue, item); return; } const r = await service.convertToSpeechAsync(item.request); if (!r.success || this.isDisposed || item.cancel.isCanceled) { aryRemoveItem(this.queue, item); return; } item.tts = r.tts; this.flushAsync(); } catch (ex) { console.error(`Failed to convert text to speech. error:${getErrorMessage(ex)}, text:${item.request.text}`); aryRemoveItem(this.queue, item); } } async flushAsync() { if (this.isDisposed || this.isFlushing) { return; } this.isFlushing = true; try { let item; while ((item = this.queue[0])?.tts) { aryRemoveItem(this.queue, item); if (item.cancel.isCanceled) { continue; } const url = URL.createObjectURL(item.tts.audio); try { const audio = new Audio(url); audio.onended = () => { item?.cancel.cancelNow(); }; audio.onerror = () => { item?.cancel.cancelNow(); }; audio.play(); await item.cancel.toPromise(); audio.pause(); } finally { URL.revokeObjectURL(url); } } } finally { this.isFlushing = false; } } } //# sourceMappingURL=TtsCtrl.js.map