Share
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
Transcript
142 slides
-
spr.com Chromecast & Android Deliver an incredible experience from your
smartphone to your TV - Jorge Coca | Chicago Code Camp 2015 - -
spr.com What is Chromecast? • HDMI dongle for your TV
• $35 • Smart TV • Cast content from your smartphone/table/computer to your TV -
spr.com Sender & Receiver Sender and receiver will have to
be connected to the same WIFI (or use guest mode) -
spr.com Sender • Android SDK • iOS SDK • Google
Chrome SDK (desktop, mobile…) • No Windows Phone • No Internet Explorer/Safari… -
spr.com … but first • Register your device in the
Cast Developer Console • https://cast.google.com/publish/#/overview • Developer fees: $5 -
spr.com Google Cast Developer Console • App Info: package name
• Receiver info • Listing details (app marketing) • Get your APPLICATION ID -
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 -
spr.com Options • Chromecast SDK • Flexible • Complex •
CastCompanionLibrary • Predefined scenarios • Limited flexibility -
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 -
spr.com Meet Woody! • App for tracking & discovering movies
• Cast open source movies -
spr.com Introduce Cast to users Helps users to know that
the sender app now supports Casting -
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 -
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); } } -
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); } } -
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); } } -
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(); } -
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(); } -
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); } -
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); } -
spr.com BaseChromecastActivity @Override protected void onDestroy() { if (videoCastManager !=
null) { miniController.removeOnMiniControllerChangedListener(videoCastManager); videoCastManager.removeMiniController(miniController); videoCastManager.clearContext(this); } super.onDestroy(); } -
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); } -
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); } -
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; } -
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; } -
spr.com ReconnectionService Background service that handles reconnection logic when wifi
connectivity is lost <service android:name="com.google.sample.castcompanionlibrary.cast.reconnection.ReconnectionService"/> -
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); -
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); -
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(); } -
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(); } -
spr.com VideoCastNotificationService Service that allows notifications to appear when needed
beyond the availability of the application -
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> -
spr.com Introduce Cast to users Helps users to know that
the sender app now supports Casting -
spr.com ShowCaseView • Not included in the CastCompanionLibrary, but used
by Google in their examples • https://github.com/amlcurran/ShowcaseView • Easily customizable: styles.xml -
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); } } -
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); } } -
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; } -
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 -
spr.com Search • Sender app starts MediaRouter device discover •
MediaRouter informs sender app of the route the user selected -
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 -
spr.com Connect • Sender app retrieves CastDevice instance • Sender
app creates a GoogleApiClient • Sender app connects the GoogleApiClient • SDK confirms that GoogleApiClient is connected -
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 -
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> -
spr.com Adding cast button: MediaRouteItem • Cast Menu, not connected:
receivers available • Cast Menu, connected but not casting • Cast Menu, while casting -
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> -
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; -
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; -
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; -
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; -
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(); } -
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(); } -
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(); } -
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(); } -
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; } -
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; } -
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(); } -
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(); } -
spr.com MediaRouterCallback • Extension of provided MediaRouter.Callback • Need to
override two methods: • onRouteSelected(MediaRouter router, RouteInfo info) • onRouteUnselected(MediaRouter router, RouteInfo info) -
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; } } -
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; } } -
spr.com initCastClientListener() private void initCastClientListener() { castClientListener = new Cast.Listener()
{ @Override public void onApplicationDisconnected(int statusCode) { teardown(); } }; } -
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; } } -
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; } }); } -
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; } }); } -
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; } } -
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; } } -
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(); } -
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(); } -
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(); } -
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(); } -
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(); } -
spr.com ConnectionFailedListener private class ConnectionFailedListener implements GoogleApiClient.OnConnectionFailedListener { @Override public
void onConnectionFailed(ConnectionResult connectionResult) { teardown(); } } -
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 -
spr.com private class ConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks { @Override public void
onConnected(Bundle bundle) { // implementation here } @Override public void onConnectionSuspended(int i) { waitingForReconnect = true; } } -
spr.com private class ConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks { @Override public void
onConnected(Bundle bundle) { // implementation here } @Override public void onConnectionSuspended(int i) { waitingForReconnect = true; } } -
spr.com @Override public void onConnected(Bundle bundle) { if (waitingForReconnect) {
waitingForReconnect = false; reconnectChannels(bundle); } else { // Happy path :) } } -
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) {} } } -
spr.com @Override public void onConnected(Bundle bundle) { if (waitingForReconnect) {
waitingForReconnect = false; reconnectChannels(bundle); } else { // Happy path :) } } -
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) {} -
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) {} -
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) {} -
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) {} -
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(); } } }); } -
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(); } } }); } -
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) {} } -
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) {} } -
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) {} } -
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) {} } -
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(); } } }); } -
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)); } } -
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; } -
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; } -
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; } -
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; } -
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 -
spr.com Custom Receiver • Full control of what happens on
the receiver • No predefined fields • Need to host on secure servers -
spr.com Supported media: Image Images have a display size limitation
of 720p • BMP • GIF • JPEG • PNG • WEBP -
spr.com Audio Codecs • HE-AAC • AAC LC • MP3
• Vorbis • WAV -
spr.com Video • H.264 High Profile Level 4.1 • VP8
• Subtitles: • TTML • WebVTT • CEA-608 -
spr.com Helpful links • https://developers.google.com/cast/ • https://github.com/googlecast • This repo
contains different examples on how to use Chromecast