做过Android视频播放器的码农们都或多或少知道自带的VideoView用着没有那么顺心。需要处理很多东西。于是就各种度娘、Google。终于皇天不负苦心人。找到了一个却又不大符合。无奈,想自己动手写吧!又浪费时间。在这里,附上一个采用Vitamio框架写的视频播放器贡献给大伙。希望对你有用。好了。进入正题:
附上github下载地址:https://github.com/eternityzqf/VitamioTestDemo
先来个效果图看看:
图片可能有点糙,但运行在手机上是没问题的。
功能点:
①:播放网络视频
②:可以实现缓存/缓存加载提示
③:竖屏缩放正常画面、横屏缩放全屏画面
④:滑动左边亮度调节、滑动右边调节声音
⑤:自定义媒体控制画面。增加扩展性。
主要类:
一、App:用于全局初始化VItamio
/** * class from * Created by zqf * Time 2017/7/25 15:43 */ public class App extends Application { @Override public void onCreate() { super.onCreate(); //初始化一次Vitamio Vitamio.isInitialized(this); } }
二、CustomMediaController:自定义视频播放器控制器
/** * Created by zqf on 2017/7/25. * 自定义视频控制器 */ public class CustomMediaController extends MediaController { private static final int HIDEFRAM = 0;//控制提示窗口的显示 private GestureDetector mGestureDetector; private ImageButton img_back;//返回按钮 private TextView mFileName;//文件名 private VideoView videoView; private Activity activity; private Context context; private String videoname;//视频名称 private int controllerWidth = 0;//设置mediaController高度为了使横屏时top显示在屏幕顶端 private View mVolumeBrightnessLayout;//提示窗口 private ImageView mOperationBg;//提示图片 private TextView mOperationTv;//提示文字 private AudioManager mAudioManager; private SeekBar progress; private boolean mDragging; private MediaPlayerControl player; //最大声音 private int mMaxVolume; //当前声音 private int mVolume = -1; //当前亮度 private float mBrightness = -1f; //返回监听 private View.OnClickListener backListener = new View.OnClickListener() { public void onClick(View v) { if (activity != null) { activity.finish(); } } }; private View.OnClickListener scaleListener = new View.OnClickListener() { @Override public void onClick(View v) { if (activity != null) { switch (activity.getResources().getConfiguration().orientation) { case Configuration.ORIENTATION_LANDSCAPE://横屏 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); break; case Configuration.ORIENTATION_PORTRAIT://竖屏 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); break; } } } }; private Handler myHandler = new Handler() { @Override public void handleMessage(Message msg) { long pos; switch (msg.what) { case HIDEFRAM://隐藏提示窗口 mVolumeBrightnessLayout.setVisibility(View.GONE); mOperationTv.setVisibility(View.GONE); break; } } }; private ImageView mIvScale; //videoview 用于对视频进行控制的等,activity为了退出 public CustomMediaController(Context context, VideoView videoView, Activity activity) { super(context); this.context = context; this.videoView = videoView; this.activity = activity; WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); controllerWidth = wm.getDefaultDisplay().getWidth(); mGestureDetector = new GestureDetector(context, new MyGestureListener()); } @Override protected View makeControllerView() { //此处的 mymediacontroller 为我们自定义控制器的布局文件名称 View v = ((LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(getResources().getIdentifier("mymediacontroller", "layout", getContext().getPackageName()), this); v.setMinimumHeight(controllerWidth); //获取控件 img_back = (ImageButton) v.findViewById(getResources().getIdentifier("mediacontroller_top_back", "id", context.getPackageName())); mFileName = (TextView) v.findViewById(getResources().getIdentifier("mediacontroller_filename", "id", context.getPackageName())); //缩放控件 mIvScale = (ImageView) v.findViewById(getResources().getIdentifier("mediacontroller_scale", "id", context.getPackageName())); if (mFileName != null) { mFileName.setText(videoname); } //声音控制 mVolumeBrightnessLayout = (RelativeLayout) v.findViewById(R.id.operation_volume_brightness); mOperationBg = (ImageView) v.findViewById(R.id.operation_bg); mOperationTv = (TextView) v.findViewById(R.id.operation_tv); mOperationTv.setVisibility(View.GONE); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mMaxVolume = mAudioManager .getStreamMaxVolume(AudioManager.STREAM_MUSIC); //注册事件监听 img_back.setOnClickListener(backListener); mIvScale.setOnClickListener(scaleListener); return v; } @Override public boolean dispatchKeyEvent(KeyEvent event) { System.out.println("MYApp-MyMediaController-dispatchKeyEvent"); return true; } @Override public boolean onTouchEvent(MotionEvent event) { if (mGestureDetector.onTouchEvent(event)) return true; // 处理手势结束 switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_UP: endGesture(); break; } return super.onTouchEvent(event); } /** * 手势结束 */ private void endGesture() { mVolume = -1; mBrightness = -1f; // 隐藏 myHandler.removeMessages(HIDEFRAM); myHandler.sendEmptyMessageDelayed(HIDEFRAM, 1); } private class MyGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onSingleTapUp(MotionEvent e) { return false; } /** * 因为使用的是自定义的mediaController 当显示后,mediaController会铺满屏幕, * 所以VideoView的点击事件会被拦截,所以重写控制器的手势事件, * 将全部的操作全部写在控制器中, * 因为点击事件被控制器拦截,无法传递到下层的VideoView, * 所以 原来的单机隐藏会失效,作为代替, * 在手势监听中onSingleTapConfirmed()添加自定义的隐藏/显示, * * @param e * @return */ @Override public boolean onSingleTapConfirmed(MotionEvent e) { //当手势结束,并且是单击结束时,控制器隐藏/显示 toggleMediaControlsVisiblity(); return super.onSingleTapConfirmed(e); } @Override public boolean onDown(MotionEvent e) { return true; } //滑动事件监听 @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { float mOldX = e1.getX(), mOldY = e1.getY(); int y = (int) e2.getRawY(); int x = (int) e2.getRawX(); Display disp = activity.getWindowManager().getDefaultDisplay(); int windowWidth = disp.getWidth(); int windowHeight = disp.getHeight(); if (mOldX > windowWidth * 3.0 / 4.0) {// 右边滑动 屏幕 3/4 onVolumeSlide((mOldY - y) / windowHeight); } else if (mOldX < windowWidth * 1.0 / 4.0) {// 左边滑动 屏幕 1/4 onBrightnessSlide((mOldY - y) / windowHeight); } return super.onScroll(e1, e2, distanceX, distanceY); } @Override public boolean onDoubleTap(MotionEvent e) { playOrPause(); return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return super.onFling(e1, e2, velocityX, velocityY); } } /** * 滑动改变声音大小 * * @param percent */ private void onVolumeSlide(float percent) { if (mVolume == -1) { mVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); if (mVolume < 0) mVolume = 0; // 显示 mVolumeBrightnessLayout.setVisibility(View.VISIBLE); mOperationTv.setVisibility(VISIBLE); } int index = (int) (percent * mMaxVolume) + mVolume; if (index > mMaxVolume) index = mMaxVolume; else if (index < 0) index = 0; if (index >= 10) { mOperationBg.setImageResource(R.drawable.volmn_100); } else if (index >= 5 && index < 10) { mOperationBg.setImageResource(R.drawable.volmn_60); } else if (index > 0 && index < 5) { mOperationBg.setImageResource(R.drawable.volmn_30); } else { mOperationBg.setImageResource(R.drawable.volmn_no); } //DecimalFormat df = new DecimalFormat("######0.00"); mOperationTv.setText((int) (((double) index / mMaxVolume) * 100) + "%"); // 变更声音 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, index, 0); } /** * 滑动改变亮度 * * @param percent */ private void onBrightnessSlide(float percent) { if (mBrightness < 0) { mBrightness = activity.getWindow().getAttributes().screenBrightness; if (mBrightness <= 0.00f) mBrightness = 0.50f; if (mBrightness < 0.01f) mBrightness = 0.01f; // 显示 mVolumeBrightnessLayout.setVisibility(View.VISIBLE); mOperationTv.setVisibility(VISIBLE); } WindowManager.LayoutParams lpa = activity.getWindow().getAttributes(); lpa.screenBrightness = mBrightness + percent; if (lpa.screenBrightness > 1.0f) lpa.screenBrightness = 1.0f; else if (lpa.screenBrightness < 0.01f) lpa.screenBrightness = 0.01f; activity.getWindow().setAttributes(lpa); mOperationTv.setText((int) (lpa.screenBrightness * 100) + "%"); if (lpa.screenBrightness * 100 >= 90) { mOperationBg.setImageResource(R.drawable.light_100); } else if (lpa.screenBrightness * 100 >= 80 && lpa.screenBrightness * 100 < 90) { mOperationBg.setImageResource(R.drawable.light_90); } else if (lpa.screenBrightness * 100 >= 70 && lpa.screenBrightness * 100 < 80) { mOperationBg.setImageResource(R.drawable.light_80); } else if (lpa.screenBrightness * 100 >= 60 && lpa.screenBrightness * 100 < 70) { mOperationBg.setImageResource(R.drawable.light_70); } else if (lpa.screenBrightness * 100 >= 50 && lpa.screenBrightness * 100 < 60) { mOperationBg.setImageResource(R.drawable.light_60); } else if (lpa.screenBrightness * 100 >= 40 && lpa.screenBrightness * 100 < 50) { mOperationBg.setImageResource(R.drawable.light_50); } else if (lpa.screenBrightness * 100 >= 30 && lpa.screenBrightness * 100 < 40) { mOperationBg.setImageResource(R.drawable.light_40); } else if (lpa.screenBrightness * 100 >= 20 && lpa.screenBrightness * 100 < 20) { mOperationBg.setImageResource(R.drawable.light_30); } else if (lpa.screenBrightness * 100 >= 10 && lpa.screenBrightness * 100 < 20) { mOperationBg.setImageResource(R.drawable.light_20); } } /** * 设置视频文件名 * * @param name */ public void setVideoName(String name) { videoname = name; if (mFileName != null) { mFileName.setText(name); } } /** * 隐藏或显示 */ private void toggleMediaControlsVisiblity() { if (isShowing()) { hide(); } else { show(); } } /** * 播放/暂停 */ private void playOrPause() { if (videoView != null) if (videoView.isPlaying()) { videoView.pause(); } else { videoView.start(); } } }
自定义控制器类主要是最播放、暂停、播放时间、全屏的一些控件界面封装;通过在MainActivity里面的
new CustomMediaController();将VideoView传进来进行一些操作。但播放的相关还是放在主界面里面操作的。
可在里面更改界面以适应需求。SeekBar样式都将采用自定义的。增加扩展。
涉及的公共方法:
setVideoName();设置视频名称
当然还有什么添加喜欢,收藏子类的。你们都可以自己添加。
三、ManActivity:主界面
/** * class from 主界面 * Created by zqf * Time 2017/7/25 15:43 */ public class MainActivity extends Activity implements MediaPlayer.OnInfoListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnCompletionListener { private String video_path = "http://baobab.wdjcdn.com/145076769089714.mp4"; private Uri mUri; private ProgressBar pb; private TextView downloadRateView, loadRateView; private CustomMediaController mCustomMediaController; private VideoView mVideoView; public static long mCurrent_position = 0;//当前播放的位置 public static final int VIDEO_LAYOUT_ORIGIN = 0;//缩放参数,原始画面大小0。 public static final int VIDEO_LAYOUT_SCALE = 1;//缩放参数,画面全屏1。 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //定义全屏参数 int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN; //获得当前窗体对象 Window window = MainActivity.this.getWindow(); //设置当前窗体为全屏显示 window.setFlags(flag, flag); setContentView(R.layout.activity_main); initView(); initData(); } private void initData() { mUri = Uri.parse(video_path);//将地址转化为Uri mVideoView.setVideoURI(mUri);//设置播放视频的地址 mCustomMediaController.show(5000);//设置显示时间差 mVideoView.setMediaController(mCustomMediaController);//设置媒体控制器。 mVideoView.setVideoQuality(MediaPlayer.VIDEOQUALITY_HIGH);//设置画质 mVideoView.requestFocus();//获取焦点 mVideoView.setBufferSize(512 * 1024);//设置缓冲大小(单位Byte) /** * 监听在有警告或错误信息时调用。例如:开始缓冲、缓冲结束、下载速度变化。 */ mVideoView.setOnInfoListener(this); /** * 监听在网络视频流缓冲变化时调用。 */ mVideoView.setOnBufferingUpdateListener(this); /** * 视频播放完成后调用。 */ mVideoView.setOnCompletionListener(this); mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { /** * 在视频预处理完成后调用。在视频预处理完成后被调用。 * 此时视频的宽度、高度、宽高比信息已经获取到, * 此时可调用seekTo让视频从指定位置开始播放。 */ mp.setPlaybackSpeed(1.0f); } }); } private void initView() { mVideoView = (VideoView) findViewById(R.id.vitamio_video); mCustomMediaController = new CustomMediaController(this, mVideoView, this); mCustomMediaController.setVideoName("此处可以设置视频名称"); pb = (ProgressBar) findViewById(R.id.probar); downloadRateView = (TextView) findViewById(R.id.download_rate); loadRateView = (TextView) findViewById(R.id.load_rate); } @Override public boolean onInfo(MediaPlayer mp, int what, int extra) { switch (what) { case MediaPlayer.MEDIA_INFO_BUFFERING_START: //开始缓存事,执行暂停播放、加载、下载、缓存控件可见 if (mVideoView.isPlaying()) { Log.e("Tag", what + "---缓存flag----"); mVideoView.pause(); pb.setVisibility(View.VISIBLE); downloadRateView.setVisibility(View.VISIBLE); loadRateView.setVisibility(View.VISIBLE); } break; case MediaPlayer.MEDIA_INFO_BUFFERING_END: //缓存完成,执行继续播放;加载、下载、缓存控件不可见 mVideoView.start(); pb.setVisibility(View.GONE); downloadRateView.setVisibility(View.GONE); loadRateView.setVisibility(View.GONE); break; case MediaPlayer.MEDIA_INFO_DOWNLOAD_RATE_CHANGED: //缓存时显示下载速度 //此时下载速度应该实时获取手机的网速。这以kb/s代替 Log.e("Tag", what + "----下载flag---" + extra); downloadRateView.setText(extra + "kb/s" + ""); break; } return true; } @Override public void onBufferingUpdate(MediaPlayer mp, int percent) { loadRateView.setText("缓冲" + percent + "%"); Log.e("Tag", "+++++++" + percent); } @Override protected void onResume() { super.onResume(); Log.e("Tag", "onResume"); if (mCurrent_position != 0) { mVideoView.seekTo(mCurrent_position); mVideoView.start(); } } @Override protected void onPause() { super.onPause(); Log.e("Tag", "onPause"); mCurrent_position = mVideoView.getCurrentPosition(); Log.e("Tag", mCurrent_position + ""); if (mVideoView.isPlaying()) { mVideoView.pause(); } } @Override protected void onStop() { super.onStop(); Log.e("Tag", "onStop"); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); Log.e("Tag", "onRestoreInstanceState"); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Log.e("Tag", "onSaveInstanceState"); } @Override protected void onDestroy() { super.onDestroy(); Log.e("Tag", "onDestroy"); //停止视频播放,并释放资源。 mVideoView.stopPlayback(); mCurrent_position = 0; } /** * 视频播放完成后调用。 * * @param mp the MediaPlayer that reached the end of the file */ @Override public void onCompletion(MediaPlayer mp) { mCurrent_position = 0; } /** * getRequestedOrientation获取横竖屏标志 * -1 || 1--->竖屏 * 0 --->横屏 */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { Log.e("Tag", "返回键。。。。" + getRequestedOrientation()); int orient = getRequestedOrientation(); if (orient == -1 || orient == 1) { finish(); } else if (orient == 0) { //切换为竖屏 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } return false; } return super.onKeyDown(keyCode, event); } /** * 屏幕切换时 */ @Override public void onConfigurationChanged(Configuration newConfig) { Log.e("Tag", newConfig.orientation + ""); if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { //竖屏-->显示原始画面 if (mVideoView != null) { mVideoView.setVideoLayout(VIDEO_LAYOUT_ORIGIN, 0); } } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { //横屏-->显示全屏画面 if (mVideoView != null) { mVideoView.setVideoLayout(VIDEO_LAYOUT_SCALE, 0); } } super.onConfigurationChanged(newConfig); } }主界面里面
一、
先看看Vitamio的VideoView都带有什么功能;
设置缓存大小(单位Byte):
mVideoView.setBufferSize(512 * 1024);监听在有警告或错误信息时调用。例如:开始缓冲、缓冲结束、下载速度变化:
mVideoView.setOnInfoListener(this); @Override public boolean onInfo(MediaPlayer mp, int what, int extra) { switch (what) { case MediaPlayer.MEDIA_INFO_BUFFERING_START: //开始缓存事,执行暂停播放、加载、下载、缓存控件可见 if (mVideoView.isPlaying()) { Log.e("Tag", what + "---缓存flag----"); mVideoView.pause(); pb.setVisibility(View.VISIBLE); downloadRateView.setVisibility(View.VISIBLE); loadRateView.setVisibility(View.VISIBLE); } break; case MediaPlayer.MEDIA_INFO_BUFFERING_END: //缓存完成,执行继续播放;加载、下载、缓存控件不可见 mVideoView.start(); pb.setVisibility(View.GONE); downloadRateView.setVisibility(View.GONE); loadRateView.setVisibility(View.GONE); break; case MediaPlayer.MEDIA_INFO_DOWNLOAD_RATE_CHANGED: //缓存时显示下载速度 //此时下载速度应该实时获取手机的网速。这以kb/s代替 Log.e("Tag", what + "----下载flag---" + extra); downloadRateView.setText(extra + "kb/s" + ""); break; } return true; }监听在网络视频流缓冲变化时调用:
mVideoView.setOnBufferingUpdateListener(this); @Override public void onBufferingUpdate(MediaPlayer mp, int percent) { loadRateView.setText("缓冲" + percent + "%"); Log.e("Tag", "+++++++" + percent); }........二、手机Home切换到候后台时我们需要在生命周期里面记录和恢复播放位置:
记录:
@Override protected void onPause() { super.onPause(); Log.e("Tag", "onPause"); mCurrent_position = mVideoView.getCurrentPosition(); Log.e("Tag", mCurrent_position + ""); if (mVideoView.isPlaying()) { mVideoView.pause(); } }
恢复:
@Override protected void onResume() { super.onResume(); Log.e("Tag", "onResume"); if (mCurrent_position != 0) { mVideoView.seekTo(mCurrent_position); mVideoView.start(); } } 当然我们还需要在ConfigurationChanged里面改一下; @Override public void onConfigurationChanged(Configuration newConfig) { Log.e("Tag", newConfig.orientation + ""); if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { //竖屏-->显示原始画面 if (mVideoView != null) { mVideoView.setVideoLayout(VIDEO_LAYOUT_ORIGIN, 0); } } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { //横屏-->显示全屏画面 if (mVideoView != null) { mVideoView.setVideoLayout(VIDEO_LAYOUT_SCALE, 0); } } super.onConfigurationChanged(newConfig); } 差不多就这些,有些细节就没贴出来。可以去github下来看看。欢迎各位star和fork。
参考的Vitamio官网地址:https://www.vitamio.org/