Android播放器开发之SurfaceView(二)

3 篇文章 0 订阅
2 篇文章 0 订阅

Android播放器开发第一步——自定义VideoView继承SurfaceView


上文介绍了开发Android播发器的简单流程:利用Vitamio开发视频播放器(一)

这里直接进入第一步:

首先我们来看下官方文档对surface的介绍:

  • SurfaceView是视图(View)的继承类,这个视图里内嵌了一个专门用于绘制的Surface。

  • 你可以控制这个Surface的格式和尺寸。Surfaceview控制这个Surface的绘制位置。

  • surface是纵深排序(Z-ordered)的,这表明它总在自己所在窗口的后面。

  • surfaceview提供了一个可见区域,只有在这个可见区域内 的surface部分内容才可见,可见区域外的部分不可见。

  • surface的排版显示受到视图层级关系的影响,它的兄弟视图结点会在顶端显示。这意味者 surface的内容会被它的兄弟视图遮挡,这一特性可以用来放置遮盖物(overlays)(例如,文本和按钮等控件)。

注意,如果surface上面 有透明控件,那么它的每次变化都会引起框架重新计算它和顶层控件的透明效果,这会影响性能。

SurfaceView和View最本质的区别在于:SurfaceView是在一个新起的单独线程中可以重新绘制画面而View必须在UI的主线程中更新画面。所以surface这些特性正好满足了我们作为视频容器的要求,下面代码写起来:

1.定义一个类VideoView继承SurfaceView

2.定义一个接口SurfaceCallback

    private SurfaceCallback mListener;

    public interface SurfaceCallback {

        public void onSurfaceCreated(SurfaceHolder holder);

        public void onSurfaceChanged(SurfaceHolder holder, int format,int width, int height);

        public void onSurfaceDestroyed(SurfaceHolder holder);
    }

这个接口干什么等会在分析

3.初始化SurfaceHolder和它的接口mCallback

private SurfaceHolder mSurfaceHolder;

    private SurfaceHolder.Callback mCallback = new SurfaceHolder.Callback() {

        //surface大小或者格式改变时调用
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height) {
            holder.setKeepScreenOn(true);
            if (mListener != null)
                mListener.onSurfaceChanged(holder, format, width, height);
        }
        //surface创建时调用,一般在这里调用画面
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            mSurfaceHolder = holder;
            if (mListener != null)
                mListener.onSurfaceCreated(holder);
        }
        //surface销毁时调用,一般在这里将画面的停止
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            if (mListener != null)
                mListener.onSurfaceDestroyed(holder);
        }
    };

万物都有它的生命周期,就像Activity,一个surface也不例外

这个SurfaceHolder类似一个监听器(其实就是个接口),重写了三个方法可以看出它监听了surface的创建,销毁和改变。

到时候我们要让播放Activity实现我们的监听,所以我们不在这三个方法里做具体实现而是交给mListener,这个mListener就是第2步中我们定义的SurfaceCallback接口了,到时候我们只需在Activity重写SurfaceCallback下的三个方法就可以监听这个surface了。

4.记得在构造方法中addCallback,为SurfaceHolder添加mCallback回调接口

    public VideoView(Context context, AttributeSet attrs) {
        super(context, attrs);
        getHolder().addCallback(mCallback); // 为SurfaceHolder添加mCallback回调接口
        getHolder().setFormat(PixelFormat.RGBA_8888);
    }

通过SurfaceHolder接口访问这个surface,getHolder()方法可以得到这个接口

5.再写一个初始化方法initialize

private Activity mActivity;

public void initialize(Activity activity, SurfaceCallback l, boolean push) {
        mActivity = activity;
        mListener = l;//拿到回调
        if (mSurfaceHolder != null) {
            mSurfaceHolder = getHolder();
        }
        if (push)
            //设置Surface不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到用户面前
            getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        else
            getHolder().setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
    }

传两个参数,activity后面要用,SurfaceCallback作为回调拿到,其实在构造方法里就可以做这些事了 -,- 但是第三个参数push为后面是否开启硬件加速做准备。

6.设置屏幕尺寸setVideoLayout


private int mSurfaceWidth, mSurfaceHeight;
private int mVideoMode = VIDEO_LAYOUT_SCALE;//默认全屏
public static final int VIDEO_LAYOUT_ORIGIN = 0;//100%
public static final int VIDEO_LAYOUT_SCALE = 1;//全屏
public static final int VIDEO_LAYOUT_STRETCH = 2;//拉伸
public static final int VIDEO_LAYOUT_ZOOM = 3;//裁剪

public void setVideoLayout(int mode, float userRatio, int videoWidth,
            int videoHeight, float videoRatio) {
        mVideoMode = mode;
        setSurfaceLayout(userRatio, videoWidth, videoHeight, videoRatio);
    }
// 屏幕适配
private void setSurfaceLayout(float userRatio, int videoWidth,
            int videoHeight, float videoAspectRatio) {
        LayoutParams lp = getLayoutParams();
        //拿到屏幕的宽高,display.getWidth
        int windowWidth = DeviceUtils.getScreenWidth(mActivity);
        int windowHeight = DeviceUtils.getScreenHeight(mActivity);
        //屏幕宽高比
        float windowRatio = windowWidth / (float) windowHeight;
        //视频宽高比
        float videoRatio = userRatio <= 0.01f ? videoAspectRatio : userRatio;
        mSurfaceHeight = videoHeight;
        mSurfaceWidth = videoWidth;
        //100%,视频原始尺寸显示在屏幕上
        if (VIDEO_LAYOUT_ORIGIN == mVideoMode && mSurfaceWidth < windowWidth
                && mSurfaceHeight < windowHeight) {
            lp.width = (int) (mSurfaceHeight * videoRatio);
            lp.height = mSurfaceHeight;
        } else if (mVideoMode == VIDEO_LAYOUT_ZOOM) {
            //裁剪,通过视频宽高比和屏幕宽高比比较来判断是否裁剪视频宽高
            lp.width = windowRatio > videoRatio ? windowWidth
                    : (int) (videoRatio * windowHeight);
            lp.height = windowRatio < videoRatio ? windowHeight
                    : (int) (windowWidth / videoRatio);
        } else {
            //伸缩
            boolean full = mVideoMode == VIDEO_LAYOUT_STRETCH;
            lp.width = (full || windowRatio < videoRatio) ? windowWidth
                    : (int) (videoRatio * windowHeight);
            lp.height = (full || windowRatio > videoRatio) ? windowHeight
                    : (int) (windowWidth / videoRatio);
        }
        setLayoutParams(lp);
        getHolder().setFixedSize(mSurfaceWidth, mSurfaceHeight); // 设置分辨率,必须的

    }

代码注释的很详细了,这里做了几个简单的视频适配,对外暴露了一个setVideoLayout,拿到了视频的宽高和比,还有视频的模式,把这几个参数传给私有的setSurfaceLayout去判断:

  • 如果为VIDEO_LAYOUT_ORIGIN模式,视频原始模式,那么视频surface高=视频高,surface宽=视频高*视频宽高比=视频宽

  • 如果为VIDEO_LAYOUT_ZOOM模式,裁剪模式,那么判断:

    • 如果屏幕宽高比>视频视频宽高比(以宽做参考系),那么surface的宽为屏幕的宽,surface的高为视频放大到宽正好为屏幕宽时高的大小,就是windowWidth / videoRatio(注意这里是÷),此时可以肯定的是当视频宽正好适配屏幕宽时,视频高肯定会大于屏幕的高(前提我们已经控制分辨率不变了),多出来的部分我们看不到了,所以达到了裁剪多余高的效果
    • 反之屏幕宽高比<视频视频宽高比(以高为参考系),那么就是裁剪宽了。
  • 如果为VIDEO_LAYOUT_STRETCH模式,那么surface宽高就等于屏幕宽高,起到了伸缩的效果。

最后提几点:

  1. 所有SurfaceView和SurfaceHolder.Callback的方法都应该在UI线程里调用,一般来说就是应用程序主线程。渲染线程所要访问的各种变量应该作同步处理。
  2. 由于surface可能被销毁,它只在SurfaceHolder.Callback.surfaceCreated()和SurfaceHolder.Callback.surfaceDestroyed()之间有效,所以要确保渲染线程访问的是合法有效的surface。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值