[JS30] Day23: Speech Synthesis
這一天挑戰的目標是要將文字轉成語音輸出,透過SpeechSynthesisUtterance的瀏覽器 API,可以選擇不同的人、語言,以及他們說話的語速和音調來去呈現不同的聲音演講。
HTML
有兩個<input />,用來調整語速跟音調,根據<textarea>的文字,和<select>的語言人是來說出來。
<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>
JavaScript
建立SpeechSynthesisUtterance的實例叫做msg,並選取其他元素,msg.text帶入要講出來的文字
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;
將提供的發聲(utterance)種類放入 option 之中
function populateVoices() {
voices = this.getVoices();
const voiceOptions = voices
.filter((voice) => voice.lang.includes("en")) // 包含en的聲音,可刪除
.map(
(voice) =>
`<option value="${voice.name}">${voice.name} (${voice.lang})</option>`
)
.join("");
voicesDropdown.innerHTML = voiceOptions; // 放入dropdown
}
speechSynthesis.addEventListener("voiceschanged", populateVoices);
在選取的時候,將發聲放入msg實例
voicesDropdown.addEventListener("change", setVoice);
function setVoice() {
msg.voice = voices.find((voice) => voice.name === this.value);
}
設置一個 function: toggle,要在變換聲音時,重新發聲
function toggle(startOver = true) {
speechSynthesis.cancel(); // 取消發聲
if (startOver) {
speechSynthesis.speak(msg);
}
}
setOption函式設置語速和音調,透過name屬性,知道是rate還是pitch,然後設定msg的屬性為 input 的值
function setOption() {
msg[this.name] = this.value;
toggle();
}
options.forEach((option) => option.addEventListener("change", setOption));
在 speakButton 和 stopButton 加入點擊的監聽器,觸發toggle來發聲,toggle.bind會回傳一個函式(this 為 null, param 為 false),因此會使得發聲暫停,且不會重新發生。
speakButton.addEventListener("click", toggle);
stopButton.addEventListener("click", toggle.bind(null, false)); // () => toggle(false)