$30 off During Our Annual Pro Sale. View Details »

Android TV: This is not the idiot box you are looking for

Android TV: This is not the idiot box you are looking for

Do you wonder what Android TV is and how could you make the most out of it? Take a look at the features it has to offer and how to build a great user experience with Android TV. Talk given at Droidcon Madrid 2015.

David González

April 26, 2015
Tweet

More Decks by David González

Other Decks in Technology

Transcript

  1. Android TV This is not the idiot box you are

    looking for
  2. +DavidGonzalezMalmstein malmstein David González Technical Product Owner at Novoda @dggonzalez

    +DavidGonzalezMalmstein malmstein Android TV
  3. None
  4. “Designers are also nice people*” Sebastiano Poggi Android Developer @Novoda

    This is not the idiot box you are looking for *allegedly Dave Clements Head of Design @Novoda
  5. The 10 foot experience http://rantfarm.com/wp-content/uploads/2011/01/football.jpg

  6. This is not a big tablet http://www.oneextraordinarymarriage.com/wp-content/uploads/2013/02/Couple-watching-tv.jpg

  7. What is Android TV? Smarter way of using your TV

  8. Android TV Devices ADT-1, Nexus Player, Sony Bravia, LG, Panasonic

  9. Adapting for Android TV Distinguish between phone, tablet and television

  10. Manifest.xml <application android:banner="@drawable/banner">
 <activity
 android:name="com.example.android.TvActivity"
 android:label="@string/app_name"
 android:theme="@style/Theme.Leanback">
 
 <intent-filter>
 <action

    android:name="android.intent.action.MAIN" />
 <category android:name= "android.intent.category.LEANBACK_LAUNCHER" /> 
 </intent-filter>
 
 </activity>
 </application>
  11. Theming <style name="Theme.Example.Leanback" parent=“Theme.Leanback"> 
 <item name=“android:colorPrimary">@color/search_opaque</item> 
 <item name="android:windowEnterTransition">@android:transition/fade</item>


    <item name=“android:windowExitTransition">@android:transition/fade</item> 
 </style>
  12. There is no touchable screen The remote control travels with

    you
  13. There is no touchable screen <uses-feature
 android:name="android.software.leanback"
 android:required="false" /> <uses-feature


    android:name="android.hardware.touchscreen"
 android:required="false" />
  14. There is no touchable screen <ImageView
 android:focusable="true"
 android:focusableInTouchMode="true"
 android:nextFocusDown="@+id/view1"
 android:nextFocusUp="@+id/view2"

    /> 
 KeyEvent.KEYCODE_DPAD_CENTER;
 KeyEvent.KEYCODE_DPAD_LEFT;
 KeyEvent.KEYCODE_DPAD_RIGHT;
 KeyEvent.KEYCODE_DPAD_DOWN;
 KeyEvent.KEYCODE_DPAD_UP ;

  15. There is no touchable screen <ImageView
 android:focusable="true"
 android:focusableInTouchMode="true"
 android:nextFocusDown="@+id/view1"
 android:nextFocusUp="@+id/view2"

    /> 
 KeyEvent.KEYCODE_DPAD_CENTER;
 KeyEvent.KEYCODE_DPAD_LEFT;
 KeyEvent.KEYCODE_DPAD_RIGHT;
 KeyEvent.KEYCODE_DPAD_DOWN;
 KeyEvent.KEYCODE_DPAD_UP ;

  16. Check for a TV Device public static final String TAG

    = "DeviceTypeRuntimeCheck";
 
 UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE);
 if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { 
 Log.d(TAG, "Running on a TV Device")
 } else {
 Log.d(TAG, "Running on a non-TV Device")
 }
  17. Leanback library http://images.blastro.com/images/artist_images/full/full_ciara_artist_photo10.jpg

  18. Gradle dependencies dependencies {
 compile 'com.android.support:recyclerview-v7:22.0.0'
 compile 'com.android.support:leanback-v17:22.0.0'
 compile 'com.android.support:appcompat-v7:22.0.0'


    }
  19. Leanback Library - BrowseFragment

  20. Customising the BrowseFragment public class MainFragment extends BrowseFragment setBadgeDrawable(getDrawable(R.drawable.videos_by_google_banner));
 setTitle(getString(R.string.browse_title));

    
 setHeadersState(HEADERS_ENABLED);
 setHeadersTransitionOnBackEnabled(true); 
 setBrandColor(getResources().getColor(R.color.fastlane_background));
 setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));
 

  21. Presenting data setHeaderPresenterSelector(new PresenterSelector() {
 @Override
 public Presenter getPresenter(Object o)

    {
 return new IconHeaderItemPresenter();
 }
 }); public class IconHeaderItemPresenter extends Presenter { @Override
 public ViewHolder onCreateViewHolder(ViewGroup viewGroup) {
 return new ViewHolder(inflater.inflate(R.layout.icon_header_item, null));
 } @Override
 public void onBindViewHolder(ViewHolder viewHolder, Object o) {
 View rootView = viewHolder.view;
 } }
  22. Presenting data setHeaderPresenterSelector(new PresenterSelector() {
 @Override
 public Presenter getPresenter(Object o)

    {
 return new IconHeaderItemPresenter();
 }
 }); public class IconHeaderItemPresenter extends Presenter { @Override
 public ViewHolder onCreateViewHolder(ViewGroup viewGroup) {
 return new ViewHolder(inflater.inflate(R.layout.icon_header_item, null));
 } @Override
 public void onBindViewHolder(ViewHolder viewHolder, Object o) {
 View rootView = viewHolder.view;
 } }
  23. Presenting data mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
 CardPresenter cardPresenter =

    new CardPresenter(); for (Map.Entry<String, List<Movie>> entry : data.entrySet()) {
 ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
 List<Movie> list = entry.getValue();
 
 for (int j = 0; j < list.size(); j++) {
 listRowAdapter.add(list.get(j));
 }
 HeaderItem header = new HeaderItem(i, entry.getKey());
 i++;
 mRowsAdapter.add(new ListRow(header, listRowAdapter));
 } setAdapter(mRowsAdapter);
  24. Presenting data mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
 CardPresenter cardPresenter =

    new CardPresenter(); for (Map.Entry<String, List<Movie>> entry : data.entrySet()) {
 ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
 List<Movie> list = entry.getValue();
 
 for (int j = 0; j < list.size(); j++) {
 listRowAdapter.add(list.get(j));
 }
 HeaderItem header = new HeaderItem(i, entry.getKey());
 i++;
 mRowsAdapter.add(new ListRow(header, listRowAdapter));
 } setAdapter(mRowsAdapter);
  25. Presenting data mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
 CardPresenter cardPresenter =

    new CardPresenter(); for (Map.Entry<String, List<Movie>> entry : data.entrySet()) {
 ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
 List<Movie> list = entry.getValue();
 
 for (int j = 0; j < list.size(); j++) {
 listRowAdapter.add(list.get(j));
 }
 HeaderItem header = new HeaderItem(i, entry.getKey());
 i++;
 mRowsAdapter.add(new ListRow(header, listRowAdapter));
 } setAdapter(mRowsAdapter);
  26. Leanback Library - Event listeners

  27. Setup event listeners setOnSearchClickedListener(new View.OnClickListener() {
 
 @Override
 public void

    onClick(View view) {
 Intent intent = new Intent(getActivity(), SearchActivity.class);
 startActivity(intent);
 }
 });
 
 setOnItemViewClickedListener(new ItemViewClickedListener());
 setOnItemViewSelectedListener(new ItemViewSelectedListener());
  28. Setup event listeners setOnSearchClickedListener(new View.OnClickListener() {
 
 @Override
 public void

    onClick(View view) {
 Intent intent = new Intent(getActivity(), SearchActivity.class);
 startActivity(intent);
 }
 });
 
 setOnItemViewClickedListener(new ItemViewClickedListener());
 setOnItemViewSelectedListener(new ItemViewSelectedListener());
  29. Setup event listeners private final class ViewSelectedListener implements OnItemViewSelectedListener {


    @Override
 public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) {
 
 mBackgroundURI = ((Movie) item).getBackgroundImageURI();
 startBackgroundTimer(); 
 }
 }
  30. Leanback Library - Background Manager

  31. Setting a background image private void prepareBackgroundManager() {
 mBackgroundManager =

    BackgroundManager.getInstance(getActivity());
 mBackgroundManager.attach(getActivity().getWindow());
 } Glide.with(getActivity())
 .load(uri)
 .centerCrop()
 .error(mDefaultBackground)
 .into(new SimpleTarget<GlideDrawable>(width, height) {
 @Override
 public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
 mBackgroundManager.setDrawable(resource);
 }
 });
  32. Setting a background image private void prepareBackgroundManager() {
 mBackgroundManager =

    BackgroundManager.getInstance(getActivity());
 mBackgroundManager.attach(getActivity().getWindow());
 } Glide.with(getActivity())
 .load(uri)
 .centerCrop()
 .error(mDefaultBackground)
 .into(new SimpleTarget<GlideDrawable>(width, height) {
 @Override
 public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
 mBackgroundManager.setDrawable(resource);
 }
 });
  33. Leanback Library - Playback Controls

  34. Presenting details /*
 * LeanbackDetailsFragment extends DetailsFragment, a Wrapper fragment

    for leanback details screens.
 * It shows a detailed view of video and its meta plus related videos.
 */
 public class MovieDetailsFragment extends android.support.v17.leanback.app.DetailsFragment {
  35. Presenting details private void setupAdapter() {
 mPresenterSelector = new ClassPresenterSelector();


    mAdapter = new ArrayObjectAdapter(mPresenterSelector);
 setAdapter(mAdapter);
 } private void setupDetailsOverviewRowPresenter() { DetailsOverviewRowPresenter detailsPresenter =
 new DetailsOverviewRowPresenter( new DetailsDescriptionPresenter());
 detailsPresenter.setBackgroundColor(R.color.selected_background);
 detailsPresenter.setStyleLarge(true); mPresenterSelector.addClassPresenter(DetailsOverviewRow.class, detailsPresenter); }
  36. Presenting details private void setupAdapter() {
 mPresenterSelector = new ClassPresenterSelector();


    mAdapter = new ArrayObjectAdapter(mPresenterSelector);
 setAdapter(mAdapter);
 } private void setupDetailsOverviewRowPresenter() { DetailsOverviewRowPresenter detailsPresenter =
 new DetailsOverviewRowPresenter( new DetailsDescriptionPresenter());
 detailsPresenter.setBackgroundColor(R.color.selected_background);
 detailsPresenter.setStyleLarge(true); mPresenterSelector.addClassPresenter(DetailsOverviewRow.class, detailsPresenter); }
  37. Presenting details 
 public class DetailsDescriptionPresenter extends AbstractDetailsDescriptionPresenter {
 


    @Override
 protected void onBindDescription(ViewHolder viewHolder, Object item) {
 Movie movie = (Movie) item;
 
 if (movie != null) {
 viewHolder.getTitle().setText(movie.getTitle());
 viewHolder.getSubtitle().setText(movie.getStudio());
 viewHolder.getBody().setText(movie.getDescription());
 }
 }
 }
  38. Adding actions private void setupDetailsOverviewRow() {
 final DetailsOverviewRow row =

    new DetailsOverviewRow(mSelectedMovie);
 row.addAction(new Action(ACTION_WATCH_TRAILER, 
 getResources().getString(
 R.string.watch_trailer_1), 
 getResources().getString(R.string.watch_trailer_2))); 
 row.addAction(new Action(ACTION_RENT, 
 getResources().getString(R.string.rent_1),
 getResources().getString(R.string.rent_2))); 
 row.addAction(new Action(ACTION_BUY, 
 getResources().getString(R.string.buy_1),
 getResources().getString(R.string.buy_2))); 
 mAdapter.add(row);
 }
  39. Adding actions private void setupDetailsOverviewRow() {
 final DetailsOverviewRow row =

    new DetailsOverviewRow(mSelectedMovie);
 row.addAction(new Action(ACTION_WATCH_TRAILER, 
 getResources().getString(
 R.string.watch_trailer_1), 
 getResources().getString(R.string.watch_trailer_2))); 
 row.addAction(new Action(ACTION_RENT, 
 getResources().getString(R.string.rent_1),
 getResources().getString(R.string.rent_2))); 
 row.addAction(new Action(ACTION_BUY, 
 getResources().getString(R.string.buy_1),
 getResources().getString(R.string.buy_2))); 
 mAdapter.add(row);
 }
  40. Adding actions private void setupDetailsOverviewRow() {
 final DetailsOverviewRow row =

    new DetailsOverviewRow(mSelectedMovie);
 row.addAction(new Action(ACTION_WATCH_TRAILER, 
 getResources().getString(
 R.string.watch_trailer_1), 
 getResources().getString(R.string.watch_trailer_2))); 
 row.addAction(new Action(ACTION_RENT, 
 getResources().getString(R.string.rent_1),
 getResources().getString(R.string.rent_2))); 
 row.addAction(new Action(ACTION_BUY, 
 getResources().getString(R.string.buy_1),
 getResources().getString(R.string.buy_2))); 
 mAdapter.add(row);
 }
  41. Leanback Library - Play Overlay

  42. Add a PlaybackOverlayFragment <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent" >
 
 <VideoView

    android:id="@+id/videoView"
 android:layout_width="match_parent"
 android:layout_height="match_parent">
 </VideoView>
 
 <fragment
 android:id="@+id/playback_controls_fragment"
 android:name="ui.PlaybackOverlayFragment"
 android:layout_width="match_parent"
 android:layout_height="match_parent" />
 
 </FrameLayout>
  43. Playing a video in the background /*
 * Class for

    video playback with media control
 */
 public class PlayFragment extends android.support.v17.leanback.app.PlaybackOverlayFragment
  44. Playing a video in the background PlaybackControlsRowPresenter playbackControlsRowPresenter;
 playbackControlsRowPresenter =

    new PlaybackControlsRowPresenter(
 new DescriptionPresenter()); ps.addClassPresenter(PlaybackControlsRow.class, playbackControlsRowPresenter); mRowsAdapter = new ArrayObjectAdapter(ps); setAdapter(mRowsAdapter); static class DescriptionPresenter extends AbstractDetailsDescriptionPresenter {
 @Override
 protected void onBindDescription(ViewHolder viewHolder, Object item) {
 viewHolder.getTitle().setText(((Movie) item).getTitle());
 viewHolder.getSubtitle().setText(((Movie) item).getStudio());
 }
 }

  45. Playing a video in the background PlaybackControlsRowPresenter playbackControlsRowPresenter;
 playbackControlsRowPresenter =

    new PlaybackControlsRowPresenter(
 new DescriptionPresenter()); ps.addClassPresenter(PlaybackControlsRow.class, playbackControlsRowPresenter); mRowsAdapter = new ArrayObjectAdapter(ps); setAdapter(mRowsAdapter); static class DescriptionPresenter extends AbstractDetailsDescriptionPresenter {
 @Override
 protected void onBindDescription(ViewHolder viewHolder, Object item) {
 viewHolder.getTitle().setText(((Movie) item).getTitle());
 viewHolder.getSubtitle().setText(((Movie) item).getStudio());
 }
 }

  46. Playback Controls private void addPlaybackControlsRow() { ControlButtonPresenterSelector presenterSelector = new

    ControlButtonPresenterSelector();
 mPrimaryActionsAdapter = new ArrayObjectAdapter(presenterSelector);
 mSecondaryActionsAdapter = new ArrayObjectAdapter(presenterSelector); 
 mPlaybackControlsRow.setPrimaryActionsAdapter(mPrimaryActionsAdapter);
 mPlaybackControlsRow.setSecondaryActionsAdapter(mSecondaryActionsAdapter); mPlayPauseAction = new PlayPauseAction(sContext);
 mRepeatAction = new RepeatAction(sContext); mPrimaryActionsAdapter.add(mPlayPauseAction); mSecondaryActionsAdapter.add(mRepeatAction); }
  47. Playback Controls private void addPlaybackControlsRow() { ControlButtonPresenterSelector presenterSelector = new

    ControlButtonPresenterSelector();
 mPrimaryActionsAdapter = new ArrayObjectAdapter(presenterSelector);
 mSecondaryActionsAdapter = new ArrayObjectAdapter(presenterSelector); 
 mPlaybackControlsRow.setPrimaryActionsAdapter(mPrimaryActionsAdapter);
 mPlaybackControlsRow.setSecondaryActionsAdapter(mSecondaryActionsAdapter); mPlayPauseAction = new PlayPauseAction(sContext);
 mRepeatAction = new RepeatAction(sContext); mPrimaryActionsAdapter.add(mPlayPauseAction); mSecondaryActionsAdapter.add(mRepeatAction); }
  48. Request behind playback @Override
 public void onPause() {
 super.onPause();
 requestVisibleBehind(true);


    } @Override
 public void onVisibleBehindCanceled() {
 super.onVisibleBehindCanceled();
 stopPlayback();
 } @Override
 protected void onStop() {
 super.onStop();
 mSession.release();
 }
  49. Android TV How to build a rich experience

  50. Recommendations

  51. Add Service to the Manifest <service
 android:name=“.recommendations.notifications.UpdateRecommendationsService"
 android:enabled="true" />
 


    <receiver
 android:name=“.recommendations.scheduler.RecommendationsBootupService"
 android:enabled="true"
 android:exported="false">
 <intent-filter>
 <action android:name="android.intent.action.BOOT_COMPLETED" />
 </intent-filter>
 </receiver>
  52. Create the notification Bundle extras = new Bundle();
 extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, imageUri);


    NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
 .setContentTitle(title)
 .setContentText(description)
 .setPriority(PRIORITY)
 .setLocalOnly(true)
 .setOngoing(true)
 .setColor(CARD_TEXT_BACKGROUND_COLOR_RESOURCE)
 .setCategory(Notification.CATEGORY_RECOMMENDATION)
 .setLargeIcon(filmPoster)
 .setSmallIcon(CARD_SMALL_APPLICATION_LOGO)
 .setContentIntent(intent)
 .setExtras(extras);

  53. And trigger it 
 Notification notification = new NotificationCompat.BigPictureStyle(builder).build();
 


    NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
 notificationManager.notify(notificationUniqueId, notification);
  54. Now Playing Card

  55. Setup the Media Session mSession = new MediaSession (this, "MyApp");


    mSession.setCallback(new MediaSessionCallback());
 mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
 MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); if (!mSession.isActive()) {
 mSession.setActive(true);
 }
  56. Use the Media Session private void updatePlaybackState() {
 long position

    = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
 position = mMediaPlayer.getCurrentPosition();
 
 PlaybackState.Builder stateBuilder = new PlaybackState.Builder()
 .setActions(getAvailableActions());
 stateBuilder.setState(mState, position, 1.0f);
 mSession.setPlaybackState(stateBuilder.build());
 }
  57. Use the Media Session private void updateMetadata(MediaData myData) {
 MediaMetadata.Builder

    metadataBuilder = new MediaMetadata.Builder();
 metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE,
 myData.displayTitle);
 metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE,
 myData.displaySubtitle);
 metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,
 myData.artUri);
 mSession.setMetadata(metadataBuilder.build());
 }
  58. Global search

  59. Because everybody loves Content Providers <provider
 android:name=".search.FilmSearchContentProvider"
 android:authorities="com.mubi.search"
 android:exported="true">
 <path-permission


    android:pathPrefix="/search_suggest_query"
 android:readPermission="android.permission.GLOBAL_SEARCH" />
 </provider>
  60. Because everybody loves Content Providers private static UriMatcher buildUriMatcher() {


    UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
 matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
 matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
 return matcher;
 } @Override
 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
 switch (URI_MATCHER.match(uri)) {
 case SEARCH_SUGGEST:
 return getSuggestions(selectionArgs[0]);
 default:
 throw new IllegalArgumentException("Unknown Uri: " + uri);
 }
 }
  61. Because everybody loves Content Providers private static UriMatcher buildUriMatcher() {


    UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
 matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
 matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
 return matcher;
 } @Override
 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
 switch (URI_MATCHER.match(uri)) {
 case SEARCH_SUGGEST:
 return getSuggestions(selectionArgs[0]);
 default:
 throw new IllegalArgumentException("Unknown Uri: " + uri);
 }
 }
  62. Provide Cursor with your own search results private void addFilmToCursor(Film

    film, MatrixCursor matrixCursor) {
 matrixCursor.addRow(
 new Object[]{
 film.getId().toString(),
 film.getTitle(),
 getDirectorsLabel(film),
 VIDEO_MP4,
 film.getYear(),
 TimeUnit.MINUTES.toMillis(film.getDuration()),
 film.getPosterUrl(),
 film.getId().toString(),
 MubiIntentAction.SEARCH.getAction()
 }
 );
 }
  63. Not everything has to look the same

  64. ExoPlayer minSDK > 16 youtube.com/watch?v=6VjF638VObA

  65. ExoPlayer <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/root"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:keepScreenOn="true">
 
 <com.google.android.exoplayer.VideoSurfaceView android:id="@+id/surface_view"


    android:layout_width="match_parent"
 android:layout_height="match_parent"/>
 
 <fragment
 android:id="@+id/playback_controls_fragment" android:name="fastlane.PlaybackOverlayFragment"
 android:layout_width="match_parent"
 android:layout_height="match_parent" />
 
 </FrameLayout>
  66. Prepare ExoPlayer private void preparePlayer() { 
 SampleSource sampleSource =


    new FrameworkSampleSource(this, Uri.parse(mVideo.getContentUrl()), null, RENDERER_COUNT);
 
 MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT); 
 TrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
 
 }
  67. Prepare ExoPlayer private void preparePlayer() { 
 SampleSource sampleSource =


    new FrameworkSampleSource(this, Uri.parse(mVideo.getContentUrl()), null, RENDERER_COUNT);
 
 MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT); 
 TrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
 
 }
  68. Prepare ExoPlayer private void preparePlayer() { 
 SampleSource sampleSource =


    new FrameworkSampleSource(this, Uri.parse(mVideo.getContentUrl()), null, RENDERER_COUNT);
 
 MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT); 
 TrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
 
 }
  69. Setup the player and start player = ExoPlayer.Factory.newInstance(RENDERER_COUNT, 1000, 5000);


    player.addListener(this);
 player.prepare(videoRenderer, audioRenderer); Surface surface = surfaceView.getHolder().getSurface();
 player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface); playerControl = new PlayerControl(player);
 playerControl.start();
  70. Setup the player and start player = ExoPlayer.Factory.newInstance(RENDERER_COUNT, 1000, 5000);


    player.addListener(this);
 player.prepare(videoRenderer, audioRenderer); Surface surface = surfaceView.getHolder().getSurface();
 player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface); playerControl = new PlayerControl(player);
 playerControl.start();
  71. Setup the player and start player = ExoPlayer.Factory.newInstance(RENDERER_COUNT, 1000, 5000);


    player.addListener(this);
 player.prepare(videoRenderer, audioRenderer); Surface surface = surfaceView.getHolder().getSurface();
 player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface); playerControl = new PlayerControl(player);
 playerControl.start();
  72. Android TV ExoPlayer on Github github.com/google/ExoPlayer What’s next? Android TV

    Samples github.com/googlesamples/androidtv-Leanback
  73. “Thank You! @dggonzalez +DavidGonzalezMalmstein malmstein Any questions? for putting up

    with all the blue”