Building TV Playback Apps
GET STARTED
DEPENDENCIES AND PREREQUISITES
- Android 5.0 (API level 21) or higher
YOU SHOULD ALSO READ
VIDEO
DevBytes: Android TV — Using the Leanback library
Browsing and playing media files is frequently part of the user experience provided by a TV app. Building such an experience from scratch, while making sure that it is fast, fluid, and attractive can be quite challenging. Whether your app provides access to a small or large media catalog, it is important to allow users to quickly browse options and get to the content they want.
The Android framework provides classes for building user interfaces for these types of apps with the v17 leanback support library. This library provides a framework of classes for creating an efficient and familiar interface for browsing and playing media files with minimal coding. The classes are designed be extended and customized so you can create an experience that is unique to your app.
This class shows you how to build a TV app for browsing and playing media content using the Leanback support libraries for TV.
Topics
- Creating a Catalog Browser
- Learn how to use the Leanback support library to build a browsing interface for media catalogs.
- Providing a Card View
- Learn how to use the Leanback support library to build a card view for content items.
- Building a Details View
- Learn how to use the Leanback support library to build a details page for media items.
- Displaying a Now Playing Card
- Learn how to use a MediaSession to display a Now Playing card on the home screen.
Creating a Catalog Browser
PREVIOUS NEXTTHIS LESSON TEACHES YOU TO
- Create a Media Browse Layout
- Display Media Lists
- Update the Background
Media apps that run on TV need to allow users to browse its content offerings, make a selection, and start playing content. The content browsing experience for apps of this type should be simple and intuitive, as well as visually pleasing and engaging.This lesson discusses how to use the classes provided by thev17 leanback support library to implement a user interface for browsing music or videos from your app's media catalog.Create a Media Browse Layout
TheBrowseFragment
class in the leanback library allows you to create a primary layout for browsing categories and rows of media items with a minimum of code. The following example shows how to create a layout that contains aBrowseFragment
:<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <fragment android:name="android.support.v17.leanback.app.BrowseFragment" android:id="@+id/browse_fragment" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
In order to work with this layout in an activity, retrieve theBrowseFragment
element from the layout. Use the methods in this class to set display parameters such as the icon, title, and whether category headers are enabled. The following code sample demonstrates how to set the layout parameters for aBrowseFragment
in a layout:public class BrowseMediaActivity extends Activity { public static final String TAG ="BrowseActivity"; protected BrowseFragment mBrowseFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.browse_fragment); final FragmentManager fragmentManager = getFragmentManager(); mBrowseFragment = (BrowseFragment) fragmentManager.findFragmentById( R.id.browse_fragment); // Set display parameters for the BrowseFragment mBrowseFragment.setHeadersState(BrowseFragment.HEADERS_ENABLED); mBrowseFragment.setTitle(getString(R.string.app_name)); mBrowseFragment.setBadgeDrawable(getResources().getDrawable( R.drawable.ic_launcher)); mBrowseFragment.setBrowseParams(params); } }
Displaying Media Lists
TheBrowseFragment
allows you to define and display browsable media content categories and media items from a media catalog using adapters and presenters. Adapters enable you to connect to local or online data sources that contain your media catalog information. Presenters hold data about media items and provide layout information for displaying an item on screen.The following example code shows an implementation of aPresenter
for displaying string data:public class StringPresenter extends Presenter { private static final String TAG = "StringPresenter"; public ViewHolder onCreateViewHolder(ViewGroup parent) { TextView textView = new TextView(parent.getContext()); textView.setFocusable(true); textView.setFocusableInTouchMode(true); textView.setBackground( parent.getContext().getResources().getDrawable(R.drawable.text_bg)); return new ViewHolder(textView); } public void onBindViewHolder(ViewHolder viewHolder, Object item) { ((TextView) viewHolder.view).setText(item.toString()); } public void onUnbindViewHolder(ViewHolder viewHolder) { // no op } }
Once you have constructed a presenter class for your media items, you can build and attach an adapter to theBrowseFragment
to display those items on screen for browsing by the user. The following example code demonstrates how to construct an adapter to display categories and items in those categories using theStringPresenter
class shown in the previous code example:private ArrayObjectAdapter mRowsAdapter; private static final int NUM_ROWS = 4; @Override protected void onCreate(Bundle savedInstanceState) { ... buildRowsAdapter(); } private void buildRowsAdapter() { mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); for (int i = 0; i < NUM_ROWS; ++i) { ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter( new StringPresenter()); listRowAdapter.add("Media Item 1"); listRowAdapter.add("Media Item 2"); listRowAdapter.add("Media Item 3"); HeaderItem header = new HeaderItem(i, "Category " + i, null); mRowsAdapter.add(new ListRow(header, listRowAdapter)); } mBrowseFragment.setAdapter(mRowsAdapter); }
This example shows a static implementation of the adapters. A typical media browsing application uses data from an online database or web service. For an example of a browsing application that uses data retrieved from the web, see the Android TV sample app.Update the Background
In order to add visual interest to a media-browsing app on TV, you can update the background image as users browse through content. This technique can make interaction with your app feel more cinematic and enjoyable for users.The Leanback support library provides aBackgroundManager
class for changing the background of your TV app activity. The following example shows how to create a simple method for updating the background within your TV app activity:protected void updateBackground(Drawable drawable) { BackgroundManager.getInstance(this).setDrawable(drawable); }
Many of the existing media-browse apps automatically update the background as the user navigates through media listings. In order to do this, you can set up a selection listener to automatically update the background based on the user's current selection. The following example shows you how to set up anOnItemViewSelectedListener
class to catch selection events and update the background:protected void clearBackground() { BackgroundManager.getInstance(this).setDrawable(mDefaultBackground); } protected OnItemViewSelectedListener getDefaultItemViewSelectedListener() { return new OnItemViewSelectedListener() { @Override public void onItemSelected(Object item, Row row) { if (item instanceof Movie ) { URI uri = ((Movie)item).getBackdropURI(); updateBackground(uri); } else { clearBackground(); } } }; }
Note: The implementation above is a simple example shown for purposes of illustration. When creating this function in your own app, you should consider running the background update action in a separate thread for better performance. In addition, if you are planning on updating the background in response to users scrolling through items, consider adding a time to delay a background image update until the user settles on an item. This technique avoids excessive background image updates.- Providing a Card View
THIS LESSON TEACHES YOU TO
- Create a Card Presenter
- Create a Card View
TRY IT OUT
- Android Leanback sample app
In the previous lesson, you created a catalog browser, implemented in a browse fragment, that displays a list of media items. In this lesson, you create the card views for your media items and present them in the browse fragment.TheBaseCardView
class and subclasses display the meta data associated with a media item. TheImageCardView
class used in this lesson displays an image for the content along with the media item's title.This lesson describes code from the Android Leanback sample app, available on GitHub. Use this sample code to start your own app.
Create a Card Presenter
APresenter
generates views and binds objects to them on demand. In the browse fragment where your app presents its content to the user, you create aPresenter
for the content cards and pass it to the adapter that adds the content to the screen. In the following code, theCardPresenter
is created in theonLoadFinished()
callback of theLoaderManager
.@Override public void onLoadFinished(Loader<HashMap<String, List<Movie>>> arg0, HashMap<String, List<Movie>> data) { mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); CardPresenter cardPresenter = new CardPresenter(); int i = 0; 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(), null); i++; mRowsAdapter.add(new ListRow(header, listRowAdapter)); } HeaderItem gridHeader = new HeaderItem(i, getString(R.string.more_samples), null); GridItemPresenter gridPresenter = new GridItemPresenter(); ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(gridPresenter); gridRowAdapter.add(getString(R.string.grid_view)); gridRowAdapter.add(getString(R.string.error_fragment)); gridRowAdapter.add(getString(R.string.personal_settings)); mRowsAdapter.add(new ListRow(gridHeader, gridRowAdapter)); setAdapter(mRowsAdapter); updateRecommendations(); }
Create a Card View
In this step, you build the card presenter with a view holder for the card view that describes your media content items. Note that each presenter must only create one view type. If you have two different card view types then you need two different card presenters.In thePresenter
, implement anonCreateViewHolder()
callback that creates a view holder that can be used to display a content item.@Override public class CardPresenter extends Presenter { private Context mContext; private static int CARD_WIDTH = 313; private static int CARD_HEIGHT = 176; private Drawable mDefaultCardImage; @Override public ViewHolder onCreateViewHolder(ViewGroup parent) { mContext = parent.getContext(); mDefaultCardImage = mContext.getResources().getDrawable(R.drawable.movie); ...
In theonCreateViewHolder()
method, create a card view for content items. The sample below uses anImageCardView
.When a card is selected, the default behavior expands it to a larger size. If you want to designate a different color for the selected card, callsetSelected()
as shown here.... ImageCardView cardView = new ImageCardView(mContext) { @Override public void setSelected(boolean selected) { int selected_background = mContext.getResources().getColor(R.color.detail_background); int default_background = mContext.getResources().getColor(R.color.default_background); int color = selected ? selected_background : default_background; findViewById(R.id.info_field).setBackgroundColor(color); super.setSelected(selected); } }; ...
When the user opens your app, thePresenter.ViewHolder
displays theCardView
objects for your content items. You need to set these to receive focus from the D-pad controller by callingsetFocusable(true)
andsetFocusableInTouchMode(true)
.... cardView.setFocusable(true); cardView.setFocusableInTouchMode(true); return new ViewHolder(cardView); }
When the user selects theImageCardView
, it expands to reveal its text area with the background color you specify, as shown in figure 2.
- Building a Details View
THIS LESSON TEACHES YOU TO
- Build a Details Presenter
- Extend the Details Fragment
- Create a Details Activity
- Define a Listener for Clicked Items
The media browsing interface classes provided by the v17 leanback support library include classes for displaying additional information about a media item, such as a description or reviews, and for taking action on that item, such as purchasing it or playing its content.This lesson discusses how to create a presenter class for media item details, and how to extend theDetailsFragment
class to implement a details view for a media item when it is selected by a user.Note: The implementation example shown here uses an additional activity to contain theDetailsFragment
. However, it is possible to avoid creating a second activity by replacing the currentBrowseFragment
with aDetailsFragment
within the same activity using fragment transactions. For more information on using fragment transactions, see the Building a Dynamic UI with Fragments training.Build a Details Presenter
In the media browsing framework provided by the leanback library, you use presenter objects to control the display of data on screen, including media item details. The framework provides theAbstractDetailsDescriptionPresenter
class for this purpose, which is a nearly complete implementation of the presenter for media item details. All you have to do is implement theonBindDescription()
method to bind the view fields to your data objects, as shown in the following code sample:public class DetailsDescriptionPresenter extends AbstractDetailsDescriptionPresenter { @Override protected void onBindDescription(ViewHolder viewHolder, Object itemData) { MyMediaItemDetails details = (MyMediaItemDetails) itemData; // In a production app, the itemData object contains the information // needed to display details for the media item: // viewHolder.getTitle().setText(details.getShortTitle()); // Here we provide static data for testing purposes: viewHolder.getTitle().setText(itemData.toString()); viewHolder.getSubtitle().setText("2014 Drama TV-14"); viewHolder.getBody().setText("Lorem ipsum dolor sit amet, consectetur " + "adipisicing elit, sed do eiusmod tempor incididunt ut labore " + " et dolore magna aliqua. Ut enim ad minim veniam, quis " + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea " + "commodo consequat."); } }
Extend the Details Fragment
When using theDetailsFragment
class for displaying your media item details, extend that class to provide additional content such as a preview image and actions for the media item. You can also provide additional content, such as a list of related media items.The following example code demonstrates how to use the presenter class shown in the previous section, to add a preview image and actions for the media item being viewed. This example also shows the addition of a related media items row, which appears below the details listing.public class MediaItemDetailsFragment extends DetailsFragment { private static final String TAG = "MediaItemDetailsFragment"; private ArrayObjectAdapter mRowsAdapter; @Override public void onCreate(Bundle savedInstanceState) { Log.i(TAG, "onCreate"); super.onCreate(savedInstanceState); buildDetails(); } private void buildDetails() { ClassPresenterSelector selector = new ClassPresenterSelector(); // Attach your media item details presenter to the row presenter: DetailsOverviewRowPresenter rowPresenter = new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter()); selector.addClassPresenter(DetailsOverviewRow.class, rowPresenter); selector.addClassPresenter(ListRow.class, new ListRowPresenter()); mRowsAdapter = new ArrayObjectAdapter(selector); Resources res = getActivity().getResources(); DetailsOverviewRow detailsOverview = new DetailsOverviewRow( "Media Item Details"); // Add images and action buttons to the details view detailsOverview.setImageDrawable(res.getDrawable(R.drawable.jelly_beans)); detailsOverview.addAction(new Action(1, "Buy $9.99")); detailsOverview.addAction(new Action(2, "Rent $2.99")); mRowsAdapter.add(detailsOverview); // Add a Related items row ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter( new StringPresenter()); listRowAdapter.add("Media Item 1"); listRowAdapter.add("Media Item 2"); listRowAdapter.add("Media Item 3"); HeaderItem header = new HeaderItem(0, "Related Items", null); mRowsAdapter.add(new ListRow(header, listRowAdapter)); setAdapter(mRowsAdapter); } }
Create a Details Activity
Fragments such as theDetailsFragment
must be contained within an activity in order to be used for display. Creating an activity for your details view, separate from the browse activity, enables you to invoke your details view using anIntent
. This section explains how to build an activity to contain your implementation of the detail view for your media items.Start creating the details activity by building a layout that references your implementation of theDetailsFragment
:<!-- file: res/layout/details.xml --> <fragment xmlns:android="http://schemas.android.com/apk/res/android" android:name="com.example.android.mediabrowser.MediaItemDetailsFragment" android:id="@+id/details_fragment" android:layout_width="match_parent" android:layout_height="match_parent" />
Next, create an activity class that uses the layout shown in the previous code example:public class DetailsActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.details); } }
Finally, add this new activity to the manifest. Remember to apply the Leanback theme to ensure that the user interface is consistent with the media browse activity:<application> ... <activity android:name=".DetailsActivity" android:exported="true" android:theme="@style/Theme.Leanback"/> </application>
Define a Listener for Clicked Items
After you have implemented theDetailsFragment
, modify your main media browsing view to move to your details view when a user clicks on a media item. In order to enable this behavior, add anOnItemViewClickedListener
object to theBrowseFragment
that fires an intent to start the item details activity.The following example shows how to implement a listener to start the details view when a user clicks a media item in the main media browsing activity:public class BrowseMediaActivity extends Activity { ... @Override protected void onCreate(Bundle savedInstanceState) { ... // create the media item rows buildRowsAdapter(); // add a listener for selected items mBrowseFragment.OnItemViewClickedListener( new OnItemViewClickedListener() { @Override public void onItemClicked(Object item, Row row) { System.out.println("Media Item clicked: " + item.toString()); Intent intent = new Intent(BrowseMediaActivity.this, DetailsActivity.class); // pass the item information intent.getExtras().putLong("id", item.getId()); startActivity(intent); } }); } }
THIS LESSON TEACHES YOU TO
TV apps may allow users to play music or other media in the background while using other applications. If your app allows this type of use, it must must provide a means for the user to return to the app to pause the music or switch to a new song. The Android framework enables TV apps to do this by displaying a Now Playing card on the home screen in the recommendations row.The Now Playing card is a system artifact that displays on the home screen in the recommendations row for an active media session. It includes the media metadata such as the album art, title, and app icon. When the user selects it, the system opens the the app that owns the session.This lesson shows how to use theMediaSession
class to implement the Now Playing card.Start a Media Session
A playback app can run as an activity or as a service. The service is required for background playback because it can continue to play media even after the activity that launched it has been destroyed. For this discussion, the media playback app is assumed to be running in aMediaBrowserService
.In your service'sonCreate()
method, create a newMediaSession
, set the callback and flags appropriate to a media app, and set the session token for theMediaBrowserService
.mSession = new MediaSession(this, "MusicService"); mSession.setCallback(new MediaSessionCallback()); mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); // for the MediaBrowserService setSessionToken(mSession.getSessionToken());
Note: The Now Playing card will display only for a media session with theFLAG_HANDLES_TRANSPORT_CONTROLS
flag set.Display a Now Playing Card
The Now Playing card shows up aftersetActive(true)
is called, if the session is the highest priority session in the system. Also, note that your app must request the audio focus, as described in Managing Audio Focus.private void handlePlayRequest() { tryToGetAudioFocus(); if (!mSession.isActive()) { mSession.setActive(true); } ...
The card is removed from the home screen whensetActive(false)
is called or if another app initiates media playback. You may want to remove the card from the home screen some time after playback is paused, depending on how long you want to keep the card up, usually 5 to 30 minutes.Update the Playback State
As with any media app, update the playback state in theMediaSession
so that the card can display the current metadata, as shown in the following example:private void updatePlaybackState() { long position = PlaybackState.PLAYBACK_POSITION_UNKNOWN; if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { position = mMediaPlayer.getCurrentPosition(); } PlaybackState.Builder stateBuilder = new PlaybackState.Builder() .setActions(getAvailableActions()); stateBuilder.setState(mState, position, 1.0f); mSession.setPlaybackState(stateBuilder.build()); } private long getAvailableActions() { long actions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PLAY_FROM_MEDIA_ID | PlaybackState.ACTION_PLAY_FROM_SEARCH; if (mPlayingQueue == null || mPlayingQueue.isEmpty()) { return actions; } if (mState == PlaybackState.STATE_PLAYING) { actions |= PlaybackState.ACTION_PAUSE; } if (mCurrentIndexOnQueue > 0) { actions |= PlaybackState.ACTION_SKIP_TO_PREVIOUS; } if (mCurrentIndexOnQueue < mPlayingQueue.size() - 1) { actions |= PlaybackState.ACTION_SKIP_TO_NEXT; } return actions; }
Display the Media Metadata
For the track currently playing, set theMediaMetadata
with thesetMetadata()
method. This method of the media session object lets you provide information to the Now Playing card about the track such as the title, subtitle, and various icons. The following example assumes your track's data is stored in a custom data class,MediaData
.private void updateMetadata(MediaData myData) { MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder(); // To provide most control over how an item is displayed set the // display fields in the metadata 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); // And at minimum the title and artist for legacy support metadataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, myData.title); metadataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, myData.artist); // A small bitmap for the artwork is also recommended metadataBuilder.putString(MediaMetadata.METADATA_KEY_ART, myData.artBitmap); // Add any other fields you have for your data as well mSession.setMetadata(metadataBuilder.build()); }
Respond to User Action
When the user selects the Now Playing card, the system opens the app that owns the session. If your app provides aPendingIntent
to pass tosetSessionActivity()
, the system launches the activity you specify, as demonstrated below. If not, the default system intent opens. The activity you specify must provide playback controls that allow users to pause or stop playback.Intent intent = new Intent(mContext, MyActivity.class); PendingIntent pi = PendingIntent.getActivity(context, 99 /*request code*/, intent, PendingIntent.FLAG_UPDATE_CURRENT); mSession.setSessionActivity(pi);
Helping Users Find Your Content on TV
DEPENDENCIES AND PREREQUISITES
- Android 5.0 (API level 21) or higher
YOU SHOULD ALSO READ
TV devices offer many entertainment options for users. They have thousands of content options from apps and related content services. At the same time, most users prefer to use TVs with the least amount of input possible. With the number of choices available to users, it is important for app developers to provide quick and easy paths for users to discover and enjoy your content.The Android framework helps you provide a number of paths for users to discover your content, including recommendations on the home screen and searching within your app's content catalog.This class shows you how to help users discover your app's content through recommendations and in-app searching.Topics
- Recommending TV Content
- Learn how to recommend content for users so that it appears in the recommendations row on the home screen of a TV device.
- Making TV Apps Searchable
- Learn how to make your content searchable from the Android TV home screen.
- Searching within TV Apps
- Learn how to use a built-for-TV user interface for searching within your app.
No comments:
Post a Comment