84 lines
1.8 KiB
TypeScript
84 lines
1.8 KiB
TypeScript
const allVoices: Record<string, string> = {}
|
|
|
|
// eslint-disable-next-line array-callback-return
|
|
speechSynthesis.getVoices().map((v) => {
|
|
allVoices[v.lang] = v.lang
|
|
})
|
|
|
|
speechSynthesis.onvoiceschanged = () => {
|
|
// eslint-disable-next-line array-callback-return
|
|
speechSynthesis.getVoices().map((v) => {
|
|
allVoices[v.lang] = v.lang
|
|
})
|
|
}
|
|
|
|
export async function speak(
|
|
s: string,
|
|
opts: {
|
|
lang?: string
|
|
forceOffline?: boolean
|
|
url?: string
|
|
} = {}
|
|
) {
|
|
if (!opts.forceOffline) {
|
|
const audio = new Audio(
|
|
opts.url ||
|
|
`/api/tts?q=${encodeURIComponent(s)}&lang=${encodeURIComponent(
|
|
opts.lang || 'ja'
|
|
)}`
|
|
)
|
|
await audio.play().catch(() =>
|
|
speak(s, {
|
|
...opts,
|
|
forceOffline: true,
|
|
})
|
|
)
|
|
return
|
|
}
|
|
|
|
let lang = 'zh'
|
|
|
|
const voices = Object.keys(allVoices)
|
|
const stage1 = () => voices.filter((v) => v === lang)[0]
|
|
const stage2 = () => {
|
|
const m1 = lang.substr(0, 2)
|
|
const m2 = lang.substr(3, 2)
|
|
const r1 = new RegExp(`^${m1}[-_]${m2}`, 'i')
|
|
return voices.filter((v) => r1.test(v))[0]
|
|
}
|
|
const stage3 = () => {
|
|
const m1 = lang.substr(0, 2).toLocaleLowerCase()
|
|
return voices.filter((v) => v.toLocaleLowerCase().startsWith(m1))[0]
|
|
}
|
|
|
|
lang = stage1() || stage2() || stage3() || ''
|
|
|
|
if (lang) {
|
|
const utterance = new SpeechSynthesisUtterance(s)
|
|
utterance.lang = lang
|
|
speechSynthesis.speak(utterance)
|
|
|
|
return new Promise<void>((resolve) => {
|
|
utterance.onend = () => {
|
|
resolve()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
window.addEventListener('keydown', (ev) => {
|
|
if (
|
|
ev.target instanceof HTMLElement &&
|
|
['INPUT', 'TEXTAREA'].includes(ev.target.tagName.toLocaleUpperCase())
|
|
) {
|
|
return
|
|
}
|
|
|
|
if (ev.key === 's') {
|
|
const s = window.getSelection()!.toString()
|
|
if (s) {
|
|
speak(s)
|
|
}
|
|
}
|
|
})
|