|
import { sleep } from './utils' |
|
|
|
const synth = window.speechSynthesis |
|
|
|
export class TTS { |
|
currentText = '' |
|
speakText = '' |
|
private controller = new AbortController() |
|
speaking = false |
|
get isSpeaking() { |
|
return this.speaking |
|
} |
|
finished = false |
|
constructor() {} |
|
abort = () => { |
|
this.controller.abort() |
|
} |
|
|
|
reset = () => { |
|
this.speaking = false |
|
this.finished = true |
|
this.currentText = '' |
|
this.speakText = '' |
|
this.abort() |
|
} |
|
|
|
speak = (text: string) => { |
|
if (!synth || text?.trim()?.length < 2) { |
|
return |
|
} |
|
this.currentText = text.replace(/[^\u4e00-\u9fa5_a-zA-Z0-9,。?,:;\.,:]+/g, '') |
|
this.finished = false |
|
this.loop() |
|
} |
|
|
|
private async doSpeek() { |
|
return new Promise((resolve) => { |
|
const endIndex = this.finished ? this.currentText.length : |
|
Math.max( |
|
this.currentText.lastIndexOf('。'), |
|
this.currentText.lastIndexOf(';'), |
|
this.currentText.lastIndexOf('、'), |
|
this.currentText.lastIndexOf('?'), |
|
this.currentText.lastIndexOf('\n') |
|
) |
|
const startIndex = this.speakText.length ? Math.max(0, this.currentText.lastIndexOf(this.speakText) + this.speakText.length) : 0 |
|
|
|
if (startIndex >= endIndex) { |
|
return resolve(true) |
|
} |
|
const text = this.currentText.slice(startIndex, endIndex) |
|
this.speakText = text |
|
const utterThis = new SpeechSynthesisUtterance(text) |
|
this.controller.signal.onabort = () => { |
|
synth.cancel() |
|
this.finished = true |
|
resolve(false) |
|
} |
|
|
|
utterThis.onend = function (event) { |
|
resolve(true) |
|
} |
|
|
|
utterThis.onerror = function (event) { |
|
resolve(false) |
|
} |
|
|
|
const voice = synth.getVoices().find(v => v.name.includes('Microsoft Yunxi Online')) ?? null |
|
utterThis.voice = voice |
|
synth.speak(utterThis) |
|
}) |
|
} |
|
|
|
private async loop() { |
|
if (this.speaking) return |
|
this.speaking = true |
|
while(!this.finished) { |
|
await Promise.all([sleep(1000), this.doSpeek()]) |
|
} |
|
this.speaking = false |
|
} |
|
} |
|
|