欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android TV框架TIF

程序员文章站 2022-07-14 23:37:43
...

Android TIF(Android TV input Framework)是Google向电视制造商提供了一套标准的API,用于创建Input模块来控制Android电视。这套API的底层实现的原理是aidl和provider,从而进行了跨进程通信。系统或第三方的应用可以通过TIF获得所有输入(input)的信源(输入的模块包括:搜台模块,MDMI模块,网络模块等),然后通过AIDL切台输出到屏幕上。

电视相关知识

在介绍这套框架之前,先复习一下电视相关的专业术语

  • HDMI:高清晰度多媒体接口(英文:High Definition Multimedia Interface,HDMI)是一种数字化视频/音频接口技术,是适合影像传输的专用型数字化接口

  • IPTV:网络电视,也叫VOD电视,三方比如说某某视频公司提供的视频资源在电视上播放

  • DTV:数字电视

  • ATV:模拟电视

TIF的组成部分

Android TV框架TIF

  • TV Provider (com.android.providers.tv.TvProvider):一个包含频道、节目和相关权限的数据库

  • TV App (com.android.tv.TvActivity):一个和用户交互的系统应用

  • TV Input Manager (android.media.tv.TvInputManager):一个中间接口层,能够让TV Inputs和TV App进行通讯

  • TV Input:可以看做是一个代表物理或者虚拟的电视接收器或者输入端口的应用。Input在TIF中可以看做是一个输入源

  • TV Input HAL (tv_input module):TV Input的硬件抽象层,可以让系统的TV inputs访问TV特有硬件

  • Parental Control:儿童锁,一种可以锁住某些频道和节目的技术

  • HDMI-CEC:一种可以通过HDMI在多种设备上进行远程控制的技术

  • CEC(Consumer Electronics Control):消费电子控制

TIF的整理使用流程

Android TV框架TIF
如上图所示,LiveTV App通过turning调用TV Input Manager获得一个session,session里面放的是一路信源的状态。TvInput将获得的Channel和Programs信息写入到/data/data/com.android.providers.tv/databases/tv.db数据库中。LiveTV App通过session以aidl的方式调用TVinputService获得相关的频道和具体的节目信息进行播放。

启动流程

SystemServer -> TvInputManagerService <-> TvInputHardwareManager <-> TvInputHal->JNI <-> HAL
Android TV框架TIF

接口介绍

TvView

负责显示播放的内容。它是一个ViewGroup的子类,它是切台的入口,内置surface用于显示视频播放的内容和通过控制session可以控制音量的大小等

TvInputService

这是一个重要的类,继承它并实现一些规范就可以实现一路Input信源供其它应用使用。

在该Service中要实现onCreatSession()方法,该方法会返回一个TvInputService.Session对象。

这里的Service在Manifest中定义时要注意要添加permission和action,添加完之后,系统的TvInputManager可以检测到该Service是一个TvInputService,也就是一路信源。

<uses-permission android:name=“android.permission.TV_INPUT_HARDWARE” />
Android TV框架TIF

TvInputService.Sssion

该session类TvView通过Tune方法会指定相应的inputId(往往是该service对应的“包名/.类名”)和uri,uri中包含对应的节目id,该tune方法会调用Session的Onturn方法中,在这个方法中解析传过来的id,根据id利用TvProvider去查询数据库的数据,设置给player,这里使用onSetSurface()方法将TvView创建的surface设置给player,然后player就在该surface上显示内容。

TvContract

介于TvProvider和TvApp之间的一层封装,它里面封装了一些uri。有两个内部类是两个JavaBean,他们分别是TvContract.channels(频道表),TvContract.Programs(频道里面的节目单)。

TvInputManager

这个是TIF的核心类,它是系统的类,可以监测到在系统的Service中注册android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMSaction的类,并将其设为一路信源。它来管理一些回调,比如video是否可用,video的大小尺寸是否变换。

通过下面的代码可以获得一个TvInputManager:

TvInputManager tvInputManager =(TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);

得到TvInputManager后我们可以遍历拿到系统当前有多少个service是TV信源:

List<TvInputInfo> list = tvInputManager.getTvInputList();
  for(TvInputInfo info:list){
  Log.i(TAG, "id:" + info.getId());
}

我们可以拿到inputId,在TvView的tune方法中设置。这里的信源就是注册了服务并没有开启,在TvView的tune方法调用的时候会开启服务。

TvInputInfo

TvInput的信息,包括频道类型,图标,名称等。

TvInputCallback

这里是TvView的一个内部类,TvInputCallBack可以反馈给TvView一些信息比如连接Service是否成功,Video是否可用等:

tvView.setCallback(new TvView.TvInputCallback() {
@Override
 public void onConnectionFailed(String inputId) {
super.onConnectionFailed(inputId);
LogUtil.i(this,"MainActivity.onConnectionFailed:"+inputId);
}
@Override
public void onDisconnected(String inputId) {
super.onDisconnected(inputId);
LogUtil.i(this,"MainActivity.onDisconnected.");
}
@Override
public void onVideoSizeChanged(String inputId, int width, int height) {
super.onVideoSizeChanged(inputId, width, height);
LogUtil.i(this,"MainActivity.onVideoSizeChanged.");
}
@Override
public void onVideoAvailable(String inputId) {
super.onVideoAvailable(inputId);
LogUtil.i(this,"MainActivity.onVideoAvailable.inputId:"+inputId);
}
@Override
public void onVideoUnavailable(String inputId, int reason) {
super.onVideoUnavailable(inputId, reason);
LogUtil.i(this,"MainActivity.onVideoUnavailable.");
}
......
});

使用注意事项

通过uri解析id

Long channelId = ContentUris.parseId(channelUri);

状态的回传

在TvView中我们如果想要获取一些播放器的状态,比如buffer状态,在开始播放之前有一个loading的状态,获取节目的size的变换,以及自定义的一些状态。下面依次说明:

loading状态的回传

在tune方法的时候使用mSimpleSession.notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);通知Video不可用,原因是tuning其他对应的状态还有:

TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:未知原因
TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL:信号弱
TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:缓冲
TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY:仅仅是音频

在视频播放的时候即在onPrepared时调用mSimpleSession.notifyVideoAvailable()

buffer状态的回传

在MediaPlayer中Buffer的两种状态,开始缓冲和结束缓冲对应的是701和702两个状态。在MediaPlayer的onInfo方法中收到了701开始调用mSimpleSession.notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING);

自定义的状态

这个使用make的方式编代码的时候才能引用,因为这个方法用@system api注解了。
可以传一个bundle对象:notifySessionEvent(@NonNull final String eventType, final Bundle eventArgs)

Program表清空问题修复

在使用TvProvider提供的Program表的时候,我这里遇到了一个问题,发现表的数据会被不定期的清空,测试那边给的也是偶现的,通过断网,切台,重启系统发现programs表总是被清空。

对于开发来说找到bug的复现步骤是最好不过的事情了。通过阅读TvProvider的源码可以看到有一个类专门负责清空Programs的数据,在EpgDataCleanupService
中会去清除当前时间以前的节目信息,在这个字段对应的时间信息COLUMN_END_TIME_UTC_MILLIS,而这个时间是以毫秒为单位的,我们服务器给的数据是以秒为单位的,所以会被清空,修改一下就可以了:

 /**
  * Clear program info that ended before {@code maxEndTimeMillis}.
  */
 @VisibleForTesting
void clearOldPrograms(long maxEndTimeMillis) {
    int deleteCount = getContentResolver().delete(
             Programs.CONTENT_URI,
             Programs.COLUMN_END_TIME_UTC_MILLIS + "<?",
             new String[] { String.valueOf(maxEndTimeMillis) });
    if (DEBUG && deleteCount > 0) {
        Log.d(TAG, "Deleted " + deleteCount + " programs"
              + " (reason: ended before "
               + DateUtils.getRelativeTimeSpanString(this, maxEndTimeMillis) + ")");
    }
 }