使用 Web 语音 API

Web 语音 API 提供两个不同的功能区域——语音识别和语音合成(也称为文本到语音或 tts)——为可访问性和控制机制打开了新的可能性。本文对这两个领域进行了简单的介绍,并附带演示。

语音识别

语音识别涉及通过设备的麦克风接收语音,然后由语音识别服务根据语法列表(基本上是您想要在特定应用程序中识别的词汇表)进行检查。当成功识别出一个单词或短语时,它将作为文本字符串返回为结果(或结果列表),并且可以作为结果启动进一步的操作。

Web 语音 API 为此提供了一个主控制器接口——SpeechRecognition——以及用于表示语法、结果等的许多密切相关的接口。通常,设备上可用的默认语音识别系统将用于语音识别——大多数现代操作系统都具有用于发出语音命令的语音识别系统。例如,macOS 上的听写、iOS 上的 Siri、Windows 10 上的 Cortana、Android 语音等等。

注意:在某些浏览器(如 Chrome)中,在网页上使用语音识别涉及基于服务器的识别引擎。您的音频将发送到 Web 服务进行识别处理,因此它无法离线工作。

演示

为了展示 Web 语音识别的简单用法,我们编写了一个名为语音颜色更改器的演示。当屏幕被点击/单击时,你可以说一个 HTML 颜色关键词,应用程序的背景颜色将改变为该颜色。

The UI of an app titled Speech Color changer. It invites the user to tap the screen and say a color, and then it turns the background of the app that color. In this case it has turned the background red.

要运行演示,请在支持的移动浏览器(如 Chrome)中导航到实时演示 URL

HTML 和 CSS

该应用程序的 HTML 和 CSS 非常简单。我们有一个标题、一个说明段落和一个 div,我们在其中输出诊断消息。

html
<h1>Speech color changer</h1>
<p>Tap/click then say a color to change the background color of the app.</p>
<div>
  <p class="output"><em>…diagnostic messages</em></p>
</div>

CSS 提供了非常简单的响应式样式,使其在各种设备上看起来都很好。

JavaScript

让我们更详细地了解一下 JavaScript 代码。

带前缀的属性

浏览器目前使用带前缀的属性来支持语音识别。因此,在我们代码的开头,我们包含了以下几行,以允许使用带前缀的属性和将来可能支持的无前缀版本

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

语法

我们代码的下一部分定义了我们希望应用程序识别的语法。以下变量被定义为保存我们的语法

js
const colors = [
  "aqua",
  "azure",
  "beige",
  "bisque",
  "black",
  "blue",
  "brown",
  "chocolate",
  "coral" /* … */,
];
const grammar = `#JSGF V1.0; grammar colors; public <color> = ${colors.join(
  " | ",
)};`;

使用的语法格式是JSpeech 语法格式 (JSGF)——您可以在之前的链接到其规范中找到更多相关信息。不过,现在让我们快速浏览一下。

  • 这些行用分号分隔,就像 JavaScript 中一样。
  • 第一行——#JSGF V1.0;——声明了使用的格式和版本。这始终需要包含在最前面。
  • 第二行表示我们要识别的术语类型。public 声明它是一个公共规则,尖括号中的字符串定义了该术语的识别名称(color),而等号后面的项目列表是将被识别和接受为该术语的适当值的备选值。注意,每个值都用管道符分隔。
  • 您可以根据上述结构在单独的行上定义任意多个术语,并包含相当复杂的语法定义。对于这个简单的演示,我们只是保持简单。

将语法插入到我们的语音识别中

接下来要做的是定义一个语音识别实例来控制应用程序的识别。这是使用 SpeechRecognition() 构造函数完成的。我们还创建了一个新的语音语法列表来保存我们的语法,使用 SpeechGrammarList() 构造函数。

js
const recognition = new SpeechRecognition();
const speechRecognitionList = new SpeechGrammarList();

我们使用 SpeechGrammarList.addFromString() 方法将我们的 grammar 添加到列表中。它接受我们要添加的字符串作为参数,以及一个可选的权重值,该值指定此语法相对于列表中其他语法的重要性(可以是 0 到 1 之间的任何值)。添加的语法在列表中以 SpeechGrammar 对象实例的形式提供。

js
speechRecognitionList.addFromString(grammar, 1);

然后,我们通过将列表设置为 SpeechRecognition.grammars 属性的值,将 SpeechGrammarList 添加到语音识别实例中。在我们继续之前,我们还设置了识别实例的几个其他属性

  • SpeechRecognition.continuous:控制是否捕获连续结果(true),或者每次启动识别时只捕获单个结果(false)。
  • SpeechRecognition.lang:设置识别的语言。设置它是一个好习惯,因此建议这样做。
  • SpeechRecognition.interimResults:定义语音识别系统是否应该返回中间结果,或者只返回最终结果。对于这个简单的演示,最终结果已经足够了。
  • SpeechRecognition.maxAlternatives:设置每个结果应该返回的备选潜在匹配项的数量。这在某些情况下可能有用,例如,如果结果不够清晰,并且您想显示一个备选值列表,以便用户从中选择正确的选项。但是,对于这个简单的演示,它不是必需的,因此我们只指定了一个(实际上是默认值)。
js
recognition.grammars = speechRecognitionList;
recognition.continuous = false;
recognition.lang = "en-US";
recognition.interimResults = false;
recognition.maxAlternatives = 1;

启动语音识别

在获取对输出 <div> 和 HTML 元素(以便我们稍后可以输出诊断消息并更新应用程序背景颜色)的引用之后,我们实现了一个 onclick 处理程序,以便当屏幕被点击/单击时,语音识别服务将启动。这是通过调用 SpeechRecognition.start() 来实现的。forEach() 方法用于输出彩色指示器,显示尝试说出哪些颜色。

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

let colorHTML = "";
colors.forEach((color, i) => {
  console.log(color, i);
  colorHTML += `<span style="background-color:${color};"> ${color} </span>`;
});
hints.innerHTML = `Tap or click then say a color to change the background color of the app. Try ${colorHTML}.`;

document.body.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] 返回位置为 0 的 SpeechRecognitionResult。每个 SpeechRecognitionResult 对象都包含 SpeechRecognitionAlternative 对象,其中包含单个识别的单词。这些对象也有 getter,因此可以像数组一样访问它们——因此第二个 [0] 返回位置为 0 的 SpeechRecognitionAlternative。然后,我们返回它的 transcript 属性以获取一个包含单个识别结果的字符串作为字符串,将背景颜色设置为该颜色,并将识别到的颜色作为诊断消息报告到 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}`;
};

语音合成

语音合成(也称为文本到语音或 TTS)涉及接收应用程序中包含的文本并将其合成到语音中,然后通过设备的扬声器或音频输出连接播放。

Web 语音 API 为此提供了一个主控制器接口——SpeechSynthesis——以及用于表示要合成的文本(称为话语)、要用于话语的语音等的许多密切相关的接口。同样,大多数操作系统都有一些语音合成系统,API 将根据可用性使用这些系统来执行此任务。

演示

为了展示 Web 语音合成的简单用法,我们提供了一个名为Speak easy 合成的演示。这包括一组用于输入要合成的文本以及设置朗读文本时要使用的音调、语速和语音的表单控件。输入文本后,您可以按 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 的实例,它是网络语音合成的控制器接口。

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);

在处理程序的最后部分,我们包含了一个 pause 事件,以演示如何将 SpeechSynthesisEvent 用于实际应用。当调用 SpeechSynthesis.pause() 时,它将返回一条消息,报告语音暂停时的字符数和名称。

js
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}".`,
  );
};

最后,我们对文本输入调用 blur()。这主要是为了隐藏 Firefox OS 上的键盘。

js
  inputTxt.blur();
}

更新显示的音高和速率值

代码的最后一部分更新 UI 中显示的 pitch/rate 值,每次移动滑块位置时。

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

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