Android播放器開發之SurfaceView(二)

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。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章