Share
Embed
ThatConference 2015
CHROMECAST & ANDROID: DELIVER AN INCREDIBLE EXPERIENCE FROM YOUR SMARTPHONE TO YOUR TV
Jorge Coca
August 11, 2015
Transcript
161 slides
-
JORGE COCA - SPR CONSULTING TOPIC DATE SPEAKER THAT CONFERENCE
2015 CHROMECAST & ANDROID DELIVER AN INCREDIBLE EXPERIENCE FROM YOUR SMARTPHONE TO YOUR TV -
What is Chromecast? HDMI dongle $35 Smart TV Cast content
from your device to your TV -
How does it work? Two devices are involved in the
process: SENDER: device used for discovery and media control RECEIVER: device used for streaming media (Cast- ready device) -
Sender Will discover Cast-ready device (same Wifi network or guest
access) Will discover media content and tell the Cast-ready device what to play Will behave as a remote control once the Cast-ready device is streaming content -
Receiver Will stream media content that the sender tells it
to reproduce Can display informative content while in “idle” mode -
IT WILL BE AN “HTML5” WEBSITE DISPLAYED ON OUR TV
CONTROLLED BY OUR “SENDER” APP -
Step by step - Connection (1/3) 1) Receiver advertises itself
in the “same network” 2) Sender will discover receiver and will give the option to establish connection with the receiver 3) If user decides to connect, a web socket will be opened between sender and receiver -
Step by step - Media discovery (2/3) 1) Receiver will
fetch its first HTML5 screen (Splash Screen) 2) User will select media to play in the sender app, and the sender app will send the URL that the receiver should play -
Step by step - Streaming content (3/3) 1) Media is
downloaded and streamed by the receiver 2) HTML5 receiver app receives media request and starts playing the content -
… BUT GOOGLE WILL PROVIDE SOME “MAGIC” TOOL TO MAKE
THE DEVELOPMENT PROCESS EASIER, RIGHT? -
Google Cast SDK For sender apps: Full set of APIs
for Android (Java), iOS (Objective-C & Swift) and Chrome (Javascript) For receiver apps: HTML5/Javascript APIs -
YOUR APP IS TRYING TO CONQUER THE LIVING ROOM, SO
USERS DON’T WANT TO DEAL WITH PROBLEMS, BUGS, CRASHES OR BAD UX -
Google Cast UX Guidelines - Key Ideas Your app is
the remote It’s a shared experience It’s cross platform -
INTRODUCE CAST THIS IS A ONE-TIME INTRODUCTION SCREEN, SO USERS
CAN LEARN HOW TO USE CHROMECAST CAST ICON MUST VISIBLE FROM EVERY SCREEN CAST ICON LOCATION MUST CONSISTENT ACROSS THE APP PREFERRED LOCATION: RIGHT CORNER OF THE NAVIGATION BAR -
Cast Button - States UNAVAILABLE: while receivers are not available,
cast button doesn’t appear DISCONNECTED: the cast button appears if receivers are available CONNECTING: while the cast receiver is connecting, the cast button animates the waves CONNECTED: the cast button appears with a filled frame shape -
SENDER PLAYS CONTENT - IT BEHAVES AS A REMOTE CONTROL
RECEIVER IS PLAYING CONTENT -
SENDER PLAYS CONTENT - IT BEHAVES AS A REMOTE CONTROL
RECEIVER IS PLAYING CONTENT -
SENDER IS NOT CONNECTED - PLAYS MEDIA LOCALLY ON THE
DEVICE RECEIVER SHOWS HOME SCREEN -
SENDER PLAYS CONTENT - IT BEHAVES AS A REMOTE CONTROL
RECEIVER IS PLAYING CONTENT -
SENDER PLAYS CONTENT - IT BEHAVES AS A REMOTE CONTROL
RECEIVER IS PLAYING CONTENT -
LOCK SCREEN CONTROLS YOUR DEVICE NOW IS A TV REMOTE
CONTROL, SO DON’T MAKE YOUR USERS WASTE THEIR TIME -
WHAT IF WE LOSE CONNECTION WITH THE RECEIVER? WIFI DISCONNECTION
BATTERY DIES APP PROCESS STOPPED -
Cast Companion Library Wrapper around Cast SDK APIs Makes SUPER
EASY to develop what’s on the UX guidelines Open source Available on Github: https://github.com/googlecast/CastCompanionLibrary-android -
… but first Developers must register a test device in
the Cast Developer Console https://cast.google.com/publish/#/overview Developer fees: $5 -
Google Cast Developer Console Need to provide app info: package
name Register your receiver Marketing details Get your CHROMECAST_APP_ID -
MEET WOODY! MY LITTLE PROJECT FOR SHOWING HOW THE CAST
COMPANION LIBRARY WORKS -
Assumptions We already have an app with screens in place:
Results screen with a list of movies with multiple frames for Popular movies, Most rated,… A details screen listing information about the selected movie A search screen We will have to check if Google Play Services are available -
Architecture We will build a BaseChromecastActivity, and all the activities
in our app will inherit from it Will only contain Cast related code ActionBarActivity BaseChromecastActivity Rest of activities of the app -
Our goals are… 1. Add cast icon 2. Introduce cast
to first time users 3. Connect to a Cast-ready device 4. Cast content 5. Add a mini controller in our app 6. Add a sticky notification with media controls 7. Add a lock screen with media controls 8. Auto-reconnect when connection is lost -
CastManager It is the brain of the Cast functionality. It
will update the NotificationService and the MiniController accordingly. BaseCastManager: abstract class that handles most of connectivity issues that transcends the lifecycle of individual activities. VideoCastManager: designed for video-centric apps -
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); } } -
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); } } -
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); } } -
Our goals are… 1. Add cast icon 2. Introduce cast
to first time users 3. Connect to a Cast-ready device 4. Cast content 5. Add a mini controller in our app 6. Add a sticky notification with media controls: ENABLED 7. Add a lock screen with media controls: ENABLED 8. Auto-reconnect when connection is lost: ENABLED -
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(); } -
Check for 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(); } -
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; } -
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; } -
Our goals are… 1. Add cast icon: DONE 2. Introduce
cast to first time users 3. Connect to a Cast-ready device: DONE 4. Cast content 5. Add a mini controller in our app 6. Add a sticky notification with media controls: ENABLED 7. Add a lock screen with media controls: ENABLED 8. Auto-reconnect when connection is lost: ENABLED -
LET’S PLAY SOME CONTENT! VIDEOCASTCONTROLLERACTIVITY WILL PROVIDE A DEFAULT IMPLEMENTATION
OF THE PLAYER CONTROL SCREEN -
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); -
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); -
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(); } -
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(); } -
Our goals are… 1. Add cast icon: DONE 2. Introduce
cast to first time users 3. Connect to a Cast-ready device: DONE 4. Cast content: DONE 5. Add a mini controller in our app 6. Add a sticky notification with media controls: ENABLED 7. Add a lock screen with media controls: ENABLED 8. Auto-reconnect when connection is lost: ENABLED -
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); } -
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); } -
BaseChromecastActivity @Override protected void onDestroy() { if (videoCastManager != null)
{ miniController.removeOnMiniControllerChangedListener(videoCastManager); videoCastManager.removeMiniController(miniController); videoCastManager.clearContext(this); } super.onDestroy(); } -
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); } -
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); } -
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); } -
Our goals are… 1. Add cast icon: DONE 2. Introduce
cast to first time users 3. Connect to a Cast-ready device: DONE 4. Cast content: DONE 5. Add a mini controller in our app: DONE 6. Add a sticky notification with media controls: ENABLED 7. Add a lock screen with media controls: ENABLED 8. Auto-reconnect when connection is lost: ENABLED -
THIS NOTIFICATION WILL APPEAR EVERY TIME OUR APP IS CASTING
AND IS NOT THE ACTIVE APP -
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> -
Our goals are… 1. Add cast icon: DONE 2. Introduce
cast to first time users 3. Connect to a Cast-ready device: DONE 4. Cast content: DONE 5. Add a mini controller in our app: DONE 6. Add a sticky notification with media controls: DONE 7. Add a lock screen with media controls: ENABLED 8. Auto-reconnect when connection is lost: ENABLED -
Our goals are… 1. Add cast icon: DONE 2. Introduce
cast to first time users 3. Connect to a Cast-ready device: DONE 4. Cast content: DONE 5. Add a mini controller in our app: DONE 6. Add a sticky notification with media controls: DONE 7. Add a lock screen with media controls: ENABLED 8. Auto-reconnect when connection is lost: ENABLED -
Remember this code? 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(); } -
Our goals are… 1. Add cast icon: DONE 2. Introduce
cast to first time users 3. Connect to a Cast-ready device: DONE 4. Cast content: DONE 5. Add a mini controller in our app: DONE 6. Add a sticky notification with media controls: DONE 7. Add a lock screen with media controls: DONE 8. Auto-reconnect when connection is lost: ENABLED -
INTRODUCE CAST TO FIRST TIME USERS USING THE SHOW CASE
VIEW LIBRARY (DEVELOPED BY @AMLCURRAN) -
ShowCaseView Not included in the CastCompanionLibrary, but used by Google
in their examples https://github.com/amlcurran/ShowcaseView Easy to customize: styles.xml -
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(); } -
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(); } -
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); } } -
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); } } -
Our goals are… 1. Add cast icon: DONE 2. Introduce
cast to first time users: DONE 3. Connect to a Cast-ready device: DONE 4. Cast content: DONE 5. Add a mini controller in our app: DONE 6. Add a sticky notification with media controls: DONE 7. Add a lock screen with media controls: DONE 8. Auto-reconnect when connection is lost: ENABLED -
StyledMediaReceiver .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; } -
Find this presentation ThatConference 2015 https://speakerdeck.com/jorgecoca/thatconference-2015 Chicago Code Camp 2015
https://speakerdeck.com/jorgecoca/chromecast-and-android