语音命令不仅适用于 Google 或 Alexa 等助手。它们还可以添加到您的移动和桌面应用程序中,为您的最终用户提供额外的功能甚至乐趣。向您的应用程序添加语音命令或语音搜索非常容易。在本文中,我们将使用 Web Speech API 来构建语音控制的图书搜索应用程序。
Web Speech API 简介
在开始之前,请务必注意 Web Speech API 当前的浏览器支持有限。要继续阅读本文,您需要使用受支持的浏览器。
主要浏览器对 mdn-api__SpeechRecognition 功能的支持数据。
首先,让我们看看启动和运行Web Speech API 【https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API】是多么容易。要开始使用 Speech API,我们只需要实例化一个新SpeechRecognition
类以允许我们听取用户的嗓音:
const SpeechRecognition = webkitSpeechRecognition; const speech = new SpeechRecognition(); speech.onresult = event => { console.log(event); }; speech.start();
我们首先创建一个SpeechRecognition
常量,它等于全局浏览器供应商前缀webkitSpeechRecognition
。在此之后,我们可以创建一个语音变量,它将成为我们SpeechRecognition
类的新实例。这将允许我们开始聆听用户的讲话。为了能够处理来自用户语音的结果,我们需要创建一个在用户停止说话时触发的事件监听器。最后,我们start
在类实例上调用该函数。
首次运行此代码时,将提示用户允许访问麦克风。这是浏览器为防止不必要的窥探而实施的安全检查。一旦用户接受,他们就可以开始发言,并且不会再次要求他们对该域的许可。用户停止说话后,onresult
将触发事件处理函数。
该onresult
事件传递一个SpeechRecognitionEvent
对象,该对象由一个SpeechRecognitionResultList
结果数组组成。该SpeechRecognitionResultList
对象包含SpeechRecognitionResult
的对象。数组中的第一项返回一个SpeechRecognitionResult
对象,该对象包含另一个数组。此数组中的第一项包含用户所说内容的转录。
上面的代码可以从 Chrome DevTools 或普通的 JavaScript 文件中运行。现在我们已经了解了基础知识,让我们看看将其构建到 React 应用程序中。通过 Chrome DevTools 控制台运行时,我们可以看到以下结果:
在 React 中使用 Web Speech
使用我们已经学到的知识,将 Web Speech API 添加到 React 应用程序是一个简单的过程。我们唯一需要处理的问题是 React 组件的生命周期。首先,让我们按照入门指南使用Create React App创建一个新项目。这假设您的机器上安装了Node:
npx create-react-app book-voice-search cd book-voice-search npm start
接下来,我们用App
下面的代码替换文件来定义一个基本的 React 组件。然后我们可以给它添加一些语音逻辑:
// App.js import React from 'react'; const App = () => { return ( <div> Example component </div> ); }; export default App;
这个简单的组件渲染了一个 div,里面有一些文本。现在我们可以开始将我们的语音逻辑添加到组件中。我们想要构建一个创建语音实例的组件,然后在 React 生命周期中使用它。当 React 组件第一次渲染时,我们想要创建语音实例,开始收听结果,并为用户提供一种启动语音识别的方法。我们首先需要导入一些 React 钩子、一些 CSS样式和供用户单击的麦克风图像:
// App.js import { useState, useEffect } from "react"; import "./index.css"; import Mic from "./microphone-black-shape.svg";
在此之后,我们将创建我们的语音实例。在查看 Web Speech API 的基础知识时,我们可以使用我们之前学到的知识。我们必须对粘贴到浏览器开发人员工具中的原始代码进行一些更改。首先,我们通过添加浏览器支持检测使代码更加健壮。我们可以通过检查webkitSpeechRecognition
窗口对象上是否存在该类来做到这一点。这将告诉我们浏览器是否知道我们要使用的 API。
然后我们将continuous
设置更改为 true。这将配置语音识别 API 以继续收听。在我们的第一个示例中,这默认为 false,这意味着当用户停止说话时,onresult
事件处理程序将触发。但是,由于我们允许用户控制他们希望站点停止收听的时间,因此我们使用continuous
允许用户想说多久就说多久:
// App.js let speech; if (window.webkitSpeechRecognition) { // eslint-disable-next-line const SpeechRecognition = webkitSpeechRecognition; speech = new SpeechRecognition(); speech.continuous = true; } else { speech = null; } const App = () => { ... };
现在我们已经设置了语音识别代码,我们可以开始在 React 组件中使用它。正如我们之前看到的,我们导入了两个 React 钩子—— useState
anduseEffect
钩子。这些将允许我们添加onresult
事件侦听器并将用户记录存储到状态,以便我们可以在 UI 上显示它:
// App.js const App = () => { const [isListening, setIsListening] = useState(false); const = useState(""); const listen = () => { setIsListening(!isListening); if (isListening) { speech.stop(); } else { speech.start(); } }; useEffect(() => { //handle if the browser does not support the Speech API if (!speech) { return; } speech.onresult = event => { setText(event.results[event.results.length - 1][0].transcript); }; }, []); return ( <> <div className="app"> <h2>Book Voice Search</h2> <h3>Click the Mic and say an author's name</h3> <div> <img className={`microphone ${isListening && "isListening"}`} src={Mic} alt="microphone" onClick={listen} /> </div> <p>{text}</p> </div> </> ); } export default App;
在我们的组件中,我们首先声明两个状态变量——一个用于保存用户语音的转录文本,另一个用于确定我们的应用程序是否在听用户。我们调用 ReactuseState
钩子,传递false
for的默认值isListening
和文本的空字符串。这些值稍后将根据用户的交互在组件中更新。
设置状态后,我们创建一个函数,当用户单击麦克风图像时将触发该函数。这将检查应用程序当前是否正在侦听。如果是,我们停止语音识别;否则,我们启动它。此功能后来添加到onclick
麦克风图像中。
然后我们需要添加我们的事件侦听器来捕获来自用户的结果。我们只需要创建一次这个事件侦听器,并且只有在 UI 呈现时才需要它。所以我们可以使用useEffect
钩子来捕获组件何时挂载并创建我们的onresult
事件。我们还将一个空数组传递给该useEffect
函数,以便它只运行一次。
最后,我们可以渲染出允许用户开始说话并查看文本结果所需的 UI 元素。
自定义可重用 React 语音钩子
我们现在有一个可以运行的 React 应用程序,它可以听取用户的声音并在屏幕上显示该文本。但是,我们可以通过创建我们自己的自定义 React 钩子来更进一步,我们可以在应用程序中重用它来收听用户的语音输入。
首先,让我们创建一个名为 .js 的新 JavaScript 文件useVoice.js
。对于任何自定义 React 钩子,最好遵循文件名模式useHookName.js
。这使它们在查看项目文件时脱颖而出。然后我们可以开始导入我们之前在示例组件中使用的所有需要的内置 React 钩子:
// useVoice.js import { useState, useEffect } from 'react'; let speech; if (window.webkitSpeechRecognition) { // eslint-disable-next-line const SpeechRecognition = SpeechRecognition || webkitSpeechRecognition; speech = new SpeechRecognition(); speech.continuous = true; } else { speech = null; }
这与我们之前在 React 组件中使用的代码相同。在此之后,我们声明了一个名为 的新函数useVoice
。我们匹配文件名,这也是自定义 React 钩子中的常见做法:
// useVoice.js const useVoice = () => { const = useState(''); const [isListening, setIsListening] = useState(false); const listen = () => { setIsListening(!isListening); if (isListening) { speech.stop(); } else { speech.start(); } }; useEffect(() => { if (!speech) { return; } speech.onresult = event => { setText(event.results[event.results.length - 1][0].transcript); setIsListening(false); speech.stop(); }; }, []) return { text, isListening, listen, voiceSupported: speech !== null }; } export { useVoice, };
在useVoice
函数内部,我们正在执行多项任务。与我们的组件示例类似,我们创建了两个状态项——isListening
标志和文本状态。然后我们listen
使用与之前相同的逻辑再次创建该函数,使用效果挂钩来设置onresult
事件侦听器。
最后,我们从函数中返回一个对象。该对象允许我们的自定义钩子提供使用用户语音作为文本的任何组件。我们还返回一个变量,它可以告诉消费组件浏览器是否支持 Web Speech API,我们稍后将在应用程序中使用它。在文件的末尾,我们导出函数以便可以使用。
现在让我们回到我们的App.js
文件并开始使用我们的自定义钩子。我们可以从删除以下内容开始:
-
SpeechRecognition
类实例 -
导入
useState
-
对于状态变量
isListening
和text
-
该
listen
功能 -
该
useEffect
添加的onresult
事件监听器
然后我们可以导入我们自定义的useVoice
React 钩子:
// App.js import { useVoice } from './useVoice';
我们开始像使用内置的 React 钩子一样使用它。我们调用useVoice
函数并解构生成的对象:
// App.js const { text, isListening, listen, voiceSupported, } = useVoice();
导入这个自定义钩子后,我们不需要对组件进行任何更改,因为我们重用了所有状态变量名称和函数调用。生成的 App.js 应如下所示:
// App.js import React from 'react'; import { useVoice } from './useVoice'; import Mic from './microphone-black-shape.svg'; const App = () => { const { text, isListening, listen, voiceSupported, } = useVoice(); if (!voiceSupported) { return ( <div className="app"> <h1> Voice recognition is not supported by your browser, please retry with a supported browser e.g. Chrome </h1> </div> ); } return ( <> <div className="app"> <h2>Book Voice Search</h2> <h3>Click the Mic and say an author's name</h3> <div> <img className={`microphone ${isListening && "isListening"}`} src={Mic} alt="microphone" onClick={listen} /> </div> <p>{text}</p> </div> </> ); } export default App;
我们现在以一种允许我们跨组件或应用程序共享 Web Speech API 逻辑的方式构建了我们的应用程序。我们还能够检测浏览器是否支持 Web Speech API 并返回消息而不是损坏的应用程序。
这也从我们的组件中删除了逻辑,使其保持干净且更易于维护。但我们不要停在这里。让我们为我们的应用程序添加更多功能,因为我们目前只是在聆听用户的声音并显示它。
图书语音搜索
使用我们迄今为止所学和构建的内容,让我们构建一个图书搜索应用程序,它允许用户说出他们最喜欢的作者的名字并获取图书列表。
首先,我们需要创建第二个自定义挂钩,以允许我们搜索库 API。让我们首先创建一个名为useBookFetch.js
. 在这个文件中,我们将遵循useVoice
钩子中的相同模式。我们将为状态和效果导入我们的 React 钩子。然后我们可以开始构建我们的自定义钩子:
// useBookFetch.js import { useEffect, useState } from 'react'; const useBookFetch = () => { const [authorBooks, setAuthorBooks] = useState([]); const [isFetchingBooks, setIsFetchingBooks] = useState(false); const fetchBooksByAuthor = author => { setIsFetchingBooks(true); fetch(`https://openlibrary.org/search.json?author=${author}`) .then(res => res.json()) .then(res => { setAuthorBooks(res.docs.map(book => { return { title: book.title } })) setIsFetchingBooks(false); }); } return { authorBooks, fetchBooksByAuthor, isFetchingBooks, }; }; export { useBookFetch, }
让我们分解一下我们在这个新的自定义钩子中所做的事情。我们首先创建两个状态项。authorBooks
默认为空数组,最终将保存所选作者的书籍列表。isFetchingBooks
是一个标志,它会告诉我们的消费组件是否正在进行获取作者书籍的网络调用。
然后我们声明一个函数,我们可以使用作者姓名调用该函数,它将对打开的图书馆进行 fetch 调用以获取所提供作者的所有书籍。在then
fetch的最后,我们映射每个结果并获得书名。然后我们最终返回一个带有authorBooks
状态的对象、表示我们正在取书的标志和fetchBooksByAuthor
函数。
让我们跳回我们的App.js
文件并以与导入useBookFetch
钩子相同的方式导入useVoice
钩子。我们可以调用这个钩子并解构这些值并开始在我们的组件中使用它们:
// App.js const { authorBooks, isFetchingBooks, fetchBooksByAuthor } = useBookFetch(); useEffect(() => { if (text !== "") { fetchBooksByAuthor(text); } }, );
我们可以使用useEffect
钩子来观察text
变量的变化。当用户的语音文本更改时,这将自动获取作者的书籍。如果文本为空,我们不会尝试获取操作。当我们第一次渲染组件时,这可以防止不必要的获取。对App.js
组件的最后更改是添加逻辑以呈现作者书籍或显示获取消息:
// App.js { isFetchingBooks ? 'fetching books....' : <ul> { authorBooks.map((book, index) => { return ( <li key={index}> <span> {book.title} </span> </li> ); }) } </ul> }
最终App.js
文件应如下所示:
// App.js import React, { useEffect } from "react"; import "./index.css"; import Mic from "./microphone-black-shape.svg"; import { useVoice } from "./useVoice"; import { useBookFetch } from "./useBookFetch"; const App = () => { const { text, isListening, listen, voiceSupported } = useVoice(); const { authorBooks, isFetchingBooks, fetchBooksByAuthor } = useBookFetch(); useEffect(() => { if (text !== "") { fetchBooksByAuthor(text); } }, ); if (!voiceSupported) { return ( <div className="app"> <h1> Voice recognition is not supported by your browser, please retry with a supported browser e.g. Chrome </h1> </div> ); } return ( <> <div className="app"> <h2>Book Voice Search</h2> <h3>Click the Mic and say an autors name</h3> <div> <img className={`microphone ${isListening && "isListening"}`} src={Mic} alt="microphone" onClick={listen} /> </div> <p>{text}</p> {isFetchingBooks ? ( "fetching books...." ) : ( <ul> {authorBooks.map((book, index) => { return ( <li key={index}> <span>{book.title}</span> </li> ); })} </ul> )} </div> <div className="icon-reg"> Icons made by{" "} <a href="https://www.flaticon.com/authors/dave-gandy" title="Dave Gandy" > Dave Gandy </a>{" "} from{" "} <a href="https://www.flaticon.com/" title="Flaticon"> www.flaticon.com </a> </div> </> ); }; export default App;
结论
这只是如何使用 Web Speech API 向应用程序添加附加功能的一个简单示例,但可能性是无限的。API 有更多我们在此未介绍的选项,例如提供语法列表,以便我们可以限制用户可以提供的语音输入。该 API 仍处于试验阶段,但希望能在更多浏览器中使用,以实现易于实现的语音交互。
至于完整的代码,大家可以加QQ群,@群主获取。