Android TV 开发之 TV视频播放器

Posted 初学者_Study

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android TV 开发之 TV视频播放器相关的知识,希望对你有一定的参考价值。


android TV视频播放器VideoView

不想往下看可以直接在GitHub上面克隆到自己的项目中
​​​GitHub地址​

闲谈

  最近公司又给了一个新任务,说要做电视机顶盒开发,这个机顶盒开发之前也没有接触过啊,没经验,这使我走了很多坑,写这个日志就是帮助和自己一样的新手开发代码顺利一些,少走弯路,如果你是有经验的,请无视我的废话和文章,因为我将从创建项目开始写这篇日志,这意味着会比较无聊,你要有心理准备,当然如果你是一个新手的话,恰好最近又要做AndroidTV的开发,那么你就来对地方了,好了,话不多说了,进入正题吧。

简介

  Android TV 开发,顾名思义也就是电视开发,说的高端点就是智能电视,相信你们家里都有吧,不要说你家至今用着十几年前的老电视,那我无话可说了,TV开发的资源我从网上找到的都是一些零零碎碎的,不够完整,而有一些项目还要你给积分才能去下载看,不够开源,痛定思痛,我决定自己弄一个开源的项目出来,自己来维护,学习中开发,也有可能TV这方面的文章我还会写,也有可能只写这一篇,接下来我们从创建TV项目开始。

正题

开发准备:
  电脑(笔记本、台式都行)、JDK环境变量配置(PS:因为是Java写的,想了解Kotlin的可以和我沟通)、Android Studio3.5(开发软件)。

创建项目:

第一步:File → New → New Project

Android


第二步:选择TV 然后创建一个空的项目也就是点左边的 Add No Activity(PS:为什么不用谷歌的框架呢,因为这个第一次我觉得自己从头来一遍会比较好,这样更有帮助,后面你再用这个框架,而且这个空项目进去之后在,AndroidManifest.xml里面还是要添加leanback的,下面继续吧)

Android


第三步:确定你的开发信息无误之后点击Finish,创建这个项目(Android Studio3.5中开发TV 最低的API版本为5.0,低于这个版本则不能正常运行)

Android


第四步:然后我们打开这个项目

Android


第五步:可以看到AndroidManifest.xml文件中没有运行Main的一个主活动,所以上面的是机器人头会有一个红色的小×,然后创建一个MainActivity之后再改动这个文件。

Android


第六步:鼠标右键你的包名 → New → Activity → Empty Activity(空的活动)

Android


Android


上图中我勾选了Launcher Activity(勾中的意思就是把你的这个Activity作为运行的主入口,默认是不勾中的,因为我的AndroidManifest.xml文件中没有主运行活动,所以我勾中),然后点击Finish

Android


创建好之后打开AndroidManifest.xml文件可以看到,中间多个一部分配置内容,主要的意思就是将MainActivity这个活作为启动入口,然后可以看到上方的mainfest下面有一条红色波浪线,这说明有问题,然后具体看看是什么问题。

鼠标放在上面可以看到有一个tip,

Android


意思很明显要我们配置leanback眼熟不? 然后我们鼠标点击这个mainfest使用快捷键Alt + Enter 引入配置,引入三次之后发现不报错了

Android


这个时候这个项目已经初步搭建完成了,(PS:我是不是很啰嗦啊)配置项目:

你以为这个时候你就能运行了吗?不,你不能,首先你得找一台虚拟机或者一台真机,我这里用的是真机,然后用usb线连接上你的电脑,然后打开这个机顶盒的开发者模式,再启用usb调试,这个时候你的Android Studio中就会有读取到你的设备名称(PS:这一块有时候比较看人品,不见得你就能成功,所以我成功了!)

Android


这是我的设备名称,接下来就是配置项目的build.gradle,在android的闭包里面加入:

compileOptions 
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8

然后在dependencies闭包中加入:(这几句代码有什么用呢,因为我不想再写findById了,所以用BindView的方式来做)

//butterknife
implementation com.jakewharton:butterknife:10.1.0
annotationProcessor com.jakewharton:butterknife-compiler:10.1.0

build.gradle的完整代码如下:

apply plugin: com.android.application

android
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig
applicationId "com.llw.androidtvdemo"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"


compileOptions
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8



buildTypes
release
minifyEnabled false
proguardFiles getDefaultProguardFile(proguard-android-optimize.txt), proguard-rules.pro




dependencies
implementation fileTree(dir: libs, include: [*.jar])
implementation androidx.leanback:leanback:1.0.0
implementation androidx.appcompat:appcompat:1.1.0
implementation androidx.constraintlayout:constraintlayout:1.1.3

//butterknife
implementation com.jakewharton:butterknife:10.1.0
annotationProcessor com.jakewharton:butterknife-compiler:10.1.0

build.gradle改动代码之后记得sync一下啊,否则改动无效的。

加入上述代码后,点击File → Settings → Plugins → Marketolace → 输入butterknife然后搜索 → 再下载安装 安装之后会提醒你重启AS(PS:Android Studio的简称)

重启之后我们再layout_main.xml文件中写一点东西

Android

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_test"
android:text="Hello TV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

<Button
android:layout_marginTop="20dp"
android:id="@+id/btn_test"
android:text="TV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>

布局很简单,纵向线性布局,里面是一个TextView和Button,然后我们换成横屏的布局预览效果

Android


点击Preview就可以看到你的预览布局,然后点击那个下拉窗口,选择TV,

Android


TV有1920 X 1080和 1280 X 720两种尺寸的,这也是市面上常用的,当然你也可以通过custom来自定义,这里我们选择TV 720p的,这时候我们打开MainActivity.java文件,然后选中布局文件,鼠标右键,点击Generate生成也可通过快捷键 Alt + Insert

Android


然后会弹出一个小窗口

Android


点击之后会出现如下弹窗,可以实例化控件并控件添加点击事件,这里给按钮添加一个点击事件,然后我们看一下MainActivity.java中的代码,并在点击的时候弹出一个Toast消息

Android


MainActivity.java

package com.llw.androidtvdemo;

import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends AppCompatActivity

@BindView(R.id.tv_test)
TextView tvTest;
@BindView(R.id.btn_test)
Button btnTest;

@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);


@OnClick(R.id.btn_test)
public void onViewClicked()
//Toast 提示
Toast.makeText(this,tvTest.getText().toString(),Toast.LENGTH_SHORT).show();

这个时候你应该迫不及待的想要运行一下了吧,我们还有一步就是主题的设置

打开values下面的styles.xml文件

Android


我们不用它这个主题,重新创建一个

"AppTheme2" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>

styles.xml的完整代码如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>

<style name="AppTheme" parent="@style/Theme.Leanback" />

<!-- Base application theme. -->
<style name="AppTheme2" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>

</resources>

然后你会发现少几个颜色,这时候我们在values文件夹下面创建一个colors.xml的文件
colors.xml代码如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
<color name="tx_onclick">#1EBADE</color>
</resources>

然后就要使用我们新创建的主题了,打开AndroidManifest.xml文件

Android


修改,AppTheme,改成AppTheme2,然后运行项目.运行效果如下图

Android


这个时候你没有想过,我怎么点击这个按钮呢?电视机都是用遥控器的啊,遥控器又怎么操作呢?

这些问题一定在你的脑海里面环绕着,我们注意到,电视机使用遥控器,而我们的手机使用手指触摸点击,这个不能混为一谈,所以电视上需要用到焦点电视上都是通过控件获取焦点来实现点击效果的,我们在布局文件的button中写入

android:focusable="true"

意思就是可以获取到焦点,为false则不可获取焦点,
在代码里

btnTest.setFocusable(true);

为false则不可获取焦点。
在已知控件ID的情况下我们可以设置上下左右的移动控件,

android:nextFocusUp="@id/tv_test"
android:nextFocusDown="@id/tv_test"
android:nextFocusLeft="@id/tv_test"
android:nextFocusRight="@id/tv_test"

代码中:

.setNextFocusUpId(R.id.tv_test);
btnTest.setNextFocusDownId(R.id.tv_test);
btnTest.setNextFocusLeftId(R.id.tv_test);
btnTest.setNextFocusRightId(R.id.tv_test);

了解这个之后,我们还得知道遥控器的按键监听,毕竟是用遥控器来操作的啊,按键监听代码如下:

private String TAG = "key";

@Override
public boolean onKeyDown(int keyCode, KeyEvent event)

switch (keyCode)

case KeyEvent.KEYCODE_ENTER: //确定键enter
case KeyEvent.KEYCODE_DPAD_CENTER:
Log.d(TAG, "enter--->");
break;

case KeyEvent.KEYCODE_BACK: //返回键
Log.d(TAG,"back--->");
return true; //这里由于break会退出,所以我们自己要处理掉 不返回上一层

case KeyEvent.KEYCODE_SETTINGS: //设置键
Log.d(TAG, "setting--->");
break;

case KeyEvent.KEYCODE_DPAD_DOWN: //向下键

/* 实际开发中有时候会触发两次,所以要判断一下按下时触发 ,松开按键时不触发
* exp:KeyEvent.ACTION_UP
*/
if (event.getAction() == KeyEvent.ACTION_DOWN)
Log.d(TAG, "down--->");


break;

case KeyEvent.KEYCODE_DPAD_UP: //向上键
Log.d(TAG, "up--->");

break;

case KeyEvent.KEYCODE_0: //数字键0
Log.d(TAG, "0--->");

break;
case KeyEvent.KEYCODE_DPAD_LEFT: //向左键
Log.d(TAG, "left--->");
break;

case KeyEvent.KEYCODE_DPAD_RIGHT: //向右键
Log.d(TAG, "right--->");
break;

case KeyEvent.KEYCODE_INFO: //info键
Log.d(TAG, "info--->");

break;

case KeyEvent.KEYCODE_PAGE_DOWN: //向上翻页键
case KeyEvent.KEYCODE_MEDIA_NEXT:
Log.d(TAG, "page down--->");
break;


case KeyEvent.KEYCODE_PAGE_UP: //向下翻页键
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
Log.d(TAG, "page up--->");

break;

case KeyEvent.KEYCODE_VOLUME_UP: //调大声音键
Log.d(TAG, "voice up--->");

break;

case KeyEvent.KEYCODE_VOLUME_DOWN: //降低声音键
Log.d(TAG, "voice down--->");

break;
case KeyEvent.KEYCODE_VOLUME_MUTE: //禁用声音
Log.d(TAG, "voice mute--->");
break;
default:
break;


return super.onKeyDown(keyCode, event);

如果你要监听Home键的话,就需要通过广播来,
在MainActivity中创建一个class

class  HomeReceiver extends BroadcastReceiver 
@Override
public void onReceive(Context context, Intent intent)
String action = intent.getAction();
if(action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))

String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
if(SYSTEM_DIALOG_REASON_HOME_KEY.equals(reason))
Toast.makeText(MainActivity.this,"home键触发",Toast.LENGTH_SHORT).show();
Log.d(TAG, "home键触发");



在onCreate()方法中注册广播,只要调用initReceiver()方法即可

public final String SYSTEM_DIALOG_REASON_KEY = "reason";
public final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
private HomeReceiver homeReceiver;
/**
* 注册广播
*/
private void initReceiver()

homeReceiver = new HomeReceiver();
IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
registerReceiver(homeReceiver, filter);

页面销毁时,注销掉广播

@Override
protected void onDestroy()
super.onDestroy();
if(homeReceiver!=null)
unregisterReceiver(homeReceiver);

这段代码我也是从网上找的,
然后我们在确定键的下面弹出这个Toast

case KeyEvent.KEYCODE_ENTER:     //确定键enter
case KeyEvent.KEYCODE_DPAD_CENTER:
Log.d(TAG, "enter--->");
Toast.makeText(this,tvTest.getText().toString(),Toast.LENGTH_SHORT).show();
break;

运行效果如下:

Android


MainActivity.java完整代码如下:

package com.llw.androidtvdemo;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends AppCompatActivity

@BindView(R.id.tv_test)
TextView tvTest;
@BindView(R.id.btn_test)
Button btnTest;

public final String SYSTEM_DIALOG_REASON_KEY = "reason";
public final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
private HomeReceiver homeReceiver;
/**
* 注册广播
*/
private void initReceiver()

homeReceiver = new HomeReceiver();
IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
registerReceiver(homeReceiver, filter);


@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
btnTest.setFocusable(true);
initReceiver();


@OnClick(R.id.btn_test)
public void onViewClicked()
//Toast 提示
Toast.makeText(this,tvTest.getText().toString(),Toast.LENGTH_SHORT).show();


class HomeReceiver extends BroadcastReceiver
@Override
public void onReceive(Context context, Intent intent)
String action = intent.getAction();
if(action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))

String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
if(SYSTEM_DIALOG_REASON_HOME_KEY.equals(reason))
Toast.makeText(MainActivity.this,"home键触发",Toast.LENGTH_SHORT).show();
Log.d(TAG, "home键触发");






private String TAG = "key";

@Override
public boolean onKeyDown(int keyCode, KeyEvent event)

switch (keyCode)

case KeyEvent.KEYCODE_ENTER: //确定键enter
case KeyEvent.KEYCODE_DPAD_CENTER:
Log.d(TAG, "enter--->");
Toast.makeText(this,tvTest.getText().toString(),Toast.LENGTH_SHORT).show();
break;

case KeyEvent.KEYCODE_BACK: //返回键
Log.d(TAG,"back--->");
return true; //这里由于break会退出,所以我们自己要处理掉 不返回上一层

case KeyEvent.KEYCODE_SETTINGS: //设置键
Log.d(TAG, "setting--->");
break;

case KeyEvent.KEYCODE_DPAD_DOWN: //向下键

/* 实际开发中有时候会触发两次,所以要判断一下按下时触发 ,松开按键时不触发
* exp:KeyEvent.ACTION_UP
*/
if (event.getAction() == KeyEvent.ACTION_DOWN)
Log.d(TAG, "down--->");


break;

case KeyEvent.KEYCODE_DPAD_UP: //向上键
Log.d(TAG, "up--->");

break;

case KeyEvent.KEYCODE_0: //数字键0
Log.d(TAG, "0--->");

break;
case KeyEvent.KEYCODE_DPAD_LEFT: //向左键
Log.d(TAG, "left--->");
break;

case KeyEvent.KEYCODE_DPAD_RIGHT: //向右键
Log.d(TAG, "right--->");
break;

case KeyEvent.KEYCODE_INFO: //info键
Log.d(TAG, "info--->");

break;

case KeyEvent.KEYCODE_PAGE_DOWN: //向上翻页键
case KeyEvent.KEYCODE_MEDIA_NEXT:
Log.d(TAG, "page down--->");
break;


case KeyEvent.KEYCODE_PAGE_UP: //向下翻页键
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
Log.d(TAG, "page up--->");

break;

case KeyEvent.KEYCODE_VOLUME_UP: //调大声音键
Log.d(TAG, "voice up--->");

break;

case KeyEvent.KEYCODE_VOLUME_DOWN: //降低声音键
Log.d(TAG, "voice down--->");

break;
case KeyEvent.KEYCODE_VOLUME_MUTE: //禁用声音
Log.d(TAG, "voice mute--->");
break;
default:
break;


return super.onKeyDown(keyCode, event);



@Override
protected void onDestroy()
super.onDestroy();
if(homeReceiver!=null)
unregisterReceiver(homeReceiver);




然后我们就要想一下编码的过程和逻辑问题了,
1.播放视频的来源 本地 和 网络
2.播放视频的的停止播放、继续播放、重新播放
3.播放视频时的时间和进度计算
4.播放时候按遥控器左右键时,前进 后退
先想清楚这些问题,才能使编码过程中变得有条理

视频来源

本地:
我们可以在valuse文件夹下面创建一个raw文件夹,在里面放一个mp4短视频文件,(PS:至于在真机存储里面放一个视频,你只要播放路径指定这个视频所在地址,然后再加上文件的读写权限,因为我不是这么实现的,所以就不过多赘述了
网络:
就是通过一个视频地址来播放视频,既然是通过网络来播放的,我们肯定要有联网的权限啊,在AndroidManifest.xml文件中添加联网许可权限
如下所示

<uses-permission android:name="android.permission.INTERNET"

布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--自定义的VideoView 做了绘制改变,和网络地址许可-->
<com.llw.androidtvdemo.view.MyVideoView
android:id="@+id/video_view"
以上是关于Android TV 开发之 TV视频播放器的主要内容,如果未能解决你的问题,请参考以下文章

视频未在 Webview android (Google TV) 内播放

Android TV Leanback Exoplayer 视频缩放问题

安卓TV开发 移动智能终端UI之实现主流TV焦点可控UI

Android TV 从 URL 播放视频

在 Android TV 上升级 Android WebView 后,hls 264 视频不再播放

Android TV 中的 ExoPlayer 以纵向模式而不是横向模式播放视频