步骤8: 对指令做出反应(VSK Fire TV)
步骤8: 对指令做出反应(VSK Fire TV)
仅限VSK应用的集成
→
→
→
→
→
→
→
→
→
在清单中添加BroadcastReceiver后,Fire TV将通过Alexa指令将任何意图发送到您指定的BroadcastReceiver类,例如AlexaDirectiveReceiver
。在这个BroadcastReceiver类中,您接受和处理Alexa发送到您应用的指令。在这个类中,您需要开发自己的处理逻辑。
示例应用注意事项示例应用已经对指令做出了反应,因此在此步骤中不需要进行任何编码。您可以查看示例应用如何处理
AlexaDirectiveReceiver
类中的指令(位于
java/com/example/vskfiretv/company/receiver
中)。展开本主题底部附近的“示例应用如何处理指令”按钮部分,查看示例应用处理逻辑的说明。
VSK指令
当用户通过Alexa发出语音请求时,VSK指令会通过VSK Agent传达给您的应用。有关指令的更多详细信息,请参阅API参考中的说明。
一旦您在BroadcastReceiver中收到指令,您需要自己在应用中完成指令的请求。通常设计的model-view-viewmodel(MVVM)应用一般会让这一点变得相当简单,因为大多数应用都有主视图模型,该模型已围绕应用路由远程点击和搜索查询。将相关字符串从BroadcastReceiver传递到视图模型通常是在应用中执行VSK指令所需的全部操作。
例如,当用户说 “Find comedy movies”(查找喜剧电影)时,您可能会收到SearchAndDisplayResults
指令,该指令针对MediaType: Movie
以及Genre: Comedies
。如果您的应用支持搜索,您就已经有了一种机制,可让用户用遥控器将文本输入到输入字段中,并搜索他们所输入的内容。您可以通过VSK使用相同的搜索查询机制,针对从指令解析的 “comedy movies” 执行操作。
对指令做出反应
Alexa发送的指令取决于您声明的功能。自定义BroadcastReceiver类以处理应用中的指令(参见步骤7: 添加BroadcastReceiver)。具体操作为:
为认证需要的指令
Alexa可以为许多不同的请求发送指令。是否必须对它们全部进行处理? 如果您的应用不能处理某些指令,您的应用会让认证失败吗? 在每个指令的表述列表中,如果需要支持某个表述,则此表述下方会出现 “Required for certification”(需要认证)字样。
所需的表述包括标题、演员、流派、系列片的搜索,以及传输控制表述。如果表述被标记为需要认证,但您的应用不支持该功能,您的应用不会让认证失败,您可以无视该要求。对于可选指令(如ChangeChannel
),只有当您选择处理该指令时,所需指令才算是要求。
API参考
API参考文档中描述了实现和处理上述指令的预期逻辑。Alexa期望每个传入指令都有一个特定的响应和操作。更多详细信息,请参阅以下内容:
如果您正在实现一些其他接口(不建议),请参阅以下内容:
表述参考
有关Fire TV设备支持的各种表述(包括按区域设置划分的每个短语)的详细列表,请参阅表述参考。您可在此查看RemoteVideoPlayer
的表述:
其他接口的表述包括:
示例应用注意事项
有关示例应用如何处理指令的详细解释,请展开以下部分。
示例应用如何处理指令
在编写处理VSK指令的代码时,更仔细地关注示例应用中的AlexaDirectiveReceiver
类如何处理这些指令可能会有所帮助。
VSK Agent将指令封装在一个意图中,该意图会传递到onReceive
方法中:
public void onReceive(final Context context, final Intent intent) {
Log.i(TAG, MessageFormat.format("处理来自VSK Agent: {0} 的意图", intent));
if(context == null || intent == null) {
return;
}
//...
}
代码从指令中获取几个属性,并将它们设置为名为directiveNameSpace
、directiveName
、directivePayload
和directivePlayloadVersion
的字符串:
if (VSKIntentConstants.ACTION_ALEXA_DIRECTIVE.equals(intent.getAction())) {
final String directiveNameSpace = intent.getStringExtra(VSKIntentConstants.EXTRA_DIRECTIVE_NAMESPACE);
final String directiveName = intent.getStringExtra(VSKIntentConstants.EXTRA_DIRECTIVE_NAME);
final String directivePayload = intent.getStringExtra(VSKIntentConstants.EXTRA_DIRECTIVE_PAYLOAD);
final String directivePayloadVersion = intent.getStringExtra(VSKIntentConstants.EXTRA_DIRECTIVE_PAYLOAD_VERSION);
//...
}
EXTRA_DIRECTIVE_NAMESPACE
包含VSK API接口名称(例如RemoteVideoPlayer
)。EXTRA_DIRECTIVE_NAME
是接口的指令(例如SearchAndDisplayResults
)。EXTRA_DIRECTIVE_PAYLOAD
获取指令的payload
属性。而EXTRA_DIRECTIVE_PAYLOAD_VERSION
可获取接口版本(例如3
)。(不能打印出整个指令,只能打印出VSK Agent公开的不同属性。)
一系列 “if” 条件根据接收到的指令类型调用适当的函数来处理指令:
if (directiveName != null) {
if ("SearchAndPlay".equals(directiveName)) {
handleSearchAndPlay(directivePayload);
} else if ("SearchAndDisplayResults".equals(directiveName)) {
handleSearchAndDisplayResults(directivePayload);
} else if ("pause".equals(directiveName)) {
handlePause();
} else if ("play".equals(directiveName)) {
handlePlay();
} else if ("stop".equals(directiveName)) {
handleStop();
} else if ("next".equals(directiveName)) {
handleNext();
} else if ("previous".equals(directiveName)) {
handlePrevious();
} else if ("fastForward".equals(directiveName)) {
handleFastForward();
} else if ("rewind".equals(directiveName)) {
handleRewind();
} else if ("startOver".equals(directiveName)) {
handleStartOver();
} else if ("adjustSeekPosition".equals(directiveName)) {
handleAdjustSeekPosition(directivePayload);
} else if ("ChangeChannel".equals(directiveName)) {
handleChangeChannel();
} else if ("SendKeystroke".equals(directiveName)) {
handleSendKeystroke();
} else if("Test".equals(directiveName)) {
handleTestDirective();
} else {
Log.i(TAG, "未知的Alexa指令。向VSK Agent发送失败响应意图");
sendPendingIntentResponse(context, intent, false);
return;
}
//...
}
如果接收到SearchAndPlay
指令,将调用handleSearchAndPlay
函数,并将directivePayload
作为函数参数传入。如果接收到SearchAndDisplayResults
指令,将调用handleSearchAndDisplayResults
函数,并将directivePayload
作为函数参数传入。以此类推。
请注意,directivePayload
名称在各个接口中都是唯一的,因此您不需要将directiveNameSpace
与directivePayload
组合。
处理SearchAndPlay指令
在示例应用中,handleSearchAndPlay
函数将directivePayload
传递到另一个名为getMovieFromDirectivePayload
的方法中,以提取movieName
标题,然后将此movieName
传递到发送至PlaybackActivity
的播放意图中,以播放媒体:
private void handleSearchAndPlay(final String directivePayload) {
Log.i(TAG, "正在处理SearchAndPlay指令...");
final Movie movieToBePlayed = getMovieFromDirectivePayload(directivePayload);
final String movieName = movieToBePlayed.getTitle();
Log.d(TAG, "随机播放一些电影" + movieName);
final Intent playIntent = new Intent();
final String packageName = VSKReferenceApplication.getInstance().getApplicationContext().getPackageName();
playIntent.setClassName(packageName, packageName + ".PlaybackActivity");
playIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//PlaybackActivity要求当前选择一部电影,如果没有选择,请立即设置
playIntent.putExtra(DetailsActivity.MOVIE, movieToBePlayed);
VSKReferenceApplication.getInstance().getApplicationContext().startActivity(playIntent);
Log.i(TAG, "SearchAndPlay指令处理已完成");
}
getMovieFromDirectivePayload
函数通常会在数据源中查找匹配的电影名称,但为了让代码保持简单,在这个示例应用中,该函数只需获取MovieList
中编译列表内的第一部电影:
private Movie getMovieFromDirectivePayload(final String directivePayload) {
//在这里处理指令有效负载,并构建要播放的适当电影对象
//为了便于演示,抓取了示例电影列表中的第一项,没有和电影ID对应
final Movie someMovie = MovieList.getList().get(0);
return someMovie;
}
MovieList
是一个类(在java/com/example/vskfiretv/company
内部),它有大约5部不同的电影。
处理SearchAndDisplayResults指令
如果接收到SearchAndDisplayResults
指令,将调用handleSearchAndDisplayResults
函数,并将来自指令的directivePayload
作为函数参数传入。handleSearchAndDisplayResults
函数使用JsonParser
来获取用户转录的搜索请求(transcribed
)和entities
对象,该对象包含一组要播放的实体对象,如Title、Franchise、Actor、Team或MediaType。
然后,代码从MovieList
类中获取一个随机电影,收集该电影的所有元数据,并将其放入searchedMovies
对象中。它将此传递给mainActivity
,以显示用户搜索的结果。
private void handleSearchAndDisplayResults(final String searchPayload) {
Log.i(TAG, "正在处理SearchAndDisplayResults指令...");
final JsonParser jsonParser = new JsonParser();
final JsonElement searchPayloadJsonTree = jsonParser.parse(searchPayload);
if(searchPayloadJsonTree.isJsonObject()) {
final JsonObject searchPayloadJsonObject = searchPayloadJsonTree.getAsJsonObject();
final JsonObject searchTermJsonObject = searchPayloadJsonObject.getAsJsonObject("searchText");
final String searchText = searchTermJsonObject.get("transcribed").getAsString();
final JsonArray searchEntitiesJsonArray = searchPayloadJsonObject.getAsJsonArray("entities");
final Iterator<JsonElement> searchEntitiesIterator = searchEntitiesJsonArray.iterator();
final Random rand = new Random();
final List<Movie> movieList = MovieList.getList();
int moviesCount = movieList.size();
final List<Movie> searchedMovies = new ArrayList<>();
while (searchEntitiesIterator.hasNext()) {
final JsonElement entityElement = searchEntitiesIterator.next();
final JsonObject entity = entityElement.getAsJsonObject();
final String entityValue = entity.get("value").getAsString();
final String entityJsonString = entityElement.toString();
final Movie movie = movieList.get(rand.nextInt(moviesCount));
movie.setTitle(entityValue);
movie.setDescription(entityJsonString);
searchedMovies.add(MovieList.buildMovieInfo(movie.getMovieId(), movie.getTitle(),
movie.getDescription(), movie.getStudio(), movie.getVideoUrl(),
movie.getCardImageUrl(), movie.getBackgroundImageUrl()));
}
final VSKReferenceApplication myReferenceApp = VSKReferenceApplication.getInstance();
final Activity currentActivity = myReferenceApp.getCurrentActivity();
try {
final MainActivity mainActivity = (MainActivity) currentActivity;
Log.i(TAG, MessageFormat.format("显示 {0} 的搜索结果", searchText));
//只能从UI线程调用此项
mainActivity.runOnUiThread(() -> mainActivity.showResults(searchText, searchedMovies));
} catch (final Exception ex) {
Log.e(TAG, "无法在主屏幕上显示搜索结果", ex);
return;
}
} else {
Log.i(TAG, "搜索有效负载的json无效");
}
Log.i(TAG, "SearchAndDisplayResults指令处理完成");
}
重要须知: 示例应用中的逻辑仅用于简短演示目的,而不用于处理您可能在实际应用中使用的逻辑。在这些示例中,目的是演示如何从指令中获取信息,并将这些信息传递到处理请求的函数中,以及在应用中执行操作以满足请求。
应用处理完请求后,您需要向VSK Agent发送一个响应,指示已成功处理该指令。在示例应用中,您可以在AlexaDirectiveReceiver
类中的指令处理后的条件语句中看到此逻辑。会将intent
以及状态true
传递到sendPendingIntentResponse
函数中:
//如果指令处理成功,则将PendingIntent发送回VSK Agent
Log.i(TAG, "向VSK Agent发送成功响应意图");
sendPendingIntentResponse(context, intent, true);
} else {
Log.i(TAG, "从VSK Agent接收到空的指令");
}
该状态是一个布尔值,可以接受的值为true
或false
。(没有其他状态可用。)
该状态作为VSKIntentConstants.EXTRA_DIRECTIVE_STATUS
中的值添加,并作为PendingIntent的额外项添加,然后发送至VSK Agent:
private void sendPendingIntentResponse(final Context context, final Intent intent, final boolean directiveExecutionStatus) {
final PendingIntent pendingIntent = intent.getParcelableExtra(VSKIntentConstants.EXTRA_DIRECTIVE_RESPONSE_PENDING_INTENT);
if(pendingIntent != null) {
final Intent responseIntent = new Intent().putExtra(VSKIntentConstants.EXTRA_DIRECTIVE_STATUS, directiveExecutionStatus);
try {
pendingIntent.send(context, 0, responseIntent);
} catch (final PendingIntent.CanceledException ex) {
Log.e(TAG, "向VSK Agent发送待定意图时出错", ex);
}
}
}
后续步骤
转到下一步: 步骤9: 对应用签名并配置安全配置文件。