9 minutes

Developing a Chromecast Ready Application for Android Platform

What is Chromecast and how does it work?

Chromecast, latest Google’s gadget allows users to stream media from their smartphones to TV.

This 2.83-inch and $35 device was announced on July 24, 2013, and was named Time's 2013 Gadget of the Year. Basically, it’s a screen sharing technology that allows content streaming from phone, tablet or laptop to a large display device like TV.

After the initial release, Chromacast SDK was in a beta stage, and that was limiting usage of the device. But on February 3, 2014 Google released final SDK version.

Now, the number of apps that supports Chromecast is rapidly growing, so we decided to describe our experience of developing a Chromecast ready application for Android. This article covers the topic of developing an Android Chromecast-ready application: below you’ll find some notes, comments and advice for the developers, as well as code samples and source code of the app built by Lemberg’s team.

How does it work?

Chromecast device is running a special version of Chrome browser, that can run special web applications (named receiver application). These applications' goals are to deliver media content to Chromecast users. But Chromecast devices have no direct controls, so obviously, some external stuff should be used and, as noted earlier, it can be either mobile app or Web app (named sender application).

So, with a help of sender applications, you can control a Chromecast device. But in what way?

This can be done through common WiFi network — i.e. both Sender and Receiver applications should be connected to the same WiFi network. This channel is bidirectional so, a sender app can receive events from “receiver” application. For example, “sender” app can issue a command to play some media stream, while “Receiver” application will notify sender app about playing status and position.

Chromecast application development

In general, Chromecast application development involves the development of sender and receiver applications. As was noted earlier, the sender app can be either a mobile app or web app, although it is not restricted to that. Receiver app is a special form of a web app which is capable of running on a Chromecast device.

In this article, we will describe a development of general Chromecast application which will allow messages exchange between sender and receiver parts. The app we have developed can be used as a skeleton for further development.

But before we begin we need to setup Chromecast device and register our application.

Chromecast setup

The setup procedure is described here.

Registering an application

You need to register your application in order to get application ID (which will be used in both sender and receiver applications).

The registration procedure is described here link.

Developing a sender application

In this article, we will cover the process of sender application development. However, in order to operate, a sender application requires a receiver app to be built. So we can check the results only after developing a receiver app.

Also, developing a sender application for Android is described here.

In our case, the sender application is an Android app which uses a set of technologies and libraries to communicate with the receiver application.

Prerequisites:

Steps for creating a skeleton

Create an empty Android application and add the following libraries as dependencies:

  • android-sdk\extras\android\support\v7\appcompat
  • android-sdk\extras\android\support\v7\mediarouter
  • android-sdk\extras\google\google_play_services\libproject\google-play-services_lib

Specify minimum Android version:

<uses-sdk

       android:minSdkVersion="9"
       android:targetSdkVersion="19" />
and permissions:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
also the following meta-data element is required (place it inside application node):
<meta-data
      android:name="com.google.android.gms.version"
      android:value="@integer/google_play_services_version" />
and last thing - since we will use “appcompat” ActionBarActivity as base for our activity then we need to specify appcompat theme:
<application
android:label="@string/app_name"
android:icon="@drawable/ic_launcher"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar">

Now create an activity which extends ActionBarActivity.

Implementation

There are several key states in application functionality:

  • device discovery;
  • managing the application session;
  • messages exchange between a sender application and a receiver application.

Let's describe all stages in details.

Device discovery can be launched by pressing the “Cast” button. There are several ways to show this button, but we will use MediaRouter ActionBar provider.

Add the following menu item to your menu:

<item
       android:id="@+id/media_route_menu_item"
       android:title="Route"
       app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"
       app:showAsAction="always"/>

The following fields are involved in the process:

private final MediaRouter.Callback mediaRouterCallback = new MediaRouter.Callback()
{
   @Override
   public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route)
   {
       CastDevice device = CastDevice.getFromBundle(route.getExtras());
       //setSelectedDevice(device);
   }

   @Override
   public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route)
   {
       //setSelectedDevice(null);
   }
};

private MediaRouter mediaRouter;
private MediaRouteSelector mediaRouteSelector;

Initialize mediaRouter and mediaRouteSelector in your onCreate method:

@Override
protected void onCreate(Bundle savedInstanceState)
{
   super.onCreate(savedInstanceState);
   mediaRouter = MediaRouter.getInstance(getApplicationContext());
   mediaRouteSelector = new MediaRouteSelector.Builder().addControlCategory(CastMediaControlIntent.categoryForCast(APP_ID)).build();
}

Configure your action provider:

@Override
public boolean onCreateOptionsMenu(Menu menu)
{
   super.onCreateOptionsMenu(menu);
   getMenuInflater().inflate(R.menu.main, menu);

   MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
   MediaRouteActionProvider mediaRouteActionProvider = (MediaRouteActionProvider) MenuItemCompat.getActionProvider(mediaRouteMenuItem);
   mediaRouteActionProvider.setRouteSelector(mediaRouteSelector);

   return true;
}

and manage your callback in onStart and onStop methods:

@Override
protected void onStart()
{
   super.onStart();
   mediaRouter.addCallback(mediaRouteSelector, mediaRouterCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
}

@Override
protected void onStop()
{
   //setSelectedDevice(null);
   mediaRouter.removeCallback(mediaRouterCallback);
   super.onStop();
}

APP_ID is a string identifier obtained during application registration.

Now connect Android device to the same WiFi network as Chromecast device is, and launch the application (note that receiver application should be already available. It is very simple in our case so you can use it from provided sources. We will return to it back soon).

If all requirements are met “cast” icon will appear in a top right corner. Pressing it will open dialog with a list of available devices allowing you to connect to one of them. After connecting the icon will become “blue”, indicating that the connection is established. Clicking it once again will open another dialog which will allow you to change sound “volume” on Chromecast device and to disconnect from it:

Chromecast app - Lemberg Solutions

To manage the application session, we will introduce a bunch of fields and methods:

private CastDevice selectedDevice;
private GoogleApiClient apiClient;
private boolean applicationStarted;

private void setSelectedDevice(CastDevice device)
{
    Log.d(TAG, "setSelectedDevice: " + device);

    selectedDevice = device;

    if (selectedDevice != null)
    {
   	 try
   	 {
   		 stopApplication();
   		 disconnectApiClient();
   		 connectApiClient();
   	 }
   	 catch (IllegalStateException e)
   	 {
   		 Log.w(TAG, "Exception while connecting API client", e);
   		 disconnectApiClient();
   	 }
    }
    else
    {
   	 if (apiClient != null)
   	 {
   		 disconnectApiClient();
   	 }

   	 mediaRouter.selectRoute(mediaRouter.getDefaultRoute());
    }
}

private void connectApiClient()
{
    Cast.CastOptions apiOptions = Cast.CastOptions.builder(selectedDevice, castClientListener).build();
    apiClient = new GoogleApiClient.Builder(this)
   		 .addApi(Cast.API, apiOptions)
   		 .addConnectionCallbacks(connectionCallback)
   		 .addOnConnectionFailedListener(connectionFailedListener)
   		 .build();
    apiClient.connect();
}

private void disconnectApiClient()
{
    if (apiClient != null)
    {
   	 apiClient.disconnect();
   	 apiClient = null;
    }
}

private void stopApplication()
{
    if (apiClient == null) return;

    if (applicationStarted)
    {
   	 Cast.CastApi.stopApplication(apiClient);
   	 applicationStarted = false;
    }
}

as well as several listeners:

private final Cast.Listener castClientListener = new Cast.Listener()
{
    @Override
    public void onApplicationDisconnected(int statusCode)
    {
    }

    @Override
    public void onVolumeChanged()
    {
    }
};

private final GoogleApiClient.ConnectionCallbacks connectionCallback = new GoogleApiClient.ConnectionCallbacks()
{
    @Override
    public void onConnected(Bundle bundle)
    {
   	 try
   	 {
   		 Cast.CastApi.launchApplication(apiClient, APP_ID, false).setResultCallback(connectionResultCallback);
   	 }
   	 catch (Exception e)
   	 {
   		 Log.e(TAG, "Failed to launch application", e);
   	 }
    }

    @Override
    public void onConnectionSuspended(int i)
    {
    }
};

private final GoogleApiClient.OnConnectionFailedListener connectionFailedListener = new GoogleApiClient.OnConnectionFailedListener()
{
    @Override
    public void onConnectionFailed(ConnectionResult connectionResult)
    {
   	 setSelectedDevice(null);
    }
};

private final ResultCallback connectionResultCallback = new ResultCallback()
{
    @Override
    public void onResult(Cast.ApplicationConnectionResult result)
    {
   	 Status status = result.getStatus();
   	 if (status.isSuccess())
   	 {
   		 applicationStarted = true;
   	 }
    }
};

Now, if you launch the application and connect to Chromecast, the receiver application will be activated and you will see some debug information. Let's expose message exchange subject.

The outgoing message exchange is done with a help of the following method:

private void sendMessage(String message)
{
   if (apiClient != null)
   {
       try
       {
           Cast.CastApi.sendMessage(apiClient, NAMESPACE, message)
                   .setResultCallback(new ResultCallback<Status>()
                   {
                       @Override
                       public void onResult(Status result)
                       {
                           if (!result.isSuccess())
                           {
                               Log.e(TAG, "Sending message failed");
                           }
                       }
                   });
       }
       catch (Exception e)
       {
           Log.e(TAG, "Exception while sending message", e);
       }
   }
}

In our case, NAMESPACE is a string constant equal to "urn:x-cast:com.ls.cast.sample". It is used to distinguish our message channel from the others. The same constant should be used in a Receiver application.

To handle incoming messages we need to register Cast.MessageReceivedCallback instance as a message receiver. We will do this in a connectionResultCallback:

@Override
public void onResult(Cast.ApplicationConnectionResult result)
{
   Status status = result.getStatus();
   if (status.isSuccess())
   {
       applicationStarted = true;

       try
       {
           Cast.CastApi.setMessageReceivedCallbacks(apiClient, NAMESPACE, incomingMsgHandler);
       }
       catch (IOException e)
       {
           Log.e(TAG, "Exception while creating channel", e);
       }
   }
}

public final Cast.MessageReceivedCallback incomingMsgHandler = new Cast.MessageReceivedCallback()
{
   @Override
   public void onMessageReceived(CastDevice castDevice, String namespace, String message)
   {
   }
};

And the last thing we need to do is to perform an actual exchange. In our demo application, we created a simple layout which allows to show incoming and send outgoing messages. 

Developing the receiver application

The receiver application is a special form of the web application that can run on a Chromecast device.

The app goal is to display useful information for the users and allow them to interact with this information through a sender application.

The first task of displaying information is usual for a web app and can be very diverse: it can be either media application like YouTube or some sort of media players, or it can be some UI heavy app like Google Maps with additional layers, or it can be simple HTML page displaying some statistics.

As for the second task — interoperability between Receiver application and a sender application — Chromecast SDK provides JavaScript library for this. This will allow you to exchange with string messages in any direction. Let's cover this topic.

First of all, keep in mind that you have to register your app as described in paragraph 2.2. In this case, you will be asked to provide an URL. This URL will be used for storing your Receiver application. Receiver app development will consist of the following stages:

  1. Creating an application locally.
  2. Making it accessible through provided URL.;
  3. Activating the app and debugging it using special URL. To activate the application you should simply launch your sender app and connect to the device. To debug a receiver application you need to launch the following URL in a Web browser on your PC: http://{chromecast_ip}:9222/ ,  where CHROMECAST_IP is IP address of your Chromecast device. To obtain IP address of your Chromecast device you should launch Chromecast setup utility either for PC or for Mobile. Note that the device used for configuration should have direct WiFi connection with Chromecast device (so your PC should have a WiFi adapter if you choose it for configuring):
Chromecast settings - Lemberg Solutions

Also, note that “Send this Chromecast’s serial number when checking for updates” should be enabled to allow debugging. After connecting your browser in order to find IP address you will see the following:

Inspectable WebContents. Chromecast Chrome screen.

The highlighted link refers to running application. If you click on it, a new page with Chrome Developer tools will open. Don’t stray it with your local Developer tools — actually, it is remote and belongs to your Chromecast device:

Chrome Developer tools page. Chromecast app development.

At last,  we are now ready for the application development.

First of all, we need to include Receiver application SDK:

<script src="https://www.gstatic.com/cast/sdk/libs/receiver/2.0.0/cast_receiver.js">

After that, we need to cast.receiver.CastReceiverManager instance, attach onSenderConnected, onSenderDisconnected and onMessage listeners and start message exchange. We can do this in onLoad listener:

window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance();

window.castReceiverManager.onSenderConnected = onChannelOpened;
window.castReceiverManager.onSenderDisconnected = onChannelClosed;

window.customMessageBus = window.castReceiverManager.getCastMessageBus(namespace);
window.customMessageBus.onMessage = onMessage;
window.castReceiverManager.start();


To handle incoming messages the following method will be used:

function onMessage(event)
{
    var message = event.data;
    var senderId = event.senderId;
    log("message from: " + senderId + " message: " + message);
}

To send outgoing messages the following method will be used:

function broadcast(message)
{
    window.customMessageBus.broadcast(message);
}

Note that several sender applications can connect to a Receiver app simultaneously. Therefore, we use a broadcast method which will send a message to all connected senders.

In our demo application, we broadcast a message by timer hit for demonstrative purposes.

And a few more notes. Default text in Receiver application is black, so you will probably need to override it via CSS. Also, note that Chromecast screen resolution is 1280x720 pixels.

Hurray! We have created custom sender and receiver applications that allow to exchange messages in both directions. This should be enough to understand and use the full power of the Chromecast device.

For more on mobile app development services for connected devices, check out our experience page.

Article Contents: