@convo-lang/convo-lang
Version:
The language of AI
124 lines • 4.31 kB
JavaScript
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