Content Launcher - Handling launch requests and implementation guide

1. Introduction

The Content Launcher API enables Vega apps to seamlessly integrate with both the Fire TV home launcher and Alexa voice commands. This integration allows users to search and play content directly through these interfaces.

This comprehensive guide details how Vega apps can handle content search and playback requests using Content Launcher, with examples demonstrating the data schema for various use cases.

2. Content Launcher Interface

Refer Content Launcher inetgraiton guide to implement conentent launcher interface in your app. The callback method handleLaunchContent is invoked when application receives content search or play requests from Fire TV

handleLaunchContent method has 3 parameters:

  1. contentSearch:IContentSearch

    This parameter has all data required to handle launch request. Refer this section in the developer guide for more details to retrieve launch data received.

  2. autoPlay :** Boolean**

    This data indicates if the request is for playing content or to search. true indicates quickplay and false indicates search.

  3. _optionalFields :** ILaunchContentOptionalFields.**

    This is optional filed and not used now. No need to look at this value.

In upcoming secitons ,we wil explore how to use contentSearch and autoPlay parameters to implement searching and playing content when the interface is invoked.

2. Content Launcher request examples

The following examples reference content from the sample ‘streamz_us’ catalog:

Movie: “Seabound”
TV Show: “The SeaShow: The Real Story”

...
<Movie>
  <ID>1700000725</ID>
  <Title locale="en-US">Seabound</Title>
  <Offers>
    <SubscriptionOffer>
      <LaunchDetails>
        <Quality>UHD</Quality>
        <Subtitle>en-US</Subtitle>
        <LaunchId>tv.streamz/movie/46720001</LaunchId>
      </LaunchDetails>
    </SubscriptionOffer>
    <FreeOffer>
      <Regions>
        <Territories>US</Territories>
      </Regions>
      <LaunchDetails>
        <Quality>SD</Quality>
        <Subtitle>en-US</Subtitle>
        <LaunchId>tv.streamz/movie/467200002</LaunchId>
      </LaunchDetails>
    </FreeOffer>
  </Offers>
</Movie>

<TvShow>
  <ID>1700000123</ID>
  <Title locale="en-US">The SeaShow: The Real story</Title>
  <Offers>
    <FreeOffer>
      <Regions>
        <Territories>US</Territories>
      </Regions>
      <LaunchDetails>
        <Quality>HD</Quality>
        <Subtitle>en-US</Subtitle>
        <LaunchId>459800033</LaunchId>
      </LaunchDetails>
    </FreeOffer>
  </Offers>
</TvShow>

<TvSeason>
  <ID>1700000234</ID>
  <Title locale="en-US">Season 1</Title>
    <Offers>
     <FreeOffer>
       <Regions>
         <Territories>US</Territories>
       </Regions>
       <LaunchDetails>
         <Quality>HD</Quality>
         <Subtitle>en-US</Subtitle>
         <LaunchId>453200012</LaunchId>
        </LaunchDetails>
     </FreeOffer>
   </Offers>
   <ShowID>1700000123</ShowID>
   <SeasonInShow>2</SeasonInShow>
</TvSeason>

<TvEpisode>
  <ID>1700000825</ID>
  <Title locale="en-US">The Seashow. Story Starts</Title>
  <Offers>
    <FreeOffer>
      <Regions>
        <Territories>US</Territories>
      </Regions>
      <LaunchDetails>
        <Quality>HD</Quality>
        <Subtitle>en-US</Subtitle>
        <LaunchId>453100008</LaunchId>
      </LaunchDetails>
    </FreeOffer>
  </Offers>
  <ShowID>1700000123</ShowID>
  <SeasonID>1700000234</SeasonID>
  <EpisodeInSeason>5</EpisodeInSeason>
</TvEpisode>
...

Follwing examples explain contentSearch parameter values for some key content launcher use cases. All supported use cases are listed here.

1. Play content through remote

Launch content Seabound through remote from Fire TV home screen.

"parameterList": [
  {                      // parameter 0
      "type": 13,        // ContentSearchParamType::VIDEO,
      "value": ,         // Content title will not be populated
      "externalIdList": [{
          "name": "catalogContentId",          // Will be deprecated. Ignore this
          "value": "tv.catalog/movie/46720000" // ID or LaunchId from catalog
      },
      {
          "name": "amzn_id",
          "value": "tv.catalog/movie/46720000" // ID or LaunchId from catalog
      }
     ]
  }
]

Identify the content “tv.streamz/movie/46720000” using amzn_id and play it. Note that this is launchId. App should play the content direcly without going to intermediate pages like conetent details page.

:red_exclamation_mark: In this example , “tv.streamz/movie/46720000” is launchId. if the catalog does not have LaunchId, ID will be passed as value.

2. Play content through voice

Utterance: “Alexa, Play Seabound
Action : Play movie Seabound when interface is invoked

autoPlay will be true here. contentSearch: IContentSearch will have following values.

"parameterList": [
    {                             // parameter 0
        "type": 13,               // ContentSearchParamType::VIDEO
        "value": "Seabound",      // search string
        "externalIdList": [{
            "name": "streamz_us"   // your catalog id. Will be deprecated. Ignore this
            "value": "1700000725" // ID from catalog
        },
        {
            "name": "amzn_id"
            "value": "1700000725" // ID from catalog
        }
      ]
    }
]

Identify the content “1700000725” using amzn_id and play it. App should play the content direcly without going to intermediate pages like conetent details page.

:red_exclamation_mark: Alexa will not send LaunchId even when it is present in the catalog.

3. Episodic Play with voice

Utterance: “Alexa, Play Season 2 Episode 5 from The SeaShow: The Real story
Action : Play episode 5 from Season 2 in the TV Show The SeaShow: The Real story when interface is invoked

autoPlay will be true here. contentSearch: IContentSearch will have following values.

"parameterList": [
  {                // parameter 0
    "type": 13,    // ContentSearchParamType::VIDEO
    "value": "Season Two Episode Five The SeaShow: The Real story"

    "externalIdList": [{
       "name": "streamz_us",   // Will be deprecated, Ignore this
       "value": "1700000123" // ID of the Show from catalog
     },
     {
      "name": "amzn_id",
      "value": "1700000123" // ID of the Show from catalog
    }]
  },
  {                // parameter 1
    "type": 14,    // ContentSearchParamType::SEASON
    "value": "2",  // Season number in catalog
    "externalIdList": []
  },
  {               // parameter 2
    "type": 15,   // ContentSearchParamType::EPISODE
    "value": "5", // episode number in catalog
    "externalIdList": []
  }
]

Note that contentSearch will have only the ID of the Show, not the episode ID. App has to fetch the episode url using Show ID 1700000123, ContentSearchParamType::SEASON and ContentSearchParamType::EPISODE values to play the requested epiusode.

4. Episodic Play with voice - variance

Utterances:

  1. Alexa, Play Episode 5 from The SeaShow: The Real story
    Payload will have only show ID and episode number. it is not clear from which season
  2. Alexa, Play Season 2 from The SeaShow: The Real story
    Payload will have only show ID and season number. it is not clear which episode to play
  3. Alexa, Play The SeaShow: The Real story
    Payload will have only show ID. it is not clear wht to play here

autoPlay will be true here. contentSearch: IContentSearch will have following values.

"parameterList": [
  {                // parameter 0
    "type": 13,    // ContentSearchParamType::VIDEO
    "value": "Season Two Episode Five The SeaShow: The Real story",

    "externalIdList": [{
       "name": "streamz_us",   // Will be deprecated, Ignore this
       "value": "1700000123" // ID of the Show from catalog
     },
     {
      "name": "amzn_id",
      "value": "1700000123" // ID of the Show from catalog
    }]
  },
  {               // parameter 1
    "type": 15,   // ContentSearchParamType::EPISODE
    "value": "5", // episode number in catalog
    "externalIdList": []
  }
]

App can choose to play relevant content such as continuing previously watched episode , recent epidode etc. Alternatibely app can show relvant Show , Season , Episode details page though autoplay is true.

5. In-App search for content title using voice

Utterane: “Alexa, find Seabound in Streamz”.
Action : Display content details page for the requsted content.

Autoplay will be false here. Use amzn_id 1700000725 to identify the content.

"parameterList": [
  {                      // parameter 0
    "type": 13,          // ContentSearchParamType::VIDEO
    "value": "Seabound", // Search keyword to use in search
    "externalIdList": [{
        "name": "streamz_us",  // Will be deprecated. Ignore this
        "value": "1700000725"  // ID from catalog
    },
   {
        "name": "amzn_id",
        "value": "1700000725" // ID from catalog
    }
   ]
  }
]

6. In-App search for Genre using voice

Utterance “Alexa, find comedy movies in Streamz”.
Action : Show search result for “comedy movies”

autoPlay will be false. contentSearch: IContentSearch will have following values.

"parameterList": [
  {                               // parameter 0
    "type": 6,                    // ContentSearchParamType::GENRE
    "value": "Comedy",           // Search keyword to use in search
    "externalIdList": []
  },
 {                                // parameter 1
    "type": 6,                    // ContentSearchParamType::GENRE
    "value": "Dark Comedy",       // Search keyword to use in search
    "externalIdList": []
  },
 {                                // parameter 2
    "type": 6,                    // ContentSearchParamType::GENRE
    "value": "Romantic Comedy",   // Search keyword to use in search
    "externalIdList": []
  },
 {                                // parameter 3
    "type": 13,                   // ContentSearchParamType::VIDEO
    "value": "comedy movies",     // keyword to use in search
    "externalIdList": []
  }
]

Alexa will return multiple genres and types. You can chose your own logic to implement search.

You can fetch the search keyword customer used from ContentSearchParamType::VIDEO type. It is “comedy movies” as in the following block in this example.

{
  "type": 13, // ContentSearchParamType::VIDEO
  "value": "comedy movies", // keyword you can use in search
  "externalIdList": []
}

7. In-App search for Actor using voice

Utterance: “Alexa, find Sean Connery movies in Streamz”.
Action : Show search result for “Sean Connery movies”

autoPlay will be false. contentSearch: IContentSearch will have following values.

"parameterList": [
  {                                 // parameter 0
    "type": 0,                      // ContentSearchParamType::ACTOR
    "value": "Sean Connery"         // Keyword to use in search
    "externalIdList": []
  },
 {                                    // parameter 1
    "type": 13,                       // ContentSearchParamType::TYPE for VIDEO
    "value": "Sean Connery movies",   // Keyword to use in search
    "externalIdList": []
  }
]

Alexa might return multiple types. You can chose your own logic to implement search.

You can fetch the search keyword customer used from ContentSearchParamType::VIDEO type. It is “Sean Connery movies” in this example

3. Processing launch request - Implementation Guide

This section provides a detailed walkthrough of processing content search data and handling requests effectively. We define three primary intents to standardize request handling across both Alexa voice commands and remote input:

  • ‘Play’ intent

      App can play the requested content directly when
    
  • autoplay is true

  • amzn_id is present

  • Neither episode not season number are present.

      It is possible to have the Show ID (when the customer asks to play show, without mentioning season/episode) here. In that case, fallback to Show search page.
    
  • ‘Search’ intent

      App can not play the requested content directly when
    
  • autoplay is false

  • autoplay is true, but amz_id is not present. ex: “Alexa, play comedy movies”

  • autoplay is true, but only Epidode or season numbner is present. Ex: “Alexa, play Seabound season 3”

  • ‘playshow’ intent

      We handle “Alexa, play Seabound season 3 episode 5” here. It is not handled in ‘play’ intent as apps need to fetch episode url to play the requested episode.
    

Sample Implementation

First, we process the contentSearch parameter to determine the appropriate launch intent.
We extract the search keyword from the Alexa utterance, and retrieve the amzn_id, episode number, and season number from the contentSearch parameter. Based on these values and the autoplay flag, we determine the correct launch intent.

We define a ContentLaunchPayload interface to structure these extracted values and pass them to the app for further processing.

const processedPayload: ContentLaunchPayload = {
  launchIntent: null,   // The action to be performed by the app  (play, search, playshow)
  searchKeyword: null,  // The value to be used for searching content, It is the voice input from user.
  amzn_id: null,        // ID of the content to play
  episodeNumber: null,  // The episode number from TV Show
  seasonNumber: null,   // The season number from TV Show
};

Following function processContentSearch() process the input data and returns ContentLaunchPayload.

function processContentSearch(
  contentSearch: IContentSearch,
  autoPlay: boolean
): { processedPayload: ContentLaunchPayload | null } {
  const processedPayload: ContentLaunchPayload = {
    launchIntent: null,
    searchKeyword: null,
    amzn_id: null,
    episodeNumber: null,
    seasonNumber: null,
  };

  const searchParameters = contentSearch.getParameterList();

  if (searchParameters.length === 0) {
    console.warn("Content_Launcher_Sample: Error fetching search string");
    return {
      processedPayload,
    };
  }

  for (let i = 0; i < searchParameters.length; i++) {
    const param = searchParameters[i];

    const paramType = param.getParamType();
    const searchString = param.getValue();

    if (param.getExternalIdList) {
      const externalIdList = param.getExternalIdList();
      token.externalIds = [];

      for (const externalId of externalIdList) {
        const name = externalId.getName();
        const value = externalId.getValue();

        //Store the amazn_id if it is present. If multiple amzn_ids are present,  we store the first one
        // Update the code if you want to handle multiple amzn_ids when present

        if (name === "amzn_id" && !processedPayload.amzn_id) {
          processedPayload.amzn_id = value;
        }
      }
    }

    // Then handle paramType specific logic. If launched by Alexa , ContentSearchParamType.VIDEO type will have the search string

    if (paramType === ContentSearchParamType.VIDEO) {
      processedPayload.searchKeyword = searchString; // this can be used for search as keyword
    } else if (paramType === ContentSearchParamType.EPISODE) {
      processedPayload.episodeNumber = searchString;
    } else if (paramType === ContentSearchParamType.SEASON) {
      processedPayload.seasonNumber = searchString;
    }
  }

  // Determine launchIntent value based on the app's logic
  processedPayload.launchIntent = determineLaunchIntent(
    processedPayload,
    autoPlay
  );

  return {
    processedPayload,
  };
}

We implement determineLaunchIntent() function to decide play, search and playshow intent.

/*
  Function to determine the launch intent based on the processed content launcher payload.
  It checks the presence of amzn_id, episode number, and season number to decide whether
  to play content directly, search for content, or play a specific episode of a show.
  */
function determineLaunchIntent(
  payload: ContentLaunchPayload,
  autoPlay: boolean
): "play" | "search" | "playshow" | null {
  const { amzn_id, episodeNumber, seasonNumber } = payload;

  const hasCompleteEpisodeInfo = () =>
    episodeNumber !== null && seasonNumber !== null;
  const hasPartialEpisodeInfo = () =>
    (episodeNumber !== null) !== (seasonNumber !== null);
  const hasNoEpisodeInfo = () =>
    episodeNumber === null && seasonNumber === null;

  try {
    // Case 1: Direct play Intent
    // If autoPlay is true, amzn_id is present, and there is no episode or season number,
    // you can play content directly without additional logic.
   
    if (autoPlay && amzn_id && hasNoEpisodeInfo()) {
      return "play";
    }

    // Case 2: Search Intent
    // 1. If autoPlay is false,the request is handled as a content search
    // 2. If amzn_id is is not present, direct playback is not possible even whenautoplay is true . Hence, the request is handled as a content search.
    // 3. If either  episode number or  season number is provided without the other, direct playback cannot be resolved even when autoplay is true. Hence, the request is handled as a content search.

    if (
      !autoPlay ||               // autoPlay is false
      !amzn_id ||                // No content ID to play
      hasPartialEpisodeInfo()    // Incomplete episode/season information
    ) {
      return "search";
    }

    // Case 3: Playshow Intent (for episodic content)
    // If autoPlay is true, amzn_id is present, and both episode and season numbers are provided, 
    // it is a request to play a specific episode of a show.
    // This is handled in 'playshow' as it needs additional logic to play a specific episode of a show.

    if (autoPlay && amzn_id && hasCompleteEpisodeInfo()) {
      return "playshow";
    }

    return null;

  } catch (error) {
    console.error(
      "ontent_Launcher_Sample: Error determining launch intent:",
      error
    );
    return null;
  }
}

Next, we implement launchPlayOrSearch() function that will use processedPayload and perform the action. You will navigate to your app’s player or search screen as given in the pseudo code.

async function launchPlayOrSearch(processedPayload: ContentLaunchPayload) {

    const {launchIntent, searchKeyword, amzn_id, seasonNumber, episodeNumber} = processedPayload;

    switch (launchIntent) {
      case 'play':
        {
          /*
            1. Search for amz_id in your catalog.
            2. If it is Movie or Episode , play directly
            3. If it is a Show, decide your logic. ex: playing the recent episode or showing a list of episodes.
           */

           // Your implementation might look like:

            const movieUrl = await fetchMovieStreamUrl(amzn_id);

            if (movieUrl) {
                videoPlayer.play(movieUrl);
            } else {
                showSearchScreen(searchKeyword);
            }

        }
        break;

      case 'playshow':
        {
         /* You will get here if the play request has specifc amzn_id to play and has episode and season number.
            Use your API to fetch the episide url usig amzn_id, seasonNumber and episodeNumber.
            Note that the amzn_id is the ID of the show and not the episode */

          // Your implementation might look like:

            const episodeUrl = await fetchEpisodeUrl(amzn_id, seasonNumber, episodeNumber);
            videoPlayer.play(episodeUrl);
        }
        break;

      case 'search':
        {
          /*1. Handle Search using searchKeyword
            2. For TV Shows , you can search for amzn_id, season number, episode number and show specific content details page
          */

             if (amzn_id || seasonNumber || episodeNumber) {
               showSearchScreen(amzn_id , seasonNumber , episodeNumber);
             } else {
                showSearchScreen(searchKeyword);
            }
        }
        break;
    }
  }

4. Testing Content Launcher - Sample code

Use the attached sample to test the Content Launcher. It includes the complete code implementation discussed in the previous section. You can use the provided CLI tool to invoke the Content Launcher and validate its behavior.

content_launcher_sample_v2.zip

  1. Change the package name “com.kepler.contentlaunchersampleapp” to your app’s package name across your app’s files
  2. Change the partner id “PARTNER_ID” in manifest.toml to your app’s partner id.
  3. Follow the testing instructions from this document to test for your app’s content

Following are the processed payload for some sample utterances:

  1. “Alexa, Watch Seabound “
{
  "launchIntent": "play",
  "searchKeyword": "Seabound",
  "amzn_id": "1700000849",
  "episodeNumber": null,
  "seasonNumber": null
}
  1. “Alexa, Watch Seabound Season 1 “
{
    "launchIntent":"search",
    "searchKeyword":"Seabound season one",
    "amzn_id":"1700000849",
    "episodeNumber":null,
    "seasonNumber":"1"
}
  1. “Alexa, Watch Seabound season 1 episode 3 “
{
    "launchIntent": "playshow",
    "searchKeyword": "Seabound season one episode three",
    "amzn_id": "1700000849",
    "episodeNumber": "3",
    "seasonNumber": "1"
}
  1. “Alexa, Find Seabound “
{
  "launchIntent": "search",
  "searchKeyword": "Seabound",
  "amzn_id": "1700000849",
  "episodeNumber": null,
  "seasonNumber": null
}
  1. “Alexa, Find Seabound season 1 episode 3“
{
  "launchIntent": "search",
  "searchKeyword": "Seabound season one episode three",
  "amzn_id": "1700000849",
  "episodeNumber": "3",
  "seasonNumber": "1"
}
  1. “Alexa, Find Comedy Movies “
    “Alexa, Watch Comedy Movies “
{
  "launchIntent": "search",
  "searchKeyword": "comedy movies",
  "amzn_id": null,
  "episodeNumber": null,
  "seasonNumber": null
}
  1. “Alexa, Find Sean Connery Movies “
    “Alexa, Watch Sean Connery Movies “
{
  "launchIntent": "search",
  "searchKeyword": "comedy movies",
  "amzn_id": null,
  "episodeNumber": null,
  "seasonNumber": null
}

Note: " At this time,Content Launcher and Account Login are available to select partners only."