1.概述
Android为我们提供了视频播放的控件VideoView,通过这个控件我们只需要设置controller,setVideoUrl然后调用start方法就可以播放视频。但是这种最原始的播放器完全不能满足我们开发中的需求。特别是videoView自带的播放控制器只有播放暂停,快进快退的功能,像拖拽进度,横屏竖屏,手势等功能都没有。而且原始的videoView的大小我们还不能随意的修改。所以我们自定义了videoView,自定义了controller,从而优化功能,达到Android开发的标准,博客记录一下。
先看一张最后的效果图:
2.实现思路
自定义一个view继承framLayout,自己写一个布局通过LayoutInflate注入进来,布局的代码会在下面体现。总体布局就是头部有返回按钮,视频标题,中间是自定义的videoView,还有加载缩略图的ImageView,底部布局有播放暂停的按钮,有当前播放时间/总时间,有可以改变播放进度的seekBar,还有一个可以控制横竖屏的按钮。缩略图中间有一个开始播放的按钮。点击后加载loading动画,当player prepared监听中隐藏动画。当开始按钮点击后,将top bottom显示出来,然后点击videoView可以控制top and bottom的显示隐藏。点击bottom的开始暂停按钮可以控制视频的播放暂停。通过handler定义一个定时器每0.5秒获取一下videoView当前的播放位置,计算seekbar的进度,更新进度条。监听seekbar的进度改变事件,通过videoView的seekTo方法改变videoView的播放进度。横竖屏的按钮控制全屏播放,因为系统的videoView不允许随便修改大小,所以我们自定义videoView并重写onMeasure方法。好了,说了这么多看一下核心代码吧:
1.自定义的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) {
int width = getDefaultSize(getWidth(), widthMeasureSpec);
int height = getDefaultSize(getHeight(), heightMeasureSpec);
setMeasuredDimension(width, height);
}
}
2.自定义的播放控制器
public class MyController extends FrameLayout implements View.OnClickListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnInfoListener, SeekBar.OnSeekBarChangeListener, MediaPlayer.OnCompletionListener {
private Context mContext;
private String url;
private FrameLayout mRoot_rl;
private MyVideoView videoView;
private FrameLayout fl_videoParent;
private ImageView mImage;
private ImageView mCenterStart;
private LinearLayout mTop;
private ImageView mBack;
private TextView mTitle;
private LinearLayout mBottom;
private ImageView mRestartPause;
private TextView mPosition;
private TextView mDuration;
private SeekBar mSeek;
private ImageView mFullScreen;
private ImageView iv_loading;
//播放控件的动画
private ObjectAnimator bufferAnimation;
private boolean isTopBottomShow = false;
//更新进度条进度定时器
private Handler mHandler = new Handler();
private Runnable mRunnable;
//当前视屏的播放进度
private int currentProgress;
public MyController(Context context) {
super(context);
this.mContext = context;
init(context);
}
public MyController(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
init(context);
}
public MyController(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
init(context);
}
private void init(Context context) {
View view = LayoutInflater.from(mContext).inflate(R.layout.layout_controller, null);
fl_videoParent = (FrameLayout)view.findViewById(R.id.fl_videoParent);
videoView = (MyVideoView) view.findViewById(R.id.videoView);
mRoot_rl = (FrameLayout) view.findViewById(R.id.root_rl);
mCenterStart = (ImageView) view.findViewById(R.id.center_start);
mImage = (ImageView) view.findViewById(R.id.image);
mTop = (LinearLayout) view.findViewById(R.id.top);
mBack = (ImageView) view.findViewById(R.id.back);
mTitle = (TextView) view.findViewById(R.id.title);
mBottom = (LinearLayout) view.findViewById(R.id.bottom);
mRestartPause = (ImageView) view.findViewById(R.id.restart_or_pause);
mPosition = (TextView) view.findViewById(R.id.position);
mDuration = (TextView) view.findViewById(R.id.duration);
mSeek = (SeekBar) view.findViewById(R.id.seek);
mFullScreen = (ImageView) view.findViewById(R.id.full_screen);
iv_loading = view.findViewById(R.id.iv_loading);
addView(view);
}
/**
* 播放器初始化
*
* @param url
* @param thumbUrl
*/
public void initVideo(String url, String thumbUrl,String title) {
this.url = url;
videoView.setVideoPath(url);
mTitle.setText(title);
LayoutParams lp = (LayoutParams) videoView.getLayoutParams();
lp.width = (int) Util.getScreenWidth(mContext);
lp.height = lp.width * 9 / 16;
videoView.setLayoutParams(lp);
LayoutParams lp2 = (LayoutParams) mImage.getLayoutParams();
lp2.width = (int) Util.getScreenWidth(mContext);
lp2.height = lp2.width * 9 / 16;
mImage.setLayoutParams(lp2);
mTop.setVisibility(GONE);
mBottom.setVisibility(GONE);
Glide.with(mContext).load(thumbUrl).into(mImage);
initListener();
mRunnable = new Runnable() {
@Override
public void run() {
float position = videoView.getCurrentPosition();
currentProgress = (int) ((position / videoView.getDuration()) * 100);
if(!isPlayOver){
mSeek.setProgress(currentProgress);
}
mPosition.setText(stringForTime((int) position));
mHandler.postDelayed(mRunnable, 500);
}
};
}
/**
* 监听初始化
*/
private void initListener() {
mCenterStart.setOnClickListener(this);
fl_videoParent.setOnClickListener(this);
mRestartPause.setOnClickListener(this);
mFullScreen.setOnClickListener(this);
mBack.setOnClickListener(this);
videoView.setOnPreparedListener(this);
videoView.setOnInfoListener(this);
videoView.setOnCompletionListener(this);
mSeek.setOnSeekBarChangeListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.center_start:
startPlay();
if(url.startsWith("http") || url.startsWith("https")){
iv_loading.setVisibility(VISIBLE);
}else {
iv_loading.setVisibility(GONE);
}
startBufferAnimation();
break;
case R.id.fl_videoParent:
if(videoView.isPlaying()){
showHideTopAndBottom();
}
break;
case R.id.restart_or_pause:
if(videoView != null){
if(videoView.isPlaying()){
videoView.pause();
mRestartPause.setImageResource(R.drawable.ic_player_start);
}else {
startPlay();
mRestartPause.setImageResource(R.drawable.ic_player_pause);
}
}
break;
case R.id.full_screen:
switchScreenOrientation();
break;
case R.id.back:
switchScreenOrientation();
break;
}
}
private void startPlay(){
mRestartPause.setImageResource(R.drawable.ic_player_pause);
videoView.start();
mImage.setVisibility(GONE);
mCenterStart.setVisibility(GONE);
}
/**
* videoView准备完毕监听
*
* @param mediaPlayer
*/
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
stopBufferAnimation();
iv_loading.setVisibility(GONE);
mDuration.setText(stringForTime(videoView.getDuration()));
mHandler.post(mRunnable);
}
/**
* 缓冲标志动画
*/
private void startBufferAnimation() {
if (bufferAnimation == null) {
bufferAnimation = ObjectAnimator.ofFloat(iv_loading, "rotation", 0, 359);
bufferAnimation.setDuration(1500);
bufferAnimation.setInterpolator(new LinearInterpolator());
bufferAnimation.setRepeatCount(ObjectAnimator.INFINITE);
}
if (!bufferAnimation.isRunning()) {
bufferAnimation.start();
}
}
private void stopBufferAnimation() {
if (bufferAnimation != null) {
bufferAnimation.cancel();
}
}
/**
* 头部,底部隐藏显示
*/
private void showHideTopAndBottom() {
if (isTopBottomShow) {
mTop.setVisibility(GONE);
mBottom.setVisibility(GONE);
isTopBottomShow = false;
} else {
mTop.setVisibility(VISIBLE);
mBottom.setVisibility(VISIBLE);
isTopBottomShow = true;
}
}
@Override
public boolean onInfo(MediaPlayer mediaPlayer, int what, int extra) {
if(what == MediaPlayer.MEDIA_INFO_BUFFERING_START){//开始缓冲
iv_loading.setVisibility(VISIBLE);
startBufferAnimation();
}else if(what == MediaPlayer.MEDIA_INFO_BUFFERING_END){//缓冲结束
iv_loading.setVisibility(GONE);
stopBufferAnimation();
}
return false;
}
private String stringForTime(int timeMs) {
if (timeMs <= 0 || timeMs >= 24 * 60 * 60 * 1000) {
return "00:00";
}
int totalSeconds = timeMs / 1000;
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
StringBuilder stringBuilder = new StringBuilder();
Formatter mFormatter = new Formatter(stringBuilder, Locale.getDefault());
if (hours > 0) {
return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
} else {
return mFormatter.format("%02d:%02d", minutes, seconds).toString();
}
}
/**
* seekBar进度改变实现方法
* @param seekBar
* @param i
* @param b
*/
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
//滑动SeekBar结束后让视屏定位到对应位置
if (videoView != null) {
int position = (int) ((seekBar.getProgress() * 1.0f /
seekBar.getMax()) * videoView.getDuration());
videoView.seekTo(position);
}
}
/**
* 视频播放结束监听
* @param mediaPlayer
*/
private boolean isPlayOver = false;
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
mImage.setVisibility(VISIBLE);
mCenterStart.setVisibility(VISIBLE);
mSeek.setProgress(0);
mTop.setVisibility(VISIBLE);
mBottom.setVisibility(VISIBLE);
isTopBottomShow = true;
isPlayOver = true;
}
/**
* 设置全屏
*/
public boolean fullscreen = false;
public void switchScreenOrientation() {
Activity activity = (Activity) getContext();
LayoutParams lp = (LayoutParams) videoView.getLayoutParams();
if (fullscreen) {//全屏,也就是横屏
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
fullscreen = false;
mBack.setVisibility(VISIBLE);
lp.width = (int) Util.getScreenWidth(mContext);
lp.height = (int) Util.getScreenHeight(mContext);
videoView.setLayoutParams(lp);
mFullScreen.setImageResource(R.drawable.ic_player_shrink);
} else {//不是全屏,也就是竖屏
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
fullscreen = true;
mBack.setVisibility(GONE);
lp.width = (int) Util.getScreenWidth(mContext);
lp.height = lp.width * 9 / 16;
videoView.setLayoutParams(lp);
mFullScreen.setImageResource(R.drawable.ic_player_enlarge);
}
}
/**
* 停止播放
*/
public void onStop(){
isPlayOver = false;
mHandler.removeCallbacksAndMessages(null);
videoView.stopPlayback();
videoView = null;
}
/**
* 返回键点击事件
*/
public boolean onBackPressed(){
if(!fullscreen){
switchScreenOrientation();
return true;
}else{
return false;
}
}
}
当返回键点击的时候回去判断这时候是不是全屏状态,如果是全屏状态就先退出全屏,如果不是就直接退出。好了,核心代码就是这些了。需要的朋友可以下载源码。
最后贴一下源码地址:https://gitee.com/fireqiang/OwnDefineVideoView.git