开发者控制台

步骤4: 在Fire TV用户界面中播放

步骤4: 在Fire TV用户界面中播放

每当客户聚焦于浏览行中的某个磁贴时,直播TV都能够进行预览播放。这是一种方便快捷的内容预览方式。

播放流

要与预览播放集成,用来播放频道内容的播放器必须能够使用直播TV应用提供的Surface

创建TvInputService.Session

用户选择特定频道后,调用TvInputService类以创建Session。在会话中,可以选择内容、准备播放器和渲染频道内容将此添加到TvInputService类(RichTvInputService中)。

以下代码显示了一个具体Session类的示例。

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("尚未实现")
    }

    override fun onRelease() {
        TODO("尚未实现")
    }

    override fun onSetStreamVolume(volume: Float) {
        TODO("尚未实现")
    }
}

识别正确的频道

您可以通过由channelId进行的onTune() 回调来识别用户当前调到了哪个频道。

以下代码显示了如何从channelUri获取频道ID的示例。

long channelId = Long.parseLong(channelUri.getLastPathSegment());
import android.net.Uri

val channelUri: Uri = TODO()
var channelId: Long? = channelUri.lastPathSegment?.toLong()

频道ID是将频道插入Android电视数据库时,由Android自动分配给频道的ID。在将频道插入电视数据库时由Android分配的频道ID与您的频道ID之间,必须始终存在一个映射。通过这种方式,始终可以使用频道ID找到正确的频道内容。

在预览播放中播放频道内容

实现可以在可配置Surface上播放电视源的媒体播放器。

当用户在浏览过程将焦点移到特定频道卡片上,上线应用将使用前一步骤中的会话。您的应用将会话定义为TvInputService.Session类的一部分,并使用它来调整至所请求的频道和播放内容。

创建媒体播放器

有若干媒体播放器实现方法可供选择。您的应用中必须有一个明确定义的媒体播放器,可以供您直接使用。对于使用哪个媒体播放器没有硬性要求,只要播放器可以播放您的电视信息提要,并且您可对其进行配置以使用自定义Surface类即可。有关详细信息,请参阅#onSetSurface。由于采用了这种视具体情况而定的播放器实现,以下选项仅供参考。

ExoPlayer:非常适合作为底层媒体播放器。

示例电视应用的DemoPlayer:使用ExoPlayer构建支持电视频道调谐的媒体播放器示例。

示例电视应用的TvInputService中的DemoPlayer:如何在TvInputService中定义自定义媒体播放器的示例。

onSetSurface

在调谐过程中,Android的TIF框架调用onSetSurface(@Nullable Surface surface) 回调,这是在TvInputService.Session中定义的(参见之前的步骤)。您有责任将提供的Surface实例设置为用于预览播放的媒体播放器。

onTune

随即调用onTune(Uri channelUri) 回调,您通过此回调识别正确的频道(请参阅识别正确的频道)、检索相应的频道信息提要并准备好媒体播放器,从而在就绪时播放该频道源。

以下列举了一些不会再次调用onTune() 以进行第二次播放的典型情景:

  1. 用户将焦点移到一个频道卡片,触发onTune() 调用以进行预览播放的情况。
  2. 用户单击当前卡片并进入全屏播放的情况。
  3. 全屏播放进行无缝全屏显示的情况。在此情况下,您不会再次收到onTune()

发送调谐状态通知

您必须根据播放器加载状态,向TvInputService发送通知,说明最新调谐状态。Fire TV的上线应用引用该状态以调整预览用户界面。

以下示例演示了如何发送调谐状态通知。在此情况下,状态为“temporarily unavailable”(暂时不可用)。 在TvInputService.Session中添加此代码。

@Override
public boolean onTune(Uri channelUri) {
    Log.d(Utils.DEBUG_TAG, "onTune " + channelUri);
    // 让TvInputService知道视频正在加载。
    notifyVideoUnavailable(VIDEO_UNAVAILABLE_REASON_TUNING);
}
override fun onTune(channelUri: Uri): Boolean {
    Log.d(TAG, "onTune $channelUri")
    notifyVideoUnavailable(VIDEO_UNAVAILABLE_REASON_TUNING)
   return true
}

以下代码展示了视频可用状态通知的示例。

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()

家长监护

根据产品要求,如果启用家长监护 (PCON),则不应播放预览播放视频。

以下代码示例演示了对于直播预览或本机全屏播放,如何侦听家长监护。

private TvContentRating mBlockedRating = null;

@Override
public boolean onTune(final Uri channelUri) {
    ...
    if (mTvInputManager.isParentalControlsEnabled()) {
        // 确保在Surface上无法听到或看到播放
        mBlockedRating = < content_rating > ;
        notifyContentBlocked(mBlockedRating);
    } else {
        // 播放应开始
        notifyContentAllowed();
    }
    ...
}

@Override
public void onUnblockContent(final TvContentRating unblockedRating) {
    // 用户成功输入PIN以解禁
    // 适用于给定评级的内容
    if (unblockedRating.unblockContent(mBlockedRating)) {
        // 播放应开始
        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) {
            // 确保在Surface上无法听到或看到播放
            val blockedRating = getContentRating(channelUri)
            notifyContentBlocked(blockedRating)
        } else {
            // 播放应开始
            notifyContentAllowed()
        }
        return true
    }


    override fun onUnblockContent(unblockedRating: TvContentRating) {
        // 用户成功输入PIN以解禁
        // 适用于给定评级的内容
        if (unblockedRating.unblockContent(blockedRating)) { // <--这是什么?
            // 播放应开始
            notifyContentAllowed()
        }
    }

}

private fun getContentRating(channelUri: Uri): TvContentRating = TODO()
活动 是否必需? 注释
mTvInputManager.isParentalControlsEnabled() 检查PCON状态时将调用此方法。
notifyContentBlocked() 视情况而定 视频播放如果受PCON阻止,则将调用此方法。
notifyContentAllowed() 视情况而定 如果视频适合播放,则将调用此方法。

检查点: 在浏览部分预览播放

  1. 在Fire TV上构建并安装您的APK。
  2. 导航到On Now(当前热映)行并聚焦频道卡。预览播放应在右上角开始播放。
    • 如果不使用深层链接:选择频道卡片,然后应以全屏模式继续进行播放。
  3. 导航到Parental Control(家长监护)菜单以打开家长监护。
  4. 导航回On Now行并聚焦频道卡。此时不应开始进行预览播放,但如果提供了海报图,则海报图应出现在浏览屏幕上。
    • 如果不使用深层链接:选择频道卡片,随即显示PIN提示。需要插入PIN,才能以全屏模式开始进行播放。

故障排除

此部分包含解决您可能遇到的问题的步骤。

聚焦于频道卡片时,没有看到触发onTune() 回调。
验证该频道的输入ID。如果InputId有误,Android将不会识别您的TvInputService调用。
可以看到调用onTune(),但播放预览没有启动。
  1. 检查播放器是否采用了正确的实现方式来播放相应的源。
  2. 确保调用notifyVideoAvailable()来发送通知,说明调谐状态已准备就绪。
启用家长监护 (PCON) 后,看到预览播放仍在浏览部分中播放。
确认您是否在TvInputService中实现了家长监护代码。如果已启用PCON,则播放器应停止播放,您应使用notifyContentBlocked()来通知用户界面。有关更多信息,请参阅直播TV资源
启用家长监护 (PCON) 后,没有看到预览播放和海报图像,只看到黑色背景。
确保您的频道拥有可显示的有效海报图。它应该显示当前正在播出的节目的图像。

如果是这样,请确认您是否在调用notifyContentBlocked()。如果不发送PCON状态通知,则用户界面将不会更新以使用该海报图像。



Last updated: 2025年5月5日