了解这个之后,我们还得知道遥控器的按键监听,毕竟是用遥控器来操作的啊,按键监听代码如下:
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文件中添加联网许可权限
如下所示
<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:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" /> </LinearLayout> <!--底部控制栏 开始时间 进度条 结束时间--> <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"/> </LinearLayout> </RelativeLayout> <!--视频结束时 显示黑色背景--> <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"/> </RelativeLayout>
注释已经加在布局文件里面了,下面就不过多讲述了,布局文件中的自定义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 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); } }
运行效果如下:
播放结束再按确定键就可以重新播放了。
这里再补充一下,在我之前使用的机顶盒很少使用高版本的Android系统,所以没有对高版本的Android系统的https网络访问许可做支持,在Android9.0及以后的版本中,默认是不支持https请求的,这就导致了有一些读者在使用源码是会出现 “ 无法播放此视频 ”,这样的bug,因此我这里补充一下,其实也很简单,在res下新建一个xml文件夹,xml文件夹下新建一个network_security_config.xml文件
xml文件内容如下:
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true" /> </network-security-config>
然后在AndroidManifest.xml的application标签里配置就可以了。
如有问题请留言,定当第一时间回复您,感谢您的阅读,我是初学者-Study 山高水长,后会有期~