"MPlayer+TextureView" : 封裝一個視訊播放器的 "SDK"

學術袁發表於2018-08-11

用過很多的SDK,關於友盟的、高德地圖的、騰訊的和支付寶的等;在這些功能上的實現會讓我們使用起來特別方便,即拿即用,閱讀下文件就好!對這種神奇的效果,我一直有著好奇心,最近在這塊兒稍微花了下時間和心思,然後這裡封裝一個視訊播放器的SDK來詮釋下這方面的封裝過程和思路。

使用類圖展示業務關係

這裡寫圖片描述

SDK封裝邏輯

SDK = 視訊播放的業務功能 + 外觀模式實現業務功能的封裝

簡單描述

為了使用者快速上手使用,以最少的學習成本完成功能上的實現。比如我們使用過的騰訊、阿里和新浪微博的一些分享的或者地圖的SDK,往往那些複雜的邏輯功能最終呈現給我們開發者的時候,使用類的例項呼叫對應功能的api就搞定了。而實現的原理肯定能夠猜得到,即一層一層的封裝。最後最為開發者不用關心實現的過程,只需呼叫功能性的方法就能實現開發需求。

針對“MPlayer+TextureView” 實現的一個簡單的視訊播放的SDK :
上圖中 CustomVideoView 繼承自 RelativeLayout 所實現的一個基本的播放器功能的視訊播放核心類,只有視訊播放、暫停和停止等功能。而其他業務功能則是使用內部的一個介面 ADVideoPlayerListener其作用有 :
1)承接CustomVideoView中的一些功能,並暴露到外部。從而實現功能上的層層封裝,然後在應用層回撥。
2)實現業務功能上的擴充套件,如點選全屏播放、小平播放、播放視窗劃入螢幕播放和滑出暫停等功能。其中VideoAdShell是對眾多業務上的擴充套件,比如滑入播放、滑出暫停播放功能;
3)業務層VideoAdShell也仿照這種方式繼續向上封裝。

VideoFullDialog是對全屏播放和小屏模式的功能擴充套件。業務功能上從內到外的通訊方式使用的是介面的回撥。最後使用一個類<構建者模式建立例項、外觀模式封裝業務功能>作為該SDK呼叫視訊播放器api的入口,通過簡單的方法呼叫來實現複雜的播放邏輯,並結合介面的回撥來獲得操作上的結果反饋。

視訊效果展示

這裡寫圖片描述
簡單邏輯描述:該頁面是一個RecycleView列表,其中一個item用來展示視訊播放;視訊的item播放視窗滑入螢幕超過自身的50%則開始靜音播放,滑出暫停。點選全屏,則進入全屏的有聲播放。點選全屏按鈕,item的播放視訊的View從item的Layout中remove,然後add到全屏視訊的dialog的Layout中繼續播放;全屏點選關閉按鈕或者播放完畢,則將視訊播放的view再次從dialog的Layout中remove,將視訊的view再次add到列表的item中的Layout繼續執行全屏時候的視訊狀態。視訊核心只有播放、暫停等基本視訊功能,複雜業務擴充套件在上層進行封裝,最後通過外觀模式進行再次的封裝成為方便通用的SDK 以供使用。

程式碼介紹

通過程式碼來描述視訊播放功能,從內到外,各種業務邏輯實現的封裝過程。直至最後的sdk。

做一個視訊播放,它的生命週期是要牢記的

這裡寫圖片描述

核心層邏輯

這裡寫圖片描述
自定義一個RelativeLayout,作為視訊播放”MPlayer+TextureView”的Layout。這樣的設計會讓整個視訊播放元件的使用靈活性大大提高。

設定視訊在列表中的播放視窗大小的寬高比是 16:9

這裡寫圖片描述

當自定義RelativeLayout被載入之後執行

這裡寫圖片描述
上面74、75、76三行程式碼,目的和作用是為了防止播放頁面切換之後出現黑屏問題;
接下來就進入到了視訊播放前的資源載入邏輯。
這裡寫圖片描述
316:表示顯示視訊播放之前,開始進入載入資源之後的視訊載入動畫;
320:設定靜音播放;
321:載入視訊資源;
319:判斷、建立MediaPlayer物件,配置mediaPlayer、vvideoSurface(Surface物件)
這裡寫圖片描述

當視訊資源非同步載入成功之後,回撥

這裡寫圖片描述
呼叫resume();方法進行視訊播放。

public void resume() {
        if (this.playerState != STATE_PAUSING) {
            return;
        }
        LogUtils.d(TAG, "do resume");
        if (!isPlaying()) {
            entryResumeState();
            mediaPlayer.setOnSeekCompleteListener(null);
            mediaPlayer.start();
            mHandler.sendEmptyMessage(TIME_MSG);
            showPauseView(true);
        } else {
            showPauseView(false);
        }
    }

一個視訊播放器的基本功能當然還由暫停、播放按鈕等按鈕的響應邏輯;
這裡寫圖片描述
225:播放器狀態處於暫停狀態,點選按鈕之後,進入播放;
232:否則,重新載入視訊資源,並進行重播;
234:點選進入大屏播放;
237:點選視訊播放視窗呼叫對應的操作邏輯;
以上則是播放核心執行的過程及邏輯。作為一個sdk,除了這些,一定還會有更復雜的業務的。那麼怎對核心層進行封裝擴充套件業務?

核心層業務的封裝擴充套件——視訊播放業務層

對核心層進行業務擴充套件,就是說把一些視訊播放的業務邏輯包裹封裝在核心層外面。
這裡寫圖片描述
達到一種什麼效果呢?“視訊播放業務層” 能訪問到 “核心層” 的內容,核心層同樣也能訪問視訊播放業務層的內容。也就是說,視訊播放業務層和核心層兩者互相暴露響應的介面給對方。這個時候使用什麼方式實現呢?介面回撥。
這裡寫圖片描述
使用這種方式,而不是以內部類暴露介面的實現方式,這樣能夠更好的擴充套件Class A的業務功能。當然回到視訊播放的核心層也是一樣,也是為了更好的進行業務的擴充套件來設計的。並且使用介面進行業務分層也是一種很好的解耦和方式。
好的,進入核心層原始碼,定義核心層需要進行擴充套件的業務,使用介面進行擴充套件。
這裡寫圖片描述
在視訊播放業務層即擴充套件層進行實現,
這裡寫圖片描述
比如其中一個功能上的擴充套件,當視訊播放結束之後的擴充套件:
在核心層
這裡寫圖片描述
在業務擴充套件層——視訊播放的業務層
這裡寫圖片描述
兩個方法進行對比可得出結論,核心層直接處理視訊播放結束之後的邏輯,並使用回撥把視訊播放結束的資訊反饋到上層進行封裝並做相應處理。然後在業務擴充套件層又繼續使用介面,將視訊播放結束的資訊反饋到更上一層,直到最後的sdk封裝並暴露介面對外不的應用層做最後的簡單處理,以此來實現作為一個sdk要實現的功能。
這裡寫圖片描述
324:視訊播放結束之後的回撥;
視訊播放業務層,也使用核心層進行擴充套件的方式,定義介面進行業務的擴充套件和對外介面的暴露。
這裡寫圖片描述
在sdk的頂層實現業務層釋放的介面進行再次封裝,然後使用外觀模式*並結合*構建者模式來實現一個使用者寥寥幾行程式碼就能夠實現複雜視訊播放的業務功能。

視訊視窗、小屏和大屏切換的邏輯演算法

視訊視窗滑入螢幕、滑出螢幕,然後根據視訊視窗進入螢幕視野百分比計算的演算法。

public static int getVisiblePercent(View pView) {
        if (pView != null && pView.isShown()) {
            DisplayMetrics displayMetrics = pView.getContext().getResources().getDisplayMetrics();
            int displayWidth = displayMetrics.widthPixels;
            Rect rect = new Rect();
            pView.getGlobalVisibleRect(rect);
            if ((rect.top > 0) && (rect.left < displayWidth)) {
                double areaVisible = rect.width() * rect.height();
                double areaTotal = pView.getWidth() * pView.getHeight();
                return (int) ((areaVisible / areaTotal) * 100);
            } else {
                return -1;
            }
        }
        return -1;
    }

在列表中點選視訊播放視窗的全屏按鈕,邏輯實現在視訊播放業務層
這裡寫圖片描述

169:記錄、反饋使用者操作給後臺;
175:從列表的Layout中移除自定義的 RelativeLayout (播放視訊的View);
176:新增播放視訊的View到全屏播放的Dialog中,並繼續當前的視訊播放;

public static Bundle getViewProperty(View view) {
        Bundle bundle = new Bundle();
        int[] screenLocation = new int[2];
        view.getLocationOnScreen(screenLocation); //獲取view在整個螢幕中的位置
        bundle.putInt(PROPNAME_SCREENLOCATION_LEFT, screenLocation[0]);
        bundle.putInt(PROPNAME_SCREENLOCATION_TOP, screenLocation[1]);
        bundle.putInt(PROPNAME_WIDTH, view.getWidth());
        bundle.putInt(PROPNAME_HEIGHT, view.getHeight());

        Log.e("Utils", "Left: " + screenLocation[0] + " Top: " + screenLocation[1]
                + " Width: " + view.getWidth() + " Height: " + view.getHeight());
        return bundle;
    }

而當視訊播放完成或者點選關閉按鈕的話,會回撥上面185行程式碼,的下面方法:
這裡寫圖片描述
視訊播放完成或者點選關閉按鈕,dialog會執行dismiss方法,

    @Override
    public void dismiss() {
        LogUtils.e(TAG, "dismiss");
        mParentView.removeView(mVideoView);
        super.dismiss();
    }

dialog中remove掉,列表Layout中add。從而實現視訊的正常大小屏的切換和播放;

相關文章