iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 23
0

成品連結:Speech Synthesis操作前程式碼完成後程式碼

今天要做的作品與前幾天做過的語音辨識類似,不過今天要做的是閱讀文字。閱讀文字同樣用到了瀏覽器內建的 Web Speech API,不過目前僅有 Google Chrome 支援,在其他瀏覽器會沒有效果。

首先可以看到 JS 已經預先宣告了幾個 DOM 元素,其中包含一項 const msg = new SpeechSynthesisUtterance(); 也就是今天操作的主角,我們會利用 SpeechSynthesisUtterance() 來設置要閱讀的文字、閱讀速度、音調、以及選擇的語音。

這裡我們可以先設置 msg.text,也就是要被讀出的文字

msg.text = document.querySelector('textarea').value;

選取閱讀語音

我們可以藉由已宣告的 msg 來設置語音,但在那之前需要先取得所有可用的語音。而取得語音會用到 speechThesis.getVoices() 這個方法

但如果你直接下了這個語法 voices = speechThesis.getVoices(),會發現 voices 仍舊空空如也,這是因為 speechThesis 需要搭配監聽事件 voiceschanged 才會有效果

function populateVoices() {
    // code here
}

speechSynthesis.addEventListener('voiceschanged', populateVoices);

function voiceschanged 當中就可以填入剛剛沒有效果的 voices = speechThesis.getVoices(),或是你可以寫 voices = this.getVoices()

function populateVoices() {
    voices = this.getVoices();
}

speechSynthesis.addEventListener('voiceschanged', populateVoices);

這時候 voices 裡面多了許多語音!(如果你使用 Windows 或是其他作業系統,看到的數量可能會不同)每一項的格式看起來像這樣

過濾使語音剩下英語語音

使用 array 的方法 filter() 過濾出英語發音的語音

function populateVoices() {
    voices = this.getVoices();
    const voiceOptions = voices
        .filter(voice => voice.lang.includes('en'));
}

接著渲染至 HTML 中的 voicesDropdown。你可以使用 innerHTML 或是 createElement 然後再 append 到 HTML,這裡我用 innerHTML

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

如果我的 JS30 前幾篇也都有看的話應該對這種鏈狀的寫法不陌生。

設定 msg 的語音

當使用者從選單選到語音時,要將該語音加至 msg.voice,如此到時候閱讀時才會用此語音讀出聲音。要從一個 array 中找到特定項目很適合用 find() 這個方法

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

現在當你在 console 輸入 speechSynthesis.speak(msg) 應該就會聽到聲音了,換個聲音再試試看吧!

開始 / 停止播放聲音

現在我們要把 speechSynthesis.speak(msg) 這個功能寫入 function,方便隨時呼叫(像是在更換語音、頻率、速度時)

function toggle() {
    speechSynthesis.cancel();
    speechSynthesis.speak(msg);
}

第一個方法 speechSynthesis.cancel() 可以將目前的所有的 SpeechSynthesisUtterance() 都清除,清除後我們再接著播放(配合新的聲音、速度、頻率等等...)

我們可以將這個 function 放在剛剛建立的 setVoice 中執行

function setVoice() {
    msg.voice = voices.find(voice => voice.name === this.value);
    
    toggle();  // 語音設置好後閱讀文字
}

設定速度、頻率、文字

可以看到已有變數儲存 兩個 input 及一個 textarea,先建立監聽事件

function setOption() {
    // code here
}
options.forEach(option => option.addEventListener('input', setOption));

從這幾個元素的name 屬性可以看到分別是 ratepitchtext,剛好跟 SpeechSynthesisUtterance() 中的屬性名稱符合!我們可以善用這點來進行修改

function setOption() {
    msg[this.name] = this.value;

    toggle();
}
options.forEach(option => option.addEventListener('input', setOption));

this.value 的值會是input[type="range"] 中的數值以及 textarea 的文字內容,設定完成後一樣執行 toggle()

設定開始閱讀 / 停止閱讀

來到最後的部分了!這裡相對簡單,只要設定監聽事件並執行 toggle() 就好。

等一下!開始閱讀好處理,但是停止閱讀呢?toggle() 沒辦法停止啊!

沒有錯,所以我們需要修改一下原本的 toggle() 一下。我們要在帶入參數中設定 boolean,如果是 true 才開始閱讀,反之只會執行 speechSynthesis.cancel() 停止閱讀

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

startOver = true 是 ES6 的寫法,意思是預設 startOver 的值是 true,所以如果沒有帶入任何參數會預設帶入 true

最後來綁定事件吧!

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

stopButton 的 callback 看起來奇怪,其實意思與下方一樣

stopButton.addEventListener('click', function(e) {
    toggle(false);
});

完成!

Reference


上一篇
JS30 Day 22 - Follow Along Link
下一篇
JS30 Day 24 - Sticky Nav
系列文
一起挑戰 JavaScript 30 吧!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言