Developer Console

Step 9: Interpret and React to Alexa Directives (VSK Fire TV)

In this step, you will customize your Lambda code as needed for the directives you want to handle. At this point, it's assumed that you're no longer using the sample app but are instead working with your real app.

Declaring Supported Capabilities

The directives Alexa sends depends on the capabilities you declare in the Alexa Client Library configuration and your Lambda code. In this example, Alexa would send all directives:

// Create a list of supported capabilities in your skill.
List<String> capabilities = new ArrayList<>();
capabilities.add(AlexaClientManager.CAPABILITY_REMOTE_VIDEO_PLAYER);
capabilities.add(AlexaClientManager.CAPABILITY_PLAY_BACK_CONTROLLER);
capabilities.add(AlexaClientManager.CAPABILITY_SEEK_CONTROLLER);
capabilities.add(AlexaClientManager.CAPABILITY_CHANNEL_CONTROLLER);
capabilities.add(AlexaClientManager.CAPABILITY_RECORD_CONTROLLER);
capabilities.add(AlexaClientManager.CAPABILITY_VIDEO_RECORDER);
capabilities.add(AlexaClientManager.CAPABILITY_KEYPAD_CONTROLLER);

If you declare all capabilities, your Lambda would need to handle all of the incoming directives. (For additional information, see the Step 3: Integrate the Alexa Client Library.) The following table lists all the capabilities you can declare:

Alexa Client Library Capability Directive Capability Directives
CAPABILITY_CHANNEL_CONTROLLER Alexa.ChannelController ChangeChannel
CAPABILITY_REMOTE_VIDEO_PLAYER Alexa.RemoteVideoPlayer SearchAndDisplayResults
SearchAndPlay
CAPABILITY_PLAY_BACK_CONTROLLER Alexa.PlaybackController Pause
Play
Stop
Resume
Next
Previous
FastForward
Rewind
StartOver
CAPABILITY_SEEK_CONTROLLER Alexa.SeekController AdjustSeekPosition
CAPABILITY_KEYPAD_CONTROLLER Alexa.KeypadController SendKeystroke

In addition to listing the capabilities in your app's code, you must also list the capabilities in your Lambda code. When your Lambda receives a Discover directive, your Lambda must send a Discover.Response back to indicate which capabilities your skill supports. For example, here's a sample response to a Discover directive:

{
  "event": {
    "header": {
      "namespace":"Alexa.Discovery",
      "name":"Discover.Response",
      "payloadVersion":"3",
      "messageId":"2aa731f1-b6dd-471c-98fe-3ac5fa5d6554"
    },
    "payload": {
        "endpoints": [
            {
                "capabilities": [
                    {
                        "interface": "Alexa.RemoteVideoPlayer",
                        "type": "AlexaInterface",
                        "version": "1.0"
                    },
                    {
                        "type": "AlexaInterface",
                        "interface": "Alexa.PlaybackController",
                        "version": "3",
                        "supportedOperations" : ["Play", "Pause", "Stop", "StartOver", "Next", "Previous", "Rewind", "FastForward"]
                    }
                    {
                        "interface": "Alexa.SeekController",
                        "type": "AlexaInterface",
                        "version": "1.0"
                    },
                    {
                        "interface": "Alexa.ChannelController",
                        "type": "AlexaInterface",
                        "version": "1.0"
                    },
                      {
                        "interface": "Alexa.KeypadController",
                        "type": "AlexaInterface",
                        "version": "3",
                        "keys": [
                          "INFO", "MORE", "SELECT",
                          "UP", "DOWN", "LEFT", "RIGHT",
                          "PAGE_UP", "PAGE_DOWN", "PAGE_LEFT", "PAGE_RIGHT"
                        ]
                      }  
                ]
              }
          ]
       }
  }
};

See the Discovery interface for more details. In short, discovery about your skill's capabilities occurs both through your app and Lambda.

The details for implementing and handling each of the above directives are detailed in the API reference documentation. Alexa expects a specific response and action for each incoming directive.

Note that most apps don't implement every capability within the Video Skill API. For example, changing channels might not be relevant if your app does not have channels. Recording might not be relevant if your app does not have recording capabilities, and so on.

Customize Your Handling of Directives

Open the index.js file from the firetv-lambda project again (as explained in Step 6: Create and Deploy a Lambda Package ). In this index.js file, you will see placeholders for custom logic that you must write yourself. For example, you'll see inline comments such as these:

// Partner-specific logic to handle the the directives goes here
// Code for sending directive from the lambda to the app goes here

In every place you see these notes about the need for partner-specific logic, write the logic to handle these directives. See the API reference for details about each of the directives, their payloads, expected actions, and responses.

The recommended approach is to take directives received by your Lambda function and send these directives to your app through ADM. Any further processing can occur within your app. This is how the sample app handles the directives.

For example, in the sample app, if you open TenFootApp.java and browse to line 119, you'll see the following code that handles the incoming directives ADM sends to the app:

if (directive != null) {
       String directiveName = directive.getDirective().getHeader().getName();
       if ("SearchAndPlay".equals(directiveName)) {

           Content playContent = null;
           String directivePayload = directive.getDirective().getPayload().getEntities().get(0).getValue().toLowerCase();

           List<ContentContainer> containers = browser.getContentLoader().getRootContentContainer().getContentContainers();
           for(ContentContainer container : containers) {
               if(null != playContent) break;
               for(Content c : container.getContents()) {
                   // Checking against first payload entity only
                   if(c.getTitle().toLowerCase().contains( directive.getDirective().getPayload().getEntities().get(0).getValue().toLowerCase() )) {
                       playContent = c;
                       break;
                   }
               }
           }

           // Play video if we find some content matching directive payload
           if (playContent == null) {
               playContent = containers.get(0).getContents().get(0);
           }
           if(null != playContent) {
               browser.switchToRendererScreen(playContent, ContentBrowser.CONTENT_ACTION_WATCH_FROM_BEGINNING);
           } else {
               // search for the content
               searchForContent(app, browser, directivePayload);
           }

       } else if ("SearchAndDisplayResults".equals(directiveName)) {
           String directivePayload = directive.getDirective().getPayload().getEntities().get(0).getValue().toLowerCase();
           searchForContent(app, browser, directivePayload);
       } else if ("Pause".equals(directiveName)) {
           try {
               PlaybackActivity playbackActivity = (PlaybackActivity) app.getCurrentActivity();
               if(playbackActivity.isPlaying()) {
                   playbackActivity.runOnUiThread(new Runnable() {
                       @Override
                       public void run() {
                           playbackActivity.pause();
                       }
                   });
               }
           } catch (Exception castE) {
               Log.e(TAG, "Could not cast to PlayBackActivity!");
               return;
           }

       }  else if ("Play".equals(directiveName) ) {
           try {
               PlaybackActivity playbackActivity = (PlaybackActivity) app.getCurrentActivity();
               playbackActivity.runOnUiThread(new Runnable() {
                   @Override
                   public void run() {
                       playbackActivity.play();
                   }
               });
           } catch (Exception castE) {
               Log.e(TAG, "Could not cast to PlayBackActivity!");
               return;
           }

       }

   }

}

/**
* @param searchQuery - Search text from the directive
*/
private void searchForContent(TenFootApp app, ContentBrowser browser, String searchQuery) {

   browser.switchToScreen(ContentBrowser.CONTENT_SEARCH_SCREEN);
   // Wait upto 2 sec for the screen to switch
   for(int i = 0; i < 5; i++)  {
       Activity newActivity = app.getCurrentActivity();
       if(newActivity instanceof ContentSearchActivity) break;
       try {
           Thread.sleep(400);
       } catch (InterruptedException e) {
           return;
       }

   }

   ContentSearchActivity searchActivity = (ContentSearchActivity) app.getCurrentActivity();

   searchActivity.runOnUiThread(new Runnable() {
       @Override
       public void run() {
           searchActivity.getmFragment().setSearchQuery(searchQuery, true);
       }
   });
}

}

In this implementation, the code gets the payload from the first item in the entities array and plays it if available; otherwise, it searches for the content. The index.js file has other inline comments that provide tips and instructions for customizing your Lambda function.

As you update the Lambda code, note that responses back to Alexa should not be modified, only code to process/send directives. Also note that you should send directives from your Lambda function to your app, but you should not try to send messages from your app back to the Lambda function beyond the simple success response. Success responses from your Lambda function are only meant to indicate that the message was received by Lambda, not your app.

Uploading the Finalized Lambda

After you finish writing the logic to handle the directives specified in the previous section, repeat the steps in Create a Lambda Deployment Package (running zip -r firetv-lambda.zip .) and upload the generated zip into file into your Lambda in AWS. Make sure you test and validate any code before using it in production.

Next Steps

Continue on to the next step: Step 10: Push Your App to Live App Testing.

(If you run into any issues that prevent you from continuing, see Troubleshooting for Cloudside Integrations.)


Last updated: Oct 13, 2021