我们都有过这样的经历:我们听过一首歌,但就是不记得它的名字,或者它就在我们的舌尖上。在这种时候,Shazam是个好办法;打开应用程序,让它听一些音频,它会立即输出歌曲名称和艺术家。
当Shazam首次推出时,它最初只是英国的一项电话服务,你拨打 "2580 "来识别一首歌曲。一旦你拨打了这个号码,你就会把你的手机放在音频附近,30秒后它就会挂断,同时给你发送一条歌曲名称和艺术家的短信。
在发现他们的 "2580 "服务后,我内心的工程师就出来了。我很想知道如何用Twilio的可编程语音和短信来构建这个服务,所以我挑战自己,创建一个克隆的服务--做了一些改进
在本教程中,你将学习如何使用Twilio的可编程语音和短信,用Node.js创建一个电话服务来识别歌曲。将用于识别歌曲的API是API Dojo的Shazam API。
概述
在我进入教程之前,让我告诉你这个服务将如何工作。
将用于服务的Twilio号码将把所有来电(通过HTTP请求)路由到一个Node.js应用程序,该应用程序将使用Twilio的标记语言(TwiML)来指示和处理这些电话。TwiML提供了一套简单的动词和名词,用来告诉Twilio如何处理你的电话。
第一个动词将被用来记录来电,它是 <Record>
它将记录一个来电5秒钟,然后返回一个包含录音的文件的URL。然后这个URL将被传递给一个函数,该函数将尝试从音频文件中识别歌曲。
录音电话或语音信息有各种法律上的考虑,你必须确保在录制任何东西时遵守当地、州和联邦法律。
音频文件将被下载,并为(非官方的)Shazam API正确格式化。来自Twilio的音频文件将是一个WAV文件,API要求它是44100赫兹采样的原始数据,所以将使用一个第三方软件包来正确转换该文件。然后,原始数据将作为一个字节数组的Base64编码字符串被发送到API。
然后Shazam API将尝试从Base64字符串中识别歌曲,如果成功的话,将返回歌曲信息(歌曲名称、艺术家、专辑、封面等等)。该 <Hangup>
动词将挂断电话,然后应用程序将发送歌曲信息的短信给呼叫者。
如果歌曲没有被检测到,动词将把电话转回 <Redirect>
动词将把电话转回使用<Record>
动词的第一个功能,并试图识别歌曲的下一个5秒钟。这个循环将重复进行,直到歌曲被识别。
现在你已经了解了电话服务将如何工作,你可以开始构建它了
设置你的应用程序
创建你的项目结构
首先,在你的首选目录中建立项目的脚手架。在你的终端或命令提示符中,导航到你喜欢的目录并运行以下命令:
mkdir song-identifier
cd song-identifier
安装依赖项
下一步是启动一个全新的Node.js项目,并安装该项目所需的依赖项:
npm init -y
npm install twilio dotenv express wavefile axios
你将需要:
- 在
twilio
使用Twilio可编程语音和短信API的软件包,以接收电话和发送文本信息 dotenv
访问环境变量,这是你存储Twilio证书和RapidAPI密钥的地方,需要与这两个API互动。- 构建服务器的
express
包来构建你的服务器:你将在这里编写代码来捕捉和记录所有来电。 - 对于Shazam API,你将需要
wavefile
包来修改录音的声音数据,使其符合API要求的格式;API要求声音数据为44100Hz。 - 最后,需要一个
axios
包来向Shazam API发送请求。
接下来,用你喜欢的文本编辑器打开你的项目目录,创建两个新文件:index.js 和*.env*。
index.js文件是你编写电话服务代码的地方,.env文件将保存你的Rapid API密钥和Twilio凭证。
安全环境变量
打开*.env*文件,将以下几行放入文件中。
TWILIO_NUMBER=XXXXXXXXXX
TWILIO_ACCOUNT_SID=XXXXXXXXXX
TWILIO_AUTH_TOKEN=XXXXXXXXXX
RAPID_API_KEY=XXXXXXXXXX
你需要把XXXXXXXXXX
占位符替换成它们各自的值。
要获得你的Twilio号码、账户SID和Auth Token,请登录Twilio控制台,它将出现在你的仪表盘上。
在添加你的电话号码时,别忘了使用E.164格式 。
要获得你的RapidAPI密钥,请登录并前往开发者仪表板。然后,在左边标签上的 "我的应用程序"下拉菜单下,导航到你的默认应用程序(应该是自动为你创建的)。你的RapidAPI密钥应该被显示并列为应用密钥。
一旦你把所有的XXXXXXXXXX
占位符替换成各自的值,下一步就是建立电话服务。
创建电话服务
在本节中,你将在index.js文件中编写电话服务的代码,在这里你将创建两个路由:/record
和/identify
。
/record
途径将捕获一个来电,记录一个5秒钟的通话片段,然后将包含录音的文件的URL传递给/identify
途径。该路由将是重新编排的函数。 */identify*
路由将是重新格式化音频文件的函数,并通过Shazam API识别它。
打开index.js 文件,将以下代码放入该文件。
require('dotenv').config();
const twilio = require('twilio')(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
const WaveFile = require('wavefile').WaveFile;
const VoiceResponse = require('twilio').twiml.VoiceResponse;
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.urlencoded({
extended: true
}));
这段代码将初始化你之前安装的dotenv
,twilio
,wavefile
,express
, 和axios
包。
录制电话
在初始化包的下面,复制并粘贴以下代码:
app.post('/record', async (req, res) => {
const twiml = new VoiceResponse();
twiml.record({
action: '/identify',
maxLength: '5',
});
res.type('text/xml');
res.send(twiml.toString());
});
这段代码实现了/record
路由,每当向你服务器上的端点发出POST请求时,就会调用该路由。每当从你的Twilio号码收到一个电话,就会发出这个请求。
上面这段代码用TwiML的语音响应对象创建了一个叫做twiml
的变量。 创建这个变量后,TwiML用来指示Twilio通过<Record>
这个动词来记录电话。maxLength
属性告诉Twilio将电话记录5秒,action
属性告诉Twilio在电话记录后将其重定向到/identify
路径上
然后通过HTTP响应将指令发回给Twilio。
从录音中识别出歌曲
在你刚刚实现的/record
路由下面,复制并粘贴以下代码:
app.post('/identify', async (req, res) => {
const twiml = new VoiceResponse();
let response;
// Fetch recording by URL.
// Request needs to be polled since recording may be processing
const delay = ms => new Promise(res => setTimeout(res, ms));
while(true) {
await delay(1000);
response = await axios.get(req.body.RecordingUrl, { responseType: 'arraybuffer' }).catch(err => {});
if(response) break;
}
// Reformat recording for API
wav = new WaveFile();
wav.fromBuffer(response.data);
wav.toSampleRate(44100);
const wavBuffer = wav.toBuffer()
const base64String = new Buffer.from(wavBuffer).toString('base64');
// If track is identified, send sms of track info. Else, record and identify the next 5 seconds of the song
const track = await fetchTrack(base64String)
if(track) {
sendSMS(track, req.body.Caller)
await twiml.hangup()
}
else {
twiml.redirect('/record')
}
res.type('text/xml');
res.send(twiml.toString());
});
包含录音的文件的URL被传递到发送到/identify
的请求正文中,并存储在req.body.RecordingUrl
变量中。然后,axios
被用来发送HTTP GET请求来抓取该文件。
你可能想知道为什么对RecordingUrl
的请求要通过一个循环轮询。在某些情况下,对URL的立即请求可能会失败,因为录音可能仍处于处理阶段。
<Record>
动词提供了recordingStatusCallback
,它可以发出一个HTTP请求,并在录音可以访问时运行/identify
路线。然而,应用程序将需要决定在尝试识别歌曲后如何处理该电话。电话不能被重定向到这个回调方法。它只会被重定向到action
属性中的URL。
然后文件以44,100赫兹重新取样,然后转换为原始数据,再转换为Base 64字符串。这个字符串(base64String
)然后被传入fetchTrack()
函数,以使用Shazam API识别歌曲。
如果歌曲被识别,轨道将被返回,sendSMS()
函数将被用来发送歌曲信息给调用者。如果歌曲没有被识别,<Redirect>
动词将被调用,以重定向调用到/record
来识别歌曲的下一个5秒。
辅助功能
在*/identify*路线的下面,放置fetchTrack()
函数:
async function fetchTrack(base64String) {
const options = {
method: 'POST',
url: 'https://shazam.p.rapidapi.com/songs/v2/detect',
headers: {
'content-type': 'text/plain',
'X-RapidAPI-Key': process.env.RAPID_API_KEY,
'X-RapidAPI-Host': 'shazam.p.rapidapi.com'
},
data: base64String,
};
const response = await axios.request(options)
.catch(function (error) {
console.error(error);
});
if(response.data.matches.length) return response.data.track;
else return null;
}
这个函数(在/identify
路由中使用)将向Shazam API的 /songs/v2/detect端点发送一个POST请求。这个请求的主体将包含位于base64String
变量中的音频记录的采样原始数据。如果API返回匹配的歌曲,它将返回该歌曲的信息。
接下来,将最后的代码块附加到index.js文件中:
async function sendSMS(track, caller) {
twilio.messages
.create({
body: `Song detected: ${track.title} - ${track.subtitle}\n\n${track.url}`,
from: process.env.TWILIO_NUMBER,
mediaUrl: [track.images.coverart],
to: caller
}).then(message => console.log(message.sid));
}
app.listen(3000, () => {
console.log(`Listening on port 3000`);
});
这个代码块包括sendSMS()
函数(在/identify
路径中使用),并从Shazam API中接收一个歌曲轨道。短信将包含歌曲名称、艺术家、封面和歌曲的Shazam URL,将被发送给调用者。
代码块的最后一点将启动Express服务器并监听3000端口的请求。
部署电话服务
在生产环境中,建议在云服务器上运行你的Node.js应用程序。然而,为了简化本教程的部署,你将在自己的电脑上部署你的应用程序。
ngrok将被用来将你的Express服务器连接到互联网上,它将生成一个公共的URL,将所有的请求直接传输到你的电脑上。这个公共URL将被配置到你的Twilio控制台的Twilio号码上,这样所有的电话将被路由到你的应用程序。
回到你的终端,运行以下命令:
node index.js
这个命令将运行index.js文件,它将在你的电脑的3000端口上启动一个本地Express服务器。
在终端上写一个新标签,导航到你的项目目录,并运行以下命令:
ngrok http 3000
然后,你的终端将看起来像下面这样:
你会看到ngrok已经生成了两个转发URL到你的3000端口的本地服务器(在某些情况下,可能只显示一个URL)。复制其中一个URL--建议使用https URL,因为它是加密的--因为其中一个需要插入到你的Twilio号码的信息设置中。
导航到你的Twilio控制台的**活动号码** 部分 。你可以从控制台的左边标签点击电话号码>管理>活动号码。
现在,点击你想用于电话服务的Twilio号码,向下滚动到语音和传真部分。在A CALL COMES IN下面,在第一个下拉框中选择Webhook,然后在下一个文本框中,输入你的转发URL,后面是"/record"(见下面的URL应该是什么样子)。
一旦你配置好了你的Twilio号码,以参考你的Express服务器,点击蓝色的保存按钮。
一旦保存完毕,你的歌曲识别器电话服务就可以使用了!开始播放一首歌曲,然后拨打你的Twilio号码,并把你的手机放在扬声器附近。一旦电话挂断,你会得到一个短信回复,看起来像这样。
进一步改进
这项电话服务不仅复制了Shazam的 "2580 "服务,还进行了一些升级。该电话以5秒为单位记录音频,一旦从其中一个记录中检测到歌曲,就会挂断,而不是只记录30秒就挂断。这项服务还输出歌曲的封面和Shazam链接,而不仅仅是歌名和名称。
尽管这项电话服务是一个伟大的开始,但仍有改进的余地。音频传输的电话标准被固定为8位PCM,采样率为8000hz。与使用语音备忘录应用程序的语音记录相比,这种音频的质量非常差,所以在很多情况下,歌曲的传输效果很差,无法检测。
为了提高音频质量,可以使用WhatsApp Business API与Twilio将电话服务转换为WhatsApp服务,因为语音备忘录可以通过WhatsApp录制。然后,这个语音备忘录可以发送到你在WhatsApp上的Twilio号码,在那里它可以被你的Node.js应用程序读取。
另一个改进是,当录音还没有准备好时,删除对录音URL的轮询请求。该请求实际上会因为其他原因而失败,并无限循环,这并不好。
为了解决这个问题,所有的调用都可以用录音的URL和它的状态进行缓存。/record
路由可以被修改,这样一旦它完成了录音,它就会通过将action
设置为空白来继续路由到自己。该 recordingStatusCallback
可以用来更新缓存中的状态,所以一旦录音被处理,action
,然后可以改变为*/identify* 路由。
总结
祝贺你!你刚刚通过Twilio建立了一个类似Shazam的电话服务!🎉
尽管下载和使用Shazam应用程序要容易得多,但如果有Twilio在身边,那就真的没有什么乐趣了。我希望你能从这个教程中得到一些乐趣,并在此过程中学到一些东西