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