如何在Android(安卓)TV 桌面添加自定义频道/节目
16lz
2021-01-26
最近在做Android TV O的项目,需要在TV 桌面添加自定义频道/节目,节目的背景图片要显示为SD卡或者缓存目录里面的图片。
- 添加自定义频道
- 节目背景显示本地目录的图片
一、添加频道
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 mPrograms; private boolean mChannelPublished; private long mChannelId; MediaChannel(String name, List 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 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); } @WorkerThread static 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 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; } @WorkerThread static 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"); } } @WorkerThread public static void deleteProgram(Context context, MediaProgram program) { deleteProgram(context, program.getProgramId()); } @WorkerThread static 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. */ @WorkerThread static private void writeChannelLogo(Context context, long channelId, @DrawableRes int drawableId) { Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), drawableId); ChannelLogoUtils.storeChannelLogo(context, channelId, bitmap); } @WorkerThread static 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)); } @WorkerThread static 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 节点下申明
2. 在res下xml文件夹下新建filepaths.xml
<?xml version="1.0" encoding="utf-8"?>
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权限
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(安卓)Studio V3.12环境下TV开发教程(十)添加引导步骤
- Android(安卓)添加数据到本地Excel表中
- Android(安卓)studio 2.2 JNI ffmpeg 简单的播放器(这里只有视频
- 【Android(安卓)Studio 4.0.0】高版本Android(安卓)Studio 打开
- 添加android系统通知
- android中如何添加一个监听按钮,点击之后从一个activity跳转到另
- Window和WindowManager(Android开发艺术探索学习笔记)
- Android动态添加布局LayoutInflater简单用法
- Android(安卓)NDK 编译过程中遇到错误 exception handling disab