讓你的瀏覽器變成Siri一樣的語音助手

飛灰同學發表於2021-03-15

最近業餘時間瀏覽技術文章的時候,看到了一篇關於語音朗讀的文章:Use JavaScript to Make Your Browser Speak(用Javascript讓你的瀏覽器說話),文章中提到可以通過speechSynthesis實現讓現代瀏覽器語音朗讀指定的內容,這激發了我的好奇心去探索了一番,於是便有了下文。

本文提及的程式碼片段執行需要音訊輸出裝置(如音響、耳機)和音訊輸入裝置(如麥克風)等硬體裝置的支援。

語音朗讀 speechSynthesis

嚴格意義來上,實現語音朗讀的功能需要speechSynthesisSpeechSynthesisUtterance兩個方法共同協作完成。SpeechSynthesisUtterance告訴瀏覽器需要語音朗讀的內容,而speechSynthesis將需要朗讀的內容合成為音訊內容,由音響等一類的音訊輸出裝置進行播放。

支援朗讀的語言

speechSynthesis的實現是通過瀏覽器底層呼叫了作業系統的相關介面實現的語音朗讀。因此語言的支援度可能因為瀏覽器和作業系統的不同而不同,可以通過speechSynthesis.getVoices()獲取當前裝置支援的朗讀語言。

不過,多數支援speechSynthesis方法的瀏覽器一般都支援中文內容的朗讀。而且這樣也帶來了一個好處:可以離線使用,也可以通過SpeechSynthesisVoice.localService方法替換成自己的音源。

程式碼示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <title>語音朗讀</title>
</head>
<body>
  <button type="button" onclick="speak('你好,李煥英')">說話</button>
  <script type="text/javascript">
    // 語音朗讀功能
    function speak(sentence) {
      // 生成需要語音朗讀的內容
      const utterance = new SpeechSynthesisUtterance(sentence)
      // 由瀏覽器發起語音朗讀的請求
      window.speechSynthesis.speak(utterance)
    }
  </script>
</body>
</html>

相容性

speechSynthesis的相容性

排除已不再維護的IE瀏覽器,PC幾個主流的瀏覽器和IOS均已支援,安卓支援性有好有壞,需要做好相容處理。

M71提案

不過值得注意的一點是,當Chrome上線相關功能之後,發現語音朗讀的功能被一些網站濫用,於是Chrome在M71提案(提案連結)之後,將觸發機制變更成:需要使用者自行觸發事件才能進行語音朗讀。

我個人測試了Chrome、Edge兩款瀏覽器,Chrome無法通過直接呼叫和通過建立DOM節點觸發click事件間接呼叫,而Edge在寫文時(2021-03-13)兩種方法都可以呼叫;因此如果有相關業務需求時,建議做好相應的相容準備。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <title>語音朗讀</title>
</head>
<body>
  <script type="text/javascript">
    function speak(sentence) {
      const utterance = new SpeechSynthesisUtterance(sentence);
      window.speechSynthesis.speak(utterance);
    };
    // 在M71提案後,Chrome禁止了自動呼叫語言朗讀的機制
    // Edge在2021-03-13時可以直接呼叫,其他瀏覽器跟程式度未知
    speak('直接呼叫');
	
    const button = document.createElement('button');
    button.onclick = () => speak('建立節點呼叫');
    document.body.appendChild(button);
    button.click();
    setTimeout(() => document.body.removeChild(button), 50);
  </script>
</body>
</html>

測試完這些程式碼的時候,腦海中忽然閃過一個想法:既然都有語音朗讀了,那有沒有語音識別的方法呢?於是我查了MDN及一些相關的資料,發現還真有語音識別的方法:SpeechRecognition文件連結)。

語音識別 SpeechRecognition

跟語音朗讀speechSynthesis本地朗讀不同,SpeechRecognition在MDN文件(點選此處)中明確提出了是基於伺服器的語音識別,也就是說必須聯網才能識別。

On some browsers, like Chrome, using Speech Recognition on a web page involves a server-based recognition engine. Your audio is sent to a web service for recognition processing, so it won't work offline.

在某些瀏覽器(例如Chrome)上,網頁上使用的語音識別基於伺服器的識別引擎。您的音訊將傳送到網路服務以進行識別處理,因此它將無法離線工作。

如果你使用的瀏覽器是Chrome,語音識別的服務端則是由谷歌提供的,如果不用梯子的話會直接提示結束。不過好在提供了SpeechRecognition.serviceURI用來自定義語音識別的提供商,算是一種權宜之計吧。

程式碼示例

<!DOCTYPE html>
<head lang="zh-CN">
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <title>語音朗讀</title>
</head>
<body>
  <button type="button" onclick="recognition.start()">點選識別語音</button>
  <button type="button" onclick="recognition.stop()">結束語音識別</button>
  <p id="status"></p>
  <p id="output"></p>
  <script type="text/javascript">
    // 目前只有Chrome和Edge支援該特性,在使用時需要加私有化字首
    const SpeechRecognition = window.webkitSpeechRecognition
    const recognition = new SpeechRecognition()
    const output = document.getElementById("output")
    const status = document.getElementById("status")

    // 語音識別開始的鉤子
    recognition.onstart = function() {
      output.innerText = ''
      status.innerText = '語音識別開始'
    }
    // 如果沒有聲音則結束的鉤子
    recognition.onspeechend = function() {
      status.innerText = "語音識別結束"
      recognition.stop()
    }
    // 識別錯誤的鉤子
    recognition.onerror = function({ error }) {
      const errorMessage = {
        'not-speech': '未檢測到聲源',
        'not-allowed': '未檢測到麥克風裝置或未允許瀏覽器使用麥克風'
      }
      status.innerText = errorMessage[ error ] || '語音識別錯誤'
    }
    // 識別結果的鉤子,
    // 可通過interimResults控制是否實時識別,maxAlternatives設定識別結果的返回數量
    recognition.onresult = function({ results }) {
      const { transcript, confidence } = results[0][0]
      output.innerText = `識別的內容:${ transcript },識別率:${ (confidence * 100).toFixed(2) }%`
    }
  </script>
</body>
</html>

識別準確度

我個人拿Chrome瀏覽器嘗試了一下午,當識別率低於90%的時候,基本就會出現丟字的情況,比如我用較快的語氣說了一句“今天天氣怎麼樣”,最後識別的結果是“怎麼樣”。當環境比較嘈雜的時候,基本識別率就沒有高於70%的時候,說一句“你好”,得到的結果要麼直接報錯要麼不搭邊,對長難句的識別率也不怎麼高。

低識別率會丟字

如果希望達到一個比較高的識別率,則需要安靜的環境,簡單的語句,說話清晰響亮緩慢(類似於播音腔)。

高識別率

相容性

這算是一個相當新的api,新到什麼程度呢?

新到MDN文件建立SpeechRecognition相關詞條的時間在2020年9月15日,截止我寫文的2021年03月13日剛好半年的時間。

雖然新意味著相容性差,但這也從某種層次上說明,未來Web前端的發展方向也許真的可能替代原生應用。

SpeechRecognition的相容性

從相容性來看,PC只有Chrome和Edge(僅限Chromium核心)這兩款瀏覽器支援,移動端幾乎全軍覆沒,只有少數幾個比較新的版本支援,但不確定對整體的相容性如何。

經過實際的測試,Chrome支援英文和中文的語音識別,而Edge會提示language-not-supported的錯誤,更改html上的語言仍然報錯,懷疑需要更改電腦的系統語言才能解決(未確定)。

Edge提示language-not-supported的錯誤

語音識別 + 語音朗讀 = 語音助手

市面上比較常見的各類語音助手(比如Siri),在前端的邏輯都比較簡單,一般情況下如果只處理本地化的配置,如設定鬧鐘、詢問日期等功能,核心功能主要分為語音識別和語音朗讀兩部分,當瀏覽器提供了這兩項能力的時候,便已滿足了語音助手的條件。

於是我嘗試寫了一個很簡單的DEMO,將兩者合二為一實現了一個語音助手。

程式碼示例

<!DOCTYPE html>
<head lang="zh-CN">
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <title>語音助手</title>
</head>
<body>
  <button type="button" onclick="recognition.start()">點選識別語音</button>
  <p id="status"></p>
  <p id="output"></p>
  <script type="text/javascript">
    function speak(sentence) {
      const utterance = new SpeechSynthesisUtterance(sentence)
      window.speechSynthesis.speak(utterance)
    }
    // 目前只有Chrome和Edge支援該特性,在使用時需要加私有化字首
    const SpeechRecognition = window.webkitSpeechRecognition
    const recognition = new SpeechRecognition()
    const output = document.getElementById("output")
    const status = document.getElementById("status")
    // 語音識別開始的鉤子
    recognition.onstart = function() {
      output.innerText = ''
      status.innerText = '語音識別開始'
    }
    // 如果沒有聲音則結束的鉤子
    recognition.onspeechend = function() {
      recognition.stop()
    }
    // 識別錯誤的鉤子
    recognition.onerror = function({ error }) {
      const errorMessage = {
        'not-speech': '未檢測到聲源',
        'not-allowed': '未檢測到麥克風裝置或未允許瀏覽器使用麥克風'
      }
      status.innerText = errorMessage[ error ] || '語音識別錯誤'
    }
    // 識別結果的鉤子
    recognition.onresult = function({ results }) {
      // 設定一些比較簡單的回覆
      const answers = {
        '今天是星期幾': '今天是星期六',
        '今天天氣怎麼樣': '今天天氣晴朗'
      }
      const { transcript, confidence } = results[0][0]
      // 設定一個閾值分別處理
      if( confidence * 100 >= 90 ) {
        speak(answers[transcript] || '這件事我還不知道,換個問題吧')
        status.innerText = `語音回覆的內容:${ answers[transcript] || '這件事我還不知道,換個問題吧' }`
      } else {
        speak('我好像沒聽明白')
        status.innerText = `我好像沒聽明白`
      }
    }
  </script>
</body>
</html>

總結

目前大部分的瀏覽器都還沒相容語音識別的特性,但在可預見的未來,不僅主流瀏覽器會支援語音識別的特性,也會有一些第三方服務商通過瀏覽器原生的方法提供類似的服務,同時會有更多類似的能力出現在Web平臺上。

相關文章