观看实况电视节目和其他连续的基于频道的内容是电视体验的重要组成部分。 用户习惯于通过频道浏览在电视上选择和观看节目。 电视输入框架在电视节目指南中创建发布视频或音乐内容的频道。
注意:电视输入框架旨在供OEM厂商用于为Android系统TV应用程序构建频道。 仅在Android 7.1(API级别25)中才支持Android 5.0(API级别21)。 第三方应用应使用Android TV主屏幕API为其内容构建频道。 详情请参阅推荐电视内容 。
电视输入框架提供了一种统一的方法,用于接收和播放来自硬件源(如HDMI端口和内置调谐器)以及软件源(如通过互联网传输的视频)的实况视频内容。
该框架使开发人员能够通过实施电视输入服务来定义实况电视输入源。 该服务向电视提供商发布频道和节目列表。 电视设备上的实时电视应用程序从TV提供商处获得可用频道和节目的列表,并将其显示给用户。 当用户选择特定频道时,实况电视应用程序通过电视输入管理器为相关电视输入服务创建一个会话,并告诉电视输入服务调谐到所请求的频道并将该内容播放到由电视应用。
图1.电视输入框架的功能图
电视输入框架旨在提供对各种实时电视输入源的访问,并将它们集中在一个用户界面中供用户浏览,查看和欣赏内容。 为您的内容构建电视输入服务可以使其在电视设备上更易于访问。
尝试电视输入服务示例应用程序。
主题
-
开发电视输入服务
-
了解如何开发可与系统电视应用程序配合使用的电视输入服务。
-
使用频道数据
-
了解如何描述系统的通道和程序数据。
-
管理用户交互
-
了解如何呈现叠加层,管理内容可用性以及处理内容选择。
-
支持时移
-
了解如何在电视输入服务中支持时间转换。
-
支持内容录制
-
了解如何在电视输入服务中支持内容录制。
电视输入服务代表媒体流来源,可让您以线性,广播电视形式呈现媒体内容作为频道和节目。 通过电视输入服务,您可以提供家长控制,节目指南信息和内容评级。 电视输入服务适用于Android系统电视应用程序。 此应用程序最终控制并在电视上呈现频道内容。 系统电视应用程序专为该设备开发,并由第三方应用程序不可变。 有关电视输入框架(TIF)架构及其组件的更多信息,请参阅电视输入框架 。
使用TIF Companion Library创建电视输入服务
TIF Companion Library是一个提供普通电视输入服务功能的可扩展实现的框架。 使用TIF Companion库快速轻松地创建您自己的电视输入服务,以遵循Android TV的最佳做法。
更新您的项目
要开始使用TIF Companion Library,请将以下内容添加到应用程序的build.gradle
文件中:
编译'com.google.android.libraries.tv:companionlibrary:0.2'
TIF Companion库目前不是Android框架的一部分。 它通过jcenter作为Gradle依赖关系分发,而不是与Android SDK分发。检查jcenter以查找最新版本的tif伴随库 。
在清单中声明您的电视输入服务
您的应用必须提供系统用来访问您的应用的TvInputService
兼容服务。 TIF Companion库提供了BaseTvInputService
类,它提供了可以自定义的TvInputService
的默认实现。 创建BaseTvInputService
的子类,并将清单中的子类声明为服务。
在清单声明中,指定BIND_TV_INPUT
权限以允许服务将电视输入连接到系统。 系统服务执行绑定并具有BIND_TV_INPUT
权限。 系统TV应用程序通过TvInputManager
接口向电视输入服务发送请求。
在你的服务声明中,包含一个intent过滤器,它指定TvInputService
为意图执行的动作。 还将服务元数据声明为单独的XML资源。 以下示例中显示了服务声明,意图过滤器和服务元数据声明:
机器人:标签= “@字符串/ rich_input_label”
机器人:权限= “android.permission.BIND_TV_INPUT”>
<! - 系统用来启动我们的帐户服务所需的过滤器。 - >
<意图滤波器>
意图滤波器>
<! - 描述此输入的XML文件。 这提供了指针
系统/电视应用程序的RichTvInputSetupActivity。 - >
<元数据
机器人:名字= “android.media.tv.input”
android:resource =“@ xml / richtvinputservice”/>
服务>
在单独的XML文件中定义服务元数据。 服务元数据XML文件必须包含描述电视输入的初始配置和频道扫描的设置界面。元数据文件还应该包含一个标志,说明用户是否能够记录内容。 有关如何在您的应用中支持录制内容的更多信息,请参阅电视录制 。
服务元数据文件位于您的应用程序的XML资源目录中,并且必须与您在清单中声明的资源的名称相匹配。 使用前面示例中的清单条目,您将在res/xml/richtvinputservice.xml
创建XML文件,其中包含以下内容:
<?xml version =“1.0”encoding =“utf-8”?>
机器人:canRecord = “真”
android:setupActivity =“com.example.android.sampletvinput.rich.RichTvInputSetupActivity”/>
定义频道并创建您的设置活动
您的电视输入服务必须定义至少一个用户通过系统电视应用访问的频道。 您应该在系统数据库中注册您的频道,并提供系统在无法找到您应用的频道时调用的设置活动。
首先,让您的应用程序读取和写入系统电子编程指南(EPG),其数据包括可供用户使用的频道和节目。 要使您的应用能够执行这些操作,并在设备重新启动后与EPG同步,请将以下元素添加到您的应用清单中:
添加以下元素以确保您的应用显示在Google Play商店中,作为在Android TV中提供内容频道的应用:
<用途特征
机器人:名字= “android.software.live_tv”
android:required =“true”/>
接下来,创建一个扩展EpgSyncJobService
类的类。 这个抽象类可以很容易地创建一个作业服务来创建和更新系统数据库中的通道。
在你的子类中,在getChannels()
创建并返回完整的频道列表。 如果您的频道来自XMLTV文件,请使用XmlTvParser
类。 否则使用Channel.Builder
类以编程方式生成通道。
对于每个通道,系统在需要可在通道上的给定时间窗口内查看的程序列表时调用getProgramsForChannel()
。 返回频道的Program
对象列表。 使用XmlTvParser
类从XMLTV文件获取程序,或使用Program.Builder
类以编程方式生成它们。
对于每个Program
对象,使用InternalProviderData
对象来设置节目信息,如节目的视频类型。 如果您只希望频道在循环中重复的程序数量有限,则在设置有关程序的信息时,请使用值为true
的InternalProviderData.setRepeatable()
方法。
在您实施了作业服务之后,将其添加到您的应用清单中:
<服务
机器人:名字= “sync.SampleJobService”
机器人:权限= “android.permission.BIND_JOB_SERVICE”
android:exported =“true”/>
最后,创建一个安装活动。 您的安装活动应提供同步通道和程序数据的方法。 一种方法是让用户通过活动中的用户界面来完成此操作。 当活动开始时,您可能也会让应用程序自动执行此操作。 当设置活动需要同步频道和节目信息时,应用程序应该启动作业服务:
String inputId = getActivity()。getIntent()。getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
EpgSyncJobService.cancelAllSyncRequests(getActivity());
EpgSyncJobService.requestImmediateSync(getActivity(),inputId,
新的ComponentName(getActivity(),SampleJobService.class));
使用requestImmediateSync()
方法来同步作业服务。 用户必须等待同步完成,因此您应该保持较短的请求时间。
使用setUpPeriodicSync()
方法让作业服务定期在后台同步通道和程序数据:
EpgSyncJobService.setUpPeriodicSync(context,inputId,
新的ComponentName(context,SampleJobService.class));
TIF Companion库提供了一个额外的requestImmediateSync()
重载方法,可以让您指定通道数据的持续时间以毫秒为单位进行同步。 默认方法可同步一小时的频道数据。
TIF Companion库还提供了一个额外的setUpPeriodicSync()
重载方法,可以让您指定要同步的频道数据的持续时间以及周期性同步应该发生的频率。 默认方法每12小时同步48小时的频道数据。
有关频道数据和EPG的更多详细信息,请参阅使用频道数据 。
处理调整请求和媒体播放
当用户选择特定频道时,系统电视应用会使用由您的应用创建的Session
调谐到所请求的频道并播放内容。 TIF Companion库提供了几个可以扩展的类来处理来自系统的通道和会话调用。
您的BaseTvInputService
子类创建处理调整请求的会话。 重写onCreateSession()
方法,创建一个从BaseTvInputService.Session
类扩展的会话,并在新会话中调用super.sessionCreated()
。 在以下示例中, onCreateSession()
返回一个扩展BaseTvInputService.Session
的RichTvInputSessionImpl
对象:
@覆盖
public final Session onCreateSession(String inputId){
RichTvInputSessionImpl session = new RichTvInputSessionImpl(this,inputId);
session.setOverlayViewEnabled(真);
return super.sessionCreated(session);
}
当用户使用系统电视应用程序开始查看您的某个频道时,系统会调用您的会话的onPlayChannel()
方法。 如果您需要在程序开始播放之前执行任何特殊频道初始化,请覆盖此方法。
然后系统获取当前安排的程序并调用会话的onPlayProgram()
方法,指定程序信息和开始时间(以毫秒为单位)。 使用TvPlayer
界面开始播放该程序。
您的媒体播放器代码应该实现TvPlayer
来处理特定的播放事件。 TvPlayer
类处理时移控件等功能,而不会增加BaseTvInputService
实现的复杂性。
在会话的getTvPlayer()
方法中,返回实现TvPlayer
媒体播放器。 电视输入服务示例应用程序实现了使用ExoPlayer的媒体播放器。
使用电视输入框架创建电视输入服务
如果您的电视输入服务无法使用TIF Companion Library,则需要实施以下组件:
TvInputService
为电视输入提供长时间运行和后台可用性
TvInputService.Session
保持电视输入状态并与主机应用程序通信
TvContract
描述了电视输入可用的频道和节目
TvContract.Channels
表示有关电视频道的信息
TvContract.Programs
描述了一个包含节目标题和开始时间等数据的电视节目
TvTrackInfo
代表音频,视频或字幕轨道
TvContentRating
描述了内容评级,允许自定义内容评级方案
TvInputManager
为系统电视应用程序提供一个API,并管理与电视输入和应用程序的交互
您还需要执行以下操作:
- 如清单中声明您的电视输入服务中所述,在清单中声明您的电视输入服务 。
- 创建服务元数据文件。
- 创建并注册您的频道和节目信息。
- 创建您的设置活动。
定义您的电视输入服务
为了您的服务,您扩展了TvInputService
类。 TvInputService
实现是一种绑定服务,其中系统服务是绑定到它的客户端。 您需要实施的服务生命周期方法如图1所示。
onCreate()
方法初始化并启动HandlerThread
,它提供了一个独立于UI线程的进程线程来处理系统驱动的操作。 在以下示例中, onCreate()
方法初始化CaptioningManager
并准备处理ACTION_BLOCKED_RATINGS_CHANGED
和ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED
操作。 这些操作描述用户更改家长控制设置时触发的系统意图,以及何时发生阻止评级列表中的更改。
@覆盖
public void onCreate(){
super.onCreate();
mHandlerThread = new HandlerThread(getClass()
.getSimpleName());
mHandlerThread.start();
mDbHandler = new Handler(mHandlerThread.getLooper());
mHandler = new Handler();
mCaptioningManager =(CaptioningManager)
getSystemService(Context.CAPTIONING_SERVICE);
setTheme(android.R.style.Theme_Holo_Light_NoActionBar);
mSessions = new ArrayList ();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(TvInputManager
.ACTION_BLOCKED_RATINGS_CHANGED);
intentFilter.addAction(TvInputManager
.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED);
registerReceiver(mBroadcastReceiver,intentFilter);
}
请参阅控制内容以获取有关处理被阻止的内容和提供家长控制的更多信息。 请参阅TvInputManager
以获取您可能想要在电视输入服务中处理的更多系统驱动操作。
TvInputService
创建一个TvInputService.Session
,实现Handler.Callback
来处理玩家状态的变化。 使用onSetSurface()
, TvInputService.Session
将Surface
设置为视频内容。 有关使用Surface
渲染视频的更多信息,请参阅将表面积分播放器 。
当用户选择频道时, TvInputService.Session
处理onTune()
事件,并通知系统TV应用程序更改内容和内容元数据。 这些notify()
方法在本次培训的控制内容和处理轨道选择中进行了描述。
定义您的设置活动
系统电视应用程序与您为电视输入定义的设置活动一起工作。 安装活动是必需的,并且必须为系统数据库提供至少一个通道记录。 系统TV应用程序无法找到电视输入的频道时调用设置活动。
设置活动向系统电视应用程序描述通过电视输入提供的频道,如下一课“ 创建和更新频道数据”所示 。
您的电视输入必须为其设置活动中的至少一个频道提供电子节目指南(EPG)数据。 您还应定期更新该数据,并考虑更新的大小和处理它的处理线程。 此外,您可以为频道提供应用链接,以指导用户查看相关内容和活动。 本课讨论如何在系统数据库中创建和更新频道和节目数据,并考虑到这些考虑因素。
尝试电视输入服务示例应用程序。
获得许可
为了使您的电视输入与EPG数据一起工作,它必须在其Android清单文件中声明读写权限,如下所示:
在数据库中注册通道
Android TV系统数据库维护电视输入的频道数据记录。 在您的设置活动中,对于您的每个频道,必须将您的频道数据映射到TvContract.Channels
类的以下字段:
COLUMN_DISPLAY_NAME
- 频道的显示名称
COLUMN_DISPLAY_NUMBER
- 显示的频道号码
COLUMN_INPUT_ID
- 电视输入服务的ID
COLUMN_SERVICE_TYPE
- 频道的服务类型
COLUMN_TYPE
- 频道的广播标准类型
COLUMN_VIDEO_FORMAT
- 频道的默认视频格式
尽管电视输入框架足够通用,可以无差别地同时处理传统广播和OTT内容,但除了上述内容之外,您还可能需要定义以下列以更好地识别传统广播频道:
COLUMN_ORIGINAL_NETWORK_ID
- 电视网络ID
COLUMN_SERVICE_ID
- 服务ID
COLUMN_TRANSPORT_STREAM_ID
- 传输流ID
如果您想为自己的频道提供应用链接详情,则需要更新其他字段。 有关应用链接字段的更多信息,请参阅添加应用链接信息 。
对于基于互联网流媒体的电视输入,相应地将您自己的值分配到上面,以便每个频道都可以唯一标识。
从您的后端服务器拉出您的频道元数据(采用XML,JSON或其他),并在您的安装活动中将这些值映射到系统数据库,如下所示:
ContentValues values = new ContentValues();
values.put(Channels.COLUMN_DISPLAY_NUMBER,channel.mNumber);
values.put(Channels.COLUMN_DISPLAY_NAME,channel.mName);
values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID,channel.mOriginalNetworkId);
values.put(Channels.COLUMN_TRANSPORT_STREAM_ID,channel.mTransportStreamId);
values.put(Channels.COLUMN_SERVICE_ID,channel.mServiceId);
values.put(Channels.COLUMN_VIDEO_FORMAT,channel.mVideoFormat);
Uri uri = context.getContentResolver()。insert(TvContract.Channels.CONTENT_URI,values);
在上面的例子中, channel
是一个保存来自后端服务器的频道元数据的对象。
目前的频道和节目信息
如图1所示,系统电视应用向用户提供频道和节目信息,如图1所示。要确保频道和节目信息与系统电视应用的频道和节目信息展示台配合使用,请遵循以下准则。
- 频道号码 (
COLUMN_DISPLAY_NUMBER
)
- 图标 (电视输入清单中的
android:icon
)
- 程式说明 (
COLUMN_SHORT_DESCRIPTION
)
- 节目标题 (
COLUMN_TITLE
)
- 频道标志 (
TvContract.Channels.Logo
)
- 使用颜色#EEEEEE来匹配周围的文字
- 不包括填充
- 海报艺术 (
COLUMN_POSTER_ART_URI
)
图1.系统电视应用程序频道和节目信息演示者。
系统TV应用通过节目指南提供相同的信息,包括海报艺术,如图2所示。
图2.系统电视应用程序指南。
更新频道数据
更新现有频道数据时,请使用update()
方法,而不是删除并重新添加数据。 在选择要更新的记录时,您可以使用Channels.COLUMN_VERSION_NUMBER
和Programs.COLUMN_VERSION_NUMBER
标识数据的当前版本。
注意:将频道数据添加到ContentProvider
可能需要一些时间。 只有在更新时才添加当前程序(当前时间为两小时内的程序),并使用同步适配器更新后台的其余通道数据。 有关示例,请参阅Android TV Live TV示例应用程序 。
批量加载频道数据
使用大量通道数据更新系统数据库时,请使用ContentResolver
applyBatch()
或bulkInsert()
方法。 这是使用applyBatch()
的一个例子:
ArrayList ops = new ArrayList <>();
int programsCount = mChannelInfo.mPrograms.size();
for(int j = 0; j
ProgramInfo program = mChannelInfo.mPrograms.get(j);
ops.add(ContentProviderOperation.newInsert(
TvContract.Programs.CONTENT_URI)
.withValues(programs.get(J))
.withValue(Programs.COLUMN_START_TIME_UTC_MILLIS,
programStartSec * 1000)
.withValue(Programs.COLUMN_END_TIME_UTC_MILLIS,
(programStartSec + program.mDurationSec)* 1000)
。建立());
programStartSec = programStartSec + program.mDurationSec;
if(j%100 == 99 || j == programsCount - 1){
尝试{
getContentResolver()。applyBatch(TvContract.AUTHORITY,ops);
} catch(RemoteException | OperationApplicationException e){
Log.e(TAG,“无法插入程序”,e);
返回;
}
ops.clear();
}
}
异步处理通道数据
数据操作(如从服务器获取流或访问数据库)不应阻塞UI线程。 使用AsyncTask
是异步执行更新的一种方法。 例如,从后端服务器加载频道信息时,可以使用AsyncTask
,如下所示:
私人静态类LoadTvInputTask扩展AsyncTask > {
private Context mContext;
public LoadTvInputTask(Context context){
mContext = context;
}
@覆盖
保护无效doInBackground(Uri ... uris){
尝试{
fetchUri(URI的[0]);
} catch(IOException e){
Log.d(“LoadTvInputTask”,“fetchUri错误”);
}
返回null;
}
私人无效fetchUri(Uri videoUri)抛出IOException {
InputStream inputStream = null;
尝试{
inputStream = mContext.getContentResolver()。openInputStream(videoUri);
XmlPullParser parser = Xml.newPullParser();
尝试{
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES,false);
parser.setInput(inputStream,null);
sTvInput = ChannelXMLParser.parseTvInput(parser);
sSampleChannels = ChannelXMLParser.parseChannelXML(parser);
} catch(XmlPullParserException e){
e.printStackTrace();
}
} finally {
if(inputStream!= null){
inputStream.close();
}
}
}
}
如果您需要定期更新EPG数据,请考虑使用Sync Adapter或JobScheduler
在空闲时间(例如每天凌晨3:00)运行更新过程。有关示例,请参阅Android TV实时电视示例应用程序 。
其他将数据更新任务与UI线程分开的技术包括使用HandlerThread
类,也可以使用Looper
和Handler
类自行实现。 有关更多信息,请参阅进程和线程 。
添加应用链接信息
频道可以使用应用链接让用户在观看频道内容时轻松启动相关活动。 频道应用使用应用链接来扩展用户参与度,方法是启动显示相关信息或附加内容的活动。 例如,您可以使用应用链接执行以下操作:
- 引导用户发现和购买相关内容。
- 提供有关当前播放内容的其他信息。
- 在观看插曲内容的同时,开始观看下一集的连续剧。
- 让用户与内容互动(例如,评分或评论内容),而不会中断内容播放。
当用户按下选择在观看频道内容时显示电视菜单时,会显示应用链接。
图1.显示频道内容时显示在Channels行上的示例应用链接。
当用户选择应用程序链接时,系统将使用频道应用程序指定的意向URI启动活动。 频道内容在应用链接活动处于活动状态时继续播放。 用户可以通过按返回返回到频道内容。
提供应用链接频道数据
Android TV会自动使用来自频道数据的信息为每个频道创建应用链接。 要提供应用链接信息,请在您的TvContract.Channels
字段中指定以下详细信息:
COLUMN_APP_LINK_COLOR
- 此频道的应用链接的重音颜色。 有关重音颜色的示例,请参见图2,标注3。
COLUMN_APP_LINK_ICON_URI
- 此频道的应用链接的应用徽章图标的URI。 有关示例应用程序徽章图标,请参见图2,标注2。
COLUMN_APP_LINK_INTENT_URI
- 此频道的应用链接的意图URI。 您可以使用带toUri(int)
创建URI,并使用toUri(int)
将该URI转换回原始意图。
COLUMN_APP_LINK_POSTER_ART_URI
- 海报艺术的URI,用作此频道的应用链接的背景。 有关示例海报图片,请参见图2,标注1。
COLUMN_APP_LINK_TEXT
- 此频道的应用链接的描述性链接文字。 有关示例应用链接描述,请参阅图2中的文本,标注3。
图2.应用程序链接详细信息
如果频道数据未指定应用链接信息,系统会创建一个默认应用链接。 系统选择默认的详细信息如下:
- 对于意图URI(
COLUMN_APP_LINK_INTENT_URI
),系统对CATEGORY_LEANBACK_LAUNCHER
类别使用ACTION_MAIN
活动,通常在应用程序清单中定义。 如果未定义此活动,则会显示无法运作的应用链接 - 如果用户点击该链接,则不会发生任何事情。
- 对于描述性文字(
COLUMN_APP_LINK_TEXT
),系统使用“打开应用程序名称 ”。 如果没有定义可行的应用链接意图URI,则系统使用“无链接可用”。
- 对于重音颜色(
COLUMN_APP_LINK_COLOR
),系统使用默认的应用程序颜色。
- 对于海报图片(
COLUMN_APP_LINK_POSTER_ART_URI
),系统使用应用的主屏幕横幅。 如果该应用不提供横幅广告,系统会使用默认的电视应用图片。
- 对于徽章图标(
COLUMN_APP_LINK_ICON_URI
),系统使用显示应用程序名称的徽章。 如果系统也将海报图片的应用横幅或默认应用图片使用,则不显示应用徽章。
您可以在应用的设置活动中为您的频道指定应用链接详情。 您可以随时更新这些应用链接详细信息,因此如果应用链接需要与频道更改匹配,请更新应用链接详细信息并根据需要调用ContentResolver.update()
。 有关更新频道数据的更多详细信息,请参阅更新频道数据 。