$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

    View Slide

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

    View Slide

  3. View Slide

  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

    View Slide

  5. The 10 foot experience
    http://rantfarm.com/wp-content/uploads/2011/01/football.jpg

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  10. Manifest.xml

    android:name="com.example.android.TvActivity"

    android:label="@string/app_name"

    android:theme="@style/Theme.Leanback">




    "android.intent.category.LEANBACK_LAUNCHER" />





    View Slide

  11. Theming
    <br/>
<br/><item name=“android:colorPrimary">@color/search_opaque</item><br/>
<br/><item name="android:windowEnterTransition">@android:transition/fade</item>
<br/><item name=“android:windowExitTransition">@android:transition/fade</item><br/>
<br/>

    View Slide

  12. There is no touchable screen
    The remote
    control travels
    with you

    View Slide

  13. There is no touchable screen
    android:name="android.software.leanback"

    android:required="false" />
    android:name="android.hardware.touchscreen"

    android:required="false" />

    View Slide

  14. There is no touchable screen
    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 ;


    View Slide

  15. There is no touchable screen
    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 ;


    View Slide

  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")

    }

    View Slide

  17. Leanback library
    http://images.blastro.com/images/artist_images/full/full_ciara_artist_photo10.jpg

    View Slide

  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'

    }

    View Slide

  19. Leanback Library - BrowseFragment

    View Slide

  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));


    View Slide

  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;

    }
    }

    View Slide

  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;

    }
    }

    View Slide

  23. Presenting data
    mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());

    CardPresenter cardPresenter = new CardPresenter();
    for (Map.Entry> entry : data.entrySet()) {

    ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);

    List 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);

    View Slide

  24. Presenting data
    mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());

    CardPresenter cardPresenter = new CardPresenter();
    for (Map.Entry> entry : data.entrySet()) {

    ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);

    List 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);

    View Slide

  25. Presenting data
    mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());

    CardPresenter cardPresenter = new CardPresenter();
    for (Map.Entry> entry : data.entrySet()) {

    ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);

    List 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);

    View Slide

  26. Leanback Library - Event listeners

    View Slide

  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());

    View Slide

  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());

    View Slide

  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(); 

    }

    }

    View Slide

  30. Leanback Library - Background Manager

    View Slide

  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(width, height) {

    @Override

    public void onResourceReady(GlideDrawable resource, GlideAnimation
    super GlideDrawable> glideAnimation) {

    mBackgroundManager.setDrawable(resource);

    }

    });

    View Slide

  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(width, height) {

    @Override

    public void onResourceReady(GlideDrawable resource, GlideAnimation
    super GlideDrawable> glideAnimation) {

    mBackgroundManager.setDrawable(resource);

    }

    });

    View Slide

  33. Leanback Library - Playback Controls

    View Slide

  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 {

    View Slide

  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);
    }

    View Slide

  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);
    }

    View Slide

  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());

    }

    }

    }

    View Slide

  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);

    }

    View Slide

  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);

    }

    View Slide

  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);

    }

    View Slide

  41. Leanback Library - Play Overlay

    View Slide

  42. Add a PlaybackOverlayFragment
    android:layout_width="match_parent"

    android:layout_height="match_parent" >


    android:layout_width="match_parent"

    android:layout_height="match_parent">



    android:id="@+id/playback_controls_fragment"

    android:name="ui.PlaybackOverlayFragment"

    android:layout_width="match_parent"

    android:layout_height="match_parent" />



    View Slide

  43. Playing a video in the background
    /*

    * Class for video playback with media control

    */

    public class PlayFragment extends
    android.support.v17.leanback.app.PlaybackOverlayFragment

    View Slide

  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());

    }

    }


    View Slide

  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());

    }

    }


    View Slide

  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);
    }

    View Slide

  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);
    }

    View Slide

  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();

    }

    View Slide

  49. Android TV
    How to build a rich experience

    View Slide

  50. Recommendations

    View Slide

  51. Add Service to the Manifest
    android:name=“.recommendations.notifications.UpdateRecommendationsService"

    android:enabled="true" />


    android:name=“.recommendations.scheduler.RecommendationsBootupService"

    android:enabled="true"

    android:exported="false">





    View Slide

  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);


    View Slide

  53. And trigger it

    Notification notification = new
    NotificationCompat.BigPictureStyle(builder).build();


    NotificationManager notificationManager = (NotificationManager)
    context.getSystemService(Context.NOTIFICATION_SERVICE);

    notificationManager.notify(notificationUniqueId, notification);

    View Slide

  54. Now Playing Card

    View Slide

  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);

    }

    View Slide

  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());

    }

    View Slide

  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());

    }

    View Slide

  58. Global search

    View Slide

  59. Because everybody loves Content Providers
    android:name=".search.FilmSearchContentProvider"

    android:authorities="com.mubi.search"

    android:exported="true">

    android:pathPrefix="/search_suggest_query"

    android:readPermission="android.permission.GLOBAL_SEARCH" />


    View Slide

  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);

    }

    }

    View Slide

  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);

    }

    }

    View Slide

  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()

    }

    );

    }

    View Slide

  63. Not everything has to look the same

    View Slide

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

    View Slide

  65. ExoPlayer
    android:id="@+id/root"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:keepScreenOn="true">


    android:id="@+id/surface_view"

    android:layout_width="match_parent"

    android:layout_height="match_parent"/>


    android:id="@+id/playback_controls_fragment"
    android:name="fastlane.PlaybackOverlayFragment"

    android:layout_width="match_parent"

    android:layout_height="match_parent" />



    View Slide

  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);


    }

    View Slide

  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);


    }

    View Slide

  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);


    }

    View Slide

  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();

    View Slide

  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();

    View Slide

  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();

    View Slide

  72. Android TV
    ExoPlayer on Github
    github.com/google/ExoPlayer
    What’s next?
    Android TV Samples
    github.com/googlesamples/androidtv-Leanback

    View Slide

  73. “Thank You!
    @dggonzalez
    +DavidGonzalezMalmstein
    malmstein
    Any questions?
    for putting up with all the blue”

    View Slide