最近在做Android TV O的项目,需要在TV 桌面添加自定义频道/节目,节目的背景图片要显示为SD卡或者缓存目录里面的图片。

  1. 添加自定义频道
  2. 节目背景显示本地目录的图片

一、添加频道

1. 首先新建频道、节目实体类,属性如下。

public class MediaChannel {private final String mName;private final String mDescription;private final String mMediaUri;private final String mBgImage;private final String mTitle;private final String mMediaChannelId;private List<MediaProgram> mPrograms;private boolean mChannelPublished;private long mChannelId;MediaChannel(String name, List<MediaProgram> programs, String mediaChannelId) {mName = name;mTitle = "playlist title";mDescription = "playlist description";mMediaUri = "dsf";mBgImage = "asdf";mPrograms = programs;mMediaChannelId = mediaChannelId;}// 省略 set get toString }public class MediaProgram implements Parcelable {private final String mMediaProgramId;private final String mContentId;private final String mTitle;private final String mDescription;private final String mBgImageUrl;private final String mCardImageUrl;private final String mMediaUrl;private final String mPreviewMediaUrl;private final String mCategory;private long mProgramId;private int mViewCount;MediaProgram(String title, String description, String bgImageUrl, String cardImageUrl,String category, String mediaProgramId, String contentId) {mMediaProgramId = mediaProgramId;mContentId = contentId;mTitle = title;mDescription = description;mBgImageUrl = bgImageUrl;mCardImageUrl = cardImageUrl;mMediaUrl = "";mPreviewMediaUrl = "";mCategory = category;}// 省略 set get toString }

2. 初始化频道、节目信息

private void initChannel() {Uri usbUri = getUSBCardImageFileUri();Uri pvrUri = getPVRCardImageFileUri();grantUriPermissionToApp("com.google.android.tvlauncher", usbUri);grantUriPermissionToApp("com.google.android.tvlauncher", pvrUri);String bgImageUrl = "";String usbCardImageUrl = getUSBCardImageFileUri().toString();String pvrCardImageUrl = getPVRCardImageFileUri().toString();int mediaProgramId = 1;int contentId = 0;MediaProgram usbProgram = new MediaProgram("USB", "usb description", bgImageUrl, usbCardImageUrl,"USB category", Integer.toString(mediaProgramId), Integer.toString(contentId ++));MediaProgram pvrProgram = new MediaProgram("PVR", "pvr description", bgImageUrl, pvrCardImageUrl,"PVR category", Integer.toString(mediaProgramId), Integer.toString(contentId ++));List<MediaProgram> programs = new ArrayList<>();programs.add(usbProgram);programs.add(pvrProgram);mChannelId = LocalDataManager.getChannelId(this);mChannel = new MediaChannel("MediaChannel", programs, Long.toString(mChannelId));
}private Uri getUSBCardImageFileUri() {String sdPath = Environment.getExternalStorageDirectory().getPath();File file = new File(sdPath + "/Pictures/mediachannel/usb_thumbnail.jpg");Uri uri = FileProvider.getUriForFile(this, "com.rogera.mediaplaychannel.fileprovider", file);Log.v(TAG, "uri:" + uri.toString());return uri;}private Uri getPVRCardImageFileUri() {String sdPath = Environment.getExternalStorageDirectory().getPath();File file = new File(sdPath + "/Pictures/mediachannel/pvr_thumbnail.jpg");Uri uri = FileProvider.getUriForFile(this, "com.rogera.mediaplaychannel.fileprovider", file);Log.v(TAG, "uri:" + uri.toString());return uri;}private void grantUriPermissionToApp(String packageName, Uri uri) {grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);}

3.添加频道、节目

mChannelId = MediaTVProvider.addChannel(MainActivity.this, mChannel);

mChannel 为initChannel() 方法里面初始化的实体类

贴出核心类MediaTVProvider.java

package com.rogera.mediaplaychannel;import android.content.ComponentName;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.support.annotation.DrawableRes;
import android.support.annotation.WorkerThread;
import android.support.media.tv.Channel;
import android.support.media.tv.ChannelLogoUtils;
import android.support.media.tv.PreviewProgram;
import android.support.media.tv.TvContractCompat;
import android.text.TextUtils;
import android.util.Log;import java.util.List;/*** Created by rogera on 2017/12/30.*/
public class MediaTVProvider {private static final String TAG = "MediaTVProvider";private static final String SCHEME = "tvmediachannels";private static final String APPS_LAUNCH_HOST = "com.google.android.tvmediachannels";private static final String PLAY_MEDIA_ACTION_PATH = "playMedia";private static final String START_APP_ACTION_PATH = "startApp";private static final Uri PREVIEW_PROGRAMS_CONTENT_URI =Uri.parse("content://android.media.tv/preview_program");static private String createInputId(Context context) {ComponentName cName = new ComponentName(context, MainActivity.class.getName());return TvContractCompat.buildInputId(cName);}@WorkerThreadstatic long addChannel(Context context, MediaChannel mediaChannel) {String channelInputId = createInputId(context);Channel channel = new Channel.Builder().setDisplayName(mediaChannel.getName()).setDescription(mediaChannel.getDescription()).setType(TvContractCompat.Channels.TYPE_PREVIEW).setInputId(channelInputId).setAppLinkIntentUri(Uri.parse(SCHEME + "://" + APPS_LAUNCH_HOST+ "/" + START_APP_ACTION_PATH)).setInternalProviderId(mediaChannel.getMediaChannelId()).build();Uri channelUri = context.getContentResolver().insert(TvContractCompat.Channels.CONTENT_URI,channel.toContentValues());if (channelUri == null || channelUri.equals(Uri.EMPTY)) {Log.e(TAG, "addChannel Insert channel failed");return 0;}long channelId = ContentUris.parseId(channelUri);mediaChannel.setChannelPublishedId(channelId);writeChannelLogo(context, channelId, R.drawable.media_logo);List<MediaProgram> programs = mediaChannel.getMediaPrograms();int weight = programs.size();for (int i = 0; i < programs.size(); ++i, --weight) {MediaProgram mp = programs.get(i);final String mediaProgramId = mp.getMediaProgramId();final String contentId = mp.getContentId();PreviewProgram program = new PreviewProgram.Builder().setChannelId(channelId).setTitle(mp.getTitle()).setDescription(mp.getDescription()).setPosterArtUri(Uri.parse(mp.getCardImageUrl())).setIntentUri(Uri.parse(SCHEME + "://" + APPS_LAUNCH_HOST+ "/" + PLAY_MEDIA_ACTION_PATH + "/" + mediaProgramId))//.setPreviewVideoUri(Uri.parse(mp.getPreviewMediaUrl())).setInternalProviderId(mediaProgramId).setContentId(contentId).setWeight(weight).setType(TvContractCompat.PreviewPrograms.TYPE_CLIP).build();Uri programUri = context.getContentResolver().insert(PREVIEW_PROGRAMS_CONTENT_URI,program.toContentValues());if (programUri == null || programUri.equals(Uri.EMPTY)) {Log.e(TAG, "addChannel Insert program failed");} else {mp.setProgramId(ContentUris.parseId(programUri));}}return channelId;}@WorkerThreadstatic void deleteChannel(Context context, long channelId) {int rowsDeleted = context.getContentResolver().delete(TvContractCompat.buildChannelUri(channelId), null, null);if (rowsDeleted < 1) {Log.e(TAG, "Delete channel failed");}}@WorkerThreadpublic static void deleteProgram(Context context, MediaProgram program) {deleteProgram(context, program.getProgramId());}@WorkerThreadstatic void deleteProgram(Context context, long programId) {int rowsDeleted = context.getContentResolver().delete(TvContractCompat.buildPreviewProgramUri(programId), null, null);if (rowsDeleted < 1) {Log.e(TAG, "Delete program failed");}}/*** Writes a drawable as the channel logo.** @param channelId  identifies the channel to write the logo.* @param drawableId resource to write as the channel logo. This must be a bitmap and not, say*                   a vector drawable.*/@WorkerThreadstatic private void writeChannelLogo(Context context, long channelId,@DrawableRes int drawableId) {Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), drawableId);ChannelLogoUtils.storeChannelLogo(context, channelId, bitmap);}@WorkerThreadstatic void updateMediaProgram(Context context, MediaProgram mediaProgram) {long programId = mediaProgram.getProgramId();Uri programUri = TvContractCompat.buildPreviewProgramUri(programId);try (Cursor cursor = context.getContentResolver().query(programUri, null, null, null,null)) {if (!cursor.moveToFirst()) {Log.e(TAG, "Update program failed");}PreviewProgram porgram = PreviewProgram.fromCursor(cursor);PreviewProgram.Builder builder = new PreviewProgram.Builder(porgram).setTitle(mediaProgram.getTitle());int rowsUpdated = context.getContentResolver().update(programUri,builder.build().toContentValues(), null, null);if (rowsUpdated < 1) {Log.e(TAG, "Update program failed");}}}static void publishProgram(Context context, MediaProgram mediaProgram, long channelId, int weight) {final String mediaProgramId = mediaProgram.getMediaProgramId();PreviewProgram program = new PreviewProgram.Builder().setChannelId(channelId).setTitle(mediaProgram.getTitle()).setDescription(mediaProgram.getDescription()).setPosterArtUri(Uri.parse(mediaProgram.getCardImageUrl())).setIntentUri(Uri.parse(SCHEME + "://" + APPS_LAUNCH_HOST+ "/" + PLAY_MEDIA_ACTION_PATH + "/" + mediaProgramId)).setPreviewVideoUri(Uri.parse(mediaProgram.getPreviewMediaUrl())).setInternalProviderId(mediaProgramId).setWeight(weight).setType(TvContractCompat.PreviewPrograms.TYPE_MOVIE).build();Uri programUri = context.getContentResolver().insert(PREVIEW_PROGRAMS_CONTENT_URI,program.toContentValues());if (programUri == null || programUri.equals(Uri.EMPTY)) {Log.e(TAG, "Insert program failed");return;}mediaProgram.setProgramId(ContentUris.parseId(programUri));}@WorkerThreadstatic void setProgramViewCount(Context context, long programId, int numberOfViews) {Uri programUri = TvContractCompat.buildPreviewProgramUri(programId);try (Cursor cursor = context.getContentResolver().query(programUri, null, null, null,null)) {if (!cursor.moveToFirst()) {return;}PreviewProgram existingProgram = PreviewProgram.fromCursor(cursor);PreviewProgram.Builder builder = new PreviewProgram.Builder(existingProgram).setInteractionCount(numberOfViews).setInteractionType(TvContractCompat.PreviewProgramColumns.INTERACTION_TYPE_VIEWS);int rowsUpdated = context.getContentResolver().update(TvContractCompat.buildPreviewProgramUri(programId),builder.build().toContentValues(), null, null);if (rowsUpdated != 1) {Log.e(TAG, "Update program failed");}}}
}

二、节目背景显示本地目录的图片

对于显示本地图片,需要使用FileProvider 获取图片文件的uri然后设置给节目。如果使用 Uri.fromFile(new File(filePath) 这种方式,就会报Permission问题:
class java.io.FileNotFoundException: /storage/emulated/0/Pictures/mediachannel/usb_thumbnail.jpg (Permission denied)

使用FileProvider分享文件给其他应用需要给对应的应用赋予读权限,可以通过如下两种方式:

 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);grantUriPermissionToApp("com.google.android.tvlauncher", usbUri);

这里只能使用第二种方式了。com.google.android.tvlauncher 为TV launcher的包名。

1. 使用FileProvider首先需要在AndroidManifest.xml 节点下申明

    <providerandroid:name="android.support.v4.content.FileProvider"android:authorities="com.rogera.mediaplaychannel.fileprovider"android:grantUriPermissions="true"android:exported="false"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/filepaths" /></provider>

2. 在res下xml文件夹下新建filepaths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"><external-path path="Pictures" name="pictures" />
</paths>

3. 获取文件URI

File file = new File(sdPath + "/Pictures/mediachannel/usb_thumbnail.jpg");
Uri uri = FileProvider.getUriForFile(this, "com.rogera.mediaplaychannel.fileprovider", file);

三、其他说明

1、sdcard里面的文件是push进去的,是假设应用获取U盘里面的电影/图片/音乐 生成的缩略图。点击桌面的usb节目就会播放相应的电影/图片/音乐。

2. 在桌面添加频道、节目需要申请EPG权限,SD需要申请storage权限

<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
<uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

3. 同时需要在gradle添加如下依赖

implementation 'com.android.support:leanback-v17:26.1.0'
implementation 'com.android.support:support-tv-provider:26.1.0'

4. 上图啦

Google官方参考:https://developer.android.google.cn/training/tv/tif/channel.html#update

如何在Android TV 桌面添加自定义频道/节目相关推荐

  1. 如何在Android实现桌面清理内存简单Widget小控件

    如何在Android实现桌面清理内存简单Widget小控件 我们经常会看到类似于360.金山手机卫士一类的软件会带一个widget小控件,显示在桌面上,上面会显示现有内存大小,然后会带一个按键功能来一 ...

  2. 如何在Android TV上自定义推荐行

    When you fire up Android TV, the first thing you see is a list of movies and shows the system thinks ...

  3. Android 应用卸载按钮怎么打开,如何在Android TV上卸载应用程序 | MOS86

    我有一个问题109mh1112为什么你有这么多的应用程序安装在你的Android电视上?你知道,就像你所安装的所有这些东西,只是为了尝试,然后再没有使用过?是的,那东西这是关于您清理您的小机顶盒的时间 ...

  4. android tv 使用_如何在Android TV上设置和使用家长控制

    android tv 使用 Syafiq Adnan/Shutterstock.comSyafiq Adnan / Shutterstock.com Parental controls are ess ...

  5. android tv 桌面,自定义RecyclerView打造Android TV桌面

    前言 Android TV Launcher页在RecyclerView出来之前大家用GridView去实现.TV开发有五向键的监听,遥控器hover监听,点击事件等.用GridView去处理焦点是有 ...

  6. 如何在Android Framework中添加自定义硬件编解码器?

    原文:How to add custom hardware codec to Android Framework? http://gopinaths.gitlab.io/post/custom_har ...

  7. android live 电视 源码,GitHub - mxiaoguang/LivePlayback: Android TV直播电视节目 ,包含各央视频道及卫视频道...

    Android TV直播电视节目 更多技术博客,项目,欢迎关注公众号: Android TV开发交流群:135622564 传统电视直播节目,在Android TV上起着越来越重要的作用,央视,各地卫 ...

  8. android tv 云播放器,Android TV开发总结(六)构建一个TV app的直播节目实例

    近年来,Android TV的迅速发展,传统的有线电视受到较大的冲击,在TV上用户同样也可以看到各个有线电视的直播频道,相对于手机,这种直播节目,体验效果更佳,尤其是一样赛事节目,大屏幕看得才够痛快, ...

  9. 一个 TV app 的直播节目实例,包含各央视频道及卫视频道

    LivePlayback 项目地址:hejunlin2013/LivePlayback 简介:一个 TV app 的直播节目实例,包含各央视频道及卫视频道 PS:注册过魅族帐号的同鞋,帮忙投下魅族开发 ...

最新文章

  1. Java笔记整理-02.Java基础语法
  2. 判断数组中某个元素除自身外是否和其他数据不同_布隆过滤器,我也是个处理过 10 亿数据的人...
  3. SAP实施项目中顾问与客户的有效沟通
  4. qt 报错pcap.h头文件不存在
  5. ASP.NET Core分布式项目实战(运行Consent Page)--学习笔记
  6. OpenCV-图像NaN处理
  7. linux从哪里入侵电脑,linux入侵的基本命令网站安全 -电脑资料
  8. 最新王者荣耀发卡网源码,手机电脑自适应
  9. 课堂经验值管理小程序_济南小程序开发,微信小程序应用开发实现单店管理
  10. 美通企业日报 | 阿里、腾讯占领公有云市场半壁江山;2018年雅思考生350万人次创新高...
  11. C#生成PDF格式的合同文件
  12. C++、MFC往Word模板中添加图片
  13. CFSSL: 证书管理工具:4:生成Kubernetes集群证书
  14. 给你的公众号添加一个智能机器人
  15. TAT-QA: A Question Answering Benchmark on a Hybrid of Tabular and Textual Content in Finance
  16. ⅰsee是什么意思_see是什么意思
  17. ios中达到Android中九妹图的效果
  18. spring alibaba seata 分布式事务实践
  19. 电感、电容、电阻基本元器件参数选型
  20. 2017从Android工程师到策略工程师的转型——序章

热门文章

  1. ad怎么修改栅格_AD软件的3种栅格设置详解
  2. 天翼云监控客户pc端及手机端下载安装教程
  3. 使用C大调编排一首歌曲
  4. 返回一个月中最大的天数(适用于2000年到2099年之间)
  5. 常见前端九十道面试题及答案-韩烨
  6. python-Matplotlib数据可视化
  7. linux系统 服务器 安装,服务器上怎么安装linux系统
  8. java 异常恢复_Java学习之异常处理
  9. 50欧姆线设计 高频pcb_50欧阻抗天线设计
  10. PDF怎么删除空白页,PDF删除空白页的方法