3. MVP视频播放器详解
3.1 VipVideoPlayer.java
主要有三个作用:
1.初始化VipVideoView,对VipVideoView设置,如设置videoView的controller,loader,buffer更新的监听等等。
这个在3.2节会详细分析。
2.开放了视频的接口,如获取视频时长,设置视频播放路径,暂停和恢复等等。
虽然核心的类是VipVideoView,但是用户添加的只是VipVideoPlayer类,所以对外的接口都只能由它提供,VideoView也只是作为VipVideoPlayer中的一个对象,在不同的接口中提供对应的方法。
暂停和恢复:
private boolean mHasPause = false;
private int mVideoPosition = 0;
public void onPause() {
mHasPause = true;
mVideoPosition = (int)mVideoView.getCurrentPosition();
mVideoView.pause();
}
public void onResume() {
if (mHasPause) {
mVideoView.seekTo(mVideoPosition);
mVideoView.start();
}
}
获取视频总长和获取视频当前位置:
public long getDuration(){
return mVideoView.getDuration();
}
public long getCurrentPosition(){
return mVideoView.getCurrentPosition();
}
设置视频的Url:includeAdvert是表示是否包含广告,暂时不使用,默认false。
public void setVideoURI(Uri uri, boolean includeAdvert) {
setVideoURI(uri, null, includeAdvert);
}
public void setVideoURI(Uri uri, Map<String, String> headers, boolean includeAdvert) {
mVideoView.setVideoURI(uri, headers, includeAdvert);
}
3.处理滑动调节音量和播放进度的事件。
@Override
public boolean onTouchEvent(MotionEvent event) {
int uriType = mVideoView.getUri_type();
if (VipVideoView.URI_TYPE_AD == uriType) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = event.getX();
mLastY = event.getY();
mCurrentState = STATE_IDLE;
times = 0;
if ((mReplay.getVisibility() == GONE)) {
if(VipVideoView.URI_TYPE_APP == uriType && mVideoView.isInPlaybackState()){
if (mMediaControllerBottom != null) {
toggleMediaControlsVisiblity();
}
}
}
break;
case MotionEvent.ACTION_MOVE:
if (videoPlayerHandleMotion(event.getX() - mLastX, event.getY() - mLastY)) {
mLastX = event.getX();
mLastY = event.getY();
}
break;
case MotionEvent.ACTION_UP:
if (mCurrentState == STATE_PREPARED) {
videoPlayerHandleSeekTo();
}
mCurrentState = STATE_COMPLETED;
break;
}
return true;
}
private boolean videoPlayerHandleMotion(float dx, float dy) {
//if (mVideoView.isPlaying()) {
if (Math.abs(dx) > VIDEO_MOVE_X) {
if (mCurrentState == STATE_IDLE) {
mVideoView.pause();
mCurrentState = STATE_PREPARED;
} else if (mCurrentState == STATE_PREPARED) {
videoPlayerSeekTo((int)dx);
}
return true;
} else if (Math.abs(dy) > VIDEO_MOVE_Y && mCurrentState != STATE_PREPARED) {
mCurrentState = STATE_VOLUME;
if (dy > 0) {
mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,AudioManager.ADJUST_LOWER,
AudioManager.FX_FOCUS_NAVIGATION_UP);
} else {
mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,AudioManager.ADJUST_RAISE,
AudioManager.FX_FOCUS_NAVIGATION_UP);
}
return true;
}
//}
return false;
}
private int times = 0;
private void videoPlayerSeekTo(int x) {
times = x > 0 ? times + 1 : times - 1;
mMediaControllerTop.setVisibility(View.VISIBLE);
mMediaControllerBottom.setVisibility(View.VISIBLE);
mMediaControllerBottom.setProgressTime(times * VIDEO_MOVE_TIME);
}
private void videoPlayerHandleSeekTo(){
mVideoPosition = (int)mVideoView.getCurrentPosition();
if (times != 0) {
mVideoView.seekTo(mVideoPosition + times * VIDEO_MOVE_TIME);
}
mVideoView.start();
}
所有的触摸事件都是由onTouchEvent发出,URI_TYPE_AD表示广告类型,暂时没有使用。当我们手指按下的时候,触发ACTION DOWN,记录当前的位置和状态,初始化times,times表示移动的时间单位,times*VIDEO_MOVE_TIME表示移动times秒,手指滑动的时候,出发ACTION MOVE,这时候将移动的位置差交给videoPlayerHandleMotion处理,根据上下或者左右移动的位置差判断需要触发事件的性质,如果是上下滑动,直接在MOVE里调节音量,如果是左右滑动,再交个ACTION UP处理。
3.2 VipVideoView.java
视频播放器的核心类,视频播放,消息的相应,外部接口的实现都是由这个类提供。、
首先,了解视频是如何显示出来的。从类的定义可以看出:
public class VipVideoView extends SurfaceView
implements MediaPlayerControl {
VipVideoView其实就是一个SurfaceView,视频的内容就是通过SurfaceView中的Surface绘制出来的,我们需要到访问Surface,可以通过SurfaceView的一个叫做SurfaceHolder的接口现实。在这之前,我们还需要定义一个MediaPlayer,它就是Vitamio提供的多媒体框架,对MediaPlayer进行设置,看这段:
mMediaPlayer.setDisplay(mSurfaceHolder);
这样MediaPlayer就可以通过mSurfaceHolder接口获取到Surface并显示视频内容了。(我们这里只讨论视频的如何显示)
其次,消息的相应。我们创建了一个MediaPlayer对象,应该对它进行一些设置。
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer(mContext);
}
mMediaPlayer.setOnPreparedListener(mPreparedListener);
//当视频准备完成时,这个动作发生在mMediaPlayer.prepareAsync();之后
mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
//当视频尺寸发生改变时
mMediaPlayer.setOnCompletionListener(mCompletionListener);
//当视频播放完成时
mMediaPlayer.setOnErrorListener(mErrorListener);
//当视频播放发生错误时
mMediaPlayer.setOnInfoListener(mOnInfoListener);
//视频播放的时候会发送一些特定的消息,比如开始缓冲,缓冲结束等等
mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
//当视频缓冲发生变化时,这里可以获取到缓冲的百分比
mMediaPlayer.setDisplay(mSurfaceHolder);
//视频需要显示在Surface上
mMediaPlayer.setOnSeekCompleteListener(mSeekCompleteListener);
//直接拖动视频,seek结束时
mMediaPlayer.setOnTimedTextListener(mTimedTextListener);
//视频播放时间更新
if (uri_type == URI_TYPE_AD) {
//URI_TYPE_AD 表示广告类型,在广告播放完成之后会将类型改为URI_TYPE_APP,然后继续播放,此功能已经不使用
mMediaPlayer.setDataSource(mContext, getADUrl(), mHeaders);
} else if (uri_type == URI_TYPE_APP){
mMediaPlayer.setDataSource(mContext, mUri, mHeaders);
//视频播放的数据源
}
//mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setVideoChroma(mVideoChroma == MediaPlayer.VIDEOCHROMA_RGB565 ? MediaPlayer.VIDEOCHROMA_RGB565 : MediaPlayer.VIDEOCHROMA_RGBA);
mMediaPlayer.setScreenOnWhilePlaying(true);
//视频播放过程中,要保持屏幕常亮
if (!mIsComplete) {
//mIsComplete 和 needPrepare 联合使用,是为了解决:(视频播放完成,退出视频播放器再进来的时候,还会加载视频)的问题
mMediaPlayer.prepareAsync();
//准备视频
}
setXXXListener设置了视频播放的监听器,我们一一分析一下各个监听器的工作内容:
(下面说到的controller,loader,replay,就是覆盖在播放器上面的控制器,加载进度,重播,它们都是一个个布局类的试图)
mPreparedListener :这个监听器能监听到事件的前提是mMediaPlayer.preparedAsync(),字面上就可以看出它是一个异步线程,当视频准备好时,mPreparedListener接受到消息,执行onPrepared(), onPrepared()主要是执行真正的start()播放动作,并且显示controller,seekTo到某个需要的位置,例如接着上一次播放结束的位置开始播放。
mSizeChangedListener:主要是获取视频的size
mCompletionListener:播放完成之后将controller隐藏,将replay显示出来
mErrorListener:当发生错误的处理
mOnInfoListener:处理了两个状态,
开始缓冲:如果正在播放,暂停视频,将loader显示
缓冲结束:如果刚刚暂停了,继续播放,隐藏loader,显示controller
mBufferingUpdateListener:这里可以获取到缓冲的百分比,然后提供接口将数值传递到loader显示出来
mSeekCompleteListener:拖动进度条结束之后的一个外部接口
mTimedTextListener:暂时没有用到
播放的流程差不多就是这样: -->prepareAsync-->start-->开始缓冲-->正在缓冲百分比-->缓冲结束-->播放-->播放结束-->显示重播
关于重播,seekTo(0)从头开始,注意,如果我们退出过播放器界面,应该再一次prepareAsync,如果没有退出过,直接start就可以了,这里通过mIsComplete&needPrepare两个参数控制。
参数mCurrentState&mTargetState,表示播放的当前状态和下一个状态,例如执行了mMediaPlayer.prepareAsync();之后,
mCurrentState = STATE_PREPARING; //当前状态就是正在准备
mTargetState = STATE_PLAYING; //下一个状态应该是开始播放
我们也是根据每个播放状态的阶段,对controller,loader,replay等view进行适时的控制。
上面只是播放的流程,那到底入口在哪里呢?
public void setVideoURI(Uri uri, Map<String, String> headers, boolean includeAdvert) {
mUri = uri;
mHeaders = headers;
mSeekWhenPrepared = 0;
uri_type = includeAdvert ? URI_TYPE_AD : URI_TYPE_APP;
openVideo();
requestLayout();
invalidate();
}
设置视频的数据源后,openVideo就会立即创建MediaPlayer对象,然后进行上面的播放流程。