VideoView是继承自SurfaceView的,其实真正用于播放视频的是MediaPlayer+SurfaceView,VideoView只是将这两者封装起来,便于开发者使用。
1、VideoView的三个构造方法,都调用了initVideView()方法。在initVideoView()中做了两个重要的事:
(1)给与SurfaceView相关的SurfaceHolder注册监听器
getHolder().addCallback(mSHCallback);
注:SurfaceView 提供了一个surface,这个surface采用一个单独的线程在屏幕进行渲染,与surface交互使用SurfaceHodler接口,在SurfaceView中通过 getHolder()可以获得SurfaceHolder接口。
SurfaceHolder中注册的回调可以监听到surface的创建和销毁,通过surfaceCreated(SurfaceHolder holder)和surfaceDestroyed(SurfaceHolder holder)。
surface是在SurfaceView的window可见时创建出来的,是在window 隐藏的时候销毁的。
接着就会执行mSHCallback中响应的回调函数,具体看下面代码中注释:
SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
{
//This is called immediately after any structural changes (format or size) have been made to the surface.
public void surfaceChanged(SurfaceHolder holder, int format,int w, int h)
{
mSurfaceWidth = w;
mSurfaceHeight = h;
boolean isValidState = (mTargetState == STATE_PLAYING);
boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h);
if (mMediaPlayer != null && isValidState && hasValidSize) {
if (mSeekWhenPrepared != 0) {
seekTo(mSeekWhenPrepared);
}
start();
}
}
//surface第一次被创建的时候,立即调用
public void surfaceCreated(SurfaceHolder holder)
{
mSurfaceHolder = holder;
openVideo();
}
//This is called immediately before a surface is being destroyed.
public void surfaceDestroyed(SurfaceHolder holder)
{
// after we return from this we can't use the surface any more
mSurfaceHolder = null;
if (mMediaController != null) mMediaController.hide();
release(true);
}
};
(2)初始化在整个播放中都会用到而且很重要的两个变量
mCurrentState = STATE_IDLE;
mTargetState = STATE_IDLE;
2、调用构造函数,生成VideoView对象之后,我们会调用setVideoPath或者setVideoURI传入播放地址。
在setVideoURI(Uri uri, Map<String, String> headers)中会调用openVideo()方法,这是一个很重要的方法。不过只有在setVideoURI和surfaceCreated都执行过后才会真正执行openVideo中的东西,具体看源码。到底是setVideoURI先执行还是surfaceCreated,得看具体的实现流程。
也就是说,只有surface和URI后准备好之后,才会继续。
openVideo中到底做了什么工作呢?
其实看了源码后,也很好理解。初始化了一个MediaPlayer播放器,给这个播放器设置各种监听器,最重要的是调用了MediaPlayer的setDataSource方法传入上面取到的URI,调用mMediaPlayer.setDisplay(mSurfaceHolder);将画面输出到surface中,接着调用mMediaPlayer.prepareAsync();准备数据去,最后mCurrentState = STATE_PREPARING;。
3、接着程序的流程就转到了,数据装备好之后了。在mPreparedListener监听器中。
首先状态进行改变:mCurrentState = STATE_PREPARED;
时刻牢记MediaPlayer的播放流程很重要:
要想播放,最终必须调用MediaPlayer的start方法,无论你之前执行了什么,这是最高原则!
当你发现不能播的时候,你首先想想是不是没有调用start方法。
setDataSource:传入一个播放地址
prepareAsync:异步准备数据去。此时的状态是Preparing状态,在这个状态调用seekTo是没有作用的,不过我们需要把需要seek的位置保留下来(mSeekWhenPrepared),当准备完成时,即在onPrepared回调方法中,状态变为Prepared,这时才真正的去执行seekTo。这时流程转到了和Seek相关的逻辑上,当seek完成时,即在onSeekComplete回调方法中,状态依旧是Prepared。这个时候你要想播放,还得调用start方法。
看一个简单的播放流程
//传入播放地址
setVideoURI
//接着调用了seekto,不过此时的状态是 STATE_IDLE,因此先将需要seek的值保存在 mSeekWhenPrepared
seekTo, mCurrentState : 0
//接着SurfaceView可见了,surface被创建了出来,现在开始真正的去执行openVideo() 此时的状态是 STATE_PREPARING
surfaceCreated//surfaceCreated 比如后调用一次surfaceChanged,此时1280*720这个值,是系统默认的或有什么策略吧
surfaceChanged, w : 1280 h : 720
//接着影片装备好了 ,此时状态是 STATE_PREPARED ,开始真正去seekto,然后去start,可以看源码流程,不过此时影片的尺寸(应该是分辨率)还没有获得
//不过不影响播放流程,以后获得也可以 状态会进入 STATE_PLAYING
onPrepared, mSeekWhenPrepared : 360000 mVideoWidth : 0 mVideoHeight : 0
seekTo, mCurrentState : 2 // 进入这个回调,才说明得到了影片分辨率,然后根据这个分辨率去设定surface的分辨率尺寸。
onVideoSizeChanged, mVideoWidth : 1920 mVideoHeight : 1072
surfaceChanged, w : 1920 h : 1072
注意:
(1)surfaceHolder.setFixedSize(w, h);是设置分辨率,视频窗口的大小是由surfaceView的大小决定的。只要设置surfaceView的layout就行了。(这句话不一定是对的)到现在我都没搞明白setFixedSize设置的是什么东西,到底有什么作用?
(2)播放影片,大小显示问题:
SurfaceView的onMesuare方法中的长宽是SurfaceView的大小,也就是SurfaceView在屏幕中显示的大小。SurfaceView中有一个Surface,这个Surface就是专门用来显示影片的。这个Surface有两个“大小”,一个大小是和SurfaceView的大小一样,就是在屏幕中显示的大小,另一个大小是这surface的分辨率,这个分辨率最终要和影片的分辨率一样,是通过setFixedSize来设置的。要想用户看到画面大小的变化,还是需要调整第一个大小,也就是SurfaceView的大小,也就是说调整SurfaceView的显示的大小,调整SurfaceView的大小,是通过surfaceView的setLayoutParams来调整的。setLayoutParams是告诉父控件,自己想在父控件中的宽高,这只是一种意愿,而真正surfaceView的大小还是在onMesure中决定的,不过,在onMeasure中,这种“意愿”反应在getDefaultSize(w, widthMeasureSpec)和getDefaultSize(h, heightMeasureSpec)中。
不过还有一点需要说明,在VideoView(也就是surfaceView)的onMesure中,有一个计算surfaceView的方法,它不是直接使用setLayoutParams传入的高宽(这个高宽可以是具体的值,比如屏幕的高宽,也可以是系统提供的属性值MATCH_PARENT,有时影片尺寸没有达到我们想要的效果,可能问题就是出在这里),而是让传入的高宽和影片的大小(也就是分辨率)进行运算,然后计算出一个高宽,这样做,是为了根据影片的比例(分辨率)很好的显示影片,而不会让影片看着走样。不过我们有时确实需要,让影片走样(比如全屏),那么,我们就要修改onMesure代码了。
总的来说,就是一句话:“用户看到影片的大小,是由SurfaceView的大小决定的!”
注意:上面的描述不一定对,but,对你的开发一定有帮助
20140123
调用VideoView的start方法,有两个作用:(1)由于状态还不对,调用start只是为了让mTargetState = STATE_PLAYING;为以后再次调用start能进行播放。(2)一切准备就绪真正的播放。