新手村23 - Speech Synthesis

23 - Speech Synthesis

俗話說的好,一天一蘋果,醫生遠離我

一天一 JS,What the f*ck JavaScript?

small steps every day - 記錄著新手村日記

完成目標

語音輸出(speech synthesis)

  • 功能
    • 切換不同的聲音(語系)
    • 調整說話速度(聲調改變)
    • 調整咬字速度
    • 要能輸入要唸的內容
    • 可以觸發開始講、暫停
    • 切換聲音時,會先暫停再開始講

index_START.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Speech Synthesis</title>
<link href='https://fonts.googleapis.com/css?family=Pacifico' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="style.css">
</head>
<body>

<div class="voiceinator">
<h1>The Voiceinator 5000</h1>

<select name="voice" id="voices">
<option value="">Select A Voice</option>
</select>

<label for="rate">Rate:</label>
<input name="rate" type="range" min="0" max="3" value="1" step="0.1">

<label for="pitch">Pitch:</label>

<input name="pitch" type="range" min="0" max="2" step="0.1">
<textarea name="text">Hello! I love JavaScript 👍</textarea>
<button id="stop">Stop!</button>
<button id="speak">Speak</button>
</div>

<script>
</script>

</body>
</html>

JS - step by step

首先,我們先將變數中的 msg 設定 text 值,也就是聲音要講的文字內容,在抓到值之前我們可以先看一下 SpeechSynthesisUtterance 裡面存放著什麼

1
//SpeechSynthesisUtterance {text: "", lang: "", voice: null, volume: -1, rate: -1, …}

透過 querySelector 抓取文字方塊內的 value ,並存進到上方的 text 之中

1
msg.text = document.querySelector('[name="text"]').value;

這時候就可以看到原本的 SpeechSynthesisUtterance 中的 text 值已經被更新了!

1
2
3
4
5
6
7
8
9
10
11
<script>
const msg = new SpeechSynthesisUtterance();
let voices = [];
const voicesDropdown = document.querySelector('[name="voice"]');
const options = document.querySelectorAll('[type="range"], [name="text"]');
const speakButton = document.querySelector('#speak');
const stopButton = document.querySelector('#stop');
msg.text = document.querySelector('[name="text"]').value;

//SpeechSynthesisUtterance {text: "Hello! I love JavaScript 👍", lang: "", voice: null, volume: -1, rate: -1, …}
</script>

再來我們來將 speechSynthesis 加上監聽事件 onvoiceschanged 並觸發populateVoices 方法,透過 SpeechSynthesis.getVoices() 取得包含所有物件的陣列,並將資料加入下拉式選單 Dropdown

SpeechSynthesis: voiceschanged event:https://tinyurl.com/y24evh2v

SpeechSynthesis.getVoices():https://tinyurl.com/ybxxro7q

1
2
3
4
5
6
7
8
9
10
11
<script>  
// 上略
speechSynthesis.addEventListener('voiceschanged', populateVoices);
function populateVoices() {
voices = this.getVoices();
voicesDropdown.innerHTML = voices
.filter(voice => voice.lang.includes('en'))
.map(voice => `<option value="${voice.name}">${voice.name} (${voice.lang})</option>`)
.join('');
}
</script>

接下來來將剛剛加入的下拉式選單與聲音連動起來吧!透過陣列尋找如果現在我們選擇的聲音資料與API那端的相符,就會以那個聲音觸發 toggle 方法

1
2
3
4
5
6
7
8
<script>  
// 上略
voicesDropdown.addEventListener('change', setVoice);
function setVoice() {
msg.voice = voices.find(voice => voice.name === this.value);
toggle();
}
</script>

toogle 方法就單純許多,只是控制先執行取消發生、再執行說話

SpeechSynthesis.cancel():https://tinyurl.com/yysl5zxs

SpeechSynthesis.speak():https://tinyurl.com/yykz4obn

1
2
3
4
5
6
7
8
9
<script> 
// 上略
function toggle(startOver = true) {
speechSynthesis.cancel();
if (startOver) {
speechSynthesis.speak(msg);
}
}
</script>

再來調整發音速度與音調的部分

1
2
3
4
5
6
7
8
9
<script> 
// 上略
options.forEach(option => option.addEventListener('change', setOption));
function setOption() {
//console.log(this.name, this.value);
msg[this.name] = this.value;
toggle();
}
</script>

最後就將按鈕觸發 click 來播放、停止說話,來控制 toggle 就可以控制說話惹w

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<script>
const msg = new SpeechSynthesisUtterance();
let voices = [];
const voicesDropdown = document.querySelector('[name="voice"]');
const options = document.querySelectorAll('[type="range"], [name="text"]');
const speakButton = document.querySelector('#speak');
const stopButton = document.querySelector('#stop');
msg.text = document.querySelector('[name="text"]').value;

speechSynthesis.addEventListener('voiceschanged', populateVoices);
function populateVoices() {
voices = this.getVoices();
voicesDropdown.innerHTML = voices
.filter(voice => voice.lang.includes('en'))
.map(voice => `<option value="${voice.name}">${voice.name} (${voice.lang})</option>`)
.join('');
}

voicesDropdown.addEventListener('change', setVoice);
function setVoice() {
msg.voice = voices.find(voice => voice.name === this.value);
toggle();
}

function toggle(startOver = true) {
speechSynthesis.cancel();
if (startOver) {
speechSynthesis.speak(msg);
}
}

options.forEach(option => option.addEventListener('change', setOption));
function setOption() {
console.log(this.name, this.value);
msg[this.name] = this.value;
toggle();
}

speakButton.addEventListener('click', toggle);
stopButton.addEventListener('click', () => toggle(false));

</script>

就大功告成啦!

JS - final

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<script>
const msg = new SpeechSynthesisUtterance();
let voices = [];
const voicesDropdown = document.querySelector('[name="voice"]');
const options = document.querySelectorAll('[type="range"], [name="text"]');
const speakButton = document.querySelector('#speak');
const stopButton = document.querySelector('#stop');
msg.text = document.querySelector('[name="text"]').value;

speechSynthesis.addEventListener('voiceschanged', populateVoices);
function populateVoices() {
voices = this.getVoices();
voicesDropdown.innerHTML = voices
.filter(voice => voice.lang.includes('en'))
.map(voice => `<option value="${voice.name}">${voice.name} (${voice.lang})</option>`)
.join('');
}

voicesDropdown.addEventListener('change', setVoice);
function setVoice() {
msg.voice = voices.find(voice => voice.name === this.value);
toggle();
}

function toggle(startOver = true) {
speechSynthesis.cancel();
if (startOver) {
speechSynthesis.speak(msg);
}
}

options.forEach(option => option.addEventListener('change', setOption));
function setOption() {
console.log(this.name, this.value);
msg[this.name] = this.value;
toggle();
}

speakButton.addEventListener('click', toggle);
stopButton.addEventListener('click', () => toggle(false));

</script>

本刊同步於個人網站:http://chestertang.site/

本次範例程式碼原作者來源:https://tinyurl.com/yavm5f5n