原文:Introducing the HTML5 Web Speech API
协议:CC BY-NC-SA 4.0
八、项目:查找和播放音乐
我喜欢听音乐——当我花几个小时开发代码或写书的时候,听听你最喜欢的艺术家的音乐是有道理的。只要我手里也有一杯酒,我就很开心。但是我跑题了。
在数字化我的全部音乐收藏和发现在线音乐流的乐趣之前,我常常费力地翻阅数百张 CD。我们只能说这是一个折衷的收藏;它是什么并不重要——它是一张 CD,然后点击播放。自从改用 Spotify 以来,那些日子已经一去不复返了——找到我的音乐变得容易多了,我也不用担心空间问题了!
自从改用 Spotify 后,我开始思考,有没有可能用我的声音来控制它?在我的研究过程中,我还没有发现任何人做过类似的事情,至少在 Spotify 上;这是否意味着没有人设法做到这一点,或有这样做的愿望?我想知道。由于我热衷于划船,探索未知的领域,我想,为什么不试一试呢?
为我们的项目设置背景
在本章的过程中,我们将组装一个快速而肮脏的应用,从我们的浏览器中使用 Spotify API 来播放选定的专辑。作为其中的一部分,我们将增加以下功能:
-
该应用将(几乎)完全通过语音控制,使用语音识别 API——唯一不可控的部分将是初始授权过程和从真正的 Spotify 客户端的第一次播放(更多信息请见下文)。
-
我们将能够执行基本任务,例如播放或暂停音乐,向前或向后跳过一首歌曲,以及将专辑添加到 Spotify 中您保存的专辑列表中。
-
我们将显示一个曲目列表,以及每个曲目的长度,加上同一艺术家的专辑列表-后者将包括专辑名称和图像。
-
我们将提供一个选项来搜索相似名字的艺术家-结果将显示他们的名字和 Spotify ID,我们也将使用它们来获得他们的专辑。
这只是我们使用 Spotify API 所能实现的一小部分——我们还可以做一大堆其他事情,但空间限制意味着我们无法涵盖所有内容!
希望这会给你一个平衡的混合功能,我们可以用我们的声音控制,以及如何在这种情况下使用 Spotify API 的感觉;作为预览,你可以在图 8-1 中看到一张完整演示的截图。
图 8-1
我们完成演示的预览
好吧,我们继续。我相信你会问一个问题:当我们有很多其他服务可以使用时,比如 Deezer,Google Play Music,甚至亚马逊音乐,为什么还要选择 Spotify?这是一个很好的问题,所以我们来看看选择使用 Spotify 的原因。
为什么选择 Spotify?
当与在线音乐流媒体服务合作时,我们有一个相当健康的选项列表可供选择——有些你可能从 Spotify 或 Deezer 等电视广告中了解到,其他则来自已经涉足这一领域的老牌公司,如亚马逊或谷歌 Play Music。对于这一章,我选择使用 Spotify,有几个原因:
-
与许多服务一样,你总是需要注册——Spotify 的帐户要求很低,并且很容易用代码建立一个基本的认证系统(正如我们将在后面的演示中看到的)。
-
使用 Spotify 的一个重要原因是,我已经是付费用户了——当然,还有其他渠道提供类似的服务,但如果你已经使用了市场领导者,那么使用它们就没有意义了。他们提供各种各样的音乐,尽管分辨率不高,但这一限制并不重要,因为这个练习是关于用我们的声音远程控制服务,而不是服务提供的声音质量水平!
-
随着 Deezer、Spotify 和 Amazon Music 等公司的出现,在线音乐流媒体正在发展成为一个健康的市场,但它们中的大多数都有一个共同点——它们似乎都很难使用标准的客户端技术与每个服务的 API 进行交互!唯一的例外是 Spotify——你很快就会看到,我们将利用第三方包装库来帮助运行我们的代码,这是 Spotify 可用的有限数量之一。(事实上,有一个服务甚至不让我登录他们的 API 区域……)
好吧,我们继续。现在我们已经讨论了我们将使用的音乐服务,我们需要探索如何构建我们的演示。除了 Spotify 和语音 API 之外,我们还将使用许多工具(当然!),所以让我们深入了解一下我们将使用什么来更详细地构建我们的演示。
构建我们的演示
为了构建我们的演示,除了 Spotify 和 Speech APIs 之外,我们还将使用几个工具。我选择使用的方法如下:
-
Vue.js 用于授权框架代码——我可以使用 React 之类的工具,但是这增加了不必要的复杂性。Vue.js 让事情变得美好而简单,默认情况下不需要使用 Node.js 等服务器端工具来展示使用 Spotify 的基本操作。授权访问 Spotify 的一个很好的例子是李·马汀在
https://codepen.io/leemartin/pen/EOxxYR
进行的 CodePen 演示——我们将以此为基础进行演示。我们稍后将更多地讨论这个过程的授权部分。
-
我们可以直接与 Spotify 的 API 交互,但为了简单起见,我们将使用 José Perez 的包装库,该库可从
https://github.com/JMPerez/spotify-web-api-js
获得。 -
jQuery——这纯粹是为了方便;在理想的情况下,我们应该重构代码来使用 Vue 或者普通的 JavaScript!为了透明起见,我们将使用 jQuery 的最新版本,在撰写本文时是 3.4.1。可以使用 jQuery 的其他版本,尽管您需要测试以确保您的代码仍然工作。
好了,有了我们的主要工具,我们现在可以开始编码了!但是——我听到你说——授权是怎么回事?是的,与任何 API 一样,我们需要成为授权用户才能访问服务;服务提供商需要一种方法来跟踪使用情况,并为所有用户维持适当的服务水平。尽管这并不影响我们对语音 API 的使用,但它仍然是我们演示的一个关键部分,所以让我们深入研究一下,更详细地看一下我们的选项。
授权我们的演示
当使用 Spotify API 时,我们应用的一个关键部分将是我们和 Spotify 之间的授权过程;这是为了允许注册访问的 API,所以我们可以流音乐。
这是一个由两部分组成的过程。第一个,我们向 Spotify 注册应用的地方,我们稍后会介绍;现在,让我们假设这种情况已经发生,并看看在与 Spotify 合作时授权可能发生的各种方式。
选择方法
假设我们已经向 Spotify 注册了我们的应用,有三种方法可以授权我们使用 Spotify API。它们如下:
-
将我们自己授权为用户,可以定期刷新——使用授权码方法。
-
为用户设置临时授权–使用隐式授权方法。
-
为应用设置授权,授权可以定期刷新-使用客户端凭证流方法。
我们可以在表 8-1 中看到这些对比。
表 8-1
授权访问 Spotify API 的方法
|流动
|
访问用户资源
|
需要密钥(服务器端)
|
访问令牌刷新
|
| — | — | — | — |
| 授权代码 | 是 | 是 | 是 |
| 客户端凭据 | 不 | 是 | 不 |
| 隐性资助 | 是 | 不 | 不 |
来源:Spotify 开发者门户
出于演示的目的,我们将使用隐式授权流选项——这是为完全用 JavaScript 编写的客户端设计的,不需要使用服务器端代码来操作。确实有一个以spotify-web-api-node
形式存在的服务器端选项,但是为了展示语音 API 是如何工作的,在服务器端运行它只是增加了一层不必要的复杂性。毕竟,为什么要把事情复杂化,对吗?
使用我们选择的方法的含义
对于我们的演示,我们选择使用隐式授权流方法来授权访问——这个标准是由互联网工程任务组或 IEFT 创建的 RFC-6749。在我们的情况下,使用这种方法是最好的选择,原因如下:
-
隐式授权流适用于完全使用 JavaScript 实现并在资源所有者的浏览器中运行的客户端。
-
您不需要任何服务器端代码来使用它——这消除了对复杂的服务器端工具的需要,例如 Node.js
-
改进了请求的速率限制,但是没有提供刷新令牌。
你可以在 IETF 网站的
https://tools.ietf.org/html/rfc6749#section-4.2
上看到关于这种方法如何工作的更深入的讨论。
这对我们意味着什么?我们可以直接访问 Spotify 账户服务,使用 API 提供的访问令牌,该令牌以类似于 https://accounts.spotify.com/authorize
的东西开始。
整个过程是在客户端执行的,不涉及密钥,但是访问令牌是短暂的,需要手动刷新,并且当它们过期时没有选项来延长它们。完整的请求将包括查询字符串中的参数,我们可以在表 8-2 中看到这些参数(以及我们演示的后面部分)。
表 8-2
隐式流授权所需的各种属性
|询问参数
|
价值
|
| — | — |
| 客户端 id | *必选。*Spotify 在注册时向您提供的客户 ID。 |
| 响应类型 | *必选。*设置为“令牌” |
| 重定向 uri | *必选。*用户授予/拒绝权限后重定向到的注册 URI。 |
| 状态 | 可选,但强烈推荐。状态对于关联请求和响应非常有用——使用值可以额外保证连接是真正的请求。 |
| 范围 | *可选。*用空格分隔的作用域列表。 |
| 显示对话框 | *可选。*如果应用已经获得批准,则强制用户再次批准该应用。 |
来源:Spotify 开发者门户
除了看到它的运行,理解它如何组合在一起的最好方法是把它看作一个流程图——我们可以在图 8-2 中看到流程是如何运行的。
图 8-2
我们的演示源的授权过程:Spotify 开发者门户
尽管选择一种方法会有一些限制,但理解这一点很重要——毕竟,我们不可能免费得到任何东西,没有可能影响我们做事方式的东西!
幸运的是,这些限制并不太严重,如果我们决定使用 Spotify 提供的其他授权方法之一,它们的影响可能会减少。不过,那是另一个故事了。与此同时,让我们更详细地看看约束可能如何影响我们的演示。
使用这种方法的限制
虽然我们选择的方法使事情变得简单,并且最适合我们的需求,但仍然有一些约束需要我们注意,这意味着我们不能完全通过语音控制一切。让我们来看看它们是什么:
-
在运行我们的演示之前,我们需要启动 Spotify 并运行一首歌曲几秒钟(这是我们必须定期做的事情)。如果我们根本不运行 Spotify,你会看到控制台出现错误,专辑也不会播放。
-
我们需要使用 Spotify 的浏览器版本来进行演示;如果您尝试使用桌面应用,您会发现这两者互不影响,并且您还可能会发现桌面应用中播放的另一张专辑与您的演示中播放的专辑无关!
-
当运行演示的授权部分时,我们必须为 Spotify 单击 Accept 按钮以允许访问。不幸的是,这在我们的演示中不能通过语音实现,因此我们不能完全用声音控制事情!
好了,现在我们已经介绍了授权访问 Spotify 的基础知识,是时候开始开发代码了。我知道在我们这么做之前,这可能看起来像是一个漫长的等待,但是 Spotify API 并不像它应该的那样简单;我们已经讨论了一些关于如何访问 API 的要点。
现在我们已经讨论了这个问题,我们可以开始设置我们的应用了;第一项任务是建立一个集成,以便 Spotify 将来自我们应用的呼叫识别为正版,并提供适当的内容。
设置先决条件
当使用 API 时,我们经常不得不建立某种形式的帐户或集成——这是必要的,但也是重要的,以便服务提供商可以管理需求并保证注册用户的访问安全。
使用 Spotify 也是一样——我们演示的第一步是设置一个集成,所以让我们深入了解一下为我们的应用创建集成所需的步骤。
Dealing With Prerequisites
要设置集成,请执行以下步骤:
-
继续填写所需的详细信息(用红星表示)–您可以使用以下内容作为向导来帮助您完成向导:(我们目前正在以纯粹的开发能力工作,但是如果您决定进行商业化,请确保您相应地设置了一个集成。需要注意的是,一旦集成被启用,您就不能编辑该选项。
-
你在开发商业整合吗?–否
-
我知道这个应用不是用于商业用途的-否
-
我知道我不能迁移…–不
-
我理解并同意…–否
-
-
点击提交。此时,将显示详细信息,并向您展示您的应用的仪表盘。
-
您将会看到一个标记为“显示客户端机密”的链接,以及您的客户端 ID。单击该链接,然后记下这两个 id,因为我们将在稍后的演示中使用这两个 id。
-
我们将从下载本书附带的代码下载副本开始——继续将
spotify
文件夹保存到我们的项目区域。这将包含相关的样式和 Vue.js 和 jQuery 库,供我们使用。 -
接下来,我们需要在
https://developer.spotify.com/dashboard/
登录 Spotify 的开发者仪表盘——为此,你需要创建一个免费账户,或者如果你已经是该服务的现有订户,也可以使用现有账户。 -
Once logged in, click Create a Client ID – you will see a modal appear, similar to the (partial) screenshot shown in Figure 8-3.
图 8-3
在显示器上创建客户端 ID 模式
我们现在准备开始开发代码了!在我们这样做之前,有几件事需要注意:
-
我们将利用 Flaticon 网站上的 Spotify 图标——这纯粹是为了给我们的网站创建一个 favicon。我使用的图标可以从
https://www.flaticon.com/free-icon/spotify-logo_49097
获得。 -
位于
https://favicon.io/favicon-converter/
的 Favicon 网站非常便于设置正确显示 Favicon 所需的代码——我们将把从该网站生成的代码整合到我们的应用中。
添加一个图标完全是自愿的;你当然可以决定不使用它,它不会影响你的演示如何运行!我这样做纯粹是为了防止我们的应用抛出找不到合适图标的错误。
您可能会发现,如果您在每次演示后都测试代码,并不是所有的代码都能正常工作。不要惊慌;这是意料之中的!我们覆盖了很多代码,所以我们需要分部分来做——在最终演示结束时,一切都会迎刃而解。
在这个阶段,具备了所有的先决条件,我们现在可以开始编写代码了。有相当多的代码需要处理,所以我们将分阶段完成——第一项任务是建立基本的授权框架,这样我们就可以开始添加代码来与 Spotify 的 API 进行交互。
创建框架
我们现在真的到了可以写一些代码的阶段了!我们的应用集成已经设置好并准备好使用,我们可以将注意力转向为我们的应用设置代码。在这个项目的过程中,有相当多的代码需要完成,所以我把它分成了三个阶段的过程;第一阶段负责基本的授权过程。
为了避免重复发明轮子,我将使用李·马汀的 CodePen 演示(你可以在 https://codepen.io/leemartin/pen/EOxxYR?editors=1010
看到原文)。这使用 Vue.js 框架来布局代码。如果您不熟悉这个框架,也不用担心——在基本层面上,它保持了标记和 JavaScript 代码之间的分离。练习结束后,我们将详细讲解每一部分。
Setting Up Our Skeleton Code
要设置我们的授权框架并准备好投入使用,请执行以下步骤:
-
我们首先打开
index.html
的副本,然后向下滚动(或寻找)标记为<!—INSERT CODE HERE -->
的评论。 -
我们有相当多的代码要添加,所以我们将分阶段完成——首先,删除注释,然后添加几个空行。
-
接下来,继续插入这个块——这将负责我们的语音工具周围的标记:
<main id="app"> <h2>Introducing HTML5 Speech API: <br>Controlling Spotify by Voice</h2> <template v-if="me"> <div id="speech"> <button> <i class="fa fa-microphone"></i> Click and talk to me! </button> <p class="output">You said: <br><strong class="output_result"></strong></p> <span class="voice">Spoken voice: US English</span> <p>Responses:</p> <div class="response"> <span class="output_log"></span> </div> </div>
-
我们还有几个模块要介绍——我们需要添加的下一个模块将通过提供图像、艺术家和曲目数量等细节来处理我们正在播放的专辑的显示。在前一个代码块之后留出一行空白,然后添加以下代码:
<div id="currentalbum"> <span class="albumimage"><img src="img/100.png" /></span> <span class="albumartist"></span> <span class="albumname"></span> <span class="trackcount"></span> <span class="year"></span> <span class="albumtype"></span> <span class="albumID"></span> <span class="artistID"></span> </div>
-
授权过程不需要下一个块,但现在添加它更容易——这个块将设置控制音乐播放所需的按钮。继续添加下面的代码,在前一个代码块之后留下一个空行:
<button @click="playmusic"><i class="fa fa-microphone"></i> Play music</button> <button @click="pausemusic"><i class="fa fa-microphone"></i> Pause music</button> <button @click="playnexttrack"><i class="fa fa-microphone"></i> Go forward 1 track</button> <button @click="playprevioustrack"><i class="fa fa-microphone"></i> Go back 1 track</button> <button @click="addtosavedalbums"><i class="fa fa-microphone"></i> Add to saved albums</button>
-
我们正在取得良好的进展。下一部分为曲目列表设置了占位符,用于我们在演示中播放的专辑。继续添加以下代码行,首先在前一个代码块之后留下一个空行:
<div id="albumlist"> <p>Track listing:</p> <ul></ul> </div>
-
下一部分负责显示同一艺术家的其他专辑——在前一部分之后留出一个空行,然后插入以下代码:
<div id="otheralbums"> <span>Other albums by Artist:</span><button @click="getalbumsbyartist"><i class="fa fa-microphone"></i> Get Albums</button> <ul></ul> </div>
-
我们还有一个显示其他同名艺术家的部分——为此,在前面的块之后添加以下代码:
<div id="artistlisting"> Search for Artist: <input v-model="searchartist"><button @click="searchartistsbyname"><i class="fa fa-microphone"></i> Search</button> <span>Chosen artist: {{searchartist}}</span> <div id="artistlist"><ul></ul></div> </div>
-
我们几乎完成了标记。还有两个部分需要添加:一个确认你已经登录的隐藏信息块和我们的 Vue 模板的结束代码。继续在上一步之后添加以下代码,中间留一个空行:
<div id="info">{{ me }}</div> </template> <template v-else> <button @click="login">Login with Spotify</button> </template> </main>
-
在这一点上,保存文件-让它暂时打开。我们将稍作休息,但很快将继续代码。
我们现在已经准备好了我们的标记,可以使用了——但是它不会做很多事情,因为我们还没有添加脚本代码来使它可以操作。
我们将很快添加这一点。请随意去喝杯咖啡或饮料,休息一下,因为我们还有很多代码要添加!假设您准备好了,让我们继续演示的下一部分,添加授权代码。
从 Spotify 获得授权
在我们的下一个演示中,我们应该开始看到事情的发生——这是我们添加代码来启动授权请求并希望得到批准的地方!好吧,这听起来比实际情况更复杂,因为这一切都发生在后台,只需要我们点击一下按钮。为了理解我的意思,让我们在下一个演示中加入代码。
Making Our Authentication Process Operational
要让我们的演示授权使用 Spotify,请执行以下步骤:
-
本演示的第一项任务是在上一个演示的结束标签
</main>
后添加几个空行,然后插入以下代码——这将为我们提供基本的 Vue 对象,我们将使用该对象启动对 Spotify 的授权:<script> const app = new Vue({ el: '#app', data() { return { client_id: 'bf253330696448f696dc45889f3fd61c', scopes: 'user-top-read playlist-read-collaborative playlist-read-collaborative playlist-modify-public playlist-read-private playlist-modify-private streaming app-remote-control user-modify-playback-state user-read-currently-playing user-read-playback-state user-library-modify', redirect_uri: 'https://speech/spotify', me: null, albumname: 'Not listed', searchartist: null, createplist: null } }, methods: { <!—ADD IN ADDITIONAL METHODS HERE --> } }) </script>
-
我们需要添加更多的配置功能——首先是实际调用 Spotify 来请求授权。继续在 methods 对象中添加以下代码,替换标记为
<!-- ADD IN ADDITIONAL METHODS HERE -->
的注释:login() { let popup = window.open(`https://accounts.spotify.com/authorize?client_id=${this.client_id}&response_type=token&redirect_uri=${this.redirect_uri}&scope=${this.scopes}&show_dialog=true`, 'Login with Spotify', 'width=600,height=800') window.spotifyCallback = (payload) => { popup.close() fetch('https://api.spotify.com/v1/me', { headers: { 'Authorization': `Bearer ${payload}` } }).then(response => { return response.json() }).then(data => { this.me = data }) spotifyApi = new SpotifyWebApi({ clientId: '<ADD IN CLIENT ID HERE>', clientSecret: '<ADD IN CLIENT SECRET HERE>' }); spotifyApi.setAccessToken(payload); } },
您将看到几行与 clientID 和 clientSecret 值相关的代码;这里需要它们是有原因的,尽管它们不用于授权过程。在这个演示之后,我们将讨论它的重要性。
-
我们还需要添加一个对象——这将触发调用,向 Spotify 发起请求。为此,在我们的 Vue 对象的结束})之前,紧接着前一个块的结束}之后添加以下代码:
mounted() { this.token = window.location.hash.substr(1). split('&')[0].split("=")[1] if (this.token) { // alert(this.token) window.opener.spotifyCallback(this.token) } }
-
Go ahead and save the file, but leave it open (or minimized) – we’re now ready to test our work! For this, browse to
https://speech/spotify/
. If all is well, we should see the initial login button displayed, as indicated in Figure 8-4.图 8-4
我们演示中 Spotify 的初始登录按钮
如果我们单击 Login with Spotify 按钮,我们应该会看到一个弹出窗口,请求访问我们的演示以使用 Spotify API。图 8-5 显示了该请求的(部分)截图。
图 8-5
授权我们演示的(部分)请求
破解密码
在上两次演示过程中,我们已经建立了授权访问 Spotify API 所需的基本框架;这暴露了一些有用的指针,所以让我们更详细地看一下代码,以了解它们是如何组合在一起的。
第一个演示的第一部分很简单;在本章的后面,我们设置了所有的标记,包括操作语音功能所需的标记。唯一需要注意的是在演示的第 5 步中使用了@
符号;这些是对我们在第二个演示中创建的 Vue 对象中的函数的调用(它们的操作方式与普通 JavaScript 中的onclick="...."
类似)。我们还使用了双花括号——这些只是占位符,它们由 Vue 中捆绑的 Handlebars 库代替实际值。
这两个演示的真正关键在第二部分——对于外行来说,Vue 的工作原理是创建(并初始化)一个配置对象。我们通过将 app const 定义为 Vue 的一个新实例来单击 off,并向其中传递一个目标元素("#app"
);然后,我们在数据对象中定义一些值——分别是我们的客户端 ID、允许的范围、用于授权的重定向 URL,以及用于演示的一些其他占位符。
接下来,我们创建一个方法对象,在其中我们设置了login()
对象(或函数);这定义了一个包含我们用来请求访问的 URL 的弹出变量。一旦 Vue 实例在演示中被挂载(),这将启动一个回调,调用login()
对象。这会向 Spotify 发出请求,然后 Spotify 会相应地进行处理。假设它成功了,我们得到了一个响应——它隐藏在演示中,因为我们不需要一直显示它。同时,我们创建了一个新的spotify-web-api
库实例,在其中定义了 clientID 和 clientSecret 值,为我们开始使用 Spotify 做好了准备。
来自 Spotify 的流媒体内容
随着我们的授权流程现在开始运作,是时候添加代码来从 Spotify 流式传输内容了。为此,我们将利用 José Perez 的spotify-web-api-js
包装器库;这使用基于承诺的语法包装了对每个 Spotify 端点的相关调用。
剧透这是一个冗长的演示。如果你需要休息,请随时暂停!
为了提醒您完成后它会是什么样子,我们可以在图 8-6 中看到我们完成的应用的部分截图,一旦我们点击登录 Spotify 并确定以允许授权。
图 8-6
预览我们的 Spotify 演示…
好吧,让我们开始我们的代码。
Making Use Of The Spotify Api
要将 Spotify 中的内容流式传输到我们的演示中,请按照以下步骤操作:
-
返回到我们在之前的练习中打开的
index.html
文件,然后向下滚动到开始的<script>
标记,并在它的正下方添加这行代码,如下所示:<script> var spotifyApi, albumIDplaying, artistimage;
-
我们需要添加一个助手函数来将时间从毫秒转换成更合理的形式。为此,将此代码添加到上一步中变量声明的下方,中间留一个空行:
function msToMinAndSec(millis) { var minutes = Math.floor(millis / 60000); var seconds = ((millis % 60000) / 1000).toFixed(0); return minutes + ":" + (seconds < 10 ? '0' : ") + seconds; }
-
接下来,回头看看我们创建的
login()
对象。您应该会看到类似这样的内容: -
我们需要从我们的 Spotify 帐户中添加客户端 ID 和客户端 secrets 值;为此,继续用您在本章前面创建的客户机和秘密 id 替换注释。
-
下一个任务是开始添加在我们的演示中操作各种特性的函数。首先是添加一些东西,让我们做最重要的部分:播放音乐!为此,我们有一个实质性的功能要添加进去;它的第一部分负责从 Spotify 获取当前状态,并在屏幕上显示专辑详细信息:
playmusic() { // get current playing album spotifyApi.getMyCurrentPlaybackState() .then(function(data) { spotifyApi.play(data); sessionStorage.setItem("chosenalbum", data.item.album.id); sessionStorage.setItem("chosenartist", data.item.artists[0] .id); var albumtype = data.item.album.album_type; $(".year").html(data.item.album.release_date.substring(0,4)); $(".albumtype").html(albumtype.charAt(0).toUpperCase() + albumtype.slice(1)); $(".trackcount").html(data.item.album.total_tracks + " tracks"); $(".albumID").html(data.item.album.id); $(".artistID").html(data.item.artists[0].id); $("#currentalbum > span.albumname").html(data.item.album.name); $("#currentalbum > span.albumartist").html(data.item.artists[0].name); $("#currentalbum > span.albumimage > img").attr("src", data.item.album.images[1].url); }, function(err) { console.error(err); });
-
接下来,留下一个空行——我们现在需要添加
play()
函数的右半部分。为此,加入以下代码,它负责在屏幕上显示曲目的详细信息和时间:// get album tracks spotifyApi.getAlbumTracks(sessionStorage.getItem("chosenalbum")) .then(function(data) { var tracklength; $("#albumlist > ul > li").remove(); data.items.map( function(item) { spotifyApi.getAudioFeaturesForTrack(item.id) .then(function(response) { tracklength = response.duration_ms; $("#albumlist > ul").append(`<li>${item.track_number}: ${item.name} - ${msToMinAndSec(tracklength)}</li>`); }); }); }, function(err) { console.error(err); }); },
-
相比之下,接下来我们需要添加的三个对象看起来很小!三个函数中的第一个是允许我们暂停音乐所需的函数——继续,将它添加到步骤 6 中函数的结尾
}
的正下方:pausemusic() { // stop music spotifyApi.pause(); },
-
其次,类似地,我们需要添加一些东西,让我们能够前进一个轨道——将它添加到
pausemusic()
函数的正下方:playnexttrack() { // play next track spotifyApi.skipToNext(); },
-
对于这些小函数中的第三个,在前一个块的正下方添加以下代码,以允许我们返回一个轨道:
playprevioustrack() { // play previous track spotifyApi.skipToPrevious(); },
-
我们正在取得良好的进展,尽管仍有大量代码需要添加。下一个功能将允许我们按名称搜索艺术家。继续添加以下代码,紧跟在
playprevioustrack()
对象的结束},
之后:searchartistsbyname() { // search artists by name var artistquery = $("#artistlisting > input").val(); spotifyApi.searchArtists(artistquery) .then(function(data) { data.artists.items.map( function(item) { if (item.images.length == 0) { artistimage = "https://speech/spotify/img/noimage.png"; } else { artistimage = item.images[2].url } $("#artistlist > ul").append(`<li><span><img src ="${artistimage}"></span><span>${item.name} - ${item.id}</span> </li>`); }); }, function(err) { console.error(err); }); },
-
我们需要添加的下一个函数是检索我们选择的艺术家的专辑列表——这由下面的代码负责,我们需要将它添加到步骤 10 中的上一个对象的正下方:
getalbumsbyartist() { // get albums by a certain artist var selectedartist = sessionStorage.getItem("chosenartist"); spotifyApi.getArtistAlbums(selectedartist) .then(function(data) { data.items.map( function(item) { $("#otheralbums > ul").append(`<li><span><img src="${item.images[2].url}"></span><span>${item.name}</span></li>`); }); }, function(err) { console.error(err); }); },
-
我们快完成了。添加的最后一个功能将负责将选择的专辑添加到我们在 Spotify 中保存的专辑中。与其他函数相比,这是一个短函数;继续在
getalbumsbyartist()
对象的结束}
下面添加以下代码:addtosavedalbums() { // add to saved albums var getalbum = $(".albumID").text(); spotifyApi.addToMySavedAlbums([getalbum]); }, },
-
不可否认,这最后一段代码有点骗人——我们用它来帮助将我们选择的艺术家的 Spotify ID 设置到会话存储中。向下滚动到结束的
</script>
标签,然后留下一行,并添加以下代码:
```html
<script>
$(document).ready(function() {
$("body").on("click", "#artistlist ul li", function() {
var chosenartist = $(this).text();
var chosen = chosenartist.split(" ");
sessionStorage.setItem("chosenartist", chosen.pop());
});
});
</script>
```
-
现在,我们可以保存我们的工作了——继续,休息一下!这可能看起来有很多代码,但是我向你保证,我们已经添加了大部分代码,相比之下,下一部分(通过语音控制我们的演示)会短得多。
-
当你准备好了,让我们预览一下我们的工作成果——为此,请浏览
https://speech/spotify/
;如果一切顺利,我们应该会看到类似于这个庞大练习开始时显示的屏幕截图。
spotifyApi = new SpotifyWebApi({
clientId: '<ADD IN CLIENT ID HERE>',
clientSecret: '<ADD IN CLIENT SECRET HERE>'
});
spotifyApi.setAccessToken(payload);
}
},
在这一点上,我们可以让 index.html 文件打开,但最小化-我们将在这个项目的最后一部分重新访问它。
如果你成功地用一个工作演示做到了这一点,我必须向你表示祝贺——最后一个练习肯定是一个怪物!虽然很高兴看到这个庞然大物开始变得像一些可操作的东西,但是这是一个更详细地研究代码的好机会;它展示了几个有用的点,值得更多的关注。
理解代码
我们首先设置了一些在代码中使用的变量,比如 SpotifyAPI 包装器实例和相册图像的新占位符。然后,我们添加了一个 helper 函数,将 Spotify 为每个曲目返回的时间转换为更有用的时间,然后使用包装器库用我们的客户端详细信息启动 Spotify 实例。
接下来,我们添加了许多不同的事件处理程序,它们利用了包装器库;尽管它们都以不同的方式工作,但大多数都基于相同的原则,即使用基于承诺的语法。一个很好的例子是playmusic()
函数,它看起来像这样:
playmusic() { // get current playing album
spotifyApi.getMyCurrentPlaybackState()
.then(function(data) {
spotifyApi.play(data);
sessionStorage.setItem("chosenalbum", data.item.album.id);
sessionStorage.setItem("chosenartist", data.item.artists[0].id);
$(".year").html(data.item.album.release_date
.substring(0,4));
$(".albumtype").html(data.item.album.album_type);
$(".trackcount").html(data.item.album.total_tracks + " tracks");
$(".albumID").html(data.item.album.id);
...
albumIDplaying = data.item.album.id;
sessionStorage.setItem("chosenartist", data.item.artists[0].id);
}, function(err) {
console.error(err);
});
在我们的演示中,当我们点击播放按钮时,该功能被触发——我们首先从 Spotify 获取当前播放状态(因此我们需要运行 web 客户端几秒钟!).然后,我们将当前艺术家和专辑的值压入会话存储,然后开始播放并输出各种值,如专辑图像、曲目数量和每首曲目的名称。使用会话存储有点麻烦,但却是必要的。它强制 spotifyApi 库在正确的时间为专辑获取正确的 ID;否则,我们可能会以它试图传递一个空值而告终,结果得到一个 400 错误(或者没有列表)!
其他函数以类似的方式工作(有一些例外)——每个函数都使用基于承诺的语法从 Spotify 请求带有数据的 JSON 对象,然后提取相关数据并在屏幕上的适当位置显示。
与 Spotify 交谈
最后,我们可以让我们的应用识别我们的声音!是的,看起来我们在触及项目的真正关键(和本书的重点)之前已经讨论了很多。然而,如前所述,Spotify API 并不是最容易使用的;Jose 的库向抽象出大量代码迈出了一大步,但这仍然意味着我们必须向项目中添加相当多的代码。
谢天谢地,您将要看到的大部分代码现在应该已经相当熟悉了;我们已经使用了以前演示中的大部分内容,这有利于重用。唯一真正的变化是在result()
处理程序中,我们告诉我们的演示程序当它识别出有效的语音时该做什么。一旦我们完成了修改,我们可以在图 8-7 中看到事情的预览。
图 8-7
我们的 Spotify 演示,支持语音控制
考虑到这一点,让我们深入研究一下让我们的应用更详细地识别口头命令所需的代码。
Talking To Our Demo
要添加语音功能,请按照下列步骤操作:
-
我们将从返回到之前演示中打开的 index.html 开始。向下滚动到底部的第二个
<script>
块,然后在DOM.ready()
功能的结束});
前添加一个空行。 -
我们现在需要添加代码,使我们的演示能够通过语音识别命令——我们有大量的代码要添加,所以让我们一个块一个块地做吧。第一个块放在我们刚刚添加的空行下面,它将为语音识别 API 的操作声明一些变量和属性:
const output = $(".output_result"); navigator.mediaDevices.getUserMedia({ audio: true }).then(function(stream) { const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; const recognition = new SpeechRecognition(); recognition.interimResults = false; recognition.maxAlternatives = 1; recognition.continuous = true;
-
我们有一系列的事件监听器要添加——第一个将在我们点击“点击并跟我说话”时触发语音识别服务!按钮。对于每个事件处理程序,在前一个事件处理程序之后留一个空行,然后依次添加代码块:
$("body").on("click", "#speech > button", function() { let recogLang = "en-US"; recognition.lang = recogLang.value; recognition.start(); });
-
下一个事件处理程序是我们在之前的演示中使用过的——当演示检测到麦克风处于活动状态,并且检测到声音,但不一定识别为语音时触发:
recognition.addEventListener("speechstart", () => { $(".output_log").text("Speech has been detected."); });
-
我们之前已经看到了这个下一个处理程序的开始——它决定了所说的任何东西是否可以被识别为语音,如果是这样的话,就对其进行操作:
recognition.addEventListener("result", (e) => { $(".output_log").html("Result has been detected."); let last = e.results.length - 1; let text = e.results[last][0].transcript; $(".output_result").html(text); /* Play music */ if (text.indexOf("play music") != -1) { $("#app > button:nth-child(4)").trigger("click"); } /* Pause music */ if (text.indexOf("pause music") != -1) { $("#app > button:nth-child(5)").trigger("click"); } /* Go forward */ if (text.indexOf("go forward") != -1) { $("#app > button:nth-child(7)").trigger("click"); } /* Go back */ if (text.indexOf("go back") != -1) { $("#app > button:nth-child(8)").trigger("click"); } /* Save album */ if (text.indexOf("save album") != -1) { $("#app > button:nth-child(9)").trigger("click"); } /* Get other albums */ if (text.indexOf("get other albums") != -1) { $("#otheralbums > button").trigger("click"); } /* Search artist */ if (text.indexOf("search artist") != -1) { $("#artistlist > input").val("enigma"); $("#artistlist > button").trigger("click"); } $(".output_log").html("Confidence: " + (e.results[0][0].confidence * 100).toFixed(2) + "%"); });
-
我们又添加了三个事件处理程序——下一个程序检测我们是否停止了说话,如果是这种情况,就会触发
onspeechend
处理程序:recognition.addEventListener("speechend", () => { recognition.stop(); }); recognition.onspeechend = function() { $(".output_log").html("You were quiet for a while so voice recognition turned itself off."); //console.log("off"); };
-
最后一个事件处理程序负责基本的错误处理——在生产环境中,这可能会更深入,但是对于我们的目的来说,只需在控制台中呈现原始的错误消息就足够了:
recognition.addEventListener("error", e => { if (e.error == "no-speech") { $(".output_result").html("Error: no speech detected"); } else { $(".output_result").html("Error: " + e.error); } }); }).catch(function(err){ console.log(err); });
-
我们已经完成了对文件的编辑,请继续并保存它。我们现在可以预览我们的工作结果;为此,请浏览至
https://speech/spotify/
。如果一切正常,我们应该首先看到类似于图 8-7 所示的截图。
在这最后一个演示的过程中,我们回顾了在本书前面的第一个项目中看到的一些函数;它显示了添加语音功能是多么容易,而且大部分代码可以轻而易举地重用。与许多项目一样,结果处理程序经常改变;记住这一点,让我们更详细地看一下代码,看看它们是如何组合在一起的。
详细研究代码
我们在本演示中使用的许多代码现在应该开始看起来有点熟悉了 API 的美妙之处在于我们可以设置许多股票处理程序,并在我们的项目中重用它们。当然,有些(比如结果处理器——more anon)可能需要改变,但是许多(比如onspeechend
)可以在不同的项目中保持不变。考虑到这一点,让我们更详细地看一下代码。
我们首先声明了一些变量和属性,包括缓存.output_result
,定义一个语音识别 API 实例为SpeechRecognition
,
,并为 API 设置interimResults
、maxAlternatives,
和continuous
属性。
然后我们开始设置事件处理程序来响应 API——第一个允许我们使用en-US
作为默认语言来触发 API。请注意,这不是使用事件处理程序的标准 jQuery 语法——我们必须推迟它,以便一旦按钮出现在页面上就可以分配它(直到我们登录后才可用)。
我们添加的下一个处理程序是针对speechstart
事件的——这是直接从一个早期的项目中提取的,这表明在使用语音 API 时重用代码是多么容易。然后,我们转向一个相当长的结果事件处理程序——它看起来很长,但是大部分都是定义当 API 识别某些短语时应该发生什么。该处理程序转录结果属性的内容,然后将其输出到屏幕上,并确定是否可以执行所提供的任何操作。
然后,我们添加了三个事件处理程序,它们是从以前的项目中继承来的:两个用于确定我们是否已经停止说话(speechend
和onspeechend
),最后一个用于提供基本的错误日志记录(error)。值得注意的是,两个speechend
处理程序的工作方式略有不同——第一个决定我们是否已经停止说话,第二个控制当我们停止说话时会发生什么,因此可以关闭 API。
更进一步
希望到现在为止,你已经有机会玩这个演示了——当然有些地方有点粗糙,但我们应该记住,这一章不是关于使用 Spotify,而是我们如何设置语音 API 来与 Spotify 内容进行交互!
然而,我们可以做一些事情来改善界面,并开始为我们的应用添加更多的润色:
-
你可能已经注意到,曲目列表的排序顺序似乎不正确。这是有原因的;一旦从 API 中检索到列表,它就与每个条目在包含列表的
<span>
元素中的插入顺序有关。为了解决这个问题,我们可以将条目推入一个对象数组,然后在屏幕上显示新的顺序之前使用.sort
方法对它们进行排序。(只是确认一下,曲目 id 是正确的,只是顺序不对!) -
对于你们当中的纯粹主义者来说,你们可能已经发现了一些曲目的时间稍微有点晚;这很可能是由于我们将原来的毫秒值转换成更容易阅读和识别的值。这可能不是一个真正的问题,但可能值得试验一下这个函数,看看是否可以改进。
-
回想一下本书前面的内容——在许多项目中,我们实现了对另一种语言(法语)的支持。我们没有理由不能在这里添加同样的支持;我们需要更新 UI 来反映这些变化,并为每个特性添加正确的触发命令,比如播放音乐。
-
这个演示中非常缺乏的一个方面是来自应用的任何形式的反馈——我们可以看到我们口头发出的命令,但是使用语音合成 API 来确认某个操作已经发生怎么样?
-
返回的相册列表可能相当大;这是我们绝对可以改进的一个方面!我们的演示显示了从 API 返回的前 20 个项目,但是我们应该考虑将它限制为前五个(?),如果需要,可以选择显示扩展列表。
-
我们已经实现了
navigator.mediaDevices.getUserMedia
方法来启动对麦克风的访问(我们在第一章中使用过),但是目前关闭按钮是不可操作的。理想情况下,我们应该提供一个关闭语音功能的选项,但这需要对代码进行重构,因为按钮最初是不可见的。
这应该给你一个我们可以去的地方的味道和一些想法让你开始。在 José的 wrapper 库中,我们可以使用更多的选项,所以我建议通读他的网站和 Spotify 开发者门户的文档——有很多内容可以帮助你将应用开发成未来某个时候可以成为语音制作解决方案的东西。
摘要
为任何应用添加语音功能都开启了一个可能性的世界,尤其是对于我们这些能力较弱的人——或者可能只是懒惰的人!在本章的过程中,我们一直致力于为使用 Spotify API 的项目添加语音功能;让我们花一点时间来回顾一下我们在本章中所讲的内容。
在探索为什么我们选择 Spotify 作为我们的 API 提供商之前,我们首先设置了我们项目的背景。然后,我们进入了一个简短的讨论,围绕我们将如何设计我们的项目,以及如何获得 Spotify 授权的 API(以及这对我们的项目意味着什么)。
然后,我们设置了基本的认证过程,首先解决了一些先决条件;然后,我们将注意力转向添加代码,以允许我们从 Spotify 流式播放内容。最后但并非最不重要的是,我们随后添加了代码,允许我们与 Spotify 进行口头交互,然后探索一些想法,以帮助我们开始将我们的项目进一步开发成可以在生产环境中使用的东西。
好了,休息一下,伙计们,因为我们有另一个大项目要做了!人们不会不注意到网上购物在过去几年中是如何真正起飞的;越来越多的人使用设备在线购物,他们不一定需要在屏幕上看到结果。解决这个问题的一个方法是在结账过程中加入语音功能——听我说,我会在下一章向你展示如何做到这一点。
九、项目:采购流程自动化
我们几乎就要完成这本书了,但是我们还有一个项目等待着我们!我敢肯定,你已经花了几个小时试图找到一个特定的产品,然后把它放在一个篮子里,并通过了几个屏幕来完成结帐,对不对?用更复杂的篮子真的很痛苦。如果我们能够利用我们声音的力量来自动化这个过程的一部分,会怎么样?
借助语音 API 的强大功能,我们应该能够口头要求网站找到产品并将其添加到购物篮中,然后使用支付请求 API 结账——所有这一切都无需触摸任何键盘。听起来不可能?在本章的过程中,我将向你展示这个想法的大部分现在可能只是现实。我们将介绍将语音功能添加到流程核心部分所需的各个步骤,以便您可以看到我们可以为客户节省多少时间和精力。
设置场景
在我们深入研究如何实现这样一个项目的细节之前,我想回答一个问题,我相信至少你们中的一些人会提出这个问题:我们为什么要这样做?嗯,有两个很好的理由,如果不是三个的话:亚马逊(或谷歌——取决于你的亲和力),可访问性,以及…嗯,为什么不呢?在你们都认为我已经完全失去了情节之前,让我解释一下我说的那个有点神秘的答案是什么意思。
首先,亚马逊,或者谷歌,是因为智能助理的发明。我们在书的前面提到过这个主题,越来越多的搜索是如何在没有视觉显示的情况下实现的。他们是如何做到的?嗯,是通过智能助手的使用!当你可以让亚马逊的 Alexa 或苹果的 Siri 为你工作时,为什么要花时间在网站上搜索呢?
第二,或可访问性,是当今包容世界的一个关键因素——我们已经有了屏幕阅读器,它可以(或多或少地)在网站周围帮助视力有缺陷的人。问题是,它要求我们开发人员花费大量的时间和资源来增加基于 ARIA 的可访问性能力——这是有用的,但存在风险,它可能不会对每个网站都很好,或限制我们如何做事。相反,为什么不让网站为我们做这项工作呢?我们总是可以从提供基本功能和可访问性标签开始,但是添加语音功能将使我们更加灵活,并提供更加个性化的内容。
“为什么不”的第三个也是最后一个原因和罐头上说的一模一样——为什么不呢?我们永远不应该因为需要保证安全或只做我们知道的事情而感到压抑;过了一段时间,这变得很无聊,我们开始没有食欲!我们绝对应该寻求突破我们所能达到的极限——它可能总是有效,也可能不总是有效,但毕竟,你只有尝试过才会知道,对吗?
带着那个惊天动地的想法,我们继续吧。我们将很快看看我们将如何为这一章设计我们的项目,但在此之前,让我们把注意力转向我们将如何保持事情在范围内,并确保这个项目保持在正轨上。
保持事物在范围内
在这一点上,我们必须小心——如果我们不小心,建立一个电子商务商店,更不用说添加语音功能,可以很容易地填满整本书的页面!
考虑到这一点,我们将把自己限制在几个关键的过程中,这样您就可以了解如何设置语音选项,并将其扩展到您自己项目的其他领域。我们将涉及的两个关键领域是
-
将产品添加到购物车中——我们将为一个有四种产品的商店设置代码(这应该足以显示一点多样性;不管我们卖的是限量系列还是上百种产品,流程都是一样的)。
-
通过输入卡的详细信息并点击支付按钮来结帐-这将模拟付款被发送并收到积极的回应作为回报。
我们将不讨论如何构建我们的商店,因为所需的代码量太大了,在本章的篇幅中我们无法做到。相反,我们将假设我们有一个基本的,但功能,商店工作,并在此基础上继续,我们将增加语音功能。
在本章的最后,我们将通过一些提示让你开始将商店扩展到其他领域。
好了,现在我们已经设置好了场景,是时候深入研究我们将要使用的工具了;我们已经遇到并使用了两个(以语音 API 的形式),但是我们将在我们的项目中使用一些其他的。
设计项目
与任何项目一样,我们可以使用各种不同工具中的任何一种来完成工作——不言而喻,显然没有一种工具是我们可以(或者应该)使用的!我们已经知道我们将使用我们之前看到的语音 API,但是为了让它工作,我们需要引入一些额外的技术。它们如下:
-
CompressPNG.com(
https://compresspng.com
)——这是我用来压缩演示中使用的 PNG 图像;这在开发能力中不是绝对必要的,但是原件比需要的要大! -
谷歌字体——我们正在使用来自
https://fonts.google.com
的开放 Sans 和警告字体;如果需要的话,可以下载并在本地设置。 -
jQuery–在
https://code.jquery.com/jquery-3.4.1.min.js
从 jQuery CDN 下载并重命名。 -
Stripe——我们使用 Stripe 的基于 jQuery 的插件库,帮助格式化和管理信用卡信息;这可从
stripe 获得。github。io/ jquery。支付/库/查询。付款。js
。 -
字体真棒–为此我们在
https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css
使用 MaxCDN 链接–这用于购物车中的篮子图标。
Note
使用 jQuery 只是为了方便;在一个理想的世界中,我们会设法不再使用它,并可能专注于使用普通的 JavaScript 或 React 或 Vue 等框架。
好了,说完了,让我们继续。是时候陷进编码了!在接下来的几页中,我们将涉及相当多的代码;由于篇幅原因,我们将主要关注用于语音功能的 JavaScript,因为 HTML 标记和 CSS 样式是标准的。考虑到这一点,让我们深入研究一下代码的细节。
准备我们的购物车
是时候开始设置东西了。对于这本书的最后一个项目,我们将创建一个基本的商店来销售有限范围的饼干——不仅仅是我妈妈常说的任何一种老饼干,而是那些非常柔软和耐嚼的饼干……嗯……但是我跑题了!
从技术角度来看,我们的演示将是基于两个 CodePen 演示的代码和来自早期演示的语音识别代码的融合;该店铺是维吉尔·帕纳从 https://codepen.io/virgilpana/pen/ZYqJXN
的一支笔的删减版,弹出的支付形式是基于梅康·路易斯在 https://codepen.io/mycnlz/pen/reLOZV
的那支笔。从头开始创造一些东西是可能的,但是考虑到本书中可用空间的限制,我们不可能公正地完成它!
Setting up the Shopping Cart
先不说这个,让我们从开店开始:
图 9-1
我们最初的商店
-
我们将从本书附带的代码下载中提取一个
shop
文件夹的副本开始;继续并将其保存到我们的项目区域。 -
启动浏览器,然后前往
https://speech/shop/
。如果一切正常,我们应该会看到商店出现,类似于图 9-1 中的截图。
设定期望
在这一点上,我们有了一个(各种)功能商店——当然它不会是完美的,但它足以满足我们的需求。尽管设定正确的期望值很重要,但为了保持透明度,本章中有几点需要牢记:
-
代码还没有投入生产——事实上,人们期望在商店展厅和结账流程中看到的许多功能还没有出现。这一章不是关于建立购物车,而是建立如何通过使用口头命令使它们可用。正是因为这个原因,我们将更多地关注这样做的技术,而不是商店本身。
-
由于篇幅原因,我将重点介绍从画廊选择产品并进行(模拟)购买的关键部分——有人可能会说搜索产品也是必不可少的,但如果我们不能将它添加到我们的购物篮中,那就没有任何好处了!关键是,我们网站的“支持语音”的原则对网站的大部分都是一样的,所以我们总是可以修改现有的代码来为其他领域工作。
解决了这个问题,让我们把注意力转向我知道你们都在等待的一点——更新我们的演示!别担心。它只有几行,但是在我们开始之前,让我们更详细地看看更新我们的演示所涉及的步骤。
添加语音功能
好了,关键时刻到了!嗯,也许这有点太俗气了,因为我们店里的产品是饼干,但我离题了…总之,回到现实。
在添加语音功能时,我们可以分阶段进行;以下是我们需要遵循的步骤:
-
为我们的麦克风和响应添加标记和样式。
-
调整产品的价格。
-
添加脚本功能以使其可操作。
-
添加基本样式,使我们的演示看起来像样。
我们流程的第一步是添加控制麦克风并在屏幕上呈现任何响应的标记——一旦进入,我们就可以测试它以确认我们的网站正在接收语音,并且一旦我们的产品标记和脚本就绪,我们就能够对其采取行动。让我们深入研究一下如何添加标记,这样我们就可以开始看到我们的语音能力正在形成。
为我们的麦克风插入标记
更新演示的第一部分非常简单——我们首先需要为麦克风和各种消息或响应添加标记,然后添加可视指示器,以便客户知道使用麦克风时要说什么。
在第一部分中没有什么特别复杂的东西,尽管数据标签有一个有趣的用法;演示结束后,我们将探究使用它们的原因。让我们首先继续更新或添加相关的标记到我们的演示中。
Adding Speech Part 1: The Microphone Markup
要添加我们的标记,请按照下列步骤操作:
-
我们所有的修改都在作为下载一部分的商店文件夹中的
index.html
文件中——继续并在您常用的文本编辑器中打开它。 -
接下来,查找
<div id="sidebar" ....
块,并在它的结束</div>
元素之前添加这个标记。我们最终应该得到这样的结果:<button id="microphone"> <i class="fa fa-microphone"></i> Click and talk to me! </button> <div class="response"> <span class="output_log"></span> </div> <p class="output">You said: <strong class="output_result"> </strong></p> <span class="voice">Spoken voice: US English</span> </div>
-
继续保存文件,然后最小化它,我们将需要在下一个练习中恢复它。
-
接下来,我们需要为麦克风按钮和响应文本添加一些基本样式。为此,打开
styles.css
文件,一直滚动到底部。 -
在底部,添加以下样式规则:
/* SPEECH RECOGNITION --------------------------- */ .speechterm { padding-left: 15px; font-style: italic; } i.fa.fa-microphone { padding-right: 10px; } p.output { padding: 10px 0; } #microphone { margin-top: 20px; } #microphone:hover { background-color: darkgrey; }
-
此时,保存您的工作,并关闭 styles.css 文件–我们已经添加了本演示所需的所有内容。
-
We can now preview the results of our work – for this, browse to
https://speech/shop/
. If all is well, we should see something akin to the screenshot shown in Figure 9-2.图 9-2
更新的商店,增加了麦克风选项
在这个阶段,我们还没有真正做出任何重大的改变——我们现在有了麦克风按钮的基础,以及用于用户各种响应或 API 返回消息的空间。我们将在下一个练习结束时进行,而不是在此时探索代码更改。所以,事不宜迟,让我们快速前进,更详细地探索需要更新哪些内容来支持 API 图库中的每个产品。
改变我们的产品价格
插入麦克风标记后,我们现在可以将注意力转向“启用”每个 cookie 以用于我们的语音识别功能。
我说“启用”是因为没有更好的方式来表达它,但我们所做的只是调整我们的标记,使我们的代码更容易识别并向我们的篮子中添加正确的 cookie。相信我——现在可能没有意义,但一旦我们完成练习,一切都会变得清晰!记住这一点,让我们开始更新标记。
Adding Speech Part 2: Updating Product Markup
要更新我们的产品标记,请按照下列步骤操作:
图 9-3
“我们现在可以和我们的饼干说话了……”
-
第一阶段是恢复到我们在之前的练习中打开的
index.html
文件——演示的这一部分的所有更改都在这个文件中。 -
第一个变化是更新 Cherry bake well cookie——查找
add_to_cart
元素,并按照指示添加数据标签:<div class="add_to_cart" data-product="cherry bakewell">Add to cart</div>
-
Next, scroll down a couple of lines and insert this markup immediately below the
<span class="product_price"...
line:<span class="product_price">$0.50</span> <span class="speechterm"><i class="fa fa-microphone"></i>"Add a cherry"</span>
这将为 cookie 添加一个麦克风和合适的文本,这样我们的客户就可以知道如何口头请求。
-
我们需要对剩下的三种产品重复步骤 2 和 3——对于下一种饼干(黑巧克力),按照指示添加数据标签:
<div class="add_to_cart" data-product="dark chocolate">Add to cart</div>
-
在 cookie 的
product_price
标记的正下方,添加这行代码——这将为我们的语音识别功能“启用”黑巧克力 cookie:<span class="speechterm"><i class="fa fa-microphone"></i>"Add a dark"</span>
-
我们还需要对树莓和白巧克力曲奇进行类似的修改——为此,添加高亮显示的代码:
<div class="add_to_cart" data-product="raspberry">Add to cart</div>
-
与之前的 cookie 类似,我们也需要添加麦克风标记,为此,在该 cookie 的 product_price 行的正下方添加以下行:
<span class="speechterm"><i class="fa fa-microphone"></i>"Add a raspberry"</span>
-
最后,但绝不是最不重要的,继续添加数据标签,如太妃糖 cookie 所示:
<div class="add_to_cart" data-product="toffee">Add to cart</div>
-
对于这个产品,我们还需要做一个修改——在它的 product_price 加价行的正下方,添加以下代码:
<span class="speechterm"><i class="fa fa-microphone"></i>"Add a toffee"</span>
-
继续保存文件-我们现在预览结果!为此,浏览至
https://speech/shop
;如果一切正常,我们应该看到所有四个 cookies 现在都有一个视觉指示器,指示在使用我们的麦克风时需要什么(图 9-3 )。
嗯,光是看着那块饼干就让我觉得很饿。咯咯!暂时把食物的想法放在一边,我们添加到代码中的变化可能看起来有点不寻常,但正如所承诺的,疯狂中有方法!在我解释更多之前,让我们更详细地看一下代码。
剖析代码
我们使用的大部分标记非常相似——我们所做的改变分为两个阵营:第一个是在麦克风按钮周围添加标记,第二个是为每个产品调整我们的标记。
第一个块将一个标准的<button>
元素添加到我们的标记中,加上一个 div 元素和两个 spans 后者用于显示来自 API 的响应(例如任何错误)、来自用户的响应以及 API 使用的声音的指示。接着,我们在每个 cookie 中添加了一个data-product
标签,以及一个使用麦克风时要求什么的可视化指示,以。speechterm
斯潘。
现在,正如所承诺的,使用数据标签是有原因的。正如您将在后面的代码中看到的,我们使用一个通用的.add_to_cart
类来触发将任何 cookie 添加到篮子中。原则上,这似乎是一个明智的想法,对不对?
错误——如果我们单独使用它,我们会有一个问题:它会一次添加同一个 cookie 的四个实例!原因在于 jQuery 的工作方式——.add_to_cart
类将应用于所有四个产品,因为我们对每个产品使用相同的类。
为了解决这个问题,我们添加了数据标签,这样我们就有了对每个 cookie 的特定引用。然而关键在于如何触发添加项目的调用——我们使用绑定到每个add_to_cart
div 的数据标签属性。动态引用意味着我们可以将一个add_to_cart
div 的实例传递给事件处理程序:
$('[data-product="' + cookieChosen + '"]').trigger("click");
如果它现在不完全有意义,请不要担心——当我们检查为使我们的演示工作而添加的代码时,我们将再次讨论它!
添加脚本功能
继续,我们清单上的下一个任务是添加我们需要操作语音特性的代码。这其中的大部分你会在之前的演示中看到,所以现在应该不会完全陌生;它的关键在于我们如何将我们的语音响应转换成我们的代码可以识别的东西,并用来添加到适当的 cookie 中。为了让你感受一下它的样子,你可以在图 9-4 中看到完整的文章。
图 9-4
一个曲奇用我们的声音加入了篮子
记住这一点,让我们把注意力转向设置我们的演示。
Adding Speech Part 3: Making IT Work
让我们开始添加演示脚本:
-
在本练习中,我们所做的所有更改都将保存在
script.js
文件中,所以请在您常用的文本编辑器中打开它。 -
向下滚动到底部,直到你看到这些评论:
/* SPEECH RECOGNITION ------------------------------ */ /* Code to be added here */
-
这就是我们要添加代码的地方——有相当多的部分要添加,所以我们将一个一个地进行。
-
第一块是添加一些变量或对象声明,并为一些语音识别 API 设置值。继续留一个空行,然后在上一步的第二个注释下面添加以下代码:
const log = document.querySelector(".output_log"); const output = document.querySelector(".output_result"); const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; const recognition = new SpeechRecognition(); recognition.interimResults = false; recognition.maxAlternatives = 1; recognition.continuous = true;
-
我们需要添加的第一个事件处理程序负责启用我们的麦克风——这里我们设置了几个属性来配置语音识别 API 的实例。添加以下代码,在上一步的代码后留下一个空行:
document.querySelector("#microphone").addEventListener("click", () => { let recogLang = "en-GB"; recognition.lang = recogLang.value; recognition.start(); });
-
我们添加的下一个处理程序在检测到语音时触发——包括背景噪音!为此,添加以下代码,在上一步的代码后留下一个空行:
recognition.addEventListener("speechstart", () => { log.textContent = "Speech has been detected."; });
-
下一步是魔法开始发生的地方——在这里,我们检测说了什么,解析内容,并决定最终采取什么行动。这是一段相当长的代码,所以我们将把它分成几个部分——要设置基本的处理程序,继续添加这段代码,在上一步的代码后留下一个空行:
recognition.addEventListener("result", (e) => { log.textContent = "Result has been detected."; let last = e.results.length - 1; let text = e.results[last][0].transcript; output.textContent = text; // ACTION CODE HERE log.textContent = "Confidence: " + (e.results[0][0].confidence * 100).toFixed(2) + "%"; });
-
现在有了基本的处理程序,我们可以开始扩展它了。在前面的步骤中查找这行代码——
// ACTION CODE HERE
——然后用这个条件块替换它:// SR - "Add an X to the basket" if (text.search(/\badd\b/)) { var request = text.split(" ").pop(); console.log(request); var cookieChosen; if (request == "cherry") { cookieChosen = "cherry bakewell"; } if (request == "dark") { cookieChosen = "dark chocolate"; } if (request == "raspberry") { cookieChosen = "raspberry"; } if (request == "toffee") { cookieChosen = "toffee"; } $('[data-product="' + cookieChosen + '"]').trigger("click");
-
对于这个条件块的第三和最后一部分,继续在上一步的
data-product
赋值下面添加以下行,中间留一个空行:/* ----------------- */ /* click on checkout */ if (text.indexOf("check") != -1) { $("#checkout").trigger("click"); } /* ----------------- */ /* enter credit card number */ if (text.indexOf("credit card") != -1) { $("#cardnumber").val("4111111111111111"); } /* ----------------- */ /* enter card date */ if (text.indexOf("expiry") != -1) { $("#cardexpiration").val("10/2022"); } /* ----------------- */ /* enter CVV number */ if (text.indexOf("security") != -1) { $("#cardcvc").val("672"); } /* ----------------- */ /* click on purchase */ if (text.indexOf("purchase") != -1) { $("div.card-form > button > span").trigger("click"); } }
-
我们还剩下三个事件处理程序,与上一个事件处理程序相比,它们看起来很简单!当 API 检测到听不到更多的语音时,下一个要添加的将被触发:
```html
recognition.addEventListener("speechend", () => {
recognition.stop();
});
```
- 下一个事件处理程序也在没有检测到更多语音时触发,但是有一个微妙的区别——这个事件处理程序在 API 关闭时触发。继续添加下面的代码,在前面的事件处理程序后留下一个空行:
```html
recognition.onspeechend = function() {
log.textContent = 'You were quiet for a while so voice recognition turned itself off.';
console.log("off");
};
```
- 最后但绝不是最不重要的,我们需要实现一些基本的错误处理——现在,我们只是在屏幕上呈现 API 生成的任何错误。继续添加以下代码:
```html
recognition.addEventListener("error", e => {
if (e.error == "no-speech") {
output.textContent = "Error: no speech detected";
} else {
output.textContent = "Error: " + e.error;
}
});
```
- 至此,我们完成了对文件的编辑。继续保存它,然后关闭文件。我们现在可以预览我们的结果。浏览到
https://speech/shop/
,点击麦克风按钮,然后尝试对着麦克风说“加一颗樱桃”。如果一切正常,我们应该会看到类似于本练习开始时显示的屏幕截图。
我们现在有了一个添加到购物车的工作流程——我们应该能够将四个 cookies 中的任何一个添加到我们的购物车中。我们已经看到它与 Cherry Bakewell 一起工作(如图 9-4 所示),但对一些人来说,这个故事可能有刺!这是我们以前见过的事情(还记得 Alexa 克隆演示吗?)–在我们探索它是什么之前,让我们更详细地浏览一下代码,因为与我们在早期演示中使用的代码相比,有几个关键的变化。
打破我们的代码
在本章的过程中,我们已经讲述了相当多的代码,作为添加语音功能的一部分——其中大部分现在应该看起来很熟悉了,特别是因为我们已经使用了本书早期演示中的部分内容。也就是说,更详细地浏览我们添加的代码仍然是一个好主意——有一个关键部分我们需要知道,所以让我们深入并更详细地看一看。
我们通过定义几个常数开始了语音识别部分——我们使用.output_log
显示来自 API 的消息,使用.output_result
显示来自客户的转录文本。然后,我们创建一个语音识别 API 的新实例;这使用本机实现或供应商前缀版本,具体取决于所使用的浏览器。除此之外,我们还设置了三个属性——interimResults
为 false(因此我们只得到最终结果),maxAlternatives
为 1(我们专注于获得原始的、检测到的单词,而不是可能的替代词),以及continuous
为 true(因此语音识别 API 不会太快关闭)。
然后我们有了一系列事件处理程序。第一种允许客户在浏览器中启用他们的麦克风;这将在启动识别服务之前,将语言设置为美国英语("en-US"
)。接下来是speechstart
事件处理程序,一旦检测到任何语音文本(不一定是来自客户的!).
接下来是这个演示的关键部分——这是一个扩展的结果事件处理程序。在将口述文本的内容分配给 text 变量之前,它首先检测服务是否识别了口述文本。然后,我们使用pop()
方法分割这个变量的内容,并获取最后一个条目。这很重要,因为它存储在cookieChosen
变量中;我们用它来触发右边的添加到购物车按钮。
值得暂停一会儿,因为浏览代码不会发现任何可以被称为纯粹的添加到购物车按钮处理程序的东西!我们在本章的前面提到了这一点,这是有充分理由的——我们可以创建一些分配一个唯一的 ID 或类的东西,但是正确地做到这一点是很棘手的。我们最终可能会有很多处理程序,或者一个非常丑陋的一刀切的方法。
相反,我们使用了一个data-product
标签——我们动态地将从cookieChosen
保存的值连接到触发点击处理程序的事件中,该处理程序触发右按钮。这是可行的,如果你仔细观察我们的标记,你会看到数据标签与add_to_cart
div 相对,如图 9-5 所示。
图 9-5
标记中使用的数据标签的示例
对于该事件处理程序的其余部分,我们简单地使用了一组条件检查——如果我们转录的文本结果包含某些词,如card
、security,
或expiry
,我们就在适当的字段中输入测试值。事件处理程序的最后一步是添加一个提交付款的触发机制——我们在演示中模拟了这一点,但如果这是在生产中使用的话,付款将在这一点上进行。
你会注意到在这个演示中使用了伪造的信用卡信息。这是而不是推荐的做法;它们是为了说明这个演示的一个缺陷。我们将在本章的后面讨论这对我们意味着什么。
代码的剩余部分包含我们在之前的演示中使用的事件处理程序——我们有两个speechend
处理程序和一个处理程序来处理我们演示中的基本错误。不过,有两个speechend
处理程序是有原因的:第一个(speechend
)在服务检测到我们已经停止说话时触发(因此关闭它自己);一旦发生这种情况,第二个(onspeechend
)就会启动,并在屏幕上为我们的客户显示适当的信息。
好吧,我们继续。我们已经构建了一个基本的购物车,它使用自定义的结账流程。有一个相对较新的 API,旨在跨所有浏览器标准化结帐表单。问题是,我们能把同样的原理应用到语音上吗?在一个理想的世界里,不应该有任何不同,除了这一次我们可能没有那么幸运。为了理解我的意思,让我们看看这种变化会如何影响我们的战略,以及我们是否需要重新考虑我们的计划。
退房的另一种方法
从我记事起(这要追溯到 20 多年前!),任何通过互联网购买商品的人无疑都经历过定制的或从诸如光化学等商业产品开发的结账过程。这(当时)没什么问题,但现在许多都被视为笨重且难以维护——这通常是整个采购流程中最容易遗漏的地方!
在过去的几年中,W3C 和浏览器供应商一直在开发一种可以直接在浏览器中使用的标准化 API——现在被称为支付请求 API。虽然它在每个浏览器中看起来都不一样,但它提供了一个标准框架,支付提供商可以在其中插入自己的支付处理程序,而不必担心 UI 或用户体验。
在我们的下一个练习中,我们将利用这个 API 来生成一个简单的付款结账——它不会有 API 附带的所有功能,但至少允许我们运行结账流程。作为一个品尝者,图 9-6 展示了一旦我们实现了请求支付 API 所需的更改,我们的演示将会是什么样子。
图 9-6
我们的另一种支付方式
现在我们已经看到了它的样子,让我们开始修改我们的演示。
Using the Payment Request API
对于这个演示,我建议从上一个演示中复制一份完整的shop
文件夹,然后保存为shop-alternative
;我们将以此为基础,用付款申请 API 取代人工结账。
如果您遇到困难,在本书附带的代码下载中有这个演示的完整版本;它在车间-替代-完成版本文件夹中。
要完成交换,请执行以下步骤:
-
我们编辑完了。继续保存 index.html 和 script.js 它们现在可以关闭了。
-
现在,我们可以预览更改的结果了——启动浏览器,然后导航到
https://speech/shop-alternative
。如果一切正常,我们应该会看到类似于图 9-6 所示的视图,这里显示的是我们的可选结帐表单。 -
第一个任务是在我们的标记文件中去掉付款部分——为此,寻找以
<!--- PAYMENT....
开始的行,然后从这里向下移动到<div id="header">
之前的结束</div>
标记。 -
接下来,切换到 script.js,这样我们可以删除 modal,因为不再需要它了。查找以
/* MODAL ----
开始的行,然后删除它,并将代码向下移动到/* PAYMENT FORM...
行之前的结束});
。 -
我们还需要删除原来的支付冻结–查找并删除从
/* PAYMENT FORM ---...
开始的冻结–删除到/* SPEECH RECOGNITION ---...
之前。 -
我们有一个新的代码块要插入,作为我们的支付处理程序的替换——继续插入这个代码,作为我们的处理程序的第一部分:
/* PAYMENT FORM USING PAYMENT REQUEST API---------- */ const methodData = [{ supportedMethods: 'basic-card', data: { supportedNetworks: ['visa', 'mastercard', 'amex'] } }];
-
付款请求的真正内容来自下一个事件处理程序——保留一行空白,然后在
methodData
常量下面添加以下代码:document.getElementById('checkout').onclick = function (e) { if(window.PaymentRequest) { let subtotal = Number(countCookies * 0.50); let tax = 1.99; let shipping = 2.99; const details = { total: { label: 'Total due', amount: { currency: 'USD', value: (subtotal + tax + shipping).toFixed(2) } }, displayItems: [{ label: 'Sub-total', amount: { currency: 'USD', value: subtotal.toFixed(2) } }, { label: 'Delivery', amount: { currency: 'USD', value: 2.99 } }, { label: 'Sales Tax', amount: { currency: 'USD', value: tax.toFixed(2) } }] }; const options = { requestPayerEmail: true }; const request = new PaymentRequest(methodData, details, options); //Show the Native UI request .show() .then(function(result) { result.complete('success') .then(console.log(JSON.stringify(result))); }).catch(function(err) { console.error(err.message); }); } else { // Fallback to traditional checkout } };
-
We’re almost done. There is one last block of code to remove. In the Speech Recognition block , look for and remove this code, as it is no longer needed:
/* ----------------- */ /* enter credit card number */ if (text.indexOf("credit card") != -1) { $("#cardnumber").val("4111111111111111"); } /* ----------------- */ /* enter card date */ if (text.indexOf("expiry") != -1) { $("#cardexpiration").val("10/2022"); } /* ----------------- */ /* enter CVV number */ if (text.indexOf("security") != -1) { $("#cardcvc").val("672"); }
这可能看起来有点奇怪,但删除它有一个很好的理由——一切将很快揭晓。
在这个练习的过程中,我们已经剥离了原始的付款表单,并用付款请求 API 中的一个实例替换了它。这看起来没问题,但是请注意,作为步骤 6 的一部分,我们是如何删除在原始版本中完成的一些检查的?我暗示这在当时看起来很奇怪,但这是有很好的理由的——为了解释和更多,让我们深入了解一下更详细的变化。
破解密码
在这一章的过程中,我们做了一些彻底的改变。我们首先从标记文件中删除了原始的支付代码,以及模态代码。这两个都不是必需的,因为表单将由浏览器中的支付请求 API 提供。
接下来,我们做了类似的事情,但是在script.js
文件中——我们驱逐了那里的整个支付块,因为一旦我们输入新的支付请求代码,原始代码将是多余的。
我们演示的真正关键是新的支付请求 API 代码;我们从声明一个methodData
常量开始,它定义了我们的浏览器允许的可接受的支付方式。我们坚持使用basic-card
,这是现成可用的方法;这是一种不安全的方法,不应该在实践中使用,但只用于测试目的是可以的。
然后,我们添加了一个事件处理程序,只要单击#checkout div 就会触发该事件处理程序;这可以通过鼠标或口头进行,就像我们在本演示的原始版本中所做的那样。这首先是对window.PaymentRequest
的检查,看看我们的浏览器是否支持它——假设它支持,我们为subtotal
、tax,
和shipping
定义一组变量(所有其他变量已经在代码的其他地方声明了)。
在下一个常量(details)中,我们定义了一个对象,该对象包含要在表单中显示的标签文本和金额,然后将付款请求 API 的实例初始化为 Request。这就是所谓的承诺;我们首先show()
表单,然后要么触发在控制台显示结果的.complete()
方法,要么通过catch()
陷阱抛出一个错误,以声明我们的支付过程出现了问题。
如果你有兴趣了解更多关于支付请求 API 的知识,那么你可以参考我的书,由 Apress 出版的《用支付请求 API 结账》。
减少功能:注意
在我们结束这个演示之前,有一些事情我们应该考虑一下——还记得我说过我们需要从代码中删除一大块条件检查吗?你可以在图 9-7 中看到我的意思,这里我们已经删除了用于检查信用卡号、有效期和 CVV 安全号码的原始 if 语句。
图 9-7
我们更新的演示,无条件检查
我们不能包含这些支票的原因很简单——付款申请 API 表单内置于浏览器中,因此不能暴露出来让我们与之交互。这意味着,虽然我们篮子的其余部分可以通过语音控制,但我们无法控制结账表单本身!
在某些方面,这可以被视为降低了网站的可访问性——因此,这意味着我们必须提供一个后备或设置它,以便支付请求 API 可以在标准支付结帐过程中启用。伟大的事情是,虽然 API 仍然处于不断变化的状态;虽然它现在使用起来足够稳定,但在它获得官方地位之前,事情可能会发生变化,所以谁知道呢?对语音 API 的支持可以得到很好的改进!
我们继续吧。我们的演示已经完成,但这并不是故事的结尾!不幸的是,这个故事有一点刺痛,这将影响我们的商店。为了理解其中的原因,让我们深入了解一下在给电子商务网站添加语音功能时可能会遇到的一些陷阱。
探索我们解决方案的缺陷
是时候坦白了。是的,我听到了。你可能在想,啊哦…我想知道他指的是什么?小心谨慎是对的,但不要担心。事情并不像看起来那么糟!语音 API 仍然非常新,还没有达到 W3C 推荐的候选标准。这并不意味着我们不能使用它们,而是我们需要保持一定的谨慎。让我们更详细地看看这些陷阱:
-
第一个是你可能会对使用的一些视觉标签感到惊讶——注意它们都没有给出全名,而是像“加一颗樱桃”这样的东西?有一个很好的理由-如果我们使用全名,我们会发现不是所有的 cookies 都可以添加!原因是我们在 Alexa clone 演示中提到的:API 很难识别某些单词,特别是在不同音节之间几乎没有差异的情况下。这不是可以固定的,但是可以微调;我们需要小心选择哪些词来选择我们的产品,只有测试才能确定使用的最佳组合。
-
如果您尝试点击麦克风按钮来启用语音,然后口头要求网站添加一个产品,您很可能会发现,对于第一个产品,您必须这样做两次。API 需要一段时间才能完全激活,因此它的潜在客户会在麦克风完全准备好之前尝试添加产品。为了解决这个问题,我们可以实现一个
Promise()
来使麦克风提示只在特定时间后出现——这是一个微小的变化,但绝对值得一做! -
正如我们在支付请求 API 中看到的那样,我们的能力有限——我们将能够使用语音显示表单,但从那以后,就必须打字或点击按钮。这确实意味着(至少目前)这可能是一个不太有吸引力的选择,并且可能只对那些不想使用语音服务的人开放。这并不好,因为支付请求 API 旨在简化流程;然而,由于这个 API 还没有成为主流,我们将不得不使用现有的 API!
-
我们在代码中设置的属性是我们需要仔细考虑的——在我们生活的这个多元文化世界中,不是每个人都能说英语,更不用说美国英语了,这是语音 API 的默认值!虽然设定一个值很容易,但是设定正确的值值就比较难了——我们是根据一个网站的固定语言来设定,还是根据我们的客户来自哪个国家来设定?这很大程度上取决于你如何运营你的网站——是一个单一的多语言网站(不利于搜索引擎优化)还是多个网站,具有相同的品牌但使用不同的语言?
-
你会从演示中注意到我们硬编码了信用卡的详细信息——实际上,我们这样做是为了提供提交结账表单的流程,而不是以为借口将任何值硬编码到我们的解决方案中!我们可以使用现有的 API 作为识别每个输入数字的基础,但是要做到这一点,我们可能必须说“数字一”,而不仅仅是“一”。这是一个平衡可靠性和不激怒客户的愿望的问题,因为输入任何细节都需要太长时间!
希望这能给我们一些思考。它不应该阻止我们使用 API 我们可以解决这些限制。它确实强调了确保我们仔细考虑使用 API 的更广泛含义的重要性,并且我们将这一点考虑到在我们的解决方案中使用这些 API 的任何开发工作中。
好吧,我们继续。既然我们已经构建了基本的演示并为其添加了语音功能,那么是时候考虑如何扩展我们的演示了。我想到了一些事情,可以帮助你开始;让我们深入研究一下,更详细地看看它们。
更进一步
如果有人问我我们如何进一步推进我们的项目,我想我通常的回答会是“世界是你的牡蛎”——因为你可以去你想去的任何地方,只要你能让它工作起来!似乎有点讽刺的是,这个短语并不是来自于一个技术起源,而是可以追溯到莎士比亚的温莎的风流娘儿们,已经有 400 多年的历史了!但是我跑题了…
反正回到现实,一个人能做什么?嗯,除了添加人们期望在购物篮和结账流程中看到的剩余功能之外,我们还可以查看和实现一些东西。让我们更详细地看看这些想法:
-
其中一个明显的优势是更好的语言支持——还记得我们在演示中如何将 recognition.lang 设置为“en-US”吗?嗯,我们可以研究实现一个你经常在网站上看到的语言选择器的可能性,以及为页面设置语言;它可以用于同时设置合适的语言值。例如,对于位于爱沙尼亚等国家的站点,他们说芬兰语、俄语和英语以及其他语言,您可以设置诸如
fi-FI
、ru-RU
、en-US
和et-EE
(用于爱沙尼亚)之类的值。这将允许我们的语音 API 更好地识别基于该国方言的文本。 -
继续语言支持的主题,我们将如何着手本地化我们的网站,接受其他语言的请求?一种解决方案可能是使用 JSON 来提供每个触发短语的本地语言等价物(例如我们的演示中每个麦克风符号所使用的那些)。我们可以调用其中的每一个,而不是将它们硬编码到我们的演示中。
-
我们已经使用语音识别 API 来添加产品或触发结账过程——使用语音合成 API 来给出已完成操作的口头指示怎么样?我们没有任何东西来指示每个动作发生的时间(除了在屏幕上看到它)——如果有东西来告诉那些有视力障碍的人一个动作已经完成,那将会很有帮助。
-
完全避免使用信用卡,实施 Google Pay 等更现代的支付方式如何?有许多不同的公司提供这种支持,如 Braintree 你可以在
https://developers.braintreepayments.com/guides/google-pay/client-side/javascript/v3
看到他们如何使用 JavaScript 设置支付的例子。这里的想法是,如果我们可以实现一些东西(当然,假设您有一个合适的帐户),那么提供一个链接来发起支付请求应该很容易。
这只是让你开始的几个想法——我相信你能想出更多,但是正如本节的导语所说,世界真的是你的牡蛎!这完全是一个思考的问题,你可以在你的网站中使用语音功能,并给予适当的考虑,它是否真的会帮助客户,或者只是被视为一个噱头,客户会很高兴没有它!
摘要
Web Speech API 是一个实现起来很简单的工具,但是对于使站点更容易访问来说是非常棒的——尽管它仍处于开发阶段!在本章的过程中,我们已经探索了如何在一个基本的购物车和结帐过程中使用它;让我们复习一下我们所学的内容。
我们以介绍这一章和设置场景开始。然后,在介绍用于构建最终解决方案的工具之前,我们讨论了本章的内容。同时,我们简要地讨论了设定期望值,因为我们不可能涵盖购买过程的每一个部分,而是将重点放在本章的核心要素上。
接下来,我们深入研究添加代码,使我们的语音能力滴答作响;在为我们的演示添加脚本之前,我们研究了修改标记所需的更改。然后,我们继续探索使用相对较新的付款请求 API 的另一种结账流程,看看这会如何影响我们在演示中使用语音。
然后,在探索一些我们可以遵循的途径来帮助扩展和扩充我们的演示以供生产使用之前,我们通过查看在结帐过程的上下文中使用 Speech APIs 时需要注意的一些陷阱来结束这一章。
唷!我们已经到了这本书的结尾。多好的旅程啊!我希望你能像我写这本书一样喜欢在这个项目中工作,并且你现在对如何在你未来的项目中使用 Speech APIs 有了更好的理解。