JS 30 是由加拿大的全端工程師 Wes Bos 免費提供的 JavaScript 簡單應用課程,課程主打 No Frameworks
、No Compilers
、No Libraries
、No 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 來得輕鬆很多吧~~~
div(.words
) 用來放經過語音辨識後所產生的文字段落(<p>~</p>
)。
<div class="words" contenteditable></div>
SpeechRecognition
本身是一個在 browser 之下的 global variable
,但是因為瀏覽器支援性的問題,我們在 Chrome 必須使用帶有前綴字的webkitSpeechRecognition
。
window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
首先建立一個SpeechRecognition
物件recognition
來幫我們做語音辨識。
設定SpeechRecognition
物件的屬性interimResults
為 true,它就會將現在辨識到的所有內容都回傳,如果是 false 的話,則只有停止說話才回傳內容。
設定SpeechRecognition
物件的屬性lang
為en-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
所構成的陣列,最後將陣列內的元素 transcript
用join()
串成一個大字串。
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