1page.title=Managing User Interaction 2page.tags=tv, tif 3helpoutsWidget=true 4 5trainingnavtop=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 27channel and program information briefly before the information disappears. Other types of information, 28such as messages ("DO NOT ATTEMPT AT HOME"), subtitles, or ads may need to persist. As with any TV 29app, 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 37content's rating and parental control settings, and how your app behaves and informs the user when 38content is blocked or unavailable. This lesson describes how to develop your TV input's user 39experience 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 44the {@link android.media.tv.TvInputService.Session#onSetSurface(android.view.Surface) TvInputService.Session.onSetSurface()} 45method. Here's an example of how to use a {@link android.media.MediaPlayer} instance for playing 46content in the {@link android.view.Surface} object:</p> 47 48<pre> 49@Override 50public boolean onSetSurface(Surface surface) { 51 if (mPlayer != null) { 52 mPlayer.setSurface(surface); 53 } 54 mSurface = surface; 55 return true; 56} 57 58@Override 59public 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"> 68ExoPlayer</a>:</p> 69 70<pre> 71@Override 72public 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@Override 83public 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 96overlay 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)}, 98as in the following example:</p> 99 100<pre> 101@Override 102public 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@Override 114public 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<?xml version="1.0" encoding="utf-8"?> 135<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"> 141 142 <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"/> 151</FrameLayout> 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) 157onTune()} callback in the {@link android.media.tv.TvInputService.Session} object. The system TV 158app's parental controls determine what content displays, given the content rating. 159The following sections describe how to manage channel and program selection using the 160{@link android.media.tv.TvInputService.Session} <code>notify</code> methods that 161communicate 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 166video artifacts before your TV input renders the content. When you call {@link android.media.tv.TvInputService.Session#onTune(android.net.Uri) TvInputService.Session.onTune()}, 167you can prevent the video from being presented by calling {@link android.media.tv.TvInputService.Session#notifyVideoUnavailable(int) TvInputService.Session.notifyVideoUnavailable()} 168and passing the {@link android.media.tv.TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING} constant, as 169shown in the following example.</p> 170 171<pre> 172@Override 173public 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()} 189to allow the video to display, like so:</p> 190 191<pre> 192@Override 193public 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 200visually 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 203with {@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()} 209and {@link android.media.tv.TvInputManager#isRatingBlocked(android.media.tv.TvContentRating)}. You 210might also want to make sure the content's {@link android.media.tv.TvContentRating} is included in a 211set of currently allowed content ratings. These considerations are shown in the following sample.</p> 212 213<pre> 214private 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 236app by calling the 237{@link android.media.tv.TvInputService.Session} method {@link android.media.tv.TvInputService.Session#notifyContentAllowed() notifyContentAllowed()} 238or 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 243the {@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> 245method, as shown here:</p> 246 247<pre> 248TvContentRating 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 258as 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 262is 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> 264again 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 269track is available for a given track type; for example, subtitles in different languages. Your TV 270input responds to the 271{@link android.media.tv.TvInputService.Session#onSelectTrack(int, java.lang.String) onSelectTrack()} 272call 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> 275is passed as the track ID, this <em>deselects</em> the track.</p> 276 277<pre> 278@Override 279public 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