使用 Web Speech API

Web Speech API 提供兩個獨立的功能領域:語音識別和語音合成(也稱為文字轉語音,或 TTS),這為可訪問性和控制開闢了有趣的可能性。本文介紹了這兩個領域,並提供了演示。

語音識別

語音識別涉及從裝置的麥克風(或從音軌)接收音訊,然後由語音識別服務進行檢查。當服務成功識別一個單詞或短語時,它會返回一個文字字串(或字串列表),您可以用於啟動進一步的操作。

Web Speech API 為此提供了一個主控制器介面 — SpeechRecognition — 以及幾個用於表示結果的相關介面。

通常,使用者裝置上可用的語音識別系統用於語音識別。大多數現代作業系統都有一個用於發出語音命令的語音識別系統,例如 macOS 上的 Dictation 或 Windows 上的 Copilot

預設情況下,在網頁上使用語音識別涉及基於伺服器的識別引擎。您的音訊將傳送到 Web 服務進行識別處理,因此它無法離線工作。

為了提高隱私和效能,您可以指定在裝置上執行語音識別。這確保了音訊和轉錄的語音都不會發送到第三方服務進行處理。我們將在裝置上的語音識別部分更詳細地介紹裝置上的功能。

演示

為了演示如何使用語音識別,我們建立了一個名為語音顏色轉換器的示例應用程式。按下開始識別按鈕後,說出一個 HTML 顏色關鍵字。應用程式的背景顏色將變為該顏色。

Screenshot of our demo app called speech color changer. It invites the user to press the button and say a color. It turns the background of the app to that color. In this case, it has turned the background color to pink.

要執行演示,請在支援的瀏覽器中導航到即時演示 URL

HTML 和 CSS

應用程式的 HTML 和 CSS 是基礎的。有一個標題,一個說明段落(<p>),一個控制<button>,以及一個輸出段落,我們在此顯示診斷訊息,包括我們應用程式識別的單詞。

html
<h1>Speech color changer</h1>

<p class="hints"></p>

<button>Start recognition</button>

<p class="output"><em>...diagnostic messages</em></p>

CSS 提供了基本的響應式樣式,使其在不同裝置上看起來都很好。

JavaScript

讓我們更詳細地瞭解一下 JavaScript。

帶字首的屬性

一些瀏覽器目前支援帶字首屬性的語音識別。因此,在我們的程式碼開頭,我們包含這些行以同時支援帶字首的屬性和無字首版本

js
const SpeechRecognition =
  window.SpeechRecognition || window.webkitSpeechRecognition;
const SpeechRecognitionEvent =
  window.SpeechRecognitionEvent || window.webkitSpeechRecognitionEvent;

顏色列表

我們程式碼的下一部分定義了一些示例顏色,我們將其列印到 UI 中,以便使用者瞭解要說什麼

js
const colors = [
  "aqua",
  "azure",
  "beige",
  "bisque",
  "black",
  "blue",
  "brown",
  "chocolate",
  "coral",
  // …
];

建立語音識別例項

接下來,我們定義一個語音識別例項來控制應用程式中的識別。我們透過使用SpeechRecognition()建構函式來做到這一點。

js
const recognition = new SpeechRecognition();

然後我們設定識別例項的一些屬性

  • SpeechRecognition.continuous:控制是連續捕獲結果 (true) 還是每次識別開始時只捕獲一次 (false)。
  • SpeechRecognition.lang:設定識別的語言。明確設定此項是推薦的最佳實踐。
  • SpeechRecognition.interimResults:定義語音識別系統是應返回中間結果還是僅返回最終結果。對於此演示,最終結果就足夠了。
  • SpeechRecognition.maxAlternatives:設定每個結果應返回的備選潛在匹配的數量。這有時可能很有用,例如如果結果不完全清晰並且您希望顯示一個備選列表供使用者選擇。但此演示不需要,所以我們只指定一個(反正這也是預設值)。
js
recognition.continuous = false;
recognition.lang = "en-US";
recognition.interimResults = false;
recognition.maxAlternatives = 1;

啟動語音識別

在獲取到輸出段落、<html> 元素、指令段落和 <button> 的引用後,我們實現了一個 onclick 處理程式。當用戶按下按鈕時,語音識別服務透過呼叫 SpeechRecognition.start() 開始。我們還使用了 forEach() 方法輸出彩色指示器,顯示使用者可以嘗試說出哪些顏色。

js
const diagnostic = document.querySelector(".output");
const bg = document.querySelector("html");
const hints = document.querySelector(".hints");
const startBtn = document.querySelector("button");

const colorHTML = colors
  .map((v) => `<span style="background-color:${v};">${v}</span>`)
  .join("");
hints.innerHTML = `Press the button then say a color to change the background color of the app. Try ${colorHTML}.`;

startBtn.onclick = () => {
  recognition.start();
  console.log("Ready to receive a color command.");
};

接收和處理結果

語音識別啟動後,將有幾個事件處理程式可用,您可以使用它們來檢索結果和其他相關資訊(請參閱 事件 以瞭解 SpeechRecognition)。最常見的是 result 事件,它在成功接收結果後觸發

js
recognition.onresult = (event) => {
  const color = event.results[0][0].transcript;
  diagnostic.textContent = `Result received: ${color}.`;
  bg.style.backgroundColor = color;
  console.log(`Confidence: ${event.results[0][0].confidence}`);
};

第二行有點複雜,所以我們在這裡解釋每個部分

  • SpeechRecognitionEvent.results 屬性返回一個 SpeechRecognitionResultList 物件,其中包含 SpeechRecognitionResult 物件。它有一個 getter,因此可以像陣列一樣訪問 — 第一個 [0] 返回位置 0SpeechRecognitionResult
  • 每個 SpeechRecognitionResult 物件又包含 SpeechRecognitionAlternative 物件,每個物件代表一個單獨的識別詞。這些也都有 getter,因此可以像陣列一樣訪問 — 第二個 [0] 返回位置 0SpeechRecognitionAlternative
  • SpeechRecognitionAlternativetranscript 屬性返回一個包含識別文字的字串。此值隨後用於將背景顏色設定為識別到的顏色,並將其作為診斷訊息報告到 UI 中。

我們還使用 speechend 事件在識別到一個單詞後停止語音識別服務(使用 SpeechRecognition.stop()

js
recognition.onspeechend = () => {
  recognition.stop();
};

處理錯誤和未識別的語音

最後兩個處理程式涵蓋了未識別口語詞或識別發生錯誤的情況。nomatch 事件應該處理第一種情況,儘管在大多數情況下,識別引擎會返回一些東西,即使它難以理解

js
recognition.onnomatch = (event) => {
  diagnostic.textContent = "I didn't recognize that color.";
};

error 事件處理識別出現實際錯誤的情況 — SpeechRecognitionErrorEvent.error 屬性包含返回的錯誤

js
recognition.onerror = (event) => {
  diagnostic.textContent = `Error occurred in recognition: ${event.error}`;
};

裝置上的語音識別

語音識別通常使用線上服務進行。這意味著音訊錄音會發送到伺服器進行處理,然後結果返回到瀏覽器。這有兩個問題

  • 隱私:許多使用者不願意將他們的語音傳送到伺服器。
  • 效能:將資料傳送到伺服器進行每一次識別可能會在更密集型應用程式中降低效能,並且您的應用程式無法離線工作。

為了緩解這些問題,Web Speech API 允許您指定語音識別應由瀏覽器在裝置上處理。這需要為每種要識別的語言一次性下載語言包;一旦安裝,該功能將可離線使用。

本節解釋如何使用裝置上的語音識別。

演示

為了演示裝置上的語音識別,我們建立了一個名為裝置上的語音顏色轉換器的示例應用程式(執行即時演示)。

此演示與前面討論的線上語音顏色轉換器演示的工作方式非常相似,區別如下。

指定裝置上識別

要指定您想使用瀏覽器的裝置上處理,請在開始任何語音識別之前將 SpeechRecognition.processLocally 屬性設定為 true(預設值為 false

js
recognition.processLocally = true;

檢查可用性並安裝語言包

為了使裝置上的語音識別工作,瀏覽器必須安裝您要識別的語言的語言包。如果在指定 processLocally = true 後執行 start() 方法,但未安裝正確的語言包,則函式呼叫將失敗並出現 language-not-supported 錯誤。

要安裝正確的語言包,請確保遵循以下兩個步驟

  1. 檢查使用者裝置上是否提供語言包:這透過 SpeechRecognition.available() 靜態方法進行處理。
  2. 如果語言包不可用,則安裝語言包:這透過 SpeechRecognition.install() 靜態方法進行處理。

這些步驟在應用程式控制 <button> 上的以下 click 事件處理程式中處理

js
startBtn.addEventListener("click", () => {
  // check availability of target language
  SpeechRecognition.available({ langs: ["en-US"], processLocally: true }).then(
    (result) => {
      if (result === "unavailable") {
        diagnostic.textContent = `en-US is not available to download at this time. Sorry!`;
      } else if (result === "available") {
        recognition.start();
        console.log("Ready to receive a color command.");
      } else {
        diagnostic.textContent = `en-US language pack is downloading...`;
        SpeechRecognition.install({
          langs: ["en-US"],
          processLocally: true,
        }).then((result) => {
          if (result) {
            diagnostic.textContent = `en-US language pack downloaded. Start recognition again.`;
          } else {
            diagnostic.textContent = `en-US language pack failed to download. Try again later.`;
          }
        });
      }
    },
  );
});

available() 方法接受一個包含兩個屬性的選項物件

  • 一個 langs 陣列,其中包含要檢查可用性的語言。
  • 一個 processLocally 布林值,指定是僅在裝置上檢查語言的可用性 (true) 還是 透過本地或基於伺服器的識別服務檢查 (false,預設值)。

執行時,此方法返回一個 Promise,該 Promise 解析為一個列舉值,指示指定語言的可用性。在我們的演示中,我們測試了三種情況

  • 如果結果值為 unavailable,則表示沒有合適的語言包可供下載。我們還會向輸出列印一條相應的訊息。
  • 如果結果值為 available,則表示語言包可在本地使用,因此可以開始識別。在這種情況下,我們執行 start() 並在應用程式準備好接收語音時將訊息記錄到控制檯。
  • 如果該值是其他值(downloadabledownloading),我們列印一條診斷訊息,通知使用者語言包下載正在開始,然後執行 install() 方法來處理下載。

install() 方法的工作方式與 available() 方法類似,只是其選項物件只接受 langs 陣列。執行時,它開始下載 langs 中指示的所有語言的語言包,並返回一個 Promise,該 Promise 解析為一個布林值,指示指定的語言包是否成功下載並安裝 (true) 或未成功 (false)。

對於此演示,我們列印一條診斷訊息以指示成功和失敗情況。在一個更完整的應用程式中,您可能會在下載過程中停用控制元件,並在 Promise 解析後重新啟用它們。

許可權策略整合

available()install() 方法的使用受 on-device-speech-recognition Permissions-Policy 的控制。具體來說,如果定義的策略阻止使用,則任何呼叫這些方法的嘗試都將失敗。

on-device-speech-recognition 的預設允許列表值為 self。這意味著您無需擔心調整策略,除非您嘗試在嵌入式跨源文件中使用這些方法或希望明確停用它們的使用。

無字首的 Web Speech API

在原始的語音顏色轉換器演示中,我們包含了額外的行來處理僅支援帶有供應商字首屬性的 Web Speech API 的瀏覽器(有關詳細資訊,請參閱帶字首的屬性部分)。

在演示的裝置上版本中,不需要字首處理程式碼,因為支援此功能的實現沒有字首。

語音識別中的上下文偏差

有時語音識別服務會無法正確識別特定的單詞或短語。這最常發生在特定領域的術語(例如醫學或科學詞彙)、專有名詞、不常見的短語或聽起來與其他單詞相似並可能被錯誤識別的單詞。

例如,在測試期間,我們發現我們的裝置上的語音顏色轉換器難以識別顏色 azure — 它總是返回類似“as you”的結果。其他經常被誤識別的顏色包括 khaki (“car key”)、tanthistle (“this all”)。

為了緩解此類問題,Web Speech API 允許您向識別引擎提供提示,以突出更可能被說出的短語,並且引擎應該偏向於這些短語。這使得這些單詞和短語更有可能被正確識別。

您可以透過將 SpeechRecognitionPhrase 物件的陣列設定為 SpeechRecognition.phrases 屬性的值來做到這一點。每個 SpeechRecognitionPhrase 物件包含

  • 一個 phrase 屬性,它是一個包含您要提升的單詞或短語的字串。
  • 一個 boost 屬性,它是一個介於 0.010.0(包括)之間的浮點數,用於設定您要應用於該單詞或短語的提升量。值越高,單詞或短語被識別的可能性越大。

在我們的“裝置上的語音顏色轉換器”演示中,我們透過建立要提升的短語陣列及其提升值來處理此問題

js
const phraseData = [
  { phrase: "azure", boost: 5.0 },
  { phrase: "khaki", boost: 3.0 },
  { phrase: "tan", boost: 2.0 },
];

這些需要表示為 SpeechRecognitionPhrase 物件的 ObservableArray。我們透過對映原始陣列來處理此問題,使用 SpeechRecognitionPhrase() 建構函式將每個陣列元素轉換為 SpeechRecognitionPhrase 物件

js
const phraseObjects = phraseData.map(
  (p) => new SpeechRecognitionPhrase(p.phrase, p.boost),
);

建立 SpeechRecognition 例項後,我們透過將 phraseObjects 陣列設定為 SpeechRecognition.phrases 屬性的值來新增我們的上下文偏差短語

js
recognition.phrases = phraseObjects;

短語陣列可以像普通的 JavaScript 陣列一樣進行修改,例如透過動態地向其推送新短語

js
recognition.phrases.push(new SpeechRecognitionPhrase("thistle", 5.0));

有了這段程式碼,我們發現有問題的顏色關鍵字比以前識別得更準確了。

語音合成

語音合成(又稱文字轉語音,或 TTS)涉及將應用程式中包含的文字合成為語音,並透過裝置的揚聲器或音訊輸出連線播放。

Web Speech API 為此提供了一個主控制器介面 — SpeechSynthesis — 以及一些密切相關的介面,用於表示要合成的文字(稱為話語)、用於話語的語音等。同樣,大多數作業系統都具有某種語音合成系統,該 API 將根據可用性用於此任務。

演示

為了演示如何使用 Web 語音合成,我們建立了一個名為語音合成器的示例應用程式。它有一個輸入欄位,用於輸入要合成的文字。您可以調整語速和音高,還可以從下拉選單中選擇一個語音用於朗讀文字。輸入文字後,按 Enter/Return 或單擊播放按鈕即可聽到文字朗讀。

UI of an app called speak easy synthesis. It has an input field in which to input text to be synthesized, slider controls to change the rate and pitch of the speech, and a drop down menu to choose between different voices.

要執行演示,請在支援的瀏覽器中導航到即時演示 URL

HTML 和 CSS

此應用程式的 HTML 和 CSS 都非常基礎。有一個標題、一些使用說明和一個帶有一些基本控制元件的表單。<select> 元素最初是空的;它透過 JavaScript(稍後介紹)填充了 <option> 元素。

html
<h1>Speech synthesizer</h1>

<p>
  Enter some text in the input below and press return to hear it. Change voices
  using the dropdown menu.
</p>

<form>
  <input type="text" class="txt" />
  <div>
    <label for="rate">Rate</label
    ><input type="range" min="0.5" max="2" value="1" step="0.1" id="rate" />
    <div class="rate-value">1</div>
    <div class="clearfix"></div>
  </div>
  <div>
    <label for="pitch">Pitch</label
    ><input type="range" min="0" max="2" value="1" step="0.1" id="pitch" />
    <div class="pitch-value">1</div>
    <div class="clearfix"></div>
  </div>
  <select></select>
</form>

JavaScript

讓我們研究一下為這個應用程式提供動力的 JavaScript。

設定變數

首先,我們捕獲了 UI 中涉及的所有 DOM 元素的引用,但更有趣的是,我們捕獲了 Window.speechSynthesis 的引用。這是 API 的入口點——它返回 SpeechSynthesis 的例項,即 Web 語音合成的控制器介面。

js
const synth = window.speechSynthesis;

const inputForm = document.querySelector("form");
const inputTxt = document.querySelector(".txt");
const voiceSelect = document.querySelector("select");

const pitch = document.querySelector("#pitch");
const pitchValue = document.querySelector(".pitch-value");
const rate = document.querySelector("#rate");
const rateValue = document.querySelector(".rate-value");

const voices = [];

填充 select 元素

為了用裝置可用的不同語音選項填充 <select> 元素,我們編寫了一個 populateVoiceList() 函式。我們首先呼叫 SpeechSynthesis.getVoices(),它返回所有可用語音的列表,這些語音由 SpeechSynthesisVoice 物件表示。然後我們遍歷此列表 — 對於每個語音,我們建立一個 <option> 元素,將其文字內容設定為顯示語音的名稱(從 SpeechSynthesisVoice.name 獲取)、語音的語言(從 SpeechSynthesisVoice.lang 獲取),如果語音是合成引擎的預設語音,則顯示 -- DEFAULT(透過檢查 SpeechSynthesisVoice.default 是否返回 true 來檢查)。

我們還為每個選項建立了 data- 屬性,其中包含關聯語音的名稱和語言,以便我們以後可以輕鬆獲取它們,然後將選項作為 select 的子元素追加。

js
function populateVoiceList() {
  voices = synth.getVoices();

  for (const voice of voices) {
    const option = document.createElement("option");
    option.textContent = `${voice.name} (${voice.lang})`;

    if (voice.default) {
      option.textContent += " — DEFAULT";
    }

    option.setAttribute("data-lang", voice.lang);
    option.setAttribute("data-name", voice.name);
    voiceSelect.appendChild(option);
  }
}

較舊的瀏覽器不支援 voiceschanged 事件,並且在觸發 SpeechSynthesis.getVoices() 時只返回語音列表。而在其他瀏覽器(如 Chrome)上,您必須等到事件觸發後才能填充列表。為了同時支援這兩種情況,我們按如下所示執行函式

js
populateVoiceList();
if (speechSynthesis.onvoiceschanged !== undefined) {
  speechSynthesis.onvoiceschanged = populateVoiceList;
}

朗讀輸入的文字

接下來,我們建立一個事件處理程式,以開始朗讀文字欄位中輸入的文字。我們正在使用表單上的 onsubmit 處理程式,以便在按下 Enter/Return 時執行此操作。我們首先使用其建構函式建立一個新的 SpeechSynthesisUtterance() 例項——這將文字輸入的值作為引數傳遞。

接下來,我們需要確定要使用哪種語音。我們使用 HTMLSelectElementselectedOptions 屬性來返回當前選定的 <option> 元素。然後,我們使用此元素的 data-name 屬性,找到名稱與此屬性值匹配的 SpeechSynthesisVoice 物件。我們將匹配的語音物件設定為 SpeechSynthesisUtterance.voice 屬性的值。

最後,我們將 SpeechSynthesisUtterance.pitchSpeechSynthesisUtterance.rate 設定為相關範圍表單元素的值。然後,在完成所有必要的準備工作後,我們透過呼叫 SpeechSynthesis.speak() 來開始朗讀,將 SpeechSynthesisUtterance 例項作為引數傳遞給它。

js
inputForm.onsubmit = (event) => {
  event.preventDefault();

  const utterThis = new SpeechSynthesisUtterance(inputTxt.value);
  const selectedOption =
    voiceSelect.selectedOptions[0].getAttribute("data-name");
  for (const voice of voices) {
    if (voice.name === selectedOption) {
      utterThis.voice = voice;
    }
  }
  utterThis.pitch = pitch.value;
  utterThis.rate = rate.value;
  synth.speak(utterThis);
  utterThis.onpause = (event) => {
    const char = event.utterance.text.charAt(event.charIndex);
    console.log(
      `Speech paused at character ${event.charIndex} of "${event.utterance.text}", which is "${char}".`,
    );
  };
  inputTxt.blur();
};

在處理程式的最後一部分,我們包含一個 pause 事件,以演示如何有效地利用 SpeechSynthesisEvent。當呼叫 SpeechSynthesis.pause() 時,它會返回一條訊息,報告語音暫停時的字元編號和名稱。

最後,我們在文字輸入上呼叫 blur()。這主要是為了在 Firefox OS 上隱藏鍵盤。

更新顯示的音高和語速值

程式碼的最後一部分會在每次滑塊位置移動時更新 UI 中顯示的 pitch/rate 值。

js
pitch.onchange = () => {
  pitchValue.textContent = pitch.value;
};

rate.onchange = () => {
  rateValue.textContent = rate.value;
};