步骤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()
。以下列举了一些不会再次调用onTune()
以进行第二次播放的典型情景:
- 用户将焦点移到一个频道卡片,触发
onTune()
调用以进行预览播放的情况。 - 用户单击当前卡片并进入全屏播放的情况。
- 全屏播放进行无缝全屏显示的情况。在此情况下,您不会再次收到
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() |
视情况而定 | 如果视频适合播放,则将调用此方法。 |
检查点: 在浏览部分预览播放
- 在Fire TV上构建并安装您的APK。
- 导航到On Now(当前热映)行并聚焦频道卡。预览播放应在右上角开始播放。
- 如果不使用深层链接:选择频道卡片,然后应以全屏模式继续进行播放。
- 导航到Parental Control(家长监护)菜单以打开家长监护。
- 导航回On Now行并聚焦频道卡。此时不应开始进行预览播放,但如果提供了海报图,则海报图应出现在浏览屏幕上。
- 如果不使用深层链接:选择频道卡片,随即显示PIN提示。需要插入PIN,才能以全屏模式开始进行播放。
故障排除
此部分包含解决您可能遇到的问题的步骤。
- 聚焦于频道卡片时,没有看到触发
onTune()
回调。 - 验证该频道的输入ID。如果
InputId
有误,Android将不会识别您的TvInputService
调用。 - 可以看到调用
onTune()
,但播放预览没有启动。 -
- 检查播放器是否采用了正确的实现方式来播放相应的源。
- 确保调用
notifyVideoAvailable()
来发送通知,说明调谐状态已准备就绪。
- 启用家长监护 (PCON) 后,看到预览播放仍在浏览部分中播放。
- 确认您是否在
TvInputService
中实现了家长监护代码。如果已启用PCON,则播放器应停止播放,您应使用notifyContentBlocked()
来通知用户界面。有关更多信息,请参阅直播TV资源。 - 启用家长监护 (PCON) 后,没有看到预览播放和海报图像,只看到黑色背景。
- 确保您的频道拥有可显示的有效海报图。它应该显示当前正在播出的节目的图像。
-
如果是这样,请确认您是否在调用
notifyContentBlocked()
。如果不发送PCON状态通知,则用户界面将不会更新以使用该海报图像。
Last updated: 2025年5月5日