做过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();
}
}
@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官网地址:https://www.vitamio.org/