This lesson teaches you to
Try It Out
Your TV input must provide Electronic Program Guide (EPG) data for at least one channel in its setup activity. You should also periodically update that data, with consideration for the size of the update and the processing thread that handles it. This lesson discusses creating and updating channel and program data on the system database with these considerations in mind.
Get Permission
In order for your TV input to work with EPG data, it must declare the read and write permissions in its Android manifest file as follows:
<uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" /> <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
Register Channels in the Database
The Android TV system database maintains records of channel data for TV inputs. In your setup
activity, for each of your channels, you must map your channel data to the following fields of the
TvContract.Channels
class:
COLUMN_DISPLAY_NAME
- the displayed name of the channelCOLUMN_DISPLAY_NUMBER
- the displayed channel numberCOLUMN_INPUT_ID
- the ID of the TV input serviceCOLUMN_SERVICE_TYPE
- the channel's service typeCOLUMN_TYPE
- the channel's broadcast standard typeCOLUMN_VIDEO_FORMAT
- the default video format for the channel
Although the TV input framework is generic enough to handle both traditional broadcast and over-the-top (OTT) content without any distinction, you may want to define the following columns in addition to those above to better identify traditional broadcast channels:
COLUMN_ORIGINAL_NETWORK_ID
- the television network IDCOLUMN_SERVICE_ID
- the service IDCOLUMN_TRANSPORT_STREAM_ID
- the transport stream ID
For internet streaming based TV inputs, assign your own values to the above accordingly so that each channel can be identified uniquely.
Pull your channel metadata (in XML, JSON, or whatever) from your backend server, and in your setup activity map the values to the system database as follows:
ContentValues values = new ContentValues(); values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.mNumber); values.put(Channels.COLUMN_DISPLAY_NAME, channel.mName); values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.mOriginalNetworkId); values.put(Channels.COLUMN_TRANSPORT_STREAM_ID, channel.mTransportStreamId); values.put(Channels.COLUMN_SERVICE_ID, channel.mServiceId); values.put(Channels.COLUMN_VIDEO_FORMAT, channel.mVideoFormat); Uri uri = context.getContentResolver().insert(TvContract.Channels.CONTENT_URI, values);
In the example above, channel
is an object which holds channel metadata from the
backend server.
Present Channel and Program Information
The system TV app presents channel and program information to users as they flip through channels, as shown in figure 1. To make sure the channel and program information works with the system TV app's channel and program information presenter, follow the guidelines below.
- Channel number (
COLUMN_DISPLAY_NUMBER
) - Icon
(
android:icon
in the TV input's manifest) - Program description (
COLUMN_SHORT_DESCRIPTION
) - Program title (
COLUMN_TITLE
) - Channel logo (
TvContract.Channels.Logo
)- Use the color #EEEEEE to match the surrounding text
- Don't include padding
- Poster art (
COLUMN_POSTER_ART_URI
)- Aspect ratio between 16:9 and 4:3
The system TV app provides the same information through the program guide, including poster art, as shown in figure 2.
Update Channel Data
When updating existing channel data, use the
update()
method instead of deleting and re-adding the data. You can identify the current version of the data
by using Channels.COLUMN_VERSION_NUMBER
and Programs.COLUMN_VERSION_NUMBER
when choosing the records to update.
Note: Adding channel data to the ContentProvider
can take time. Only add current programs (those within two hours of the current time) when you update,
and use a Sync Adapter to
update the rest of the channel data in the background. See the
Android TV Live TV Sample App for an example.
Batch Loading Channel Data
When updating the system database with a large amount of channel data, use the ContentResolver
applyBatch()
or
bulkInsert()
method. Here's an example using applyBatch()
:
ArrayList<ContentProviderOperation> ops = new ArrayList<>(); int programsCount = mChannelInfo.mPrograms.size(); for (int j = 0; j < programsCount; ++j) { ProgramInfo program = mChannelInfo.mPrograms.get(j); ops.add(ContentProviderOperation.newInsert( TvContract.Programs.CONTENT_URI) .withValues(programs.get(j)) .withValue(Programs.COLUMN_START_TIME_UTC_MILLIS, programStartSec * 1000) .withValue(Programs.COLUMN_END_TIME_UTC_MILLIS, (programStartSec + program.mDurationSec) * 1000) .build()); programStartSec = programStartSec + program.mDurationSec; if (j % 100 == 99 || j == programsCount - 1) { try { getContentResolver().applyBatch(TvContract.AUTHORITY, ops); } catch (RemoteException | OperationApplicationException e) { Log.e(TAG, "Failed to insert programs.", e); return; } ops.clear(); } }
Processing Channel Data Asynchronously
Data manipulation, such as fetching a stream from the server or accessing the database, should
not block the UI thread. Using an AsyncTask
is one
way to perform updates asynchronously. For example, when loading channel info from a backend server,
you can use AsyncTask
as follows:
private static class LoadTvInputTask extends AsyncTask<Uri, Void, Void>> { private Context mContext; public LoadTvInputTask(Context context) { mContext = context; } @Override protected Void doInBackground(Uri... uris) { try { fetchUri(uris[0]); } catch (IOException e) { Log.d(“LoadTvInputTask”, “fetchUri error”); } return null; } private void fetchUri(Uri videoUri) throws IOException { InputStream inputStream = null; try { inputStream = mContext.getContentResolver().openInputStream(videoUri); XmlPullParser parser = Xml.newPullParser(); try { parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); parser.setInput(inputStream, null); sTvInput = ChannelXMLParser.parseTvInput(parser); sSampleChannels = ChannelXMLParser.parseChannelXML(parser); } catch (XmlPullParserException e) { e.printStackTrace(); } } finally { if (inputStream != null) { inputStream.close(); } } } }
If you need to update EPG data on a regular basis, consider using
a
Sync Adapter or JobScheduler
to run the update process during idle time,
such as every day at 3:00 a.m. See the
Android TV live TV sample app for an example.
Other techniques to separate the data update tasks from the UI thread include using the
HandlerThread
class, or you may implement your own using Looper
and Handler
classes. See
Processes and Threads for more information.