開發與維運

05.視頻播放器內核切換封裝

05.視頻播放器內核切換封裝

目錄介紹

  • 01.視頻播放器內核封裝需求
  • 02.播放器內核架構圖
  • 03.如何兼容不同內核播放器
  • 04.看一下ijk的內核實現類
  • 05.看一下exo的內核實現類
  • 06.如何創建不同內核播放器
  • 07.看一下工廠類實現代碼
  • 08.後期如何添加新的內核

00.視頻播放器通用框架

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

01.視頻播放器內核封裝需求

  • 播放器內核難以切換

    • 不同的視頻播放器內核,由於api不一樣,所以難以切換操作。要是想兼容內核切換,就必須自己制定一個視頻接口+實現類的播放器
  • 一定要解耦合

    • 播放器內核與播放器解耦: 支持更多的播放場景、以及新的播放業務快速接入,並且不影響其他播放業務,比如後期添加阿里雲播放器內核,或者騰訊播放器內核
  • 傳入不同類型方便創建不同內核

    • 隱藏內核播放器創建具體細節,開發者只需要關心所需產品對應的工廠,無須關心創建細節,甚至無須知道具體播放器類的類名。需要符合開閉原則

02.播放器內核架構圖

image

03.如何兼容不同內核播放器

  • 提問:針對不同內核播放器,比如谷歌的ExoPlayer,B站的IjkPlayer,還有原生的MediaPlayer,有些api不一樣,那使用的時候如何統一api呢?

    • 比如說,ijk和exo的視頻播放listener監聽api就完全不同,這個時候需要做兼容處理
    • 定義接口,然後各個不同內核播放器實現接口,重寫抽象方法。調用的時候,獲取接口對象調用api,這樣就可以統一Api
  • 定義一個接口,這個接口有什麼呢?這個接口定義通用視頻播放器方法,比如常見的有:視頻初始化,設置url,加載,以及播放狀態,簡單來說可以分為三個部分。

    • 第一部分:視頻初始化實例對象方法,主要包括:initPlayer初始化視頻,setDataSource設置視頻播放器地址,setSurface設置視頻播放器渲染view,prepareAsync開始準備播放操作
    • 第二部分:視頻播放器狀態方法,主要包括:播放,暫停,恢復,重製,設置進度,釋放資源,獲取進度,設置速度,設置音量
    • 第三部分:player綁定view後,需要監聽播放狀態,比如播放異常,播放完成,播放準備,播放size變化,還有播放準備

04.看一下ijk的內核實現類

  • ijk的內核實現類代碼如下所示

    public class IjkVideoPlayer extends AbstractVideoPlayer {
    
        protected IjkMediaPlayer mMediaPlayer;
        private int mBufferedPercent;
        private Context mAppContext;
    
        public IjkVideoPlayer(Context context) {
            if (context instanceof Application){
                mAppContext = context;
            } else {
                mAppContext = context.getApplicationContext();
            }
        }
    
        @Override
        public void initPlayer() {
            mMediaPlayer = new IjkMediaPlayer();
            //native日誌
            IjkMediaPlayer.native_setLogLevel(VideoLogUtils.isIsLog()
                    ? IjkMediaPlayer.IJK_LOG_INFO : IjkMediaPlayer.IJK_LOG_SILENT);
            setOptions();
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            initListener();
        }
    
        @Override
        public void setOptions() {
        }
    
        /**
         * ijk視頻播放器監聽listener
         */
        private void initListener() {
            // 設置監聽,可以查看ijk中的IMediaPlayer源碼監聽事件
            // 設置視頻錯誤監聽器
            mMediaPlayer.setOnErrorListener(onErrorListener);
            // 設置視頻播放完成監聽事件
            mMediaPlayer.setOnCompletionListener(onCompletionListener);
            // 設置視頻信息監聽器
            mMediaPlayer.setOnInfoListener(onInfoListener);
            // 設置視頻緩衝更新監聽事件
            mMediaPlayer.setOnBufferingUpdateListener(onBufferingUpdateListener);
            // 設置準備視頻播放監聽事件
            mMediaPlayer.setOnPreparedListener(onPreparedListener);
            // 設置視頻大小更改監聽器
            mMediaPlayer.setOnVideoSizeChangedListener(onVideoSizeChangedListener);
            // 設置視頻seek完成監聽事件
            mMediaPlayer.setOnSeekCompleteListener(onSeekCompleteListener);
            // 設置時間文本監聽器
            mMediaPlayer.setOnTimedTextListener(onTimedTextListener);
            mMediaPlayer.setOnNativeInvokeListener(new IjkMediaPlayer.OnNativeInvokeListener() {
                @Override
                public boolean onNativeInvoke(int i, Bundle bundle) {
                    return true;
                }
            });
        }
    
        /**
         * 設置播放地址
         *
         * @param path    播放地址
         * @param headers 播放地址請求頭
         */
        @Override
        public void setDataSource(String path, Map<String, String> headers) {
            // 設置dataSource
            if(path==null || path.length()==0){
                if (mPlayerEventListener!=null){
                    mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_URL_NULL, 0);
                }
                return;
            }
            try {
                //解析path
                Uri uri = Uri.parse(path);
                if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())) {
                    RawDataSourceProvider rawDataSourceProvider = RawDataSourceProvider.create(mAppContext, uri);
                    mMediaPlayer.setDataSource(rawDataSourceProvider);
                } else {
                    //處理UA問題
                    if (headers != null) {
                        String userAgent = headers.get("User-Agent");
                        if (!TextUtils.isEmpty(userAgent)) {
                            mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "user_agent", userAgent);
                        }
                    }
                    mMediaPlayer.setDataSource(mAppContext, uri, headers);
                }
            } catch (Exception e) {
                mPlayerEventListener.onError();
            }
        }
    
        /**
         * 用於播放raw和asset裡面的視頻文件
         */
        @Override
        public void setDataSource(AssetFileDescriptor fd) {
            try {
                mMediaPlayer.setDataSource(new RawDataSourceProvider(fd));
            } catch (Exception e) {
                mPlayerEventListener.onError();
            }
        }
    
        /**
         * 設置渲染視頻的View,主要用於TextureView
         * @param surface                           surface
         */
        @Override
        public void setSurface(Surface surface) {
            mMediaPlayer.setSurface(surface);
        }
    
        /**
         * 準備開始播放(異步)
         */
        @Override
        public void prepareAsync() {
            try {
                mMediaPlayer.prepareAsync();
            } catch (IllegalStateException e) {
                mPlayerEventListener.onError();
            }
        }
    
        /**
         * 暫停
         */
        @Override
        public void pause() {
            try {
                mMediaPlayer.pause();
            } catch (IllegalStateException e) {
                mPlayerEventListener.onError();
            }
        }
    
        /**
         * 播放
         */
        @Override
        public void start() {
            try {
                mMediaPlayer.start();
            } catch (IllegalStateException e) {
                mPlayerEventListener.onError();
            }
        }
    
        /**
         * 停止
         */
        @Override
        public void stop() {
            try {
                mMediaPlayer.stop();
            } catch (IllegalStateException e) {
                mPlayerEventListener.onError();
            }
        }
    
        /**
         * 重置播放器
         */
        @Override
        public void reset() {
            mMediaPlayer.reset();
            mMediaPlayer.setOnVideoSizeChangedListener(onVideoSizeChangedListener);
            setOptions();
        }
    
        /**
         * 是否正在播放
         */
        @Override
        public boolean isPlaying() {
            return mMediaPlayer.isPlaying();
        }
    
    
        /**
         * 調整進度
         */
        @Override
        public void seekTo(long time) {
            try {
                mMediaPlayer.seekTo((int) time);
            } catch (IllegalStateException e) {
                mPlayerEventListener.onError();
            }
        }
    
        /**
         * 釋放播放器
         */
        @Override
        public void release() {
            mMediaPlayer.setOnErrorListener(null);
            mMediaPlayer.setOnCompletionListener(null);
            mMediaPlayer.setOnInfoListener(null);
            mMediaPlayer.setOnBufferingUpdateListener(null);
            mMediaPlayer.setOnPreparedListener(null);
            mMediaPlayer.setOnVideoSizeChangedListener(null);
            new Thread() {
                @Override
                public void run() {
                    try {
                        mMediaPlayer.release();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    
        /**
         * 獲取當前播放的位置
         */
        @Override
        public long getCurrentPosition() {
            return mMediaPlayer.getCurrentPosition();
        }
    
        /**
         * 獲取視頻總時長
         */
        @Override
        public long getDuration() {
            return mMediaPlayer.getDuration();
        }
    
        /**
         * 獲取緩衝百分比
         */
        @Override
        public int getBufferedPercentage() {
            return mBufferedPercent;
        }
    
        /**
         * 設置渲染視頻的View,主要用於SurfaceView
         */
        @Override
        public void setDisplay(SurfaceHolder holder) {
            mMediaPlayer.setDisplay(holder);
        }
    
        /**
         * 設置音量
         */
        @Override
        public void setVolume(float v1, float v2) {
            mMediaPlayer.setVolume(v1, v2);
        }
    
        /**
         * 設置是否循環播放
         */
        @Override
        public void setLooping(boolean isLooping) {
            mMediaPlayer.setLooping(isLooping);
        }
    
        /**
         * 設置播放速度
         */
        @Override
        public void setSpeed(float speed) {
            mMediaPlayer.setSpeed(speed);
        }
    
        /**
         * 獲取播放速度
         */
        @Override
        public float getSpeed() {
            return mMediaPlayer.getSpeed(0);
        }
    
        /**
         * 獲取當前緩衝的網速
         */
        @Override
        public long getTcpSpeed() {
            return mMediaPlayer.getTcpSpeed();
        }
    
        /**
         * 設置視頻錯誤監聽器
         * int MEDIA_INFO_VIDEO_RENDERING_START = 3;//視頻準備渲染
         * int MEDIA_INFO_BUFFERING_START = 701;//開始緩衝
         * int MEDIA_INFO_BUFFERING_END = 702;//緩衝結束
         * int MEDIA_INFO_VIDEO_ROTATION_CHANGED = 10001;//視頻選擇信息
         * int MEDIA_ERROR_SERVER_DIED = 100;//視頻中斷,一般是視頻源異常或者不支持的視頻類型。
         * int MEDIA_ERROR_IJK_PLAYER = -10000,//一般是視頻源有問題或者數據格式不支持,比如音頻不是AAC之類的
         * int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200;//數據錯誤沒有有效的回收
         */
        private IMediaPlayer.OnErrorListener onErrorListener = new IMediaPlayer.OnErrorListener() {
            @Override
            public boolean onError(IMediaPlayer iMediaPlayer, int framework_err, int impl_err) {
                mPlayerEventListener.onError();
                VideoLogUtils.d("IjkVideoPlayer----listener---------onError ——> STATE_ERROR ———— what:" + framework_err + ", extra: " + impl_err);
                return true;
            }
        };
    
        /**
         * 設置視頻播放完成監聽事件
         */
        private IMediaPlayer.OnCompletionListener onCompletionListener = new IMediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(IMediaPlayer iMediaPlayer) {
                mPlayerEventListener.onCompletion();
                VideoLogUtils.d("IjkVideoPlayer----listener---------onCompletion ——> STATE_COMPLETED");
            }
        };
    
    
        /**
         * 設置視頻信息監聽器
         */
        private IMediaPlayer.OnInfoListener onInfoListener = new IMediaPlayer.OnInfoListener() {
            @Override
            public boolean onInfo(IMediaPlayer iMediaPlayer, int what, int extra) {
                mPlayerEventListener.onInfo(what, extra);
                VideoLogUtils.d("IjkVideoPlayer----listener---------onInfo ——> ———— what:" + what + ", extra: " + extra);
                return true;
            }
        };
    
        /**
         * 設置視頻緩衝更新監聽事件
         */
        private IMediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new IMediaPlayer.OnBufferingUpdateListener() {
            @Override
            public void onBufferingUpdate(IMediaPlayer iMediaPlayer, int percent) {
                mBufferedPercent = percent;
            }
        };
    
    
        /**
         * 設置準備視頻播放監聽事件
         */
        private IMediaPlayer.OnPreparedListener onPreparedListener = new IMediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(IMediaPlayer iMediaPlayer) {
                mPlayerEventListener.onPrepared();
                VideoLogUtils.d("IjkVideoPlayer----listener---------onPrepared ——> STATE_PREPARED");
            }
        };
    
        /**
         * 設置視頻大小更改監聽器
         */
        private IMediaPlayer.OnVideoSizeChangedListener onVideoSizeChangedListener = new IMediaPlayer.OnVideoSizeChangedListener() {
            @Override
            public void onVideoSizeChanged(IMediaPlayer iMediaPlayer, int width, int height,
                                           int sar_num, int sar_den) {
                int videoWidth = iMediaPlayer.getVideoWidth();
                int videoHeight = iMediaPlayer.getVideoHeight();
                if (videoWidth != 0 && videoHeight != 0) {
                    mPlayerEventListener.onVideoSizeChanged(videoWidth, videoHeight);
                }
                VideoLogUtils.d("IjkVideoPlayer----listener---------onVideoSizeChanged ——> WIDTH:" + width + ", HEIGHT:" + height);
            }
        };
    
        /**
         * 設置時間文本監聽器
         */
        private IMediaPlayer.OnTimedTextListener onTimedTextListener = new IMediaPlayer.OnTimedTextListener() {
            @Override
            public void onTimedText(IMediaPlayer iMediaPlayer, IjkTimedText ijkTimedText) {
    
            }
        };
    
        /**
         * 設置視頻seek完成監聽事件
         */
        private IMediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new IMediaPlayer.OnSeekCompleteListener() {
            @Override
            public void onSeekComplete(IMediaPlayer iMediaPlayer) {
    
            }
        };
    }

05.看一下exo的內核實現類

  • exo的內核實現類代碼如下所示,和ijk的api有些區別

    public class ExoMediaPlayer extends AbstractVideoPlayer implements VideoListener, Player.EventListener {
    
        protected Context mAppContext;
        protected SimpleExoPlayer mInternalPlayer;
        protected MediaSource mMediaSource;
        protected ExoMediaSourceHelper mMediaSourceHelper;
        private PlaybackParameters mSpeedPlaybackParameters;
        private int mLastReportedPlaybackState = Player.STATE_IDLE;
        private boolean mLastReportedPlayWhenReady = false;
        private boolean mIsPreparing;
        private boolean mIsBuffering;
    
        private LoadControl mLoadControl;
        private RenderersFactory mRenderersFactory;
        private TrackSelector mTrackSelector;
    
        public ExoMediaPlayer(Context context) {
            if (context instanceof Application){
                mAppContext = context;
            } else {
                mAppContext = context.getApplicationContext();
            }
            mMediaSourceHelper = ExoMediaSourceHelper.getInstance(context);
        }
    
        @Override
        public void initPlayer() {
            //創建exo播放器
            mInternalPlayer = new SimpleExoPlayer.Builder(
                    mAppContext,
                    mRenderersFactory == null ? mRenderersFactory = new DefaultRenderersFactory(mAppContext) : mRenderersFactory,
                    mTrackSelector == null ? mTrackSelector = new DefaultTrackSelector(mAppContext) : mTrackSelector,
                    mLoadControl == null ? mLoadControl = new DefaultLoadControl() : mLoadControl,
                    DefaultBandwidthMeter.getSingletonInstance(mAppContext),
                    Util.getLooper(),
                    new AnalyticsCollector(Clock.DEFAULT),
                    /* useLazyPreparation= */ true,
                    Clock.DEFAULT)
                    .build();
            setOptions();
    
            //播放器日誌
            if (VideoLogUtils.isIsLog() && mTrackSelector instanceof MappingTrackSelector) {
                mInternalPlayer.addAnalyticsListener(new EventLogger((MappingTrackSelector) mTrackSelector, "ExoPlayer"));
            }
            initListener();
        }
    
        /**
         * exo視頻播放器監聽listener
         */
        private void initListener() {
            mInternalPlayer.addListener(this);
            mInternalPlayer.addVideoListener(this);
        }
    
        public void setTrackSelector(TrackSelector trackSelector) {
            mTrackSelector = trackSelector;
        }
    
        public void setRenderersFactory(RenderersFactory renderersFactory) {
            mRenderersFactory = renderersFactory;
        }
    
        public void setLoadControl(LoadControl loadControl) {
            mLoadControl = loadControl;
        }
    
        /**
         * 設置播放地址
         *
         * @param path    播放地址
         * @param headers 播放地址請求頭
         */
        @Override
        public void setDataSource(String path, Map<String, String> headers) {
            // 設置dataSource
            if(path==null || path.length()==0){
                if (mPlayerEventListener!=null){
                    mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_URL_NULL, 0);
                }
                return;
            }
            mMediaSource = mMediaSourceHelper.getMediaSource(path, headers);
        }
    
        @Override
        public void setDataSource(AssetFileDescriptor fd) {
            //no support
        }
    
        /**
         * 準備開始播放(異步)
         */
        @Override
        public void prepareAsync() {
            if (mInternalPlayer == null){
                return;
            }
            if (mMediaSource == null){
                return;
            }
            if (mSpeedPlaybackParameters != null) {
                mInternalPlayer.setPlaybackParameters(mSpeedPlaybackParameters);
            }
            mIsPreparing = true;
            mMediaSource.addEventListener(new Handler(), mMediaSourceEventListener);
            //準備播放
            mInternalPlayer.prepare(mMediaSource);
        }
    
        /**
         * 播放
         */
        @Override
        public void start() {
            if (mInternalPlayer == null){
                return;
            }
            mInternalPlayer.setPlayWhenReady(true);
        }
    
        /**
         * 暫停
         */
        @Override
        public void pause() {
            if (mInternalPlayer == null){
                return;
            }
            mInternalPlayer.setPlayWhenReady(false);
        }
    
        /**
         * 停止
         */
        @Override
        public void stop() {
            if (mInternalPlayer == null){
                return;
            }
            mInternalPlayer.stop();
        }
    
        private MediaSourceEventListener mMediaSourceEventListener = new MediaSourceEventListener() {
            @Override
            public void onReadingStarted(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
                if (mPlayerEventListener != null && mIsPreparing) {
                    mPlayerEventListener.onPrepared();
                }
            }
        };
    
        /**
         * 重置播放器
         */
        @Override
        public void reset() {
            if (mInternalPlayer != null) {
                mInternalPlayer.stop(true);
                mInternalPlayer.setVideoSurface(null);
                mIsPreparing = false;
                mIsBuffering = false;
                mLastReportedPlaybackState = Player.STATE_IDLE;
                mLastReportedPlayWhenReady = false;
            }
        }
    
        /**
         * 是否正在播放
         */
        @Override
        public boolean isPlaying() {
            if (mInternalPlayer == null){
                return false;
            }
            int state = mInternalPlayer.getPlaybackState();
            switch (state) {
                case Player.STATE_BUFFERING:
                case Player.STATE_READY:
                    return mInternalPlayer.getPlayWhenReady();
                case Player.STATE_IDLE:
                case Player.STATE_ENDED:
                default:
                    return false;
            }
        }
    
        /**
         * 調整進度
         */
        @Override
        public void seekTo(long time) {
            if (mInternalPlayer == null){
                return;
            }
            mInternalPlayer.seekTo(time);
        }
    
        /**
         * 釋放播放器
         */
        @Override
        public void release() {
            if (mInternalPlayer != null) {
                mInternalPlayer.removeListener(this);
                mInternalPlayer.removeVideoListener(this);
                final SimpleExoPlayer player = mInternalPlayer;
                mInternalPlayer = null;
                new Thread() {
                    @Override
                    public void run() {
                        //異步釋放,防止卡頓
                        player.release();
                    }
                }.start();
            }
    
            mIsPreparing = false;
            mIsBuffering = false;
            mLastReportedPlaybackState = Player.STATE_IDLE;
            mLastReportedPlayWhenReady = false;
            mSpeedPlaybackParameters = null;
        }
    
        /**
         * 獲取當前播放的位置
         */
        @Override
        public long getCurrentPosition() {
            if (mInternalPlayer == null){
                return 0;
            }
            return mInternalPlayer.getCurrentPosition();
        }
    
        /**
         * 獲取視頻總時長
         */
        @Override
        public long getDuration() {
            if (mInternalPlayer == null){
                return 0;
            }
            return mInternalPlayer.getDuration();
        }
    
        /**
         * 獲取緩衝百分比
         */
        @Override
        public int getBufferedPercentage() {
            return mInternalPlayer == null ? 0 : mInternalPlayer.getBufferedPercentage();
        }
    
        /**
         * 設置渲染視頻的View,主要用於SurfaceView
         */
        @Override
        public void setSurface(Surface surface) {
            if (mInternalPlayer != null) {
                mInternalPlayer.setVideoSurface(surface);
            }
        }
    
        @Override
        public void setDisplay(SurfaceHolder holder) {
            if (holder == null){
                setSurface(null);
            } else{
                setSurface(holder.getSurface());
            }
        }
    
        /**
         * 設置音量
         */
        @Override
        public void setVolume(float leftVolume, float rightVolume) {
            if (mInternalPlayer != null){
                mInternalPlayer.setVolume((leftVolume + rightVolume) / 2);
            }
        }
    
        /**
         * 設置是否循環播放
         */
        @Override
        public void setLooping(boolean isLooping) {
            if (mInternalPlayer != null){
                mInternalPlayer.setRepeatMode(isLooping ? Player.REPEAT_MODE_ALL : Player.REPEAT_MODE_OFF);
            }
        }
    
        @Override
        public void setOptions() {
            //準備好就開始播放
            mInternalPlayer.setPlayWhenReady(true);
        }
    
        /**
         * 設置播放速度
         */
        @Override
        public void setSpeed(float speed) {
            PlaybackParameters playbackParameters = new PlaybackParameters(speed);
            mSpeedPlaybackParameters = playbackParameters;
            if (mInternalPlayer != null) {
                mInternalPlayer.setPlaybackParameters(playbackParameters);
            }
        }
    
        /**
         * 獲取播放速度
         */
        @Override
        public float getSpeed() {
            if (mSpeedPlaybackParameters != null) {
                return mSpeedPlaybackParameters.speed;
            }
            return 1f;
        }
    
        /**
         * 獲取當前緩衝的網速
         */
        @Override
        public long getTcpSpeed() {
            // no support
            return 0;
        }
    
        @Override
        public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
            if (mPlayerEventListener == null){
                return;
            }
            if (mIsPreparing){
                return;
            }
            if (mLastReportedPlayWhenReady != playWhenReady || mLastReportedPlaybackState != playbackState) {
                switch (playbackState) {
                    //最開始調用的狀態
                    case Player.STATE_IDLE:
                        break;
                    //開始緩充
                    case Player.STATE_BUFFERING:
                        mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_BUFFERING_START, getBufferedPercentage());
                        mIsBuffering = true;
                        break;
                    //開始播放
                    case Player.STATE_READY:
                        if (mIsBuffering) {
                            mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_BUFFERING_END, getBufferedPercentage());
                            mIsBuffering = false;
                        }
                        break;
                    //播放器已經播放完了媒體
                    case Player.STATE_ENDED:
                        mPlayerEventListener.onCompletion();
                        break;
                    default:
                        break;
                }
                mLastReportedPlaybackState = playbackState;
                mLastReportedPlayWhenReady = playWhenReady;
            }
        }
    
        @Override
        public void onPlayerError(ExoPlaybackException error) {
            if (mPlayerEventListener != null) {
                mPlayerEventListener.onError();
            }
        }
    
        @Override
        public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
            if (mPlayerEventListener != null) {
                mPlayerEventListener.onVideoSizeChanged(width, height);
                if (unappliedRotationDegrees > 0) {
                    mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_VIDEO_ROTATION_CHANGED, unappliedRotationDegrees);
                }
            }
        }
    
        @Override
        public void onRenderedFirstFrame() {
            if (mPlayerEventListener != null && mIsPreparing) {
                mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_VIDEO_RENDERING_START, 0);
                mIsPreparing = false;
            }
        }
    }

06.如何創建不同內核播放器

  • 先來看一下創建不同內核播放器的代碼,只需要開發者傳入一個類型參數,即可創建不同類的實例對象。代碼如下所示

    /**
     * 獲取PlayerFactory具體實現類,獲取內核
     * 創建對象的時候只需要傳遞類型type,而不需要對應的工廠,即可創建具體的產品對象
     * TYPE_IJK                 IjkPlayer,基於IjkPlayer封裝播放器
     * TYPE_NATIVE              MediaPlayer,基於原生自帶的播放器控件
     * TYPE_EXO                 基於谷歌視頻播放器
     * TYPE_RTC                 基於RTC視頻播放器
     * @param type                              類型
     * @return
     */
    public static AbstractVideoPlayer getVideoPlayer(Context context,@PlayerConstant.PlayerType int type){
        if (type == PlayerConstant.PlayerType.TYPE_EXO){
            return ExoPlayerFactory.create().createPlayer(context);
        } else if (type == PlayerConstant.PlayerType.TYPE_IJK){
            return IjkPlayerFactory.create().createPlayer(context);
        } else if (type == PlayerConstant.PlayerType.TYPE_NATIVE){
            return MediaPlayerFactory.create().createPlayer(context);
        } else if (type == PlayerConstant.PlayerType.TYPE_RTC){
            return IjkPlayerFactory.create().createPlayer(context);
        } else {
            return IjkPlayerFactory.create().createPlayer(context);
        }
    }
  • 使用工廠模式創建不同對象的動機是什麼,為何要這樣使用?

    • 一個視頻播放器可以提供多個內核Player(如ijk、exo、media,rtc等等), 這些player都源自同一個基類,不過在繼承基類後不同的子類修改了部分屬性從而使得它們可以呈現不同的外觀。
    • 如果希望在使用這些內核player時,不需要知道這些具體內核的名字,只需要知道表示該內核類的一個參數,並提供一個調用方便的方法,把該參數傳入方法即可返回一個相應的內核對象,此時,就可以使用工廠模式。
  • 首先定義一個工廠抽象類,然後不同的內核播放器分別創建其具體的工廠實現具體類

    • PlayerFactory:抽象工廠,擔任這個角色的是工廠方法模式的核心,任何在模式中創建對象的工廠類必須實現這個接口
    • ExoPlayerFactory:具體工廠,具體工廠角色含有與業務密切相關的邏輯,並且受到使用者的調用以創建具體產品對象。
  • 如何使用,分為三步,具體操作如下所示

    • 1.先調用具體工廠對象中的方法createPlayer方法;2.根據傳入產品類型參數獲得具體的產品對象;3.返回產品對象並使用。
    • 簡而言之,創建對象的時候只需要傳遞類型type,而不需要對應的工廠,即可創建具體的產品對象

07.看一下工廠類實現代碼

  • 抽象工廠類,代碼如下所示

    public abstract class PlayerFactory<T extends AbstractVideoPlayer> {
        public abstract T createPlayer(Context context);
    }
  • 具體實現工廠類,代碼如下所示

    public class ExoPlayerFactory extends PlayerFactory<ExoMediaPlayer> {
    
        public static ExoPlayerFactory create() {
            return new ExoPlayerFactory();
        }
    
        @Override
        public ExoMediaPlayer createPlayer(Context context) {
            return new ExoMediaPlayer(context);
        }
    }
  • 這種創建對象最大優點

    • 工廠方法用來創建所需要的產品,同時隱藏了哪種具體產品類將被實例化這一細節,用戶只需要關心所需產品對應的工廠,無須關心創建細節,甚至無須知道具體產品類的類名。
    • 加入新的產品時,比如後期新加一個阿里播放器內核,這個時候就只需要添加一個具體工廠和具體產品就可以。系統的可擴展性也就變得非常好,完全符合“開閉原則”

08.後期如何添加新的內核

  • 比如後期想要添加一個騰訊視頻內核的播放器。代碼如下所示,這個是簡化的

    public class TxPlayerFactory extends PlayerFactory<TxMediaPlayer> {
    
        public static TxPlayerFactory create() {
            return new TxPlayerFactory();
        }
    
        @Override
        public TxMediaPlayer createPlayer(Context context) {
            return new TxMediaPlayer(context);
        }
    }
    
    public class TxMediaPlayer extends AbstractVideoPlayer {
        //省略接口的實現方法代碼
    }

Leave a Reply

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