TV 输入服务代表媒体流来源,可让您以线性广播电视方式将媒体内容呈现为频道和节目。借助 TV 输入服务,您可以提供家长控制、收视指南信息和内容分级。TV 输入服务适用于 Android 系统 TV 应用。此应用最终控制并在 TV 上呈现频道内容。系统 TV 应用是专为设备而开发的,第三方应用无法更改。如需详细了解 TV 输入框架 (TIF) 架构及其组件,请参阅 TV 输入框架。
使用 TIF 随播内容库创建 TV 输入服务
TIF 随播内容库框架提供常用 TV 输入服务功能的可扩展实现。它仅供 OEM 用于为 Android 5.0(API 级别 21)到 Android 7.1(API 级别 25)构建渠道。
更新项目
TIF 随播内容库可供 OEM 在 androidtv-sample-inputs 代码库中使用(旧版)。有关如何在应用中添加库的示例,请查看该代码库。
在清单中声明 TV 输入服务
您的应用必须提供与 TvInputService
兼容的服务,系统可使用该服务来访问您的应用。TIF 随播内容库提供 BaseTvInputService
类,该类提供 TvInputService
的默认实现,您可以自定义实现。创建 BaseTvInputService
的子类,并在清单中将该子类声明为服务。
在清单声明内,指定 BIND_TV_INPUT
权限,以允许该服务将 TV 输入源连接到系统。系统服务执行绑定并具有 BIND_TV_INPUT
权限。系统 TV 应用通过 TvInputManager
接口向 TV 输入服务发送请求。
在服务声明中,请添加一个 intent 过滤器,将 TvInputService
指定为通过该 intent 执行的操作。另外,请将服务元数据声明为单独的 XML 资源。以下示例展示了服务声明、intent 过滤器和服务元数据声明:
<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 文件必须包含描述 TV 输入的初始配置和频道扫描的设置接口。元数据文件还应包含一个标志,用于说明用户能否录制内容。如需详细了解如何在应用中支持录制内容,请参阅支持内容录制。
服务元数据文件位于应用的 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" />
定义频道并创建设置 Activity
您的 TV 输入服务必须定义至少一个用户通过系统 TV 应用访问的频道。您应在系统数据库中注册您的频道,并提供系统无法在为您的应用找到频道时调用的设置 activity。
首先,让应用能够对系统电子收视指南 (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
对象设置节目信息,如节目的视频类型。如果您希望频道循环重复播放少数节目,请在设置节目相关信息时使用 InternalProviderData.setRepeatable()
方法,并将值设置为 true
。
实现该作业服务后,将其添加到您的应用清单中:
<service android:name=".sync.SampleJobService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true" />
最后,创建设置 Activity。您的设置 activity 应提供一种同步频道和节目数据的方法。一种方式是让用户通过 activity 中的界面执行此操作。也可在 activity 启动时,让应用自动执行此操作。当设置 activity 需要同步频道和节目信息时,该应用应启动此作业服务:
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 随播内容库提供 requestImmediateSync()
的额外过载方法,让您能够指定要同步的频道数据的持续时间(以毫秒为单位)。默认方法会同步一小时的频道数据。
TIF 随播内容库还提供 setUpPeriodicSync()
的额外过载方法,让您能够指定要同步的频道数据的持续时间,以及应发生定期同步的频率。默认方法每 12 小时同步 48 小时的频道数据。
如需详细了解频道数据和电子节目单,请参阅 使用频道数据。
处理微调请求和媒体播放
当用户选择特定频道时,系统 TV 应用使用您的应用创建的 Session
来调到请求的频道并播放内容。TIF 随播内容库提供了多个类,您可以扩展这些类来处理来自系统的频道和会话调用。
您的 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; }
当用户使用系统 TV 应用开始观看您的某个频道时,系统会调用您会话的 onPlayChannel()
方法。如果您需要在节目开始播放之前进行任何特殊的频道初始化,请替换此方法。
然后,系统获取当前已安排的节目并调用您的会话的 onPlayProgram()
方法,指定节目信息和开始时间(以毫秒为单位)。使用 TvPlayer
接口开始播放节目。
您的媒体播放器代码应实现 TvPlayer
以处理特定的播放事件。TvPlayer
类可以处理时移控制等功能,而不会增加 BaseTvInputService
实现的复杂性。
在您的会话的 getTvPlayer()
方法中,返回实现 TvPlayer
的媒体播放器。
TV 输入服务示例应用实现了使用 ExoPlayer 的媒体播放器。
使用 TV 输入框架创建 TV 输入服务
如果您的 TV 输入服务无法使用 TIF 随播内容库,则需要实现以下组件:
TvInputService
为 TV 输入提供长时间运行的后台可用性TvInputService.Session
可维护 TV 输入状态并与托管应用通信TvContract
描述提供给 TV 输入的频道和节目TvContract.Channels
代表有关 TV 频道的信息TvContract.Programs
描述电视节目,包含节目标题和开始时间等数据TvTrackInfo
代表音频、视频或字幕轨道TvContentRating
描述内容分级,允许自定义内容分级方案TvInputManager
为系统 TV 应用提供 API,并管理与 TV 输入和应用的互动
您还需要执行以下操作:
- 在清单中声明您的 TV 输入服务,如在清单中声明您的 TV 输入服务中所述。
- 创建服务元数据文件。
- 创建并注册您的频道和节目信息。
- 创建您的设置 Activity。
定义您的 TV 输入服务
对于您的服务,您可以扩展 TvInputService
类。TvInputService
实现是一种绑定服务,其中系统服务是与其绑定的客户端。您需要实现的服务生命周期方法如图 1 所示。
onCreate()
方法会初始化并启动 HandlerThread
,后者可提供与界面线程分开的进程线程来处理系统驱动的操作。在以下示例中,onCreate()
方法初始化 CaptioningManager
并准备处理 ACTION_BLOCKED_RATINGS_CHANGED
和 ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED
操作。这些操作描述了当用户更改家长控制设置以及屏蔽分级列表发生更改时触发的系统 intent。
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); }
如需详细了解如何使用已屏蔽内容并提供家长控制,请参阅
控制内容。如需详细了解您可能需要在 TV 输入服务中处理的系统驱动的操作,请参阅 TvInputManager
。
TvInputService
会创建一个实现 Handler.Callback
的 TvInputService.Session
,以处理播放器状态变化。通过 onSetSurface()
,TvInputService.Session
会使用视频内容设置 Surface
。如需详细了解如何使用 Surface
呈现视频,请参阅将播放器与 surface 集成。
TvInputService.Session
会在用户选择频道时处理 onTune()
事件,并将内容和内容元数据的变化通知系统 TV 应用。本培训中的
控制内容和处理轨道选择对这些 notify()
方法进行了进一步介绍。
定义您的设置 Activity
系统 TV 应用使用您为 TV 输入定义的设置 Activity。设置 activity 是必需的,并且必须为系统数据库提供至少一个频道记录。系统 TV 应用会在无法找到 TV 输入的频道时调用设置 activity。
设置 activity 向系统 TV 应用描述通过 TV 输入提供的频道,如下一课创建和更新频道数据中所示。