电视输入服务表示媒体流源,并允许您以线性广播电视方式将您的媒体内容呈现为频道和节目。使用电视输入服务,您可以提供家长控制、节目指南信息和内容评级。电视输入服务与 Android 系统电视应用配合使用。该应用最终控制并在电视上呈现频道内容。系统电视应用是专门为设备开发的,第三方应用无法更改。有关电视输入框架 (TIF) 架构及其组件的更多信息,请参阅 电视输入框架.
使用 TIF Companion 库创建电视输入服务
TIF Companion 库是一个框架,它提供了常见电视输入服务功能的可扩展实现。它旨在由 OEM 用于构建仅适用于 Android 5.0(API 级别 21)到 Android 7.1(API 级别 25)的频道。
更新您的项目
TIF Companion 库可供 OEM 在 androidtv-sample-inputs 存储库中进行旧版使用。请参阅该存储库以了解如何在应用中包含库的示例。
在清单中声明您的电视输入服务
您的应用必须提供一个 TvInputService
兼容的服务,系统使用该服务来访问您的应用。TIF Companion 库提供了 BaseTvInputService
类,它提供了 TvInputService
的默认实现,您可以对其进行自定义。创建 BaseTvInputService
的子类,并在清单中将其声明为服务。
在清单声明中,指定 BIND_TV_INPUT
权限以允许服务将电视输入连接到系统。系统服务执行绑定并具有 BIND_TV_INPUT
权限。系统电视应用通过 TvInputManager
接口将请求发送到电视输入服务。
在您的服务声明中,包含一个意图过滤器,该过滤器指定 TvInputService
作为要使用意图执行的操作。还将服务元数据声明为单独的 XML 资源。服务声明、意图过滤器和服务元数据声明在以下示例中显示
<service android:name=".rich.RichTvInputService" android:label="@string/rich_input_label" android:permission="android.permission.BIND_TV_INPUT"> <!-- Required filter used by the system to launch our account service. --> <intent-filter> <action android:name="android.media.tv.TvInputService" /> </intent-filter> <!-- An XML file which describes this input. This provides pointers to the RichTvInputSetupActivity to the system/TV app. --> <meta-data android:name="android.media.tv.input" android:resource="@xml/richtvinputservice" /> </service>
在单独的 XML 文件中定义服务元数据。服务元数据 XML 文件必须包含一个设置接口,该接口描述电视输入的初始配置和频道扫描。元数据文件还应包含一个标志,指示用户是否能够录制内容。有关如何在应用中支持录制内容的更多信息,请参阅 支持内容录制.
服务元数据文件位于您的应用程序的 XML 资源目录中,并且必须与您在清单中声明的资源名称匹配。使用上一示例中的清单条目,您将在 res/xml/richtvinputservice.xml
创建 XML 文件,其内容如下:
<?xml version="1.0" encoding="utf-8"?> <tv-input xmlns:android="http://schemas.android.com/apk/res/android" android:canRecord="true" android:setupActivity="com.example.android.sampletvinput.rich.RichTvInputSetupActivity" />
定义频道并创建您的设置活动
您的电视输入服务必须定义至少一个频道,用户可以通过系统电视应用程序访问该频道。您应该在系统数据库中注册您的频道,并提供一个设置活动,系统在找不到您的应用程序的频道时调用该活动。
首先,启用您的应用程序从系统电子节目指南 (EPG) 中读取和写入,其数据包括用户可用的频道和节目。要启用您的应用程序执行这些操作,并在设备重启后与 EPG 同步,请将以下元素添加到您的应用程序清单中:
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED "/>
添加以下元素以确保您的应用程序在 Google Play 商店中显示为在 Android TV 中提供内容频道的应用程序
<uses-feature android:name="android.software.live_tv" android:required="true" />
接下来,创建一个扩展 EpgSyncJobService
类的类。这个抽象类使创建在系统数据库中创建和更新频道的作业服务变得容易。
在您的子类中,在 getChannels()
中创建并返回您的完整频道列表。如果您的频道来自 XMLTV 文件,请使用 XmlTvParser
类。否则,使用 Channel.Builder
类以编程方式生成频道。
对于每个频道,系统在需要特定时间窗口内在该频道上可以观看的节目列表时,会调用 getProgramsForChannel()
。返回该频道的 Program
对象列表。使用 XmlTvParser
类从 XMLTV 文件中获取节目,或使用 Program.Builder
类以编程方式生成节目。
对于每个 Program
对象,使用 InternalProviderData
对象来设置节目信息,例如节目的视频类型。如果您只有少量节目,您希望频道以循环方式重复播放,请在设置有关您的节目的信息时,使用 true
的值将 InternalProviderData.setRepeatable()
方法设置为 true
。
在您实现作业服务后,将其添加到您的应用程序清单中
<service android:name=".sync.SampleJobService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true" />
最后,创建一个设置活动。您的设置活动应提供一种同步频道和节目数据的方法。一种方法是让用户通过活动中的 UI 执行此操作。您也可以让应用程序在活动启动时自动执行此操作。当设置活动需要同步频道和节目信息时,应用程序应启动作业服务
Kotlin
val inputId = getActivity().intent.getStringExtra(TvInputInfo.EXTRA_INPUT_ID) EpgSyncJobService.cancelAllSyncRequests(getActivity()) EpgSyncJobService.requestImmediateSync( getActivity(), inputId, ComponentName(getActivity(), SampleJobService::class.java) )
Java
String inputId = getActivity().getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID); EpgSyncJobService.cancelAllSyncRequests(getActivity()); EpgSyncJobService.requestImmediateSync(getActivity(), inputId, new ComponentName(getActivity(), SampleJobService.class));
使用 requestImmediateSync()
方法同步作业服务。用户必须等待同步完成,因此您应该将请求周期保持在较短时间内。
使用 setUpPeriodicSync()
方法让作业服务定期在后台同步频道和节目数据
Kotlin
EpgSyncJobService.setUpPeriodicSync( context, inputId, ComponentName(context, SampleJobService::class.java) )
Java
EpgSyncJobService.setUpPeriodicSync(context, inputId, new ComponentName(context, SampleJobService.class));
TIF Companion Library 提供了 requestImmediateSync()
的另一个重载方法,该方法允许您指定要同步的频道数据的持续时间(以毫秒为单位)。默认方法同步一小时的频道数据。
TIF Companion Library 还提供了一个额外的 setUpPeriodicSync()
重载方法,该方法允许您指定要同步的频道数据的持续时间,以及定期同步的频率。默认方法每 12 小时同步 48 小时的频道数据。
有关频道数据和 EPG 的更多详细信息,请参阅 使用频道数据。
处理调谐请求和媒体播放
当用户选择特定频道时,系统电视应用程序使用由您的应用程序创建的 Session
调谐到请求的频道并播放内容。TIF Companion Library 提供了几个您可以扩展的类来处理来自系统的频道和会话调用。
您的 BaseTvInputService
子类创建会话,这些会话处理调谐请求。覆盖 onCreateSession()
方法,创建从 BaseTvInputService.Session
类扩展的会话,并使用您的新会话调用 super.sessionCreated()
。在以下示例中,onCreateSession()
返回一个扩展 BaseTvInputService.Session
的 RichTvInputSessionImpl
对象
Kotlin
override fun onCreateSession(inputId: String): Session = RichTvInputSessionImpl(this, inputId).apply { setOverlayViewEnabled(true) }
Java
@Override public final Session onCreateSession(String inputId) { RichTvInputSessionImpl session = new RichTvInputSessionImpl(this, inputId); session.setOverlayViewEnabled(true); return 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
操作。这些操作描述了当用户更改家长控制设置时,以及当阻止的评级列表发生更改时触发的系统意图。
Kotlin
override fun onCreate() { super.onCreate() handlerThread = HandlerThread(javaClass.simpleName).apply { start() } dbHandler = Handler(handlerThread.looper) handler = Handler() captioningManager = getSystemService(Context.CAPTIONING_SERVICE) as CaptioningManager setTheme(android.R.style.Theme_Holo_Light_NoActionBar) sessions = mutableListOf<BaseTvInputSessionImpl>() val intentFilter = IntentFilter().apply { addAction(TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED) addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED) } registerReceiver(broadcastReceiver, intentFilter) }
Java
@Override public void onCreate() { super.onCreate(); handlerThread = new HandlerThread(getClass() .getSimpleName()); handlerThread.start(); dbHandler = new Handler(handlerThread.getLooper()); handler = new Handler(); captioningManager = (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE); setTheme(android.R.style.Theme_Holo_Light_NoActionBar); sessions = new ArrayList<BaseTvInputSessionImpl>(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(TvInputManager .ACTION_BLOCKED_RATINGS_CHANGED); intentFilter.addAction(TvInputManager .ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED); registerReceiver(broadcastReceiver, intentFilter); }
有关使用阻止的内容和提供家长控制的更多信息,请参阅 控制内容。有关您可能希望在电视输入服务中处理的更多系统驱动操作,请参阅 TvInputManager
。
该 TvInputService
创建一个 TvInputService.Session
,该会话实现 Handler.Callback
来处理播放器状态更改。使用 onSetSurface()
,该 TvInputService.Session
使用视频内容设置 Surface
。有关使用 Surface
渲染视频的更多信息,请参阅 将播放器与 Surface 集成。
该 TvInputService.Session
在用户选择频道时处理 onTune()
事件,并通知系统电视应用程序内容和内容元数据的更改。这些 notify()
方法在本培训中进一步介绍了 控制内容 和 处理轨道选择。
定义您的设置活动
系统电视应用程序与您为电视输入定义的设置活动协同工作。设置活动是必需的,并且必须为系统数据库提供至少一个频道记录。当系统电视应用程序找不到电视输入的频道时,它会调用设置活动。
设置活动向系统电视应用程序描述了通过电视输入提供的频道,如下一课 创建和更新频道数据 中所演示。