| 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 |
| } |
| } |
|
|