Android Studio TV开发教程
(转自Android官网https://developer.android.com/training/tv/start)
文章源自:光谷佳武 https://blog.csdn.net/jiawuhan/article/details/80648174
管理电视用户互动
在实况电视体验中,用户改变频道并且在信息消失之前简要地呈现频道和节目信息。 其他类型的信息,如消息(“不要在家里尝试”),字幕或广告可能需要坚持。 与任何电视应用程序一样,此类信息不应干扰屏幕上播放的节目内容。
图1.实时电视应用程序中的重叠消息。
考虑内容的评分和家长控制设置,以及您的应用如何运行并在内容被阻止或不可用时通知用户,还要考虑是否应呈现某些节目内容。 本课介绍如何针对这些考虑事项开发电视输入的用户体验。
尝试电视输入服务示例应用程序。
整合玩家与表面
您的电视输入必须将视频呈现到Surface
对象,该对象由TvInputService.Session.onSetSurface()
方法传递。 以下是如何使用MediaPlayer
实例播放Surface
对象中的内容的示例:
@覆盖
public boolean onSetSurface(Surface surface){
if(mPlayer!= null){
mPlayer.setSurface(表面);
}
mSurface = surface;
返回true;
}
@覆盖
public void onSetStreamVolume(float volume){
if(mPlayer!= null){
mPlayer.setVolume(volume,volume);
}
mVolume =音量;
}
同样,这里是如何使用ExoPlayer来做到这一点的:
@覆盖
public boolean onSetSurface(Surface surface){
if(mPlayer!= null){
mPlayer.sendMessage(mVideoRenderer,
MediaCodecVideoTrackRenderer.MSG_SET_SURFACE,
表面);
}
mSurface = surface;
返回true;
}
@覆盖
public void onSetStreamVolume(float volume){
if(mPlayer!= null){
mPlayer.sendMessage(mAudioRenderer,
MediaCodecAudioTrackRenderer.MSG_SET_VOLUME,
卷);
}
mVolume =音量;
}
使用覆盖
使用叠加显示字幕,信息,广告或MHEG-5数据广播。 默认情况下,叠加层被禁用。 您可以在通过调用TvInputService.Session.setOverlayViewEnabled(true)
创建会话时启用它,如下例所示:
@覆盖
public final Session onCreateSession(String inputId){
BaseTvInputSessionImpl session = onCreateSessionInternal(inputId);
session.setOverlayViewEnabled(真);
mSessions.add(会话);
返回会话;
}
如下所示,从TvInputService.Session.onCreateOverlayView()
返回的覆盖图使用View
对象:
@覆盖
public View onCreateOverlayView(){
LayoutInflater inflater =(LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE);
查看视图= inflater.inflate(R.layout.overlayview,null);
mSubtitleView =(SubtitleView)view.findViewById(R.id.subtitles);
//配置字幕视图。
CaptionStyleCompat captionStyle;
float captionTextSize = getCaptionFontSize();
captionStyle = CaptionStyleCompat.createFromCaptionStyle(
mCaptioningManager.getUserStyle());
captionTextSize * = mCaptioningManager.getFontScale();
mSubtitleView.setStyle(captionStyle);
mSubtitleView.setTextSize(captionTextSize);
返回视图;
}
叠加层的布局定义可能如下所示:
<?xml version =“1.0”encoding =“utf-8”?>
<的FrameLayout
的xmlns:机器人= “http://schemas.android.com/apk/res/android”
的xmlns:工具= “http://schemas.android.com/tools”
机器人:layout_width = “match_parent”
机器人:layout_height = “match_parent”>
机器人:ID = “@ + ID /字幕”
机器人:layout_width = “WRAP_CONTENT”
机器人:layout_height = “WRAP_CONTENT”
机器人:layout_gravity = “底部| CENTER_HORIZONTAL”
机器人:layout_marginLeft = “16DP”
机器人:layout_marginRight = “16DP”
机器人:layout_marginBottom = “32dp”
机器人:能见度= “看不见的”/>
的FrameLayout>
控制内容
当用户选择频道时,您的电视输入会处理TvInputService.Session
对象中的onTune()
回调。 系统电视应用程序的家长控制决定了显示的内容,给出内容评级。 以下各节介绍如何使用与系统TV应用程序通信的TvInputService.Session
notify
方法来管理频道和节目选择。
使视频不可用
当用户更改频道时,您希望在电视输入呈现内容之前确保屏幕不显示任何杂散视频瑕疵。 调用TvInputService.Session.onTune()
,可以通过调用TvInputService.Session.notifyVideoUnavailable()
并传递VIDEO_UNAVAILABLE_REASON_TUNING
常量来阻止呈现视频,如以下示例所示。
@覆盖
public boolean onTune(Uri channelUri){
if(mSubtitleView!= null){
mSubtitleView.setVisibility(View.INVISIBLE);
}
notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
mUnblockedRatingSet.clear();
mDbHandler.removeCallbacks(mPlayCurrentProgramRunnable);
mPlayCurrentProgramRunnable = new PlayCurrentProgramRunnable(channelUri);
mDbHandler.post(mPlayCurrentProgramRunnable);
返回true;
}
然后,当内容呈现给Surface
,您可以调用TvInputService.Session.notifyVideoAvailable()
以允许显示视频,如下所示:
@覆盖
public void onDrawnToSurface(Surface surface){
mFirstFrameDrawn = true;
notifyVideoAvailable();
}
这种转换仅持续几分之一秒,但呈现空白屏幕在视觉上好于让图片闪烁奇怪的闪烁和抖动。
另请参阅使用Surface
集成播放器以获取有关使用Surface
渲染视频的更多信息。
提供家长控制
要确定给定内容是否被父母控制和内容分级阻止,请检查TvInputManager
类方法isParentalControlsEnabled()
和isRatingBlocked(android.media.tv.TvContentRating)
。 您可能还需要确保内容的TvContentRating
包含在一组当前允许的内容评级中。 下面的示例中显示了这些注意事项。
private void checkContentBlockNeeded(){
if(mCurrentContentRating == null ||!mTvInputManager.isParentalControlsEnabled()
|| !mTvInputManager.isRatingBlocked(mCurrentContentRating)
|| mUnblockedRatingSet.contains(mCurrentContentRating)){
//内容分级已更改,因此我们无需再阻止。
//明确地取消阻止内容以恢复播放。
unblockContent(NULL);
返回;
}
mLastBlockedRating = mCurrentContentRating;
if(mPlayer!= null){
//儿童限制内容也可能被电视应用阻止,
//但TIF应尽全力不要显示任何被阻止内容的单帧。
releasePlayer();
}
notifyContentBlocked(mCurrentContentRating);
}
一旦确定内容是否应该被阻止,请通过调用TvInputService.Session
方法notifyContentAllowed()
或notifyContentBlocked()
来通知系统TV应用程序,如上例所示。
使用TvContentRating
类,通过TvContentRating.createRating()
方法为COLUMN_CONTENT_RATING
生成系统定义的字符串,如下所示:
TvContentRating rating = TvContentRating.createRating(
“com.android.tv”
“US_TV”
“US_TV_PG”
“US_TV_D”,“US_TV_L”);
处理曲目选择
TvTrackInfo
类保存关于媒体轨道的信息,例如轨道类型(视频,音频或字幕)等等。
您的电视输入会话第一次能够获取曲目信息时,应该使用所有曲目列表调用TvInputService.Session.notifyTracksChanged()
以更新系统电视应用程序。 当轨道信息发生变化时,再次调用notifyTracksChanged()
来更新系统。
系统电视应用程序为用户提供了一个界面,以便在给定轨道类型有多个轨道可用的情况下选择特定轨道; 例如,不同语言的字幕。 您的电视输入通过调用notifyTrackSelected()
来响应来自系统TV应用程序的onSelectTrack()
调用,如以下示例所示。 请注意,当传递null
作为曲目ID时,将取消选择曲目。
@覆盖
public boolean onSelectTrack(int type,String trackId){
if(mPlayer!= null){
if(type == TvTrackInfo.TYPE_SUBTITLE){
if(!mCaptionEnabled && trackId!= null){
返回false;
}
mSelectedSubtitleTrackId = trackId;
if(trackId == null){
mSubtitleView.setVisibility(View.INVISIBLE);
}
}
if(mPlayer.selectTrack(type,trackId)){
notifyTrackSelected(type,trackId);
返回true;
}
}
返回false;
}
支持时移
在电视输入服务中使用时移API,让用户在服务频道中暂停,倒带和快进直播节目。 如果您的应用支持时间转换,用户可以在观看您的内容方面获得更多灵活性,例如:
- 用户可以在处理短暂中断时暂停程序,确保他们不会错过关键时刻。
- 用户可以快速浏览他们已经看到的内容或者不感兴趣的内容。
- 用户可以回放节目内容并重新编排喜爱的片段。
图1.用于时移的Android TV播放控件。
时间转换使用短暂的,暂时的,录制的节目数据段来实现回放现场节目的能力。 用户无法在当前回放会话之外播放这些时移记录,因此他们无法使用时移来暂停节目以观看第二天,或者暂停节目以在稍后切换到其他频道时观看。 如果您想让用户录制节目内容以在当前回放会话之外观看,请使用电视录制API 。
添加时移支持
要为您的电视输入服务添加时移功能,您需要在TvInputService.Session
类中实现时移API,在您的TvInputService.Session
处理时移录制的录制和回放,并通知系统您的输入服务提供了时移支持。
你必须实现的TvInputService.Session
方法是:
onTimeShiftGetCurrentPosition()
:由系统调用以毫秒为单位获取当前播放位置。 有关更多详情,请参阅追踪播放时间
onTimeShiftGetStartPosition()
:由系统调用,以毫秒为单位获取当前时移记录的开始位置。 有关更多详情,请参阅追踪播放时间
onTimeShiftPause()
:当用户暂停播放时调用。
onTimeShiftResume()
:当用户正在恢复播放时调用。
onTimeShiftSeekTo(long)
:当系统需要寻找新的时间位置时调用。 通常情况下,新位置在起始位置和当前位置之间。
onTimeShiftSetPlaybackParams(PlaybackParams)
:由系统调用,为当前会话提供播放参数,例如播放速度。有关更多详情,请参阅支持播放参数
有关如何通知系统您的输入服务支持时间转换的更多详细信息,请参阅通知系统有关时间转换状态 。
如果您使用TIF Companion Library来实现您的TvInputService.Session
类,您将自动获得使用ExoPlayer的时间偏移实现。 您可以使用此实现,或覆盖BaseTvInputService.Session
时间平移的API方法并提供您自己的实现。 有关使用TIF Companion Library的更多信息,请参阅使用TIF Companion Library 创建电视输入服务 。
会话开始时记录内容
用户可以通过访问该频道的播放控件(在观看内容并导航到播放控件时按下选择 ),或者通过在设备遥控器上使用专用播放控件(如果可用)来暂停,倒带和快进节目内容。 因为用户可以在观看节目内容的任何时间使用时移功能,所以只要用户在onTune()
实现中调谐到频道,电视输入服务就必须开始记录时移内容。 此时,您还需要按照通知系统有关时移状态的描述,通过调用notifyTimeShiftStatusChanged(int)
来通知系统您可以进行录制。
管理录制的内容存储
您的电视输入服务负责在您的应用程序的私人应用程序存储中存储时移记录,并在系统调用诸如onTimeShiftResume()
类的时移方法时播放内容。 如果您的内容已存储在云中,并且您的应用可以管理云中的时间转换录制,则可以使用云存储,而不是应用存储。
如果您的内容使用受保护的内容,则您的电视输入服务负责在播放过程中正确加密录制的内容并解密内容。
由于录制的视频内容可能需要大量存储空间,因此您需要在会话播放期间仔细管理录制的内容。 如果回放会话时间超过您可以记录和存储以进行时移的时间量,则应调整时移记录以保持当前缓冲区,但确保捕获当前时间。 例如,如果用户已经播放了31分钟的内容,并且您的最大时移记录大小为30分钟,则可以调整记录并开始时间以包含从第1分钟到第31分钟的内容。
如果您的电视输入服务由于缺少存储而无法支持时间转换,则必须通知系统。 有关如何通知系统时移支持限制的更多详细信息,请参阅通知系统有关时移状态 。
当用户切换到其他频道或以其他方式结束播放时,应删除记录的时移数据。
通知系统时间转换状态
如果您的电视输入服务支持时移功能,请在用户调谐到频道时,在onTune()
实现中调用notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_AVAILABLE)
。
要通知系统输入服务的任何时移功能是否更改,请使用notifyTimeShiftStatusChanged(int)
。 例如,如果您的电视输入服务由于存储空间限制或其他原因而无法支持时移,请调用notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE)
。
如果您的电视输入服务根本不支持时移,在创建回放会话时请调用notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_UNSUPPORTED)
。 系统notifyTimeShiftStatusChanged()
不调用notifyTimeShiftStatusChanged()
作为输入服务的任何输入服务视为不支持时间转换的输入服务 - 这涵盖了使用API Level 22及更早版本的输入服务。
跟踪播放时间
时移记录的开始位置是用户可以寻求的最早的绝对时间位置(从纪元开始以毫秒为单位)。 这通常是在调用onTune()
之后开始播放视频的时间。 但是,如果用户观看的内容数量超出了您的应用可以记录的数量,则可能需要开始记录新的时间段,然后相应地更新开始时间。
时移记录的当前位置是当前回放位置,从纪元开始以毫秒为单位。 播放期间此位置会不断变化。 通常,您可以使用播放引擎来确定此值; 例如:
@覆盖
public long onTimeShiftGetCurrentPosition(){
if(getTvPlayer()!= null && mCurrentProgram!= null){
返回getTvPlayer()。getCurrentPosition()+
mCurrentProgram.getStartTimeUtcMillis();
}
返回TvInputManager.TIME_SHIFT_INVALID_TIME;
}
确保系统调用onTimeShiftGetStartPosition()
时提供的开始时间永远不会大于您在onTimeShiftGetCurrentPosition()
提供的当前时间位置。 系统使用这些调用来更新播放控件UI中的时移时间。
支持播放参数
要在时移期间更改播放速度,系统将使用播放参数。 例如,如果用户决定倒带当前播放,则新的播放参数以负播放速度传递到您的应用程序。 时移还支持几个不同的级别(2倍,3倍)的回放速度,用于倒带或快进。
系统使用包含当前会话参数的PlaybackParams
对象调用onTimeShiftSetPlaybackParams(PlaybackParams)
方法。使用此信息适当地配置您的媒体播放引擎。
如果您的回放引擎不支持参数,则应尽可能地模拟预期的行为。 例如,如果您的播放引擎不支持2x速度,请在播放引擎上使用重复的搜索操作以获得大约两倍的播放速度。
参数设置完毕后,请勿更改设置,除非用户发出需要不同参数或切换到新频道的播放命令。
支持内容录制
电视输入服务让用户通过时移API暂停和恢复频道播放。 Android 7.0通过让用户保存多个录制的会话来扩展时间转换。
用户可以预先安排录音,或在观看节目时开始录音。 一旦系统保存了录像,用户就可以使用系统TV应用程序浏览,管理和回放录像。
如果您想为电视输入服务提供录制功能,则必须向系统指出您的应用程序支持录制,实现录制节目的能力,处理和传达录制过程中发生的任何错误,并管理录制的会话。
指示对录制的支持
要告诉系统您的电视输入服务支持录制,请将服务元数据XML文件中的android:canRecord
属性设置为true
:
机器人:canRecord = “真”
android:setupActivity =“com.example.sampletvinput.SampleTvInputSetupActivity”/>
有关服务元数据文件的更多信息,请参阅在清单中声明您的电视输入服务 。
或者,您可以使用以下步骤在代码中指示录制支持:
- 在您的TV输入服务
onCreate()
方法中,使用TvInputInfo.Builder
类创建一个新的TvInputInfo
对象。
- 创建新的
TvInputInfo
对象时,在调用build()
之前调用setCanRecord(true)
build()
以表明您的服务支持录制。
- 通过调用
TvInputManager.updateTvInputInfo()
注册您的TvInputInfo
对象与系统。
记录一个会话
在您的电视输入服务注册它支持录制功能之后,当系统需要访问您的应用程序的录制实现时,系统会调用您的TvInputService.onCreateRecordingSession()
方法。 实现你自己的TvInputService.RecordingSession
子类,并在onCreateRecordingSession()
回调触发时返回它。 这个小类负责切换到正确的频道数据,记录所请求的数据,并将记录状态和错误传达给系统。
当系统调用RecordingSession.onTune()
传递一个通道URI时,调到URI指定的通道。 通过调用notifyTuned()
通知系统您的应用程序已调谐到所需的频道,或者如果您的应用程序无法调谐到正确的频道,请调用notifyError()
。
系统接下来调用RecordingSession.onStartRecording()
回调。 您的应用必须立即开始录制。 当系统调用此回调函数时,它可能会提供一个URI,其中包含有关即将录制的程序的信息。 录制完成后,您会将此数据复制到RecordedPrograms
数据表中。
最后,系统调用RecordingSession.onStopRecording()
。 此时,您的应用必须立即停止录制。 您还需要在RecordedPrograms
表中创建一个条目。 此条目应在RecordedPrograms.COLUMN_RECORDING_DATA_URI
列中包含记录的会话数据URI,以及系统在初次调用onStartRecording()
提供的任何程序信息。
有关如何访问RecordedPrograms
表的更多详细信息,请参阅管理录制的会话 。
处理记录错误
如果记录过程中发生错误,导致记录数据不可用,请通过调用notifyError()
通知系统。 同样,您可以在创建记录会话后调用notifyError()
,让系统知道您的应用程序不能再记录会话。
如果在录制过程中发生错误,但您想为用户提供部分录制以进行播放,请调用notifyRecordingStopped()
以使系统能够使用部分会话。
管理录制的会话
系统为RecordedPrograms
内容提供程序表中的所有支持录音的频道应用程序维护所有录制会话的信息。 此信息可通过RecordedPrograms
内容记录URI访问。 使用内容提供程序API读取,添加和删除此表中的条目。
有关处理内容提供者数据的更多信息,请参阅内容提供者基础知识 。
最佳实践
电视设备可能存储空间有限,因此在分配存储空间以保存录制的会话时请使用最佳判断。 当没有足够的空间保存记录的会话时,使用RecordingCallback.onError(RECORDING_ERROR_INSUFFICIENT_SPACE)
。
当用户开始录制时,您应该尽快开始录制数据。 为了实现这一点,当系统调用onCreateRecordingSession()
回调函数时,完成任何先期耗时的任务,例如访问和分配存储空间。 这样做可让您在onStartRecording()
回调触发时立即开始录制。