刚开始,还以为VideoView是Android提供我们另一种播放媒体的控件,看了源码之后,原来是MediaPlayer+SurfaceView的封装,只是方便我们使用而已,当然也有好处。既然是封装,那我们来看看VideoView是怎么使用MediaPlayer+SurfaceView的。
源码分析
源码虽不多,但是只分析有用的代码,直接上码:
public class VideoView extends SurfaceView implements MediaPlayerControl,
SubtitleController.Anchor {
private SurfaceHolder mSurfaceHolder = null;
private MediaPlayer mMediaPlayer = null;
}
可以看出,VideoView就是我们用的SurfaceView,而且使用了MediaPlayer来播放视频。
public class VideoView extends SurfaceView implements MediaPlayerControl,
SubtitleController.Anchor {
// 视频的大小
private int mVideoWidth;
private int mVideoHeight;
// SurfaceView的大小
private int mSurfaceWidth;
private int mSurfaceHeight;
public VideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mVideoWidth = 0;
mVideoHeight = 0;
}
/**
* 设置SurfaceView的宽高
* /
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
if (mVideoWidth > 0 && mVideoHeight > 0) {
// 获取测量模式和测量大小
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
// 分情况设置大小
if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
// layout_width = 确定值或match_parent
// layout_height = 确定值或match_parent
// the size is fixed
width = widthSpecSize;
height = heightSpecSize;
// 做适配,不让视频拉伸,保持原来宽高的比例
// for compatibility, we adjust size based on aspect ratio
if ( mVideoWidth * height < width * mVideoHeight ) {
//Log.i("@@@", "image too wide, correcting");
width = height * mVideoWidth / mVideoHeight;
} else if ( mVideoWidth * height > width * mVideoHeight ) {
//Log.i("@@@", "image too tall, correcting");
height = width * mVideoHeight / mVideoWidth;
}
} else if (widthSpecMode == MeasureSpec.EXACTLY) {
// layout_width = 确定值或match_parent
// layout_height = wrap_content
// only the width is fixed, adjust the height to match aspect ratio if possible
width = widthSpecSize;
// 计算高多少,保持原来宽高的比例
height = width * mVideoHeight / mVideoWidth;
if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
// couldn't match aspect ratio within the constraints
height = heightSpecSize;
}
} else if (heightSpecMode == MeasureSpec.EXACTLY) {
// layout_width = wrap_content
// layout_height = 确定值或match_parent
// only the height is fixed, adjust the width to match aspect ratio if possible
height = heightSpecSize;
// 计算宽多少,保持原来宽高的比例
width = height * mVideoWidth / mVideoHeight;
if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
// couldn't match aspect ratio within the constraints
width = widthSpecSize;
}
} else {
// layout_width = wrap_content
// layout_height = wrap_content
// neither the width nor the height are fixed, try to use actual video size
width = mVideoWidth;
height = mVideoHeight;
if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
// too tall, decrease both width and height
height = heightSpecSize;
width = height * mVideoWidth / mVideoHeight;
}
if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
// too wide, decrease both width and height
width = widthSpecSize;
height = width * mVideoHeight / mVideoWidth;
}
}
} else {
// no size yet, just adopt the given spec sizes
}
// 设置SurfaceView的宽高
setMeasuredDimension(width, height);
}
/**
* 这里监听MediaPlayer的视频大小改变事件
*/
MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
new MediaPlayer.OnVideoSizeChangedListener() {
public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
// 获取视频的宽高
mVideoWidth = mp.getVideoWidth();
mVideoHeight = mp.getVideoHeight();
if (mVideoWidth != 0 && mVideoHeight != 0) {
getHolder().setFixedSize(mVideoWidth, mVideoHeight);
// 当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求
// parent view重新调用他的onMeasure onLayout来对重新设置自己位置
requestLayout();
}
}
};
}
从上面的代码可以看出,VideoView允许我们布局时,layout_width或layout_height设置为wrap_content,会自动计算大小,而且还帮我们解决了适配问题,不让视频拉伸导致变形。这值得我们参考!!
public class VideoView extends SurfaceView implements MediaPlayerControl,
SubtitleController.Anchor {
// 控制MediaPlayer控件
private MediaController mMediaController;
}
VideoView还有个方便我们开发播放器的地方就是这个MediaController,一个控制MediePlayer的控件,有开始,暂停,进度条等。但是MediaController是一个View,而且是一个高度封装的View,虽然方便使用,如果要定制化的话,估计不容易,而且市场上很多都需要定制化。个人感觉这里封装得不好,没有面向抽象,而是面向细节(依赖倒置原则)。所以要使用,还得自定义View,组织逻辑。
再了解几个常用的方法:
/**
* 开始播放
*/
@Override
public void start() {
if (isInPlaybackState()) {
mMediaPlayer.start();
mCurrentState = STATE_PLAYING;
}
mTargetState = STATE_PLAYING;
}
/**
* 暂停播放
*/
@Override
public void pause() {
if (isInPlaybackState()) {
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
mCurrentState = STATE_PAUSED;
}
}
mTargetState = STATE_PAUSED;
}
/**
* 释放资源
*/
public void suspend() {
release(false);
}
/**
* 创建MediaPlayer
*/
public void resume() {
openVideo();
}
/**
* 获取视频时长
*/
@Override
public int getDuration() {
if (isInPlaybackState()) {
return mMediaPlayer.getDuration();
}
return -1;
}
/**
* 获取当前时长
*/
@Override
public int getCurrentPosition() {
if (isInPlaybackState()) {
return mMediaPlayer.getCurrentPosition();
}
return 0;
}
/**
* 移动具体位置播放
*/
@Override
public void seekTo(int msec) {
if (isInPlaybackState()) {
mMediaPlayer.seekTo(msec);
mSeekWhenPrepared = 0;
} else {
mSeekWhenPrepared = msec;
}
}
/**
* 是否播放
*/
@Override
public boolean isPlaying() {
return isInPlaybackState() && mMediaPlayer.isPlaying();
}
/**
* 获取缓冲时长
*/
@Override
public int getBufferPercentage() {
if (mMediaPlayer != null) {
return mCurrentBufferPercentage;
}
return 0;
}
/**
* 是否在播放状态:正在播放、暂停,其实是否是有效状态或可操作状态,详细可以查看MediaPlayer的状态机图
*/
private boolean isInPlaybackState() {
return (mMediaPlayer != null &&
mCurrentState != STATE_ERROR &&
mCurrentState != STATE_IDLE &&
mCurrentState != STATE_PREPARING);
}
源码分析到这里!!!
代码实现之后再补上吧!!