Step 4: Playback in Fire TV UI
Live TV has the ability to preview playback whenever a customer focuses on one of the tiles in a browse row. This is a convenient and quick way to preview content.
- Create
TvInputService.Session
- Identify the Correct Channel
- Play the Channel Content in Preview Playback
- Checkpoint - Preview Playback in Browse
- Troubleshooting
- I don't see the
onTune()
callback being triggered when focusing on my channel card - I can see
onTune()
being called, but the playback preview doesn’t start - After enabling PCON, I see the preview playback still playing in the browse section
- After enabling PCON, I don't see the preview playback nor the poster image. I only see a black background.
- I don't see the
- Next Steps
Integration with preview playback requires that you play channel content within a player that can use a Surface provided by the Live TV app.
Create TvInputService.Session
When a user selects a specific channel, the TvInputService
class will be called to create a Session. The Session is where you can choose the content, prepare the player, and render channel content. Add this into the TvInputService class (RichTvInputService)
.
Example of a concrete Session class:
private class PreviewSession extends TvInputService.Session {
PreviewSession(Context context) {
super(context);
Log.d(Utils.DEBUG_TAG, "session created!");
}
@Override
public boolean onTune(Uri channelUri) {
Log.d(Utils.DEBUG_TAG, "onTune " + channelUri);
...
return false;
}
@Override
public boolean onSetSurface(@Nullable Surface surface) {
Log.d(Utils.DEBUG_TAG, "onSetSurface");
...
return false;
}
}
import android.content.Context
import android.media.tv.TvInputService
import android.net.Uri
import android.util.Log
import android.view.Surface
private const val TAG = "MyTag"
private class PreviewSession(context: Context) : TvInputService.Session(context) {
init {
Log.d(TAG, "session created!")
}
override fun onTune(channelUri: Uri): Boolean {
Log.d(TAG, "onTune $channelUri")
return false
}
override fun onSetSurface(surface: Surface?): Boolean {
Log.d(TAG, "onSetSurface")
return false
}
override fun onSetCaptionEnabled(enabled: Boolean) {
TODO("Not yet implemented")
}
override fun onRelease() {
TODO("Not yet implemented")
}
override fun onSetStreamVolume(volume: Float) {
TODO("Not yet implemented")
}
}
Identify the Correct Channel
You should be able to identify the current channel users are tuning to through the onTune()
callback by the channelId
.
Example of how to get the channel ID from the channelUri
:
long channelId = Long.parseLong(channelUri.getLastPathSegment());
import android.net.Uri
val channelUri: Uri = TODO()
var channelId: Long? = channelUri.lastPathSegment?.toLong()
The channel ID is the ID that’s auto-assigned by Android during the channel insertion into Android’s TV Database. You must keep a map between the channel ID Android assigns when the channel is inserted into the TV database, and your channel ID. This way, you can always find the correct channel content using the channel ID.
Play the Channel Content in Preview Playback
Implement a media player that can play your TV feed on a configurable surface.
When a user moves the focus to a specific channel card while browsing, the Live app uses the Session from the previous step. This is defined by your app as part of the TvInputService.Session
class, and is used to tune to the requested channel and play content.
Create a Media Player
There are a number of options for implementing a media player. You should already have a media player well-defined inside your app that can be used directly. There is no hard requirement as to which media player you should use, as long as the player can play your TV feeds and can be configured to use a customized Surface class (refer to the next step). Because of this case-by-case player implementation, we will provide only a few options here as reference.
ExoPlayer: a good candidate for the underlying media player.
SampleTvApp's DemoPlayer: an example of using ExoPlayer to construct a media player that supports TV channel tunings.
SampleTvApp's DemoPlayer in TvInputService: an example of how to define the customized media player in TvInputService
.
onSetSurface
During the tuning process, Android's TIF framework will call the onSetSurface(@Nullable Surface surface)
callback, which is defined in the TvInputService.Session
(refer to previous steps). It's your responsibility to set the provided Surface instance to your media player, which will be used for preview playback.
onTune
The onTune(Uri channelUri)
callback will be called next, where you should identify the correct channel (refer to Identify the Correct Channel), retrieve the corresponding channel feed, and prepare the media player to play the feed when ready.
onTune
won't be called again.Here are some typical scenarios for not calling onTune()
for the second playback:
- When the user moves focus to a channel card, onTune calls for preview playback.
- When the user clicks the current card and enters the fullscreen playback.
- When fullscreen playback seamlessly displays fullscreen. You won't receive another
onTune()
in this case.
Notify tuning status
You must notify TvInputService
about the most recent tuning status based on the player's loading status. Fire TV's Live app will refer to the status to adjust the preview UI.
Here is an example of notifying the tuning status. In this case, the status is “temporarily unavailable.” Place this code in TvInputService.Session
.
@Override
public boolean onTune(Uri channelUri) {
Log.d(Utils.DEBUG_TAG, "onTune " + channelUri);
// Let the TvInputService know that the video is being loaded.
notifyVideoUnavailable(VIDEO_UNAVAILABLE_REASON_TUNING);
}
override fun onTune(channelUri: Uri): Boolean {
Log.d(TAG, "onTune $channelUri")
notifyVideoUnavailable(VIDEO_UNAVAILABLE_REASON_TUNING)
return true
}
Example of a notification of video availability status:
notifyTracksChanged(getAllTracks());
String audioId = getTrackId(TvTrackInfo.TYPE_AUDIO,
mPlayer.getSelectedTrack(TvTrackInfo.TYPE_AUDIO));
String videoId = getTrackId(TvTrackInfo.TYPE_VIDEO,
mPlayer.getSelectedTrack(TvTrackInfo.TYPE_VIDEO));
String textId = getTrackId(TvTrackInfo.TYPE_SUBTITLE,
mPlayer.getSelectedTrack(TvTrackInfo.TYPE_SUBTITLE));
notifyTrackSelected(TvTrackInfo.TYPE_AUDIO, audioId);
notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, videoId);
notifyTrackSelected(TvTrackInfo.TYPE_SUBTITLE, textId);
notifyVideoAvailable();
val audioId: String = getTrackId(
TvTrackInfo.TYPE_AUDIO,
mPlayer.getSelectedTrack(TvTrackInfo.TYPE_AUDIO)
)
val videoId: String = getTrackId(
TvTrackInfo.TYPE_VIDEO,
mPlayer.getSelectedTrack(TvTrackInfo.TYPE_VIDEO)
)
val textId: String = getTrackId(
TvTrackInfo.TYPE_SUBTITLE,
mPlayer.getSelectedTrack(TvTrackInfo.TYPE_SUBTITLE)
)
notifyTrackSelected(TvTrackInfo.TYPE_AUDIO, audioId)
notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, videoId)
notifyTrackSelected(TvTrackInfo.TYPE_SUBTITLE, textId)
notifyVideoAvailable()
Parental controls
Based on the product requirements, you should not play preview playback videos if Parental Controls (PCON) is enabled.
Example of how to listen to Parental Controls concerning live preview or native full screen playback.
private TvContentRating mBlockedRating = null;
@Override
public boolean onTune(final Uri channelUri) {
...
if (mTvInputManager.isParentalControlsEnabled()) {
// ensure playback is not audible or visible on the Surface
mBlockedRating = < content_rating > ;
notifyContentBlocked(mBlockedRating);
} else {
// playback should start
notifyContentAllowed();
}
...
}
@Override
public void onUnblockContent(final TvContentRating unblockedRating) {
// the user successfully entered their PIN to unblock content for the
// provided rating
if (unblockedRating.unblockContent(mBlockedRating)) {
// playback should start
notifyContentAllowed();
}
}
import android.content.Context
import android.media.tv.TvContentRating
import android.media.tv.TvInputManager
import android.media.tv.TvInputService
import android.net.Uri
import android.view.Surface
private const val TAG = "MyTag"
private class PreviewSession(context: Context) : TvInputService.Session(context) {
private val tvInputManager: TvInputManager = TODO()
override fun onTune(channelUri: Uri): Boolean {
if (tvInputManager.isParentalControlsEnabled) {
// ensure playback is not audible or visible on the Surface
val blockedRating = getContentRating(channelUri)
notifyContentBlocked(blockedRating)
} else {
// playback should start
notifyContentAllowed()
}
return true
}
override fun onUnblockContent(unblockedRating: TvContentRating) {
// the user successfully entered their PIN to unblock content for the
// provided rating
if (unblockedRating.unblockContent(blockedRating)) { // <-- What is this?
// playback should start
notifyContentAllowed()
}
}
}
private fun getContentRating(channelUri: Uri): TvContentRating = TODO()
Activity | Required? | Notes |
---|---|---|
mTvInputManager.isParentalControlsEnabled() |
Yes | This method is called to check PCON status. |
notifyContentBlocked() |
Depends | This method should be called whenever the video is blocked from playing because of PCON. |
notifyContentAllowed() |
Depends | This method should be called whenever the video is good to play. |
Checkpoint - Preview Playback in Browse
- Build and install your APK onto Fire TV.
- Navigate to On Now Row, focus on a channel card, and preview playback should start to play at the top right corner.
- If not using deeplink: select channel card and playback should continue in full screen.
- Navigate to the Parental Control menu to turn on Parental Controls.
- Navigate back to the On Now Row, focus on a channel card, and preview playback should NOT start to play, but poster art should show up on the browse screen if provided.
- If not using deeplink: select channel card and PIN prompt will appear. Insert the PIN for playback to start in full screen.
Troubleshooting
I don't see the onTune()
callback being triggered when focusing on my channel card
Double check the inputId of that channel. Android won't identify your TvInputService
to call it if the InputId
is not correct.
I can see onTune()
being called, but the playback preview doesn’t start
- Check if your player is correctly implemented for playing the feed.
- Ensure you are calling
notifyVideoAvailable()
to notify that your tuning status is ready.
After enabling PCON, I see the preview playback still playing in the browse section
Double check you are implementing the Parental Control code correctly in your TvInputService
. Your player should stop if PCON is on, and you use notifyContentBlocked()
to notify the UI. For more information, see Live TV Resources.
After enabling PCON, I don't see the preview playback nor the poster image. I only see a black background.
Make sure your channel has valid poster art to show. It is supposed to be the image of the program currently airing.
If it is, double check that you are calling notifyContentBlocked()
. The UI won't update to use the poster image if you are not sending a notification about the PCON status.
Next Steps
For more details on the process, see Live TV Resources.
Last updated: Jul 02, 2024