iT邦幫忙

2021 iThome 鐵人賽

DAY 20
0
自我挑戰組

JS30 學習日記系列 第 20

Day 20 - Native Speech Recognition

前言

JS 30 是由加拿大的全端工程師 Wes Bos 免費提供的 JavaScript 簡單應用課程,課程主打 No FrameworksNo CompilersNo LibrariesNo Boilerplate 在30天的30部教學影片裡,建立30個JavaScript的有趣小東西。

另外,Wes Bos 也很無私地在 Github 上公開了所有 JS 30 課程的程式碼,有興趣的話可以去 fork 或下載。


本日目標

利用 Web Speech API 取得使用者的麥克風存取權限進行語音辨識,然後把識別出的內容寫到網頁上。


設定本地伺服器

跟昨天一樣,要取得使用者的媒體裝置必須是在安全連線之下(secure origin),所以我們必須自己架一個 little server。

今天不採用 Wes Bos 用 NPM 架 Server 的方法,因為我發現只要在Visual Studio 有安裝 Live Server 這個延伸模組就可以不用安裝一些 NPM 看不懂的套件,也能達到一樣的效果。

  • 點擊左方的方塊(延伸模組 or Extension),然後搜尋Live Server安裝後啟用即可

  • 打開你的網頁檔案,右下角就會有一個Go Live的按鈕,點下去就可以用localhost即時預覽網頁

這樣有比用 NPM 來得輕鬆很多吧~~~


解析程式碼

HTML 部分

div(.words) 用來放經過語音辨識後所產生的文字段落(<p>~</p>)。

<div class="words" contenteditable></div>

JS 部分

SpeechRecognition本身是一個在 browser 之下的 global variable,但是因為瀏覽器支援性的問題,我們在 Chrome 必須使用帶有前綴字的webkitSpeechRecognition

window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;

首先建立一個SpeechRecognition物件recognition來幫我們做語音辨識。

設定SpeechRecognition物件的屬性interimResults為 true,它就會將現在辨識到的所有內容都回傳,如果是 false 的話,則只有停止說話才回傳內容。

設定SpeechRecognition物件的屬性langen-US,表示要辨識的語言是英文。

const recognition = new SpeechRecognition();
recognition.interimResults = true;
recognition.lang = 'en-US';

建立<p>標籤用來放入辨識出的文字內容。

宣告常數words並取得 div(.words)。

words.appendChild(p);,將<p>標籤的文字段落放到div(.words)裡面,換句話說就是把辨識出的內容先放到<p>再放到<div class="words">裡面。

let p = document.creatElement('p');
const words = document.querySelector('.words');
words.appendChild(p);

SpeechRecognition物件recognition註冊result event的監聽器,如果有回傳result就觸發事件,然後把這個Speech Recognition Event印到 console,觀察後你會發現我們需要用到的東西是放在‵e.results這個 lsit 裡面。

recognition.start();開始語音識別,原則上只會識別一次,當停止說話一段時間即停止。

recognition.addEventListener('result',e=>{
    console.log(e); //log speech recognition event
    console.log(e.results);
    debugger; //breakpoint
});

recognition.start();    

SpeechRecognitionEvent

SpeechRecognitionEvent.results

其中 results[0] 隱含一些必要資訊,包括語音辨識出的內容(transcript)和是否已經停止辨識(isFinal)。

宣告常數transcript並透過兩次map取得results[0].transcript也就是語音辨識出的內容。

因為results本身不是 Array 不能用 map(),所以先用Array.from()換成 Array。

第一次的 map() 取的 results[0] 所構成的陣列,第二次 map()取得 results[0] 內部 transcript 所構成的陣列,最後將陣列內的元素 transcriptjoin()串成一個大字串。

p.textContent = transcript;,將這次辨識出的所有內容放到<p>標籤裡面。

recognition.addEventListener('result',e=>{
    /*上略...*/
    const transcript = Array.from(e.results)
    .map(result => result[0])
    .map(result => result.transcript).join('');
    
    p.textContent = transcript; // will be overwrite
});

在第一次語音辨識結束之後,接下來它不會主動開始下一次的語音辨識。

我們可以在recognition註冊end event,當語音辨識結束就觸發下一次語音辨識開始(recognition.start)。

recognition.addEventListener('end',recognition.start);

目前為止我們還有一個問題沒有解決,就是在每一次的語音辨識的過程,<p>標籤會不斷被覆寫,因為我們沒有為每一次的語音辨識建立自己的<p>標籤,一直都是在改原來的那個。

下面我們用 if 判斷當這次的語音辨識結束,就新增一個新的<p>標籤到<div class="words">上,這樣就不會有覆寫的狀況。

recognition.addEventListener('result',e=>{
    /*上略...*/
    if(e.results[0].isFinal){
      p = document.createElement('p');
      words.appendChild(p);
    }
});

最後,我們還可以加點有趣的東西,當語音辨識到特定內容,就在 console 上印出提示。

recognition.addEventListener('result',e=>{
    //上略...//
    if(transcript.includes('rainning day')){
      console.log('It is a bad weather.');
    }

    console.log(transcript);
});

補充資料:

使用 Web Speech API
SpeechRecognition

範例網頁請點此


上一篇
Day 19 - Unreal Webcam Fun [更新]
下一篇
Day 21 - Geolocation based Speedometer and Compass
系列文
JS30 學習日記30

尚未有邦友留言

立即登入留言