Google Cast is a technology that allows users to send online content to a device, such as a Chromecast or Android TV, connected to a television. Once the content is available on the television, users can control it from their mobile device or computer.
In this tutorial, you will learn how to create a basic Cast-enabled application for Android using the Cast SDK v3, which was announced during the 2016 Google I/O conference.
Cast Console Setup
Google Cast is built around two components: the receiver, which is essentially a webpage that is displayed on a casting device with your content, and the sender, which is the client program that requests media and controls playback.
Before you can create your sender application, you will need to register an account on the Google Cast Developer Console, and then create and configure a new receiver application. In order to register an account, you will need to pay a one-time $5 fee. Once your account is created, you can click on the red ADD NEW APPLICATION button to create a new receiver application.
Next, you will have three options: Custom Receiver, Styled Media Receiver, and Remote Display Receiver. For simplicity, in this tutorial you will use a Styled Media Receiver.
On the next screen, you will be able to select some basic settings for your receiver, such as the application name, an optional URL for a CSS style sheet to customize the look of the receiver, and the ability to enable guest mode and audio-only casting.
Once you hit the blue Save button, you will be presented with a screen that shows you the basic details of your new receiver app. You'll notice that this screen also contains your new Application ID. You will need to use this value in your Android application.
It's worth noting that even though your receiver app is created, it may take a few hours to be discoverable by your sender application.
In order to test, you will need to white-list at least one casting device. You can do this from the Google Cast Developer Console by clicking on the red ADD NEW DEVICE button. On the screen that comes up, you can enter your device's serial number and a description to white-list it for testing with your receiver application.
At this point, you should have a receiver created and a test device white-listed, so you're all set to start building an Android sender app. When you have created and published your application on the Play Store, you will want to return to the Cast Developer Console to publish your receiver, allowing any casting device to be used with your sender app.
Android Setup
The first thing you'll need to do in your Android app is include the Cast Framework and Media Router libraries under the dependencies
node in your build.gradle file.
compile 'com.android.support:mediarouter-v7:24.1.1' compile 'com.google.android.gms:play-services-cast-framework:9.4.0'
Next, you will want to store the application ID that you were given when creating your receiver in your strings.xml file.
<string name="cast_app_id">(your ID goes here)</string>
The final step in the setup process is including the Internet permission for your application. Open AndroidManifest.xml and include the following line before your application
node.
<uses-permission android:name="android.permission.INTERNET" />
Now that your setup is done, you can move on to including the media route button in your application.
Displaying a Routing Button and Connecting to Cast Devices
The routing button is the icon in an application's toolbar that generally signifies that an app supports casting for the user.
In order to get this button to appear in your application's Toolbar
, the easiest way is to include it in the menu XML file for your Activity
(it's also recommended that this go into every Activity
in your app).
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/media_route_menu_item" android:title="Cast" app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider" app:showAsAction="always" /> </menu>
Next, you will need to initialize this new MenuItem
in the onCreateOptionsMenu
method of your Activity
.
@Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.menu_main, menu); mMediaRouterButton = CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu, R.id.media_route_menu_item); return true; }
Once your media route button is initialized, you will want to add state listeners to your application for casting.
Available Listeners
While there are multiple listeners available, there are three worth discussing as you start to use the Google Cast framework.
-
CastStateListener
: This listener monitors the current casting state of an app. It is triggered when the app has switched toCONNECTING
,CONNECTED
,NOT_CONNECTED
, orNO_DEVICES_AVAILABLE
. -
AppVisibilityListener
: This listener has two methods:onAppEnteredForeground
andonAppEnteredBackground
. These methods are called when your app has been backgrounded by your user, or when the user has reopened your application, respectively.
-
SessionManagerListener
: The final listener we'll go over is also the most verbose. ASession
is the lifecycle of user interaction with a casting device, starting when the user has connected to a device, maintained through casting, and ending when the user has disconnected. The Google Cast Android framework interacts with theSession
through theSessionManager
object.
These three listeners can be associated with the Google Cast framework like so, where this
in this example is the Activity
that has implemented each of the above interfaces.
CastContext.getSharedInstance(this).addCastStateListener(this); CastContext.getSharedInstance(this).addAppVisibilityListener(this); CastContext.getSharedInstance(this).getSessionManager().addSessionManagerListener(this);
You may have also noticed that you access the SessionManager
and Cast framework using CastContext.getSharedInstance(Context)
. This is because the CastContext
, the main interaction point between your app and the Cast framework, is lazily initialized for improved app performance.
When your Activity
is no longer active, you will need to remember to remove these listeners.
CastContext.getSharedInstance(this).removeAppVisibilityListener(this); CastContext.getSharedInstance(this).removeCastStateListener(this); CastContext.getSharedInstance(this).getSessionManager().removeSessionManagerListener(this);
Creating an OptionsProvider
In order to do anything with the Cast framework, you will need to create a new class that extends OptionsProvider
. This class will be where you can configure various options for your sender app.
We'll keep this simple for now and just return a CastOptions
object from the getCastOptions
method, which will enable resuming saved sessions and reconnecting to sessions that are already in progress (though both of these are already enabled by default, they are provided here as examples).
The CastOptions
object is also where your receiver app ID is associated with your sender. Although the method getAdditionalSessionProviders
must be declared in this class, we can safely ignore it for our purposes.
public class CastOptionsProvider implements OptionsProvider { @Override public CastOptions getCastOptions(Context context) { CastOptions castOptions = new CastOptions.Builder() .setResumeSavedSession(true) .setEnableReconnectionService(true) .setReceiverApplicationId(context.getString(R.string.cast_app_id)) .build(); return castOptions; } @Override public List<SessionProvider> getAdditionalSessionProviders(Context context) { return null; } }
You will also need to include this class in your AndroidManifest.xml file within a meta-data
tag under your application
node.
<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME" android:value="com.tutsplus.googlecastv3.CastOptionsProvider" />
At this point, your application should be able to find any white-listed casting devices and connect to them through your application.
Routing Dialog Styling
Depending on the theme that you are using in your app (such as Theme.AppCompat.Light.NoActionBar
), you may have noticed some weird behaviors with colors in the casting device dialog, such as white font and icons on a white background.
You may also decide that you want to customize how the dialog appears to fit with your application. You can do this by overriding the two styles used for the Cast dialog: Theme.MediaRouter.Light.DarkControlPanel
and Theme.MediaRouter.LightControlPanel
. For example, if you are running into white font on a white background, you can include the following code in your styles.xml file to change the icons and font color to be black on the white background.
<style name="Theme.MediaRouter.Light.DarkControlPanel"> <item name="mediaRoutePlayDrawable">@drawable/mr_ic_play_light</item> <item name="mediaRoutePauseDrawable">@drawable/mr_ic_pause_light</item> <item name="mediaRouteCastDrawable">@drawable/mr_ic_cast_dark</item> <item name="mediaRouteAudioTrackDrawable">@drawable/ic_audiotrack_light</item> <item name="mediaRouteControllerPrimaryTextStyle">@style/MediaRouteControllerTextColor</item> <item name="mediaRouteControllerSecondaryTextStyle">@style/MediaRouteControllerTextColor</item> </style> <style name="MediaRouteControllerTextColor"> <item name="android:textColor">#000000</item> <item name="android:textSize">14sp</item> </style>
Casting Content
Once you've connected to a casting device, you'll probably want to let your users cast content to it. Luckily, the Cast SDK makes this incredibly easy to do. In your app, you will want to determine if your user has connected to a device, which can be done by ensuring that the SessionManager
has a current Session
and that the current Session
has a RemoteMediaClient
object associated with it.
if( CastContext.getSharedInstance(this).getSessionManager().getCurrentCastSession() != null && CastContext.getSharedInstance(this).getSessionManager().getCurrentCastSession().getRemoteMediaClient() != null ) {
Once you know that the application is associated with a RemoteMediaClient
, you will want to create a MediaInfo
object that contains a link to the remote content you want to play, as well as the streaming and content types for your media. When MediaInfo
is created and populated, you can call the load method on the RemoteMediaClient
to start casting the content. For example, the following code will cast a video file to the television.
RemoteMediaClient remoteMediaClient = CastContext.getSharedInstance(this).getSessionManager().getCurrentCastSession().getRemoteMediaClient(); MediaInfo mediaInfo = new MediaInfo.Builder(getString(R.string.movie_link)) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setContentType("videos/mp4") .build(); remoteMediaClient.load(mediaInfo, true, 0);
Metadata
The receiver and UI components in the Cast SDK use a MediaMetadata
object for storing and referencing information about the media that is currently being played. You can add values to this object using keys provided by the class, and you can add image URLs using the addImage
method.
MediaMetadata metadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE); metadata.putString(MediaMetadata.KEY_TITLE, "Title"); metadata.putString(MediaMetadata.KEY_SUBTITLE, "Subtitle"); metadata.addImage(new WebImage(Uri.parse(getString(R.string.movie_poster))));
Once the MediaMetadata
object is created, you can associate it with the content's MediaInfo
.
MediaInfo mediaInfo = new MediaInfo.Builder(getString(R.string.movie_link)) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setContentType("videos/mp4") .setMetadata(metadata) .build();
UI Components
While the Cast SDK handles the logic for connecting and casting content to the television, it also provides multiple UI components that help developers meet the Casting UI design guidelines.
Introductory Overlay
When your user first opens your application, it's recommended that you let them know that you support Google Cast. You can do this by including an IntroductoryOverlay
, which will highlight the cast button as it becomes available for the first time.
To include the IntroductoryOverlay
, the first thing you will want to do is add it as a member variable at the top of your main activity.
private IntroductoryOverlay mIntroductoryOverlay;
Once you have a common object for the overlay, you can create a method that will check to see if the media router button is shown, and if shown, will display the overlay.
This component is fleshed out using a simple builder pattern that will accept a String
for the text, a color resource ID, and a few other customization attributes. More often than not, you will also want to make sure that you call setSingleTime()
, so that the overlay is only ever shown once for the user.
private void showIntroductoryOverlay() { if (mIntroductoryOverlay != null) { mIntroductoryOverlay.remove(); } if ((mMediaRouterButton != null) && mMediaRouterButton.isVisible()) { new Handler().post(new Runnable() { @Override public void run() { mIntroductoryOverlay = new IntroductoryOverlay.Builder( MainActivity.this, mMediaRouterButton) .setTitleText("Introduction text") .setOverlayColor(R.color.colorPrimary) .setSingleTime() .setOnOverlayDismissedListener( new IntroductoryOverlay.OnOverlayDismissedListener() { @Override public void onOverlayDismissed() { mIntroductoryOverlay = null; } }) .build(); mIntroductoryOverlay.show(); } }); } }
Now that you have a method created to display the overlay, you will simply need to call it. There are two points where you should add this method: in onCreateOptionsMenu
, and in onCastStateChanged
from your CastStateListener
when the state is not NO_DEVICES_AVAILABLE
. This will handle both contingencies of when the routing button could appear.
@Override public void onCastStateChanged(int newState) { if (newState != CastState.NO_DEVICES_AVAILABLE) { showIntroductoryOverlay(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.menu_main, menu); mMediaRouterButton = CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu, R.id.media_route_menu_item); showIntroductoryOverlay(); return true; }
At this point, you should be able to start up your app and see the overlay, as shown in the next figure. If you need to see it again for testing purposes, you can clear your application's data and reopen.
Expanded Controls
While casting, you'll want to be able to provide an easy UI widget for controlling content on the user's television. Google has made this easy by providing the ExpandedControllerActivity
class within the Cast SDK.
To use this, create a new Java class and extend ExpandedControllerActivity
. This tutorial will create one called ExpandedControlsActivity
. Once your activity is created, update onCreateOptionsMenu
to include the casting routing button within the toolbar.
public class ExpandedControlsActivity extends ExpandedControllerActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.menu_main, menu); CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item); return true; } }
Next, open your OptionsProvider
class. You will want to go into the getCastOptions
method and create a CastMediaOptions
object that ties in to your ExpandedControllerActivity
. Once your CastMediaOptions
object is created, you can associate it with the CastOptions
item that is returned by the method.
CastMediaOptions mediaOptions = new CastMediaOptions.Builder() .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName()) .build(); CastOptions castOptions = new CastOptions.Builder() .setResumeSavedSession(true) .setEnableReconnectionService(true) .setReceiverApplicationId(context.getString(R.string.cast_app_id)) .setCastMediaOptions(mediaOptions) .build(); return castOptions;
Finally, to get a working ExpandedControllerActivity
, you will need to include it in AndroidManifest.xml, like so.
<activity android:name=".ExpandedControlsActivity" android:label="@string/app_name" android:theme="@style/ExpandedCastControlsStyle" android:launchMode="singleTask" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN"/> </intent-filter> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value=".MainActivity"/> </activity>
You should notice that the activity
node has a theme
property set. This style is used to optionally customize the ExpandedControllerActivity
and the buttons that are displayed.
The controller consists of four customizable button slots, with a play/pause toggle in the middle. Using a new style and an array resources, you can customize which buttons appear. In arrays.xml, I have added a new array
that sets slot 1 to empty, slot 2 to the 30-second rewind button, slot 3 (first item to the right of the play/pause toggle) to fast forward 30 seconds, and the last slot to host a mute toggle.
<?xml version="1.0" encoding="utf-8"?> <resources> <array name="cast_expanded_controller_control_buttons"> <item>@id/cast_button_type_empty</item> <item>@id/cast_button_type_rewind_30_seconds</item> <item>@id/cast_button_type_forward_30_seconds</item> <item>@id/cast_button_type_mute_toggle</item> </array> </resources>
You can then associate this array
with your Activity
by creating your new style
resource and overriding the castExpandedControllerStyle
value with a new style
that extends CastExpandedController
.
<style name="ExpandedCastControlsStyle" parent="Theme.AppCompat.NoActionBar"> <item name="castExpandedControllerStyle">@style/CustomCastExpandedController</item> </style> <style name="CustomCastExpandedController" parent="CastExpandedController"> <item name="castControlButtons">@array/cast_expanded_controller_control_buttons</item> </style>
At this point, you should be able to click on the image in your router dialog for casted media to open your new controller Activity
, or launch it yourself from within your application with a simple startActivity
call.
startActivity(new Intent(this, ExpandedControlsActivity.class));
Notification/Lock Screen Controls
When a user is casting content to their TV, there's a good chance that they won't keep your app in the foreground or their phone unlocked. When they navigate away from your app, you will want to provide an easy way for them to control the content of your app. You can do this by adding a notification to your app when it is not in the foreground for Lollipop and above devices, and the Cast SDK will handle creating a lock screen RemoteControlClient
for KitKat and earlier devices.
Adding notification/lock screen controls is rather straightforward, as it is all handled in the getCastOptions
method of your OptionsProvider
(CastOptionsProvider.java for this tutorial).
First, you will need to create an ArrayList
of strings that will contain the buttons that you want for your controls. Next, you can create an int
array that will contain the indices of the buttons that you will want to display when the notification is in compact mode.
Once you have your two arrays created, you will create a NotificationOptions
object that binds the actions to the new notification and assign an Activity
to be opened when the notification is selected. For this example, we will simply use the ExpandedControlsActivity
that we created in the last section.
Finally, you can add the notification to your CastMediaOptions
.
List<String> buttonActions = new ArrayList<>(); buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK); buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING); int[] compatButtonActionsIndicies = new int[]{ 0, 1 }; NotificationOptions notificationOptions = new NotificationOptions.Builder() .setActions(buttonActions, compatButtonActionsIndicies) .setTargetActivityClassName(ExpandedControlsActivity.class.getName()) .build(); CastMediaOptions mediaOptions = new CastMediaOptions.Builder() .setNotificationOptions(notificationOptions) .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName()) .build();
Now, when your users cast content to their televisions and lock the screen or navigate away from your app, a notification will appear that allows them to control the content on the big screen while they continue to interact with their phone. Clicking on the notification outside of the controls will bring your app back to the forefront with the ExpandedControlsActivity
, giving your users more fine-grained control of their viewing experience.
Mini Controller
The last UI widget you will learn about in this tutorial is the MiniControllerFragment
. This item can be placed in your activity layout files, and when your app is casting content, it will automatically become visible and provide an easily accessed controller for your users as they browse your app. Although this is the last component that we will discuss, it is also by far the easiest to implement. You simply need to include it in your layout files, like so.
<fragment android:id="@+id/castMiniController" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:visibility="gone" class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />
When you click on this item anywhere outside of the play/pause toggle, your ExtendedControllerActivity
will be brought up, giving your users easy access to the content on their television.
Conclusion
In this tutorial you have learned a lot about the new Google Cast SDK for Android, the UI components that are provided within it, and how to create a basic styled receiver application for casting. What you've covered here will help you build the most common types of casting applications, though Google also provides features that will let you quickly create Cast-enabled games and custom receiver applications.
Comments