大數據

02.視頻播放器整體結構

02.視頻播放器整體結構

目錄介紹

  • 01.視頻常見的佈局視圖
  • 02.後期可能涉及的視圖
  • 03.需要達到的目的和效果
  • 04.視頻視圖層級示意圖
  • 05.整體架構思路分析流程
  • 06.如何創建不同播放器
  • 07.如何友好處理播放器UI
  • 08.交互交給外部開發者
  • 09.關於優先級視圖展示
  • 10.代碼項目lib代碼介紹

00.視頻播放器通用框架

  • 基礎封裝視頻播放器player,可以在ExoPlayer、MediaPlayer,聲網RTC視頻播放器內核,原生MediaPlayer可以自由切換
  • 對於視圖狀態切換和後期維護拓展,避免功能和業務出現耦合。比如需要支持播放器UI高度定製,而不是該lib庫中UI代碼
  • 針對視頻播放,音頻播放,播放回放,以及視頻直播的功能。使用簡單,代碼拓展性強,封裝性好,主要是和業務徹底解耦,暴露接口監聽給開發者處理業務具體邏輯
  • 該播放器整體架構:播放器內核(自由切換) + 視頻播放器 + 邊播邊緩存 + 高度定製播放器UI視圖層
  • 項目地址:https://github.com/yangchong211/YCVideoPlayer
  • 關於視頻播放器整體功能介紹文檔:https://juejin.im/post/6883457444752654343

01.視頻常見的佈局視圖

  • 視頻底圖(用於顯示初始化視頻時的封面圖),視頻狀態視圖【加載loading,播放異常,加載視頻失敗,播放完成等】
  • 改變亮度和聲音【改變聲音視圖,改變亮度視圖】,改變視頻快進和快退,左右滑動快進和快退視圖(手勢滑動的快進快退提示框)
  • 頂部控制區視圖(包含返回健,title等),底部控制區視圖(包含進度條,播放暫停,時間,切換全屏等)
  • 鎖屏佈局視圖(全屏時展示,其他隱藏),底部播放進度條視圖(很多播放器都有這個),清晰度列表視圖(切換清晰度彈窗)
  • 底部播放進度條視圖(很多播放器都有這個),當bottom視圖顯示時底部進度條隱藏,反之則顯示

02.後期可能涉及的視圖

  • 手勢指導頁面(有些播放器有新手指導功能),離線下載的界面(該界面中包含下載列表, 列表的item編輯(全選, 刪除))
  • 用戶從wifi切換到4g網絡,提示網絡切換彈窗界面(當網絡由wifi變為4g的時候會顯示)
  • 圖片廣告視圖(帶有倒計時消失),開始視頻廣告視圖,非會員試看視圖
  • 彈幕視圖(這個很重要),水印顯示視圖,倍速播放界面(用於控制倍速),底部視頻列表縮略圖視圖
  • 投屏視頻視圖界面,視頻直播間刷禮物界面,老師開課界面,展示更多視圖(下載,分享,切換音頻等)

03.需要達到的目的和效果

  • 基礎封裝視頻播放器player,可以在ExoPlayer、MediaPlayer,聲網RTC視頻播放器內核,原生MediaPlayer可以自由切換
  • 對於視圖狀態切換和後期維護拓展,避免功能和業務出現耦合。比如需要支持播放器UI高度定製,而不是該lib庫中UI代碼
  • 針對視頻播放,音頻播放,播放回放,以及視頻直播的功能。使用簡單,代碼拓展性強,封裝性好,主要是和業務徹底解耦,暴露接口監聽給開發者處理業務具體邏輯

04.視頻視圖層級示意圖

image

05.整體架構思路分析流程

  • 播放器內核

    • 可以切換ExoPlayer、MediaPlayer,IjkPlayer,聲網視頻播放器,這裡使用工廠模式Factory + AbstractVideoPlayer + 各個實現AbstractVideoPlayer抽象類的播放器類
    • 定義抽象的播放器,主要包含視頻初始化,設置,狀態設置,以及播放監聽。由於每個內核播放器api可能不一樣,所以這裡需要實現AbstractVideoPlayer抽象類的播放器類,方便後期統一調用
    • 為了方便創建不同內核player,所以需要創建一個PlayerFactory,定義一個createPlayer創建播放器的抽象方法,然後各個內核都實現它,各自創建自己的播放器
  • VideoPlayer播放器

    • 可以自由切換視頻內核,Player+Controller。player負責播放的邏輯,Controller負責視圖相關的邏輯,兩者之間用接口進行通信
    • 針對Controller,需要定義一個接口,主要負責視圖UI處理邏輯,支持添加各種自定義視圖View【統一實現自定義接口Control】,每個view儘量保證功能單一性,最後通過addView形式添加進來
    • 針對Player,需要定義一個接口,主要負責視頻播放處理邏輯,比如視頻播放,暫停,設置播放進度,設置視頻鏈接,切換播放模式等操作。需要注意把Controller設置到Player裡面,兩者之間通過接口交互
  • UI控制器視圖

    • 定義一個BaseVideoController類,這個主要是集成各種事件的處理邏輯,比如播放器狀態改變,控制視圖隱藏和顯示,播放進度改變,鎖定狀態改變,設備方向監聽等等操作
    • 定義一個view的接口InterControlView,在這裡類裡定義綁定視圖,視圖隱藏和顯示,播放狀態,播放模式,播放進度,鎖屏等操作。這個每個實現類則都可以拿到這些屬性呢
    • 在BaseVideoController中使用LinkedHashMap保存每個自定義view視圖,添加則put進來後然後通過addView將視圖添加到該控制器中,這樣非常方便添加自定義視圖
    • 播放器切換狀態需要改變Controller視圖,比如視頻異常則需要顯示異常視圖view,則它們之間的交互是通過ControlWrapper(同時實現Controller接口和Player接口)實現

06.如何創建不同播放器

  • 目標要求

    • 基礎播放器封裝了包含ExoPlayer、MediaPlayer,ijkPlayer,聲網視頻播放器等
    • 可以自由切換初始化任何一種視頻播放器,比如通過構造傳入類型參數來創建不同的視頻播放器
    PlayerFactory playerFactory = IjkPlayerFactory.create();
    IjkVideoPlayer ijkVideoPlayer = (IjkVideoPlayer) playerFactory.createPlayer(this);
    PlayerFactory playerFactory = ExoPlayerFactory.create();
    ExoMediaPlayer exoMediaPlayer = (ExoMediaPlayer) playerFactory.createPlayer(this);
    PlayerFactory playerFactory = MediaPlayerFactory.create();
    AndroidMediaPlayer androidMediaPlayer = (AndroidMediaPlayer) playerFactory.createPlayer(this);
  • 使用那種形式創建播放器

    • 工廠模式

      • 隱藏內核播放器創建具體細節,開發者只需要關心所需產品對應的工廠,無須關心創建細節即可創建播放器。符合開閉原則
    • 適配器模式

    • 如何做到內核無縫切換?

      • 具體的代碼案例,以及具體做法,在下一篇博客中會介紹到。或者直接看代碼:視頻播放器
  • 播放器內核的架構圖如下所示

    • image

07.如何友好處理播放器UI

  • 發展中遇到的問題

    • 播放器可支持多種場景下的播放,多個產品會用到同一個播放器,這樣就會帶來一個問題,一個播放業務播放器狀態發生變化,其他播放業務必須同步更新播放狀態,各個播放業務之間互相交叉,隨著播放業務的增多,開發和維護成本會急劇增加, 導致後續開發不可持續。
  • 播放器內核和UI層耦合

    • 也就是說視頻player和ui操作柔和到了一起,尤其是兩者之間的交互。比如播放中需要更新UI進度條,播放異常需要顯示異常UI,都比較難處理播放器狀態變化更新UI操作
  • UI難以自定義或者修改麻煩

    • 比如常見的視頻播放器,會把視頻各種視圖寫到xml中,這種方式在後期代碼會很大,而且改動一個小的佈局,則會影響大。這樣到後期往往只敢加代碼,而不敢刪除代碼……
    • 有時候難以適應新的場景,比如添加一個播放廣告,老師開課,或者視頻引導業務需求,則需要到播放器中寫一堆業務代碼。迭代到後期,違背了開閉原則,視頻播放器需要做到和業務分離
  • 視頻播放器結構需要清晰

    • 這個是指該視頻播放器能否看了文檔後快速上手,知道封裝的大概流程。方便後期他人修改和維護,因此需要將視頻播放器功能分離。比如切換內核+視頻播放器(player+controller+view)
  • 一定要解耦合

    • 播放器player與視頻UI解耦:支持添加自定義視頻視圖,比如支持添加自定義廣告,新手引導,或者視頻播放異常等視圖,這個需要較強的拓展性
  • 適合多種業務場景

    • 比如適合播放單個視頻,多個視頻,以及列表視頻,或者類似抖音那種一個頁面一個視頻,還有小窗口播放視頻。也就是適合大多數業務場景
  • 具體操作

    • 播放狀態變化是導致不同播放業務場景之間交叉同步,解除播放業務對播放器的直接操控,採用接口監聽進行解耦。比如:player+controller+interface
    • 具體的代碼案例,以及具體做法,在下一篇博客中會介紹到。或者直接看代碼:視頻播放器

08.交互交給外部開發者

  • 在播放器中,很重要一個就是需要把播放器player的播放模式(小屏幕,正常,全屏模式),以及播放狀態(播放,暫停,異常,完成,加載,緩衝等多種狀態)暴露給控制層view,方便做UI更新。
  • 比如外部開發者想加一個廣告視圖,這個時候肯定需要給它播放器的狀態

    • 添加了自定義播放器視圖,比如添加視頻廣告,可以選擇跳過,選擇播放暫停。那這個視圖view,肯定是需要操作player或者獲取player的狀態的。這個時候就需要暴露監聽視頻播放的狀態接口監聽
    • 首先定義一個InterControlView接口,也就是說所有自定義視頻視圖view需要實現這個接口,該接口中的核心方法有:綁定視圖到播放器,視圖顯示隱藏變化監聽,播放狀態監聽,播放模式監聽,進度監聽,鎖屏監聽等
    • 在BaseVideoController中的狀態監聽中,通過InterControlView接口對象就可以把播放器的狀態傳遞到子類中
  • 舉一個代碼的例子

    • 比如,現在有個業務需求,需要在視頻播放器剛開始添加一個廣告視圖,等待廣告倒計時120秒後,直接進入播放視頻邏輯。相信這個業務場景很常見,大家都碰到過,使用該播放器就特別簡單,代碼如下所示:
    • 首先創建一個自定義view,需要實現InterControlView接口,重寫該接口中所有抽象方法,這裡省略了很多代碼,具體看demo。
    public class AdControlView extends FrameLayout implements InterControlView, View.OnClickListener {
    
        private ControlWrapper mControlWrapper;
        public AdControlView(@NonNull Context context) {
            super(context);
            init(context);
        }
    
        private void init(Context context){
            LayoutInflater.from(getContext()).inflate(R.layout.layout_ad_control_view, this, true);
        }
       
        /**
         * 播放狀態
         * -1               播放錯誤
         * 0                播放未開始
         * 1                播放準備中
         * 2                播放準備就緒
         * 3                正在播放
         * 4                暫停播放
         * 5                正在緩衝(播放器正在播放時,緩衝區數據不足,進行緩衝,緩衝區數據足夠後恢復播放)
         * 6                暫停緩衝(播放器正在播放時,緩衝區數據不足,進行緩衝,此時暫停播放器,繼續緩衝,緩衝區數據足夠後恢復暫停
         * 7                播放完成
         * 8                開始播放中止
         * @param playState                     播放狀態,主要是指播放器的各種狀態
         */
        @Override
        public void onPlayStateChanged(int playState) {
            switch (playState) {
                case ConstantKeys.CurrentState.STATE_PLAYING:
                    mControlWrapper.startProgress();
                    mPlayButton.setSelected(true);
                    break;
                case ConstantKeys.CurrentState.STATE_PAUSED:
                    mPlayButton.setSelected(false);
                    break;
            }
        }
    
        /**
         * 播放模式
         * 普通模式,小窗口模式,正常模式三種其中一種
         * MODE_NORMAL              普通模式
         * MODE_FULL_SCREEN         全屏模式
         * MODE_TINY_WINDOW         小屏模式
         * @param playerState                   播放模式
         */
        @Override
        public void onPlayerStateChanged(int playerState) {
            switch (playerState) {
                case ConstantKeys.PlayMode.MODE_NORMAL:
                    mBack.setVisibility(GONE);
                    mFullScreen.setSelected(false);
                    break;
                case ConstantKeys.PlayMode.MODE_FULL_SCREEN:
                    mBack.setVisibility(VISIBLE);
                    mFullScreen.setSelected(true);
                    break;
            }
            //暫未實現全面屏適配邏輯,需要你自己補全
        }
    }
    • 然後該怎麼使用這個自定義view呢?很簡單,在之前基礎上,通過控制器對象add進來即可,代碼如下所示
    controller = new BasisVideoController(this);
    AdControlView adControlView = new AdControlView(this);
    adControlView.setListener(new AdControlView.AdControlListener() {
        @Override
        public void onAdClick() {
            BaseToast.showRoundRectToast( "廣告點擊跳轉");
        }
    
        @Override
        public void onSkipAd() {
            playVideo();
        }
    });
    controller.addControlComponent(adControlView);
    //設置控制器
    mVideoPlayer.setController(controller);
    mVideoPlayer.setUrl(proxyUrl);
    mVideoPlayer.start();

09.關於優先級視圖展示

  • 視頻播放器為了拓展性,需要暴露view接口供外部開發者自定義視頻播放器視圖,通過addView的形式添加到播放器的控制器中。

    • 這就涉及view視圖的層級性。控制view視圖的顯示和隱藏是特別重要的,這個時候在自定義view中就需要拿到播放器的狀態
  • 舉一個簡單的例子,基礎視頻播放器

    • 添加了基礎播放功能的幾個播放視圖。有播放完成,播放異常,播放加載,頂部標題欄,底部控制條欄,鎖屏,以及手勢滑動欄。如何控制它們的顯示隱藏切換呢?
    • 在addView這些視圖時,大多數的view都是默認GONE隱藏的。比如當視頻初始化時,先緩衝則顯示緩衝view而隱藏其他視圖,接著播放則顯示頂部/底部視圖而隱藏其他視圖
  • 比如有時候需要顯示兩種不同的自定義視圖如何處理

    • 舉個例子,播放的時候,點擊一下視頻,會顯示頂部title視圖和底部控制條視圖,那麼這樣會同時顯示兩個視圖。
    • 點擊頂部title視圖的返回鍵可以關閉播放器,點擊底部控制條視圖的播放暫停可以控制播放條件。這個時候底部控制條視圖FrameLayout的ChildView在整個視頻的底部,頂部title視圖FrameLayout的ChildView在整個視頻的頂部,這樣可以達到上下層都可以相應事件。
  • 那麼FrameLayout層層重疊,如何讓下層不響應事件

    • 在最上方顯示的層加上: android:clickable="true" 可以避免點擊上層觸發底層。或者直接給控制設置一個background顏色也可以。

10.代碼項目lib代碼介紹

image
image
image
image

Leave a Reply

Your email address will not be published. Required fields are marked *