* commit '5f78af44bd9627dace7db4ce47fdf61cf8d78b42': docs: Building Live TV Apps
This commit is contained in:
BIN
docs/html/images/tv/channel-info.png
Normal file
BIN
docs/html/images/tv/channel-info.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
BIN
docs/html/images/tv/do-not-attempt.png
Normal file
BIN
docs/html/images/tv/do-not-attempt.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 195 KiB |
BIN
docs/html/images/tv/prog-guide.png
Normal file
BIN
docs/html/images/tv/prog-guide.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
BIN
docs/html/images/tv/tvinput-life.png
Normal file
BIN
docs/html/images/tv/tvinput-life.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
@@ -998,10 +998,25 @@ include the action bar on devices running Android 2.1 or higher."
|
|||||||
Building TV Games</a>
|
Building TV Games</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li class="nav-section">
|
||||||
|
<div class="nav-section-header">
|
||||||
<a href="<?cs var:toroot ?>training/tv/tif/index.html"
|
<a href="<?cs var:toroot ?>training/tv/tif/index.html"
|
||||||
description="How to build Live TV apps.">
|
description="How to build Live TV apps.">
|
||||||
Building Live TV Apps</a>
|
Building Live TV Apps</a>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="<?cs var:toroot ?>training/tv/tif/tvinput.html">
|
||||||
|
Developing a TV Input Service</a>
|
||||||
|
<li>
|
||||||
|
<a href="<?cs var:toroot ?>training/tv/tif/channel.html">
|
||||||
|
Working with Channel Data</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="<?cs var:toroot ?>training/tv/tif/ui.html">
|
||||||
|
Managing User Interaction</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
239
docs/html/training/tv/tif/channel.jd
Normal file
239
docs/html/training/tv/tif/channel.jd
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
page.title=Working with Channel Data
|
||||||
|
page.tags=tv, tif
|
||||||
|
helpoutsWidget=true
|
||||||
|
|
||||||
|
trainingnavtop=true
|
||||||
|
|
||||||
|
@jd:body
|
||||||
|
|
||||||
|
<div id="tb-wrapper">
|
||||||
|
<div id="tb">
|
||||||
|
<h2>This lesson teaches you to</h2>
|
||||||
|
<ol>
|
||||||
|
<li><a href="#permission">Get Permission</a></li>
|
||||||
|
<li><a href="#register">Register Channels in the Database</a></li>
|
||||||
|
<li><a href="#update">Update Channel Data</a></li>
|
||||||
|
</ol>
|
||||||
|
<h2>Try It Out</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs">
|
||||||
|
TV Input Service sample app</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>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.</p>
|
||||||
|
|
||||||
|
<p> </p>
|
||||||
|
|
||||||
|
<h2 id="permission">Get Permission</h2>
|
||||||
|
|
||||||
|
<p>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:</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
|
||||||
|
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2 id="register">Register Channels in the Database</h2>
|
||||||
|
|
||||||
|
<p>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
|
||||||
|
{@link android.media.tv.TvContract.Channels} class:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>{@link android.media.tv.TvContract.Channels#COLUMN_DISPLAY_NAME} - the displayed name of the
|
||||||
|
channel</li>
|
||||||
|
<li>{@link android.media.tv.TvContract.Channels#COLUMN_DISPLAY_NUMBER} - the displayed channel
|
||||||
|
number</li>
|
||||||
|
<li>{@link android.media.tv.TvContract.Channels#COLUMN_INPUT_ID} - the ID of the TV input service</li>
|
||||||
|
<li>{@link android.media.tv.TvContract.Channels#COLUMN_SERVICE_TYPE} - the channel's service type</li>
|
||||||
|
<li>{@link android.media.tv.TvContract.Channels#COLUMN_TYPE} - the channel's broadcast standard
|
||||||
|
type</li>
|
||||||
|
<li>{@link android.media.tv.TvContract.Channels#COLUMN_VIDEO_FORMAT} - the default video format
|
||||||
|
for the channel</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>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:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>{@link android.media.tv.TvContract.Channels#COLUMN_ORIGINAL_NETWORK_ID} - the television
|
||||||
|
network ID</li>
|
||||||
|
<li>{@link android.media.tv.TvContract.Channels#COLUMN_SERVICE_ID} - the service ID</li>
|
||||||
|
<li>{@link android.media.tv.TvContract.Channels#COLUMN_TRANSPORT_STREAM_ID} - the transport stream
|
||||||
|
ID</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>For internet streaming based TV inputs, assign your own values to the above accordingly so that
|
||||||
|
each channel can be identified uniquely.</p>
|
||||||
|
|
||||||
|
<p>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:</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
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);
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>In the example above, <code>channel</code> is an object which holds channel metadata from the
|
||||||
|
backend server.</p>
|
||||||
|
|
||||||
|
<h3 id="art">Present Channel and Program Information</h2>
|
||||||
|
|
||||||
|
<p>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.</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li><strong>Channel number</strong> ({@link android.media.tv.TvContract.Channels#COLUMN_DISPLAY_NUMBER})
|
||||||
|
<li><strong>Icon</strong>
|
||||||
|
(<a href="guide/topics/manifest/application-element.html#icon"><code>android:icon</code></a> in the
|
||||||
|
TV input's manifest)</li>
|
||||||
|
<li><strong>Program description</strong> ({@link android.media.tv.TvContract.Programs#COLUMN_SHORT_DESCRIPTION})
|
||||||
|
<li><strong>Program title</strong> ({@link android.media.tv.TvContract.Programs#COLUMN_TITLE})</li>
|
||||||
|
<li><strong>Channel logo</strong> ({@link android.media.tv.TvContract.Channels.Logo})
|
||||||
|
<ul>
|
||||||
|
<li>Use the color #EEEEEE to match the surrounding text</li>
|
||||||
|
<li>Don't include padding
|
||||||
|
</ul></li>
|
||||||
|
<li><strong>Poster art</strong> ({@link android.media.tv.TvContract.Programs#COLUMN_POSTER_ART_URI})
|
||||||
|
<ul>
|
||||||
|
<li>Aspect ratio between 16:9 and 4:3</li>
|
||||||
|
</ul>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<img src="{@docRoot}images/tv/channel-info.png" id="figure1">
|
||||||
|
<p class="img-caption">
|
||||||
|
<strong>Figure 1.</strong> The system TV app channel and program information presenter.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>The system TV app provides the same information through the program guide, including poster art,
|
||||||
|
as shown in figure 2.</p>
|
||||||
|
|
||||||
|
<img src="{@docRoot}images/tv/prog-guide.png" id="figure2">
|
||||||
|
<p class="img-caption">
|
||||||
|
<strong>Figure 2.</strong> The system TV app program guide.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="update">Update Channel Data</h2>
|
||||||
|
|
||||||
|
<p>When updating existing channel data, use the
|
||||||
|
{@link android.content.ContentProvider#update(android.net.Uri, android.content.ContentValues,
|
||||||
|
java.lang.String, java.lang.String[]) update()}
|
||||||
|
method instead of deleting and re-adding the data. You can identify the current version of the data
|
||||||
|
by using {@link android.media.tv.TvContract.Channels#COLUMN_VERSION_NUMBER Channels.COLUMN_VERSION_NUMBER}
|
||||||
|
and {@link android.media.tv.TvContract.Programs#COLUMN_VERSION_NUMBER Programs.COLUMN_VERSION_NUMBER}
|
||||||
|
when choosing the records to update.</p>
|
||||||
|
|
||||||
|
<p class="note"><strong>Note:</strong> Adding channel data to the {@link android.content.ContentProvider}
|
||||||
|
can take time. Only add current programs (those within two hours of the current time) when you update,
|
||||||
|
and use a <a href="{@docRoot}training/sync-adapters/creating-sync-adapter.html">Sync Adapter</a> to
|
||||||
|
update the rest of the channel data in the background. See the <a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs/blob/master/app/src/main/java/com/example/android/sampletvinput/syncadapter/SyncAdapter.java">
|
||||||
|
Android TV Live TV Sample App</a> for an example.</p>
|
||||||
|
|
||||||
|
<h3 id="batch">Batch Loading Channel Data</h3>
|
||||||
|
|
||||||
|
<p>When updating the system database with a large amount of channel data, use the {@link android.content.ContentResolver}
|
||||||
|
{@link android.content.ContentResolver#applyBatch applyBatch()}
|
||||||
|
or
|
||||||
|
{@link android.content.ContentResolver#bulkInsert(android.net.Uri, android.content.ContentValues[]) bulkInsert()}
|
||||||
|
method. Here's an example using {@link android.content.ContentResolver#applyBatch applyBatch()}:<p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
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 {
|
||||||
|
<strong>getContentResolver().applyBatch(TvContract.AUTHORITY, ops);</strong>
|
||||||
|
} catch (RemoteException | OperationApplicationException e) {
|
||||||
|
Log.e(TAG, "Failed to insert programs.", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ops.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h3 id="async">Processing Channel Data Asynchronously</h3>
|
||||||
|
|
||||||
|
<p>Data manipulation, such as fetching a stream from the server or accessing the database, should
|
||||||
|
not block the UI thread. Using an {@link android.os.AsyncTask} is one
|
||||||
|
way to perform updates asynchronously. For example, when loading channel info from a backend server,
|
||||||
|
you can use {@link android.os.AsyncTask} as follows:</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>If you need to update EPG data on a regular basis, consider using
|
||||||
|
a <a href="{@docRoot}training/sync-adapters/creating-sync-adapter.html">
|
||||||
|
Sync Adapter</a> or {@link android.app.job.JobScheduler} to run the update process during idle time,
|
||||||
|
such as every day at 3:00 a.m. See the <a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs/blob/master/app/src/main/java/com/example/android/sampletvinput/syncadapter/SyncAdapter.java">
|
||||||
|
Android TV live TV sample app</a> for an example.</p>
|
||||||
|
|
||||||
|
<p>Other techniques to separate the data update tasks from the UI thread include using the
|
||||||
|
{@link android.os.HandlerThread} class, or you may implement your own using {@link android.os.Looper}
|
||||||
|
and {@link android.os.Handler} classes. See <a href="{@docRoot}guide/components/processes-and-threads.html">
|
||||||
|
Processes and Threads</a> for more information.</p>
|
||||||
@@ -1,17 +1,26 @@
|
|||||||
page.title=Building Live TV Apps
|
page.title=Building Live TV Apps
|
||||||
page.tags=tv, tif
|
page.tags=tv, tif
|
||||||
helpoutsWidget=true
|
helpoutsWidget=true
|
||||||
page.article=true
|
startpage=true
|
||||||
|
|
||||||
@jd:body
|
@jd:body
|
||||||
|
|
||||||
<div id="tb-wrapper">
|
<div id="tb-wrapper">
|
||||||
<div id="tb">
|
<div id="tb">
|
||||||
|
<h2>Dependencies and Prerequisites</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Android 5.0 (API level 21) or higher</li>
|
||||||
|
</ul>
|
||||||
<h2>You should also read</h2>
|
<h2>You should also read</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{@docRoot}reference/android/media/tv/package-summary.html">
|
<li><a href="{@docRoot}reference/android/media/tv/package-summary.html">
|
||||||
android.media.tv</a></li>
|
android.media.tv</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<h2>Try It Out</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs">
|
||||||
|
TV Input Service sample app</a></li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -44,6 +53,17 @@ page.article=true
|
|||||||
Building a TV input service for your content can help make it more accessible on TV devices.
|
Building a TV input service for your content can help make it more accessible on TV devices.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>For more information about TV Input Framework, see the
|
<h2>Topics</h2>
|
||||||
<a href="{@docRoot}reference/android/media/tv/package-summary.html">android.media.tv</a>
|
|
||||||
reference.</p>
|
<dl>
|
||||||
|
<dt><b><a href="tvinput.html">Developing a TV Input Service</a></b></dt>
|
||||||
|
<dd>Learn how to develop a TV input service, which works with the system TV app.</dd>
|
||||||
|
|
||||||
|
<dt><b><a href="channel.html">Working with Channel Data</a></b></dt>
|
||||||
|
<dd>Learn how to describe channel and program data for the system.</dd>
|
||||||
|
|
||||||
|
<dt><b><a href="ui.html">Managing User Interaction</a></b></dt>
|
||||||
|
<dd>Learn how to present overlays, manage content availability, and handle content selection.</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
177
docs/html/training/tv/tif/tvinput.jd
Normal file
177
docs/html/training/tv/tif/tvinput.jd
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
page.title=Developing a TV Input Service
|
||||||
|
page.tags=tv, tif
|
||||||
|
helpoutsWidget=true
|
||||||
|
|
||||||
|
trainingnavtop=true
|
||||||
|
|
||||||
|
@jd:body
|
||||||
|
|
||||||
|
<div id="tb-wrapper">
|
||||||
|
<div id="tb">
|
||||||
|
<h2>This lesson teaches you to</h2>
|
||||||
|
<ol>
|
||||||
|
<li><a href="#manifest">Declare Your TV Input Service in the Manifest</a></li>
|
||||||
|
<li><a href="#tvinput">Define Your TV Input Service</a></li>
|
||||||
|
<li><a href="#setup">Define Setup and Settings Activities</a></li>
|
||||||
|
</ol>
|
||||||
|
<h2>You should also read</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="{@docRoot}reference/android/media/tv/package-summary.html">
|
||||||
|
android.media.tv</a></li>
|
||||||
|
<li><a class="external-lin" href="http://source.android.com/devices/tv/index.html">
|
||||||
|
TV Input Framework</a></li>
|
||||||
|
</ul>
|
||||||
|
<h2>Try It Out</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs">
|
||||||
|
TV Input Service sample app</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>A TV input service represents a media stream source, and lets you present your media content in a
|
||||||
|
linear, broadcast TV fashion as channels and programs. With the TV input service, you can provide
|
||||||
|
parental controls, program guide information, and content ratings. The TV input service works
|
||||||
|
with the Android system TV app, developed for the device and immutable by third-party apps, which
|
||||||
|
ultimately controls and presents content on the TV. See
|
||||||
|
<a class="external-link" href="http://source.android.com/devices/tv/index.html">
|
||||||
|
TV Input Framework</a> for more information about the framework architecture and its components.</p>
|
||||||
|
|
||||||
|
<p>To develop a TV input service, you implement the following components:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>{@link android.media.tv.TvInputService} provides long-running and background availability for
|
||||||
|
the TV input</li>
|
||||||
|
<li>{@link android.media.tv.TvInputService.Session} maintains the TV input state and communicates
|
||||||
|
with the hosting app</li>
|
||||||
|
<li>{@link android.media.tv.TvContract} describes the channels and programs available to the TV
|
||||||
|
input</li>
|
||||||
|
<li>{@link android.media.tv.TvContract.Channels} represents information about a TV channel</li>
|
||||||
|
<li>{@link android.media.tv.TvContract.Programs} describes a TV program with data such as program
|
||||||
|
title and start time</li>
|
||||||
|
<li>{@link android.media.tv.TvTrackInfo} represents an audio, video, or subtitle track</li>
|
||||||
|
<li>{@link android.media.tv.TvContentRating} describes a content rating, allows for custom content
|
||||||
|
rating schemes</li>
|
||||||
|
<li>{@link android.media.tv.TvInputManager} provides an API to the system TV app and manages
|
||||||
|
the interaction with TV inputs and apps</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="manifest">Declare Your TV Input Service in the Manifest</h2>
|
||||||
|
|
||||||
|
<p>Your app manifest must declare your {@link android.media.tv.TvInputService}. Within that
|
||||||
|
declaration, specify the {@link android.Manifest.permission#BIND_TV_INPUT} permission to allow the
|
||||||
|
service to connect the TV input to the system. A system service (<code>TvInputManagerService</code>)
|
||||||
|
performs the binding and has that permission. The system TV app sends requests to TV input services
|
||||||
|
via the {@link android.media.tv.TvInputManager} interface. The service declaration must also
|
||||||
|
include an intent filter that specifies the {@link android.media.tv.TvInputService}
|
||||||
|
as the action to perform with the intent. Also within the service declaration, declare the service
|
||||||
|
meta data in a separate XML resource. The service declaration, the intent filter and the service
|
||||||
|
meta data are described in the following example.</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<service android:name="com.example.sampletvinput.SampleTvInput"
|
||||||
|
android:label="@string/sample_tv_input_label"
|
||||||
|
android:permission="android.permission.BIND_TV_INPUT">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.media.tv.TvInputService" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data android:name="android.media.tv.input"
|
||||||
|
android:resource="@xml/sample_tv_input" />
|
||||||
|
</service>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>Define the service meta data in separate XML file, as shown in the following example. The service
|
||||||
|
meta data must include a setup interface that describes the TV input's initial configuration and
|
||||||
|
channel scan. Also, the service meta data may (optionally) describe a settings activity for users to
|
||||||
|
modify the TV input's behavior. The service meta data file is located in the XML resources directory
|
||||||
|
for your application and must match the name of the resource in the manifest. Using the example
|
||||||
|
manifest entries above, you would create an XML file in the location
|
||||||
|
<code>res/xml/sample_tv_input.xml</code>, with the following contents:</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
<!-- Required: activity for setting up the input -->
|
||||||
|
android:setupActivity="com.example.sampletvinput.SampleTvInputSetupActivity"
|
||||||
|
<!-- Optional: activity for controlling the settings -->
|
||||||
|
android:settingsActivity="com.example.sampletvinput.SampleTvInputSettingsActivity" />
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2 id="tvinput">Define Your TV Input Service</h2>
|
||||||
|
|
||||||
|
<div class="figure">
|
||||||
|
<img id="tvinputlife" src="{@docRoot}images/tv/tvinput-life.png" alt=""/>
|
||||||
|
<p class="img-caption"><strong>Figure 1.</strong>TvInputService lifecycle.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>For your service, you extend the {@link android.media.tv.TvInputService} class. A
|
||||||
|
{@link android.media.tv.TvInputService} implementation is a
|
||||||
|
<a href="{@docRoot}guide/components/bound-services.html">bound service</a> where the system service
|
||||||
|
(<code>TvInputManagerService</code>) is the client that binds to it. The service life cycle methods
|
||||||
|
you need to implement are illustrated in figure 1.</p>
|
||||||
|
|
||||||
|
<p>The {@link android.app.Service#onCreate()} method initializes and starts the
|
||||||
|
{@link android.os.HandlerThread} which provides a process thread separate from the UI thread to
|
||||||
|
handle system-driven actions. In the following example, the {@link android.app.Service#onCreate()}
|
||||||
|
method initializes the {@link android.view.accessibility.CaptioningManager} and prepares to handle
|
||||||
|
the {@link android.media.tv.TvInputManager#ACTION_BLOCKED_RATINGS_CHANGED}
|
||||||
|
and {@link android.media.tv.TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} actions. These
|
||||||
|
actions describe system intents fired when the user changes the parental control settings, and when
|
||||||
|
there is a change on the list of blocked ratings.</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
mHandlerThread = new HandlerThread(getClass()
|
||||||
|
.getSimpleName());
|
||||||
|
mHandlerThread.start();
|
||||||
|
mDbHandler = new Handler(mHandlerThread.getLooper());
|
||||||
|
mHandler = new Handler();
|
||||||
|
mCaptioningManager = (CaptioningManager)
|
||||||
|
getSystemService(Context.CAPTIONING_SERVICE);
|
||||||
|
|
||||||
|
setTheme(android.R.style.Theme_Holo_Light_NoActionBar);
|
||||||
|
|
||||||
|
mSessions = new ArrayList<BaseTvInputSessionImpl>();
|
||||||
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
|
intentFilter.addAction(TvInputManager
|
||||||
|
.ACTION_BLOCKED_RATINGS_CHANGED);
|
||||||
|
intentFilter.addAction(TvInputManager
|
||||||
|
.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED);
|
||||||
|
registerReceiver(mBroadcastReceiver, intentFilter);
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p> See <a href="{@docRoot}training/tv/tif/ui.html#control">
|
||||||
|
Control Content</a> for more information about working with blocked content and providing
|
||||||
|
parental control. See {@link android.media.tv.TvInputManager} for more system-driven actions that
|
||||||
|
you may want to handle in your TV input service.</p>
|
||||||
|
|
||||||
|
<p>The {@link android.media.tv.TvInputService} creates a
|
||||||
|
{@link android.media.tv.TvInputService.Session} that implements {@link android.os.Handler.Callback}
|
||||||
|
to handle player state changes. With {@link android.media.tv.TvInputService.Session#onSetSurface(android.view.Surface) onSetSurface()},
|
||||||
|
the {@link android.media.tv.TvInputService.Session} sets the {@link android.view.Surface} with the
|
||||||
|
video content. See <a href="{@docRoot}training/tv/tif/ui.html#surface">Integrate Player with Surface</a>
|
||||||
|
for more information about working with {@link android.view.Surface} to render video.</p>
|
||||||
|
|
||||||
|
<p>The {@link android.media.tv.TvInputService.Session} handles the
|
||||||
|
{@link android.media.tv.TvInputService.Session#onTune(android.net.Uri) onTune()}
|
||||||
|
event when the user selects a channel, and notifies the system TV app for changes in the content and
|
||||||
|
content meta data. These <code>notify()</code>code> methods are described in
|
||||||
|
<a href="{@docRoot}training/tv/tif/ui.html#control">
|
||||||
|
Control Content</a> and <a href="training/tv/tif/ui.html#track">Handle Track Selection</a> further
|
||||||
|
in this training.</p>
|
||||||
|
|
||||||
|
<h2 id="setup">Define Setup and Settings Activities</h2>
|
||||||
|
|
||||||
|
<p>The system TV app works with the setup and settings activities you define for your TV input. The
|
||||||
|
setup activity is required and must provide at least one channel record for the system database. The
|
||||||
|
system TV app will invoke the setup activity when it cannot find a channel for the TV input.
|
||||||
|
<p>The setup activity describes to the system TV app the channels made available through the TV
|
||||||
|
input, as demonstrated in the next lesson, <a href="{@docRoot}training/tv/tif/channel.html">Creating
|
||||||
|
and Updating Channel Data</a>.</p>
|
||||||
|
|
||||||
|
<p>The settings activity is optional. You can define a settings activity to turn on parental
|
||||||
|
controls, enable closed captions, set the display attributes, and so forth.</p>
|
||||||
|
|
||||||
|
|
||||||
304
docs/html/training/tv/tif/ui.jd
Normal file
304
docs/html/training/tv/tif/ui.jd
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
page.title=Managing User Interaction
|
||||||
|
page.tags=tv, tif
|
||||||
|
helpoutsWidget=true
|
||||||
|
|
||||||
|
trainingnavtop=true
|
||||||
|
|
||||||
|
@jd:body
|
||||||
|
|
||||||
|
<div id="tb-wrapper">
|
||||||
|
<div id="tb">
|
||||||
|
<h2>This lesson teaches you to</h2>
|
||||||
|
<ol>
|
||||||
|
<li><a href="#surface">Integrate Player with Surface</a></li>
|
||||||
|
<li><a href="#overlay">Use an Overlay</a></li>
|
||||||
|
<li><a href="#control">Control Content</a></li>
|
||||||
|
<li><a href="#track">Handle Track Selection</a></li>
|
||||||
|
</ol>
|
||||||
|
<h2>Try It Out</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs">
|
||||||
|
TV Input Service sample app</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>In the live TV experience the user changes channels and is presented with
|
||||||
|
channel and program information briefly before the information disappears. Other types of information,
|
||||||
|
such as messages ("DO NOT ATTEMPT AT HOME"), subtitles, or ads may need to persist. As with any TV
|
||||||
|
app, such information should not interfere with the program content playing on the screen.</p>
|
||||||
|
|
||||||
|
<img src="{@docRoot}images/tv/do-not-attempt.png" id="figure1">
|
||||||
|
<p class="img-caption">
|
||||||
|
<strong>Figure 1.</strong> An overlay message in a live TV app.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Also consider whether certain program content should be presented, given the
|
||||||
|
content's rating and parental control settings, and how your app behaves and informs the user when
|
||||||
|
content is blocked or unavailable. This lesson describes how to develop your TV input's user
|
||||||
|
experience for these considerations.</p>
|
||||||
|
|
||||||
|
<h2 id="surface">Integrate Player with Surface</h2>
|
||||||
|
|
||||||
|
<p>Your TV input must render video onto a {@link android.view.Surface} object, which is passed by
|
||||||
|
the {@link android.media.tv.TvInputService.Session#onSetSurface(android.view.Surface) TvInputService.Session.onSetSurface()}
|
||||||
|
method. Here's an example of how to use a {@link android.media.MediaPlayer} instance for playing
|
||||||
|
content in the {@link android.view.Surface} object:</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
@Override
|
||||||
|
public boolean onSetSurface(Surface surface) {
|
||||||
|
if (mPlayer != null) {
|
||||||
|
mPlayer.setSurface(surface);
|
||||||
|
}
|
||||||
|
mSurface = surface;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetStreamVolume(float volume) {
|
||||||
|
if (mPlayer != null) {
|
||||||
|
mPlayer.setVolume(volume, volume);
|
||||||
|
}
|
||||||
|
mVolume = volume;
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>Similarly, here's how to do it using <a href="{@docRoot}guide/topics/media/exoplayer.html">
|
||||||
|
ExoPlayer</a>:</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
@Override
|
||||||
|
public boolean onSetSurface(Surface surface) {
|
||||||
|
if (mPlayer != null) {
|
||||||
|
mPlayer.sendMessage(mVideoRenderer,
|
||||||
|
MediaCodecVideoTrackRenderer.MSG_SET_SURFACE,
|
||||||
|
surface);
|
||||||
|
}
|
||||||
|
mSurface = surface;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetStreamVolume(float volume) {
|
||||||
|
if (mPlayer != null) {
|
||||||
|
mPlayer.sendMessage(mAudioRenderer,
|
||||||
|
MediaCodecAudioTrackRenderer.MSG_SET_VOLUME,
|
||||||
|
volume);
|
||||||
|
}
|
||||||
|
mVolume = volume;
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2 id="overlay">Use an Overlay</h2>
|
||||||
|
|
||||||
|
<p>Use an overlay to display subtitles, messages, ads or MHEG-5 data broadcasts. By default, the
|
||||||
|
overlay is disabled. You can enable it when you create the session by calling
|
||||||
|
{@link android.media.tv.TvInputService.Session#setOverlayViewEnabled(boolean) TvInputService.Session.setOverlayViewEnabled(true)},
|
||||||
|
as in the following example:</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
@Override
|
||||||
|
public final Session onCreateSession(String inputId) {
|
||||||
|
BaseTvInputSessionImpl session = onCreateSessionInternal(inputId);
|
||||||
|
session.setOverlayViewEnabled(true);
|
||||||
|
mSessions.add(session);
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>Use a {@link android.view.View} object for the overlay, returned from {@link android.media.tv.TvInputService.Session#onCreateOverlayView() TvInputService.Session.onCreateOverlayView()}, as shown here:</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
@Override
|
||||||
|
public View onCreateOverlayView() {
|
||||||
|
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
|
||||||
|
View view = inflater.inflate(R.layout.overlayview, null);
|
||||||
|
mSubtitleView = (SubtitleView) view.findViewById(R.id.subtitles);
|
||||||
|
|
||||||
|
// Configure the subtitle view.
|
||||||
|
CaptionStyleCompat captionStyle;
|
||||||
|
float captionTextSize = getCaptionFontSize();
|
||||||
|
captionStyle = CaptionStyleCompat.createFromCaptionStyle(
|
||||||
|
mCaptioningManager.getUserStyle());
|
||||||
|
captionTextSize *= mCaptioningManager.getFontScale();
|
||||||
|
mSubtitleView.setStyle(captionStyle);
|
||||||
|
mSubtitleView.setTextSize(captionTextSize);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>The layout definition for the overlay might look something like this:</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.exoplayer.text.SubtitleView
|
||||||
|
android:id="@+id/subtitles"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|center_horizontal"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:visibility="invisible"/>
|
||||||
|
</FrameLayout>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2 id="control">Control Content</h2>
|
||||||
|
|
||||||
|
<p>When the user selects a channel, your TV input handles the {@link android.media.tv.TvInputService.Session#onTune(android.net.Uri)
|
||||||
|
onTune()} callback in the {@link android.media.tv.TvInputService.Session} object. The system TV
|
||||||
|
app's parental controls determine what content displays, given the content rating.
|
||||||
|
The following sections describe how to manage channel and program selection using the
|
||||||
|
{@link android.media.tv.TvInputService.Session} <code>notify</code> methods that
|
||||||
|
communicate with the system TV app.</p>
|
||||||
|
|
||||||
|
<h3 id="unavailable">Make Video Unavailable</h3>
|
||||||
|
|
||||||
|
<p>When the user changes the channel, you want to make sure the screen doesn't display any stray
|
||||||
|
video artifacts before your TV input renders the content. When you call {@link android.media.tv.TvInputService.Session#onTune(android.net.Uri) TvInputService.Session.onTune()},
|
||||||
|
you can prevent the video from being presented by calling {@link android.media.tv.TvInputService.Session#notifyVideoUnavailable(int) TvInputService.Session.notifyVideoUnavailable()}
|
||||||
|
and passing the {@link android.media.tv.TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING} constant, as
|
||||||
|
shown in the following example.</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
@Override
|
||||||
|
public boolean onTune(Uri channelUri) {
|
||||||
|
if (mSubtitleView != null) {
|
||||||
|
mSubtitleView.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
|
||||||
|
mUnblockedRatingSet.clear();
|
||||||
|
|
||||||
|
mDbHandler.removeCallbacks(mPlayCurrentProgramRunnable);
|
||||||
|
mPlayCurrentProgramRunnable = new PlayCurrentProgramRunnable(channelUri);
|
||||||
|
mDbHandler.post(mPlayCurrentProgramRunnable);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>Then, when the content is rendered to the {@link android.view.Surface}, you call
|
||||||
|
{@link android.media.tv.TvInputService.Session#notifyVideoAvailable() TvInputService.Session.notifyVideoAvailable()}
|
||||||
|
to allow the video to display, like so:</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
@Override
|
||||||
|
public void onDrawnToSurface(Surface surface) {
|
||||||
|
mFirstFrameDrawn = true;
|
||||||
|
notifyVideoAvailable();
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>This transition lasts only for fractions of a second, but presenting a blank screen is
|
||||||
|
visually better than allowing the picture to flash odd blips and jitters.</p>
|
||||||
|
|
||||||
|
<p>See also, <a href="#surface">Integrate Player with Surface</a> for more information about working
|
||||||
|
with {@link android.view.Surface} to render video.</p>
|
||||||
|
|
||||||
|
<h3 id="parental">Provide Parental Control</h3>
|
||||||
|
|
||||||
|
<p>To determine if a given content is blocked by parental controls and content rating, you check the
|
||||||
|
{@link android.media.tv.TvInputManager} class methods, {@link android.media.tv.TvInputManager#isParentalControlsEnabled()}
|
||||||
|
and {@link android.media.tv.TvInputManager#isRatingBlocked(android.media.tv.TvContentRating)}. You
|
||||||
|
might also want to make sure the content's {@link android.media.tv.TvContentRating} is included in a
|
||||||
|
set of currently allowed content ratings. These considerations are shown in the following sample.</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
private void checkContentBlockNeeded() {
|
||||||
|
if (mCurrentContentRating == null || !mTvInputManager.isParentalControlsEnabled()
|
||||||
|
|| !mTvInputManager.isRatingBlocked(mCurrentContentRating)
|
||||||
|
|| mUnblockedRatingSet.contains(mCurrentContentRating)) {
|
||||||
|
// Content rating is changed so we don't need to block anymore.
|
||||||
|
// Unblock content here explicitly to resume playback.
|
||||||
|
unblockContent(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mLastBlockedRating = mCurrentContentRating;
|
||||||
|
if (mPlayer != null) {
|
||||||
|
// Children restricted content might be blocked by TV app as well,
|
||||||
|
// but TIF should do its best not to show any single frame of blocked content.
|
||||||
|
releasePlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyContentBlocked(mCurrentContentRating);
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>Once you have determined if the content should or should not be blocked, notify the system TV
|
||||||
|
app by calling the
|
||||||
|
{@link android.media.tv.TvInputService.Session} method {@link android.media.tv.TvInputService.Session#notifyContentAllowed() notifyContentAllowed()}
|
||||||
|
or
|
||||||
|
{@link android.media.tv.TvInputService.Session#notifyContentBlocked(android.media.tv.TvContentRating) notifyContentBlocked()}
|
||||||
|
, as shown in the previous example.</p>
|
||||||
|
|
||||||
|
<p>Use the {@link android.media.tv.TvContentRating} class to generate the system-defined string for
|
||||||
|
the {@link android.media.tv.TvContract.Programs#COLUMN_CONTENT_RATING} with the
|
||||||
|
<code><a href="{@docRoot}reference/android/media/tv/TvContentRating.html#createRating(java.lang.String, java.lang.String, java.lang.String, java.lang.String...)">TvContentRating.createRating()</a></code>
|
||||||
|
method, as shown here:</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
TvContentRating rating = TvContentRating.createRating(
|
||||||
|
"com.android.tv",
|
||||||
|
"US_TV",
|
||||||
|
"US_TV_PG",
|
||||||
|
"US_TV_D", "US_TV_L");
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2 id="track">Handle Track Selection</h2>
|
||||||
|
|
||||||
|
<p>The {@link android.media.tv.TvTrackInfo} class holds information about media tracks such
|
||||||
|
as the track type (video, audio, or subtitle) and so forth. </p>
|
||||||
|
|
||||||
|
<p>The first time your TV input session is able to get track information, it should call
|
||||||
|
<code><a href="{@docRoot}reference/android/media/tv/TvInputService.Session.html#notifyTracksChanged(java.util.List<android.media.tv.TvTrackInfo>)">TvInputService.Session.notifyTracksChanged()</a></code> with a list of all tracks to update the system TV app. When there
|
||||||
|
is a change in track information, call
|
||||||
|
<code><a href="{@docRoot}reference/android/media/tv/TvInputService.Session.html#notifyTracksChanged(java.util.List<android.media.tv.TvTrackInfo>)">notifyTracksChanged()</a></code>
|
||||||
|
again to update the system.
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>The system TV app provides an interface for the user to select a specific track if more than one
|
||||||
|
track is available for a given track type; for example, subtitles in different languages. Your TV
|
||||||
|
input responds to the
|
||||||
|
{@link android.media.tv.TvInputService.Session#onSelectTrack(int, java.lang.String) onSelectTrack()}
|
||||||
|
call from the system TV app by calling
|
||||||
|
{@link android.media.tv.TvInputService.Session#notifyTrackSelected(int, java.lang.String) notifyTrackSelected()}
|
||||||
|
, as shown in the following example. Note that when <code>null</code>
|
||||||
|
is passed as the track ID, this <em>deselects</em> the track.</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
@Override
|
||||||
|
public boolean onSelectTrack(int type, String trackId) {
|
||||||
|
if (mPlayer != null) {
|
||||||
|
if (type == TvTrackInfo.TYPE_SUBTITLE) {
|
||||||
|
if (!mCaptionEnabled && trackId != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mSelectedSubtitleTrackId = trackId;
|
||||||
|
if (trackId == null) {
|
||||||
|
mSubtitleView.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mPlayer.selectTrack(type, trackId)) {
|
||||||
|
notifyTrackSelected(type, trackId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user