项目中需要做一个视频播放模块,大体是用的七牛播放器android端SDK+SurfaceView+自定义MediaController。这里就只介绍自己写的mediaController。下面正式开始。
三步走:重点内容
1. 定义MediaControllerInterface接口
2. 自定义controller类,实现MediaControllerInterface接口
3. 创建播放界面实现MediaControllerInterface.MediaControl接口
第一步:先上代码:
public interface MediaControllerInterface {
void show();//显示
void show(int time);//显示
void hide();//隐藏
boolean isShowing();//是否显示
void setCollected(boolean isCollected);//是否被收藏
void setSeekBarEnabled(boolean var1);
void isCompleted();//设置已经播放完成了
void To_change_screen(int w,int h);//改变宽高
interface MediaControl {
void VideoStart();//开始播放
void VideoResume();//继续播放
void VideoPause();//暂停
void VideoStop();//停止
long getDuration();//视频时长
long getCurrentPosition();//当前播放进度
void seekTo(long var1);//拖动进度
boolean isPlaying();//是否正在播放
boolean isPausing();//是否是暂停状态
boolean isFullScreen();//判断是否是全屏状态
void toCollect();//收藏按钮
void actionForFullScreen();//收藏按钮
}
}
这个MediaControllerInterface 接口类主要是为了让controller类实现,与实现了MediaControllerInterface.MediaControl的播放界面类,进行回调操作。
重点在MediaControl接口,需要播放界面实现当中的方法,与controller上面的组件进行绑定。
第二步:部分代码如下:
初始化代码:
//初始化
private View initView() {
View view = LayoutInflater.from(mContext).inflate(R.layout.view_controller, null);
ll_top = (LinearLayout) view.findViewById(R.id.ll_top);
iv_collect = (ImageView) view.findViewById(R.id.iv_collect);
iv_center_play = (ImageView) view.findViewById(R.id.iv_center_play);
ll_bottom = (LinearLayout) view.findViewById(R.id.ll_bottom);
iv_play = (ImageView) view.findViewById(R.id.iv_play);
tv_watched = (TextView) view.findViewById(R.id.tv_watched);
sb_progress = (SeekBar) view.findViewById(R.id.sb_progress);
tv_total = (TextView) view.findViewById(R.id.tv_total);
iv_fullscreen = (ImageView) view.findViewById(R.id.iv_fullscreen);
iv_collect.setOnClickListener(mCollectListener);//收藏监听
iv_center_play.setOnClickListener(mPlayListener);//播放按钮的监听
iv_play.setOnClickListener(mPlayListener);//播放按钮的监听
iv_fullscreen.setOnClickListener(mFullListener);//全屏按钮的监听
sb_progress.setMax((int) SEEKBAR_MAX);
//进度条拖动的监听
sb_progress.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mPlayer.seekTo(mDuration * seekBar.getProgress() / SEEKBAR_MAX);
}
});
return view;
}
与播放器的绑定:
//添加控制绑定
public void setControl(MediaControllerInterface.MediaControl mPlayer) {
this.mPlayer = mPlayer;
}
public void setAnchorView(ViewGroup view) {
mAnchorVGroup = view;
FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
mAnchorVGroup.getWidth(),
mAnchorVGroup.getHeight()
);
removeAllViews();
controllerView = initView();
mAnchorVGroup.addView(controllerView, frameParams);
}
controller触摸事件的获取:目的是为了防止用户操作焦点在controller上时,controller不会消失。
//当触摸事件处于控制器上的时候,显示控制器,不让其消失
@Override
public boolean onTouchEvent(MotionEvent event) {
show(sDefaultTimeout);
return false;
}
//当触摸事件处于控制器上的时候,显示控制器,不让其消失
@Override
public boolean onTrackballEvent(MotionEvent ev) {
show(sDefaultTimeout);
return false;
}
//当触摸事件处于控制器上的时候,显示控制器,不让其消失
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
if (event.getRepeatCount() == 0
&& (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
|| keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || keyCode == KeyEvent.KEYCODE_SPACE)) {
show(sDefaultTimeout);
if (iv_play != null)
iv_play.requestFocus();
return true;
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP) {
if (mPlayer.isPlaying()) {
mPlayer.VideoPause();
}
return true;
} else if (keyCode == KeyEvent.KEYCODE_BACK
|| keyCode == KeyEvent.KEYCODE_MENU) {
hide();
return true;
} else {
show(sDefaultTimeout);
}
return super.dispatchKeyEvent(event);
}
控制器的显示和隐藏
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
long progress;
switch (msg.what) {
case FADE_OUT:
hide();
break;
case SHOW_PROGRESS:
if (mPlayer.isPlaying()) {
progress = setProgress();
} else {
return;
}
if (mShowing && mPlayer.isPlaying()) {
msg = obtainMessage(SHOW_PROGRESS);
sendMessageDelayed(msg, SEEKBAR_MAX - (progress % SEEKBAR_MAX));
}
break;
}
}
};
private long setProgress() {
if (mPlayer == null) {
return 0;
}
long position = mPlayer.getCurrentPosition();
long duration = mPlayer.getDuration();
if (sb_progress != null) {
if (duration > 0) {
long pos = SEEKBAR_MAX * position / duration;
sb_progress.setProgress((int) pos);
}
// int percent = mPlayer.getBufferPercentage();//得到当前缓存进度
// sb_progress.setSecondaryProgress(percent * 10);//设置缓存进度
}
mDuration = duration;
if (tv_total != null)
tv_total.setText(generateTime(mDuration));
if (tv_watched != null)
tv_watched.setText(generateTime(position));
return position;
}
//long数据格式化
private static String generateTime(long position) {
int totalSeconds = (int) (position / 1000);
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
if (hours > 0) {
return String.format(Locale.US, "%02d:%02d:%02d", hours, minutes,
seconds).toString();
} else {
return String.format(Locale.US, "%02d:%02d", minutes, seconds)
.toString();
}
}
@Override
public void show() {
show(sDefaultTimeout);
}
//显示控制器
@Override
public void show(int time) {
if (!mShowing && mAnchorVGroup != null) {
if (mPlayer.isFullScreen()) {//是全屏,显示上下
showTopAndBottom();
} else {//显示底部
showBottom();
}
}
if (mPlayer.isPlaying()) {
iv_play.setImageResource(R.mipmap.k_stop);
} else {
iv_play.setImageResource(R.mipmap.k_play);
}
mHandler.sendEmptyMessage(SHOW_PROGRESS);
mShowing = true;
if (time != 0) {
mHandler.removeMessages(FADE_OUT);
mHandler.sendMessageDelayed(mHandler.obtainMessage(FADE_OUT),
time);
}
}
//隐藏控制器
@Override
public void hide() {
if (mAnchorVGroup == null) {
return;
}
try {
if (mPlayer.isPausing()) {//正处于暂停状态,隐藏上下,中间按钮不隐藏
hideAll();
} else {
hideTopAndBottom();
}
if (mHandler != null) {
mHandler.removeMessages(SHOW_PROGRESS);
}
} catch (IllegalArgumentException ex) {
Log.w("MediaController", "already removed");
}
mShowing = false;
}
第三步:播放页面实现MediaControllerInterface.MediaControl接口,代码如下:
public class MainAcivity extends AppCompatActivity {
private static final String TAG = "MainAcivity";
private SurfaceView mSurfaceView;
private PLMediaPlayer mMediaPlayer;
private AVOptions mAVOptions;
private int mSurfaceWidth = 0;
private int mSurfaceHeight = 0;
private String mVideoPath = null;
private long mLastUpdateStatTime = 0;
private String is_over = "0";
private int sv_height;//记录非全屏状态时,surfaceView的高度,以便退出全屏时,设置回来
boolean firstRendering = true;//视频第一次渲染,在OnInfoListener 中改变值,保证恢复到先前播放的进度
private MyController controller;
private FrameLayout fl_surfaceview_parent;
private boolean seekbarDrag = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.module_activity_video_detail);
super.onCreate(savedInstanceState);
boolean isLiveStreaming = getIntent().getIntExtra("liveStreaming", 0) == 1;
//播放地址
mVideoPath = "";
mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView);
fl_surfaceview_parent = (FrameLayout) findViewById(R.id.fl_surfaceview_parent);
mSurfaceView.getHolder().addCallback(mCallback);
mSurfaceWidth = getResources().getDisplayMetrics().widthPixels;
mSurfaceHeight = getResources().getDisplayMetrics().heightPixels;
mAVOptions = new AVOptions();
mAVOptions.setInteger(AVOptions.KEY_PREPARE_TIMEOUT, 10 * 1000);
int codec = getIntent().getIntExtra("mediaCodec", AVOptions.MEDIA_CODEC_SW_DECODE);
mAVOptions.setInteger(AVOptions.KEY_MEDIACODEC, codec);
mAVOptions.setInteger(AVOptions.KEY_LIVE_STREAMING, isLiveStreaming ? 1 : 0);
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
controller = new MyController(this);
ViewTreeObserver vto2 = mSurfaceView.getViewTreeObserver();
vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mSurfaceView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
sv_height = mSurfaceView.getHeight();
mSurfaceView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, sv_height));
}
});
mSurfaceView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
controller.show();
}
});
}
private boolean isPausing = false;//是否是暂停状态
private boolean isFullScreen = false;//是否是全屏状态
private MediaControllerInterface.MediaControl mPlayerControl = new MediaControllerInterface.MediaControl() {
@Override
public void VideoStart() {
mMediaPlayer.start();
}
@Override
public void VideoResume() {
mMediaPlayer.start();
}
@Override
public void VideoPause() {
mMediaPlayer.pause();
}
@Override
public void VideoStop() {
mMediaPlayer.stop();
}
@Override
public long getDuration() {
return mMediaPlayer.getDuration();
}
@Override
public long getCurrentPosition() {
return mMediaPlayer.getCurrentPosition();
}
@Override
public void seekTo(long var1) {
mMediaPlayer.seekTo(var1);
}
@Override
public boolean isPlaying() {
return mMediaPlayer.isPlaying();
}
@Override
public boolean isPausing() {
return isPausing;
}
@Override
public boolean isFullScreen() {
return isFullScreen;
}
@Override
public void toCollect() {
}
@Override
public void actionForFullScreen() {
if (isFullScreen) {
//退出全屏
Exit_full_screen();
} else {
To_full_screen();
}
}
};
//进入全屏的操作
public void To_full_screen() {
isFullScreen = true;
fl_surfaceview_parent.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT));
mSurfaceView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);//设置activity横屏
getWindow().getDecorView().setSystemUiVisibility(View.INVISIBLE);//隐藏状态栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
Log.i("tag", "1--" + fl_surfaceview_parent.getMeasuredWidth() + "---" + fl_surfaceview_parent.getMeasuredHeight());
controller.To_change_screen(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
}
//退出全屏的操作
public void Exit_full_screen() {
isFullScreen = false;
fl_surfaceview_parent.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
sv_height));
mSurfaceView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, sv_height));
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);//设置activity竖屏
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);//显示状态栏
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
Log.i("tag", "2--" + fl_surfaceview_parent.getWidth() + "---" + fl_surfaceview_parent.getHeight());
controller.To_change_screen(FrameLayout.LayoutParams.MATCH_PARENT, sv_height);
}
@Override
protected void onDestroy() {
super.onDestroy();
release();
controller.release();
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.abandonAudioFocus(null);
}
@Override
protected void onResume() {
super.onResume();
}
public void releaseWithoutStop() {
if (mMediaPlayer != null) {
mMediaPlayer.setDisplay(null);
}
}
public void release() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
}
}
//播放准备
private void prepare() {
if (mMediaPlayer != null) {
mMediaPlayer.setDisplay(mSurfaceView.getHolder());
return;
}
try {
if (mMediaPlayer == null) {
mMediaPlayer = new PLMediaPlayer(this, mAVOptions);
}
mMediaPlayer.setDebugLoggingEnabled(true);
mMediaPlayer.setOnPreparedListener(mOnPreparedListener);
mMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener);
mMediaPlayer.setOnCompletionListener(mOnCompletionListener);
mMediaPlayer.setOnErrorListener(mOnErrorListener);
mMediaPlayer.setOnInfoListener(mOnInfoListener);
mMediaPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener);
// mMediaPlayer.setLooping(true);//循环播放
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
mMediaPlayer.setDataSource(mVideoPath);
mMediaPlayer.setDisplay(mSurfaceView.getHolder());
mMediaPlayer.prepareAsync();
} catch (UnsatisfiedLinkError e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//surfaceview回调函数
private SurfaceHolder.Callback mCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
prepare();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// release();
releaseWithoutStop();
}
};
//视频大小改变的监听
private PLMediaPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener = new PLMediaPlayer.OnVideoSizeChangedListener() {
public void onVideoSizeChanged(PLMediaPlayer mp, int width, int height) {
// resize the display window to fit the screen
if (width != 0 && height != 0) {
float ratioW = (float) width / (float) mSurfaceWidth;
float ratioH = (float) height / (float) mSurfaceHeight;
float ratio = Math.max(ratioW, ratioH);
width = (int) Math.ceil((float) width / ratio);
height = (int) Math.ceil((float) height / ratio);
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(width, height);
layout.gravity = Gravity.CENTER;
mSurfaceView.setLayoutParams(layout);
}
}
};
//mediaplayer准备好监听
private PLMediaPlayer.OnPreparedListener mOnPreparedListener = new PLMediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(PLMediaPlayer mp, int preparedTime) {
controller.setControl(mPlayerControl);
controller.setAnchorView(fl_surfaceview_parent);
controller.setSeekBarEnabled(false);
mMediaPlayer.start();
}
};
/**
* 转换播放时间
*
* @param milliseconds 传入毫秒值
* @return 返回 hh:mm:ss或mm:ss格式的数据
*/
@SuppressLint("SimpleDateFormat")
public String getShowTime(long milliseconds) {
// 获取日历函数
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(milliseconds);
SimpleDateFormat dateFormat = null;
// 判断是否大于60分钟,如果大于就显示小时。设置日期格式
if (milliseconds / 60000 > 60) {
dateFormat = new SimpleDateFormat("hh:mm:ss");
} else {
dateFormat = new SimpleDateFormat("mm:ss");
}
return dateFormat.format(calendar.getTime());
}
//视频播放状态监听
private PLMediaPlayer.OnInfoListener mOnInfoListener = new PLMediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(PLMediaPlayer mp, int what, int extra) {
switch (what) {
case PLMediaPlayer.MEDIA_INFO_BUFFERING_START://第一帧视频已成功渲染
break;
case PLMediaPlayer.MEDIA_INFO_BUFFERING_END:
break;
case PLMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START:
// showToastTips("first video render time: " + extra + "ms");
if (firstRendering && "0".equals(is_over)) {//第一次渲染,并且是未看过
firstRendering = false;
}
break;
case PLMediaPlayer.MEDIA_INFO_VIDEO_GOP_TIME:
break;
case PLMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START:
break;
case PLMediaPlayer.MEDIA_INFO_SWITCHING_SW_DECODE:
break;
case PLMediaPlayer.MEDIA_INFO_METADATA:
break;
case PLMediaPlayer.MEDIA_INFO_VIDEO_BITRATE:
case PLMediaPlayer.MEDIA_INFO_VIDEO_FPS:
// updateStatInfo();
break;
case PLMediaPlayer.MEDIA_INFO_CONNECTED:
break;
default:
break;
}
return true;
}
};
//视频缓存监听
private PLMediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener = new PLMediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(PLMediaPlayer mp, int percent) {
long current = System.currentTimeMillis();
if (current - mLastUpdateStatTime > 3000) {
mLastUpdateStatTime = current;
}
}
};
private boolean onComplet = false;//是否已经播放完一个视频
//视频播放完成监听
private PLMediaPlayer.OnCompletionListener mOnCompletionListener = new PLMediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(PLMediaPlayer mp) {
// 设置播放标记为false
controller.isCompleted();
}
};
private PLMediaPlayer.OnErrorListener mOnErrorListener = new PLMediaPlayer.OnErrorListener() {
@Override
public boolean onError(PLMediaPlayer mp, int errorCode) {
switch (errorCode) {
case PLMediaPlayer.ERROR_CODE_IO_ERROR://网络异常
/**
* SDK will do reconnecting automatically
*/
//如果网络异常播放失败,尝试重新连接
reload();
return false;
case PLMediaPlayer.ERROR_CODE_OPEN_FAILED://播放器打开失败
//重新打开播放器
release();//先关闭先前的播放器
prepare();
break;
case PLMediaPlayer.ERROR_CODE_SEEK_FAILED://seek拖动失败
break;
case PLMediaPlayer.ERROR_CODE_PLAYER_DESTROYED://播放器已被销毁,需要再次 setVideoURL 或 prepareAsync
//重新打开播放器
release();//先关闭先前的播放器
prepare();
break;
default://未知错误
break;
}
return true;
}
};
//如果网络异常播放失败,尝试重新连接
public void reload() {
release();//先关闭先前的播放器
//判断网络连接
prepare();
}
}
在播放页面主要实现了MediaControl接口,创建了一个controller对象,让界面上的操作得以体现到controller对象上,并进行相关操作,如show(),hide()等。。。
注:运行demo时,需要先设置一下mVideoPath ,将视频地址设置好,再进播放
demo下载地址为:https://github.com/midux001/mediacontroller