Step 4: Understand How Your Web Player Gets the Media Playback URL (VSK MM)

From your GetPlayableItemsMetadataResponse, Amazon passes the playbackContextToken to your web player. The logic in your web player can convert this playbackContextToken to a media playback URL that your web player loads.

Overview to the Media Playback URL and Media Player

One detail that might not be apparent from the previous section (Step 3: Understand the Alexa Directives and Lambda Responses), is how the multimodal device gets the playback URL for the media, since this playback URL isn't something that your Lambda passes back to Alexa. (With the Fire TV app implementation of video skills, your Lambda pushes content URLs directly to your app through ADM. But because multimodal devices don't have apps, the process here is a bit different.)

Basically, here's how it works. In your Lambda's GetPlayableItemsMetadataResponse, your Lambda passes a playbackContextToken back to Alexa. The playbackContextToken contains a value of your choosing to describe the media. Amazon passes the playbackContextToken value to your web player through handlers in the Alexa JavaScript Library (alexa.js).

Your web player's code can then convert the playbackContextToken value to a media playback URL. This way you keep your media playback URLs entirely private, known only to you. The following sections will go through this workflow and code in more detail.

Workflow Detail for the Media Playback URL

After your Lambda receives a GetPlayableItems directive from Alexa, your Lambda responds with a GetPlayableItemsResponse that contains the media matching the user's request. After any disambiguation with the user around multiple media titles, Alexa then sends a GetPlayableItemsMetadata directive to get more details about the chosen media. Your Lambda then responds with a GetPlayableItemsMetadataResponse that contains an identifier for the media. This identifier is specified in the playbackContextToken property as follows:

Lambda Response: GetPlayableItemsMetadataResponse

{
    "event": {
        "header": {
            "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==",
            "messageId": "38ce5b22-eeff-40b8-a84f-979446f9b27e",
            "name": "GetPlayableItemsMetadataResponse",
            "namespace": "Alexa.VideoContentProvider",
            "payloadVersion": "3"
        },
        "payload": {
            "searchResults": [
                {
                    "name": "Big Buck Bunny from the Blender Foundation",
                    "contentType": "ON_DEMAND",
                    "series": {
                        "seasonNumber": "1",
                        "episodeNumber": "1",
                        "seriesName": "Creative Commons Videos",
                        "episodeName": "Pilot"
                    },
                    "playbackContextToken": "{\"streamUrl\": \"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4\", \"title\": \"Big Buck Bunny\"}",
                    "parentalControl": {
                        "pinControl": "REQUIRED"
                    },
                    "absoluteViewingPositionMilliseconds": 1232340
                }
            ]
        }
    }
}

This playbackContextToken contains an identifier, defined entirely by you, that you would like Amazon to send to your web player. Your web player will convert this identifier into a media playback URL.

In the sample Lambda and web player provided in earlier steps, the playbackContextToken is a JSON object converted to a string. The sample web player code expects the playbackContextToken to be a JSON object with streamUrl and title parameters. However, you can pass whatever value here you want, so long as your media player properly parses and passes on this value to your web player.

The multimodal device invokes your web player (which resides on your server) and passes it the playbackContextToken. At this point your web player code could convert the value of the playbackContextToken to your actual media playback URL.

In the sample Lambda code, rather than making playbackContextToken an identifier that would need to be converted by the media player into a media playback URL, the sample Lambda just includes the media playback URL directly as the value for streamUrl.

Handlers and Functions Used

If you'll later be customizing the playbackContextToken in your own web player, particularly in Step 5: Build Your Web Player, it's important to understand what handlers and functions are used with the playbackContextToken.

The sample web player incorporates the Alexa JavaScript Library (alexa.js) (a required library for all web players integrating video skills on multimodal devices). The Alexa JavaScript library contains some event handlers that you must implement with your web player. These handlers are how Alexa communicates information to your web player. The handlers are defined in the readyCallback function in alexa.js:

Handlers in alexa.js

function readyCallback(controller) {
    var Event = AlexaWebPlayerController.Event;
    var handlers = {
        Event.LOAD_CONTENT: function handleLoad(params) {},
        Event.PAUSE: function handlePause() {},
        Event.RESUME: function handleResume() {},
        Event.SET_SEEK_POSITION: function handleSetPos (positionInMilliseconds) {},
        Event.ADJUST_SEEK_POSITION: function handleAdjustPos(offsetInMilliseconds) {},
        Event.NEXT: function handleNext() {},
        Event.PREVIOUS: function handlePrevious() {},
        Event.CLOSED_CAPTIONS_STATE_CHANGE: function handleCCState(state) {},
        Event.PREPARE_FOR_CLOSE: function handlePrepareForClose() {},
        Event.ACCESS_TOKEN_CHANGE: function handleAccessToken(accessToken) {}
    };
    controller.on(handlers);
}

When you implement these handlers in your web player, Alexa can send you a LOAD_CONTENT event, a PAUSE event, a RESUME event, and so on. Your web player needs to act on the information in these handlers. LOAD_CONTENT is the handler that Alexa uses to pass information about the media playback URL.

It's important to note that you own the web player component of the implementation. Each video skill references a web player URI that the skill invokes to play video. Although multimodal devices have a basic Chromium web browser, Alexa doesn't provide anything beyond it. As such, in order to receive communication from Alexa, your web player must incorporate JavaScript.

When you initialize the Alexa JavaScript Library in your web player (through AlexaWebPlayerController.initialize), Alexa can send you a LOAD_CONTENT: function handleLoad(params) {} event to load content based on the user's request. In this LOAD_CONTENT call, Alexa can pass four parameters (params) as defined in Register Event Handlers: params.contentUri, params.accessToken, params.offsetInMilliseconds, and params.autoplay. The params.contentUri parameter includes the playBackContextToken.

Your JavaScript has the responsibility of handling these parameters from LOAD_CONTENT and performing whatever tasks are needed to play the content in your player. If you aren't including the playback URL directly in the playbackContextToken, you will likely need to convert the value by consulting with your backend service (where these identifiers are presumably mapped to the playback URLs). Then you would tell your JavaScript to load the playback URL into your web player. (This step becomes unnecessary if your Lambda passes the playback URL directly in the playbackContextToken.)

Code Walkthrough

Let's get even more specific with the code in the sample web player and how the playbackContextToken gets passed. The sample web player contains several JavaScript files in the src folder:

├── src
│   ├── alexa.js
│   ├── main.js
│   ├── mediaPlayer.js
│   ├── ui.js
│   └── util
│       └── logger.js

The alexa.js file contains a loadContentHandler function:

function loadContentHandler(playbackParams) {
  function loadContent(resolve, reject) {
    try {
      const content = JSON.parse(playbackParams.contentUri);
      mediaPlayer.load(playbackParams);
      ui.setVideoTitle(content.title);
      resolve();
    } catch (err) {
      reject(err);
    }
  }
  ...
}

This function loads in the playbackParams.contentUri and sets it as content. It also calls mediaPlayer.load (the web player's function) and passes in playbackParams. Open mediaPlayer.js and look for the load function to see how playbackParams gets passed into this function:

function load(playbackParams) {
  const { video } = self;
  const { contentUri, offsetInMilliseconds, autoplay } = playbackParams;
  const source = document.createElement('source');
  const { streamUrl, title } = JSON.parse(contentUri);

  // Set the video content based on contentUri being a stream URL
  source.setAttribute('src', streamUrl);

  // Set type based on extension loosely found in URL
  if (contentUri.indexOf('.mp4') >= 0) {
    source.setAttribute('type', 'video/mp4');
  } else if (contentUri.indexOf('.m3u8') >= 0) {
    source.setAttribute('type', 'application/x-mpegURL');
  } else {
    logger.debug(`Unhandled video type for url: ${streamUrl}`);
    throw new Error({
      errorType: alexa.ErrorType.INVALID_CONTENT_URI,
    });
  }
...
}

The line const { contentUri, offsetInMilliseconds, autoplay } = playbackParams; basically unzips the playbackParams values and converts them to contentUri, offsetInMilliseconds, and autoplay (three different values).

The line const { streamUrl, title } = JSON.parse(contentUri); extracts two values(streamUrl and title) from contentUri. (This is why in the sample Lambda and web player code, playbackContextToken has to be an object.) All we need to do now is load this into our player.

Although the sample Lambda implements contentUri this way, you can customize your own varied implementation. For example, it could simply be a string (rather than a stringified object).

In mediaPlayer.js, the video.load function loads the media:

if (autoplay) {
   video.play();
 } else {
   video.load();
 }

The src attribute used with the play method is set in the following line:

source.setAttribute('src', streamUrl);

The play method will load the source parameter:

video.innerHTML = '';
video.appendChild(source);

Note that this code is your own web player, which will likely vary from this sample player. The main work you do in customizing the web player code code is with alexa.js because is where you handle all the events from Alexa.

playbackContextToken versus accessToken

In alexa.js, you'll see that another handler takes a parameter called accessToken:

Event.ACCESS_TOKEN_CHANGE: function handleAccessToken(accessToken) {}

If your service requires the user be authenticated to stream content, an accessToken is included as well. The accessToken authorizes the user to view the content with your service. In contrast, as explained at length here, the playbackContextToken (and contentUri) is how you communicate the media playback URL. The playbackContextToken does not include any authentication information.

Next Steps

Go on to Step 5: Build Your Web Player.