步骤3: 了解Alexa指令和Lambda响应 (VSK Echo Show)
在上一部分中,即步骤2: 在多模式设备上启用您的视频技能并进行测试,您从Alexa云向您的Lambda发起了请求,并观察了CloudWatch中记录的请求和响应。现在,您正在查看CloudWatch中的日志,观察Alexa发送的指令和Lambda提供的响应,让我们分析下此代码,更详细地解释发生了什么。
基本工作流程
工作流程(如架构概述中所述)从用户对多模式设备说出短语开始,例如说出“Play [media title]”(播放 [媒体作品])。Alexa的自然语言处理智能负责解析用户的表述和明确用户的意图。然后,Alexa将用户的意图打包成指令。以下指令用于多模式设备交互:
GetPlayableItemsGetPlayableItemsMetadataGetBrowseNodeItemsGetDisplayableItemsGetDisplayableItemsMetadataGetBrowseNodeItemsGetNextPage
不同的场景(搜索、播放、频道更改等)会提示发送不同的指令。这些场景和发送的指令在Alexa发送的指令和参考文档中进行了定义。
当用户说“Play the movie Manchester by the Sea”(播放电影Manchester by the Sea)时,Alexa会识别所有包含媒体的目录,然后向这些目录的相应Lambda函数发送GetPlayableItems指令。GetPlayableItems指令如下所示:
Alexa请求: GetPlayableItems
{
"directive": {
"header": {
"correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==",
"messageId": "9f4803ec-4c94-4fdf-89c2-d502d5e52bb4",
"name": "GetPlayableItems",
"namespace": "Alexa.VideoContentProvider",
"payloadVersion": "3"
},
"endpoint": {
"scope": {
"type": "BearerToken",
"token": "access-token-from-skill"
},
"endpointId": "videoDevice-001",
"cookie": {
}
},
"payload": {
"entities": [
{
"type": "Video",
"value": "Manchester by the Sea",
"externalIds": {
"imdb": "tt4574334"
}
}
],
"contentType": "RECORDING",
"locale": "en-US",
"minResultLimit": 8,
"maxResultLimit": 25,
"timeWindow": {
"end": "2016-09-07T23:59:00+00:00",
"start": "2016-09-01T00:00:00+00:00"
}
}
}
}
指令名称出现在header块中。您可以看到这是一个GetPlayableItems指令。
您的Lambda将此指令作为事件接收。您的Lambda代码需要执行任何必要的查询,以确定哪些媒体标题与该请求相匹配(基于指令中的payload)。作为查询的一部分,您的Lambda可能会识别与用户请求相关的其他媒体标题。
然后,您的Lambda向Alexa返回符合该指令类型响应要求的响应。对于GetPlayableItems指令,GetPlayableItemsResponse如下所示:
Lambda响应: GetPlayableItemsResponse
{
"event": {
"header": {
"correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==",
"messageId": "5f0a0546-caad-416f-a617-80cf083a05cd",
"name": "GetPlayableItemsResponse",
"namespace": "Alexa.VideoContentProvider",
"payloadVersion": "3"
},
"payload": {
"nextToken": "fvkjbr20dvjbkwOpqStr",
"mediaItems": [
{
"mediaIdentifier": {
"id": "recordingId://provider1.dvr.rp.1234-2345-63434-asdf"
}
},
{
"mediaIdentifier": {
"id": "recordingId://provider1.dvr.rp.1234-2345-63434-asdf"
}
}
]
}
}
}
在此代码中,有两个不同的mediaIdentifier值与用户的请求相匹配。这些mediaIdentifier属性的值与您目录中的内容ID相对应。
在Alexa收到GetPlayableItemsResponse响应后,Alexa可能会要求用户澄清用户想要播放哪个媒体作品,或者用户想通过哪个视频提供方播放媒体(如果有多个提供方拥有相同的媒体)。
在解析用户想要播放的媒体后,Alexa将向您的Lambda发送另一条名为GetPlayableItemsMetadata的指令。该指令要求您的Lambda提供有关所选媒体作品的更多详细信息。Alexa需要这些信息来显示有关该媒体作品的信息(mediaIdentifier的值,例如recordingId://provider1.dvr.rp.1234-2345-63434-asdf,对Alexa而言没有意义)。您需要提供与此mediaIdentifier相关的信息,此类信息指明要在用户屏幕上显示的内容,例如标题、缩略图、时长、评分等。
Alexa发送的GetPlayableItemsMetadata可能如下所示:
Alexa请求: GetPlayableItemsMetadata
{
"directive": {
"header": {
"correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==",
"messageId": "0f918d6e-ebae-48f1-a237-13c6f5b9f5da",
"name": "GetPlayableItemsMetadata",
"namespace": "Alexa.VideoContentProvider",
"payloadVersion": "3"
},
"endpoint": {
"scope": {
"type": "BearerToken",
"token": "access-token-from-skill"
},
"endpointId": "videoDevice-001",
"cookie": {
}
},
"payload": {
"locale": "en-US",
"mediaIdentifier": {
"id": "recordingId://provider1.dvr.rp.1234-2345-63434-asdf"
}
}
}
}
您可以在这里看到payload标识了用户想要播放的特定mediaIdentifier。
然后,您的Lambda会检索所需的有关此mediaIdentifier的信息,并返回GetPlayableItemsMetadataResponse响应,其中包含有关它的更多信息。响应可能如下所示:
Lamba响应: GetPlayableItemsMetadataResponse
{
"event": {
"header": {
"correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==",
"messageId": "38ce5b22-eeff-40b8-a84f-979446f9b27e",
"name": "GetPlayableItemsMetadataResponse",
"namespace": "Alexa.VideoContentProvider",
"payloadVersion": "3"
},
"payload": {
"searchResults": [
{
"name": "Interstellar",
"contentType": "ON_DEMAND",
"series": {
"seasonNumber": "1",
"episodeNumber": "1",
"seriesName": "The Big Bang Theory",
"episodeName": "Pilot"
},
"playbackContextToken": "{\"streamUrl\": \"http:\/\/samplemediasite.com\/sample\/video.mp4\", \"title\": \"Some Video Title\"}",
"parentalControl": {
"pinControl": "REQUIRED"
},
"absoluteViewingPositionMilliseconds": 1232340
}
]
}
}
}
然后,Alexa将playbackContextToken传递至您的网页播放器,该网页播放器随后将该标识符转换为媒体播放URL并加载媒体。
交互的类比
为了更具体地说明这种交互,可以参考下面的比喻。一位顾客走进一家音像店对店员说:“我想看To Kill a Mockingbird”。 店员将请求转达给了一名后室工作人员,该工作人员浏览了媒体库并找到了Mockingbird媒体部分。他发现这个媒体有多个匹配项,版本和内容变化各不相同,有些与Gregory Peck的相应电影匹配,还有一些与Hunger Games中的Mockingjay匹配。
后室工作人员将信息返回给店员,然后店员问客户:“在这些媒体作品中,您真正想要的是哪个?“ 客户说:“我想要第一个(Gregory Peck的相关电影)。“ 然后,店员转向后室工作人员,说明客户想要Gregory Peck的媒体作品。后室工作人员检索了有关Gregory Peck作品的所有详细信息,并将这些信息返回给店员。店员将媒体加载到播放器中,然后为客户播放。
简而言之,用户的请求会转换为发送给Lambda的GetPlayableItems指令。您的Lambda会使用列出匹配作品的GetPlayableItemsResponse进行响应。Alexa以GetPlayableItemsMetadata指令对用户选择的作品进行回复,而您的Lambda以包含所有播放详细信息的GetPlayableItemsMetadataResponse进行回复。
具体如何编写Lambda代码和执行必要的后端服务来检索正确的数据完全取决于您。本文档不会提供说明如何与您的后端服务进行交互以收集和生成所需响应的教程,因为每个合作伙伴的代码和后端服务都不同,这些后端服务因合作伙伴不同而有很大差异。
逐部分浏览示例Lambda
以下各部分将解释示例Lambda函数中的逻辑。步骤1: 创建您的视频技能和Lambda函数中提供的示例Lambda函数(更具体地讲是在步骤1.3: 为您的视频技能创建Lambda函数提供),会尝试展示数个指令类型所需的响应。让我们逐部分地分析Lambda函数。
hardCodedResponse函数绕过了您的后端服务需要执行的查询。为了处理传入您的Lambda的事件,您的代码需要与一些后端服务进行接合以执行查找、搜索等,并返回所需的信息。详细代码可能包含许多与您自己的实现无关的逻辑,因此为了避免造成困惑,我们只是在此处对响应进行了硬编码,以向您展示响应的要求。另外,请注意,您可以提供多种语言的Lambda代码。有关使用其他语言的说明,请参阅以下AWS Lambda文档主题:
- 使用Node.js构建Lambda函数
- 使用Python构建Lambda函数
- 使用Ruby构建Lambda函数
- 使用Java构建Lambda函数
- 使用Go构建Lambda函数
- 使用C#构建Lambda函数
- 使用PowerShell构建Lambda函数
本教程使用的是Node JS。以下是Lambda的完整示例。在代码之后,我们将逐个部分予以说明。
// 第1部分开始
var AWS = require('aws-sdk');
exports.handler = (event, context, callback) => {
console.log("交互开始");
hardCodedResponse(event, context);
};
// 第1部分结束
// 第2部分开始
function hardCodedResponse(event, context) {
var name = event.directive.header.name;
console.log("Alexa请求:", name, JSON.stringify(event));
// 第2部分结束
// 第3部分开始
var DiscoverResultResponse = {
"event": {
"header": {
"namespace": "Alexa.Discovery",
"name": "Discover.Response",
"payloadVersion": "3",
"messageId": "ff746d98-ab02-4c9e-9d0d-b44711658414"
},
"payload": {
"endpoints": [{
"endpointId": "ALEXA_VOICE_SERVICE_EXTERNAL_MEDIA_PLAYER_VIDEO_PROVIDER",
"endpointTypeId": "TEST_VSK_MM",
"manufacturerName": "TEST_VSK_MM",
"friendlyName": "TEST_VSK_MM",
"description": "TEST_VSK_MM",
"displayCategories": ["APPLICATION"],
"cookie": {},
"capabilities": [{
"type": "AlexaInterface",
"interface": "Alexa.RemoteVideoPlayer",
"version": "1.0"
}, {
"type": "AlexaInterface",
"interface": "Alexa.PlaybackController",
"version": "1.0"
}, {
"type": "AlexaInterface",
"interface": "Alexa.SeekController",
"version": "1.0"
}, {
"type": "AlexaInterface",
"interface": "Alexa.ChannelController",
"version": "1.0"
},
{
"type": "AlexaInterface",
"interface": "Alexa.MultiModalLandingPage",
"version": "1.0"
}]
}]
}
}
};
var GetPlayableItemsResponse = {
"event": {
"header": {
"correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==",
"messageId": "5f0a0546-caad-416f-a617-80cf083a05cd",
"name": "GetPlayableItemsResponse",
"namespace": "Alexa.VideoContentProvider",
"payloadVersion": "3"
},
"payload": {
"nextToken": "fvkjbr20dvjbkwOpqStr",
"mediaItems": [{
"mediaIdentifier": {
"id": "tt1254207"
}
}]
}
}
};
var GetPlayableItemsMetadataResponse = {
"event": {
"header": {
"correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==",
"messageId": "38ce5b22-eeff-40b8-a84f-979446f9b27e",
"name": "GetPlayableItemsMetadataResponse",
"namespace": "Alexa.VideoContentProvider",
"payloadVersion": "3"
},
"payload": {
"searchResults": [{
"name": "Big Buck Bunny",
"contentType": "ON_DEMAND",
"series": {
"seasonNumber": "1",
"episodeNumber": "1",
"seriesName": "Blender Foundation视频",
"episodeName": "Pilot"
},
"playbackContextToken": "{\"streamUrl\": \"http:\/\/commondatastorage.googleapis.com\/gtv-videos-bucket\/sample\/BigBuckBunny.mp4\", \"title\": \"Big Buck Bunny\"}",
"parentalControl": {
"pinControl": "REQUIRED"
},
"absoluteViewingPositionMilliseconds": 1232340
}]
}
}
};
var GetDisplayableItemsResponse = {
"event": {
"header": {
"correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==",
"messageId": "5f0a0546-caad-416f-a617-80cf083a05cd",
"name": "GetDisplayableItemsResponse",
"namespace": "Alexa.VideoContentProvider",
"payloadVersion": "3"
},
"payload": {
"nextToken": "fvkjbr20dvjbkwOpqStr",
"mediaItems": [{
"mediaIdentifier": {
"id": "tt1254207"
}
}, {
"mediaIdentifier": {
"id": "tt0807840"
}
}]
}
}
};
var GetNextPageResponse = {
"event": {
"header": {
"correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==",
"messageId": "9f4803ec-4c94-4fdf-89c2-d502d5e52bb4",
"name": "GetNextPageResponse",
"namespace": "Alexa.VideoContentProvider",
"payloadVersion": "3"
},
"endpoint": {
"scope": {
"type": "BearerToken",
"token": "Alexa-access-token"
},
"endpointId": "appliance-001"
},
"payload": {
"nextToken": "qefjrfiugef74",
"mediaItems": [{
"mediaIdentifier": {
"id": "tt0807840"
}
},
{
"mediaIdentifier": {
"id": "tt1254207"
}
},
{
"mediaIdentifier": {
"id": "tt7993892"
}
},
{
"mediaIdentifier": {
"id": "tt2285752"
}
},
{
"mediaIdentifier": {
"id": "tt4957236"
}
}
]
}
}
}
var GetDisplayableItemsMetadataResponse = {
"event": {
"header": {
"correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==",
"messageId": "38ce5b22-eeff-40b8-a84f-979446f9b27e",
"name": "GetDisplayableItemsMetadataResponse",
"namespace": "Alexa.VideoContentProvider",
"payloadVersion": "3"
},
"payload": {
"resultsTitle": "SearchResults",
"searchResults": [{
"name": "Big Buck Bunny",
"contentType": "ON_DEMAND",
"itemType": "VIDEO",
"releaseYear": "2014",
"selectionAction": "PLAY",
"thumbnailImage": {
"contentDescription": "Big Buck Bunny图像",
"sources": [{
"url": "https:\/\/devportal-reference-docs.s3-us-west-1.amazonaws.com\/video-skills-kit\/bigbuckbunnythumb.png",
"size": "X_LARGE",
"widthPixels": 1920,
"heightPixels": 1280
}]
},
"runtime": {
"runTimeInMilliseconds": 5400000,
"displayString": "9m"
},
"closedCaption": {
"status": "AVAILABLE",
"displayString": "CC"
},
"absoluteViewingPositionMilliseconds": 0,
"parentalControl": {
"pinControl": "REQUIRED"
},
"viewingDisplayString": "PurchaseOptions",
"reviews": [{
"totalReviewCount": 1897,
"type": "FIVE_STAR",
"ratingDisplayString": "4.06"
}],
"rating": {
"category": "G"
},
"mediaIdentifier": {
"id": "tt1254207"
}
}]
}
}
};
// 第3部分结束
// 第4部分开始
if (name === 'Discover') {
console.log("Lambda响应: DiscoverResultResponse", JSON.stringify(DiscoverResultResponse));
context.succeed(DiscoverResultResponse);
} else if (name === 'GetPlayableItems') {
console.log("Lambda响应: GetPlayableItemsResponse", JSON.stringify(GetPlayableItemsResponse));
context.succeed(GetPlayableItemsResponse);
} else if (name === 'GetPlayableItemsMetadata') {
console.log("Lambda响应: GetPlayableItemsMetadataResponse", JSON.stringify(GetPlayableItemsMetadataResponse));
context.succeed(GetPlayableItemsMetadataResponse);
} else if (name === 'GetDisplayableItems') {
console.log("Lambda响应: GetDisplayableItemsResponse", JSON.stringify(GetDisplayableItemsResponse));
context.succeed(GetDisplayableItemsResponse);
} else if (name === 'GetDisplayableItemsMetadata') {
console.log("Lambda响应: GetDisplayableItemsMetadataResponse", JSON.stringify(GetDisplayableItemsMetadataResponse));
context.succeed(GetDisplayableItemsMetadataResponse);
}
else if (name === 'GetNextPage') {
console.log("Lambda响应: GetNextPageResponse", JSON.stringify(GetNextPageResponse));
context.succeed(GetNextPageResponse);
}
};
// 第4部分结束
第1部分解释
// 第1部分开始
var AWS = require('aws-sdk');
exports.handler = (event, context, callback) => {
console.log("交互开始");
hardCodedResponse(event, context);
...
}
// 第1部分结束
首先,我们声明了有关采用Node.js的适用于JavaScript的AWS SDK的依赖项。该SDK可让您的Node JS代码在AWS内部执行许多功能,您可以在AWS SDK中查阅这些功能。
在采用Node.js的AWS Lambda函数处理程序中解释了handler方法。当调用Lambda函数时,AWS Lambda通过调用handler函数开始执行代码。AWS Lambda将任何事件数据作为第一个参数传递给该handler。运行时将三个参数传递至handler方法:event、context以及callback:
event: 第一个参数是event对象,它包含来自调用者的信息。在本例中,调用者是Alexa指令。Alexa在调用Invoke时将此指令作为JSON格式的字符串传递。context: 第二个参数是context对象,它包含有关引用、函数和执行环境的信息。callback: 第三个参数callback是一个函数,您可以在非异步函数中调用它来发送响应。callback函数采用两个参数:错误和响应。响应对象必须与JSON.stringify兼容。
您的处理程序应处理传入的事件数据,并且可以调用代码中的任何其他函数/方法。
要查看event和context,您可以将其记录到控制台:
console.log("Alexa请求:" + JSON.stringify(event, null, 2));
console.log("上下文:" + JSON.stringify(context, null, 2));
将event作为hardCodedResponse函数的一部分进行记录;示例Lambda中根本没有记录context。
event和context是JSON对象。JSON.stringify将JSON对象渲染为字符串。JSON.stringify方法采用多个参数:对象、替换者(此处未使用)和间距值。
这里的context不一定很重要,但它显示了调用的Lambda函数的名称、日志流、使用的内存和其他详细信息。在Cloudwatch中,我们工作流程中事件的context如下所示:
Context:
{
"callbackWaitsForEmptyEventLoop": true,
"functionVersion": "$LATEST",
"functionName": "hawaii_echo_lambda",
"memoryLimitInMB": "128",
"logGroupName": "/aws/lambda/hawaii_echo_lambda",
"logStreamName": "2019/06/21/[$LATEST]2eaa24e01fff497187f6d0fcc2230e8d",
"invokedFunctionArn": "arn:aws:lambda:us-east-1:458179560631:function:hawaii_echo_lambda",
"awsRequestId": "1b0c6361-bbe5-440b-95cb-3024f3abfa53"
}
您可以看到hawaii_echo_lambda是调用的Lambda,而且Cloudwatch中的日志在/aws/lambda/hawaii_echo_lambda中分组。
在此初始handler方法之后,hardCodedResponse(event, context);函数将运行,并将event和context作为参数。下一部分将解释此函数。
第2部分解释
// 第2部分开始
function hardCodedResponse(event, context) {
var name = event.directive.header.name;
console.log("Alexa请求:", name, JSON.stringify(event));
// 第2部分结束
...}
示例Lambda代码包含一个名为hardCodedResponse的函数,该函数将event和context作为参数传递。这显示了如何从传入事件(Alexa请求)中获取信息,以及如何为该事件中包含的信息设置变量。
例如,示例Lambda代码设置了一个名为name的变量来存储event.directive.header.name的值。
根据需要,您可以将指令中的任何属性设置为您在查找中使用的某个变量。您需要的信息(以及如何操作该信息)取决于您要执行的任务。例如,您可能需要根据电影作品执行查询。因此,您可能需要将某些详细信息馈送至后端服务的查找函数中。
第3部分解释
此时,示例Lambda代码仅为预定义的响应定义变量,它将使用这些变量来响应指令。例如:
// 第3部分开始
var DiscoverResultResponse = {
...
};
var GetPlayableItemsResponse = {
...
};
var GetPlayableItemsMetadataResponse = {
...
};
var GetDisplayableItemsResponse = {
...
};
var GetNextPageResponse = {
...
};
var GetDisplayableItemsMetadataResponse = {
...
};
// 第3部分结束
如前所述,示例Lambda对这些响应进行了硬编码。在实际实现中,您需要通过后端服务动态检索所需的信息。例如,根据传入的有效负载,您将获取信息并将其插入自己的逻辑中,以解析用户的请求。
此处的示例Lambda没有带媒体信息等的后端服务,因此响应只是预定义的。因此,如果您在测试中使用此示例Lambda来探索视频技能在多模式设备上的运作方式,则只能查询此处定义的内容。
未来本文档中可能会提供更具动态性的示例Lambda。但是,由于合作伙伴之间不同的后端服务和编程语言,查找过程会有很大差异,因此查询后端服务的额外代码可能没有什么参考价值。
第4部分解释
// 第4部分开始
if (name === 'Discover') {
console.log("响应", JSON.stringify(discoverResult));
context.succeed(discoverResult);
} else if (name === 'GetPlayableItems') {
console.log("响应", JSON.stringify(getPlayableItems));
context.succeed(getPlayableItems);
} else if (name === 'GetPlayableItemsMetadata') {
console.log("响应", JSON.stringify(getPlayableItemsMetadata));
context.succeed(getPlayableItemsMetadata);
} else if (name === 'GetDisplayableItems') {
console.log("响应", JSON.stringify(getDisplayableItems));
context.succeed(getDisplayableItems);
} else if (name === 'GetDisplayableItemsMetadata') {
console.log("响应", JSON.stringify(getDisplayableItemsMetadata));
context.succeed(getDisplayableItemsMetadata);
}
else if (name === 'GetNextPage') {
console.log("Lambda响应: GetNextPageResponse", JSON.stringify(GetNextPageResponse));
context.succeed(GetNextPageResponse);
}
};
// 第4部分结束
该函数的最后一部分根据指令名称返回相应的响应。如果指令是GetPlayableItems,则将在回调中把GetPlayableItemsResponse传回给Alexa。context.succeed方法将信息放入回调中。
在Cloudwatch日志中,查找以Lambda Response开头的行,以查看您的Lambda发回Alexa的响应。
后续步骤
继续执行步骤4: 了解您的网页播放器如何获取媒体播放URL中解释了该过程。
Last updated: 2020年11月2日

