开发者控制台

步骤4: 在Fire TV UI中播放

步骤4: 在Fire TV UI中播放

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

播放流

要与预览播放集成,用来播放频道内容的播放器必须能够使用电视直播应用提供的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. 用户将焦点移到一个频道卡片,触发onTune调用以进行预览播放的情况。
  2. 用户单击当前卡片并进入全屏播放的情况。
  3. 全屏播放进行无缝全屏显示的情况。在此情况下,您不会再次收到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。有关更多信息,请参阅直播TV资源

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

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

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

后续步骤

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


Last updated: 2024年7月2日