Developer Console

Step 5: 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.

Playback flow

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 or Native Player 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 previous steps), retrieve the corresponding channel feed, and prepare the media player to play the feed when ready.

Here are some typical scenarios for not calling onTune() for the second playback:

  1. When you support preview playback, and play fullscreen playback by Live app's native player.
  2. When the user moves focus to a channel card, onTune calls for preview playback.
  3. When the user clicks the current card and enters the fullscreen playback.
  4. 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) are on.

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

  1. Build and install your APK onto Fire TV.
  2. Navigate to On Now Row, focus on a channel card, and preview playback should start to play at the top right corner.
    1. If not using deeplink: select channel card and playback should continue in full screen.
  3. Navigate to the Parental Control menu to turn on Parental Controls.
  4. 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.
    1. 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

  1. Check if your player is correctly implemented for playing the feed.
  2. 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: Aug 12, 2022