* Remove stopInternal which causes audio delaying * Change audio obj to not be recreated every sentence * refactor abort signal handler --------- Co-authored-by: Huang Xin <chrox.huang@gmail.com>
This commit is contained in:
@@ -97,18 +97,17 @@ export class EdgeTTSClient implements TTSClient {
|
||||
} as TTSMessageEvent;
|
||||
|
||||
return;
|
||||
} else {
|
||||
await this.stopInternal();
|
||||
}
|
||||
|
||||
await this.stopInternal();
|
||||
// Reuse the same Audio element inside the ssml session
|
||||
this.#audioElement = new Audio();
|
||||
const audio = this.#audioElement;
|
||||
audio.setAttribute('x-webkit-airplay', 'deny');
|
||||
audio.preload = 'auto';
|
||||
|
||||
for (const mark of marks) {
|
||||
if (signal.aborted) {
|
||||
yield {
|
||||
code: 'error',
|
||||
message: 'Aborted',
|
||||
} as TTSMessageEvent;
|
||||
break;
|
||||
}
|
||||
let abortHandler: null | (() => void) = null;
|
||||
try {
|
||||
const { language: voiceLang } = mark;
|
||||
const voiceId = await this.getVoiceIdFromLang(voiceLang);
|
||||
@@ -116,11 +115,7 @@ export class EdgeTTSClient implements TTSClient {
|
||||
const blob = await this.#edgeTTS.createAudio(
|
||||
this.getPayload(voiceLang, mark.text, voiceId),
|
||||
);
|
||||
const url = URL.createObjectURL(blob);
|
||||
this.#audioElement = new Audio(url);
|
||||
const audio = this.#audioElement;
|
||||
audio.setAttribute('x-webkit-airplay', 'deny');
|
||||
audio.preload = 'auto';
|
||||
audio.src = URL.createObjectURL(blob);
|
||||
|
||||
this.controller?.dispatchSpeakMark(mark);
|
||||
|
||||
@@ -135,27 +130,30 @@ export class EdgeTTSClient implements TTSClient {
|
||||
audio.onended = null;
|
||||
audio.onerror = null;
|
||||
audio.pause();
|
||||
audio.src = '';
|
||||
URL.revokeObjectURL(url);
|
||||
if (audio.src) {
|
||||
URL.revokeObjectURL(audio.src);
|
||||
audio.removeAttribute('src');
|
||||
}
|
||||
};
|
||||
abortHandler = () => {
|
||||
cleanUp();
|
||||
resolve({ code: 'error', message: 'Aborted' });
|
||||
};
|
||||
if (signal.aborted) {
|
||||
abortHandler();
|
||||
return;
|
||||
} else {
|
||||
signal.addEventListener('abort', abortHandler);
|
||||
}
|
||||
audio.onended = () => {
|
||||
cleanUp();
|
||||
if (signal.aborted) {
|
||||
resolve({ code: 'error', message: 'Aborted' });
|
||||
} else {
|
||||
resolve({ code: 'end', message: `Chunk finished: ${mark.name}` });
|
||||
}
|
||||
resolve({ code: 'end', message: `Chunk finished: ${mark.name}` });
|
||||
};
|
||||
audio.onerror = (e) => {
|
||||
cleanUp();
|
||||
console.warn('Audio playback error:', e);
|
||||
resolve({ code: 'error', message: 'Audio playback error' });
|
||||
};
|
||||
if (signal.aborted) {
|
||||
cleanUp();
|
||||
resolve({ code: 'error', message: 'Aborted' });
|
||||
return;
|
||||
}
|
||||
this.#isPlaying = true;
|
||||
audio.play().catch((err) => {
|
||||
cleanUp();
|
||||
@@ -167,22 +165,20 @@ export class EdgeTTSClient implements TTSClient {
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === 'No audio data received.') {
|
||||
console.warn('No audio data received for:', mark.text);
|
||||
yield {
|
||||
code: 'end',
|
||||
message: `Chunk finished: ${mark.name}`,
|
||||
} as TTSMessageEvent;
|
||||
yield { code: 'end', message: `Chunk finished: ${mark.name}` } as TTSMessageEvent;
|
||||
continue;
|
||||
}
|
||||
console.log('Error:', error);
|
||||
yield {
|
||||
code: 'error',
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
} as TTSMessageEvent;
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.warn('TTS error for mark:', mark.text, message);
|
||||
yield { code: 'error', message } as TTSMessageEvent;
|
||||
break;
|
||||
} finally {
|
||||
if (abortHandler) {
|
||||
signal.removeEventListener('abort', abortHandler);
|
||||
}
|
||||
}
|
||||
|
||||
await this.stopInternal();
|
||||
}
|
||||
await this.stopInternal();
|
||||
}
|
||||
|
||||
async pause() {
|
||||
|
||||
Reference in New Issue
Block a user