Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Chromecast & Android

C887ad592770a197f114d0a1d3e3a5a7?s=47 Jorge Coca
April 18, 2015

Chromecast & Android

Deliver an incredible experience from your smartphone to your TV.

Chromecast is an $35 HDMI dongle that allows its users to cast their favorite entertainment from any smartphone, tablet or computer, directly to the TV. Chromecast has revolutionized the way we consume media entertainment: you can stream videos, pictures or music, or you can develop games that will use the TV as the main screen, and your devices as the controllers. Since its announcement during the Google I/O 2013, more and more apps have been published supporting Chromecast, such as Netflix, ESPN, HBO Go or Pandora.
With more and more companies, studios and developers supporting this technology, it is crucial that our apps provide an exceptional and simple experience, and that's what this talk will try to cover: from a coding point of view, I will explain how to integrate the Chromecast technology in an Android application; from a design and UX perspective, I'll show the fundamentals that every media app should implement in order to succeed.

C887ad592770a197f114d0a1d3e3a5a7?s=128

Jorge Coca

April 18, 2015
Tweet

Transcript

  1. spr.com Chromecast & Android Deliver an incredible experience from your

    smartphone to your TV - Jorge Coca | Chicago Code Camp 2015 -
  2. spr.com Show me an example!

  3. spr.com What is Chromecast? • HDMI dongle for your TV

    • $35 • Smart TV • Cast content from your smartphone/table/computer to your TV
  4. spr.com Cast from… • Android • iOS • Google Chrome

  5. spr.com

  6. spr.com Architecture

  7. spr.com Sender & Receiver Sender and receiver will have to

    be connected to the same WIFI (or use guest mode)
  8. spr.com Sender • Android SDK • iOS SDK • Google

    Chrome SDK (desktop, mobile…) • No Windows Phone • No Internet Explorer/Safari…
  9. spr.com Receiver HTML5/Javascript app that displays content sent from the

    receiver
  10. spr.com Let’s talk about code

  11. spr.com … but first • Register your device in the

    Cast Developer Console • https://cast.google.com/publish/#/overview • Developer fees: $5
  12. spr.com Google Cast Developer Console • App Info: package name

    • Receiver info • Listing details (app marketing) • Get your APPLICATION ID
  13. spr.com Google Cast Design Checklist • Introduce Cast to users

    • Play content on selected chromecast device • Provide media controls on the notification bar • Media controls on lock screen • Mini controller available while the app is active
  14. spr.com Where do we start?

  15. spr.com Options • Chromecast SDK • Flexible • Complex •

    CastCompanionLibrary • Predefined scenarios • Limited flexibility
  16. spr.com CastCompanionLibrary • Library project to enable developers integrate Cast

    capabilities into their applications faster and easier • Developed by Google • Strongly recommended to use when developing audio/video apps
  17. spr.com Meet Woody! • App for tracking & discovering movies

    • Cast open source movies
  18. spr.com Introduce Cast to users Helps users to know that

    the sender app now supports Casting
  19. spr.com Notification controls

  20. spr.com Media Control Activity

  21. spr.com Media Control while app is active

  22. spr.com Lock Screen Controls

  23. spr.com CastManager • BaseCastManger: abstract class that handles most of

    connectivity issues that transcends the lifecycle of individual activities • Brain that updates NotificationService and MiniController accordingly • VideoCastManager: designed for video-centric apps
  24. spr.com VideoCastManager public class WoodyApplication extends Application { @Override public

    void onCreate() { super.onCreate(); videoCastManager = VideoCastManager.initialize(getApplicationContext(), Constants.CHROMECAST_APP_ID, null, null); videoCastManager.enableFeatures(VideoCastManager.FEATURE_NOTIFICATION | VideoCastManager.FEATURE_LOCKSCREEN | VideoCastManager.FEATURE_WIFI_RECONNECT | VideoCastManager.FEATURE_CAPTIONS_PREFERENCE | VideoCastManager.FEATURE_DEBUGGING); } }
  25. spr.com VideoCastManager public class WoodyApplication extends Application { @Override public

    void onCreate() { super.onCreate(); videoCastManager = VideoCastManager.initialize(getApplicationContext(), Constants.CHROMECAST_APP_ID, null, null); videoCastManager.enableFeatures(VideoCastManager.FEATURE_NOTIFICATION | VideoCastManager.FEATURE_LOCKSCREEN | VideoCastManager.FEATURE_WIFI_RECONNECT | VideoCastManager.FEATURE_CAPTIONS_PREFERENCE | VideoCastManager.FEATURE_DEBUGGING); } }
  26. spr.com VideoCastManager public class WoodyApplication extends Application { @Override public

    void onCreate() { super.onCreate(); videoCastManager = VideoCastManager.initialize(getApplicationContext(), Constants.CHROMECAST_APP_ID, null, null); videoCastManager.enableFeatures(VideoCastManager.FEATURE_NOTIFICATION | VideoCastManager.FEATURE_LOCKSCREEN | VideoCastManager.FEATURE_WIFI_RECONNECT | VideoCastManager.FEATURE_CAPTIONS_PREFERENCE | VideoCastManager.FEATURE_DEBUGGING); } }
  27. spr.com Cast icon

  28. spr.com menu.xml <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <item android:id="@+id/media_route_menu_item" android:title="@string/settings.media_route_menu_title" app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"

    app:showAsAction="always"/> </menu>
  29. spr.com menu.xml <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <item android:id="@+id/media_route_menu_item" android:title="@string/settings.media_route_menu_title" app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"

    app:showAsAction="always"/> </menu>
  30. spr.com BaseChromecastActivity public class BaseChromecastActivity extends ActionBarActivity { protected VideoCastManager

    videoCastManager; protected MiniController miniController; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); VideoCastManager.checkGooglePlayServices(this); videoCastManager = WoodyApplication.getCastManager(); showChromecastTutorial(); videoCastManager.reconnectSessionIfPossible(); }
  31. spr.com Check Google Play Services public class BaseChromecastActivity extends ActionBarActivity

    { protected VideoCastManager videoCastManager; protected MiniController miniController; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); VideoCastManager.checkGooglePlayServices(this); videoCastManager = WoodyApplication.getCastManager(); showChromecastTutorial(); videoCastManager.reconnectSessionIfPossible(); }
  32. spr.com BaseChromecastActivity @Override protected void onResume() { super.onResume(); videoCastManager =

    WoodyApplication.getCastManager(); miniController.setOnMiniControllerChangedListener(videoCastManager); videoCastManager.incrementUiCounter(); } @Override protected void onPause() { super.onPause(); videoCastManager.decrementUiCounter(); miniController.removeOnMiniControllerChangedListener(videoCastManager); }
  33. spr.com BaseChromecastActivity @Override protected void onResume() { super.onResume(); videoCastManager =

    WoodyApplication.getCastManager(); miniController.setOnMiniControllerChangedListener(videoCastManager); videoCastManager.incrementUiCounter(); } @Override protected void onPause() { super.onPause(); videoCastManager.decrementUiCounter(); miniController.removeOnMiniControllerChangedListener(videoCastManager); }
  34. spr.com BaseChromecastActivity @Override protected void onDestroy() { if (videoCastManager !=

    null) { miniController.removeOnMiniControllerChangedListener(videoCastManager); videoCastManager.removeMiniController(miniController); videoCastManager.clearContext(this); } super.onDestroy(); }
  35. spr.com BaseChromecastActivity @Override public void setContentView(final int layoutResID) { RelativeLayout

    layout = (RelativeLayout) getLayoutInflater() .inflate(R.layout.activity_base_chromecast, null); miniController = (MiniController) layout.findViewById(R.id.mini_controller); videoCastManager.addMiniController(miniController); super.setContentView(layout); }
  36. spr.com BaseChromecastActivity @Override public void setContentView(final int layoutResID) { RelativeLayout

    layout = (RelativeLayout) getLayoutInflater() .inflate(R.layout.activity_base_chromecast, null); miniController = (MiniController) layout.findViewById(R.id.mini_controller); videoCastManager.addMiniController(miniController); super.setContentView(layout); }
  37. spr.com BaseChromecastActivity @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_base_navigation, menu);

    mediaRouteMenuItem = videoCastManager .addMediaRouterButton(menu, R.id.media_route_menu_item); return true; }
  38. spr.com BaseChromecastActivity @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_base_navigation, menu);

    mediaRouteMenuItem = videoCastManager .addMediaRouterButton(menu, R.id.media_route_menu_item); return true; }
  39. spr.com ReconnectionService Background service that handles reconnection logic when wifi

    connectivity is lost <service android:name="com.google.sample.castcompanionlibrary.cast.reconnection.ReconnectionService"/>
  40. spr.com VideoCastController Activity Provides a default implementation of the player

    control screen.
  41. spr.com Register VideoCastControllerActivity <activity android:name="com.google.sample.castcompanionlibrary.cast.player.VideoCastControllerActivity" android:launchMode="singleTask" android:screenOrientation="portrait" android:parentActivityName=".activity.movie.MovieResultsActivity"/>

  42. spr.com Play content MediaInfo mediaInfo = buildMediaInfo(); Intent intent =

    new Intent(getActivity(), VideoCastControllerActivity.class); intent.putExtra(Constants.EXTRA_MEDIA, Utils.fromMediaInfo(mediaInfo)); intent.putExtra(Constants.EXTRA_SHOULD_START, true); startActivity(intent);
  43. spr.com Play content MediaInfo mediaInfo = buildMediaInfo(); Intent intent =

    new Intent(getActivity(), VideoastControllerActivity.class); intent.putExtra(Constants.EXTRA_MEDIA, Utils.fromMediaInfo(mediaInfo)); intent.putExtra(Constants.EXTRA_SHOULD_START, true); startActivity(intent);
  44. spr.com MediaInfo private MediaInfo buildMediaInfo() { MediaMetadata movieMetadata = new

    MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE); movieMetadata.putString(MediaMetadata.KEY_TITLE, movieDetails.getTitle()); movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, movieDetails.getGenresWithCommas()); movieMetadata.putString(MediaMetadata.KEY_STUDIO, movieDetails.getProductionCompaniesWithCommas()); movieMetadata.addImage(new WebImage(Uri.parse(movieDetails.getPosterPath()))); return new MediaInfo.Builder(movieDetails.getMovieUrl()) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setContentType("video/mp4") .setMetadata(movieMetadata) .build(); }
  45. spr.com MediaInfo private MediaInfo buildMediaInfo() { MediaMetadata movieMetadata = new

    MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE); movieMetadata.putString(MediaMetadata.KEY_TITLE, movieDetails.getTitle()); movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, movieDetails.getGenresWithCommas()); movieMetadata.putString(MediaMetadata.KEY_STUDIO, movieDetails.getProductionCompaniesWithCommas()); movieMetadata.addImage(new WebImage(Uri.parse(movieDetails.getPosterPath()))); return new MediaInfo.Builder(movieDetails.getMovieUrl()) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setContentType("video/mp4") .setMetadata(movieMetadata) .build(); }
  46. spr.com VideoCastNotificationService Service that allows notifications to appear when needed

    beyond the availability of the application
  47. spr.com VideoCastNotificationService <service android:name="com.google.sample.castcompanionlibrary.notification.VideoCastNotificationService" android:exported="false"> <intent-filter> <action android:name="com.google.sample.castcompanionlibrary.action.toggleplayback" /> <action

    android:name="com.google.sample.castcompanionlibrary.action.stop" /> <action android:name="com.google.sample.castcompanionlibrary.action.notificationvisibility" /> </intent-filter> </service>
  48. spr.com MiniController • Compound control that provides a mini-controller for

    the client
  49. spr.com In your layout <com.google.sample.castcompanionlibrary.widgets.MiniController android:id="@+id/mini_controller" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:background="@color/greySoft"

    android:visibility="gone"/>
  50. spr.com Introduce Cast to users Helps users to know that

    the sender app now supports Casting
  51. spr.com ShowCaseView • Not included in the CastCompanionLibrary, but used

    by Google in their examples • https://github.com/amlcurran/ShowcaseView • Easily customizable: styles.xml
  52. spr.com ShowCaseView private void showChromecastTutorial() { Menu menu = toolbar.getMenu();

    View view = menu.findItem(R.id.media_route_menu_item).getActionView(); if (view != null && view instanceof MediaRouteButton) { new ShowcaseView.Builder(this) .setTarget(new ViewTarget(view)) .setContentTitle("Touch here to cast videos") .build(); SharedPreferencesUtils.saveBooleanSharedPreferences(this, PREF_USER_LEARNED_CHROMECAST, true); } }
  53. spr.com Need to declare a target private void showChromecastTutorial() {

    Menu menu = toolbar.getMenu(); View view = menu.findItem(R.id.media_route_menu_item).getActionView(); if (view != null && view instanceof MediaRouteButton) { new ShowcaseView.Builder(this) .setTarget(new ViewTarget(view)) .setContentTitle("Touch here to cast videos") .build(); SharedPreferencesUtils.saveBooleanSharedPreferences(this, PREF_USER_LEARNED_CHROMECAST, true); } }
  54. spr.com Cool, right?

  55. spr.com Let’s code the receiver

  56. spr.com Let’s code the receiver StyledMediaReceiver

  57. spr.com

  58. spr.com Styled Media Receiver .background { background: #0c1821; } .logo

    { background-image: url("http://your_url.com/woody_transparent.png"); } .progressBar { background-color: #d7263d; } .splash { background-image: url("http://your_url.com/splash.png") } .watermark { background-image: url("http://your_url.com/watermark.png"); background-size: 100px 100px; }
  59. spr.com

  60. spr.com

  61. spr.com But what is really VideoCastManager?

  62. spr.com Sender Application Flow • Search for available cast devices

    • Establish connection with selected Cast device • Launch receiver and stream content using sender as remote • Disconnect from selected Cast device
  63. spr.com Search • Sender app starts MediaRouter device discover •

    MediaRouter informs sender app of the route the user selected
  64. spr.com Search • MediaRouteActionProvider: displays cast button in the ActionBar

    to allow the user to select routes and control the selected route • MediaRouter: allows apps to control the routing of media channels and streams from the device to external destinations
  65. spr.com Connect • Sender app retrieves CastDevice instance • Sender

    app creates a GoogleApiClient • Sender app connects the GoogleApiClient • SDK confirms that GoogleApiClient is connected
  66. spr.com Launch receiver & Stream content • Sender app launches

    the receiver app • SDK confirms that the receiver app is connected • Sender app creates a communication channel • Sender sends a message to the receiver over the communication channel
  67. spr.com Disconnection • Unregister all callbacks • Remove objects from

    memory
  68. spr.com Chromecast SDK: Chromecaster https://github.com/jorgecoca/ Chromecaster • Implementation of “VideoCastManager”

  69. spr.com Dependencies dependencies { compile "com.android.support:appcompat-v7:22.0.0" compile "com.android.support:mediarouter-v7:22.0.0" compile "com.google.android.gms:play-services:7.0.0"

    }
  70. spr.com AndroidManifest.xml <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <application ...> <!-- Chromecast

    settings --> <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version"/> </application>
  71. spr.com Adding cast button: MediaRouteItem • Cast Menu, not connected:

    receivers available • Cast Menu, connected but not casting • Cast Menu, while casting
  72. spr.com Step 1: Connect to Cast device

  73. spr.com menu.xml <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context=".ChromecastActivity"> <item android:id="@+id/media_route_menu_item" android:title="@string/chromecast"

    app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider" app:showAsAction="always"/> </menu>
  74. spr.com Activity setup public class ChromecastActivity extends ActionBarActivity { private

    MediaRouter mediaRouter; private MediaRouteSelector mediaRouteSelector; private MediaRouterCallback mediaRouterCallback; private CastDevice selectedCastDevice; private Cast.Listener castClientListener; private GoogleApiClient apiClient; private RemoteMediaPlayer remoteMediaPlayer; private boolean waitingForReconnect; private boolean applicationStarted = false; private boolean videoIsLoaded; private boolean isPlaying;
  75. spr.com Activity setup public class ChromecastActivity extends ActionBarActivity { private

    MediaRouter mediaRouter; private MediaRouteSelector mediaRouteSelector; private MediaRouterCallback mediaRouterCallback; private CastDevice selectedCastDevice; private Cast.Listener castClientListener; private GoogleApiClient apiClient; private RemoteMediaPlayer remoteMediaPlayer; private boolean waitingForReconnect; private boolean applicationStarted = false; private boolean videoIsLoaded; private boolean isPlaying;
  76. spr.com Activity setup public class ChromecastActivity extends ActionBarActivity { private

    MediaRouter mediaRouter; private MediaRouteSelector mediaRouteSelector; private MediaRouterCallback mediaRouterCallback; private CastDevice selectedCastDevice; private Cast.Listener castClientListener; private GoogleApiClient apiClient; private RemoteMediaPlayer remoteMediaPlayer; private boolean waitingForReconnect; private boolean applicationStarted = false; private boolean videoIsLoaded; private boolean isPlaying;
  77. spr.com Activity setup public class ChromecastActivity extends ActionBarActivity { private

    MediaRouter mediaRouter; private MediaRouteSelector mediaRouteSelector; private MediaRouterCallback mediaRouterCallback; private CastDevice selectedCastDevice; private Cast.Listener castClientListener; private GoogleApiClient apiClient; private RemoteMediaPlayer remoteMediaPlayer; private boolean waitingForReconnect; private boolean applicationStarted = false; private boolean videoIsLoaded; private boolean isPlaying;
  78. spr.com onCreate() @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_chromecast);

    bindViews(); initMediaRouter(); }
  79. spr.com onCreate() @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_chromecast);

    bindViews(); initMediaRouter(); }
  80. spr.com initMediaRouter() private void initMediaRouter() { // Configure device discovery

    mediaRouter = MediaRouter.getInstance(getApplicationContext()); mediaRouteSelector = new MediaRouteSelector.Builder() .addControlCategory( CastMediaControlIntent.categoryForCast(Constants.CHROMECAST_APP_ID)) .build(); mediaRouterCallback = new MediaRouterCallback(); }
  81. spr.com initMediaRouter() private void initMediaRouter() { // Configure device discovery

    mediaRouter = MediaRouter.getInstance(getApplicationContext()); mediaRouteSelector = new MediaRouteSelector.Builder() .addControlCategory( CastMediaControlIntent.categoryForCast(Constants.CHROMECAST_APP_ID)) .build(); mediaRouterCallback = new MediaRouterCallback(); }
  82. spr.com initMediaRouter() private void initMediaRouter() { // Configure device discovery

    mediaRouter = MediaRouter.getInstance(getApplicationContext()); mediaRouteSelector = new MediaRouteSelector.Builder() .addControlCategory( CastMediaControlIntent.categoryForCast(Constants.CHROMECAST_APP_ID)) .build(); mediaRouterCallback = new MediaRouterCallback(); }
  83. spr.com initMediaRouter() private void initMediaRouter() { // Configure device discovery

    mediaRouter = MediaRouter.getInstance(getApplicationContext()); mediaRouteSelector = new MediaRouteSelector.Builder() .addControlCategory( CastMediaControlIntent.categoryForCast(Constants.CHROMECAST_APP_ID)) .build(); mediaRouterCallback = new MediaRouterCallback(); }
  84. spr.com Setup the icon on the ActionBar @Override public boolean

    onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_chromecast, menu); MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item); MediaRouteActionProvider mediaRouteActionProvider = (MediaRouteActionProvider) MenuItemCompat.getActionProvider(mediaRouteMenuItem); mediaRouteActionProvider.setRouteSelector(mediaRouteSelector); return true; }
  85. spr.com Setup the icon on the ActionBar @Override public boolean

    onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_chromecast, menu); MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item); MediaRouteActionProvider mediaRouteActionProvider = (MediaRouteActionProvider) MenuItemCompat.getActionProvider(mediaRouteMenuItem); mediaRouteActionProvider.setRouteSelector(mediaRouteSelector); return true; }
  86. spr.com Associate media router callbacks @Override protected void onResume() {

    super.onResume(); mediaRouter.addCallback(mediaRouteSelector, mediaRouterCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); } @Override protected void onPause() { if (isFinishing()) { mediaRouter.removeCallback(mediaRouterCallback); } super.onPause(); }
  87. spr.com Associate media router callbacks @Override protected void onResume() {

    super.onResume(); mediaRouter.addCallback(mediaRouteSelector, mediaRouterCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); } @Override protected void onPause() { if (isFinishing()) { mediaRouter.removeCallback(mediaRouterCallback); } super.onPause(); }
  88. spr.com MediaRouterCallback • Extension of provided MediaRouter.Callback • Need to

    override two methods: • onRouteSelected(MediaRouter router, RouteInfo info) • onRouteUnselected(MediaRouter router, RouteInfo info)
  89. spr.com private class MediaRouterCallback extends MediaRouter.Callback { @Override public void

    onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) { initCastClientListener(); initRemoteMediaPlayer(); selectedCastDevice = CastDevice.getFromBundle(route.getExtras()); launchReceiver(); } @Override public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) { teardown(); selectedCastDevice = null; videoIsLoaded = false; } }
  90. spr.com private class MediaRouterCallback extends MediaRouter.Callback { @Override public void

    onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) { initCastClientListener(); initRemoteMediaPlayer(); selectedCastDevice = CastDevice.getFromBundle(route.getExtras()); launchReceiver(); } @Override public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) { teardown(); selectedCastDevice = null; videoIsLoaded = false; } }
  91. spr.com initCastClientListener() private void initCastClientListener() { castClientListener = new Cast.Listener()

    { @Override public void onApplicationDisconnected(int statusCode) { teardown(); } }; }
  92. spr.com private class MediaRouterCallback extends MediaRouter.Callback { @Override public void

    onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) { initCastClientListener(); initRemoteMediaPlayer(); selectedCastDevice = CastDevice.getFromBundle(route.getExtras()); launchReceiver(); } @Override public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) { teardown(); selectedCastDevice = null; videoIsLoaded = false; } }
  93. spr.com initRemoteMediaPlayer() private void initRemoteMediaPlayer() { remoteMediaPlayer = new RemoteMediaPlayer();

    remoteMediaPlayer.setOnStatusUpdatedListener(new RemoteMediaPlayer.OnStatusUpdatedListener() { @Override public void onStatusUpdated() { MediaStatus mediaStatus = remoteMediaPlayer.getMediaStatus(); isPlaying = mediaStatus.getPlayerState() == MediaStatus.PLAYER_STATE_PLAYING; } }); }
  94. spr.com initRemoteMediaPlayer() private void initRemoteMediaPlayer() { remoteMediaPlayer = new RemoteMediaPlayer();

    remoteMediaPlayer.setOnStatusUpdatedListener(new RemoteMediaPlayer.OnStatusUpdatedListener() { @Override public void onStatusUpdated() { MediaStatus mediaStatus = remoteMediaPlayer.getMediaStatus(); isPlaying = mediaStatus.getPlayerState() == MediaStatus.PLAYER_STATE_PLAYING; } }); }
  95. spr.com private class MediaRouterCallback extends MediaRouter.Callback { @Override public void

    onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) { initCastClientListener(); initRemoteMediaPlayer(); selectedCastDevice = CastDevice.getFromBundle(route.getExtras()); launchReceiver(); } @Override public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) { teardown(); selectedCastDevice = null; videoIsLoaded = false; } }
  96. spr.com What do we have so far?

  97. spr.com Step 2: Launch receiver private class MediaRouterCallback extends MediaRouter.Callback

    { @Override public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) { initCastClientListener(); initRemoteMediaPlayer(); selectedCastDevice = CastDevice.getFromBundle(route.getExtras()); launchReceiver(); } @Override public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) { teardown(); selectedCastDevice = null; videoIsLoaded = false; } }
  98. spr.com launchReceiver() private void launchReceiver() { Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions.builder(selectedCastDevice,

    castClientListener); ConnectionCallbacks connectionCallbacks = new ConnectionCallbacks(); ConnectionFailedListener connectionFailedListener = new ConnectionFailedListener(); apiClient = new GoogleApiClient.Builder(getApplicationContext()) .addApi(Cast.API, apiOptionsBuilder.build()) .addConnectionCallbacks(connectionCallbacks) .addOnConnectionFailedListener(connectionFailedListener) .build(); apiClient.connect(); }
  99. spr.com launchReceiver() private void launchReceiver() { Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions.builder(selectedCastDevice,

    castClientListener); ConnectionCallbacks connectionCallbacks = new ConnectionCallbacks(); ConnectionFailedListener connectionFailedListener = new ConnectionFailedListener(); apiClient = new GoogleApiClient.Builder(getApplicationContext()) .addApi(Cast.API, apiOptionsBuilder.build()) .addConnectionCallbacks(connectionCallbacks) .addOnConnectionFailedListener(connectionFailedListener) .build(); apiClient.connect(); }
  100. spr.com launchReceiver() private void launchReceiver() { Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions.builder(selectedCastDevice,

    castClientListener); ConnectionCallbacks connectionCallbacks = new ConnectionCallbacks(); ConnectionFailedListener connectionFailedListener = new ConnectionFailedListener(); apiClient = new GoogleApiClient.Builder(getApplicationContext()) .addApi(Cast.API, apiOptionsBuilder.build()) .addConnectionCallbacks(connectionCallbacks) .addOnConnectionFailedListener(connectionFailedListener) .build(); apiClient.connect(); }
  101. spr.com launchReceiver() private void launchReceiver() { Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions.builder(selectedCastDevice,

    castClientListener); ConnectionCallbacks connectionCallbacks = new ConnectionCallbacks(); ConnectionFailedListener connectionFailedListener = new ConnectionFailedListener(); apiClient = new GoogleApiClient.Builder(getApplicationContext()) .addApi(Cast.API, apiOptionsBuilder.build()) .addConnectionCallbacks(connectionCallbacks) .addOnConnectionFailedListener(connectionFailedListener) .build(); apiClient.connect(); }
  102. spr.com launchReceiver() private void launchReceiver() { Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions.builder(selectedCastDevice,

    castClientListener); ConnectionCallbacks connectionCallbacks = new ConnectionCallbacks(); ConnectionFailedListener connectionFailedListener = new ConnectionFailedListener(); apiClient = new GoogleApiClient.Builder(getApplicationContext()) .addApi(Cast.API, apiOptionsBuilder.build()) .addConnectionCallbacks(connectionCallbacks) .addOnConnectionFailedListener(connectionFailedListener) .build(); apiClient.connect(); }
  103. spr.com ConnectionFailedListener private class ConnectionFailedListener implements GoogleApiClient.OnConnectionFailedListener { @Override public

    void onConnectionFailed(ConnectionResult connectionResult) { teardown(); } }
  104. spr.com ConnectionCallbacks • Extension for GoogleApiClient.ConnectionCallbacks • It is used

    to launch the receiver specified by the APPLICATION ID • Two abstract methods: • onConnected will be invoked asynchronously when the connect request has successfully completed • onConnectionSuspended is called when the client is temporarily in a disconnected state
  105. spr.com private class ConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks { @Override public void

    onConnected(Bundle bundle) { // implementation here } @Override public void onConnectionSuspended(int i) { waitingForReconnect = true; } }
  106. spr.com private class ConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks { @Override public void

    onConnected(Bundle bundle) { // implementation here } @Override public void onConnectionSuspended(int i) { waitingForReconnect = true; } }
  107. spr.com @Override public void onConnected(Bundle bundle) { if (waitingForReconnect) {

    waitingForReconnect = false; reconnectChannels(bundle); } else { // Happy path :) } }
  108. spr.com Don’t lose hope! private void reconnectChannels(Bundle bundle) { if

    ((bundle != null) && bundle.getBoolean(Cast.EXTRA_APP_NO_LONGER_RUNNING)) { teardown(); } else { try { Cast.CastApi.setMessageReceivedCallbacks(apiClient, remoteMediaPlayer.getNamespace(), remoteMediaPlayer); } catch (IOException e) {} } }
  109. spr.com @Override public void onConnected(Bundle bundle) { if (waitingForReconnect) {

    waitingForReconnect = false; reconnectChannels(bundle); } else { // Happy path :) } }
  110. spr.com try { Cast.CastApi.launchApplication(apiClient, Constants.CHROMECAST_APP_ID, false) .setResultCallback(new ResultCallback<Cast.ApplicationConnectionResult>() { @Override

    public void onResult(Cast.ApplicationConnectionResult applicationConnectionResult) { Status status = applicationConnectionResult.getStatus(); if (status.isSuccess()) { // Useful values from metadata: sessionID, applicationStatus, wasLaunched ApplicationMetadata applicationMetadata = applicationConnectionResult.getApplicationMetadata(); applicationStarted = true; reconnectChannels(null); } } }); } catch (Exception e) {}
  111. spr.com try { Cast.CastApi.launchApplication(apiClient, Constants.CHROMECAST_APP_ID, false) .setResultCallback(new ResultCallback<Cast.ApplicationConnectionResult>() { @Override

    public void onResult(Cast.ApplicationConnectionResult applicationConnectionResult) { Status status = applicationConnectionResult.getStatus(); if (status.isSuccess()) { // Useful values from metadata: sessionID, applicationStatus, wasLaunched ApplicationMetadata applicationMetadata = applicationConnectionResult.getApplicationMetadata(); applicationStarted = true; reconnectChannels(null); } } }); } catch (Exception e) {}
  112. spr.com try { Cast.CastApi.launchApplication(apiClient, Constants.CHROMECAST_APP_ID, false) .setResultCallback(new ResultCallback<Cast.ApplicationConnectionResult>() { @Override

    public void onResult(Cast.ApplicationConnectionResult applicationConnectionResult) { Status status = applicationConnectionResult.getStatus(); if (status.isSuccess()) { // Useful values from metadata: sessionID, applicationStatus, wasLaunched ApplicationMetadata applicationMetadata = applicationConnectionResult.getApplicationMetadata(); applicationStarted = true; reconnectChannels(null); } } }); } catch (Exception e) {}
  113. spr.com try { Cast.CastApi.launchApplication(apiClient, Constants.CHROMECAST_APP_ID, false) .setResultCallback(new ResultCallback<Cast.ApplicationConnectionResult>() { @Override

    public void onResult(Cast.ApplicationConnectionResult applicationConnectionResult) { Status status = applicationConnectionResult.getStatus(); if (status.isSuccess()) { // Useful values from metadata: sessionID, applicationStatus, wasLaunched ApplicationMetadata applicationMetadata = applicationConnectionResult.getApplicationMetadata(); applicationStarted = true; reconnectChannels(null); } } }); } catch (Exception e) {}
  114. spr.com What do we have so far?

  115. spr.com Step 3: Let’s play some videos!

  116. spr.com onCreate() @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_chromecast);

    bindViews(); initMediaRouter(); }
  117. spr.com private Button videoButton; private void bindViews() { videoButton =

    (Button) findViewById(R.id.video_button); videoButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (!videoIsLoaded) { startVideo(); } else { controlVideo(); } } }); }
  118. spr.com private Button videoButton; private void bindViews() { videoButton =

    (Button) findViewById(R.id.video_button); videoButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (!videoIsLoaded) { startVideo(); } else { controlVideo(); } } }); }
  119. spr.com private void startVideo() { MediaMetadata mediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);

    mediaMetadata.putString(MediaMetadata.KEY_TITLE, getString(R.string.video_title)); MediaInfo mediaInfo = new MediaInfo.Builder(getString(R.string.video_url)) .setContentType(getString(R.string.content_type)) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setMetadata(mediaMetadata) .build(); try { remoteMediaPlayer.load(apiClient, mediaInfo, true) .setResultCallback(new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() { @Override public void onResult(RemoteMediaPlayer.MediaChannelResult mediaChannelResult) { if (mediaChannelResult.getStatus().isSuccess()) { videoIsLoaded = true; videoButton.setText(getString(R.string.pause_video)); } } }); } catch (Exception e) {} }
  120. spr.com private void startVideo() { MediaMetadata mediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);

    mediaMetadata.putString(MediaMetadata.KEY_TITLE, getString(R.string.video_title)); MediaInfo mediaInfo = new MediaInfo.Builder(getString(R.string.video_url)) .setContentType(getString(R.string.content_type)) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setMetadata(mediaMetadata) .build(); try { remoteMediaPlayer.load(apiClient, mediaInfo, true) .setResultCallback(new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() { @Override public void onResult(RemoteMediaPlayer.MediaChannelResult mediaChannelResult) { if (mediaChannelResult.getStatus().isSuccess()) { videoIsLoaded = true; videoButton.setText(getString(R.string.pause_video)); } } }); } catch (Exception e) {} }
  121. spr.com private void startVideo() { MediaMetadata mediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);

    mediaMetadata.putString(MediaMetadata.KEY_TITLE, getString(R.string.video_title)); MediaInfo mediaInfo = new MediaInfo.Builder(getString(R.string.video_url)) .setContentType(getString(R.string.content_type)) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setMetadata(mediaMetadata) .build(); try { remoteMediaPlayer.load(apiClient, mediaInfo, true) .setResultCallback(new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() { @Override public void onResult(RemoteMediaPlayer.MediaChannelResult mediaChannelResult) { if (mediaChannelResult.getStatus().isSuccess()) { videoIsLoaded = true; videoButton.setText(getString(R.string.pause_video)); } } }); } catch (Exception e) {} }
  122. spr.com private void startVideo() { MediaMetadata mediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);

    mediaMetadata.putString(MediaMetadata.KEY_TITLE, getString(R.string.video_title)); MediaInfo mediaInfo = new MediaInfo.Builder(getString(R.string.video_url)) .setContentType(getString(R.string.content_type)) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setMetadata(mediaMetadata) .build(); try { remoteMediaPlayer.load(apiClient, mediaInfo, true) .setResultCallback(new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() { @Override public void onResult(RemoteMediaPlayer.MediaChannelResult mediaChannelResult) { if (mediaChannelResult.getStatus().isSuccess()) { videoIsLoaded = true; videoButton.setText(getString(R.string.pause_video)); } } }); } catch (Exception e) {} }
  123. spr.com controlVideo() private void bindViews() { videoButton = (Button) findViewById(R.id.video_button);

    videoButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (!videoIsLoaded) { startVideo(); } else { controlVideo(); } } }); }
  124. spr.com controlVideo() private void controlVideo() { if (remoteMediaPlayer == null

    || !videoIsLoaded) return; if (isPlaying) { remoteMediaPlayer.pause(apiClient); videoButton.setText(getString(R.string.play_video)); } else { remoteMediaPlayer.play(apiClient); videoButton.setText(getString(R.string.pause_video)); } }
  125. spr.com Step 4: Disconnection private void teardown() { if (apiClient

    != null) { if (applicationStarted) { try { Cast.CastApi.stopApplication(apiClient); if (remoteMediaPlayer != null) { Cast.CastApi.removeMessageReceivedCallbacks(apiClient, remoteMediaPlayer.getNamespace()); remoteMediaPlayer = null; } } catch (IOException e) {} applicationStarted = false; } if (apiClient.isConnected()) { apiClient.disconnect(); } } selectedCastDevice = null; videoIsLoaded = false; }
  126. spr.com Disconnection private void teardown() { if (apiClient != null)

    { if (applicationStarted) { try { Cast.CastApi.stopApplication(apiClient); if (remoteMediaPlayer != null) { Cast.CastApi.removeMessageReceivedCallbacks(apiClient, remoteMediaPlayer.getNamespace()); remoteMediaPlayer = null; } } catch (IOException e) {} applicationStarted = false; } if (apiClient.isConnected()) { apiClient.disconnect(); } } selectedCastDevice = null; videoIsLoaded = false; }
  127. spr.com Disconnection private void teardown() { if (apiClient != null)

    { if (applicationStarted) { try { Cast.CastApi.stopApplication(apiClient); if (remoteMediaPlayer != null) { Cast.CastApi.removeMessageReceivedCallbacks(apiClient, remoteMediaPlayer.getNamespace()); remoteMediaPlayer = null; } } catch (IOException e) {} applicationStarted = false; } if (apiClient.isConnected()) { apiClient.disconnect(); } } selectedCastDevice = null; videoIsLoaded = false; }
  128. spr.com Disconnection private void teardown() { if (apiClient != null)

    { if (applicationStarted) { try { Cast.CastApi.stopApplication(apiClient); if (remoteMediaPlayer != null) { Cast.CastApi.removeMessageReceivedCallbacks(apiClient, remoteMediaPlayer.getNamespace()); remoteMediaPlayer = null; } } catch (IOException e) {} applicationStarted = false; } if (apiClient.isConnected()) { apiClient.disconnect(); } } selectedCastDevice = null; videoIsLoaded = false; }
  129. spr.com

  130. spr.com Let’s code the receiver

  131. spr.com Let’s code the receiver

  132. spr.com Types of receiver • Default: no code involved •

    Styled Media Receiver: CSS file with predefined fields • Custom: full control of what’s displayed on the TV
  133. spr.com Custom Receiver • Full control of what happens on

    the receiver • No predefined fields • Need to host on secure servers
  134. spr.com Things to know

  135. spr.com Supported media: Image Images have a display size limitation

    of 720p • BMP • GIF • JPEG • PNG • WEBP
  136. spr.com Audio Codecs • HE-AAC • AAC LC • MP3

    • Vorbis • WAV
  137. spr.com Video • H.264 High Profile Level 4.1 • VP8

    • Subtitles: • TTML • WebVTT • CEA-608
  138. spr.com Helpful links • https://developers.google.com/cast/ • https://github.com/googlecast • This repo

    contains different examples on how to use Chromecast
  139. spr.com Questions?

  140. spr.com Get the slides & spread the word! • https://speakerdeck.com/jorgecoca/chromecast-and-android

    • www.jorgecoca.com
  141. spr.com Follow me on Twitter & Github @jcocaramos jorgecoca

  142. spr.com You’re awesome!