305 lines
12 KiB
Plaintext
305 lines
12 KiB
Plaintext
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>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|