开发者控制台

步骤5: 在Fire TV UI中播放

步骤5: 在Fire TV UI中播放

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

播放流

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

创建TvInputService.Session

用户选择特定频道后,将调用TvInputService类以创建Session。在Session中,可以选择内容、准备播放器和渲染频道内容。将此添加到 TvInputService class (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上播放电视源的媒体播放器。

当用户在浏览过程将焦点移到特定频道卡片上,直播应用将使用前一步骤中的Session。这是由您的应用定义,将其作为TvInputService.Session类的一部分,并用来调到请求的频道和播放内容。

创建媒体播放器

有多种媒体播放器实现方法可供选择。在您的应用内部,应该已经存在定义明确并可直接使用的媒体播放器。对于应使用哪个媒体播放器没有硬性要求,只要播放器可以播放您的电视源,并可进行配置以使用自定义Surface类(请参阅下一步骤)即可。由于播放器的实现方式视具体情况而定,我们在此仅提供几个选项以供参考。

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

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

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

onSetSurface

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

onTune

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

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

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

发送调谐状态通知

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

以下示例演示了如何发送调谐状态通知。在此情况下,状态为“暂时不可用”。 在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() 视情况而定 如果视频适合播放,则将调用此方法。

检查点 - 在“Browse(浏览)”中预览播放

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

故障排除

聚焦于频道卡片时,没有看到触发onTune()回调

仔细检查此频道的inputId。如果InputId有误,Android将不会识别您的TvInputService调用。

可以看到调用onTune(),但播放预览没有启动

  1. 检查播放器是否采用了正确的实现方式来播放相应的源。
  2. 确保调用notifyVideoAvailable()来发送通知,说明调谐状态已准备就绪。

启用PCON后,看到预览播放仍在“Browse(浏览)”部分中播放

重复检查确认您是否在TvInputService中实现了家长监护代码。如果已启用PCON,则播放器应停止播放,您应使用notifyContentBlocked()来通知UI。有关更多信息,请参阅电视直播资源

启用PCON后,没有看到预览播放和海报图像,只看到黑色背景。

确保您的频道拥有可显示的有效海报图。该海报图应该是当前播放节目的图像。

如果是的话,请重复检查确认您是否在调用notifyContentBlocked()。如果不发送PCON状态通知,则UI将不会更新以使用该海报图像。

后续步骤

有关此过程的更多详细信息,请参阅直播电视资源


Last updated: 2022年8月12日