Developer Console

Step 8: Test Your Skill and Observe Logs in CloudWatch (VSK Fire TV)

It's time to test your skill and observe the directives Alexa sends to your Lambda function. You'll view the interactions through CloudWatch, which will list the directives Alexa sends to your Lambda. To see the responses your Lambda sends to your app, you'll open Logcat in Android Studio.

Connect to Fire TV Through ADB

At this point, you're ready to run your app on Fire TV to make sure everything works and is authorized.

  1. Follow the instructions in Connect to Fire TV Through ADB to connect your computer to Fire TV.
  2. Open Android Studio, select the app configuration, and then select the Fire TV device (for example, "Amazon AFTMM") to run your app on. ("AFTMM" refers to the Fire TV build model.)

    Configuration selections in Android Studio
    Configuration selections in Android Studio
  3. Run your app by clicking the Run App button Run 'app' .

    If you didn't have a Fire TV device selected already, you'll be prompted to select your device. Choose the Fire TV device and click OK.

    Selecting the target Fire TV device
    Selecting the target Fire TV device

    While your app builds and runs, click the Logcat tab at the bottom of Android Studio to monitor the log messages. If your app runs, then you know that you have configured the security profile correctly. The sample app's home screen looks as follows:

    Several common videos are integrated into the sample app.
    Several common videos are integrated into the sample app.

If you run into issues in this step, try the following to troubleshoot:

  • If you get an error message (e.g., some entry names collided), go to Build > Rebuild Project to see if it clears up any outdated artifacts, and then run the app again.
  • If you get an installation conflict, make sure your Fire TV doesn't have any other apps with this same package name. Uninstall any apps as needed (Settings > Applications > Manage Installed Applications).
  • If the app loads a blank screen or has other issues, open Logcat and filter on "Error." If you see an error that says "Invalid API key," check to make sure you're signing your app with the custom firetv signing key you configured earlier.

Say a Test Utterance

With the app running in the foreground of your Fire TV, say to your Echo, "Alexa, watch Superman" (or any other movie or TV show title, if you're using the sample app with the ontv (IMDb) catalog).

The first time you ask this, Alexa will respond, "<Video Skill> is now Alexa-enabled…." Alexa is telling you that it has automatically paired your video skill with the Fire TV app. Then Alexa says, "Getting Superman from <Video Skill>." A sample video then starts to play.

In the sample app, all utterances are mapped to the same sample video.

Note that Alexa retrieves the listing from your video skill, rather than "from Fire TV" (which was the response earlier in step 1 when you linked your Echo to Fire TV).

View Logs in CloudWatch

You can use CloudWatch to observe the events received from the video skill and the logs written by your Lambda. This will give you a better idea of what's going on between the various components. To view logs in CloudWatch:

  1. In AWS, click Services and go to CloudWatch (search for it).
  2. In the left sidebar, go to Logs > Log groups.
  3. Click your Lambda function.

    If you don't see your Lambda function, make sure you're in the correct region, since only Lambda functions related to the region you're in appear.

  4. A list of logs for the function appears, with the most recent log group on top. (Logs won't appear here until you start saying commands to your Echo.)

If In the Lambda logs, to make the logs more scannable, you can select the Text radio button.

Note that you can also access your CloudWatch logs by going to your Lambda function. Click the Monitoring tab, and then click the View logs in CloudWatch button, as shown on the following screenshot.

CloudWatch Logs link from Lambda function screen
CloudWatch Logs link from Lambda function screen

A more detailed explanation of logs is provided in Full Interaction in CloudWatch below.

Contexts for Utterance, and Implicit versus Explicit Targeting

Before getting into the various test cases and the step-by-step walk through in Cloudwatch, note the different contexts in which you can specify utterances. There are two primary contexts in which you can say utterances:

  • Implicit targeting: A voice command that does not specify the content provider or app name, e.g., "Watch Superman."
  • Explicit targeting: A voice command that specifies the content provider or app name, e.g. "Watch Superman on Streamz."

As you test various utterances, remember that explicit targeting is only available after pushing your skill to Live App Testing. When you are sideloading your application through Android Studio, Alexa cannot handle explicit targeting of your skill. As a result, you'll need to open up your app so that it's the foreground.

If your app is in the foreground (rather than the background), when you say "Watch Superman," Alexa looks for matches within the foregrounded catalog's app (rather than in all catalogs). Saying a command while your app is in the foreground is the equivalent of explicit targeting but is supported even when you're sideloading your app from Android Studio locally.

Understanding the Interaction Model Between Alexa, Lambda, and Your Fire TV App

Now that you have all the pieces in place, it's a good time to describe how all of these components work together in more detail. When you say phrases to your Alexa-enabled device, these phrases get parsed for their meaning (through Alexa natural language services in the cloud) and converted into directives.

Directives are information blocks (formatted in JSON) sent by Alexa through the Video Skill API to your Lambda for processing. There are about a half a dozen directives used with Fire TV, listed in the API Reference section. (Note that the APIs for VSK Fire TV differ from the APIs used for VSK Echo Show.)

You can view the directives that Alexa sends to your Lambda function through CloudWatch. CloudWatch is an AWS service that collects monitoring and operational data for Lambda (and other services) in the form of logs, metrics, and events. Each time your Lambda receives directives from Alexa, your Lambda can log the directives and other events, and you can view those logs in CloudWatch.

The core task in configuring a video skill is in understanding the directives Alexa sends to your Lambda function and then implementing the expected responses in your app.

Your Lambda should also send a simple confirmation response back to Alexa to indicate that your processing of the directive was successful. However, although your Lambda function sends a response back to Alexa, your Lambda cannot provide any custom voice feedback that would be directed to the user. Alexa handles this communication exclusively. The only action you can perform is to carry out the actions specified in the API reference.

Your Lambda communicates information to your Fire TV app through Amazon Device Messaging (ADM). In the Lambda for the sample app, the Lambda simply pushes the same directive it receives from Alexa to your app. (You can customize your Lambda to send information as desired to your app — for example, after processing or querying another data source.) The sample app contains logic that looks through the message from ADM and locates any media identifiers that match its catalog and plays them. You can see the incoming message through the Logcat pane — filter for the title you searched for.

Full Interaction in CloudWatch for "Find Iron Man 2"

Before jumping into the test utterances below, let's step through a sample CloudWatch log in detail for an utterance of "Find Iron Man 2." Using the keyword "find" will prompt Alexa to send a SearchAndDisplayResults directive. (Using the keyword "watch" or "play" would prompt Alexa to send a SearchAndPlay directive.) If you're using the sample app, your CloudWatch logs should resemble the following (at least for the sample Lambda). See the inline comments for explanations of what each log message means. Note that the Request ID and timestamps have been shortened for readability.

START RequestId: b9e15474 Version: $LATEST

The request begins.

2020-03-18T04:28:49 b9e15474 INFO Lambda was invoked by Alexa

The log starts with the Alexa request event. When a Lambda function is invoked (in this case, by the video skill, which acts as the "trigger"), AWS Lambda starts executing your code by calling the handler function. The runtime passes three arguments to the handler method:

exports.handler = (event, context, callback) =>

Each argument is as follows:

  • event: The first argument, event, contains information from the invoker. In this case, the invoker is the Alexa directive. Alexa passes this directive as a JSON-formatted string when it calls Invoke.
  • context: The second argument is the context object, which contains information about the invocation, function, and execution environment.
  • callback: The third argument, callback, is a function that you can call in non-async functions to send a response.

You can learn more about Lambda and the handler in AWS Lambda Function Handler in Node.js.

2020-03-18T04:28:49 b9e15474 INFO Context: …

{
    "callbackWaitsForEmptyEventLoop": true,
    "functionVersion": "$LATEST",
    "functionName": "zombie_streamz_lambda",
    "memoryLimitInMB": "128",
    "logGroupName": "/aws/lambda/zombie_streamz_lambda",
    "logStreamName": "2020/03/10/[$LATEST]34de0361da2c4450a97f2ba2c674fcb0",
    "invokedFunctionArn": "arn:aws:lambda:us-east-1:458179560631:function:zombie_streamz_lambda",
    "awsRequestId": "980c8eef"
}

To see the context, the sample Lambda logs this information:

console.log("Context: " + JSON.stringify(context, null, 2));

The context contains information about the event that invoked your Lambda. The context information isn't that useful except that it shows the Lambda ARN that was invoked and the trigger (your video skill, or invokedFunctionArn) that invoked it. You will likely want to just comment this out. Context is uncommented in the sample app so you can see what information gets passed to it. The video skill is a smart home trigger that you connected to this Lambda function.

2020-03-18T04:28:49 b9e15474 INFO Directive received from Alexa: SearchAndDisplayResults

{
    "directive": {
        "payload": {
            "entities": [
                {
                    "type": "Video",
                    "uri": "entity://provider/program/amzn1.p11cat.merged-video.ae637959-7f3c-5c5e-8baf-ce730ed9beb6",
                    "value": "Iron Man 2",
                    "externalIds": {
                        "ENTITY_ID": "amzn1.p11cat.merged-video.ae637959-7f3c-5c5e-8baf-ce730ed9beb6",
                        "tms": "MV002527780000"
                    }
                },
                {
                    "type": "Video",
                    "uri": "entity://provider/program/amzn1.p11cat.merged-video.b4e77dca-954a-555d-ba2b-da5e0ca9e903",
                    "value": "Iron Man 2",
                    "externalIds": {
                        "ENTITY_ID": "amzn1.p11cat.merged-video.b4e77dca-954a-555d-ba2b-da5e0ca9e903"
                    }
                },
                {
                    "type": "Video",
                    "uri": "entity://provider/program/amzn1.p11cat.merged-video.0e3a1fb7-b973-52c0-98ef-cc4765fb9238",
                    "value": "Iron Man 2",
                    "externalIds": {
                        "ENTITY_ID": "amzn1.p11cat.merged-video.0e3a1fb7-b973-52c0-98ef-cc4765fb9238"
                    }
                },
                {
                    "type": "Video",
                    "uri": "entity://provider/program/amzn1.p11cat.merged-video.6beb3014-436d-5138-b0c0-bcf819cfadef",
                    "value": "Iron Man 2",
                    "externalIds": {
                        "ENTITY_ID": "amzn1.p11cat.merged-video.6beb3014-436d-5138-b0c0-bcf819cfadef"
                    }
                },
                {
                    "type": "Video",
                    "uri": "entity://provider/program/amzn1.p11cat.merged-video.80a7eda2-3e16-55de-b56b-3136f5dc0e74",
                    "value": "Iron Man 2",
                    "externalIds": {
                        "ENTITY_ID": "amzn1.p11cat.merged-video.80a7eda2-3e16-55de-b56b-3136f5dc0e74",
                        "tms": "MV006154660000"
                    }
                }
            ],
            "searchText": {
                "transcribed": "iron man two"
            }
        },
        "header": {
            "payloadVersion": "3",
            "messageId": "f5cbe32c-72fc-4816-892d-92f695c125a4",
            "namespace": "Alexa.RemoteVideoPlayer",
            "name": "SearchAndDisplayResults",
            "correlationToken": "7e8bdcef-1aff-452e-b405-5a877d712755"
        },
        "endpoint": {
            "endpointId": "432521e3d404c9e3##amzn1-ask-skill-c7dc9021-ba50-4aa9-bbad-7b9bcd2e94c3##development##com-tomjoht-vskfiretv",
            "cookie": {
                "deviceType": "AKPGW064GI9HE",
                "VSKClientVersion": "1.4.6",
                "appPackageName": "com.tomjoht.vskfiretv",
                "deviceId": "G070VM1694861D9N",
                "appName": "VSKFireTV",
                "applicationInstanceId": "amzn1.adm-registration.v3.Y29tLmFtYXpvbi5EZXZpY2VNZXNzYWdpbmcuUmVnaXN0cmF0aW9uSWRFbmNyeXB0aW9uS2V5ITEheFgvdzhUODl1SGc0WU96N0svSlZCVWFXVnBqUkNhdnJuWHlXWFBucUtvMHVLd0g2M1lkVEhZcjNRZ3J2K3A5QVAzdzhYbEkzZGYrY0t2Q0NQSWVQSEtEMDEvbGRhYUgrZVY4b1JRTXFMQndpYUZXajRXZlN3MEwzQ2ZaWkh2d0tUd3c1cFlYKys5WFdxZkVtRkg4RTQzcXhDMmxJYkZmd3dJeVVObStnWU00VU9GVmVwQTBVYityTFRRZHlYanNGUy9RTEhDbDhMUm1URmovZldsazF6WGRnZUV1V1pGWjkrcXYzR0lRcFV1MlpSYS9yZFA0aDEwbU5EcG1ndDE4eVJPRUg1bUtIQWVpUkZ3UlNwSlljcG9XSE96R3ZtMWJvZys2U1QyWUxvM3l0Nk4xNWI0QmFvMjBLcjNuZEl5UHpvNFBSUUs5aHZ1VUdTZ1psdUlFeVVsdzBZajFnb1ZZKzBLL3NGN2pJQnJjPSFmdk1qcTFuM0tOcVBOVzdyOGhybkp3PT0"
            },
            "scope": {
                "token": null,
                "type": "BearerToken"
            }
        }
    }
}

In this scenario, you said this command: "Find Iron Man 2", which results in a SearchAndDisplayResults. The utterance was parsed by Alexa in the cloud and a SearchAndPlay directive was generated, with a payload that contains the search terms as the value. Through the Video Skill API, Alexa reaches out to your Lambda function with the directive.

The SearchAndDisplayResults directive generated by Alexa specifies the content that your app should play. The content specified is "Iron Man 2." Alexa looks in your catalog for titles matching the user's request and lists the matching entries in the externalIds array. Alexa sends all matches to your Lambda and expects you to determine which one you should play in your app.

Note that with this "Iron Man 2" example, the movie is a franchise, so the payload includes "type": "Franchise". See Watch by Franchise for details. When a user conducts a play-by-franchise request, you will receive the franchise that the user requested (in this case, Jurassic Park) in the directive, but the directive will not contain a catalog ID. For these directives, the expectation is that you will conduct a search within your catalog for this content and then play the top result from your search.

2020-03-18T04:28:49 b9e15474 INFO Search started

The sample Lambda function contains some search logic to parse the values in the incoming request and look for matches in a database. The implementation here is shown only as an example of how you can use the payload from the directory to create a search in another data source. The sample app doesn't actually find and play this content because it has a limited amount of media to play.

In this case, the "database" connected to the Lambda is in the video_catalog.json file, which contains entries like this:

[
    {
        "id": 1,
        "movie_id": "FG1",
        "title": "Forrest Gump",
        "genre": "drama",
        "description": "A drama starring Tom Hanks",
        "video_file": "https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_10MB.mp4"
    },
    {
        "id": 2,
        "movie_id": "IM1",
        "title": "Iron Man 1",
        "genre": "action",
        "description": "Tony Stark and his magic war suit",
        "video_file": "https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_10MB.mp4"
    },
    {
        "id": 3,
        "movie_id": "IM2",
        "title": "Iron Man 2",
        "genre": "action",
        "description": "Tony Stark and his magic war suit do more cool stuff",
        "video_file": "https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_10MB.mp4"
    }
]

The sample Lambda leverages a JS library called Fuse.js, which provides a "lightweight fuzzy-search library" to match search terms against a list of potential matches. fuse takes two arguments: a data source and some search options. In the Lambda, this looks as follows:

var fuse = new Fuse(VIDEOS, options)

VIDEOS is the data source, defined as const VIDEOS = require("video_catalog.json");. The options defines weighting for different fields to perform the matching.

The Lambda gets the keywords from the Alexa directive (let searchTerm = directive.payload.entities[0].value;) and passes these terms into fuse to perform the search:

fuse.search(searchTerm)

Ideally, these search terms would get passed to your app to play the right media.

You don't need to implement Fuse to perform searches in your app. You can implement your own (hopefully more robust) logic to perform searches for your content.

2020-03-18T04:28:49 b9e15474 INFO Search Term: Iron Man 2

The search term is pulled from the incoming Alexa directive through directive.payload.entities[0].value;.

2020-03-18T04:28:49 b9e15474 INFO Search Results:

{
    "item": {
        "id": 3,
        "movie_id": "IM2",
        "title": "Iron Man 2",
        "genre": "action",
        "description": "Tony Stark and his magic war suit do more cool stuff",
        "video_file": "https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_10MB.mp4"
    },
    "score": 0.0005
}
,
{
    "item": {
        "id": 2,
        "movie_id": "IM1",
        "title": "Iron Man 1",
        "genre": "action",
        "description": "Tony Stark and his magic war suit",
        "video_file": "https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_10MB.mp4"
    },
    "score": 0.05
}
,
{
    "item": {
        "id": 201,
        "movie_id": "BA1_1",
        "episode": 1,
        "genre": "comedy",
        "title": "Black Adder Season 1 Ep 1",
        "description": "Rowan Atkinson is pretty much the funniest man alive in this debut for the Black Adder series",
        "video_file": "https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_10MB.mp4"
    },
    "score": 0.45
}
,
{
    "item": {
        "id": 202,
        "movie_id": "BA1_2",
        "episode": 2,
        "genre": "comedy",
        "title": "Black Adder Season 1 Ep 2",
        "description": "Rowan Atkinson continues his role as the esteemed and ill-fated Black Adder",
        "video_file": "https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_10MB.mp4"
    },
    "score": 0.45
}
]

The searchTerm is passed into fuse and stored in searchResults, which is logged to the console through the following:

var searchResults = fuse.search(searchTerm)

2020-03-18T04:28:49 b9e15474 INFO ADM Registration ID

The ADM registration ID is logged to the console.

2020-03-18T04:28:49 b9e15474 INFO Got Access Token Type:bearer Expires In:3600…

ADM gets an access token using your credentials. If it succeeds, the Lambda function calls the sendMessageToDevice() function. If this is unsuccessful, no access token is granted.

2020-03-18T04:28:49 b9e15474 INFO Calling device…

ADM calls the sendMessageToDevice() function to send the directive to your app, identifying it by way of the API key. ADM uses the request module for sending the directive. You can read more about ADM documentation headers in Request an access token.

2020-03-18T04:28:49 b9e15474 INFO Sending message to app:

The Lambda sends the message to the Fire TV app through ADM. The JSON is escaped.

 "{\"directive\":{\"payload\":{\"entities\":[{\"type\":\"Video\",\"uri\":\"entity://provider/program/amzn1.p11cat.merged-video.ae637959-7f3c-5c5e-8baf-ce730ed9beb6\",\"value\":\"Iron Man 2\",\"externalIds\":{\"ENTITY_ID\":\"amzn1.p11cat.merged-video.ae637959-7f3c-5c5e-8baf-ce730ed9beb6\",\"tms\":\"MV002527780000\"}},{\"type\":\"Video\",\"uri\":\"entity://provider/program/amzn1.p11cat.merged-video.b4e77dca-954a-555d-ba2b-da5e0ca9e903\",\"value\":\"Iron Man 2\",\"externalIds\":{\"ENTITY_ID\":\"amzn1.p11cat.merged-video.b4e77dca-954a-555d-ba2b-da5e0ca9e903\"}},{\"type\":\"Video\",\"uri\":\"entity://provider/program/amzn1.p11cat.merged-video.0e3a1fb7-b973-52c0-98ef-cc4765fb9238\",\"value\":\"Iron Man 2\",\"externalIds\":{\"ENTITY_ID\":\"amzn1.p11cat.merged-video.0e3a1fb7-b973-52c0-98ef-cc4765fb9238\"}},{\"type\":\"Video\",\"uri\":\"entity://provider/program/amzn1.p11cat.merged-video.6beb3014-436d-5138-b0c0-bcf819cfadef\",\"value\":\"Iron Man 2\",\"externalIds\":{\"ENTITY_ID\":\"amzn1.p11cat.merged-video.6beb3014-436d-5138-b0c0-bcf819cfadef\"}},{\"type\":\"Video\",\"uri\":\"entity://provider/program/amzn1.p11cat.merged-video.80a7eda2-3e16-55de-b56b-3136f5dc0e74\",\"value\":\"Iron Man 2\",\"externalIds\":{\"ENTITY_ID\":\"amzn1.p11cat.merged-video.80a7eda2-3e16-55de-b56b-3136f5dc0e74\",\"tms\":\"MV006154660000\"}}],\"searchText\":{\"transcribed\":\"iron man two\"},\"searchResults\":[{\"item\":{\"id\":3,\"movie_id\":\"IM2\",\"title\":\"Iron Man 2\",\"genre\":\"action\",\"description\":\"Tony Stark and his magic war suit do more cool stuff\",\"video_file\":\"https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_10MB.mp4\"},\"score\":0.0005},{\"item\":{\"id\":2,\"movie_id\":\"IM1\",\"title\":\"Iron Man 1\",\"genre\":\"action\",\"description\":\"Tony Stark and his magic war suit\",\"video_file\":\"https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_10MB.mp4\"},\"score\":0.05},{\"item\":{\"id\":201,\"movie_id\":\"BA1_1\",\"episode\":1,\"genre\":\"comedy\",\"title\":\"Black Adder Season 1 Ep 1\",\"description\":\"Rowan Atkinson is pretty much the funniest man alive in this debut for the Black Adder series\",\"video_file\":\"https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_10MB.mp4\"},\"score\":0.45},{\"item\":{\"id\":202,\"movie_id\":\"BA1_2\",\"episode\":2,\"genre\":\"comedy\",\"title\":\"Black Adder Season 1 Ep 2\",\"description\":\"Rowan Atkinson continues his role as the esteemed and ill-fated Black Adder\",\"video_file\":\"https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_10MB.mp4\"},\"score\":0.45}]},\"header\":{\"payloadVersion\":\"3\",\"messageId\":\"f5cbe32c-72fc-4816-892d-92f695c125a4\",\"namespace\":\"Alexa.RemoteVideoPlayer\",\"name\":\"SearchAndDisplayResults\",\"correlationToken\":\"7e8bdcef-1aff-452e-b405-5a877d712755\"},\"endpoint\":{\"endpointId\":\"432521e3d404c9e3##amzn1-ask-skill-c7dc9021-ba50-4aa9-bbad-7b9bcd2e94c3##development##com-tomjoht-vskfiretv\",\"cookie\":{\"deviceType\":\"AKPGW064GI9HE\",\"VSKClientVersion\":\"1.4.6\",\"appPackageName\":\"com.tomjoht.vskfiretv\",\"deviceId\":\"G070VM1694861D9N\",\"appName\":\"VSKFireTV\",\"applicationInstanceId\":\"amzn1.adm-registration.v3.Y29tLmFtYXpvbi5EZXZpY2VNZXNzYWdpbmcuUmVnaXN0cmF0aW9uSWRFbmNyeXB0aW9uS2V5ITEheFgvdzhUODl1SGc0WU96N0svSlZCVWFXVnBqUkNhdnJuWHlXWFBucUtvMHVLd0g2M1lkVEhZcjNRZ3J2K3A5QVAzdzhYbEkzZGYrY0t2Q0NQSWVQSEtEMDEvbGRhYUgrZVY4b1JRTXFMQndpYUZXajRXZlN3MEwzQ2ZaWkh2d0tUd3c1cFlYKys5WFdxZkVtRkg4RTQzcXhDMmxJYkZmd3dJeVVObStnWU00VU9GVmVwQTBVYityTFRRZHlYanNGUy9RTEhDbDhMUm1URmovZldsazF6WGRnZUV1V1pGWjkrcXYzR0lRcFV1MlpSYS9yZFA0aDEwbU5EcG1ndDE4eVJPRUg1bUtIQWVpUkZ3UlNwSlljcG9XSE96R3ZtMWJvZys2U1QyWUxvM3l0Nk4xNWI0QmFvMjBLcjNuZEl5UHpvNFBSUUs5aHZ1VUdTZ1psdUlFeVVsdzBZajFnb1ZZKzBLL3NGN2pJQnJjPSFmdk1qcTFuM0tOcVBOVzdyOGhybkp3PT0\"},\"scope\":{\"token\":null,\"type\":\"BearerToken\"}}}}"

If you were to unescape and beautify this JSON, it would look like this:

{
  "directive": {
    "payload": {
      "entities": [
        {
          "type": "Video",
          "uri": "entity://provider/program/amzn1.p11cat.merged-video.ae637959-7f3c-5c5e-8baf-ce730ed9beb6",
          "value": "Iron Man 2",
          "externalIds": {
            "ENTITY_ID": "amzn1.p11cat.merged-video.ae637959-7f3c-5c5e-8baf-ce730ed9beb6",
            "tms": "MV002527780000"
          }
        },
        {
          "type": "Video",
          "uri": "entity://provider/program/amzn1.p11cat.merged-video.b4e77dca-954a-555d-ba2b-da5e0ca9e903",
          "value": "Iron Man 2",
          "externalIds": {
            "ENTITY_ID": "amzn1.p11cat.merged-video.b4e77dca-954a-555d-ba2b-da5e0ca9e903"
          }
        },
        {
          "type": "Video",
          "uri": "entity://provider/program/amzn1.p11cat.merged-video.0e3a1fb7-b973-52c0-98ef-cc4765fb9238",
          "value": "Iron Man 2",
          "externalIds": {
            "ENTITY_ID": "amzn1.p11cat.merged-video.0e3a1fb7-b973-52c0-98ef-cc4765fb9238"
          }
        },
        {
          "type": "Video",
          "uri": "entity://provider/program/amzn1.p11cat.merged-video.6beb3014-436d-5138-b0c0-bcf819cfadef",
          "value": "Iron Man 2",
          "externalIds": {
            "ENTITY_ID": "amzn1.p11cat.merged-video.6beb3014-436d-5138-b0c0-bcf819cfadef"
          }
        },
        {
          "type": "Video",
          "uri": "entity://provider/program/amzn1.p11cat.merged-video.80a7eda2-3e16-55de-b56b-3136f5dc0e74",
          "value": "Iron Man 2",
          "externalIds": {
            "ENTITY_ID": "amzn1.p11cat.merged-video.80a7eda2-3e16-55de-b56b-3136f5dc0e74",
            "tms": "MV006154660000"
          }
        }
      ],
      "searchText": {
        "transcribed": "iron man two"
      },
      "searchResults": [
        {
          "item": {
            "id": 3,
            "movie_id": "IM2",
            "title": "Iron Man 2",
            "genre": "action",
            "description": "Tony Stark and his magic war suit do more cool stuff",
            "video_file": "https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_10MB.mp4"
          },
          "score": 0.0005
        },
        {
          "item": {
            "id": 2,
            "movie_id": "IM1",
            "title": "Iron Man 1",
            "genre": "action",
            "description": "Tony Stark and his magic war suit",
            "video_file": "https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_10MB.mp4"
          },
          "score": 0.05
        },
        {
          "item": {
            "id": 201,
            "movie_id": "BA1_1",
            "episode": 1,
            "genre": "comedy",
            "title": "Black Adder Season 1 Ep 1",
            "description": "Rowan Atkinson is pretty much the funniest man alive in this debut for the Black Adder series",
            "video_file": "https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_10MB.mp4"
          },
          "score": 0.45
        },
        {
          "item": {
            "id": 202,
            "movie_id": "BA1_2",
            "episode": 2,
            "genre": "comedy",
            "title": "Black Adder Season 1 Ep 2",
            "description": "Rowan Atkinson continues his role as the esteemed and ill-fated Black Adder",
            "video_file": "https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_10MB.mp4"
          },
          "score": 0.45
        }
      ]
    },
    "header": {
      "payloadVersion": "3",
      "messageId": "f5cbe32c-72fc-4816-892d-92f695c125a4",
      "namespace": "Alexa.RemoteVideoPlayer",
      "name": "SearchAndDisplayResults",
      "correlationToken": "7e8bdcef-1aff-452e-b405-5a877d712755"
    },
    "endpoint": {
      "endpointId": "432521e3d404c9e3##amzn1-ask-skill-c7dc9021-ba50-4aa9-bbad-7b9bcd2e94c3##development##com-tomjoht-vskfiretv",
      "cookie": {
        "deviceType": "AKPGW064GI9HE",
        "VSKClientVersion": "1.4.6",
        "appPackageName": "com.tomjoht.vskfiretv",
        "deviceId": "G070VM1694861D9N",
        "appName": "VSKFireTV",
        "applicationInstanceId": "amzn1.adm-registration.v3.Y29tLmFtYXpvbi5EZXZpY2VNZXNzYWdpbmcuUmVnaXN0cmF0aW9uSWRFbmNyeXB0aW9uS2V5ITEheFgvdzhUODl1SGc0WU96N0svSlZCVWFXVnBqUkNhdnJuWHlXWFBucUtvMHVLd0g2M1lkVEhZcjNRZ3J2K3A5QVAzdzhYbEkzZGYrY0t2Q0NQSWVQSEtEMDEvbGRhYUgrZVY4b1JRTXFMQndpYUZXajRXZlN3MEwzQ2ZaWkh2d0tUd3c1cFlYKys5WFdxZkVtRkg4RTQzcXhDMmxJYkZmd3dJeVVObStnWU00VU9GVmVwQTBVYityTFRRZHlYanNGUy9RTEhDbDhMUm1URmovZldsazF6WGRnZUV1V1pGWjkrcXYzR0lRcFV1MlpSYS9yZFA0aDEwbU5EcG1ndDE4eVJPRUg1bUtIQWVpUkZ3UlNwSlljcG9XSE96R3ZtMWJvZys2U1QyWUxvM3l0Nk4xNWI0QmFvMjBLcjNuZEl5UHpvNFBSUUs5aHZ1VUdTZ1psdUlFeVVsdzBZajFnb1ZZKzBLL3NGN2pJQnJjPSFmdk1qcTFuM0tOcVBOVzdyOGhybkp3PT0"
      },
      "scope": {
        "token": null,
        "type": "BearerToken"
      }
    }
  }
}

Open Logcat in Android Studio and filter on your search request (either by typing a partial match such as "iron," or by typing the full keyword terms). You'll be able to see this directive received by your app:

Check your ADM message handler in your app to see if you have received the directive.
Check your ADM message handler in the app to see if you have received the directive. Filter on the media title you searched for.

See the VSKFireTVMessageHandler.java class for details on how the app handles the incoming directive. For example, lines 113 to 131 contain the following logic to play the video:

if (directive != null) {
            String directiveName = directive.getDirective().getHeader().getName();
            if ("SearchAndPlay".equals(directiveName)) {
                Movie firstMovie = MovieList.getList().get(0);
                String movieName = firstMovie.getTitle();
                Log.d(TAG, "Playing MOVIE " + movieName);

                //For demonstration purposes, grabbing the first item in the movie list. Doesn't correspond to movie ID
                Movie someMovie = MovieList.getList().get(0);


                Intent playIntent = new Intent();
                String packageName = AlexaClientManager.getSharedInstance().getApplicationContext().getPackageName();
                playIntent.setClassName(packageName, packageName + ".PlaybackActivity");
                playIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

                //PlaybackActivity expects a movie to be currently selected, set this now in case there wasn't one
                playIntent.putExtra(DetailsActivity.MOVIE, someMovie);
                AlexaClientManager.getSharedInstance().getApplicationContext().startActivity(playIntent);

              } else if ("SearchAndDisplayResults".equals(directiveName)) {
                  String searchTerm = directive.getDirective().getPayload().getEntities().get(0).getValue().toLowerCase();
                  Log.d(TAG, "Searching for: " + searchTerm);

                  String searchPayload = "";
                  ...
            }
      ... }

As you can see, in this sample app the response just gets the first movie title from the MovieList, as shown in Movie firstMovie = MovieList.getList().get(0). You can view the MovieList class in the app for more detail on the logic. The sample app has a limited amount of data, so just a few responses are coded here. In your implementation, normally you would hook up the search with a comprehensive cloud database.

2020-03-18T04:28:49 b9e15474 INFO Response from the app:

{
    "registrationID": "amzn1.adm-registration.v3.Y29tLmFtYXpvbi...WVFnbXppczc0K0dWb2pnRklBQjNvdEhJV0F1cjNZTHZrakkvS09JPSE2czc5UVFhYklEN01BT1R5dVJNZDlnPT0"
}

The app responds with the registration ID to confirm the communication. (The long string is truncated here for readability.) Your app doesn't need to send the registration ID here; it can send anything you want.

2020-03-18T04:28:49 b9e15474 INFO Sending SearchAndDisplayResults response back to Alexa:

{
    "event": {
        "header": {
            "messageId": "not-required",
            "correlationToken": "not-required",
            "name": "Response",
            "namespace": "Alexa",
            "payloadVersion": "3"
        },
        "endpoint": {
            "scope": {
                "type": "DirectedUserId",
                "directedUserId": "not-required"
            },
            "endpointId": "not-required"
        },
        "payload": {}
    }
}

Your Lambda function sends a "success" response back to Alexa, indicating the directive was received by the Lambda. You can read more about this expected response in Response Example for the SearchAndDisplayResults API.

END RequestId: b9e15474

The directive has a request ID as shown. The interaction is finished.

REPORT RequestId: b9e15474 Duration: 882.12 ms Billed Duration: 900 ms Memory Size: 128 MB Max Memory Used: 105 MB

The directive took this much time and memory to process. This is the duration you were billed for for running this process. Most Fire TV apps with video skills rarely use more memory than what free tier already provides: "1 million requests per month, with 400,000 GB-seconds of compute time per month." See AWS Lambda Pricing for more details.

Workflow for SearchAndPlay Directives

The detailed Cloudwatch walkthrough above used the SearchAndDisplayResults directive. If you were to say "Play Iron Man 2", this would send a SearchAndPlay directive instead.

The workflow would be the same except that the "Directive received from Alexa: SearchAndPlay" section would show the following:

{
    "directive": {
        "payload": {
            "entities": [
                {
                    "type": "Video",
                    "uri": "entity://provider/program/amzn1.p11cat.merged-video.ae637959-7f3c-5c5e-8baf-ce730ed9beb6",
                    "value": "Iron Man 2",
                    "externalIds": {
                        "ENTITY_ID": "amzn1.p11cat.merged-video.ae637959-7f3c-5c5e-8baf-ce730ed9beb6",
                        "tms": "MV002527780000"
                    }
                },
                {
                    "type": "Video",
                    "uri": "entity://provider/program/amzn1.p11cat.merged-video.b4e77dca-954a-555d-ba2b-da5e0ca9e903",
                    "value": "Iron Man 2",
                    "externalIds": {
                        "ENTITY_ID": "amzn1.p11cat.merged-video.b4e77dca-954a-555d-ba2b-da5e0ca9e903"
                    }
                },
                {
                    "type": "Video",
                    "uri": "entity://provider/program/amzn1.p11cat.merged-video.0e3a1fb7-b973-52c0-98ef-cc4765fb9238",
                    "value": "Iron Man 2",
                    "externalIds": {
                        "ENTITY_ID": "amzn1.p11cat.merged-video.0e3a1fb7-b973-52c0-98ef-cc4765fb9238"
                    }
                },
                {
                    "type": "Video",
                    "uri": "entity://provider/program/amzn1.p11cat.merged-video.a13bd1b9-b2f6-5c15-b5a5-387f24836ba3",
                    "value": "Iron Man 2",
                    "externalIds": {
                        "ENTITY_ID": "amzn1.p11cat.merged-video.a13bd1b9-b2f6-5c15-b5a5-387f24836ba3"
                    }
                },
                {
                    "type": "Video",
                    "uri": "entity://provider/program/amzn1.p11cat.merged-video.6beb3014-436d-5138-b0c0-bcf819cfadef",
                    "value": "Iron Man 2",
                    "externalIds": {
                        "ENTITY_ID": "amzn1.p11cat.merged-video.6beb3014-436d-5138-b0c0-bcf819cfadef"
                    }
                }
            ],
            "searchText": {
                "transcribed": "iron man two"
            }
        },
        "header": {
            "payloadVersion": "3",
            "messageId": "53b4f99d-b8e4-44aa-b32f-095589f13987",
            "namespace": "Alexa.RemoteVideoPlayer",
            "name": "SearchAndPlay",
            "correlationToken": "e114fc4d-098a-47ad-8c54-94fbd693871e"
        },
        "endpoint": {
            "endpointId": "432521e3d404c9e3##amzn1-ask-skill-c7dc9021-ba50-4aa9-bbad-7b9bcd2e94c3##development##com-tomjoht-vskfiretv",
            "cookie": {
                "deviceType": "AKPGW064GI9HE",
                "VSKClientVersion": "1.4.6",
                "appPackageName": "com.tomjoht.vskfiretv",
                "deviceId": "G070VM1694861D9N",
                "appName": "VSKFireTV",
                "applicationInstanceId": "amzn1.adm-registration.v3.Y29tLmFtYXpvbi5EZXZpY2VNZXNzYWdpbmcuUmVnaXN0cmF0aW9uSWRFbmNyeXB0aW9uS2V5ITEheFgvdzhUODl1SGc0WU96N0svSlZCVWFXVnBqUkNhdnJuWHlXWFBucUtvMHVLd0g2M1lkVEhZcjNRZ3J2K3A5QVAzdzhYbEkzZGYrY0t2Q0NQSWVQSEtEMDEvbGRhYUgrZVY4b1JRTXFMQndpYUZXajRXZlN3MEwzQ2ZaWkh2d0tUd3c1cFlYKys5WFdxZkVtRkg4RTQzcXhDMmxJYkZmd3dJeVVObStnWU00VU9GVmVwQTBVYityTFRRZHlYanNGUy9RTEhDbDhMUm1URmovZldsazF6WGRnZUV1V1pGWjkrcXYzR0lRcFV1MlpSYS9yZFA0aDEwbU5EcG1ndDE4eVJPRUg1bUtIQWVpUkZ3UlNwSlljcG9XSE96R3ZtMWJvZys2U1QyWUxvM3l0Nk4xNWI0QmFvMjBLcjNuZEl5UHpvNFBSUUs5aHZ1VUdTZ1psdUlFeVVsdzBZajFnb1ZZKzBLL3NGN2pJQnJjPSFmdk1qcTFuM0tOcVBOVzdyOGhybkp3PT0"
            },
            "scope": {
                "token": null,
                "type": "BearerToken"
            }
        }
    }
}

Directives for the Sample App

Now that you have a sense of how to read the CloudWatch logs (for the information logged by the sample Lambda), it's time to run some tests. In this section, you will save four different utterances to Alexa and observe how the app behaves and what gets logged in CloudWatch. Because explicit targeting of your app isn't available yet, you'll need to start the sample app to bring it to the foreground (make sure it's actively displayed on Fire TV) before saying each utterance.

The sample app is capable of processing four directives. Remember that in this sample Lambda code, the Lambda merely passes along the same directive it receives from Alexa. The app then queries the payload and looks for media in its catalog.

You can code your Lambda with whatever process makes sense for your app. For example, as an alternative workflow, your Lambda could perform some processing on its own to locate the correct media identifier, and then send that down to your app, if you want.

SearchAndDisplayResults Directives

Sample utterance: Start the sample app. Then say, "Alexa, find Iron Man 2."

Response: "Getting Iron Man 2 from [video skill name]". The app goes to the search screen and searches for the text "iron man 2."

Directive that Alexa sends to your Lambda: SearchAndDisplayResults.

SearchAndPlay Directives

Sample utterance: Start the sample app. Then say, "Alexa, watch Iron Man 2"

Response: "Getting Iron Man 2 from [video skill name]." Then a test video from the app plays.

Directive that Alexa sends to your Lambda: SearchAndPlay

PlaybackController - Pause Directives

Sample utterance: Start the sample app (if not already in the foreground) and play one of the videos. Then say, "Alexa, pause."

Response: Alexa doesn't say anything, but the video pauses.

Directive that Alexa sends to your Lambda: PlaybackController: Pause

PlaybackController - Play Directives

Sample utterance: With the sample video paused in the previous scenario, say, "Alexa, play."

Response: Alexa doesn't say anything, but the video resumes playback.

Directive Alexa sends to your Lambda: PlaybackController: Play

Troubleshooting

If you run into issues, see the following troubleshooting tips:

"Something went wrong"
Your Lambda syntax might be invalid.
No logs appear in Cloudwatch
Make sure you have the right AWS region selected.
Sorry, I don't know that one…
Alexa doesn't understand a command.
[object Object] appears in the Cloudwatch logs.
You're not using the JSON.Stringify function to render the JSON.

Next Steps

Now that you've got a sense of how the Alexa directives and Lambda function work, it's time to customize the Lambda for your own content and service. Continue on to Step 9: Interpret and React to Directives.

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


Last updated: Oct 13, 2021