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文件
我们不用它这个主题,重新创建一个
styles.xml的完整代码如下:
<?xml version="1.0" encoding="utf-8"?>然后你会发现少几个颜色,这时候我们在values文件夹下面创建一个colors.xml的文件
colors.xml代码如下:
<?xml version="1.0" encoding="utf-8"?>#008577
#00574B
#D81B60
#1EBADE
然后就要使用我们新创建的主题了,打开AndroidManifest.xml文件
修改,AppTheme,改成AppTheme2,然后运行项目.运行效果如下图
这个时候你没有想过,我怎么点击这个按钮呢?电视机都是用遥控器的啊,遥控器又怎么操作呢?
这些问题一定在你的脑海里面环绕着,我们注意到,电视机使用遥控器,而我们的手机使用手指触摸点击,这个不能混为一谈,所以电视上需要用到焦点电视上都是通过控件获取焦点来实现点击效果的,我们在布局文件的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”
代码中:
btnTest.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;
运行效果如下:
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文件中添加联网许可权限
如下所示
<?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”>
<com.llw.androidtvdemo.view.MyVideoView
android:id=“@+id/video_view”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:layout_gravity=“center” />
<RelativeLayout
android:background=“@drawable/shape_gradual_change”
android:layout_alignParentBottom=“true”
android:layout_width=“match_parent”
android:layout_height=“@dimen/dp_100”>
<LinearLayout
android:gravity=“center_vertical”
android:layout_margin=“@dimen/dp_10”
android:layout_alignParentBottom=“true”
android:orientation=“horizontal”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”>
<TextView
android:id=“@+id/tv_play_time”
android:text=“00:00”
android:textSize=“@dimen/sp_24”
android:textColor=“@color/white”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”/>
<SeekBar
android:layout_marginLeft=“@dimen/dp_20”
android:layout_marginRight=“@dimen/dp_20”
android:id=“@+id/time_seekBar”
android:layout_width=“0dp”
android:layout_weight=“1”
android:layout_height=“wrap_content”
android:layout_centerInParent=“true”
android:max=“100”
android:maxHeight=“3dp”
android:minHeight=“3dp”
android:progress=“0”
android:progressDrawable=“@drawable/seekbar_style”
android:thumb=“@drawable/thumb” />
<TextView
android:id=“@+id/tv_total_time”
android:text=“00:00”
android:textSize=“@dimen/sp_24”
android:textColor=“@color/white”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”/>
<RelativeLayout
android:visibility=“gone”
android:id=“@+id/lay_finish_bg”
android:background=“#000”
android:layout_width=“match_parent”
android:layout_height=“match_parent”/>
<ImageButton
android:visibility=“gone”
android:focusable=“true”
android:layout_centerInParent=“true”
android:id=“@+id/btn_play_or_pause”
android:background=“@mipmap/icon_pause”
android:layout_width=“@dimen/dp_100”
android:layout_height=“@dimen/dp_100”/>
<ImageButton
android:visibility=“gone”
android:layout_centerInParent=“true”
android:id=“@+id/btn_restart_play”
android:background=“@mipmap/icon_restart_play”
android:layout_width=“@dimen/dp_100”
android:layout_height=“@dimen/dp_100”/>
注释已经加在布局文件里面了,下面就不过多讲述了,布局文件中的自定义VideoView代码如下:
package com.llw.androidtvdemo.view;
import android.content.Context;
import android.net.Uri;
import android.util.AttributeSet;
import android.widget.VideoView;
import com.llw.androidtvdemo.view.util.SSlUtiles;
import javax.net.ssl.HttpsURLConnection;
/**
- 自定义VideoView
*/
public class MyVideoView extends VideoView {
public MyVideoView(Context context) {
super(context);
}
public MyVideoView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyVideoView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getDefaultSize(getWidth(), widthMeasureSpec);
int height = getDefaultSize(getHeight(), heightMeasureSpec);
setMeasuredDimension(width, height);
}
@Override
public void setVideoURI(Uri uri) {
super.setVideoURI(uri);
try {
HttpsURLConnection.setDefaultSSLSocketFactory(SSlUtiles.createSSLSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new SSlUtiles.TrustAllHostnameVerifier());
} catch (Exception e) {
e.printStackTrace();
}
}
}
自定义VideoView中SSlUtils网络证书许可类代码如下:
package com.llw.androidtvdemo.view.util;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class SSlUtils {
/**
-
默认信任所有的证书
-
xts
*/
public static SSLSocketFactory createSSLSocketFactory() {
SSLSocketFactory sSLSocketFactory = null;
try {
SSLContext sc = SSLContext.getInstance(“TLS”);
sc.init(null, new TrustManager[] { (TrustManager) new TrustAllManager() }, new SecureRandom());
sSLSocketFactory = sc.getSocketFactory();
} catch (Exception e) {
}
return sSLSocketFactory;
}
public static class TrustAllManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
public static class TrustAllHostnameVerifier implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
}
这个类主要是针对于 VideoView 无法播放此视频 问题,如果你没有这个问题的话,可以在MyVideoView去掉下面这一段代码:
@Override
public void setVideoURI(Uri uri) {
super.setVideoURI(uri);
try {
HttpsURLConnection.setDefaultSSLSocketFactory(SSlUtils.createSSLSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new SSlUtils.TrustAllHostnameVerifier());
} catch (Exception e) {
e.printStackTrace();
}
}
然后来看MainActivity中的代码,通过注解的方式我的控件已经不需要声明和findById了。
首先配置一下我们的VideoVIew
/**
- 初始化VideoView
*/
private void initVideo() {
//本地视频
// videoView.setVideoURI(Uri.parse(“android.resource://” + getPackageName() + “/raw/test”));
//网络视频
final Uri uri = Uri.parse(“http://gslb.miaopai.com/stream/ed5HCfnhovu3tyIQAiv60Q__.mp4”);
videoView.setVideoURI(uri);
videoView.requestFocus();
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
int totalTime = videoView.getDuration();//获取视频的总时长
tvTotalTime.setText(stringForTime(totalTime));//设置视频总时间,stringForTime是写的一个时间装换方法,下面会提到
// 开始线程,更新进度条的刻度
handler.postDelayed(runnable, 0);
timeSeekBar.setMax(videoView.getDuration());
//视频加载完成,准备好播放视频的回调
videoView.start();
}
});
}
上面的初始化中用到了一个线程,线程代码如下:
private Handler handler = new Handler();
private Runnable runnable = new Runnable() {
public void run() {
if (videoView.isPlaying()) {
int current = videoView.getCurrentPosition();//获取播放过程中位置
timeSeekBar.setProgress(current);//设置进度条的位置
tvPlayTime.setText(time(videoView.getCurrentPosition()));//播放过程中的时间
}
handler.postDelayed(runnable, 500);//播放过程中0.5秒执行一次
}
};
然后是onCreate()方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);//注释然后自动添加的
timeSeekBar.setOnSeekBarChangeListener(onSeekBarChangeListener);//添加进度条的变化监听
initVideo();//初始化VideoView
//videoView播放完成监听
videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
key = 1;//这是一个全局变量,用于控制遥控单击确定或者ok键时,是暂停继续还是重新播放,1则是重新播放视频
btnRestartPlay.setVisibility(View.VISIBLE);//显示黑色背景,布局文件中注释提到了
layFinishBg.setVisibility(View.VISIBLE);//显示白色重播图标,布局文件中注释提到了
}
});
//videoView播放异常监听,类似于 此视频无法播放 这样的错误提示
videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Toast.makeText(MainActivity.this, “播放出错”, Toast.LENGTH_SHORT).show();
return false;
}
});
}
代码中用到的一个方法:
/**
-
时间转换方法
-
@param millionSeconds
-
@return
*/
protected String time(long millionSeconds) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(“mm:ss”);
Calendar c = Calendar.getInstance();
c.setTimeInMillis(millionSeconds);
return simpleDateFormat.format(c.getTime());
}
控制视频是 播放还是暂停 或者是重播
/**
-
控制视频是 播放还是暂停 或者是重播
-
@param isPlay
-
@param keys
*/
private void isVideoPlay(boolean isPlay, int keys) {
switch (keys) {
case 0:
if (isPlay) {//暂停
btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_player));//更换按钮的图标并显示出来
btnPlayOrPause.setVisibility(View.VISIBLE);
videoView.pause();
} else {//继续播放
btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_pause));//更换按钮的图标并显示出来
btnPlayOrPause.setVisibility(View.VISIBLE);
// 开始线程,更新进度条的刻度
handler.postDelayed(runnable, 0);
videoView.start();//继续播放
timeSeekBar.setMax(videoView.getDuration());
timeGone();//当我们选择继续播放之后,就不能让这个图标一直显示下去,但是又不能马上消失,这样很突兀,所以用了延时1.5秒隐藏,比较合理,这个方法后面会贴出来。
}
break;
case 1://重新播放
initVideo();
btnRestartPlay.setVisibility(View.GONE);//白色重播图标隐藏
layFinishBg.setVisibility(View.GONE);//黑色背景隐藏
key = 0;//重新播放之后,我们再将key置为0,这样就不会影响到下一次视频播放过程中的暂停和继续的监听操作了
break;
}
延时1.5秒隐藏
private void timeGone() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
btnPlayOrPause.setVisibility(View.INVISIBLE);
}
}, 1500);
}
进度条监听
/**
- 进度条监听
*/
private SeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
// 当进度条停止修改的时候触发
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// 取得当前进度条的刻度
int progress = seekBar.getProgress();
if (videoView.isPlaying()) {
// 设置当前播放的位置
videoView.seekTo(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
}
};
时间转换方法
//将长度转换为时间
StringBuilder mFormatBuilder = new StringBuilder();
Formatter mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
//整数类型转换为时分秒
private String stringForTime(int timeMs) {
int totalSeconds = timeMs / 1000;
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
mFormatBuilder.setLength(0);
if (hours > 0) {
return mFormatter.format(“%d:%02d:%02d”, hours, minutes, seconds).toString();
} else {
return mFormatter.format(“%02d:%02d”, minutes, seconds).toString();
}
}
遥控器按键监听
private String TAG = “key”;
/**
-
遥控器按键监听
-
@param keyCode
-
@param event
-
@return
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER: //确定键enter
case KeyEvent.KEYCODE_DPAD_CENTER:
Log.d(TAG, “enter—>”);
//如果是播放中则暂停、如果是暂停则继续播放
isVideoPlay(videoView.isPlaying(), key);
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_DPAD_LEFT: //向左键
Log.d(TAG, “left—>”);
if (videoView.getCurrentPosition() > 4) {
videoView.seekTo(videoView.getCurrentPosition() - 5 * 1000);
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT: //向右键
Log.d(TAG, “right—>”);
videoView.seekTo(videoView.getCurrentPosition() + 5 * 1000);
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);
}
前进后退的控制都这个遥控器监听里面。
MainActivity的完整代码如下:
package com.llw.androidtvdemo;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.llw.androidtvdemo.view.MyVideoView;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Formatter;
import java.util.Locale;
import butterknife.BindView;
import butterknife.ButterKnife;
public class MainActivity extends AppCompatActivity {
@BindView(R.id.video_view)
MyVideoView videoView;
@BindView(R.id.tv_play_time)
TextView tvPlayTime;
@BindView(R.id.time_seekBar)
SeekBar timeSeekBar;
@BindView(R.id.tv_total_time)
TextView tvTotalTime;
@BindView(R.id.lay_finish_bg)
RelativeLayout layFinishBg;
@BindView(R.id.btn_play_or_pause)
ImageButton btnPlayOrPause;
@BindView(R.id.btn_restart_play)
ImageButton btnRestartPlay;
private int key = 0;
private Handler handler = new Handler();
private Runnable runnable = new Runnable() {
public void run() {
if (videoView.isPlaying()) {
int current = videoView.getCurrentPosition();
timeSeekBar.setProgress(current);
tvPlayTime.setText(time(videoView.getCurrentPosition()));
}
handler.postDelayed(runnable, 500);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
timeSeekBar.setOnSeekBarChangeListener(onSeekBarChangeListener);
initVideo();
videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
key = 1;
btnRestartPlay.setVisibility(View.VISIBLE);
layFinishBg.setVisibility(View.VISIBLE);
}
});
videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Toast.makeText(MainActivity.this, “播放出错”, Toast.LENGTH_SHORT).show();
return false;
}
});
}
/**
-
时间转换方法
-
@param millionSeconds
-
@return
*/
protected String time(long millionSeconds) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(“mm:ss”);
Calendar c = Calendar.getInstance();
c.setTimeInMillis(millionSeconds);
return simpleDateFormat.format(c.getTime());
}
/**
- 初始化VideoView
*/
private void initVideo() {
//本地视频
// videoView.setVideoURI(Uri.parse(“android.resource://” + getPackageName() + “/raw/test”));
//网络视频
final Uri uri = Uri.parse(“http://gslb.miaopai.com/stream/ed5HCfnhovu3tyIQAiv60Q__.mp4”);
videoView.setVideoURI(uri);
videoView.requestFocus();
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
int totalTime = videoView.getDuration();//获取视频的总时长
tvTotalTime.setText(stringForTime(totalTime));
// 开始线程,更新进度条的刻度
handler.postDelayed(runnable, 0);
timeSeekBar.setMax(videoView.getDuration());
//视频加载完成,准备好播放视频的回调
videoView.start();
}
});
}
/**
-
控制视频是 播放还是暂停 或者是重播
-
@param isPlay
-
@param keys
*/
private void isVideoPlay(boolean isPlay, int keys) {
switch (keys) {
case 0:
if (isPlay) {//暂停
btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_player));
btnPlayOrPause.setVisibility(View.VISIBLE);
videoView.pause();
} else {//继续播放
btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_pause));
btnPlayOrPause.setVisibility(View.VISIBLE);
// 开始线程,更新进度条的刻度
handler.postDelayed(runnable, 0);
videoView.start();
timeSeekBar.setMax(videoView.getDuration());
timeGone();
}
break;
case 1://重新播放
initVideo();
btnRestartPlay.setVisibility(View.GONE);
layFinishBg.setVisibility(View.GONE);
key = 0;
break;
}
}
/**
- 延时隐藏
*/
private void timeGone() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
btnPlayOrPause.setVisibility(View.INVISIBLE);
}
}, 1500);
}
/**
- 进度条监听
*/
private SeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
// 当进度条停止修改的时候触发
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// 取得当前进度条的刻度
int progress = seekBar.getProgress();
if (videoView.isPlaying()) {
// 设置当前播放的位置
videoView.seekTo(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
最后
小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
如果你需要这些资料, ⬅ 专栏获取
eoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
int totalTime = videoView.getDuration();//获取视频的总时长
tvTotalTime.setText(stringForTime(totalTime));
// 开始线程,更新进度条的刻度
handler.postDelayed(runnable, 0);
timeSeekBar.setMax(videoView.getDuration());
//视频加载完成,准备好播放视频的回调
videoView.start();
}
});
}
/**
-
控制视频是 播放还是暂停 或者是重播
-
@param isPlay
-
@param keys
*/
private void isVideoPlay(boolean isPlay, int keys) {
switch (keys) {
case 0:
if (isPlay) {//暂停
btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_player));
btnPlayOrPause.setVisibility(View.VISIBLE);
videoView.pause();
} else {//继续播放
btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_pause));
btnPlayOrPause.setVisibility(View.VISIBLE);
// 开始线程,更新进度条的刻度
handler.postDelayed(runnable, 0);
videoView.start();
timeSeekBar.setMax(videoView.getDuration());
timeGone();
}
break;
case 1://重新播放
initVideo();
btnRestartPlay.setVisibility(View.GONE);
layFinishBg.setVisibility(View.GONE);
key = 0;
break;
}
}
/**
- 延时隐藏
*/
private void timeGone() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
btnPlayOrPause.setVisibility(View.INVISIBLE);
}
}, 1500);
}
/**
- 进度条监听
*/
private SeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
// 当进度条停止修改的时候触发
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// 取得当前进度条的刻度
int progress = seekBar.getProgress();
if (videoView.isPlaying()) {
// 设置当前播放的位置
videoView.seekTo(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
最后
小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
[外链图片转存中…(img-Z30mOOTv-1719166467791)]
[外链图片转存中…(img-26RS4izy-1719166467792)]
[外链图片转存中…(img-oza98jEh-1719166467792)]
[外链图片转存中…(img-Oq73fiUC-1719166467793)]
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
如果你需要这些资料, ⬅ 专栏获取