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

Chromecast & Android

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.

Jorge Coca

April 18, 2015
Tweet

More Decks by Jorge Coca

Other Decks in Technology

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 What is Chromecast? • HDMI dongle for your TV

    • $35 • Smart TV • Cast content from your smartphone/table/computer to your TV
  3. spr.com Sender & Receiver Sender and receiver will have to

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

    Chrome SDK (desktop, mobile…) • No Windows Phone • No Internet Explorer/Safari…
  5. spr.com … but first • Register your device in the

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

    • Receiver info • Listing details (app marketing) • Get your APPLICATION ID
  7. 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
  8. spr.com Options • Chromecast SDK • Flexible • Complex •

    CastCompanionLibrary • Predefined scenarios • Limited flexibility
  9. 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
  10. spr.com Introduce Cast to users Helps users to know that

    the sender app now supports Casting
  11. 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
  12. 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); } }
  13. 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); } }
  14. 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); } }
  15. 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(); }
  16. 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(); }
  17. 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); }
  18. 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); }
  19. spr.com BaseChromecastActivity @Override protected void onDestroy() { if (videoCastManager !=

    null) { miniController.removeOnMiniControllerChangedListener(videoCastManager); videoCastManager.removeMiniController(miniController); videoCastManager.clearContext(this); } super.onDestroy(); }
  20. 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); }
  21. 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); }
  22. 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; }
  23. 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; }
  24. spr.com ReconnectionService Background service that handles reconnection logic when wifi

    connectivity is lost <service android:name="com.google.sample.castcompanionlibrary.cast.reconnection.ReconnectionService"/>
  25. 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);
  26. 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);
  27. 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(); }
  28. 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(); }
  29. 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>
  30. spr.com Introduce Cast to users Helps users to know that

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

    by Google in their examples • https://github.com/amlcurran/ShowcaseView • Easily customizable: styles.xml
  32. 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); } }
  33. 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); } }
  34. 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; }
  35. 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
  36. spr.com Search • Sender app starts MediaRouter device discover •

    MediaRouter informs sender app of the route the user selected
  37. 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
  38. spr.com Connect • Sender app retrieves CastDevice instance • Sender

    app creates a GoogleApiClient • Sender app connects the GoogleApiClient • SDK confirms that GoogleApiClient is connected
  39. 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
  40. 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>
  41. spr.com Adding cast button: MediaRouteItem • Cast Menu, not connected:

    receivers available • Cast Menu, connected but not casting • Cast Menu, while casting
  42. 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;
  43. 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;
  44. 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;
  45. 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;
  46. 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(); }
  47. 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(); }
  48. 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(); }
  49. 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(); }
  50. 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; }
  51. 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; }
  52. 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(); }
  53. 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(); }
  54. spr.com MediaRouterCallback • Extension of provided MediaRouter.Callback • Need to

    override two methods: • onRouteSelected(MediaRouter router, RouteInfo info) • onRouteUnselected(MediaRouter router, RouteInfo info)
  55. 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; } }
  56. 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; } }
  57. spr.com initCastClientListener() private void initCastClientListener() { castClientListener = new Cast.Listener()

    { @Override public void onApplicationDisconnected(int statusCode) { teardown(); } }; }
  58. 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; } }
  59. 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; } }); }
  60. 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; } }); }
  61. 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; } }
  62. 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; } }
  63. 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(); }
  64. 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(); }
  65. 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(); }
  66. 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(); }
  67. 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(); }
  68. 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
  69. spr.com private class ConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks { @Override public void

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

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

    waitingForReconnect = false; reconnectChannels(bundle); } else { // Happy path :) } }
  72. 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) {} } }
  73. spr.com @Override public void onConnected(Bundle bundle) { if (waitingForReconnect) {

    waitingForReconnect = false; reconnectChannels(bundle); } else { // Happy path :) } }
  74. 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) {}
  75. 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) {}
  76. 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) {}
  77. 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) {}
  78. 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(); } } }); }
  79. 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(); } } }); }
  80. 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) {} }
  81. 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) {} }
  82. 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) {} }
  83. 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) {} }
  84. 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(); } } }); }
  85. 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)); } }
  86. 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; }
  87. 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; }
  88. 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; }
  89. 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; }
  90. 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
  91. spr.com Custom Receiver • Full control of what happens on

    the receiver • No predefined fields • Need to host on secure servers
  92. spr.com Supported media: Image Images have a display size limitation

    of 720p • BMP • GIF • JPEG • PNG • WEBP
  93. spr.com Video • H.264 High Profile Level 4.1 • VP8

    • Subtitles: • TTML • WebVTT • CEA-608