Home | History | Annotate | Download | only in tif
      1 page.title=Managing User Interaction
      2 page.tags=tv, tif
      3 helpoutsWidget=true
      4 
      5 trainingnavtop=true
      6 
      7 @jd:body
      8 
      9 <div id="tb-wrapper">
     10 <div id="tb">
     11   <h2>This lesson teaches you to</h2>
     12   <ol>
     13     <li><a href="#surface">Integrate Player with Surface</a></li>
     14     <li><a href="#overlay">Use an Overlay</a></li>
     15     <li><a href="#control">Control Content</a></li>
     16     <li><a href="#track">Handle Track Selection</a></li>
     17   </ol>
     18   <h2>Try It Out</h2>
     19   <ul>
     20     <li><a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs">
     21       TV Input Service sample app</a></li>
     22   </ul>
     23 </div>
     24 </div>
     25 
     26 <p>In the live TV experience the user changes channels and is presented with
     27 channel and program information briefly before the information disappears. Other types of information,
     28 such as messages ("DO NOT ATTEMPT AT HOME"), subtitles, or ads may need to persist. As with any TV
     29 app, such information should not interfere with the program content playing on the screen.</p>
     30 
     31 <img src="{@docRoot}images/tv/do-not-attempt.png" id="figure1">
     32 <p class="img-caption">
     33   <strong>Figure 1.</strong> An overlay message in a live TV app.
     34 </p>
     35 
     36 <p>Also consider whether certain program content should be presented, given the
     37 content's rating and parental control settings, and how your app behaves and informs the user when
     38 content is blocked or unavailable. This lesson describes how to develop your TV input's user
     39 experience for these considerations.</p>
     40 
     41 <h2 id="surface">Integrate Player with Surface</h2>
     42 
     43 <p>Your TV input must render video onto a {@link android.view.Surface} object, which is passed by
     44 the {@link android.media.tv.TvInputService.Session#onSetSurface(android.view.Surface) TvInputService.Session.onSetSurface()}
     45 method. Here's an example of how to use a {@link android.media.MediaPlayer} instance for playing
     46 content in the {@link android.view.Surface} object:</p>
     47 
     48 <pre>
     49 &#64;Override
     50 public boolean onSetSurface(Surface surface) {
     51     if (mPlayer != null) {
     52         mPlayer.setSurface(surface);
     53     }
     54     mSurface = surface;
     55     return true;
     56 }
     57 
     58 &#64;Override
     59 public void onSetStreamVolume(float volume) {
     60     if (mPlayer != null) {
     61         mPlayer.setVolume(volume, volume);
     62     }
     63     mVolume = volume;
     64 }
     65 </pre>
     66 
     67 <p>Similarly, here's how to do it using <a href="{@docRoot}guide/topics/media/exoplayer.html">
     68 ExoPlayer</a>:</p>
     69 
     70 <pre>
     71 &#64;Override
     72 public boolean onSetSurface(Surface surface) {
     73     if (mPlayer != null) {
     74         mPlayer.sendMessage(mVideoRenderer,
     75                 MediaCodecVideoTrackRenderer.MSG_SET_SURFACE,
     76                 surface);
     77     }
     78     mSurface = surface;
     79     return true;
     80 }
     81 
     82 &#64;Override
     83 public void onSetStreamVolume(float volume) {
     84     if (mPlayer != null) {
     85         mPlayer.sendMessage(mAudioRenderer,
     86                 MediaCodecAudioTrackRenderer.MSG_SET_VOLUME,
     87                 volume);
     88     }
     89     mVolume = volume;
     90 }
     91 </pre>
     92 
     93 <h2 id="overlay">Use an Overlay</h2>
     94 
     95 <p>Use an overlay to display subtitles, messages, ads or MHEG-5 data broadcasts. By default, the
     96 overlay is disabled. You can enable it when you create the session by calling
     97 {@link android.media.tv.TvInputService.Session#setOverlayViewEnabled(boolean) TvInputService.Session.setOverlayViewEnabled(true)},
     98 as in the following example:</p>
     99 
    100 <pre>
    101 &#64;Override
    102 public final Session onCreateSession(String inputId) {
    103     BaseTvInputSessionImpl session = onCreateSessionInternal(inputId);
    104     session.setOverlayViewEnabled(true);
    105     mSessions.add(session);
    106     return session;
    107 }
    108 </pre>
    109 
    110 <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>
    111 
    112 <pre>
    113 &#64;Override
    114 public View onCreateOverlayView() {
    115     LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
    116     View view = inflater.inflate(R.layout.overlayview, null);
    117     mSubtitleView = (SubtitleView) view.findViewById(R.id.subtitles);
    118 
    119     // Configure the subtitle view.
    120     CaptionStyleCompat captionStyle;
    121     float captionTextSize = getCaptionFontSize();
    122     captionStyle = CaptionStyleCompat.createFromCaptionStyle(
    123             mCaptioningManager.getUserStyle());
    124     captionTextSize *= mCaptioningManager.getFontScale();
    125     mSubtitleView.setStyle(captionStyle);
    126     mSubtitleView.setTextSize(captionTextSize);
    127     return view;
    128 }
    129 </pre>
    130 
    131 <p>The layout definition for the overlay might look something like this:</p>
    132 
    133 <pre>
    134 &lt;?xml version="1.0" encoding="utf-8"?&gt;
    135 &lt;FrameLayout
    136     xmlns:android="http://schemas.android.com/apk/res/android"
    137     xmlns:tools="http://schemas.android.com/tools"
    138 
    139     android:layout_width="match_parent"
    140     android:layout_height="match_parent"&gt;
    141 
    142     &lt;com.google.android.exoplayer.text.SubtitleView
    143         android:id="@+id/subtitles"
    144         android:layout_width="wrap_content"
    145         android:layout_height="wrap_content"
    146         android:layout_gravity="bottom|center_horizontal"
    147         android:layout_marginLeft="16dp"
    148         android:layout_marginRight="16dp"
    149         android:layout_marginBottom="32dp"
    150         android:visibility="invisible"/&gt;
    151 &lt;/FrameLayout&gt;
    152 </pre>
    153 
    154 <h2 id="control">Control Content</h2>
    155 
    156 <p>When the user selects a channel, your TV input handles the {@link android.media.tv.TvInputService.Session#onTune(android.net.Uri)
    157 onTune()} callback in the {@link android.media.tv.TvInputService.Session} object. The system TV
    158 app's parental controls determine what content displays, given the content rating.
    159 The following sections describe how to manage channel and program selection using the
    160 {@link android.media.tv.TvInputService.Session} <code>notify</code> methods that
    161 communicate with the system TV app.</p>
    162 
    163 <h3 id="unavailable">Make Video Unavailable</h3>
    164 
    165 <p>When the user changes the channel, you want to make sure the screen doesn't display any stray
    166 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()},
    167 you can prevent the video from being presented by calling {@link android.media.tv.TvInputService.Session#notifyVideoUnavailable(int) TvInputService.Session.notifyVideoUnavailable()}
    168 and passing the {@link android.media.tv.TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING} constant, as
    169 shown in the following example.</p>
    170 
    171 <pre>
    172 &#64;Override
    173 public boolean onTune(Uri channelUri) {
    174     if (mSubtitleView != null) {
    175         mSubtitleView.setVisibility(View.INVISIBLE);
    176     }
    177     notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
    178     mUnblockedRatingSet.clear();
    179 
    180     mDbHandler.removeCallbacks(mPlayCurrentProgramRunnable);
    181     mPlayCurrentProgramRunnable = new PlayCurrentProgramRunnable(channelUri);
    182     mDbHandler.post(mPlayCurrentProgramRunnable);
    183     return true;
    184 }
    185 </pre>
    186 
    187 <p>Then, when the content is rendered to the {@link android.view.Surface}, you call
    188 {@link android.media.tv.TvInputService.Session#notifyVideoAvailable() TvInputService.Session.notifyVideoAvailable()}
    189 to allow the video to display, like so:</p>
    190 
    191 <pre>
    192 &#64;Override
    193 public void onDrawnToSurface(Surface surface) {
    194     mFirstFrameDrawn = true;
    195     notifyVideoAvailable();
    196 }
    197 </pre>
    198 
    199 <p>This transition lasts only for fractions of a second, but presenting a blank screen is
    200 visually better than allowing the picture to flash odd blips and jitters.</p>
    201 
    202 <p>See also, <a href="#surface">Integrate Player with Surface</a> for more information about working
    203 with {@link android.view.Surface} to render video.</p>
    204 
    205 <h3 id="parental">Provide Parental Control</h3>
    206 
    207 <p>To determine if a given content is blocked by parental controls and content rating, you check the
    208 {@link android.media.tv.TvInputManager} class methods, {@link android.media.tv.TvInputManager#isParentalControlsEnabled()}
    209 and {@link android.media.tv.TvInputManager#isRatingBlocked(android.media.tv.TvContentRating)}. You
    210 might also want to make sure the content's {@link android.media.tv.TvContentRating} is included in a
    211 set of currently allowed content ratings. These considerations are shown in the following sample.</p>
    212 
    213 <pre>
    214 private void checkContentBlockNeeded() {
    215     if (mCurrentContentRating == null || !mTvInputManager.isParentalControlsEnabled()
    216             || !mTvInputManager.isRatingBlocked(mCurrentContentRating)
    217             || mUnblockedRatingSet.contains(mCurrentContentRating)) {
    218         // Content rating is changed so we don't need to block anymore.
    219         // Unblock content here explicitly to resume playback.
    220         unblockContent(null);
    221         return;
    222     }
    223 
    224     mLastBlockedRating = mCurrentContentRating;
    225     if (mPlayer != null) {
    226         // Children restricted content might be blocked by TV app as well,
    227         // but TIF should do its best not to show any single frame of blocked content.
    228         releasePlayer();
    229     }
    230 
    231     notifyContentBlocked(mCurrentContentRating);
    232 }
    233 </pre>
    234 
    235 <p>Once you have determined if the content should or should not be blocked, notify the system TV
    236 app by calling the
    237 {@link android.media.tv.TvInputService.Session} method {@link android.media.tv.TvInputService.Session#notifyContentAllowed() notifyContentAllowed()}
    238 or
    239 {@link android.media.tv.TvInputService.Session#notifyContentBlocked(android.media.tv.TvContentRating) notifyContentBlocked()}
    240 , as shown in the previous example.</p>
    241 
    242 <p>Use the {@link android.media.tv.TvContentRating} class to generate the system-defined string for
    243 the {@link android.media.tv.TvContract.Programs#COLUMN_CONTENT_RATING} with the
    244 <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>
    245 method, as shown here:</p>
    246 
    247 <pre>
    248 TvContentRating rating = TvContentRating.createRating(
    249     "com.android.tv",
    250     "US_TV",
    251     "US_TV_PG",
    252     "US_TV_D", "US_TV_L");
    253 </pre>
    254 
    255 <h2 id="track">Handle Track Selection</h2>
    256 
    257 <p>The {@link android.media.tv.TvTrackInfo} class holds information about media tracks such
    258 as the track type (video, audio, or subtitle) and so forth. </p>
    259 
    260 <p>The first time your TV input session is able to get track information, it should call
    261 <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
    262 is a change in track information, call
    263 <code><a href="{@docRoot}reference/android/media/tv/TvInputService.Session.html#notifyTracksChanged(java.util.List<android.media.tv.TvTrackInfo>)">notifyTracksChanged()</a></code>
    264 again to update the system.
    265 
    266 </p>
    267 
    268 <p>The system TV app provides an interface for the user to select a specific track if more than one
    269 track is available for a given track type; for example, subtitles in different languages. Your TV
    270 input responds to the
    271 {@link android.media.tv.TvInputService.Session#onSelectTrack(int, java.lang.String) onSelectTrack()}
    272 call from the system TV app by calling
    273 {@link android.media.tv.TvInputService.Session#notifyTrackSelected(int, java.lang.String) notifyTrackSelected()}
    274 , as shown in the following example. Note that when <code>null</code>
    275 is passed as the track ID, this <em>deselects</em> the track.</p>
    276 
    277 <pre>
    278 &#64;Override
    279 public boolean onSelectTrack(int type, String trackId) {
    280     if (mPlayer != null) {
    281         if (type == TvTrackInfo.TYPE_SUBTITLE) {
    282             if (!mCaptionEnabled && trackId != null) {
    283                 return false;
    284             }
    285             mSelectedSubtitleTrackId = trackId;
    286             if (trackId == null) {
    287                 mSubtitleView.setVisibility(View.INVISIBLE);
    288             }
    289         }
    290         if (mPlayer.selectTrack(type, trackId)) {
    291             notifyTrackSelected(type, trackId);
    292             return true;
    293         }
    294     }
    295     return false;
    296 }
    297 </pre>
    298 
    299 
    300 
    301 
    302 
    303 
    304 
    305