iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 23
0
自我挑戰組

新手村-30 Day JS Coding Challenge系列 第 23

新手村23 - Speech Synthesis

23 - Speech Synthesis

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

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

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

完成目標

語音輸出(speech synthesis)

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

index_START.html

<!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>
  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');
</script>

</body>
</html>

JS - step by step

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

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

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

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

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

<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

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

<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

<script> 
	// 上略
	function toggle(startOver = true) {
    speechSynthesis.cancel();
    if (startOver) {
      speechSynthesis.speak(msg);
    }
  }
</script> 

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

<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

<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

<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


上一篇
新手村22 - Follow Along Link Highlighter
下一篇
新手村24 - Sticky Nav
系列文
新手村-30 Day JS Coding Challenge30

尚未有邦友留言

立即登入留言