杨充 vor 4 Jahren
Ursprung
Commit
9783309087

+ 28 - 2
README.md

@@ -8,7 +8,7 @@
 - 06.播放器封装思路
 - 07.播放器示例展示图
 - 08.添加自定义视图
-- 09.视频优化处理
+- 09.视频播放器优化处理
 - 10.播放器问题记录说明
 - 11.性能优化和库大小
 - 12.视频缓存原理介绍
@@ -220,6 +220,10 @@
 
 #### 6.4 视频内核lib库介绍
 ![image](https://img-blog.csdnimg.cn/2020101309293329.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NzAwMjc1,size_16,color_FFFFFF,t_70#pic_center)
+![image](https://img-blog.csdnimg.cn/2020101321464162.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NzAwMjc1,size_16,color_FFFFFF,t_70#pic_center)
+
+
+
 
 #### 6.5视频播放器UI库介绍
 ![image](https://img-blog.csdnimg.cn/20201013094115174.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NzAwMjc1,size_16,color_FFFFFF,t_70#pic_center)
@@ -307,7 +311,29 @@
     ```
 
 
-### 09.视频优化处理
+### 09.视频播放器优化处理
+#### 9.1 如何兼容不同内核播放器
+- 提问:针对不同内核播放器,比如谷歌的ExoPlayer,B站的IjkPlayer,还有原生的MediaPlayer,有些api不一样,那使用的时候如何统一api呢?
+    - 比如说,ijk和exo的视频播放listener监听api就完全不同,这个时候需要做兼容处理
+    - 定义接口,然后各个不同内核播放器实现接口,重写抽象方法。调用的时候,获取接口对象调用api,这样就可以统一Api
+- 定义一个接口,这个接口有什么呢?这个接口定义通用视频播放器方法,比如常见的有:视频初始化,设置url,加载,以及播放状态,简单来说可以分为三个部分。
+    - 第一部分:视频初始化实例对象方法,主要包括:initPlayer初始化视频,setDataSource设置视频播放器地址,setSurface设置视频播放器渲染view,prepareAsync开始准备播放操作
+    - 第二部分:视频播放器状态方法,主要包括:播放,暂停,恢复,重制,设置进度,释放资源,获取进度,设置速度,设置音量
+    - 第三部分:player绑定view后,需要监听播放状态,比如播放异常,播放完成,播放准备,播放size变化,还有播放准备
+- 首先定义一个工厂抽象类,然后不同的内核播放器分别创建其具体的工厂实现具体类
+    - PlayerFactory:抽象工厂,担任这个角色的是工厂方法模式的核心,任何在模式中创建对象的工厂类必须实现这个接口
+    - ExoPlayerFactory:具体工厂,具体工厂角色含有与业务密切相关的逻辑,并且受到使用者的调用以创建具体产品对象。
+- 如何使用,分为三步,具体操作如下所示
+    - 1.先调用具体工厂对象中的方法createPlayer方法;2.根据传入产品类型参数获得具体的产品对象;3.返回产品对象并使用。
+    - 简而言之,创建对象的时候只需要传递类型type,而不需要对应的工厂,即可创建具体的产品对象
+- 这种创建对象最大优点
+    - 工厂方法用来创建所需要的产品,同时隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
+    - 加入新的产品时,比如后期新加一个阿里播放器内核,这个时候就只需要添加一个具体工厂和具体产品就可以。系统的可扩展性也就变得非常好,完全符合“开闭原则”
+
+
+
+
+#### 9.4 代码方面优化措施
 - **如果是在Activity中的话,建议设置下面这段代码**
     ```
     @Override

+ 6 - 0
VideoKernel/src/main/java/com/yc/kernel/impl/exo/ExoMediaPlayer.java

@@ -383,16 +383,22 @@ public class ExoMediaPlayer extends AbstractVideoPlayer implements VideoListener
         }
         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;

+ 6 - 1
VideoKernel/src/main/java/com/yc/kernel/inter/AbstractVideoPlayer.java

@@ -38,6 +38,7 @@ public abstract class AbstractVideoPlayer {
      */
     protected VideoPlayerListener mPlayerEventListener;
 
+    /*----------------------------第一部分:视频初始化实例对象方法----------------------------------*/
     /**
      * 初始化播放器实例
      * 视频播放器第一步:创建视频播放器
@@ -70,6 +71,8 @@ public abstract class AbstractVideoPlayer {
      */
     public abstract void prepareAsync();
 
+    /*----------------------------第二部分:视频播放器状态方法----------------------------------*/
+
     /**
      * 播放
      */
@@ -166,8 +169,10 @@ public abstract class AbstractVideoPlayer {
      */
     public abstract long getTcpSpeed();
 
+    /*----------------------------第三部分:player绑定view后,需要监听播放状态--------------------*/
+
     /**
-     * 绑定VideoView
+     * 绑定VideoView,监听播放异常,完成,开始准备,视频size变化,视频信息等操作
      */
     public void setPlayerEventListener(VideoPlayerListener playerEventListener) {
         this.mPlayerEventListener = playerEventListener;

+ 28 - 0
VideoKernel/src/main/java/com/yc/kernel/utils/PlayerFactoryUtils.java

@@ -1,9 +1,12 @@
 package com.yc.kernel.utils;
 
+import android.content.Context;
+
 import com.yc.kernel.factory.PlayerFactory;
 import com.yc.kernel.impl.exo.ExoPlayerFactory;
 import com.yc.kernel.impl.ijk.IjkPlayerFactory;
 import com.yc.kernel.impl.media.MediaPlayerFactory;
+import com.yc.kernel.inter.AbstractVideoPlayer;
 
 /**
  * <pre>
@@ -39,4 +42,29 @@ public final class PlayerFactoryUtils {
         }
     }
 
+    /**
+     * 获取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);
+        }
+    }
+
+
 }

+ 6 - 1
read/01.视频播放器介绍文档.md

@@ -137,7 +137,12 @@
 #### 4.1 关于gradle引用说明
 - 如下所示
     ```
-    
+    //视频UI层,必须要有
+    implementation 'cn.yc:VideoPlayer:3.0.1'
+    //视频缓存,如果不需要则可以不依赖
+    implementation 'cn.yc:VideoCache:3.0.0'
+    //视频内核层,必须有
+    implementation 'cn.yc:VideoKernel:3.0.1'
     ```
 
 #### 4.2 在xml中添加布局

+ 461 - 62
read/05.播放器内核切换封装.md

@@ -2,92 +2,491 @@
 #### 目录介绍
 - 01.视频播放器内核封装需求
 - 02.播放器内核架构图
-- 06.介绍一下简单工厂模式
+- 03.如何兼容不同内核播放器
+- 04.看一下ijk的内核实现类
+- 05.如何创建不同内核播放器
+- 06.看一下工厂类实现代码
 
 
 
 ### 01.视频播放器内核封装需求
 
 
+
 ### 02.播放器内核架构图
 ![image](https://img-blog.csdnimg.cn/2020101321464162.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NzAwMjc1,size_16,color_FFFFFF,t_70#pic_center)
 
 
 
-### 06.介绍一下简单工厂模式
-- 首先,我们来看,什么是简单工厂模式。
-    - 通过一个例子来解释一下。在下面这段代码中,我们根据配置文件的后缀(json、xml、yaml、properties),选择不同的解析器(JsonRuleConfigParser、XmlRuleConfigParser……),将存储在文件中的配置解析成内存对象 RuleConfig。
+### 03.如何兼容不同内核播放器
+- 提问:针对不同内核播放器,比如谷歌的ExoPlayer,B站的IjkPlayer,还有原生的MediaPlayer,有些api不一样,那使用的时候如何统一api呢?
+    - 比如说,ijk和exo的视频播放listener监听api就完全不同,这个时候需要做兼容处理
+    - 定义接口,然后各个不同内核播放器实现接口,重写抽象方法。调用的时候,获取接口对象调用api,这样就可以统一Api
+- 定义一个接口,这个接口有什么呢?这个接口定义通用视频播放器方法,比如常见的有:视频初始化,设置url,加载,以及播放状态,简单来说可以分为三个部分。
+    - 第一部分:视频初始化实例对象方法,主要包括:initPlayer初始化视频,setDataSource设置视频播放器地址,setSurface设置视频播放器渲染view,prepareAsync开始准备播放操作
+    - 第二部分:视频播放器状态方法,主要包括:播放,暂停,恢复,重制,设置进度,释放资源,获取进度,设置速度,设置音量
+    - 第三部分:player绑定view后,需要监听播放状态,比如播放异常,播放完成,播放准备,播放size变化,还有播放准备
+
+
+
+### 04.看一下ijk的内核实现类
+- 代码如下所示
     ```java
-    public class RuleConfigSource {
-      public RuleConfig load(String ruleConfigFilePath) {
-        String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
-        IRuleConfigParser parser = null;
-        if ("json".equalsIgnoreCase(ruleConfigFileExtension)) {
-          parser = new JsonRuleConfigParser();
-        } else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) {
-          parser = new XmlRuleConfigParser();
-        } else if ("yaml".equalsIgnoreCase(ruleConfigFileExtension)) {
-          parser = new YamlRuleConfigParser();
-        } else if ("properties".equalsIgnoreCase(ruleConfigFileExtension)) {
-          parser = new PropertiesRuleConfigParser();
-        } else {
-          throw new InvalidRuleConfigException(
-                 "Rule config file format is not supported: " + ruleConfigFilePath);
+    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;
         }
     
-        String configText = "";
-        //从ruleConfigFilePath文件中读取配置文本到configText中
-        RuleConfig ruleConfig = parser.parse(configText);
-        return ruleConfig;
-      }
+        /**
+         * 设置渲染视频的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) {
+    
+            }
+        };
     
-      private String getFileExtension(String filePath) {
-        //...解析文件名获取扩展名,比如rule.json,返回json
-        return "json";
-      }
+        /**
+         * 设置视频seek完成监听事件
+         */
+        private IMediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new IMediaPlayer.OnSeekCompleteListener() {
+            @Override
+            public void onSeekComplete(IMediaPlayer iMediaPlayer) {
+    
+            }
+        };
     }
     ```
-- 为了让代码逻辑更加清晰,可读性更好,要善于将功能独立的代码块封装成函数。按照这个设计思路,我们可以将代码中涉及 parser 创建的部分逻辑剥离出来,抽象成 createParser() 函数。重构之后的代码如下所示:
-    - 如果我们非得要将 if 分支逻辑去掉,那该怎么办呢?比较经典处理方法就是利用多态。按照多态的实现思路,对上面的代码进行重构。重构之后的代码如下所示:
+
+
+### 05.如何创建不同内核播放器
+- 先来看一下创建不同内核播放器的代码,只需要开发者传入一个类型参数,即可创建不同类的实例对象。代码如下所示
     ```java
-    public interface IRuleConfigParserFactory {
-      IRuleConfigParser createParser();
-    }
-    
-    public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory {
-      @Override
-      public IRuleConfigParser createParser() {
-        return new JsonRuleConfigParser();
-      }
+    /**
+     * 获取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);
+        }
     }
-    
-    public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory {
-      @Override
-      public IRuleConfigParser createParser() {
-        return new XmlRuleConfigParser();
-      }
+    ```
+- 使用工厂模式创建不同对象的动机是什么,为何要这样使用?
+    - 一个视频播放器可以提供多个内核Player(如ijk、exo、media,rtc等等), 这些player都源自同一个基类,不过在继承基类后不同的子类修改了部分属性从而使得它们可以呈现不同的外观。
+    - 如果希望在使用这些内核player时,不需要知道这些具体内核的名字,只需要知道表示该内核类的一个参数,并提供一个调用方便的方法,把该参数传入方法即可返回一个相应的内核对象,此时,就可以使用工厂模式。
+- 首先定义一个工厂抽象类,然后不同的内核播放器分别创建其具体的工厂实现具体类
+    - PlayerFactory:抽象工厂,担任这个角色的是工厂方法模式的核心,任何在模式中创建对象的工厂类必须实现这个接口
+    - ExoPlayerFactory:具体工厂,具体工厂角色含有与业务密切相关的逻辑,并且受到使用者的调用以创建具体产品对象。
+- 如何使用,分为三步,具体操作如下所示
+    - 1.先调用具体工厂对象中的方法createPlayer方法;2.根据传入产品类型参数获得具体的产品对象;3.返回产品对象并使用。
+    - 简而言之,创建对象的时候只需要传递类型type,而不需要对应的工厂,即可创建具体的产品对象
+
+
+### 06.看一下工厂类实现代码
+- 抽象工厂类,代码如下所示
+    ```java
+    public abstract class PlayerFactory<T extends AbstractVideoPlayer> {
+        public abstract T createPlayer(Context context);
     }
+    ```
+- 具体实现工厂类,代码如下所示
+    ```java
+    public class ExoPlayerFactory extends PlayerFactory<ExoMediaPlayer> {
     
-    public class YamlRuleConfigParserFactory implements IRuleConfigParserFactory {
-      @Override
-      public IRuleConfigParser createParser() {
-        return new YamlRuleConfigParser();
-      }
-    }
+        public static ExoPlayerFactory create() {
+            return new ExoPlayerFactory();
+        }
     
-    public class PropertiesRuleConfigParserFactory implements IRuleConfigParserFactory {
-      @Override
-      public IRuleConfigParser createParser() {
-        return new PropertiesRuleConfigParser();
-      }
+        @Override
+        public ExoMediaPlayer createPlayer(Context context) {
+            return new ExoMediaPlayer(context);
+        }
     }
     ```
-
-
-
-
-
-
+- 这种创建对象最大优点
+    - 工厂方法用来创建所需要的产品,同时隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
+    - 加入新的产品时,比如后期新加一个阿里播放器内核,这个时候就只需要添加一个具体工厂和具体产品就可以。系统的可扩展性也就变得非常好,完全符合“开闭原则”
 
 
 

+ 18 - 0
read/16.视频播放器列表播放.md

@@ -0,0 +1,18 @@
+# 16.视频播放器列表播放
+#### 目录介绍
+
+
+
+
+
+
+
+- 问题:
+list中是一个item,一个视频播放器,这样有何不好?如何与加载?
+list中是所有item公用一个视频播放器,该如何处理?如何处理预加载?
+
+
+
+
+
+