iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 23
0
Modern Web

JS30 錄系列 第 23

Day 23 - Speech Synthesis

  • 分享至 

  • xImage
  •  

任務目標

使用語音合成 API 建立一台語音翻譯機。
範例連結

作法

Day 21 有提到 Web Speech API 分成聲音轉字串的語音辨識 (Speech Recognition) 以及字串轉聲音的 語音合成 (Speech Synthesis) ,今天練習的是後者。

一台簡單的語音翻譯機框架如下:

<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>

要使用語音合成,得先設定好 SpeechSynthesisUtterance ,然後用 speechSynthesis.speak() 方法將其讀出。

SpeechSynthesisUtterance 是種請求,裡面包含要讀什麼、怎麼讀等資訊。整個過程可以想像成先準備好草稿 (Utterance),再發表演講(speak)這樣。

草稿最基本的需求為「要讀什麼」,可以透過設置 SpeechSynthesisUtterance.text 來達到。 先看以下程式碼:

// 創建一個語音需求的物件
const msg = new SpeechSynthesisUtterance();
const speakButton = document.querySelector('#speak');

// 播放語音的函式
function toggle() {
    // 讀出語音需求
    speechSynthesis.speak(msg);
}

// 設置要讀什麼
msg.text = 'Dudi is here.';

speakButton.addEventListener('click', toggle);

先創建一個語音需求物件,指定要讀的文字給 text 屬性,用 speeckSynthesis.speak() 讀出,其他的設定都會按照預設屬性來跑。

現在來看看要怎麼手動修改其他屬性,首先來設定要用哪種內建語音來讀。

先解釋一個機制,SpeechSynthesisVoiceList 是瀏覽器支援的語音所組成的清單,該清單會在瀏覽器頁面開啟時被安裝。

speechSynthesis.getVoices() 方法會回傳 SpeechSynthesisVoiceList
voicechanged 事件則是在 SpeechSynthesisVoiceList 內容改變時會被觸發。也就是說, voicechanged 在瀏覽器開啟後, SpeechSynthesisVoiceList 安裝完畢時就會被觸發一次。

為什麼要說這些?要擷取支援的語音清單需要用到它們,加入下面程式碼:

const voicesDropdown = document.querySelector('[name="voice"]');
let voices = [];

function populateVoices() {
  // 擷取語音模組清單
  voices = this.getVoices();
  voicesDropdown.innerHTML = voices
    .map(voice => `<option value="${voice.name}">${voice.name} (${voice.lang})</option>`)
    .join('');
}

// 當 `voicechanged` 觸發時,呼叫 `populateVoices` 函式載入支援語音
speechSynthesis.addEventListener('voiceschanged', populateVoices);

利用 voicechanged 事件的特性,當所有支援的語音模組載入完畢時,呼叫函式擷取所有的模組。取得每個語音模組的 name 屬性與 lang 屬性後,組合成 HTML 選項標籤貼到下拉選單內,我們就可以選擇想要的語音模組使用。

SpeechSynthesisUtterancevoice 屬性可以設定要使用哪個語音模組,我們可以監聽下拉式選單的變動來改變 voice 屬性,然後讓新的語音模組被設定完就立即播放。加入以下程式碼:

function setVoice() {
  msg.voice = voices.find(voice => voice.name === this.value);
  // 設定完就播放
  toggle();
}

voicesDropdown.addEventListener('change', setVoice);

SpeechSynthesisUtterancepitch 屬性可以設定語音音調、rate 屬性可以設定語音速度。將相同的字串放在 <input type="range"> 標籤的 name 屬性,用程式碼連結便能直接操控兩者。加入以下程式碼:

const options = document.querySelectorAll('[type="range"], [name="text"]');

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

options.forEach(option => option.addEventListener('change', setOption));

和前幾篇慣用的手法相同,監聽滑動軸的變化,只要值改變了就更新 SpeechSynthesisUtterance 內相對應的屬性到新的值。

順便把要講的字也接到可以輸入的欄位中,加入下列程式碼:

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

最後要對 toggle() 函式做點小小的改變。我們希望切換語音的時候,不用等到原句子講完,可以直接中斷並播放新的。另外我們也希望按了停止鍵後,會停止所有語音。改造 toggle 順便加點東西:

function toggle(startOver = true) {
  // 先停止
  speechSynthesis.cancel();
  // 如果 startOver 開關是打開的才會播放
  if(startOver) {
    speechSynthesis.speak(msg);
  }
}

// Stop 按鈕會把 startOver 開關關閉
stopButton.addEventListener('click', toggle.bind(null, false));

這裡用函式預設參數的方式讓 startOver 參數預設是打開的。只有 stop 按鈕按下時可以把切換該參數。因為在 addEventListener 內的函式加了括號會自動執行,因此無法在裡面直接塞參數。這裡用了 bind 方法, bind 方法的第一個參數可以強制指定 toggle 的呼叫對象,第二個參數可以塞參數進去 toggle 內。我們只使用第二個參數,第一個參數設 null 即可。

以上就是 JS30 第二十三篇!

Reference

SpeechSythesisUtterance
SpeechSynthesis.getVoices() 方法及 voicechanged 事件的關係
完整程式碼


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

尚未有邦友留言

立即登入留言