How to integrate the Google Assistant in a TV app
Earlier this year, we href="https://www.blog.google/products/assistant/coming-soon-google-assistant-android-tv-and-more/">announced that the Google Assistant will be coming to Android TV and it has href="https://www.blog.google/products/assistant/your-google-assistant-now-on-android-tv/">arrived. The Google Assistant on href="https://assistant.google.com/intl/en_us/platforms/tv/">Android TV will allow users to discover, launch and control media content, control smart devices like light bulbs, and much more. Your Assistant also understands that you're interacting on a TV, so you'll get the best experience possible while watching your favorite movies and TV shows.
The Google Assistant has a built-in capability to understand commands like "Watch The Incredibles", and media controls, like pause, fast forward, etc. This article will walk through how to integrate the Google Assistant into your application.
There are no new APIs needed to integrate with the Google Assistant. You just need to follow the pattern that the Google Assistant expects from your app. If you want to experiment and play with the APIs and the Assistant, you can download this sample from github.
Discovery
The Google Assistant has made some changes to improve finding information on Android TV.
There are a few ways to expose your content to users through the Google Assisant.
Server side integration. (href="https://docs.google.com/forms/d/e/1FAIpQLSdHdj-7o2c3fsMpClRfKL9qJiCrPlnmJ55oHE9jIpytKLm06w/viewform">Requires registration and onboarding)
You need to provide your href="https://developers.google.com/search/docs/data-types/tv-movies">content catalog to Google. This data is ingested and available to the Google Assistant outside of your app.
This is not specific for Google Assistant. It will also enable other Google services such as search and discovery on Google Search, Google Play, Google Home App, and Android TV.
Client side integration. (Available to all apps)
If your app is already href="https://developer.android.com/training/tv/discovery/searchable.html">searchable, then you only need to handle the href="https://developer.android.com/reference/android/support/v4/content/IntentCompat.html#EXTRA_START_PLAYBACK">EXTRA_START_PLAYBACK flag, which we go into more detail later. Content will auto-play if the app name is explicitly specified in the search results or if the user is already in your app.
Once your app is searchable, you can test by asking the Assistant or, if you are in a loud area, test quietly by running the following adb command:
class="prettyprint">adb shell am start -a "android.search.action.GLOBAL_SEARCH" --es query \"The Incredibles\"Each app that responds to the search query will have a row displaying their search results. Notice that YouTube and the sample app, Assistant Playback, each receive their own rows for content that match the search query.
For specific searches such as "Play Big Buck Bunny", the Assistant will present a card with a button for each app that href="https://developer.android.com/training/tv/discovery/searchable.html#details">exactly matched the search query. In the screenshot below, you can see the sample app, Assistant Playback, shows up as an option to watch Big Buck Bunny.
There are times when the Google Assistant will launch an app directly to start playing content. An example of when this occurs is when content is exclusive to the app; "Play the Netflix original House of Cards".
Launching
When the user selects a video from search results, an intent is sent to your app. The priority order for the intent actions are as follows:
- Intent specified in the cursor returned from the search (href="https://developer.android.com/reference/android/app/SearchManager.html#SUGGEST_COLUMN_INTENT_ACTION">SUGGEST_COLUMN_INTENT_ACTION).
- Intent specific in the href="https://developer.android.com/guide/topics/search/searchable-config.html">searchable.xml file with the href="https://developer.android.com/guide/topics/search/searchable-config.html#searchSuggestIntentAction">searchSuggestIntentAction value.
- Defaults to ACTION_VIEW.
In addition, the Assistant will also pass an extra to signal if playback should begin immediately. You app should be able to handle the intent and expect a boolean extra called href="https://developer.android.com/reference/android/support/v4/content/IntentCompat.html#EXTRA_START_PLAYBACK">EXTRA_START_PLAYBACK.
class="prettyprint">import static android.support.v4.content.IntentCompat.EXTRA_START_PLAYBACK;public class SearchableActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent() != null) {
// Retrieve video from getIntent().getData().
boolean startPlayback = getIntent().getBooleanExtra(EXTRA_START_PLAYBACK, false);
Log.d(TAG, "Should start playback? " + (startPlayback ? "yes" : "no"));
if (startPlayback) {
// Start playback.
startActivity(...);
} else {
// Show details for movie.
startActivity(...);
}
}
finish();
}
}
You can test this by modifying and running the following adb command. If your app has a custom action, then replace android.intent.action.VIEW with the custom action. Replace the value of the href="https://developer.android.com/studio/command-line/adb.html#IntentSpec">-d argument with the URI you return from the Assistant's query.
class="prettyprint">adb shell 'am start -a android.intent.action.VIEW --ezandroid.intent.extra.START_PLAYBACK true -d <URI> -f 0x14000000'
The -f argument is the logical OR value from href="https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_NEW_TASK">FLAG_ACTIVITY_NEW_TASK | href="https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_CLEAR_TOP">FLAG_ACTIVITY_CLEAR_TOP. This will force your activity to be freshly launched.
For example, in the sample app, you can run the following command to launch playback of "Big Buck Bunny" as if the assistant had launched it.
class="prettyprint">adb shell 'am start -a android.intent.action.VIEW --ezandroid.intent.extra.START_PLAYBACK true -d
content://com.example.android.assistantplayback/video/2 -n
com.example.android.assistantplayback/.SearchableActivity -f 0x14000000'
The URI above is defined by the value of android:searchSuggestIntentData in href="https://github.com/googlesamples/leanback-assistant/blob/master/app/src/main/res/xml/searchable.xml">searchable.xml (content://com.example.android.assistantplayback/video/) in addition to the value of href="https://developer.android.com/reference/android/app/SearchManager.html#SUGGEST_COLUMN_INTENT_DATA_ID">SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID (2) returned from the query.
Note that intents may be cached by the Google Assistant up to 7 days. Your app could receive a request to play content that is no longer available. The intent handler should be designed to be stateless and not rely on any previously knowledge to handle the deep link. Your app should gracefully handle this situation. One solution would be to show an error message and let the user land on your main activity or another relevant activity.
Playback
If your app href="https://developer.android.com/guide/topics/media-apps/working-with-a-media-session.html">implements MediaSession correctly, then your app should work right away with no changes.
The Google Assistant assumes that your app handles transport controls. The Assistant uses the href="https://developer.android.com/reference/android/media/session/MediaController.TransportControls.html">TransportControls to send media commands to your app's MediaSession. Video apps must support the following controls wherever possible:
- Play/Pause/Stop
- Previous/Next
- Rewind/Fast Forward (implemented with href="https://developer.android.com/reference/android/media/session/MediaController.TransportControls.html#seekTo(long)">seekTo())
You can easily get a hook for these controls by implementing a href="https://developer.android.com/reference/android/media/session/MediaSession.Callback.html">MediaSession.Callback. If you play videos using href="https://developer.android.com/reference/android/support/v17/leanback/media/PlaybackTransportControlGlue.html">PlaybackTransportControlGlue, then all your callback needs to do it sync the glue and the MediaSession. Otherwise use this callback to sync your player.
class="prettyprint">public class MyMediaSessionCallback extends MediaSessionCompat.Callback {private final PlaybackTransportControlGlue<?> mGlue;
public MediaSessionCallback(PlaybackTransportControlGlue<?> glue) {
mGlue = glue;
}
@Override
public void onPlay() {
Log.d(TAG, "MediaSessionCallback: onPlay()");
mGlue.play();
updateMediaSessionState(...);
}
@Override
public void onPause() {
Log.d(TAG, "MediaSessionCallback: onPause()");
mGlue.pause();
updateMediaSessionState(...);
}
@Override
public void onSeekTo(long position) {
Log.d(TAG, "MediaSessionCallback: onSeekTo()");
mGlue.seekTo(position);
updateMediaSessionState(...);
}
@Override
public void onStop() {
Log.d(TAG, "MediaSessionCallback: onStop()");
// Handle differently based on your use case.
}
@Override
public void onSkipToNext() {
Log.d(TAG, "MediaSessionCallback: onSkipToNext()");
playAndUpdateMediaSession(...);
}
@Override
public void onSkipToPrevious() {
Log.d(TAG, "MediaSessionCallback: onSkipToPrevious()");
playAndUpdateMediaSession(...);
}
}
Continue learning
Check out the following articles and training documents to continue learning about MediaSession and Video apps.
- href="https://developer.android.com/guide/topics/media-apps/interacting-with-assistant.html">Interacting with the Google Assistant
- href="https://developer.android.com/guide/topics/media-apps/video-app/building-a-video-player-activity.html">MediaSession implementation for videos
- href="https://medium.com/google-developers/understanding-mediasession-part-1-3-e4d2725f18e4">Understanding MediaSession
To play around with the Google Assistant on Android TV, download the href="https://github.com/googlesamples/leanback-assistant">sample app and run it on Nvidia Shield running Android M or above.
If you would like to continue the discussion, leave a response or talk to me on Twitter.
0 comments