This page contains code snippets and descriptions of the features available for customizing an Android TV Receiver app.
Getting started
Before you get started, review the sample code:
The code samples include
two Android projects. CastConnect-AndroidTV
contains a simple 'hello world'
version of Cast Connect integration. The CastConnect-TestHarness
folder
contains a Web sender, Android sender, iOS sender and Android TV receiver
application. The Android Sender and Android TV receiver are a combined project
that demonstrates how to use more advanced Cast Connect features like queueing
and the media status modifiers.
You can also check out the Cast Connect codelab with step-by-step instructions that take you through the process of enabling Cast Connect support to an ATV app.
Configuring libraries
To make Cast Connect APIs available to your Android TV app:
- Open the build.gradle file inside your application module directory.
- Verify that
google()
is included in the listedrepositories
.repositories {
- Add the latest versions of
play-services-cast-tv
(17.0.0 or higher) andplay-services-cast
(19.0.0 or higher) to your dependencies:dependencies {
Be sure you update this version number each time the services are updated. - Save the changes and click
Sync Project with Gradle Files
in the toolbar.
AndroidX requirement
New versions of Google Play Services require an app to have been updated to use
the androidx
namespace. Follow the instructions for
migrating to AndroidX.
Android TV app—prerequisites
In order to support Cast Connect in your Android TV app, you must create and support events from a media session. The data provided by your media session provides the basic information—for example, position, playback state, etc.—for your media status. Your media session also is used by the Cast Connect library to signal when it has received certain messages from a sender, like pause.
For more information on media session and how to initialize a media session, see the working with a media session guide.
Media session lifecycle
Your app should create a media session when playback starts and release it when it can’t be controlled any more. For example, if your app is a video app, you should release the session when the user exits the playback activity—either by selecting 'back' to browse other content or by backgrounding the app. If your app is a music app, you should release it when your app is no longer playing any media.
Updating session status
The data in your media session should be kept up-to-date with the status of your player. For example, when playback is paused, you should update the playback state as well as the supported actions. The following tables list what states you are responsible for keeping up to date.
MediaMetadataCompat
Metadata Field | Description |
---|---|
METADATA_KEY_DISPLAY_TITLE/METADATA_KEY_TITLE (required) | The media title. |
METADATA_KEY_DISPLAY_SUBTITLE | The subtitle. |
METADATA_KEY_DISPLAY_ICON_URI | The icon URL. |
METADATA_KEY_DURATION (required) | Media duration. |
METADATA_KEY_MEDIA_URI | The Content ID. |
METADATA_KEY_ARTIST | The artist. |
METADATA_KEY_ALBUM | The album. |
PlaybackStateCompat
Required Method | Description |
---|---|
setActions() | Sets supported media commands. |
setState() | Set the playing state and current position. |
MediaSessionCompat
Required Method | Description |
---|---|
setRepeatMode() | Sets repeat mode. |
setShuffleMode() | Sets shuffle mode. |
setMetadata() | Sets media metadata. |
setPlaybackState() | Sets playback state. |
private void updateMediaSession() {
MediaMetadataCompat metadata =
new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "title")
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle")
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI,mMovie.getCardImageUrl())
.build();
PlaybackStateCompat playbackState =
new PlaybackStateCompat.Builder()
.setState(
PlaybackStateCompat.STATE_PLAYING,
player.getPosition(),
player.getPlaybackSpeed(),
System.currentTimeMillis())
.build();
mediaSession.setMetadata(metadata);
mediaSession.setPlaybackState(playbackState);
}
Handling transport control
Your app should implement media session transport control callback. The following table shows what transport control actions they need to handle:
Actions | Description |
---|---|
onPlay() | Resume |
onPause() | Pause |
onSeekTo() | Seek to a position |
onStop() | Stop the current media |
public MyMediaSessionCallback extends MediaSessionCompat.Callback {
public void onPause() {
// Pause the player and update the play state.
...
}
public void onPlay() {
// Resume the player and update the play state.
...
}
public void onSeekTo (long pos) {
// Seek and update the play state.
...
}
...
}
mediaSession.setCallback(new MyMediaSessionCallback());
Configuring Cast support
When a launch request is sent out by a sender application, an intent is created
with an application namespace. Your application is responsible for handling it
and creating an instance of the CastReceiverContext
object when the TV app is
launched. The CastReceiverContext
object is needed to interact with Cast while
the TV app is running. This object enables your TV application to accept cast
media messages coming from any connected senders.
Android TV setup
Adding a launch intent filter
Add a new intent filter to the activity that you want to handle the launch intent from your sender app:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Specify receiver options provider
You need to implement a ReceiverOptionsProvider
to provide
CastReceiverOptions
:
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider {
@Override
public CastReceiverOptions getOptions(Context context) {
return new CastReceiverOptions.Builder(context)
.setStatusText("My App")
.build();
}
}
Then specify the options provider in your AndroidManifest
:
<meta-data
android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.example.mysimpleatvapplication.MyReceiverOptionsProvider" />
The ReceiverOptionsProvider
is used to provide the CastReceiverOptions
when
CastReceiverContext
is initialized.
Cast receiver context
Initialize the CastReceiverContext
when your app is created:
@Override
public void onCreate() {
CastReceiverContext.initInstance(this);
...
}
Start the CastReceiverContext
when your app moves to the foreground:
CastReceiverContext.getInstance().start();
Call stop()
on the CastReceiverContext
after the app goes into the
background for video apps or apps that don't support background playback:
// Player has stopped.
CastReceiverContext.getInstance().stop();
Additionally, if your app does support playing in the background, call stop()
on the CastReceiverContext
when it stops playing while in the background.
We strongly recommend you to use the LifecycleObserver from the
androidx.lifecycle
library
to manage calling CastReceiverContext.start()
and
CastReceiverContext.stop()
, especially if your native app has multiple
activities. This avoids a race conditions when you call start()
and stop()
from different activities.
// Create a LifecycleObserver class.
public class MyLifecycleObserver implements DefaultLifecycleObserver {
@Override
public void onStart(LifecycleOwner owner) {
// App prepares to enter foreground.
CastReceiverContext.getInstance().start();
}
@Override
public void onStop(LifecycleOwner owner) {
// App has moved to the background or has terminated.
CastReceiverContext.getInstance().stop();
}
}
// Add the observer when your application is being created.
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Initialize CastReceiverContext.
CastReceiverContext.initInstance(this /* android.content.Context */);
// Register LifecycleObserver
ProcessLifecycleOwner.get().getLifecycle().addObserver(
new MyLifecycleObserver());
}
}
// In AndroidManifest.xml set MyApplication as the application class
<application
...
android:name=".MyApplication">
Connecting MediaSession to MediaManager
When you create a MediaSession
, you also need to provide the current
MediaSession
token to CastReceiverContext
so it knows where to send the
commands and retrieve the media playback state:
MediaManager mediaManager = receiverContext.getMediaManager();
mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());
When you release your MediaSession
due to inactive playback, you should set a
null token on MediaManager
:
myPlayer.stop();
mediaSession.release();
mediaManager.setSessionCompatToken(null);
If your app supports playing media while your app is in the background, instead
of calling CastReceiverContext.stop()
when your app goes background, you
should call it only when your app is in the background and no longer plays
media. For example:
public class MyLifecycleObserver implements DefaultLifecycleObserver {
...
// App has moved to the background.
@Override
public void onPause(LifecycleOwner owner) {
mIsBackground = true;
myStopCastReceiverContextIfNeeded();
}
}
// Stop playback on the player.
private void myStopPlayback() {
myPlayer.stop();
myStopCastReceiverContextIfNeeded();
}
// Stop the CastReceiverContext when both the player has stopped and the app has moved to the background.
private void myStopCastReceiverContextIfNeeded() {
if (mIsBackground && myPlayer.isStopped()) {
CastReceiverContext.getInstance().stop();
}
}
Using Exoplayer with Cast Connect
If you are using
Exoplayer
, you
can use the
MediaSessionConnector
to automatically maintain the session and all related information including the
the playback state instead of tracking the changes manually.
MediaSessionConnector.MediaButtonEventHandler
can be used to handle MediaButton events by calling
setMediaButtonEventHandler(MediaButtonEventHandler)
which are otherwise
handled by MediaSessionCompat.Callback
by default.
To integrate
MediaSessionConnector
in your app, add the following to your player activity class or wherever you
manage your media session:
public class PlayerActivity extends Activity {
private MediaSessionCompat mMediaSession;
private MediaSessionConnector mMediaSessionConnector;
private MediaManager mMediaManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mMediaSession = new MediaSessionCompat(this, LOG_TAG);
mMediaSessionConnector = new MediaSessionConnector(mMediaSession);
...
}
@Override
protected void onStart() {
...
mMediaManager = receiverContext.getMediaManager();
mMediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());
mMediaSessionConnector.setPlayer(mExoPlayer);
mMediaSessionConnector.setMediaMetadataProvider(mMediaMetadataProvider);
mMediaSession.setActive(true);
...
}
@Override
protected void onStop() {
...
mMediaSessionConnector.setPlayer(null);
mMediaSession.release();
mMediaManager.setSessionCompatToken(null);
...
}
}
Sender app setup
Enable Cast Connect support
Once you have updated your sender app with Cast Connect support, you can declare
the readiness by setting the androidReceiverCompatible
flag to true.
Requires play-services-cast-framework
version
19.0.0
or higher.
The androidReceiverCompatible
flag is set in
LaunchOptions
(which is part of CastOptions
):
public class CastOptionsProvider implements OptionsProvider { @Override public CastOptions getCastOptions(Context context) { LaunchOptions launchOptions = new LaunchOptions.Builder() .setAndroidReceiverCompatible(true) .build(); return new CastOptions.Builder() .setLaunchOptions(launchOptions) ... .build(); } }
Requires google-cast-sdk
version v4.4.8
or
higher.
The androidReceiverCompatible
flag is set in
GCKLaunchOptions
(which is part of
GCKCastOptions
):
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID)) ... let launchOptions = GCKLaunchOptions() launchOptions.androidReceiverCompatible = true options.launchOptions = launchOptions GCKCastContext.setSharedInstanceWith(options)
Requires Chrome browser version
M87
or higher.
const context = cast.framework.CastContext.getInstance(); const castOptions = new cast.framework.CastOptions(); castOptions.receiverApplicationId = kReceiverAppID; castOptions.androidReceiverCompatible = true; context.setOptions(castOptions);
Cast Developer Console setup
Configure the Android TV app
Add the package name of your Android TV app in Cast Developer Console to associate it with your Cast App ID.
Register developer devices
Register the serial number of the Android TV device that you are going to use for development in the Cast Developer Console.
You can find the serial number by going to Settings > Device Preferences > Chromecast built-in > Serial number on your Android TV.
Without registration, Cast Connect will only work for apps installed from the Google Play Store due to security reasons.
For further information about registering a Cast or Android TV device for Cast development, see the registration page.
Loading media
If you have already implemented deep link support in your Android TV app, then you should have a similar definition configured in your Android TV Manifest:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https"/>
<data android:host="www.example.com"/>
<data android:pathPattern=".*"/>
</intent-filter>
</activity>
Load by entity on sender
On the senders, you can pass the deep link by setting the entity
in the load
request:
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") ... .build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Requires Chrome browser version
M87
or higher.
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); mediaInfo.entity = 'https://example.com/watch/some-id'; ... let request = new chrome.cast.media.LoadRequest(mediaInfo); request.credentials = 'user-credentials'; ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
The load command is sent via an intent with your deep link and the package name you defined in the developer console.
Setting ATV credentials on sender
It is possible that your Web Receiver app and Android TV app support different
deep links and credentials
(for example if you are handling authentication
differently on the two platforms). To address this, you can provide alternate
entity
and credentials
for Android TV:
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") .setAtvEntity("myscheme://example.com/atv/some-id") ... .build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") .setAtvCredentials("atv-user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id") mediaInfoBuilder.atvEntity = "myscheme://example.com/atv/some-id" ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" mediaLoadRequestDataBuilder.atvCredentials = "atv-user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Requires Chrome browser version
M87
or higher.
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); mediaInfo.entity = 'https://example.com/watch/some-id'; mediaInfo.atvEntity = 'myscheme://example.com/atv/some-id'; ... let request = new chrome.cast.media.LoadRequest(mediaInfo); request.credentials = 'user-credentials'; request.atvCredentials = 'atv-user-credentials'; ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
If the web receiver app is launched, the CAF SDK will still use the entity
and credentials
in the MediaLoadRequestData
. However if your Android TV app
is launched, we’ll override the entity
and credentials
with your
atvEntity
and atvCredentials
(if specified).
Loading by Content ID or MediaQueueData
If you are not using entity
or atvEntity
, and are using Content ID or
Content URL in your Media Information or use the more detailed Media Load
Request Data, you need to add the following predefined intent filter in
your Android TV app:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
On the sender side, similar to load by entity, you
can create a MediaLoadRequestData
with your content information and call
load()
.
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id").build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(contentId: "some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Requires Chrome browser version
M87
or higher.
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); ... let request = new chrome.cast.media.LoadRequest(mediaInfo); ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
Handling load requests
In your activity, to handle these load requests, you need to handle the intents in your activity lifecycle callbacks:
public class MyActivity extends Activity {
@Override
protected void onStart() {
super.onStart();
MediaManager mediaManager =
CastReceiverContext.getInstance().getMediaManager();
// Pass the intent to the SDK. You can also do this in onCreate().
if (mediaManager.onNewIntent(getIntent())) {
// If the SDK recognizes the intent, you should early return.
return;
}
// If the SDK doesn't recognize the intent, you can handle the intent with
// your own logic.
...
}
// For some cases, a new load intent triggers onNewIntent() instead of
// onStart().
@Override
protected void onNewIntent(Intent intent) {
MediaManager mediaManager =
CastReceiverContext.getInstance().getMediaManager();
// Pass the intent to the SDK. You can also do this in onCreate().
if (mediaManager.onNewIntent(intent)) {
// If the SDK recognizes the intent, you should early return.
return;
}
// If the SDK doesn't recognize the intent, you can handle the intent with
// your own logic.
...
}
}
If MediaManager
detects the intent is a load intent, it extracts a
MediaLoadRequestData
object from the intent, and invoke MediaLoadCommandCallback.onLoad()
. You
need to override this method to handle the load request. The callback must be
registered before MediaManager.onNewIntent()
is called (it’s recommended to
be on an Activity or Application onCreate()
method).
public class MyActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MediaManager mediaManager =
CastReceiverContext.getInstance().getMediaManager();
mediaManager.setMediaLoadCommandCallback(new MyMediaLoadCommandCallback());
}
}
public class MyMediaLoadCommandCallback extends MediaLoadCommandCallback {
@Override
public Task<MediaLoadRequestData> onLoad(String senderId, MediaLoadRequestData loadRequestData) {
return Tasks.call(() -> {
// Resolve the entity into your data structure and load media.
MediaInfo mediaInfo = loadRequestData.getMediaInfo();
if (!checkMediaInfoSupported(mediaInfo)) {
// Throw MediaException to indicate load failure.
throw new MediaException(
new MediaError.Builder()
.setDetailedErrorCode(DetailedErrorCode.LOAD_FAILED)
.setReason(MediaError.ERROR_REASON_INVALID_REQUEST)
.build());
}
myFillMediaInfo(new MediaInfoWriter(mediaInfo));
myPlayerLoad(mediaInfo.getContentUrl());
// Update media metadata and state (this clears all previous status
// overrides).
castReceiverContext.getMediaManager()
.setDataFromLoad(loadRequestData);
...
castReceiverContext.getMediaManager().broadcastMediaStatus();
// Return the resolved MediaLoadRequestData to indicate load success.
return loadRequestData;
});
}
private void myPlayerLoad(String contentURL) {
myPlayer.load(contentURL);
// Update the MediaSession state.
PlaybackStateCompat playbackState =
new PlaybackStateCompat.Builder()
.setState(
player.getState(), player.getPosition(), System.currentTimeMillis())
...
.build();
mediaSession.setPlaybackState(playbackState);
}
To process the load intent, you can parse the intent into the data structures
we defined
(MediaLoadRequestData
for load requests).
Supporting media commands
Basic playback control support
Basic integration commands includes the commands that are compatible with media session. These commands are notified via media session callbacks. You need to register a callback to media session to support this (you might be doing this already).
private class MyMediaSessionCallback extends MediaSessionCompat.Callback {
public void onPause() {
// Pause the player and update the play state.
myPlayer.pause();
}
public void onPlay() {
// Resume the player and update the play state.
myPlayer.play();
}
public void onSeekTo(long pos) {
// Seek and update the play state.
myPlayer.seekTo(pos);
}
...
}
mediaSession.setCallback(new MyMediaSessionCallback());
Supporting Cast control commands
There are some Cast commands that are not available in MediaSession
, such as
skipAd()
or
setActiveMediaTracks()
.
Also, some queue commands needs to be implemented here because the Cast queue
is not fully compatible with MediaSession
queue.
public MyMediaCommandCallback extends MediaCommandCallback {
@Override
public Task<Void> onSkipAd(RequestData requestData) {
// Skip your ad
...
return Tasks.forResult(null);
}
}
MediaManager mediaManager =
CastReceiverContext.getInstance().getMediaManager();
mediaManager.setMediaCommandCallback(new MyMediaCommandCallback());
Specify supported media commands
As with your Cast receiver, your Android TV app should specify which commands
are supported, so senders can enable or disable certain UI controls. For
commands that are part of MediaSession
, specify the commands in
PlaybackStateCompat
. Additional commands should be specified in the
MediaStatusModifier
.
// Set media session supported commands
PlaybackStateCompat playbackState =
new PlaybackStateCompat.Builder()
.setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE)
.setState(PlaybackStateCompat.STATE_PLAYING)
.build();
mediaSession.setPlaybackState(playbackState);
// Set additional commands in MediaStatusModifier
MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager();
mediaManager.getMediaStatusModifier()
.setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT);
Hide unsupported buttons
If your Android TV app only supports basic media control but your Cast receiver app supports more advanced control, you should make sure your sender app behave correctly when casting to the Android TV app. For example, if your Android TV app doesn’t support changing playback rate while your Web Receiver app does, you should set the supported actions correctly on each platform and make sure your sender app renders UI properly.
Modifying MediaStatus
To support advanced features like tracks, ads, live, and queueing, your Android
TV app needs to provide additional information that can't be ascertained via
MediaSession
.
We provide the MediaStatusModifier
class for you to achieve this.
MediaStatusModifier
will always operate on the MediaSession
which you have
set in CastReceiverContext
.
To create and broadcast MediaStatus
:
MediaManager mediaManager = castReceiverContext.getMediaManager();
MediaStatusModifier statusModifier = mediaManager.getMediaStatusModifier();
statusModifier
.setLiveSeekableRange(seekableRange)
.setAdBreakStatus(adBreakStatus)
.setCustomData(customData);
mediaManager.broadcastMediaStatus();
Our client library will get the base MediaStatus
from MediaSession
, your
Android TV app can specify additional status and override status via a
MediaStatus
modifier.
Some states and metadata can set both in MediaSession
and
MediaStatusModifier
. We strongly recommend you only set them in
MediaSession
. You can still use the modifier to override the states in
MediaSession
—this is discouraged because the status in the modifier always
have a higher priority than values provided by MediaSession
.
Intercepting MediaStatus before sending out
Same as the web receiver SDK, if you want to do some finishing touches before
sending out, you can specify a MediaStatusInterceptor
to process the
MediaStatus
to be sent. We pass in a MediaStatusWriter
to manipulate the
MediaStatus
before it is sent out.
mediaManager.setMediaStatusInterceptor(new MediaStatusInterceptor() {
@Override
public void intercept(MediaStatusWriter mediaStatusWriter) {
// Perform customization.
mediaStatusWriter.setCustomData(new JSONObject("{data: \"my Hello\"}"));
}
});
Handling user credentials
Your Android TV app might only allow certain users to launch or join the app session. For example, only allow a sender to launch or join if:
- The sender app is logged into same account and profile as ATV app.
- The sender app is logged into same account, but different profile as ATV app.
If your app can handle multiple or anonymous users, you may allow additional any user to join the ATV session. If the user provides credentials, your ATV app needs to handle their credentials so their progress and other user data can be properly tracked.
When your sender app launches or joins your Android TV app, your sender app should provide the credentials that represents who is joining the session.
Before a sender launches and joins your Android TV app, you can specify a launch checker to see if the sender credentials are allowed. If not, the Cast Connect SDK will fall back to launching your web receiver.
Sender app launch credentials data
On the sender side, you can specify the CredentialsData
to represent who is
joining the session.
The credentials
is a string which can be user-defined, as long as your ATV
app can understand it. The credentialsType
defines which platform the
CredentialsData
is coming from or can be a custom value. By default it is set
to the platform that it is being sent from.
The CredentialsData
is only passed to your Android TV app during launch or
join time. If you set it again while you are connected, it won't be passed to
your Android TV app. If your sender switches the profile while connected, you
could either stay in the session, or call
SessionManager.endCurrentCastSession()
if you think the new profile is
incompatible with the session.
The SDK keeps the CredentialsData
for each sender in SenderInfo
, which can
be accessed via the CastReceiverContext
.
Requires play-services-cast-framework
version
19.0.0
or higher.
CastContext.getSharedInstance().setLaunchCredentialsData( new CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build());
Requires google-cast-sdk
version v4.4.8
or
higher.
Can be called anytime after the options are set:
GCKCastContext.setSharedInstanceWith(options)
.
GCKCastContext.sharedInstance().launchCredentialsData( GCKCredentialsData(credentials: "{\"userId\": \"abc\"}")
Requires Chrome browser version
M87
or higher.
Can be called anytime after the options are set:
cast.framework.CastContext.getInstance().setOptions(options);
.
let credentialsData = new chrome.cast.CredentialsData("{\"userId\": \"abc\"}"); cast.framework.CastContext.getInstance().setLaunchCredentialsData(credentialsData);
Implementing ATV launch request checker
The CredentialsData
is passed to your Android TV app when a sender tries
to launch or join. You can implement a LaunchRequestChecker
to allow or reject
this request.
If a request is rejected, the web receiver is loaded instead of launching natively into the ATV app. You should reject a request if your ATV is unable to handle the user requesting to launch or join. Examples could be that a different user is logged into the ATV app than is requesting and your app is unable to handle switching credentials, or there is not a user currently logged into the ATV app.
If a request is allowed, the ATV app launches. You can customize this
behavior depending on if your app supports sending load requests when a user
is not logged into the ATV app or if there is a user mismatch. This behavior is
fully cusomizable in the LoadRequestChecker
.
Create a class implementing the LoadRequestChecker
interface:
public class MyLaunchRequestChecker
implements CastReceiverOptions.LaunchRequestChecker {
@Override
public Task<Boolean> checkLaunchRequestSupported(CastLaunchRequest launchRequest) {
return Tasks.call(() -> myCheckLaunchRequest(launchRequest));
}
}
private boolean myCheckLaunchRequest(CastLaunchRequest launchRequest) {
CredentialsData credentialsData = launchRequest.getCredentialsData();
if (credentialsData == null) {
return false; // or true if you allow anonymous users to join.
}
// The request comes from a mobile device, e.g. checking user match.
if (credentialsData.getCredentialsType().equals(CredentialsData.CREDENTIALS_TYPE_ANDROID)) {
return myCheckMobileCredentialsAllowed(credentialsData.getCredentials());
}
// Unrecognized credentials type.
return false;
}
Then set it in your ReceiverOptionsProvider
:
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider {
@Override
public CastReceiverOptions getOptions(Context context) {
return new CastReceiverOptions.Builder(context)
...
.setLaunchRequestChecker(new MyLaunchRequestChecker())
.build();
}
}
Resolving true
in the LaunchRequestChecker
launches the ATV app and
false
launches your web receiver app.
Sending & Receiving Custom Messages
The Cast protocol allows you to send custom string messages between senders and
your receiver application. You must register a namespace (channel) to send
messages across before initializing your CastReceiverContext
.
Android TV—Specify Custom Namespace
You need to specify your supported namespaces in your CastReceiverOptions
during setup:
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider {
@Override
public CastReceiverOptions getOptions(Context context) {
return new CastReceiverOptions.Builder(context)
.setCustomNamespaces(
Arrays.asList("urn:x-cast:com.example.cast.mynamespace"))
.build();
}
}
Android TV—Sending Messages
// If senderId is null, then the message is broadcasted to all senders.
CastReceiverContext.getInstance().sendMessage(
"urn:x-cast:com.example.cast.mynamespace", senderId, customString);
Android TV—Receive Custom Namespace Messages
class MyCustomMessageListener implements Cast.MessageReceivedListener{
@Override
public void onMessageReceived(
String namespace, String senderId, String message) {
...
}
}
CastReceiverContext.getInstance().MessageReceivedCallback(
"urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageCallback());