|
1 | 1 | import { useState } from 'react'
|
2 |
| -import { UnmuteIcon, MuteIcon } from '@primer/octicons-react' |
| 2 | +import { MuteIcon, UnmuteIcon } from '@primer/octicons-react' |
3 | 3 | import PropTypes from 'prop-types'
|
4 | 4 | import { useTranslation } from 'react-i18next'
|
| 5 | +import { useConfig } from '../../hooks/use-config.mjs' |
5 | 6 |
|
6 | 7 | ReadButton.propTypes = {
|
7 | 8 | contentFn: PropTypes.func.isRequired,
|
8 | 9 | size: PropTypes.number.isRequired,
|
9 | 10 | className: PropTypes.string,
|
10 | 11 | }
|
11 | 12 |
|
| 13 | +const synth = window.speechSynthesis |
| 14 | + |
12 | 15 | function ReadButton({ className, contentFn, size }) {
|
13 | 16 | const { t } = useTranslation()
|
14 | 17 | const [speaking, setSpeaking] = useState(false)
|
| 18 | + const config = useConfig() |
15 | 19 |
|
16 | 20 | const startSpeak = () => {
|
17 |
| - speechSynthesis.cancel() |
18 |
| - |
19 |
| - let text = contentFn() |
| 21 | + synth.cancel() |
20 | 22 |
|
| 23 | + const text = contentFn() |
21 | 24 | const utterance = new SpeechSynthesisUtterance(text)
|
| 25 | + const voices = synth.getVoices() |
| 26 | + |
| 27 | + let voice |
| 28 | + if (config.preferredLanguage.includes('en') && navigator.language.includes('en')) |
| 29 | + voice = voices.find((v) => v.name.toLowerCase().includes('microsoft aria')) |
| 30 | + else if (config.preferredLanguage.includes('zh') || navigator.language.includes('zh')) |
| 31 | + voice = voices.find((v) => v.name.toLowerCase().includes('xiaoyi')) |
| 32 | + if (!voice) voice = voices.find((v) => v.lang.substring(0, 2) === config.preferredLanguage) |
| 33 | + if (!voice) voice = voices.find((v) => v.lang === navigator.language) |
| 34 | + |
22 | 35 | Object.assign(utterance, {
|
23 |
| - // lang: 'zh-CN', |
24 |
| - rate: 0.9, |
| 36 | + rate: 1, |
25 | 37 | volume: 1,
|
26 | 38 | onend: () => setSpeaking(false),
|
27 | 39 | onerror: () => setSpeaking(false),
|
| 40 | + voice: voice, |
28 | 41 | })
|
29 | 42 |
|
30 |
| - let supportedVoices = speechSynthesis.getVoices() |
31 |
| - for (let i = 0; i < supportedVoices.length; i++) { |
32 |
| - if (supportedVoices[i].lang.indexOf(text[0]) >= 0) { |
33 |
| - utterance.voice = supportedVoices[i] |
34 |
| - break |
35 |
| - } |
36 |
| - } |
37 |
| - |
38 |
| - speechSynthesis.speak(utterance) |
| 43 | + synth.speak(utterance) |
39 | 44 | setSpeaking(true)
|
40 | 45 | }
|
41 | 46 |
|
42 | 47 | const stopSpeak = () => {
|
43 |
| - speechSynthesis.cancel() |
| 48 | + synth.cancel() |
44 | 49 | setSpeaking(false)
|
45 | 50 | }
|
46 | 51 |
|
47 | 52 | return (
|
48 | 53 | <span
|
49 |
| - title={t('Read')} |
| 54 | + title={t('Read Aloud')} |
50 | 55 | className={`gpt-util-icon ${className ? className : ''}`}
|
51 | 56 | onClick={speaking ? stopSpeak : startSpeak}
|
52 | 57 | >
|
|
0 commit comments