视频播放器开发 - VideoView

刚开始,还以为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);
}

源码分析到这里!!!

代码实现之后再补上吧!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值