iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 23
0

Day23-課題內容

在第20天的課題當中,我們透過 Web API 中的 SpeechRecognition 介面,將輸入的聲音轉成字串。而今天我們要透過另一個 SpeechSynthesis,將輸入的文字轉為聲音,做出我們自己的 google 小姐。
實作連結

SpeechSynthesisUtterance

SpeechSynthesisUtterance 為 Web Speech API 中代表一發音的需求,此物件中包含可讓語音合成伺服器辨識的資訊,例如語言、音調、聲音、速率等。[2]

SpeechSynthesisUtterance Property

  1. SpeechSynthesisUtterance.lang:設定或取得發音的語言。

  2. SpeechSynthesisUtterance.pitch:設定或取得發音的音調。

  3. SpeechSynthesisUtterance.rate:設定或取得發音的速度。

  4. SpeechSynthesisUtterance.text:設定或取得發音的文字內容。

  5. SpeechSynthesisUtterance.voice:設定或取得發音的聲音。

  6. SpeechSynthesisUtterance.volume:設定或取得發音的音量。

SpeechSynthesisUtterance Event

  1. SpeechSynthesisUtterance.onboundary:當發音到單字間或片語間的停頓時會出發此事件。

  2. SpeechSynthesisUtterance.onend:完成語句的發音之後會觸發此事件。

  3. SpeechSynthesisUtterance.onpause:發音被暫停時會觸發此事件。

  4. SpeechSynthesisUtterance.onresume:發音被繼續播放時會觸發此事件。

  5. SpeechSynthesisUtterance.onstart:開始發音時會觸發此事件。

SpeechSynthesis

SpeechSynthesis 介面,為語音合成伺服器的控制器,可透過其來取得可使用的語音合成資訊,並播放或暫停發音等相關功能。[3]

SpeechSynthesis Property

  1. SpeechSynthesis.paused:回傳目前 SpeechSynthesis object 物件是否為暫停的狀態。
  2. SpeechSynthesis.pending:回傳語言庫中是否還有沒被發音出來的語句。
  3. SpeechSynthesis.speaking:回傳目前是否有語句正在被發音出來。

SpeechSynthesis Method

  1. SpeechSynthesis.cancel():移除所有的發音資訊。

  2. SpeechSynthesis.getVoices():取得一陣列,其中包含目前所有的 SpeechSynthesisVoice 物件,其裡頭的屬性為所有發音資訊。

  3. SpeechSynthesis.pause():將 SpeechSynthesis 物件改為暫停的狀態。

  4. SpeechSynthesis.resume():取消 SpeechSynthesis 物件的暫停狀態。

  5. SpeechSynthesis.speak():將一段發音加入發音庫,當前面的發音皆播放完成後,就會播放此發音。

進入課題

首先我們先將 speechSynthesis 物件加上其唯一的監聽事件 onvoiceschanged,函示。當speechSynthesis 物件中的 SpeechSynthesisVoice 清單改變時,就會觸發該事件。而在觸發的函式中,可以利用 SpeechSynthesis.getVoices()方法,取得包含所有SpeechSynthesisVoice 物件的陣列,並將這些資料加到上方的可選取欄位中:

speechSynthesis.addEventListener('voiceschanged', populateVoices);
function populateVoices(event) {
    //取得 speechSynthesis 中包含的所有語言資料物件的陣列
    voices = event.target.getVoices();
    //將這些資料轉入我們的 dropdown 表單中
    let optionList = voices.map(function(voice){
        return `<option value="${voice.name}">${voice.name} (${voice.lang})</option>`;
        });
    voicesDropdown.innerHTML = optionList.join('');  
};

下一步我們讓語音格式與選取表單中的資料連動,因此我們要將該 select 元素加上監聽事件 onchange 以及觸發函示。在該函式當中,我們要透過前面陣列方法提過的 Array.find() 方法,從陣列中找出與我們相符的物件,並透過 SpeechSynthesisUtterance.voice,將我們選擇的聲音資料指定給 SpeechSynthesisUtterance,並先停止之前的發音,在執行這次的發音:

voicesDropdown.addEventListener('change', setVoice);
function setVoice(event) {
    msg.voice = voices.find(function(voice){
    return voice.name === this.value
    });
    speechSynthesis.cancel();
    speechSynthesis.speak(msg);
};

可以發現我們先執行了 speechSynthesis.cancel(),之後才執行 speechSynthesis.speak(msg)。這是因為如同前面 SpeechSynthesis Method 中所提到的,如果我們只單純執行 .speak() 的話,需要等到前面的發音都播放完畢才會播放當次的發音,所以需要加一個 .cancel() 在前面,才能馬上播放當下的發音。

再來要將調整器的功能加上。透過加上監聽事件 onchange 與指定 input 元素的值給 SpeechSynthesisUtterance.rate 以及 SpeechSynthesisUtterance.volume 屬性,我們就能隨心所欲的調整發音速度與音調:

options.forEach(function(option){
    option.addEventListener('change', setOption);
});

function setOption() {
    msg[this.name] = this.value;
    speechSynthesis.cancel();
    speechSynthesis.speak(msg);
};

最後只要將我們的播放按鈕與停止按鈕加上功能,即可完成今天的課題:

speakButton.addEventListener('click', speak);
stopButton.addEventListener('click', pause);
function speak () {
    speechSynthesis.speak(msg);
};
function pause () {
    speechSynthesis.cancel();
};

總結

今天我們學到以下的技能:

  1. SpeechSynthesisUtterance
  2. SpeechSynthesis

今天透過使用以上的 Web Speech API 介面,我們成功將輸入的字串轉為聲音輸出,將介面變成屬於自己的 google 小姐,並額外加上音調與速度的功能,讓我們的 google 小姐變得更潮!

參考資料

  1. javascript30
  2. SpeechSynthesisUtterance
  3. SpeechSynthesis

上一篇
JS30-Day22-Follow Along Nav
下一篇
JS30-Day24-Sticky Nav
系列文
新手也能懂的JS3030

1 則留言

0
阿姆
iT邦新手 5 級 ‧ 2018-08-31 16:46:55

你好,我發現一個問題~在這個步驟

voicesDropdown.addEventListener('change', setVoice);
function setVoice(event) {
    msg.voice = voices.find(function(voice){
    return voice.name === this.value
    });
    speechSynthesis.cancel();
    speechSynthesis.speak(msg);
};

似乎沒有正確將voice資料指定給msg
console.log(msg)如下

我按照作者原始範例
使用arrow function

msg.voice = voices.find(voice => voice.name === this.value);

就有正確抓到voice資料:

不知道傳統function在這邊為何抓不到(還是根本不是這個問題XD)

我要留言

立即登入留言