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

ThatConference 2015

ThatConference 2015

CHROMECAST & ANDROID: DELIVER AN INCREDIBLE EXPERIENCE FROM YOUR SMARTPHONE TO YOUR TV

Jorge Coca

August 11, 2015
Tweet

More Decks by Jorge Coca

Other Decks in Programming

Transcript

  1. JORGE COCA - SPR CONSULTING TOPIC DATE SPEAKER THAT CONFERENCE

    2015 CHROMECAST & ANDROID DELIVER AN INCREDIBLE EXPERIENCE FROM YOUR SMARTPHONE TO YOUR TV
  2. 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)
  3. 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
  4. Receiver Will stream media content that the sender tells it

    to reproduce Can display informative content while in “idle” mode
  5. IT WILL BE AN “HTML5” WEBSITE DISPLAYED ON OUR TV

    CONTROLLED BY OUR “SENDER” APP
  6. 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
  7. 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
  8. 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
  9. … BUT GOOGLE WILL PROVIDE SOME “MAGIC” TOOL TO MAKE

    THE DEVELOPMENT PROCESS EASIER, RIGHT?
  10. 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
  11. YOUR APP IS TRYING TO CONQUER THE LIVING ROOM, SO

    USERS DON’T WANT TO DEAL WITH PROBLEMS, BUGS, CRASHES OR BAD UX
  12. Google Cast UX Guidelines - Key Ideas Your app is

    the remote It’s a shared experience It’s cross platform
  13. 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
  14. 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
  15. SENDER IS NOT CONNECTED - PLAYS MEDIA LOCALLY ON THE

    DEVICE RECEIVER SHOWS HOME SCREEN
  16. LOCK SCREEN CONTROLS YOUR DEVICE NOW IS A TV REMOTE

    CONTROL, SO DON’T MAKE YOUR USERS WASTE THEIR TIME
  17. 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
  18. … but first Developers must register a test device in

    the Cast Developer Console https://cast.google.com/publish/#/overview Developer fees: $5
  19. Google Cast Developer Console Need to provide app info: package

    name Register your receiver Marketing details Get your CHROMECAST_APP_ID
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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. 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. 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. 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
  28. 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(); }
  29. 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(); }
  30. 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
  31. 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);
  32. 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);
  33. 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(); }
  34. 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(); }
  35. 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
  36. 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); }
  37. 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); }
  38. BaseChromecastActivity @Override protected void onDestroy() { if (videoCastManager != null)

    { miniController.removeOnMiniControllerChangedListener(videoCastManager); videoCastManager.removeMiniController(miniController); videoCastManager.clearContext(this); } super.onDestroy(); }
  39. 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); }
  40. 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); }
  41. 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); }
  42. 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
  43. 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
  44. 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
  45. 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(); }
  46. 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
  47. INTRODUCE CAST TO FIRST TIME USERS USING THE SHOW CASE

    VIEW LIBRARY (DEVELOPED BY @AMLCURRAN)
  48. ShowCaseView Not included in the CastCompanionLibrary, but used by Google

    in their examples https://github.com/amlcurran/ShowcaseView Easy to customize: styles.xml
  49. 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(); }
  50. 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(); }
  51. 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); } }
  52. 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. 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
  54. 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; }