步骤8: 对指令做出反应(VSK Fire TV)
                
                
                
                    
                    
                    
                     
                     
                      
步骤8: 对指令做出反应(VSK Fire TV)
    
    
 仅限VSK应用的集成
→
→
→
→
→
→
→
→
→
 
    
 警告: 视频技能工具包 (VSK) 不再受支持。如果对现有集成有疑问,请联系您的亚马逊技术客户经理。
在清单中添加BroadcastReceiver后,Fire TV将通过Alexa指令将任何意图发送到您指定的BroadcastReceiver类,例如AlexaDirectiveReceiver。在这个BroadcastReceiver类中,您接受和处理Alexa发送到您应用的指令。在这个类中,您需要开发自己的处理逻辑。
示例应用注意事项示例应用已经对指令做出了反应,因此在此步骤中不需要进行任何编码。您可以查看示例应用如何处理
AlexaDirectiveReceiver类中的指令(位于
java/com/example/vskfiretv/company/receiver中)。展开本主题底部附近的“示例应用如何处理指令”按钮部分,查看示例应用处理逻辑的说明。
 
VSK指令
当用户通过Alexa发出语音请求时,VSK指令会通过VSK代理传达给您的应用。有关指令的更多详细信息,请参阅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代理将指令封装在一个意图中,该意图会传递到onReceive方法中:
  public void onReceive(final Context context, final Intent intent) {
    Log.i(TAG, MessageFormat.format("处理来自VSK代理:{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代理公开的不同属性。)
  一系列 “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代理发送失败响应意图");
        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代理发送一个响应,指示已成功处理该指令。在示例应用中,您可以在AlexaDirectiveReceiver类中的指令处理后的条件语句中看到此逻辑。会将intent以及状态true传递到sendPendingIntentResponse函数中:
  //如果指令处理成功,则将PendingIntent发送回VSK代理
Log.i(TAG, "向VSK代理发送成功响应意图");
sendPendingIntentResponse(context, intent, true);
} else {
Log.i(TAG, "从VSK代理接收到空的指令");
}
该状态是一个布尔值,可以接受的值为true或false。(没有其他状态可用。)
  该状态作为VSKIntentConstants.EXTRA_DIRECTIVE_STATUS中的值添加,并作为PendingIntent的额外项添加,然后发送至VSK代理:
  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代理发送待定意图时出错", ex);
            }
        }
    }
 
后续步骤
转到下一步: 步骤9: 对应用签名并配置安全配置文件。