Browse Source

更新demo案例

杨充 4 years ago
parent
commit
cfd5e319a7
29 changed files with 1271 additions and 449 deletions
  1. 1 0
      Demo/src/main/java/org/yczbj/ycvideoplayer/BaseApplication.java
  2. 48 6
      Demo/src/main/java/org/yczbj/ycvideoplayer/BuriedPointEventImpl.java
  3. 33 0
      Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/activity/NormalActivity.java
  4. 2 1
      Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/ad/AdActivity.java
  5. 2 1
      Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/danmu/DanmuActivity.java
  6. 2 1
      Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/list/ListVideoActivity.java
  7. 2 1
      Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/list/RecyclerView2Fragment.java
  8. 3 1
      Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/list/RecyclerViewAutoPlayFragment.java
  9. 12 3
      Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/list/RecyclerViewFragment.java
  10. 2 1
      Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/tiktok/TikTok1Activity.java
  11. 2 1
      Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/tiny/TinyScreenActivity.java
  12. 110 2
      README.md
  13. 1 0
      VideoPlayer/src/main/AndroidManifest.xml
  14. 7 0
      VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/config/BuriedPointEvent.java
  15. 1 1
      VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/config/ConstantKeys.java
  16. 3 0
      VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/controller/BaseVideoController.java
  17. 48 0
      VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/player/SimpleStateListener.java
  18. 0 10
      VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/player/VideoPlayer.java
  19. 3 1
      VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/tool/PlayerUtils.java
  20. 42 2
      VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/ui/pip/CustomFloatView.java
  21. 10 0
      VideoPlayer/src/main/res/layout/custom_video_player_float.xml
  22. BIN
      image/11602825926_.pic.jpg
  23. BIN
      image/21602825931_.pic.jpg
  24. 18 3
      read/01.视频播放器介绍文档.md
  25. 115 1
      read/03.视频播放器Api说明.md
  26. 87 17
      read/04.视频播放器封装思路.md
  27. 14 394
      read/05.播放器内核切换封装.md
  28. 609 1
      read/06.播放器UI抽取封装.md
  29. 94 1
      read/29.视频播放器埋点监听.md

+ 1 - 0
Demo/src/main/java/org/yczbj/ycvideoplayer/BaseApplication.java

@@ -58,6 +58,7 @@ public class BaseApplication extends Application {
         //播放器配置,注意:此为全局配置,按需开启
         PlayerFactory player = PlayerFactoryUtils.getPlayer(PlayerConstant.PlayerType.TYPE_IJK);
         VideoViewManager.setConfig(VideoPlayerConfig.newBuilder()
+                //设置上下文
                 .setContext(this)
                 //设置视频全局埋点事件
                 .setBuriedPointEvent(new BuriedPointEventImpl())

+ 48 - 6
Demo/src/main/java/org/yczbj/ycvideoplayer/BuriedPointEventImpl.java

@@ -1,40 +1,82 @@
 package org.yczbj.ycvideoplayer;
 
+import com.yc.kernel.utils.VideoLogUtils;
+
 import org.yczbj.ycvideoplayerlib.config.BuriedPointEvent;
 
 public class BuriedPointEventImpl implements BuriedPointEvent {
+
+    /**
+     * 进入视频播放
+     * @param url                       视频url
+     */
     @Override
     public void playerIn(String url) {
-
+        VideoLogUtils.i("BuriedPointEvent---进入视频播放--"+url);
     }
 
+    /**
+     * 退出视频播放
+     * @param url                       视频url
+     */
     @Override
     public void playerDestroy(String url) {
-
+        VideoLogUtils.i("BuriedPointEvent---退出视频播放--"+url);
     }
 
+    /**
+     * 视频播放完成
+     * @param url                       视频url
+     */
     @Override
     public void playerCompletion(String url) {
-
+        VideoLogUtils.i("BuriedPointEvent---视频播放完成--"+url);
     }
 
+    /**
+     * 视频播放异常
+     * @param url                       视频url
+     * @param isNetError                是否是网络异常
+     */
     @Override
     public void onError(String url, boolean isNetError) {
-
+        VideoLogUtils.i("BuriedPointEvent---视频播放异常--"+url);
     }
 
+    /**
+     * 点击了视频广告
+     * @param url                       视频url
+     */
     @Override
     public void clickAd(String url) {
+        VideoLogUtils.i("BuriedPointEvent---点击了视频广告--"+url);
+    }
 
+    /**
+     * 视频试看点击
+     * @param url                       视频url
+     */
+    @Override
+    public void playerAndProved(String url) {
+        VideoLogUtils.i("BuriedPointEvent---视频试看点击--"+url);
     }
 
+    /**
+     * 退出视频播放时候的播放进度百度比
+     * @param url                       视频url
+     * @param progress                  视频进度,计算百分比【退出时候进度 / 总进度】
+     */
     @Override
     public void playerOutProgress(String url, float progress) {
-
+        VideoLogUtils.i("BuriedPointEvent---退出视频播放时候的播放进度百度比--"+url+"-----"+progress);
     }
 
+    /**
+     * 视频切换音频
+     * @param url                       视频url
+     */
     @Override
     public void videoToMedia(String url) {
-
+        VideoLogUtils.i("BuriedPointEvent---视频切换音频--"+url);
     }
 }

+ 33 - 0
Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/activity/NormalActivity.java

@@ -11,13 +11,19 @@ import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
 
 import com.bumptech.glide.Glide;
+import com.yc.kernel.factory.PlayerFactory;
+import com.yc.kernel.utils.PlayerConstant;
+import com.yc.kernel.utils.PlayerFactoryUtils;
 
+import org.yczbj.ycvideoplayer.BuriedPointEventImpl;
 import org.yczbj.ycvideoplayer.ConstantVideo;
 import org.yczbj.ycvideoplayer.R;
 import org.yczbj.ycvideoplayerlib.config.ConstantKeys;
+import org.yczbj.ycvideoplayerlib.config.VideoPlayerConfig;
 import org.yczbj.ycvideoplayerlib.player.OnVideoStateListener;
 import org.yczbj.ycvideoplayerlib.player.VideoPlayer;
 import org.yczbj.ycvideoplayerlib.player.VideoPlayerBuilder;
+import org.yczbj.ycvideoplayerlib.player.VideoViewManager;
 import org.yczbj.ycvideoplayerlib.ui.view.BasisVideoController;
 import org.yczbj.ycvideoplayerlib.ui.view.CustomErrorView;
 
@@ -331,6 +337,33 @@ public class NormalActivity extends AppCompatActivity implements View.OnClickLis
         controller.setDismissTimeout(8);
         //销毁
         controller.destroy();
+
+
+
+        //播放器配置,注意:此为全局配置,按需开启
+        PlayerFactory player = PlayerFactoryUtils.getPlayer(PlayerConstant.PlayerType.TYPE_IJK);
+        VideoViewManager.setConfig(VideoPlayerConfig.newBuilder()
+                //设置上下文
+                .setContext(this)
+                //设置视频全局埋点事件
+                .setBuriedPointEvent(new BuriedPointEventImpl())
+                //调试的时候请打开日志,方便排错
+                .setLogEnabled(true)
+                //设置ijk
+                .setPlayerFactory(player)
+                //在移动环境下调用start()后是否继续播放,默认不继续播放
+                .setPlayOnMobileNetwork(false)
+                //是否开启AudioFocus监听, 默认开启
+                .setEnableAudioFocus(true)
+                //是否适配刘海屏,默认适配
+                .setAdaptCutout(true)
+                //监听设备方向来切换全屏/半屏, 默认不开启
+                .setEnableOrientation(false)
+                //设置自定义渲染view,自定义RenderView
+                //.setRenderViewFactory(null)
+                //创建
+                .build());
+
     }
 
 }

+ 2 - 1
Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/ad/AdActivity.java

@@ -14,6 +14,7 @@ import org.yczbj.ycvideoplayer.ConstantVideo;
 import org.yczbj.ycvideoplayer.R;
 import com.yc.videocache.cache.ProxyVideoCacheManager;
 import org.yczbj.ycvideoplayerlib.config.ConstantKeys;
+import org.yczbj.ycvideoplayerlib.player.SimpleStateListener;
 import org.yczbj.ycvideoplayerlib.player.VideoPlayer;
 import org.yczbj.ycvideoplayerlib.tool.BaseToast;
 import org.yczbj.ycvideoplayerlib.ui.view.BasisVideoController;
@@ -107,7 +108,7 @@ public class AdActivity extends AppCompatActivity implements View.OnClickListene
         mVideoPlayer.setUrl(proxyUrl);
         mVideoPlayer.start();
         //监听播放结束
-        mVideoPlayer.addOnStateChangeListener(new VideoPlayer.SimpleOnStateChangeListener() {
+        mVideoPlayer.addOnStateChangeListener(new SimpleStateListener() {
             @Override
             public void onPlayStateChanged(int playState) {
                 if (playState == ConstantKeys.CurrentState.STATE_BUFFERING_PLAYING) {

+ 2 - 1
Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/danmu/DanmuActivity.java

@@ -11,6 +11,7 @@ import org.yczbj.ycvideoplayer.BaseActivity;
 import org.yczbj.ycvideoplayer.ConstantVideo;
 import org.yczbj.ycvideoplayer.R;
 import org.yczbj.ycvideoplayerlib.config.ConstantKeys;
+import org.yczbj.ycvideoplayerlib.player.SimpleStateListener;
 import org.yczbj.ycvideoplayerlib.player.VideoPlayer;
 import org.yczbj.ycvideoplayerlib.ui.view.BasisVideoController;
 
@@ -86,7 +87,7 @@ public class DanmuActivity extends BaseActivity implements View.OnClickListener
         mVideoPlayer.setUrl(ConstantVideo.VideoPlayerList[0]);
         mVideoPlayer.setScreenScaleType(ConstantKeys.PlayerScreenScaleType.SCREEN_SCALE_16_9);
         mVideoPlayer.start();
-        mVideoPlayer.addOnStateChangeListener(new VideoPlayer.SimpleOnStateChangeListener() {
+        mVideoPlayer.addOnStateChangeListener(new SimpleStateListener() {
             @Override
             public void onPlayStateChanged(int playState) {
                 if (playState == ConstantKeys.CurrentState.STATE_PREPARED) {

+ 2 - 1
Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/list/ListVideoActivity.java

@@ -13,6 +13,7 @@ import org.yczbj.ycvideoplayer.ConstantVideo;
 import org.yczbj.ycvideoplayer.R;
 import org.yczbj.ycvideoplayerlib.config.ConstantKeys;
 import org.yczbj.ycvideoplayerlib.config.VideoInfoBean;
+import org.yczbj.ycvideoplayerlib.player.SimpleStateListener;
 import org.yczbj.ycvideoplayerlib.player.VideoPlayer;
 import org.yczbj.ycvideoplayerlib.ui.view.BasisVideoController;
 
@@ -88,7 +89,7 @@ public class ListVideoActivity extends AppCompatActivity implements View.OnClick
         mVideoPlayer.start();
 
         //监听播放结束
-        mVideoPlayer.addOnStateChangeListener(new VideoPlayer.SimpleOnStateChangeListener() {
+        mVideoPlayer.addOnStateChangeListener(new SimpleStateListener() {
             private int mCurrentVideoPosition;
             @Override
             public void onPlayStateChanged(int playState) {

+ 2 - 1
Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/list/RecyclerView2Fragment.java

@@ -21,6 +21,7 @@ import org.yczbj.ycvideoplayer.ConstantVideo;
 import org.yczbj.ycvideoplayer.R;
 import org.yczbj.ycvideoplayerlib.config.ConstantKeys;
 import org.yczbj.ycvideoplayerlib.config.VideoInfoBean;
+import org.yczbj.ycvideoplayerlib.player.SimpleStateListener;
 import org.yczbj.ycvideoplayerlib.player.VideoPlayer;
 import org.yczbj.ycvideoplayerlib.player.VideoViewManager;
 import org.yczbj.ycvideoplayerlib.tool.PlayerUtils;
@@ -104,7 +105,7 @@ public class RecyclerView2Fragment extends Fragment {
 
     protected void initVideoView() {
         mVideoView = new VideoPlayer(context);
-        mVideoView.setOnStateChangeListener(new VideoPlayer.SimpleOnStateChangeListener() {
+        mVideoView.setOnStateChangeListener(new SimpleStateListener() {
             @Override
             public void onPlayStateChanged(int playState) {
                 //监听VideoViewManager释放,重置状态

+ 3 - 1
Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/list/RecyclerViewAutoPlayFragment.java

@@ -22,7 +22,8 @@ public class RecyclerViewAutoPlayFragment extends RecyclerViewFragment {
             @Override
             public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                 super.onScrollStateChanged(recyclerView, newState);
-                if (newState == SCROLL_STATE_IDLE) { //滚动停止
+                if (newState == SCROLL_STATE_IDLE) {
+                    //滚动停止
                     autoPlayVideo(recyclerView);
                 }
             }
@@ -37,6 +38,7 @@ public class RecyclerViewAutoPlayFragment extends RecyclerViewFragment {
                     VideoRecyclerViewAdapter.VideoHolder holder = (VideoRecyclerViewAdapter.VideoHolder) itemView.getTag();
                     Rect rect = new Rect();
                     holder.mPlayerContainer.getLocalVisibleRect(rect);
+                    //获取item的高度
                     int height = holder.mPlayerContainer.getHeight();
                     if (rect.top == 0 && rect.bottom == height) {
                         startPlay(holder.mPosition);

+ 12 - 3
Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/list/RecyclerViewFragment.java

@@ -14,10 +14,13 @@ import androidx.fragment.app.Fragment;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.yc.kernel.utils.VideoLogUtils;
+
 import org.yczbj.ycvideoplayer.ConstantVideo;
 import org.yczbj.ycvideoplayer.R;
 import org.yczbj.ycvideoplayerlib.config.ConstantKeys;
 import org.yczbj.ycvideoplayerlib.config.VideoInfoBean;
+import org.yczbj.ycvideoplayerlib.player.SimpleStateListener;
 import org.yczbj.ycvideoplayerlib.player.VideoViewManager;
 import org.yczbj.ycvideoplayerlib.player.VideoPlayer;
 import org.yczbj.ycvideoplayerlib.tool.PlayerUtils;
@@ -83,11 +86,17 @@ public class RecyclerViewFragment extends Fragment {
         mRecyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
             @Override
             public void onChildViewAttachedToWindow(@NonNull View view) {
-
+                VideoLogUtils.i("addOnChildAttachStateChangeListener-----AttachedToWindow---"+view);
             }
 
+            /**
+             * 当适配器创建的view(即列表项view)被窗口分离(即滑动离开了当前窗口界面)就会被调用
+             *
+             * @param view
+             */
             @Override
             public void onChildViewDetachedFromWindow(@NonNull View view) {
+                VideoLogUtils.i("addOnChildAttachStateChangeListener-----DetachedFromWindow---"+view);
                 FrameLayout playerContainer = view.findViewById(R.id.player_container);
                 View v = playerContainer.getChildAt(0);
                 if (v != null && v == mVideoView && !mVideoView.isFullScreen()) {
@@ -99,7 +108,7 @@ public class RecyclerViewFragment extends Fragment {
 
     protected void initVideoView() {
         mVideoView = new VideoPlayer(context);
-        mVideoView.setOnStateChangeListener(new VideoPlayer.SimpleOnStateChangeListener() {
+        mVideoView.setOnStateChangeListener(new SimpleStateListener() {
             @Override
             public void onPlayStateChanged(int playState) {
                 //监听VideoViewManager释放,重置状态
@@ -176,10 +185,10 @@ public class RecyclerViewFragment extends Fragment {
     }
 
     private void releaseVideoView() {
-        mVideoView.release();
         if (mVideoView.isFullScreen()) {
             mVideoView.stopFullScreen();
         }
+        mVideoView.release();
         if(getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
             getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
         }

+ 2 - 1
Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/tiktok/TikTok1Activity.java

@@ -29,6 +29,7 @@ import org.yczbj.ycvideoplayer.newPlayer.list.OnItemChildClickListener;
 import org.yczbj.ycvideoplayer.newPlayer.list.VideoRecyclerViewAdapter;
 import org.yczbj.ycvideoplayerlib.config.ConstantKeys;
 import org.yczbj.ycvideoplayerlib.config.VideoInfoBean;
+import org.yczbj.ycvideoplayerlib.player.SimpleStateListener;
 import org.yczbj.ycvideoplayerlib.player.VideoPlayer;
 import org.yczbj.ycvideoplayerlib.player.VideoViewManager;
 import org.yczbj.ycvideoplayerlib.tool.PlayerUtils;
@@ -128,7 +129,7 @@ public class TikTok1Activity extends AppCompatActivity {
 
     protected void initVideoView() {
         mVideoView = new VideoPlayer(this);
-        mVideoView.setOnStateChangeListener(new VideoPlayer.SimpleOnStateChangeListener() {
+        mVideoView.setOnStateChangeListener(new SimpleStateListener() {
             @Override
             public void onPlayStateChanged(int playState) {
                 //监听VideoViewManager释放,重置状态

+ 2 - 1
Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/tiny/TinyScreenActivity.java

@@ -16,6 +16,7 @@ import org.yczbj.ycvideoplayer.newPlayer.list.OnItemChildClickListener;
 import org.yczbj.ycvideoplayer.newPlayer.list.VideoRecyclerViewAdapter;
 import org.yczbj.ycvideoplayerlib.config.ConstantKeys;
 import org.yczbj.ycvideoplayerlib.config.VideoInfoBean;
+import org.yczbj.ycvideoplayerlib.player.SimpleStateListener;
 import org.yczbj.ycvideoplayerlib.player.VideoPlayer;
 import org.yczbj.ycvideoplayerlib.tool.PlayerUtils;
 import org.yczbj.ycvideoplayerlib.ui.view.BasisVideoController;
@@ -76,7 +77,7 @@ public class TinyScreenActivity extends AppCompatActivity implements OnItemChild
 
     protected void initView() {
         mVideoPlayer = new VideoPlayer(this);
-        mVideoPlayer.setOnStateChangeListener(new VideoPlayer.SimpleOnStateChangeListener() {
+        mVideoPlayer.setOnStateChangeListener(new SimpleStateListener() {
             @Override
             public void onPlayStateChanged(int playState) {
                 if (playState == ConstantKeys.CurrentState.STATE_BUFFERING_PLAYING) {

+ 110 - 2
README.md

@@ -57,6 +57,7 @@
 |**布局** | 内核和UI分离,和市面GitHub上大多数播放器不一样,方便定制,通过addView添加 |
 |**播放** | 正常播放,小窗播放,列表播放,仿抖音播放 |
 |**自定义** | 可以自定义添加视频UI层,可以说UI和Player高度分离,支持自定义渲染层SurfaceView |
+|**统一视频埋点** | 暴露用户播放视频开始,退出,异常,播放完成,以及退出视频时进度,点击广告,试看等多个统一埋点 |
 
 
 
@@ -243,6 +244,8 @@
 ![image](https://img-blog.csdnimg.cn/20201013091432616.jpg?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/20201013091432581.jpg?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/20201013091432668.jpg?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/20201016132752350.jpg?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/20201016132752342.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NzAwMjc1,size_16,color_FFFFFF,t_70#pic_center)
 
 
 
@@ -335,6 +338,51 @@
 
 
 
+#### 9.2 播放器UI抽取封装优化
+- 发展中遇到的问题
+    - 播放器可支持多种场景下的播放,多个产品会用到同一个播放器,这样就会带来一个问题,一个播放业务播放器状态发生变化,其他播放业务必须同步更新播放状态,各个播放业务之间互相交叉,随着播放业务的增多,开发和维护成本会急剧增加, 导致后续开发不可持续。 
+- UI难以自定义或者修改麻烦
+    - 比如常见的视频播放器,会把视频各种视图写到xml中,这种方式在后期代码会很大,而且改动一个小的布局,则会影响大。这样到后期往往只敢加代码,而不敢删除代码……
+    - 有时候难以适应新的场景,比如添加一个播放广告,老师开课,或者视频引导业务需求,则需要到播放器中写一堆业务代码。迭代到后期,违背了开闭原则,视频播放器需要做到和业务分离
+- 视频播放器结构需要清晰
+    - 也就是说视频player和ui操作柔和到了一起,尤其是两者之间的交互。比如播放中需要更新UI进度条,播放异常需要显示异常UI,都比较难处理播放器状态变化更新UI操作
+    - 这个是指该视频播放器能否看了文档后快速上手,知道封装的大概流程。方便后期他人修改和维护,因此需要将视频播放器功能分离。比如切换内核+视频播放器(player+controller+view)
+    - 一定要解耦合,播放器player与视频UI解耦:支持添加自定义视频视图,比如支持添加自定义广告,新手引导,或者视频播放异常等视图,这个需要较强的拓展性
+- 适合多种业务场景
+    - 比如适合播放单个视频,多个视频,以及列表视频,或者类似抖音那种一个页面一个视频,还有小窗口播放视频。也就是适合大多数业务场景
+- 方便播放业务发生变化
+    - 播放状态变化是导致不同播放业务场景之间交叉同步,解除播放业务对播放器的直接操控,采用接口监听进行解耦。比如:player+controller+interface
+- 关于视频播放器
+    - 定义一个视频播放器InterVideoPlayer接口,操作视频播放,暂停,缓冲,进度设置,设置播放模式等多种操作。
+    - 然后写一个播放器接口的具体实现类,在这个里面拿到内核播放器player,然后做相关的实现操作。
+- 关于视频视图View
+    - 定义一个视图InterVideoController接口,主要负责视图显示/隐藏,播放进度,锁屏,状态栏等操作。
+    - 然后写一个播放器视图接口的具体实现类,在这里里面inflate视图操作,然后接口方法实现,为了方便后期开发者自定义view,因此需要addView操作,将添加进来的视图用map集合装起来。
+- 播放器player和controller交互
+    - 在player中创建BaseVideoController对象,这个时候需要把controller添加到播放器中,这个时候有两个要点特别重要,需要把播放器状态监听,和播放模式监听传递给控制器
+    - setPlayState设置视频播放器播放逻辑状态,主要是播放缓冲,加载,播放中,暂停,错误,完成,异常,播放进度等多个状态,方便控制器做UI更新操作
+    - setPlayerState设置视频播放切换模式状态,主要是普通模式,小窗口模式,正常模式三种其中一种,方便控制器做UI更新
+- 播放器player和view交互
+    - 这块非常关键,举个例子,视频播放失败需要显示控制层的异常视图View;播放视频初始化需要显示loading,然后更新UI播放进度条等。都是播放器和视图层交互
+    - 可以定义一个类,同时实现InterVideoPlayer接口和InterVideoController接口,这个时候会重新这两个接口所有的方法。此类的目的是为了在InterControlView接口实现类中既能调用VideoPlayer的api又能调用BaseVideoController的api
+- 如何添加自定义播放器视图
+    - 添加了自定义播放器视图,比如添加视频广告,可以选择跳过,选择播放暂停。那这个视图view,肯定是需要操作player或者获取player的状态的。这个时候就需要暴露监听视频播放的状态接口监听
+    - 首先定义一个InterControlView接口,也就是说所有自定义视频视图view需要实现这个接口,该接口中的核心方法有:绑定视图到播放器,视图显示隐藏变化监听,播放状态监听,播放模式监听,进度监听,锁屏监听等
+    - 在BaseVideoController中的状态监听中,通过InterControlView接口对象就可以把播放器的状态传递到子类中
+
+
+#### 9.3 如何全局监控视频埋点
+- 传统一点的做法
+    - 比如用友盟或者百度统计,或者用其他的统计。之前的做法是,在每个有视频的页面比如说Activity,Fragment等开启时视频播放时埋点一次,页面退出时埋点一次。
+    - 如果app中有多个activity或者fragment页面,那么就每个页面都要进行埋点。比如如果你的app是付费视频,你想知道有多少人试看了,该怎么操作。那么你需要在每一个有视频的activity页面挨个添加埋点,那还有没有更好的办法?
+- 解决方案
+    - 举个例子:例如,你需要来让外部开发者手动去埋点,可是在类中怎么埋点又是由其他人来设计的,你只是需要对外暴露监听的方法。那么该如何做呢?采用接口 + 实现类方式即可实现。
+- 该案例中怎么操作
+    - 定义一个接口,规定其他人设计类,必须继承这个接口。在这个接口中,定义进入视频播放,退出视频播放器,记录播放进度,视频播放完成,播放异常,点击广告,点击试看等操作的抽象方法。具体可以看BuriedPointEvent类代码……
+- 外部开发者如何使用
+    - 定义一个类实现该视频埋点接口,重写里面方法。然后需要在初始化配置视频播放器的时候,将这个实现类的对象传递进来即可。通过这个配置类传进来的对象,播放器就可以处理监听设置逻辑呢。
+    - 这种操作最大的好处就是:在这个类中统一处理视频的埋点,修改快捷,而不用在每一个有视频播放器的页面埋点,方便维护。具体可以看代码案例。
+
 
 #### 9.4 代码方面优化措施
 - **如果是在Activity中的话,建议设置下面这段代码**
@@ -378,6 +426,13 @@
 
 
 ### 10.播放器问题记录说明
+- 关于如何调整视频的播放填充类型。在该库中提供了6中不同类型供你选择,即正常默认类型;16:9类型,4:3类型;充满整个控件视图;剧中裁剪类型等类型,就是模仿了图片设置缩放的方式。其实这个就是设置SurfaceView的宽高……
+    - 这里播放正常视频建议选择16:9类型的,缩放后会有留黑;针对类似快手抖音视频,一个页面一个视频建议选择充满整个控件视图,会裁剪但是会铺满视频。
+- 关于前后台切换视频问题
+    - 从前台切到后台,当视频正在播放或者正在缓冲时,调用该方法暂停视频。从后台切换到前台,当视频暂停时或者缓冲暂停时,调用该方法重新开启视频播放。也可以让用户手动去点击播放视频。
+- 播放器在正常播放和全屏模式切换状态栏问题
+    - 待完善,需要处理刘海
+
 
 
 
@@ -385,18 +440,58 @@
 
 
 ### 12.视频缓存原理介绍
+- 网络上比较好的项目:https://github.com/danikula/AndroidVideoCache
+    - 网络用的HttpURLConnection,文件缓存处理,文件最大限度策略,回调监听处理,断点续传,代理服务等。
+- 但是存在一些问题,比如如下所示
+    - 文件的缓存超过限制后没有按照lru算法删除,
+    - 处理返回给播放器的http响应头消息,响应头消息的获取处理改为head请求(需服务器支持)
+    - 替换网络库为okHttp(因为大部分的项目都是以okHttp为网络请求库的),但是这个改动性比较大
+- 然后看一下怎么使用,超级简单。传入视频url链接,返回一个代理链接,然后就可以呢
+    ```
+    HttpProxyCacheServer cacheServer = ProxyVideoCacheManager.getProxy(this);
+    String proxyUrl = cacheServer.getProxyUrl(URL_AD);
+    mVideoPlayer.setUrl(proxyUrl);
+  
+  
+    public static HttpProxyCacheServer getProxy(Context context) {
+        return sharedProxy == null ? (sharedProxy = newProxy(context)) : sharedProxy;
+    }
+
+    private static HttpProxyCacheServer newProxy(Context context) {
+        return new HttpProxyCacheServer.Builder(context)
+                .maxCacheSize(512 * 1024 * 1024)       // 512MB for cache
+                //缓存路径,不设置默认在sd_card/Android/data/[app_package_name]/cache中
+                //.cacheDirectory()
+                .build();
+    }
+    ```
+- 大概的原理
+    - 原始的方式是直接塞播放地址给播放器,它就可以直接播放。现在我们要在中间加一层本地代理,播放器播放的时候(获取数据)是通过我们的本地代理的地址来播放的,这样我们就可以很好的在中间层(本地代理层)做一些处理,比如:文件缓存,预缓存(秒开处理),监控等。
+- 原理详细一点来说
+    - 1.采用了本地代理服务的方式,通过原始url给播放器返回一个本地代理的一个url ,代理URL类似:http://127.0.0.1:port/视频url;(port端口为系统随机分配的有效端口,真实url是为了真正的下载),然后播放器播放的时候请求到了你本地的代理上了。
+    - 2.本地代理采用ServerSocket监听127.0.0.1的有效端口,这个时候手机就是一个服务器了,客户端就是socket,也就是播放器。
+    - 3.读取客户端就是socket来读取数据(http协议请求)解析http协议。
+    - 4.根据url检查视频文件是否存在,读取文件数据给播放器,也就是往socket里写入数据(socket通信)。同时如果没有下载完成会进行断点下载,当然弱网的话数据需要生产消费同步处理。
+- 如何实现预加载
+    - 其实预加载的思路很简单,在进行一个播放视频后,再返回接下来需要预加载的视频url,启用线程去请求下载数据
+    - 开启一个线程去请求并预加载一部分的数据,可能需要预加载的数据大于>1,利用队列先进入的先进行加载,因此可以采用LinkedHashMap保存正在预加载的task。
+    - 在开始预加载的时候,判断该播放地址是否已经预加载,如果不是那么创建一个线程task,并且把它放到map集合中。然后执行预加载逻辑,也就是执行HttpURLConnection请求
+    - 提供取消对应url加载的任务,因为有可能该url不需要再进行预加载了,比如参考抖音,当用户瞬间下滑几个视频,那么很多视频就需要跳过了不需要再进行预加载
+- 具体直接看项目代码:VideoCache缓冲模块
+
 
 
 ### 13.查看视频播放器日志
 - 统一管理视频播放器封装库日志,方便后期排查问题
     - 比如,视频内核,日志过滤则是:aaa
     - 比如,视频player,日志过滤则是:bbb
+    - 比如,缓存模块,日志过滤则是:VideoCache
 
 
 
 ### 14.该库异常code说明
-- 针对视频封装库,统一处理抛出的异常,为了方便开发者快速知道异常的来由,则可以查询约定的code码。这个在sdk中特别常见,因此该库一定程度是借鉴腾讯播放器……
-
+- 针对视频封装库,统一处理抛出的异常,为了方便开发者快速知道异常的来由,则可以查询约定的code码。
+    - 这个在sdk中特别常见,因此该库一定程度是借鉴腾讯播放器……
 
 
 ### 15.该库系列wiki文档
@@ -435,6 +530,19 @@
     - [21.仿抖音一次滑动一个页面播放视频库](https://github.com/yangchong211/YCScrollPager)
 
 
+#### 17.2 感谢参考案例和博客
+- exo播放器
+    - https://github.com/google/ExoPlayer
+- ijk播放器
+    - https://github.com/bilibili/ijkplayer
+- 阿里云播放器
+    - https://help.aliyun.com/document_detail/51992.html?spm=a2c4g.11186623.2.24.37131bc7j1PoVK#topic2415
+- GSY播放器
+    - https://github.com/CarGuo/GSYVideoPlayer
+- 饺子播放器
+    - https://github.com/lipangit/JiaoZiVideoPlayer
+
+
 
 #### 17.2 关于LICENSE
 ```

+ 1 - 0
VideoPlayer/src/main/AndroidManifest.xml

@@ -5,6 +5,7 @@
 
     <!--监听网络状态变化需要的权限-->
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
 
     <application>
         <receiver android:name=".old.other.BatterReceiver"/>

+ 7 - 0
VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/config/BuriedPointEvent.java

@@ -42,6 +42,13 @@ public interface BuriedPointEvent {
      */
     void clickAd(String url);
 
+    /**
+     * 视频试看点击
+     * @param url                       视频url
+     */
+    void playerAndProved(String url);
+
+
     /**
      * 退出视频播放时候的播放进度百度分
      * @param url                       视频url

+ 1 - 1
VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/config/ConstantKeys.java

@@ -81,7 +81,7 @@ public final class ConstantKeys {
     /**
      * 播放状态,主要是指播放器的各种状态
      * -1               播放错误
-     * 0                播放未开始
+     * 0                播放未开始,即将进行
      * 1                播放准备中
      * 2                播放准备就绪
      * 3                正在播放

+ 3 - 0
VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/controller/BaseVideoController.java

@@ -224,6 +224,9 @@ public abstract class BaseVideoController extends FrameLayout implements InterVi
         mControlComponents.clear();
     }
 
+    /**
+     * 移除所有独有的组件
+     */
     public void removeAllPrivateComponents() {
         Iterator<Map.Entry<InterControlView, Boolean>> it = mControlComponents.entrySet().iterator();
         while (it.hasNext()) {

+ 48 - 0
VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/player/SimpleStateListener.java

@@ -0,0 +1,48 @@
+package org.yczbj.ycvideoplayerlib.player;
+
+import org.yczbj.ycvideoplayerlib.config.ConstantKeys;
+
+/**
+ * <pre>
+ *     @author yangchong
+ *     blog  : https://github.com/yangchong211
+ *     time  : 2017/11/21
+ *     desc  : 播放器监听实现类
+ *     revise: 空实现。用的时候只需要重写需要的方法
+ * </pre>
+ */
+public class SimpleStateListener implements OnVideoStateListener {
+
+    /**
+     * 播放模式
+     * 普通模式,小窗口模式,正常模式三种其中一种
+     * MODE_NORMAL              普通模式
+     * MODE_FULL_SCREEN         全屏模式
+     * MODE_TINY_WINDOW         小屏模式
+     * @param playerState                       播放模式
+     */
+    @Override
+    public void onPlayerStateChanged(@ConstantKeys.PlayModeType int playerState) {
+
+    }
+
+    /**
+     * 播放状态
+     * -1               播放错误
+     * 0                播放未开始
+     * 1                播放准备中
+     * 2                播放准备就绪
+     * 3                正在播放
+     * 4                暂停播放
+     * 5                正在缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,缓冲区数据足够后恢复播放)
+     * 6                暂停缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,此时暂停播放器,继续缓冲,缓冲区数据足够后恢复暂停
+     * 7                播放完成
+     * 8                开始播放中止
+     * @param playState                         播放状态,主要是指播放器的各种状态
+     */
+    @Override
+    public void onPlayStateChanged(int playState) {
+
+    }
+
+}

+ 0 - 10
VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/player/VideoPlayer.java

@@ -1023,16 +1023,6 @@ public class VideoPlayer<P extends AbstractVideoPlayer> extends FrameLayout
         }
     }
 
-    /**
-     * OnStateChangeListener的空实现。用的时候只需要重写需要的方法
-     */
-    public static class SimpleOnStateChangeListener implements OnVideoStateListener {
-        @Override
-        public void onPlayerStateChanged(@ConstantKeys.PlayModeType int playerState) {}
-        @Override
-        public void onPlayStateChanged(int playState) {}
-    }
-
     /**
      * 添加一个播放状态监听器,播放状态发生变化时将会调用。
      */

+ 3 - 1
VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/tool/PlayerUtils.java

@@ -522,7 +522,9 @@ public final class PlayerUtils {
      * 将View从父控件中移除
      */
     public static void removeViewFormParent(View v) {
-        if (v == null) return;
+        if (v == null) {
+            return;
+        }
         ViewParent parent = v.getParent();
         if (parent instanceof FrameLayout) {
             ((FrameLayout) parent).removeView(v);

+ 42 - 2
VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/ui/pip/CustomFloatView.java

@@ -16,9 +16,12 @@ limitations under the License.
 package org.yczbj.ycvideoplayerlib.ui.pip;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Build;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
@@ -47,6 +50,8 @@ public class CustomFloatView extends FrameLayout implements InterControlView, Vi
     private ProgressBar mPbLoading;
     private ImageView mIvClose;
     private ImageView mIvSkip;
+    private ProgressBar mPbBottomProgress;
+    private boolean mIsShowBottomProgress = true;
 
     public CustomFloatView(@NonNull Context context) {
         super(context);
@@ -69,6 +74,10 @@ public class CustomFloatView extends FrameLayout implements InterControlView, Vi
                 R.layout.custom_video_player_float, this, true);
         initFindViewById(view);
         initListener();
+        //5.1以下系统SeekBar高度需要设置成WRAP_CONTENT
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
+            mPbBottomProgress.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
+        }
     }
 
     private void initFindViewById(View view) {
@@ -76,7 +85,7 @@ public class CustomFloatView extends FrameLayout implements InterControlView, Vi
         mPbLoading = view.findViewById(R.id.pb_loading);
         mIvClose = view.findViewById(R.id.iv_close);
         mIvSkip = view.findViewById(R.id.iv_skip);
-
+        mPbBottomProgress = view.findViewById(R.id.pb_bottom_progress);
     }
 
     private void initListener() {
@@ -120,12 +129,21 @@ public class CustomFloatView extends FrameLayout implements InterControlView, Vi
             }
             mIvStartPlay.setVisibility(VISIBLE);
             mIvStartPlay.startAnimation(anim);
+            if (mIsShowBottomProgress) {
+                mPbBottomProgress.setVisibility(GONE);
+            }
         } else {
             if (mIvStartPlay.getVisibility() == GONE){
                 return;
             }
             mIvStartPlay.setVisibility(GONE);
             mIvStartPlay.startAnimation(anim);
+            if (mIsShowBottomProgress) {
+                mPbBottomProgress.setVisibility(VISIBLE);
+                AlphaAnimation animation = new AlphaAnimation(0f, 1f);
+                animation.setDuration(300);
+                mPbBottomProgress.startAnimation(animation);
+            }
         }
     }
 
@@ -141,6 +159,15 @@ public class CustomFloatView extends FrameLayout implements InterControlView, Vi
                 mIvStartPlay.setSelected(true);
                 mIvStartPlay.setVisibility(GONE);
                 mPbLoading.setVisibility(GONE);
+                if (mIsShowBottomProgress) {
+                    if (mControlWrapper.isShowing()) {
+                        mPbBottomProgress.setVisibility(GONE);
+                    } else {
+                        mPbBottomProgress.setVisibility(VISIBLE);
+                    }
+                }
+                //开始刷新进度
+                mControlWrapper.startProgress();
                 break;
             case ConstantKeys.CurrentState.STATE_PAUSED:
                 mIvStartPlay.setSelected(false);
@@ -171,6 +198,8 @@ public class CustomFloatView extends FrameLayout implements InterControlView, Vi
                 break;
             case ConstantKeys.CurrentState.STATE_BUFFERING_PLAYING:
                 bringToFront();
+                mPbBottomProgress.setProgress(0);
+                mPbBottomProgress.setSecondaryProgress(0);
                 break;
         }
     }
@@ -182,9 +211,20 @@ public class CustomFloatView extends FrameLayout implements InterControlView, Vi
 
     @Override
     public void setProgress(int duration, int position) {
-
+        if (duration > 0) {
+            int pos = (int) (position * 1.0 / duration * mPbBottomProgress.getMax());
+            mPbBottomProgress.setProgress(pos);
+        }
+        int percent = mControlWrapper.getBufferedPercentage();
+        if (percent >= 95) {
+            //解决缓冲进度不能100%问题
+            mPbBottomProgress.setSecondaryProgress(mPbBottomProgress.getMax());
+        } else {
+            mPbBottomProgress.setSecondaryProgress(percent * 10);
+        }
     }
 
+
     @Override
     public void onLockStateChanged(boolean isLocked) {
 

+ 10 - 0
VideoPlayer/src/main/res/layout/custom_video_player_float.xml

@@ -34,4 +34,14 @@
         android:layout_margin="4dp"
         android:src="@drawable/ic_player_open" />
 
+    <ProgressBar
+        android:id="@+id/pb_bottom_progress"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_width="match_parent"
+        android:layout_height="2dp"
+        android:layout_gravity="bottom"
+        android:max="1000"
+        android:progressDrawable="@drawable/progress_bar_style"
+        android:visibility="visible" />
+
 </FrameLayout>

BIN
image/11602825926_.pic.jpg


BIN
image/21602825931_.pic.jpg


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

@@ -56,13 +56,14 @@
 #### 1.2 该库功能说明
 |**类型** | 功能说明 |
 |--------   |-----        |
-|**项目结构** | VideoCache缓存lib,VideoKernel视频内核lib,VideoPlayer视频UIlib |
+|**项目结构** | VideoCache缓存lib,VideoKernel视频内核lib,VideoPlayer视频UIlib |
 |**内核** | MediaPlayer、ExoPlayer、IjkPlayer,后期接入Rtc和TXPlayer |
 |**协议/格式** | http/https、concat、rtsp、hls、rtmp、file、m3u8、mkv、webm、mp3、mp4等 |
 |**画面** | 调整显示比例:默认、16:9、4:3、填充;播放时旋转画面角度(0,90,180,270);镜像旋转 |
 |**布局** | 内核和UI分离,和市面GitHub上大多数播放器不一样,方便定制,通过addView添加 |
 |**播放** | 正常播放,小窗播放,列表播放,仿抖音播放 |
 |**自定义** | 可以自定义添加视频UI层,可以说UI和Player高度分离,支持自定义渲染层SurfaceView |
+|**统一视频埋点** | 暴露用户播放视频开始,退出,异常,播放完成,以及退出视频时进度,点击广告,试看等多个统一埋点 |
 
 
 
@@ -153,7 +154,7 @@
     //视频UI层,必须要有
     implementation 'cn.yc:VideoPlayer:3.0.1'
     //视频缓存,如果不需要则可以不依赖
-    implementation 'cn.yc:VideoCache:3.0.0'
+    implementation 'cn.yc:VideoCache:3.0.1'
     //视频内核层,必须有
     implementation 'cn.yc:VideoKernel:3.0.1'
     ```
@@ -256,7 +257,8 @@
 ![image](https://img-blog.csdnimg.cn/20201013091432616.jpg?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/20201013091432581.jpg?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/20201013091432668.jpg?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/20201016132752350.jpg?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/20201016132752342.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NzAwMjc1,size_16,color_FFFFFF,t_70#pic_center)
 
 
 ### 08.添加自定义视图
@@ -403,6 +405,19 @@
     - 在BaseVideoController中的状态监听中,通过InterControlView接口对象就可以把播放器的状态传递到子类中
 
 
+#### 9.3 如何全局监控视频埋点
+- 传统一点的做法
+    - 比如用友盟或者百度统计,或者用其他的统计。之前的做法是,在每个有视频的页面比如说Activity,Fragment等开启时视频播放时埋点一次,页面退出时埋点一次。
+    - 如果app中有多个activity或者fragment页面,那么就每个页面都要进行埋点。比如如果你的app是付费视频,你想知道有多少人试看了,该怎么操作。那么你需要在每一个有视频的activity页面挨个添加埋点,那还有没有更好的办法?
+- 解决方案
+    - 举个例子:例如,你需要来让外部开发者手动去埋点,可是在类中怎么埋点又是由其他人来设计的,你只是需要对外暴露监听的方法。那么该如何做呢?采用接口 + 实现类方式即可实现。
+- 该案例中怎么操作
+    - 定义一个接口,规定其他人设计类,必须继承这个接口。在这个接口中,定义进入视频播放,退出视频播放器,记录播放进度,视频播放完成,播放异常,点击广告,点击试看等操作的抽象方法。具体可以看BuriedPointEvent类代码……
+- 外部开发者如何使用
+    - 定义一个类实现该视频埋点接口,重写里面方法。然后需要在初始化配置视频播放器的时候,将这个实现类的对象传递进来即可。通过这个配置类传进来的对象,播放器就可以处理监听设置逻辑呢。
+    - 这种操作最大的好处就是:在这个类中统一处理视频的埋点,修改快捷,而不用在每一个有视频播放器的页面埋点,方便维护。具体可以看代码案例。
+
+
 
 #### 9.4 代码方面优化措施
 - **如果是在Activity中的话,建议设置下面这段代码**

+ 115 - 1
read/03.视频播放器Api说明.md

@@ -1,4 +1,4 @@
-# 基础方法说明
+# 03.视频播放器Api说明
 #### 目录介绍
 - 01.最简单的播放
 - 02.如何切换视频内核
@@ -13,6 +13,8 @@
 - 11.Controller相关Api
 - 12.边播放边缓存api
 - 13.类似抖音视频预加载
+- 14.视频播放器埋点
+- 15.关于视频配置
 
 
 
@@ -566,5 +568,117 @@
 
 
 
+### 14.视频播放器埋点
+- 代码如下所示,写一个类,实现BuriedPointEvent即可。即可埋点视频的播放次数,播放进度,点击视频广告啥的,方便统一管理
+    ``` java
+    public class BuriedPointEventImpl implements BuriedPointEvent {
+    
+        /**
+         * 进入视频播放
+         * @param url                       视频url
+         */
+        @Override
+        public void playerIn(String url) {
+    
+        }
+        
+        /**
+         * 退出视频播放
+         * @param url                       视频url
+         */
+        @Override
+        public void playerDestroy(String url) {
+    
+        }
+    
+        /**
+         * 视频播放完成
+         * @param url                       视频url
+         */
+        @Override
+        public void playerCompletion(String url) {
+    
+        }
+    
+        /**
+         * 视频播放异常
+         * @param url                       视频url
+         * @param isNetError                是否是网络异常
+         */
+        @Override
+        public void onError(String url, boolean isNetError) {
+    
+        }
+    
+        /**
+         * 点击了视频广告
+         * @param url                       视频url
+         */
+        @Override
+        public void clickAd(String url) {
+    
+        }
+    
+        /**
+         * 退出视频播放时候的播放进度百度分
+         * @param url                       视频url
+         * @param progress                  视频进度,计算百分比【退出时候进度 / 总进度】
+         */
+        @Override
+        public void playerOutProgress(String url, float progress) {
+    
+        }
+    
+        /**
+         * 视频切换音频
+         * @param url                       视频url
+         */
+        @Override
+        public void videoToMedia(String url) {
+    
+        }
+    }
+    ```
+
+
+### 15.关于视频配置
+- 代码如下所示
+    ``` java
+    //播放器配置,注意:此为全局配置,按需开启
+    PlayerFactory player = PlayerFactoryUtils.getPlayer(PlayerConstant.PlayerType.TYPE_IJK);
+    VideoViewManager.setConfig(VideoPlayerConfig.newBuilder()
+            //设置上下文
+            .setContext(this)
+            //设置视频全局埋点事件
+            .setBuriedPointEvent(new BuriedPointEventImpl())
+            //调试的时候请打开日志,方便排错
+            .setLogEnabled(true)
+            //设置ijk
+            .setPlayerFactory(player)
+            //在移动环境下调用start()后是否继续播放,默认不继续播放
+            .setPlayOnMobileNetwork(false)
+            //是否开启AudioFocus监听, 默认开启
+            .setEnableAudioFocus(true)
+            //是否适配刘海屏,默认适配
+            .setAdaptCutout(true)
+            //监听设备方向来切换全屏/半屏, 默认不开启
+            .setEnableOrientation(false)
+            //设置自定义渲染view,自定义RenderView
+            //.setRenderViewFactory(null)
+            //创建
+            .build());
+    ```
+
+
+
+
+
+
+
+
+
+
+
+
 
 

+ 87 - 17
read/04.视频播放器封装思路.md

@@ -1,4 +1,4 @@
-# 04.视频封装思路
+# 04.视频播放器封装思路
 #### 目录介绍
 - 01.视频播放器的痛点
 - 02.业务需求的目标
@@ -11,9 +11,15 @@
 - 09.关于视频缓存方案
 - 10.如何监控视频埋点
 - 11.待实现的需求分析
-- 12.参考案例和博客记录
+- 12.一些细节上优化
+- 13.参考案例和博客记录
 
 
+### 00.视频播放器通用框架
+- 基础封装视频播放器player,可以在ExoPlayer、MediaPlayer,声网RTC视频播放器内核,原生MediaPlayer可以自由切换
+- 对于视图状态切换和后期维护拓展,避免功能和业务出现耦合。比如需要支持播放器UI高度定制,而不是该lib库中UI代码
+- 针对视频播放,音频播放,播放回放,以及视频直播的功能。使用简单,代码拓展性强,封装性好,主要是和业务彻底解耦,暴露接口监听给开发者处理业务具体逻辑
+- 该播放器整体架构:播放器内核(自由切换) +  视频播放器 + 边播边缓存 + 高度定制播放器UI视图层
 
 
 ### 01.视频播放器的痛点
@@ -46,6 +52,8 @@
     - 播放器player与视频UI解耦:支持添加自定义视频视图,比如支持添加自定义广告,新手引导,或者视频播放异常等视图,这个需要较强的拓展性
 - 适合多种业务场景
     - 比如适合播放单个视频,多个视频,以及列表视频,或者类似抖音那种一个页面一个视频,还有小窗口播放视频。也就是适合大多数业务场景
+- 播放器的整体架构图
+    - ![image](https://img-blog.csdnimg.cn/20201012215233584.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NzAwMjc1,size_16,color_FFFFFF,t_70#pic_center)
 
 
 
@@ -59,8 +67,12 @@
     - 隐藏内核播放器创建具体细节,开发者只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体播放器类的类名。需要符合开闭原则
 
 
+
 #### 4.2 播放器内核架构图
-![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)
+- 播放器内核架构图
+    - ![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)
+- 播放器内核代码说明
+    - ![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)
 
 
 
@@ -118,9 +130,7 @@
 
 ### 5.4 视频播放器重力感应监听
 - 区别视频几种不同的播放模式
-    - 正常播放时,设置检查系统是否开启自动旋转,打开监听
-    - 全屏模式播放视频的时候,强制监听设备方向
-    - 在小窗口模式播放视频的时候,取消重力感应监听
+    - 正常播放时,设置检查系统是否开启自动旋转,打开监听;全屏模式播放视频的时候,强制监听设备方向;在小窗口模式播放视频的时候,取消重力感应监听
     - 注意一点。关于是否开启自动旋转的重力感应监听,可以给外部开发者暴露一个方法设置的开关。让用户选择是否开启该功能
 - 具体怎么操作
     - 写一个类,然后继承OrientationEventListener类,注意视频播放器重力感应监听不要那么频繁。表示500毫秒才检测一次……
@@ -130,7 +140,7 @@
 
 
 ### 06.如何简单使用
-### 5.1 播放单个视频
+### 6.1 播放单个视频
 - 必须需要的四步骤代码如下所示
     ``` java
     //创建基础视频播放器,一般播放器的功能
@@ -154,27 +164,50 @@
     - 如果是开启的音频焦点改变监听,那么播放该视频的时候,就会停止其他音视频的播放操作。类似,你听音乐,这个时候去看视频,那么音乐就暂停呢
 
 
-#### 5.2 列表播放视频
+
+#### 6.2 列表播放视频
 - 关于列表播放视频,该案例支持
     - 列表页面有多个item
         - 第一种:点击item播放,当item滑动到不可见时暂停播放;点击其他可见item播放视频,则会暂停其他正在播放的视频,也就是说一次只能播放一个视频
-        - 第二种:滑动播放视频
+        - 第二种:滑动item,用户不用点击,让其自动进行播放,这种业务场景在玩手机碰到过。大概思路时,进入列表自动播放第一个,然后在RecyclerView滑动监听的方法中,判断如果页面滑动停止了,则遍历RecyclerView子控件找到第一个完全可见的item,然后拿到该item的索引即可播放该位置的视频
     - 列表页面是一个页面一个item
-        - 第一种:
-- 如何保证在列表中只播放一个视频
-
-
-
-
+        - 第一种操作使用ViewPager,是垂直方向可以滚动的VerticalViewPager + PagerAdapter,这种方式在item创建上可以设置预加载加载布局视图
+        - 第二种操作使用RecyclerView,是用ScrollPageHelper + RecyclerView,这种方式也可以实现一个页面一个item,一次滑动一个
+- 如何保证在列表中只播放一个视频。两种方案
+    - 第一种:每个item放一个VideoPlayer,但是要注意需要用一个单例VideoPlayerManager来保证只有一个VideoPlayer对象,这样就可以保证一次播放一个视频。当ViewHolder中的视图被回收时需要销毁视频资源
+    - 第二种:只创建一个VideoPlayer,那个播放就添加到具体的item布局中。比如播放第一个视频就把player对象添加到视图中,点击播放第三个时需要把player从它的父布局中移除后然后再添加到该item的布局中,这样就可以实现
+- list条目中滑动item不可见就停止视频播放
+    - 在列表中播放,可以监听RecyclerView中的item生命周期,有一个AttachedToWindow是绑定item视图,还有一个DetachedFromWindow方法是item离开窗口时调用,在这个里面可以做视频销毁的逻辑。
 
 
 
 
 ### 07.如何自定义播放器
+- BasisVideoController已经满足基础视频播放器功能
+    - 在该控制器中,已经做了相关的初始化操作,比如设置视频可以拖动,根据屏幕方向自动进入/退出全屏,设置滑动调节亮度,音量,进度,默认开启等操作。
+    - 快速添加基础视频播放器的模块,包括视频播放完成view,播放异常view,播放top视图view,播放底部控制蓝view,手势滑动视图view等。同时在每一个视图view中可以拿到视频播放器的状态,便于设置UI的操作。
+- 比如在此播放器基础上,添加广告视图view
+    - 现在有个业务需求,需要在视频播放器刚开始添加一个广告视图,等待广告倒计时120秒后,直接进入播放视频逻辑。相信这个业务场景很常见,大家都碰到过,使用该播放器就特别简单。
+    - 首先创建一个自定义view,需要实现InterControlView接口,重写该接口中所有抽象方法,这里省略了很多代码,具体看demo。最后在调用controller.addControlComponent(adControlView);添加到基础视频播放器,这种方式满足大多数的场景……
+- 那要是基础播放器不满足UI该怎么办?
+    - 好办,直接仿照BasisVideoController创建一个你自己的控制器,ui想怎么定制你自己决定。比如说你要实现一个小窗口播放视频,那这个时候肯定需要定制,照葫芦画瓢,具体可以看CustomFloatController类。
 
 
 
 ### 08.该案例的拓展性分享
+- 可以配置多个内核切换
+    - 只需要你在配置的时候,传入不同的类型即可创建不同的播放器内核,十分方便。如果后期你要拓展其他的内核播放器,只需要按照exo的代码案例弄一套即可,十分方便,加入其他内核播放器不会影响到你的业务。
+    ``` java
+    PlayerFactory player = PlayerFactoryUtils.getPlayer(PlayerConstant.PlayerType.TYPE_IJK);
+    ```
+- 可以配置统一视频埋点监听
+    - 避免在每个带有视频的页面activity或者Fragment中添加埋点,而是有播放器框架内部提供一个埋点的接口,外部开发者只需要实现这个接口即可全局埋点视频播放器,非常方便和管理维护,针对接口增加或者删除都是不影响你其他的业务。
+- 开发者可以自由添加自定义视频视图
+    - 在封装BaseVideoController控制器的时候,考虑到后期的拓展性,把视频各个视频都是以addView的形式添加进来,使用LinkedHashMap存储这样可以保证顺序。
+    - 需要注意的是在这个Controller中,需要把播放器的播放状态,播放模式,播放进度,锁屏等操作给绑定到开发者自定义实现的播放器视图View中。
+- 暴露众多视频操作的方法给开发者
+    - 比如给视频设置封面图片,这个时候总不能在播放器内部引入一个Glide,然后加载图片,这样和业务耦合呢。可以把这个设置封面view暴露给开发者,然后设置,这样更好一些。
+    - 比如外部开发者想要知道视频播放器的状态,做一些业务上操作,这个时候完全可以通过接口的形式暴露出来,该播放器把视频的播放模式监听,播放状态监听,还有各种视频操作都暴露了方法出来,方便开发者调用。
 
 
 
@@ -203,16 +236,53 @@
     - 在开始预加载的时候,判断该播放地址是否已经预加载,如果不是那么创建一个线程task,并且把它放到map集合中。然后执行预加载逻辑,也就是执行HttpURLConnection请求
     - 提供取消对应url加载的任务,因为有可能该url不需要再进行预加载了,比如参考抖音,当用户瞬间下滑几个视频,那么很多视频就需要跳过了不需要再进行预加载。这个后期在做
 - 缓存满了该怎么处理
-
+    - 待完善
 
 
 ### 10.如何监控视频埋点
+- 传统一点的做法
+    - 比如用友盟或者百度统计,或者用其他的统计。之前的做法是,在每个有视频的页面比如说Activity,Fragment等开启时视频播放时埋点一次,页面退出时埋点一次。
+    - 如果app中有多个activity或者fragment页面,那么就每个页面都要进行埋点。比如如果你的app是付费视频,你想知道有多少人试看了,该怎么操作。那么你需要在每一个有视频的activity页面挨个添加埋点,那还有没有更好的办法?
+- 解决方案
+    - 举个例子:例如,你需要来让外部开发者手动去埋点,可是在类中怎么埋点又是由其他人来设计的,你只是需要对外暴露监听的方法。那么该如何做呢?采用接口 + 实现类方式即可实现。
+- 该案例中怎么操作
+    - 定义一个接口,规定其他人设计类,必须继承这个接口。在这个接口中,定义进入视频播放,退出视频播放器,记录播放进度,视频播放完成,播放异常,点击广告,点击试看等操作的抽象方法。具体可以看BuriedPointEvent类代码……
+- 外部开发者如何使用
+    - 定义一个类实现该视频埋点接口,重写里面方法。然后需要在初始化配置视频播放器的时候,将这个实现类的对象传递进来即可。通过这个配置类传进来的对象,播放器就可以处理监听设置逻辑呢。
+    - 这种操作最大的好处就是:在这个类中统一处理视频的埋点,修改快捷,而不用在每一个有视频播放器的页面埋点,方便维护。比如如何处理视频播放完成监听,代码如下所示:
+    ``` java
+    /**
+     * 视频播放完成回调
+     */
+    @Override
+    public void onCompletion() {
+        VideoPlayerConfig config = VideoViewManager.getConfig();
+        if (config!=null && config.mBuriedPointEvent!=null){
+            //视频播放完成
+            config.mBuriedPointEvent.playerCompletion(mUrl);
+        }
+    }
+    ```
+
 
 
 ### 11.待实现的需求分析
+- 音视频无缝切换
+    - 比如在豆神教育中,有视频播放,也有音频播放,这两块都是写到了业务代码中,能否将两者糅合起来。但音频相比视频,多了一个可以在后台播放的功能,一般用在service中,这一相互切换需求待完善。以满足后期可能出现的需求功能。
+
+
+
+### 12.一些细节上优化
+- 多使用注解限定符
+    - 对于一些关于类型的方法参数,可以多用注解限定符,暴露给外部开发者调用的方法,可以防止传入正确的类型。比如:PlayerFactoryUtils.getPlayer(PlayerConstant.PlayerType.TYPE_IJK)
+- 完善的api文档
+    - api文档充分完善到每一个细节,以及配套demo,方便快速上手。完善的代码注释,以及项目的类结构图,方便快速了解视频播放器的整体轮廓
+- 丰富的demo案例
+    - 提供绝大多数场景的视频播放器功能,完全可以套用demo中的案例,甚至你还可以在案例基础上大幅度优化
+
 
 
-### 12.参考案例和博客记录
+### 13.参考案例和博客记录
 - exo播放器
     - https://github.com/google/ExoPlayer
 - ijk播放器

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

@@ -1,4 +1,4 @@
-# 05.视频封装思路
+# 05.视频播放器内核切换封装
 #### 目录介绍
 - 01.视频播放器内核封装需求
 - 02.播放器内核架构图
@@ -11,6 +11,17 @@
 
 
 
+### 00.视频播放器通用框架
+- 基础封装视频播放器player,可以在ExoPlayer、MediaPlayer,声网RTC视频播放器内核,原生MediaPlayer可以自由切换
+- 对于视图状态切换和后期维护拓展,避免功能和业务出现耦合。比如需要支持播放器UI高度定制,而不是该lib库中UI代码
+- 针对视频播放,音频播放,播放回放,以及视频直播的功能。使用简单,代码拓展性强,封装性好,主要是和业务彻底解耦,暴露接口监听给开发者处理业务具体逻辑
+- 该播放器整体架构:播放器内核(自由切换) +  视频播放器 + 边播边缓存 + 高度定制播放器UI视图层
+- 项目地址:https://github.com/yangchong211/YCVideoPlayer
+- 关于视频播放器整体功能介绍文档:https://juejin.im/post/6883457444752654343
+
+
+
+
 ### 01.视频播放器内核封装需求
 - 播放器内核难以切换
     - 不同的视频播放器内核,由于api不一样,所以难以切换操作。要是想兼容内核切换,就必须自己制定一个视频接口+实现类的播放器
@@ -433,401 +444,10 @@
 
 
 ### 05.看一下exo的内核实现类
-- exo的内核实现类代码如下所示,和ijk的api有些区别
+- exo的内核实现类代码如下所示,和ijk的api有些区别。代码省略,具体可以看demo
     ```java
     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;
-            }
-        }
+ 
     }
     ```
 

+ 609 - 1
read/06.播放器UI抽取封装.md

@@ -1,4 +1,4 @@
-# 06.播放器UI抽取封装
+# 06.视频播放器UI抽取封装
 #### 目录介绍
 - 01.视频播放器UI封装需求
 - 02.播放器UI架构图
@@ -11,6 +11,19 @@
 - 09.视频播放器重力感应监听
 
 
+
+
+### 00.视频播放器通用框架
+- 基础封装视频播放器player,可以在ExoPlayer、MediaPlayer,声网RTC视频播放器内核,原生MediaPlayer可以自由切换
+- 对于视图状态切换和后期维护拓展,避免功能和业务出现耦合。比如需要支持播放器UI高度定制,而不是该lib库中UI代码
+- 针对视频播放,音频播放,播放回放,以及视频直播的功能。使用简单,代码拓展性强,封装性好,主要是和业务彻底解耦,暴露接口监听给开发者处理业务具体逻辑
+- 该播放器整体架构:播放器内核(自由切换) +  视频播放器 + 边播边缓存 + 高度定制播放器UI视图层
+- 项目地址:https://github.com/yangchong211/YCVideoPlayer
+- 关于视频播放器整体功能介绍文档:https://juejin.im/post/6883457444752654343
+
+
+
+
 ### 01.视频播放器UI封装需求
 - 发展中遇到的问题
     - 播放器可支持多种场景下的播放,多个产品会用到同一个播放器,这样就会带来一个问题,一个播放业务播放器状态发生变化,其他播放业务必须同步更新播放状态,各个播放业务之间互相交叉,随着播放业务的增多,开发和维护成本会急剧增加, 导致后续开发不可持续。 
@@ -29,6 +42,8 @@
 
 
 ### 02.播放器UI架构图
+![image](https://img-blog.csdnimg.cn/20201012215233584.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/20201013094115174.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NzAwMjc1,size_16,color_FFFFFF,t_70#pic_center)
 
 
 
@@ -56,13 +71,606 @@
 
 
 ### 04.VideoPlayer如何实现
+- 代码如下所示,省略了部分代码,具体看demo
+    ``` java
+    public class VideoPlayer<P extends AbstractVideoPlayer> extends FrameLayout
+            implements InterVideoPlayer, VideoPlayerListener {
+    
+        private Context mContext;
+        /**
+         * 播放器
+         */
+        protected P mMediaPlayer;
+        /**
+         * 实例化播放核心
+         */
+        protected PlayerFactory<P> mPlayerFactory;
+        /**
+         * 控制器
+         */
+        @Nullable
+        protected BaseVideoController mVideoController;
+        /**
+         * 真正承载播放器视图的容器
+         */
+        protected FrameLayout mPlayerContainer;
+
+        public VideoPlayer(@NonNull Context context) {
+            this(context, null);
+        }
+    
+        public VideoPlayer(@NonNull Context context, @Nullable AttributeSet attrs) {
+            this(context, attrs, 0);
+        }
+    
+        public VideoPlayer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+            super(context, attrs, defStyleAttr);
+            mContext = context;
+            init(attrs);
+        }
+    
+        private void init(AttributeSet attrs) {
+            BaseToast.init(mContext.getApplicationContext());
+            //读取全局配置
+            initConfig();
+            //读取xml中的配置,并综合全局配置
+            initAttrs(attrs);
+            initView();
+        }
+    
+        private void initConfig() {
+            VideoPlayerConfig config = VideoViewManager.getConfig();
+            mEnableAudioFocus = config.mEnableAudioFocus;
+            mProgressManager = config.mProgressManager;
+            mPlayerFactory = config.mPlayerFactory;
+            mCurrentScreenScaleType = config.mScreenScaleType;
+            mRenderViewFactory = config.mRenderViewFactory;
+            //设置是否打印日志
+            VideoLogUtils.setIsLog(config.mIsEnableLog);
+        }
+
+        @Override
+        protected Parcelable onSaveInstanceState() {
+            VideoLogUtils.d("onSaveInstanceState: " + mCurrentPosition);
+            //activity切到后台后可能被系统回收,故在此处进行进度保存
+            saveProgress();
+            return super.onSaveInstanceState();
+        }
+    
+        private void initAttrs(AttributeSet attrs) {
+            TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.VideoPlayer);
+            mEnableAudioFocus = a.getBoolean(R.styleable.VideoPlayer_enableAudioFocus, mEnableAudioFocus);
+            mIsLooping = a.getBoolean(R.styleable.VideoPlayer_looping, false);
+            mCurrentScreenScaleType = a.getInt(R.styleable.VideoPlayer_screenScaleType, mCurrentScreenScaleType);
+            mPlayerBackgroundColor = a.getColor(R.styleable.VideoPlayer_playerBackgroundColor, Color.BLACK);
+            a.recycle();
+        }
+    
+        /**
+         * 初始化播放器视图
+         */
+        protected void initView() {
+            mPlayerContainer = new FrameLayout(getContext());
+            //设置背景颜色,目前设置为纯黑色
+            mPlayerContainer.setBackgroundColor(mPlayerBackgroundColor);
+            LayoutParams params = new LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT);
+            //将布局添加到该视图中
+            this.addView(mPlayerContainer, params);
+        }
+    
+        /**
+         * 设置控制器,传null表示移除控制器
+         * @param mediaController                           controller
+         */
+        public void setController(@Nullable BaseVideoController mediaController) {
+            mPlayerContainer.removeView(mVideoController);
+            mVideoController = mediaController;
+            if (mediaController != null) {
+                mediaController.setMediaPlayer(this);
+                LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                        ViewGroup.LayoutParams.MATCH_PARENT);
+                mPlayerContainer.addView(mVideoController, params);
+            }
+        }
+    
+        /**
+         * 开始播放,注意:调用此方法后必须调用{@link #release()}释放播放器,否则会导致内存泄漏
+         */
+        @Override
+        public void start() {
+            if (mVideoController==null){
+                //在调用start方法前,请先初始化视频控制器,调用setController方法
+                throw new VideoException(VideoException.CODE_NOT_SET_CONTROLLER,
+                        "Controller must not be null , please setController first");
+            }
+            boolean isStarted = false;
+            if (isInIdleState() || isInStartAbortState()) {
+                isStarted = startPlay();
+            } else if (isInPlaybackState()) {
+                startInPlaybackState();
+                isStarted = true;
+            }
+            if (isStarted) {
+                mPlayerContainer.setKeepScreenOn(true);
+                if (mAudioFocusHelper != null){
+                    mAudioFocusHelper.requestFocus();
+                }
+            }
+        }
+    
+        /**
+         * 第一次播放
+         * @return 是否成功开始播放
+         */
+        protected boolean startPlay() {
+            //如果要显示移动网络提示则不继续播放
+            if (showNetWarning()) {
+                //中止播放
+                setPlayState(ConstantKeys.CurrentState.STATE_START_ABORT);
+                return false;
+            }
+            //监听音频焦点改变
+            if (mEnableAudioFocus) {
+                mAudioFocusHelper = new AudioFocusHelper(this);
+            }
+            //读取播放进度
+            if (mProgressManager != null) {
+                mCurrentPosition = mProgressManager.getSavedProgress(mUrl);
+            }
+            initPlayer();
+            addDisplay();
+            startPrepare(false);
+            return true;
+        }
+    
+    
+        /**
+         * 初始化播放器
+         */
+        protected void initPlayer() {
+            //通过工厂模式创建对象
+            mMediaPlayer = mPlayerFactory.createPlayer(mContext);
+            mMediaPlayer.setPlayerEventListener(this);
+            setInitOptions();
+            mMediaPlayer.initPlayer();
+            setOptions();
+        }
+
+        /**
+         * 初始化视频渲染View
+         */
+        protected void addDisplay() {
+            if (mRenderView != null) {
+                mPlayerContainer.removeView(mRenderView.getView());
+                mRenderView.release();
+            }
+            //创建TextureView对象
+            mRenderView = mRenderViewFactory.createRenderView(mContext);
+            //绑定mMediaPlayer对象
+            mRenderView.attachToPlayer(mMediaPlayer);
+            LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER);
+            mPlayerContainer.addView(mRenderView.getView(), 0, params);
+        }
+    
+        /**
+         * 开始准备播放(直接播放)
+         */
+        protected void startPrepare(boolean reset) {
+            if (reset) {
+                mMediaPlayer.reset();
+                //重新设置option,media player reset之后,option会失效
+                setOptions();
+            }
+            if (prepareDataSource()) {
+                mMediaPlayer.prepareAsync();
+                setPlayState(ConstantKeys.CurrentState.STATE_PREPARING);
+                setPlayerState(isFullScreen() ? ConstantKeys.PlayMode.MODE_FULL_SCREEN :
+                        isTinyScreen() ? ConstantKeys.PlayMode.MODE_TINY_WINDOW : ConstantKeys.PlayMode.MODE_NORMAL);
+            }
+        }
+    
+        /**
+         * 设置播放数据
+         * @return 播放数据是否设置成功
+         */
+        protected boolean prepareDataSource() {
+            if (mAssetFileDescriptor != null) {
+                mMediaPlayer.setDataSource(mAssetFileDescriptor);
+                return true;
+            } else if (!TextUtils.isEmpty(mUrl)) {
+                mMediaPlayer.setDataSource(mUrl, mHeaders);
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * 向Controller设置播放状态,用于控制Controller的ui展示
+         * 这里使用注解限定符,不要使用1,2这种直观数字,不方便知道意思
+         * 播放状态,主要是指播放器的各种状态
+         * -1               播放错误
+         * 0                播放未开始
+         * 1                播放准备中
+         * 2                播放准备就绪
+         * 3                正在播放
+         * 4                暂停播放
+         * 5                正在缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,缓冲区数据足够后恢复播放)
+         * 6                暂停缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,此时暂停播放器,继续缓冲,缓冲区数据足够后恢复暂停
+         * 7                播放完成
+         * 8                开始播放中止
+         */
+        protected void setPlayState(@ConstantKeys.CurrentStateType int playState) {
+            mCurrentPlayState = playState;
+            if (mVideoController != null) {
+                mVideoController.setPlayState(playState);
+            }
+            if (mOnStateChangeListeners != null) {
+                for (OnVideoStateListener l : PlayerUtils.getSnapshot(mOnStateChangeListeners)) {
+                    if (l != null) {
+                        l.onPlayStateChanged(playState);
+                    }
+                }
+            }
+        }
+    
+        /**
+         * 向Controller设置播放器状态,包含全屏状态和非全屏状态
+         * 播放模式
+         * 普通模式,小窗口模式,正常模式三种其中一种
+         * MODE_NORMAL              普通模式
+         * MODE_FULL_SCREEN         全屏模式
+         * MODE_TINY_WINDOW         小屏模式
+         */
+        protected void setPlayerState(@ConstantKeys.PlayModeType int playerState) {
+            mCurrentPlayerState = playerState;
+            if (mVideoController != null) {
+                mVideoController.setPlayerState(playerState);
+            }
+            if (mOnStateChangeListeners != null) {
+                for (OnVideoStateListener l : PlayerUtils.getSnapshot(mOnStateChangeListeners)) {
+                    if (l != null) {
+                        l.onPlayerStateChanged(playerState);
+                    }
+                }
+            }
+        }
+    
+        /**
+         * OnStateChangeListener的空实现。用的时候只需要重写需要的方法
+         */
+        public static class SimpleOnStateChangeListener implements OnVideoStateListener {
+            @Override
+            public void onPlayerStateChanged(@ConstantKeys.PlayModeType int playerState) {}
+            @Override
+            public void onPlayStateChanged(int playState) {}
+        }
+    
+        /**
+         * 添加一个播放状态监听器,播放状态发生变化时将会调用。
+         */
+        public void addOnStateChangeListener(@NonNull OnVideoStateListener listener) {
+            if (mOnStateChangeListeners == null) {
+                mOnStateChangeListeners = new ArrayList<>();
+            }
+            mOnStateChangeListeners.add(listener);
+        }
+    
+        /**
+         * 移除某个播放状态监听
+         */
+        public void removeOnStateChangeListener(@NonNull OnVideoStateListener listener) {
+            if (mOnStateChangeListeners != null) {
+                mOnStateChangeListeners.remove(listener);
+            }
+        }
+    
+        /**
+         * 设置一个播放状态监听器,播放状态发生变化时将会调用,
+         * 如果你想同时设置多个监听器,推荐 {@link #addOnStateChangeListener(OnVideoStateListener)}。
+         */
+        public void setOnStateChangeListener(@NonNull OnVideoStateListener listener) {
+            if (mOnStateChangeListeners == null) {
+                mOnStateChangeListeners = new ArrayList<>();
+            } else {
+                mOnStateChangeListeners.clear();
+            }
+            mOnStateChangeListeners.add(listener);
+        }
+    
+        /**
+         * 移除所有播放状态监听
+         */
+        public void clearOnStateChangeListeners() {
+            if (mOnStateChangeListeners != null) {
+                mOnStateChangeListeners.clear();
+            }
+        }
+    
+        /**
+         * 改变返回键逻辑,用于activity
+         */
+        public boolean onBackPressed() {
+            return mVideoController != null && mVideoController.onBackPressed();
+        }
+
+    }
+    ```
 
 
 ### 05.VideoController实现
+- 代码如下所示,代码太长,省略部分代码,具体看demo
+    ``` java
+    public abstract class BaseVideoController extends FrameLayout implements InterVideoController,
+            OrientationHelper.OnOrientationChangeListener {
+    
+        //播放器包装类,集合了MediaPlayerControl的api和IVideoController的api
+        protected ControlWrapper mControlWrapper;
 
+        public BaseVideoController(@NonNull Context context) {
+            //创建
+            this(context, null);
+        }
+    
+        public BaseVideoController(@NonNull Context context, @Nullable AttributeSet attrs) {
+            //创建
+            this(context, attrs, 0);
+        }
+    
+        public BaseVideoController(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
+            super(context, attrs, defStyleAttr);
+            initView(context);
+        }
+
+        protected void initView(Context context) {
+            if (getLayoutId() != 0) {
+                LayoutInflater.from(getContext()).inflate(getLayoutId(), this, true);
+            }
+            mOrientationHelper = new OrientationHelper(context.getApplicationContext());
+            mEnableOrientation = VideoViewManager.getConfig().mEnableOrientation;
+            mAdaptCutout = VideoViewManager.getConfig().mAdaptCutout;
+            mShowAnim = new AlphaAnimation(0f, 1f);
+            mShowAnim.setDuration(300);
+            mHideAnim = new AlphaAnimation(1f, 0f);
+            mHideAnim.setDuration(300);
+            mActivity = PlayerUtils.scanForActivity(context);
+        }
+    
+        /**
+         * 设置控制器布局文件,子类必须实现
+         */
+        protected abstract int getLayoutId();
+    
+        /**
+         * 重要:此方法用于将{@link VideoPlayer} 和控制器绑定
+         */
+        @CallSuper
+        public void setMediaPlayer(InterVideoPlayer mediaPlayer) {
+            mControlWrapper = new ControlWrapper(mediaPlayer, this);
+            //绑定ControlComponent和Controller
+            for (Map.Entry<InterControlView, Boolean> next : mControlComponents.entrySet()) {
+                InterControlView component = next.getKey();
+                component.attach(mControlWrapper);
+            }
+            //开始监听设备方向
+            mOrientationHelper.setOnOrientationChangeListener(this);
+        }
+    
+        /**
+         * 添加控制组件,最后面添加的在最下面,合理组织添加顺序,可让ControlComponent位于不同的层级
+         */
+        public void addControlComponent(InterControlView... component) {
+            for (InterControlView item : component) {
+                addControlComponent(item, false);
+            }
+        }
+    
+        /**
+         * 添加控制组件,最后面添加的在最下面,合理组织添加顺序,可让ControlComponent位于不同的层级
+         *
+         * @param isPrivate 是否为独有的组件,如果是就不添加到控制器中
+         */
+        public void addControlComponent(InterControlView component, boolean isPrivate) {
+            mControlComponents.put(component, isPrivate);
+            if (mControlWrapper != null) {
+                component.attach(mControlWrapper);
+            }
+            View view = component.getView();
+            if (view != null && !isPrivate) {
+                addView(view, 0);
+            }
+        }
+    
+        /**
+         * 移除控制组件
+         */
+        public void removeControlComponent(InterControlView component) {
+            removeView(component.getView());
+            mControlComponents.remove(component);
+        }
+    
+        /**
+         * 移除所有的组件
+         */
+        public void removeAllControlComponent() {
+            for (Map.Entry<InterControlView, Boolean> next : mControlComponents.entrySet()) {
+                removeView(next.getKey().getView());
+            }
+            mControlComponents.clear();
+        }
+    
+        public void removeAllPrivateComponents() {
+            Iterator<Map.Entry<InterControlView, Boolean>> it = mControlComponents.entrySet().iterator();
+            while (it.hasNext()) {
+                Map.Entry<InterControlView, Boolean> next = it.next();
+                if (next.getValue()) {
+                    it.remove();
+                }
+            }
+        }
+    
+        /**
+         * {@link VideoPlayer}调用此方法向控制器设置播放状态。
+         * 这里使用注解限定符,不要使用1,2这种直观数字,不方便知道意思
+         * 播放状态,主要是指播放器的各种状态
+         * -1               播放错误
+         * 0                播放未开始
+         * 1                播放准备中
+         * 2                播放准备就绪
+         * 3                正在播放
+         * 4                暂停播放
+         * 5                正在缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,缓冲区数据足够后恢复播放)
+         * 6                暂停缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,此时暂停播放器,继续缓冲,缓冲区数据足够后恢复暂停
+         * 7                播放完成
+         * 8                开始播放中止
+         */
+        @CallSuper
+        public void setPlayState(@ConstantKeys.CurrentStateType int playState) {
+            //设置播放器的状态
+            handlePlayStateChanged(playState);
+        }
+    
+        /**
+         * {@link VideoPlayer}调用此方法向控制器设置播放器状态
+         * 播放模式
+         * 普通模式,小窗口模式,正常模式三种其中一种
+         * MODE_NORMAL              普通模式
+         * MODE_FULL_SCREEN         全屏模式
+         * MODE_TINY_WINDOW         小屏模式
+         */
+        @CallSuper
+        public void setPlayerState(@ConstantKeys.PlayModeType final int playerState) {
+            //调用此方法向控制器设置播放器状态
+            handlePlayerStateChanged(playerState);
+        }
+        
+        private void handleVisibilityChanged(boolean isVisible, Animation anim) {
+            if (!mIsLocked) {
+                //没锁住时才向ControlComponent下发此事件
+                for (Map.Entry<InterControlView, Boolean> next : mControlComponents.entrySet()) {
+                    InterControlView component = next.getKey();
+                    component.onVisibilityChanged(isVisible, anim);
+                }
+            }
+            onVisibilityChanged(isVisible, anim);
+        }
+    
+        private void handlePlayStateChanged(int playState) {
+            for (Map.Entry<InterControlView, Boolean> next : mControlComponents.entrySet()) {
+                InterControlView component = next.getKey();
+                component.onPlayStateChanged(playState);
+            }
+            onPlayStateChanged(playState);
+        }
+    
+        /**
+         * 子类重写此方法并在其中更新控制器在不同播放状态下的ui
+         */
+        @CallSuper
+        protected void onPlayStateChanged(int playState) {
+            switch (playState) {
+                case ConstantKeys.CurrentState.STATE_IDLE:
+                    mOrientationHelper.disable();
+                    mOrientation = 0;
+                    mIsLocked = false;
+                    mShowing = false;
+                    removeAllPrivateComponents();
+                    break;
+                case ConstantKeys.CurrentState.STATE_BUFFERING_PLAYING:
+                    mIsLocked = false;
+                    mShowing = false;
+                    break;
+                case ConstantKeys.CurrentState.STATE_ERROR:
+                    mShowing = false;
+                    break;
+            }
+        }
+    
+        /**
+         * 播放器状态改变
+         * @param playerState                       播放器状态
+         */
+        private void handlePlayerStateChanged(int playerState) {
+            for (Map.Entry<InterControlView, Boolean> next : mControlComponents.entrySet()) {
+                InterControlView component = next.getKey();
+                component.onPlayerStateChanged(playerState);
+            }
+            onPlayerStateChanged(playerState);
+        }
+    
+        /**
+         * 子类重写此方法并在其中更新控制器在不同播放器状态下的ui
+         * 普通模式,小窗口模式,正常模式三种其中一种
+         * MODE_NORMAL              普通模式
+         * MODE_FULL_SCREEN         全屏模式
+         * MODE_TINY_WINDOW         小屏模式
+         */
+        @CallSuper
+        protected void onPlayerStateChanged(@ConstantKeys.PlayMode int playerState) {
+            switch (playerState) {
+                case ConstantKeys.PlayMode.MODE_NORMAL:
+                    //视频正常播放是设置监听
+                    if (mEnableOrientation) {
+                        //检查系统是否开启自动旋转
+                        mOrientationHelper.enable();
+                    } else {
+                        //取消监听
+                        mOrientationHelper.disable();
+                    }
+                    if (hasCutout()) {
+                        StatesCutoutUtils.adaptCutoutAboveAndroidP(getContext(), false);
+                    }
+                    break;
+                case ConstantKeys.PlayMode.MODE_FULL_SCREEN:
+                    //在全屏时强制监听设备方向
+                    mOrientationHelper.enable();
+                    if (hasCutout()) {
+                        StatesCutoutUtils.adaptCutoutAboveAndroidP(getContext(), true);
+                    }
+                    break;
+                case ConstantKeys.PlayMode.MODE_TINY_WINDOW:
+                    //小窗口取消重力感应监听
+                    mOrientationHelper.disable();
+                    break;
+            }
+        }
+    }
+    ```
 
 
 ### 06.播放Player和UI通信
+- 比如,在自定义view视图中,我想调用VideoPlayer的api又能调用BaseVideoController的api,该如何实现呢?
+    - 当创建了下面的对象,就可以同时拿到player和controller中的api方法呢,这里面省略一部分代码,具体看demo案例
+    ``` java
+    public class ControlWrapper implements InterVideoPlayer, InterVideoController {
+        
+        private InterVideoPlayer mVideoPlayer;
+        private InterVideoController mController;
+        
+        public ControlWrapper(@NonNull InterVideoPlayer videoPlayer, @NonNull InterVideoController controller) {
+            mVideoPlayer = videoPlayer;
+            mController = controller;
+        }
+        
+        @Override
+        public void start() {
+            mVideoPlayer.start();
+        }
+    
+        @Override
+        public boolean isShowing() {
+            return mController.isShowing();
+        }
+    
+        @Override
+        public void setLocked(boolean locked) {
+            mController.setLocked(locked);
+        }
+
+    }
+    ```
 
 
 

+ 94 - 1
read/29.视频播放器埋点监听.md

@@ -1,4 +1,97 @@
 # 29.视频播放器埋点监听
 #### 目录介绍
-- 01.传统一点的做
+- 01.传统一点的做
 - 02.如何实现统一埋点
+- 03.具体实现代码案例
+
+
+
+### 01.传统一点的做法
+- 传统一点的做法
+    - 比如用友盟或者百度统计,或者用其他的统计。之前的做法是,在每个有视频的页面比如说Activity,Fragment等开启时视频播放时埋点一次,页面退出时埋点一次。
+    - 如果app中有多个activity或者fragment页面,那么就每个页面都要进行埋点。比如如果你的app是付费视频,你想知道有多少人试看了,该怎么操作。那么你需要在每一个有视频的activity页面挨个添加埋点,那还有没有更好的办法?
+
+
+### 02.如何实现统一埋点
+- 解决方案
+    - 举个例子:例如,你需要来让外部开发者手动去埋点,可是在类中怎么埋点又是由其他人来设计的,你只是需要对外暴露监听的方法。那么该如何做呢?采用接口 + 实现类方式即可实现。
+- 该案例中怎么操作
+    - 定义一个接口,规定其他人设计类,必须继承这个接口。在这个接口中,定义进入视频播放,退出视频播放器,记录播放进度,视频播放完成,播放异常,点击广告,点击试看等操作的抽象方法。具体可以看BuriedPointEvent类代码……
+- 外部开发者如何使用
+    - 定义一个类实现该视频埋点接口,重写里面方法。然后需要在初始化配置视频播放器的时候,将这个实现类的对象传递进来即可。通过这个配置类传进来的对象,播放器就可以处理监听设置逻辑呢。
+    - 这种操作最大的好处就是:在这个类中统一处理视频的埋点,修改快捷,而不用在每一个有视频播放器的页面埋点,方便维护。
+
+
+### 03.具体实现代码案例
+- 如何监听进入视频播放,刚开始想着在play方法中监听,后来发现不太好。改为在设置视频url时监听,即代表进入视频播放开始
+    ``` java
+    //在VideoPlayer类中
+    /**
+     * 设置包含请求头信息的视频地址
+     *
+     * @param url     视频地址
+     * @param headers 请求头
+     */
+    public void setUrl(String url, Map<String, String> headers) {
+        VideoPlayerConfig config = VideoViewManager.getConfig();
+        if (config!=null && config.mBuriedPointEvent!=null){
+            //相当于进入了视频页面
+            config.mBuriedPointEvent.playerIn(url);
+        }
+    }
+    ```
+- 如何监听视频退出播放,以及退出播发时候的播放进度百分比,这个方便统计用户看视频情况
+    ``` java
+    /**
+     * 释放播放器
+     */
+    public void release() {
+        if (!isInIdleState()) {
+            VideoPlayerConfig config = VideoViewManager.getConfig();
+            if (config!=null && config.mBuriedPointEvent!=null){
+                //退出视频播放
+                config.mBuriedPointEvent.playerDestroy(mUrl);
+    
+                //计算退出视频时候的进度
+                long duration = getDuration();
+                long currentPosition = getCurrentPosition();
+                float progress = (currentPosition*1.0f) / (duration*1.0f) ;
+                config.mBuriedPointEvent.playerOutProgress(mUrl,progress);
+            }
+        }
+    }
+    ```
+- 如何监听视频播放完了,这样做主要是查看用户的播放完成率,方便分析
+    ``` java
+    /**
+     * 视频播放完成回调
+     */
+    @Override
+    public void onCompletion() {
+        VideoPlayerConfig config = VideoViewManager.getConfig();
+        if (config!=null && config.mBuriedPointEvent!=null){
+            //视频播放完成
+            config.mBuriedPointEvent.playerCompletion(mUrl);
+        }
+    }
+    ```
+- 然后测试一下,打印日志如下所示
+    ``` java
+    2020-10-16 11:07:04.398 5534-5534/org.yczbj.ycvideoplayer I/YCVideoPlayer: BuriedPointEvent---进入视频播放--http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4
+    2020-10-16 11:07:16.006 5534-5534/org.yczbj.ycvideoplayer I/YCVideoPlayer: BuriedPointEvent---视频播放完成--http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4
+    2020-10-16 11:07:30.566 5534-5534/org.yczbj.ycvideoplayer I/YCVideoPlayer: BuriedPointEvent---视频播放异常--http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4
+    2020-10-16 11:07:41.107 5534-5534/org.yczbj.ycvideoplayer I/YCVideoPlayer: BuriedPointEvent---退出视频播放--http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4
+    2020-10-16 11:07:41.107 5534-5534/org.yczbj.ycvideoplayer I/YCVideoPlayer: BuriedPointEvent---退出视频播放时候的播放进度百度分--http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4
+    ``` 
+
+
+
+
+
+
+
+
+
+
+
+