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
- Customize Your Handling of Directives
- Uploading the Finalized Lambda
- Next Steps
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