跳转到相应内容

最常访问

最近访问

navigation

Managing TV User Interaction

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.

Figure 1. An overlay message in a live TV app.

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.

Integrate Player with Surface

Your TV input must render video onto a Surface object, which is passed by the TvInputService.Session.onSetSurface() method. Here's an example of how to use a MediaPlayer instance for playing content in the Surface object:

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

Similarly, here's how to do it using ExoPlayer:

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

Use an Overlay

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 TvInputService.Session.setOverlayViewEnabled(true), as in the following example:

@Override
public final Session onCreateSession(String inputId) {
    BaseTvInputSessionImpl session = onCreateSessionInternal(inputId);
    session.setOverlayViewEnabled(true);
    mSessions.add(session);
    return session;
}

Use a View object for the overlay, returned from TvInputService.Session.onCreateOverlayView(), as shown here:

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

The layout definition for the overlay might look something like this:

<?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>

Control Content

When the user selects a channel, your TV input handles the onTune() callback in the 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 TvInputService.Session notify methods that communicate with the system TV app.

Make Video Unavailable

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 TvInputService.Session.onTune(), you can prevent the video from being presented by calling TvInputService.Session.notifyVideoUnavailable() and passing the VIDEO_UNAVAILABLE_REASON_TUNING constant, as shown in the following example.

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

Then, when the content is rendered to the Surface, you call TvInputService.Session.notifyVideoAvailable() to allow the video to display, like so:

@Override
public void onDrawnToSurface(Surface surface) {
    mFirstFrameDrawn = true;
    notifyVideoAvailable();
}

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.

See also, Integrate Player with Surface for more information about working with Surface to render video.

Provide Parental Control

To determine if a given content is blocked by parental controls and content rating, you check the TvInputManager class methods, isParentalControlsEnabled() and isRatingBlocked(android.media.tv.TvContentRating). You might also want to make sure the content's TvContentRating is included in a set of currently allowed content ratings. These considerations are shown in the following sample.

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

Once you have determined if the content should or should not be blocked, notify the system TV app by calling the TvInputService.Session method notifyContentAllowed() or notifyContentBlocked() , as shown in the previous example.

Use the TvContentRating class to generate the system-defined string for the COLUMN_CONTENT_RATING with the TvContentRating.createRating() method, as shown here:

TvContentRating rating = TvContentRating.createRating(
    "com.android.tv",
    "US_TV",
    "US_TV_PG",
    "US_TV_D", "US_TV_L");

Handle Track Selection

The TvTrackInfo class holds information about media tracks such as the track type (video, audio, or subtitle) and so forth.

The first time your TV input session is able to get track information, it should call TvInputService.Session.notifyTracksChanged() with a list of all tracks to update the system TV app. When there is a change in track information, call notifyTracksChanged() again to update the system.

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 onSelectTrack() call from the system TV app by calling notifyTrackSelected() , as shown in the following example. Note that when null is passed as the track ID, this deselects the track.

@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;
}
此网站会使用 Cookie 来存储您在此网站上指定的语言和显示选项偏好设置。

获取最新的 Android Developers 资讯和提示,助您在 Google Play 上取得成功。

* 必填字段

成功!

在微信上关注 Google Developers

要以浏览此网站吗?

您请求访问的是网页,但是您为此网站设置的语言偏好为

要更改您的语言偏好设置并以浏览此网站吗?如果以后您想要更改语言偏好设置,请使用每个页面底部的语言菜单。

该类需要 或更高的 API 级别

此文档已被隐藏,因为您为该文档选择的 API 级别是 。您可以使用左侧导航栏上方的选择器来更改文档的 API 级别。

要详细了解如何根据您的应用需求指定 API 级别,请参阅支持不同平台版本

Take a short survey?
Help us improve the Android developer experience. (April 2018 — Developer Survey)