浏览代码

count down

zhaoyadi 2 年之前
父节点
当前提交
d445e5e2bf
共有 29 个文件被更改,包括 998 次插入304 次删除
  1. 26 15
      kit/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerView.java
  2. 0 5
      kit/src/main/java/com/tencent/liteav/demo/superplayer/player/ITimeout.java
  3. 2 1
      kit/src/main/java/com/tencent/liteav/demo/superplayer/player/SuperPlayer.java
  4. 16 7
      kit/src/main/java/com/tencent/liteav/demo/superplayer/player/SuperPlayerImpl.java
  5. 0 4
      kit/src/main/java/com/tencent/liteav/demo/superplayer/player/SuperPlayerObserver.java
  6. 6 5
      kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/AbsPlayer.java
  7. 3 14
      kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/FullScreenPlayer.java
  8. 0 5
      kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/Player.java
  9. 6 0
      kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/TimeOutPlayer.java
  10. 4 2
      kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/WindowPlayer.java
  11. 0 9
      kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/UnlockProgressView.java
  12. 384 0
      kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/WheelView.java
  13. 45 48
      kit/src/main/java/com/tencent/liteav/demo/superplayer/util/CountDownUtil.java
  14. 1 7
      kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/database/PlayerDatabase.kt
  15. 7 4
      kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/database/dao/CountDownDao.kt
  16. 23 5
      kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/database/entity/CountDown.kt
  17. 17 12
      kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/database/repo/PlayerRepository.kt
  18. 23 0
      kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/util/HistoryUtil.kt
  19. 64 0
      kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/util/PlayerUtil.kt
  20. 23 0
      kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/util/TimeoutUtil.kt
  21. 23 0
      kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/util/TimersUtil.kt
  22. 13 6
      ui/src/main/AndroidManifest.xml
  23. 0 38
      ui/src/main/java/com/tencent/liteav/demo/player/util/PlayerTimerUtil.java
  24. 45 0
      ui/src/main/kotlin/com/tencent/liteav/demo/player/MainActivity.kt
  25. 31 28
      ui/src/main/kotlin/com/tencent/liteav/demo/player/PlayerActivity.kt
  26. 19 0
      ui/src/main/kotlin/com/tencent/liteav/demo/player/PlayerWheel.kt
  27. 9 9
      ui/src/main/kotlin/com/tencent/liteav/demo/player/menu/PlayerTimer.kt
  28. 131 80
      ui/src/main/kotlin/com/tencent/liteav/demo/player/viewmodel/PlayerViewModel.kt
  29. 77 0
      ui/src/main/res/layout/activity_wheel.xml

+ 26 - 15
kit/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerView.java

@@ -17,13 +17,14 @@ import com.tencent.liteav.demo.superplayer.player.SuperPlayerObserver;
 import com.tencent.liteav.demo.superplayer.ui.player.FullScreenPlayer;
 import com.tencent.liteav.demo.superplayer.ui.player.Player;
 import com.tencent.liteav.demo.superplayer.ui.player.WindowPlayer;
+import com.tencent.liteav.demo.superplayer.util.TimeoutUtil;
 import com.tencent.rtmp.ui.TXCloudVideoView;
 
 import java.lang.reflect.Constructor;
 import java.util.ArrayList;
 import java.util.List;
 
-public class SuperPlayerView extends RelativeLayout {
+public class SuperPlayerView extends RelativeLayout implements TimeoutUtil.Listener {
     private static final String TAG = "SuperPlayerView";
 
     private Context mContext;
@@ -86,6 +87,18 @@ public class SuperPlayerView extends RelativeLayout {
         }
     }
 
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        TimeoutUtil.INSTANCE.addListener(this);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        TimeoutUtil.INSTANCE.removeListener(this);
+        super.onDetachedFromWindow();
+    }
+
     /**
      * 初始化view
      */
@@ -231,18 +244,22 @@ public class SuperPlayerView extends RelativeLayout {
      * 停止播放
      */
     public void stopPlay() {
+        mIsPlayInit = false;
         mSuperPlayer.stop();
     }
 
-    public void setTimeOut(final boolean timeout) {
+    @Override
+    public void onTimeout(boolean timeout) {
         this.mIsTimeOut = timeout;
+        setNeedToPause(timeout);
 
         if (timeout) {
             switchPlayMode(SuperPlayerDef.PlayerMode.FULLSCREEN);
         }
-        mSuperPlayer.setTimeout(timeout);
-        mWindowPlayer.setTimeoutState(timeout);
-        mFullScreenPlayer.setTimeoutState(timeout);
+
+        mSuperPlayer.onTimeout(timeout);
+        mFullScreenPlayer.onTimeout(timeout);
+        mWindowPlayer.onTimeout(timeout);
     }
 
     /**
@@ -366,6 +383,10 @@ public class SuperPlayerView extends RelativeLayout {
         }
     };
 
+    public SuperPlayerModel getPlayingVideoModel() {
+        return mCurrentSuperPlayerModel;
+    }
+
     private void handleResume() {
         if (mSuperPlayer.getPlayerState() == SuperPlayerDef.PlayerState.LOADING) {
             mSuperPlayer.resume();
@@ -425,11 +446,6 @@ public class SuperPlayerView extends RelativeLayout {
          */
         void onVerify(SuperPlayerDef.VerifyReason reason);
 
-        /**
-         * 单次播放历史
-         */
-        void onOnceHistory(long start, long end, String sectionId);
-
         void onResumePlay();
 
         void onPausePlay();
@@ -537,11 +553,6 @@ public class SuperPlayerView extends RelativeLayout {
             mWindowPlayer.toggleCoverView(false);
             mFullScreenPlayer.toggleCoverView(false);
         }
-
-        @Override
-        public void onOnceHistory(long start, long end, String sectionId) {
-            mPlayerViewCallback.onOnceHistory(start, end, sectionId);
-        }
     }
 
     /**

+ 0 - 5
kit/src/main/java/com/tencent/liteav/demo/superplayer/player/ITimeout.java

@@ -1,5 +0,0 @@
-package com.tencent.liteav.demo.superplayer.player;
-
-public interface ITimeout {
-    void setTimeout(boolean timeout);
-}

+ 2 - 1
kit/src/main/java/com/tencent/liteav/demo/superplayer/player/SuperPlayer.java

@@ -2,8 +2,9 @@ package com.tencent.liteav.demo.superplayer.player;
 
 import com.tencent.liteav.demo.superplayer.SuperPlayerDef;
 import com.tencent.liteav.demo.superplayer.SuperPlayerModel;
+import com.tencent.liteav.demo.superplayer.util.TimeoutUtil;
 
-public interface SuperPlayer extends ITimeout {
+public interface SuperPlayer extends TimeoutUtil.Listener {
     /**
      * 开始播放
      */

+ 16 - 7
kit/src/main/java/com/tencent/liteav/demo/superplayer/player/SuperPlayerImpl.java

@@ -9,6 +9,8 @@ import com.tencent.liteav.demo.superplayer.SuperPlayerCode;
 import com.tencent.liteav.demo.superplayer.SuperPlayerDef;
 import com.tencent.liteav.demo.superplayer.SuperPlayerGlobalConfig;
 import com.tencent.liteav.demo.superplayer.SuperPlayerModel;
+import com.tencent.liteav.demo.superplayer.util.HistoryUtil;
+import com.tencent.liteav.demo.superplayer.util.PlayerUtil;
 import com.tencent.rtmp.ITXVodPlayListener;
 import com.tencent.rtmp.TXLiveConstants;
 import com.tencent.rtmp.TXVodPlayConfig;
@@ -94,7 +96,7 @@ public class SuperPlayerImpl implements SuperPlayer, ITXVodPlayListener {
             case TXLiveConstants.PLAY_EVT_PLAY_PROGRESS:
                 int progress = param.getInt(TXLiveConstants.EVT_PLAY_PROGRESS_MS);
                 int duration = param.getInt(TXLiveConstants.EVT_PLAY_DURATION_MS);
-                mCurrent = progress/1000;
+                mCurrent = progress / 1000;
 
                 if (duration != 0) {
                     updatePlayProgress(progress / 1000, duration / 1000);
@@ -236,24 +238,28 @@ public class SuperPlayerImpl implements SuperPlayer, ITXVodPlayListener {
                 mObserver.onPlayPrepare();
                 break;
             case PLAYING:
+                PlayerUtil.INSTANCE.sendNewTimer(PlayerUtil.getSTATE_PLAY());
                 mObserver.onPlayBegin(getPlayName());
                 break;
             case PAUSE:
+                PlayerUtil.INSTANCE.sendNewTimer(PlayerUtil.getSTATE_PAUSE());
                 mObserver.onPlayPause();
                 break;
             case LOADING:
+                PlayerUtil.INSTANCE.sendNewTimer(PlayerUtil.getSTATE_LOADING());
                 mObserver.onPlayLoading();
                 break;
             case END:
+                PlayerUtil.INSTANCE.sendNewTimer(PlayerUtil.getSTATE_STOP());
                 mObserver.onPlayStop();
                 break;
         }
 
         switch (playState) {
             case PLAYING:
-                if(isStartBySeek){
+                if (isStartBySeek) {
                     isStartBySeek = false;
-                }else{
+                } else {
                     mStartPoint = mCurrent;
                 }
                 break;
@@ -261,7 +267,7 @@ public class SuperPlayerImpl implements SuperPlayer, ITXVodPlayListener {
             case END:
                 mEndPoint = mCurrent;
                 if (mCurrentModel != null) {
-                    mObserver.onOnceHistory(mStartPoint, mEndPoint, mCurrentModel.sectionId);
+                    HistoryUtil.INSTANCE.sendHistory(mStartPoint, mEndPoint, mCurrentModel.sectionId);
                 }
                 break;
         }
@@ -331,20 +337,19 @@ public class SuperPlayerImpl implements SuperPlayer, ITXVodPlayListener {
     }
 
     @Override
-    public void setTimeout(boolean timeout) {
+    public void onTimeout(boolean timeout) {
         this.mTimeout = timeout;
 
         if (timeout) {
             if (mCurrentPlayState == SuperPlayerDef.PlayerState.PLAYING || mCurrentPlayState == SuperPlayerDef.PlayerState.LOADING) {
                 isPauseByTimeout = true;
                 pause();
-
             }
         } else {
             mStartPoint = mEndPoint;
 
             if (mCurrentModel != null) {
-                mObserver.onOnceHistory(mStartPoint, mEndPoint, mCurrentModel.sectionId);
+                HistoryUtil.INSTANCE.sendHistory(mStartPoint, mEndPoint, mCurrentModel.sectionId);
             }
 
             if (isPauseByTimeout) {
@@ -379,6 +384,10 @@ public class SuperPlayerImpl implements SuperPlayer, ITXVodPlayListener {
 
     @Override
     public void seek(int position) {
+        if(mCurrentModel != null) {
+            HistoryUtil.INSTANCE.sendHistory(mStartPoint, mEndPoint, mCurrentModel.sectionId);
+        }
+
         mStartPoint = position;
         isStartBySeek = true;
 

+ 0 - 4
kit/src/main/java/com/tencent/liteav/demo/superplayer/player/SuperPlayerObserver.java

@@ -57,8 +57,4 @@ public abstract class SuperPlayerObserver {
     public void onRcvFirstIframe() {
 
     }
-
-    public void onOnceHistory(long start, long end, String sectionId) {
-
-    }
 }

+ 6 - 5
kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/AbsPlayer.java

@@ -7,11 +7,12 @@ import android.view.View;
 import android.widget.RelativeLayout;
 
 import com.tencent.liteav.demo.superplayer.SuperPlayerDef;
+import com.tencent.liteav.demo.superplayer.util.TimeoutUtil;
 
 /**
  * 播放器公共逻辑
  */
-public abstract class AbsPlayer extends RelativeLayout implements Player {
+public abstract class AbsPlayer extends RelativeLayout implements Player, TimeoutUtil.Listener {
     protected static final int HIDDEN_DELAY = 3000;
 
     protected Callback mControllerCallback; // 播放控制回调
@@ -41,16 +42,16 @@ public abstract class AbsPlayer extends RelativeLayout implements Player {
     }
 
     @Override
-    public void show() {
+    public void onTimeout(boolean timeout) {
 
     }
-
     @Override
-    public void hide() {
+    public void show() {
 
     }
 
-    public void timeOut() {
+    @Override
+    public void hide() {
 
     }
 

+ 3 - 14
kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/FullScreenPlayer.java

@@ -81,15 +81,6 @@ public class FullScreenPlayer extends AbsPlayer implements UnlockProgressView.On
 
     }
 
-    public void setTimeoutState(final boolean state) {
-        if (state) {
-            hide();
-            mTimeoutView.setVisibility(VISIBLE);
-        } else {
-            mTimeoutView.setVisibility(GONE);
-        }
-    }
-
     public void preparePlayVideo(SuperPlayerModel superPlayerModel) {
         if (!isDestroy) {
             Glide.with(getContext())
@@ -122,11 +113,9 @@ public class FullScreenPlayer extends AbsPlayer implements UnlockProgressView.On
     }
 
     @Override
-    public void timeOut() {
-        mIvLock.setVisibility(GONE);
-        mIvLockText.setVisibility(GONE);
-
-        mTimeoutView.setVisibility(VISIBLE);
+    public void onTimeout(boolean timeout) {
+        mTimeoutView.onTimeout(timeout);
+        mTimeoutView.setVisibility(timeout ? VISIBLE : GONE);
     }
 
     /**

+ 0 - 5
kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/Player.java

@@ -30,11 +30,6 @@ public interface Player {
      */
     void hide();
 
-    /**
-     * 定时时间到了
-     */
-    void timeOut();
-
     /**
      * 释放控件的内存
      */

+ 6 - 0
kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/TimeOutPlayer.java

@@ -72,6 +72,12 @@ public class TimeOutPlayer extends AbsPlayer implements View.OnClickListener {
         }
     }
 
+    @Override
+    public void onTimeout(boolean timeout) {
+        super.onTimeout(timeout);
+        this.setOnClickListener(timeout ? this : null);
+    }
+
     @Override
     public void hide() {
         super.hide();

+ 4 - 2
kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/WindowPlayer.java

@@ -286,8 +286,10 @@ public class WindowPlayer extends AbsPlayer implements View.OnClickListener,
         mLayoutBottom.setVisibility(View.GONE);
     }
 
-    public void setTimeoutState(final boolean state) {
-        mTimeoutView.setVisibility(state ? VISIBLE : GONE);
+    @Override
+    public void onTimeout(boolean timeout) {
+        mTimeoutView.onTimeout(timeout);
+        mTimeoutView.setVisibility(timeout ? VISIBLE : GONE);
     }
 
     public void toggleCoverView(boolean isVisible) {

+ 0 - 9
kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/UnlockProgressView.java

@@ -6,22 +6,13 @@ import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.PathMeasure;
-import android.graphics.RectF;
-import android.os.CountDownTimer;
 import android.util.AttributeSet;
-import android.util.Log;
-import android.view.GestureDetector;
 import android.view.MotionEvent;
-import android.widget.ImageView;
 
 import androidx.annotation.Nullable;
 
 import com.tencent.liteav.demo.superplayer.util.TimerUtil;
 
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.atomic.AtomicInteger;
-
 public class UnlockProgressView extends androidx.appcompat.widget.AppCompatImageView {
     public interface OnUnlockListener {
         void startUnlock();

+ 384 - 0
kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/WheelView.java

@@ -0,0 +1,384 @@
+package com.tencent.liteav.demo.superplayer.ui.view;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Author: wangjie
+ * Email: tiantian.china.2@gmail.com
+ * Date: 7/1/14.
+ */
+public class WheelView extends ScrollView {
+    public static final String TAG = WheelView.class.getSimpleName();
+
+    public static class OnWheelViewListener {
+        public void onSelected(int selectedIndex, String item) {
+        }
+    }
+
+
+    private Context context;
+//    private ScrollView scrollView;
+
+    private LinearLayout views;
+
+    public WheelView(Context context) {
+        super(context);
+        init(context);
+    }
+
+    public WheelView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context);
+    }
+
+    public WheelView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init(context);
+    }
+
+    //    String[] items;
+    List<String> items;
+
+    private List<String> getItems() {
+        return items;
+    }
+
+    public void setItems(List<String> list) {
+        if (null == items) {
+            items = new ArrayList<String>();
+        }
+        items.clear();
+        items.addAll(list);
+
+        // 前面和后面补全
+        for (int i = 0; i < offset; i++) {
+            items.add(0, "");
+            items.add("");
+        }
+
+        initData();
+
+    }
+
+
+    public static final int OFF_SET_DEFAULT = 1;
+    int offset = OFF_SET_DEFAULT; // 偏移量(需要在最前面和最后面补全)
+
+    public int getOffset() {
+        return offset;
+    }
+
+    public void setOffset(int offset) {
+        this.offset = offset;
+    }
+
+    int displayItemCount; // 每页显示的数量
+
+    int selectedIndex = 1;
+
+
+    private void init(Context context) {
+        this.context = context;
+        Log.d(TAG, "parent: " + this.getParent());
+        this.setVerticalScrollBarEnabled(false);
+
+        views = new LinearLayout(context);
+        views.setOrientation(LinearLayout.VERTICAL);
+        this.addView(views);
+
+        scrollerTask = new Runnable() {
+
+            public void run() {
+
+                int newY = getScrollY();
+                if (initialY - newY == 0) { // stopped
+                    final int remainder = initialY % itemHeight;
+                    final int divided = initialY / itemHeight;
+                    if (remainder == 0) {
+                        selectedIndex = divided + offset;
+
+                        onSeletedCallBack();
+                    } else {
+                        if (remainder > itemHeight / 2) {
+                            WheelView.this.post(new Runnable() {
+                                @Override
+                                public void run() {
+                                    WheelView.this.smoothScrollTo(0, initialY - remainder + itemHeight);
+                                    selectedIndex = divided + offset + 1;
+                                    onSeletedCallBack();
+                                }
+                            });
+                        } else {
+                            WheelView.this.post(new Runnable() {
+                                @Override
+                                public void run() {
+                                    WheelView.this.smoothScrollTo(0, initialY - remainder);
+                                    selectedIndex = divided + offset;
+                                    onSeletedCallBack();
+                                }
+                            });
+                        }
+
+
+                    }
+
+
+                } else {
+                    initialY = getScrollY();
+                    WheelView.this.postDelayed(scrollerTask, newCheck);
+                }
+            }
+        };
+
+
+    }
+
+    int initialY;
+
+    Runnable scrollerTask;
+    int newCheck = 50;
+
+    public void startScrollerTask() {
+
+        initialY = getScrollY();
+        this.postDelayed(scrollerTask, newCheck);
+    }
+
+    private void initData() {
+        displayItemCount = offset * 2 + 1;
+
+        for (String item : items) {
+            views.addView(createView(item));
+        }
+
+        refreshItemView(0);
+    }
+
+    int itemHeight = 0;
+
+    private TextView createView(String item) {
+        TextView tv = new TextView(context);
+        tv.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+        tv.setSingleLine(true);
+        tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 15);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            tv.setLineHeight(21);
+        } else {
+            tv.setHeight(dip2px(21));
+        }
+        tv.setText(item);
+        tv.setGravity(Gravity.CENTER);
+        int padding = dip2px(5);
+        tv.setPadding(padding, padding, padding, padding);
+        if (0 == itemHeight) {
+            itemHeight = getViewMeasuredHeight(tv);
+            Log.d(TAG, "itemHeight: " + itemHeight);
+            views.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, itemHeight * displayItemCount));
+            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) this.getLayoutParams();
+            this.setLayoutParams(new LinearLayout.LayoutParams(lp.width, itemHeight * displayItemCount));
+        }
+        return tv;
+    }
+
+
+    @Override
+    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        super.onScrollChanged(l, t, oldl, oldt);
+        refreshItemView(t);
+
+        if (t > oldt) {
+            scrollDirection = SCROLL_DIRECTION_DOWN;
+        } else {
+            scrollDirection = SCROLL_DIRECTION_UP;
+        }
+    }
+
+    private void refreshItemView(int y) {
+        int position = y / itemHeight + offset;
+        int remainder = y % itemHeight;
+        int divided = y / itemHeight;
+
+        if (remainder == 0) {
+            position = divided + offset;
+        } else {
+            if (remainder > itemHeight / 2) {
+                position = divided + offset + 1;
+            }
+        }
+
+        int childSize = views.getChildCount();
+        for (int i = 0; i < childSize; i++) {
+            TextView itemView = (TextView) views.getChildAt(i);
+            if (null == itemView) {
+                return;
+            }
+            if (position == i) {
+                itemView.setTextColor(Color.parseColor("#FF333333"));
+            } else {
+                itemView.setTextColor(Color.parseColor("#FF999999"));
+            }
+        }
+    }
+
+    /**
+     * 获取选中区域的边界
+     */
+    int[] selectedAreaBorder;
+
+    private int[] obtainSelectedAreaBorder() {
+        if (null == selectedAreaBorder) {
+            selectedAreaBorder = new int[2];
+            selectedAreaBorder[0] = itemHeight * offset;
+            selectedAreaBorder[1] = itemHeight * (offset + 1);
+        }
+        return selectedAreaBorder;
+    }
+
+
+    private int scrollDirection = -1;
+    private static final int SCROLL_DIRECTION_UP = 0;
+    private static final int SCROLL_DIRECTION_DOWN = 1;
+
+    Paint paint;
+    int viewWidth;
+
+    @Override
+    public void setBackground(Drawable background) {
+
+        if (viewWidth == 0) {
+            viewWidth = getWidth();
+            Log.d(TAG, "viewWidth: " + viewWidth);
+        }
+
+        if (null == paint) {
+            paint = new Paint();
+            paint.setColor(Color.parseColor("#FF0B57C7"));
+            paint.setStrokeWidth(dip2px(1f));
+        }
+
+        background = new Drawable() {
+            @Override
+            public void draw(Canvas canvas) {
+                canvas.drawLine(0, obtainSelectedAreaBorder()[0], viewWidth, obtainSelectedAreaBorder()[0], paint);
+                canvas.drawLine(0, obtainSelectedAreaBorder()[1], viewWidth, obtainSelectedAreaBorder()[1], paint);
+            }
+
+            @Override
+            public void setAlpha(int alpha) {
+
+            }
+
+            @Override
+            public void setColorFilter(ColorFilter cf) {
+
+            }
+
+            @Override
+            public int getOpacity() {
+                return PixelFormat.UNKNOWN;
+            }
+        };
+
+
+        super.setBackground(background);
+
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        Log.d(TAG, "w: " + w + ", h: " + h + ", oldw: " + oldw + ", oldh: " + oldh);
+        viewWidth = w;
+        setBackground(null);
+    }
+
+    /**
+     * 选中回调
+     */
+    private void onSeletedCallBack() {
+        if (null != onWheelViewListener) {
+            onWheelViewListener.onSelected(selectedIndex, items.get(selectedIndex));
+        }
+
+    }
+
+    public void setSeletion(int position) {
+        final int p = position;
+        selectedIndex = p + offset;
+        this.post(new Runnable() {
+            @Override
+            public void run() {
+                WheelView.this.smoothScrollTo(0, p * itemHeight);
+            }
+        });
+
+    }
+
+    public String getSeletedItem() {
+        return items.get(selectedIndex);
+    }
+
+    public int getSeletedIndex() {
+        return selectedIndex - offset;
+    }
+
+
+    @Override
+    public void fling(int velocityY) {
+        super.fling(velocityY / 3);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_UP) {
+
+            startScrollerTask();
+        }
+        return super.onTouchEvent(ev);
+    }
+
+    private OnWheelViewListener onWheelViewListener;
+
+    public OnWheelViewListener getOnWheelViewListener() {
+        return onWheelViewListener;
+    }
+
+    public void setOnWheelViewListener(OnWheelViewListener onWheelViewListener) {
+        this.onWheelViewListener = onWheelViewListener;
+    }
+
+    private int dip2px(float dpValue) {
+        final float scale = getContext().getResources().getDisplayMetrics().density;
+        return (int) (dpValue * scale + 0.5f);
+    }
+
+    private int getViewMeasuredHeight(View view) {
+        int width = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+        int expandSpec = View.MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, View.MeasureSpec.AT_MOST);
+        view.measure(width, expandSpec);
+        return view.getMeasuredHeight();
+    }
+
+}
+

+ 45 - 48
kit/src/main/java/com/tencent/liteav/demo/superplayer/util/CountDownUtil.java

@@ -1,5 +1,6 @@
 package com.tencent.liteav.demo.superplayer.util;
 
+import java.util.ArrayList;
 import java.util.Timer;
 import java.util.TimerTask;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -11,84 +12,80 @@ public class CountDownUtil {
         void onTimeout(CountDownUtil util);
     }
 
-    public static CountDownUtil build(long period, long maxCount, CountDownUtil.OnTickListener listener) {
-        return new CountDownUtil(period, maxCount, listener);
+    private static final CountDownUtil INSTANCE = new CountDownUtil();
+
+    public static CountDownUtil getInstance() {
+        return INSTANCE;
     }
 
-    public static final int STATE_INIT = 0;
-    public static final int STATE_RUNNING = 0;
-    public static final int STATE_PAUSE = 0;
-    public static final int STATE_CANCEL = 0;
+    public static void addListener(OnTickListener listener) {
+        INSTANCE.listeners.add(listener);
+    }
 
-    private int state = STATE_INIT;
+    public static void removeListener(OnTickListener listener) {
+        INSTANCE.listeners.remove(listener);
+    }
 
     // 周期时间
-    private final long period;
+    private static final long period = 1000;
 
     // 定时数
-    private final long maxCount;
+    private long maxCount = Integer.MAX_VALUE;
 
     // 回调监听对象
-    private final CountDownUtil.OnTickListener listener;
+    private final ArrayList<CountDownUtil.OnTickListener> listeners = new ArrayList<>();
 
     // 计时器
-    private final Timer timer = new Timer();
-
-    // 计时任务
-    private TimerTask task;
+    private Timer timer;
 
     // 计数器
     private final AtomicLong count = new AtomicLong(0L);
 
-    private CountDownUtil(long period, long maxCount, CountDownUtil.OnTickListener listener) {
-        this.period = period;
-        this.maxCount = maxCount;
-        this.listener = listener;
-    }
-
-    public void startOrResume() {
-        if (STATE_INIT == state) {
-            state = STATE_RUNNING;
-            count.set(0);
-            setupTask();
-        }else{
-            if (STATE_PAUSE == state) {
-                state = STATE_RUNNING;
-                setupTask();
-            }
-        }
-
+    private CountDownUtil() {
     }
 
-    public void pause() {
-        if (STATE_RUNNING == state) {
-            state = STATE_PAUSE;
 
-            if (task != null) {
-                task.cancel();
-                timer.purge();
-                task = null;
-            }
-        }
-    }
-
-    public void cancel() {
-        state = STATE_CANCEL;
-        timer.cancel();
+    public void startOrResume() {
+        pause();
+        setupTask();
     }
 
     private void setupTask() {
-        task = new TimerTask() {
+        timer = new Timer();
+        // 计时任务
+        TimerTask task = new TimerTask() {
             @Override
             public void run() {
                 count.addAndGet(1);
 
                 if (count.get() >= maxCount) {
-                    listener.onTimeout(CountDownUtil.this);
+                    pause();
+
+                    for (OnTickListener listener : listeners) {
+                        listener.onTimeout(CountDownUtil.this);
+                    }
                 }
             }
         };
 
         timer.schedule(task, 0, period);
     }
+
+    public void pause() {
+        if (timer != null) {
+            timer.cancel();
+            timer = null;
+        }
+    }
+
+    public void reset(long maxCount, Boolean autoStart) {
+        this.maxCount = maxCount;
+        pause();
+        count.set(0);
+        if (autoStart) startOrResume();
+    }
+
+    public Long getCount() {
+        return count.get();
+    }
 }

+ 1 - 7
kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/database/PlayerDatabase.kt

@@ -14,7 +14,7 @@ import com.tencent.liteav.demo.superplayer.database.entity.CountDown
 import com.tencent.liteav.demo.superplayer.database.entity.History
 
 @Database(
-    version = 6,
+    version = 1,
     entities = [History::class, CountDown::class],
     exportSchema = true,
 )
@@ -33,12 +33,6 @@ abstract class PlayerDatabase : RoomDatabase() {
                     context.applicationContext,
                     PlayerDatabase::class.java,
                     "player_database"
-                ).addMigrations(
-                    migration1_2,
-                    migration2_3,
-                    migration3_4,
-                    migration4_5,
-                    migration5_6,
                 ).build()
                 INSTANCE = instance
                 // return instance

+ 7 - 4
kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/database/dao/CountDownDao.kt

@@ -8,15 +8,18 @@ import androidx.room.Query
 import com.tencent.liteav.demo.superplayer.database.entity.CountDown
 import kotlinx.coroutines.flow.Flow
 
+/**
+ * 倒计时相关的数据库操作
+ */
 @Dao
 interface CountDownDao {
 
-    @Query("SELECT * FROM countdown WHERE course_id = :courseId LIMIT 1")
-    fun getCountDownByCourseId(courseId: String): Flow<CountDown?>
+    @Query("SELECT * FROM countdown WHERE id = :courseId LIMIT 1")
+    fun queryCountDown(courseId: String): CountDown?
 
     @Insert(onConflict = REPLACE)
     fun insertCountDown(countDown: CountDown)
 
-    @Query("DELETE FROM countdown WHERE course_id = :courseId")
-    fun deleteCountDown(courseId: String)
+    @Query("DELETE FROM countdown WHERE id = :courseId")
+    fun deleteCountDown(courseId: String): Int
 }

+ 23 - 5
kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/database/entity/CountDown.kt

@@ -6,11 +6,11 @@ import androidx.room.PrimaryKey
 
 @Entity
 data class CountDown(
-    @PrimaryKey(autoGenerate = true)
-    val id: Long? = null,
-
-    @ColumnInfo(name = "course_id")
-    val courseId: String,
+    /**
+     * 使用course id作为主键
+     */
+    @PrimaryKey
+    val id: String,
 
     /**
      * 定时类型
@@ -29,12 +29,30 @@ data class CountDown(
     @ColumnInfo(name = "value")
     val value: Int,
 
+    @ColumnInfo(name = "rest")
+    val rest: Int,
+
     /**
      * 作为count down的唯一的特征
      */
     @ColumnInfo(name = "datetime")
     val datetime: String? = null,
 ) {
+    fun copyWith(
+        type: Int? = null,
+        value: Int? = null,
+        rest: Int? = null,
+        datetime: String? = null,
+    ): CountDown {
+        return CountDown(
+            id = this.id,
+            type = type ?: this.type,
+            value = value ?: this.value,
+            rest = rest ?: this.rest,
+            datetime = datetime ?: this.datetime,
+        )
+    }
+
     companion object {
         @JvmStatic
         public val TYPE_EPISODE = 1;

+ 17 - 12
kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/database/repo/PlayerRepository.kt

@@ -17,25 +17,30 @@ public class PlayerRepository(
         val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.SIMPLIFIED_CHINESE)
     }
 
-    suspend fun insertHistory(history: History) {
-        historyDao.insertHistory(history)
-    }
-
-    suspend fun insertCountDown(countDown: CountDown) {
-        Log.d("ZZZ", "insertCountDown")
+    /**
+     * 定时相关的数据库操作
+     */
+    fun insertCountDown(countDown: CountDown) {
         countDownDao.insertCountDown(countDown)
     }
 
-    suspend fun deleteCountDown(courseId: String) {
-        Log.d("ZZZ", "deleteCountDown")
+    fun deleteCountDown(courseId: String) {
         countDownDao.deleteCountDown(courseId)
     }
 
-    suspend fun findHasCountDown(courseId: String): Flow<CountDown?> {
-        return countDownDao.getCountDownByCourseId(courseId)
+    fun findHasCountDown(courseId: String): CountDown? {
+        return countDownDao.queryCountDown(courseId)
+    }
+
+
+    /**
+     * 历史记录有关的操作
+     */
+    fun insertHistory(history: History) {
+        historyDao.insertHistory(history)
     }
 
-    suspend fun queryTodayEpisodeHistory(
+    fun queryTodayEpisodeHistory(
         courseId: String,
         countDown: String,
     ): Int {
@@ -43,7 +48,7 @@ public class PlayerRepository(
         return historyDao.queryTodayEpisodeHistory(courseId, countDown, date)
     }
 
-    suspend fun queryTodayDurationHistory(
+    fun queryTodayDurationHistory(
         courseId: String,
         countDown: String,
     ): List<History> {

+ 23 - 0
kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/util/HistoryUtil.kt

@@ -0,0 +1,23 @@
+package com.tencent.liteav.demo.superplayer.util
+
+object HistoryUtil {
+    interface Listener {
+        fun onHistory(start: Long, end: Long, sectionId: String)
+    }
+
+    private val listeners: MutableList<Listener> = mutableListOf()
+
+    public fun addListener(listener: Listener) {
+        listeners.add(listener)
+    }
+
+    public fun removeListener(listener: Listener) {
+        listeners.remove(listener)
+    }
+
+    public fun sendHistory(start: Long, end: Long, sectionId: String) {
+        for (listener in listeners) {
+            listener.onHistory(start, end, sectionId)
+        }
+    }
+}

+ 64 - 0
kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/util/PlayerUtil.kt

@@ -0,0 +1,64 @@
+package com.tencent.liteav.demo.superplayer.util
+
+object PlayerUtil {
+    @JvmStatic
+    val STATE_PLAY = 0
+
+    @JvmStatic
+    val STATE_RESUME = 1
+
+    @JvmStatic
+    val STATE_PAUSE = 2
+
+    @JvmStatic
+    val STATE_STOP = 3
+
+    @JvmStatic
+    val STATE_LOADING = 4
+
+    interface Listener {
+        fun onStatePlay()
+
+        fun onStateResume()
+
+        fun onStatePause()
+
+        fun onStateStop()
+
+        fun onStateLoading()
+    }
+
+    private val listeners: MutableList<Listener> = mutableListOf()
+
+    public fun addListener(listener: Listener) {
+        listeners.add(listener)
+    }
+
+    public fun removeListener(listener: Listener) {
+        listeners.remove(listener)
+    }
+
+    public fun sendNewTimer(state: Int) {
+        when (state) {
+            STATE_PLAY -> for (listener in listeners) {
+                listener.onStatePlay()
+            }
+            STATE_RESUME -> for (listener in listeners) {
+                listener.onStateResume()
+            }
+            STATE_PAUSE -> for (listener in listeners) {
+                listener.onStatePause()
+            }
+            STATE_STOP -> for (listener in listeners) {
+                listener.onStateStop()
+            }
+            STATE_LOADING -> for (listener in listeners) {
+                listener.onStateLoading()
+            }
+            else -> {
+
+            }
+        }
+
+    }
+}

+ 23 - 0
kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/util/TimeoutUtil.kt

@@ -0,0 +1,23 @@
+package com.tencent.liteav.demo.superplayer.util
+
+object TimeoutUtil {
+    interface Listener {
+        fun onTimeout(timeout: Boolean)
+    }
+
+    private val listeners: MutableList<Listener> = mutableListOf()
+
+    fun addListener(listener: Listener) {
+        listeners.add(listener)
+    }
+
+    fun removeListener(listener: Listener) {
+        listeners.remove(listener)
+    }
+
+    fun sendTimeout(timeout: Boolean) {
+        for (listener in listeners) {
+            listener.onTimeout(timeout)
+        }
+    }
+}

+ 23 - 0
kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/util/TimersUtil.kt

@@ -0,0 +1,23 @@
+package com.tencent.liteav.demo.superplayer.util
+
+object TimersUtil {
+    interface Listener {
+        fun onNewTimer(type: Int, value: Int)
+    }
+
+    private val listeners: MutableList<Listener> = mutableListOf()
+
+    fun addListener(listener: Listener) {
+        listeners.add(listener)
+    }
+
+    fun removeListener(listener: Listener) {
+        listeners.remove(listener)
+    }
+
+    fun sendNewTimer(type: Int, value: Int) {
+        for (listener in listeners) {
+            listener.onNewTimer(type, value)
+        }
+    }
+}

+ 13 - 6
ui/src/main/AndroidManifest.xml

@@ -13,20 +13,27 @@
         tools:replace="android:allowBackup,android:label">
 
         <activity
-            android:name=".PlayerActivity"
-            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:name=".MainActivity"
             android:exported="true"
-            android:label="@string/superplayer_app_name"
-            android:launchMode="singleTask"
-            android:screenOrientation="landscape"
             android:theme="@style/PlayerTheme"
-            android:windowSoftInputMode="stateHidden">
+            >
+
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
 
+        <activity
+            android:name=".PlayerActivity"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:exported="true"
+            android:label="@string/superplayer_app_name"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:theme="@style/PlayerTheme"
+            android:windowSoftInputMode="stateHidden"></activity>
+
         <activity
             android:name=".PlayerMenu"
             android:launchMode="singleTop"

+ 0 - 38
ui/src/main/java/com/tencent/liteav/demo/player/util/PlayerTimerUtil.java

@@ -1,38 +0,0 @@
-package com.tencent.liteav.demo.player.util;
-
-import java.util.ArrayList;
-
-public class PlayerTimerUtil {
-    public interface Listener {
-        void onListen(int type, int value);
-    }
-
-    private ArrayList<Listener> listeners = new ArrayList<>();
-
-    private PlayerTimerUtil() {
-    }
-
-    private static final PlayerTimerUtil util = new PlayerTimerUtil();
-
-
-    public static PlayerTimerUtil getInstance() {
-        return util;
-    }
-
-    public static void addListener(Listener listener) {
-        getInstance().listeners.add(listener);
-    }
-
-    public static void removeListener(Listener listener) {
-        getInstance().listeners.remove(listener);
-    }
-
-    public static void setTimer(int type, int num) {
-        ArrayList<Listener> listeners = getInstance().listeners;
-        for (int i = 0; i < listeners.size(); i++) {
-            if (listeners.get(i) != null) {
-                listeners.get(i).onListen(type, num);
-            }
-        }
-    }
-}

+ 45 - 0
ui/src/main/kotlin/com/tencent/liteav/demo/player/MainActivity.kt

@@ -0,0 +1,45 @@
+package com.tencent.liteav.demo.player
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.LinearLayout
+import androidx.appcompat.app.AppCompatActivity
+
+class MainActivity : AppCompatActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(getRootView())
+    }
+
+    private fun getRootView(): View {
+        val linearLayout = LinearLayout(this@MainActivity).apply {
+            val button = Button(this@MainActivity).apply {
+                setOnClickListener {
+                    val intent = Intent(this@MainActivity, PlayerActivity::class.java)
+                    startActivity(intent)
+                }
+
+                text = "进入播放器"
+                textSize = 24f
+
+                layoutParams = LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.WRAP_CONTENT,
+                    LinearLayout.LayoutParams.WRAP_CONTENT,
+                ).apply {
+                    gravity = Gravity.CENTER
+                }
+            }
+            layoutParams = ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT
+            )
+            addView(button)
+        }
+
+        return linearLayout
+    }
+}

+ 31 - 28
ui/src/main/kotlin/com/tencent/liteav/demo/player/PlayerActivity.kt

@@ -14,7 +14,6 @@ import com.tencent.liteav.demo.player.databinding.ActivityPlayerBinding
 import com.tencent.liteav.demo.player.ui.PlayerListAdapter
 import com.tencent.liteav.demo.player.ui.PlayerListDecoration
 import com.tencent.liteav.demo.player.util.PlayerModelProvider
-import com.tencent.liteav.demo.player.util.PlayerTimerUtil
 import com.tencent.liteav.demo.player.viewmodel.PlayerViewModel
 import com.tencent.liteav.demo.player.viewmodel.PlayerViewModelFactory
 import com.tencent.liteav.demo.superplayer.SuperPlayerDef
@@ -22,13 +21,12 @@ import com.tencent.liteav.demo.superplayer.SuperPlayerGlobalConfig
 import com.tencent.liteav.demo.superplayer.SuperPlayerModel
 import com.tencent.liteav.demo.superplayer.SuperPlayerView.OnSuperPlayerViewCallback
 import com.tencent.liteav.demo.superplayer.database.PlayerDatabaseProvider
-import com.tencent.liteav.demo.superplayer.database.entity.History
+import com.tencent.liteav.demo.superplayer.util.TimeoutUtil
+import com.tencent.liteav.demo.superplayer.util.TimersUtil
 import com.tencent.rtmp.TXLiveConstants
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.MainScope
 import kotlinx.coroutines.launch
-import java.text.SimpleDateFormat
-import java.util.*
 
 /**
  * Created by liyuejiao on 2018/7/3.
@@ -38,7 +36,7 @@ class PlayerActivity : AppCompatActivity(),
     View.OnClickListener,
     OnSuperPlayerViewCallback,
     PlayerListAdapter.OnItemClickListener,
-    CoroutineScope by MainScope() {
+    CoroutineScope by MainScope(), TimeoutUtil.Listener {
 
     private var _viewBinding: ActivityPlayerBinding? = null
     private val viewBinding get() = _viewBinding!!
@@ -82,11 +80,6 @@ class PlayerActivity : AppCompatActivity(),
         viewModel.toastStr.observe(this) {
             Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
         }
-
-        viewModel.timeout.observe(this) {
-            mSuperPlayerView.setTimeOut(it)
-        }
-
     }
 
 
@@ -142,20 +135,9 @@ class PlayerActivity : AppCompatActivity(),
         prefs.renderMode = TXLiveConstants.RENDER_MODE_ADJUST_RESOLUTION
     }
 
-    @SuppressWarnings("deprecation")
-    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
-        super.onActivityResult(requestCode, resultCode, data)
-        when (requestCode) {
-            0 -> {
-                if (resultCode == RESULT_OK) {
-                    PlayerTimerUtil.setTimer(0, 0)
-                }
-            }
-        }
-    }
-
     override fun onResume() {
         super.onResume()
+        TimeoutUtil.addListener(this)
         if (mSuperPlayerView.playerState == SuperPlayerDef.PlayerState.PLAYING
             || mSuperPlayerView.playerState == SuperPlayerDef.PlayerState.PAUSE
         ) {
@@ -169,14 +151,21 @@ class PlayerActivity : AppCompatActivity(),
 
     override fun onPause() {
         super.onPause()
+        TimeoutUtil.removeListener(this)
         Log.i(TAG, "onPause state :" + mSuperPlayerView.playerState)
         mIsManualPause = mSuperPlayerView.playerState == SuperPlayerDef.PlayerState.PAUSE
         mSuperPlayerView.onPause()
         mSuperPlayerView.setNeedToPause(true)
     }
 
+    override fun onStop() {
+        super.onStop()
+        viewModel.onStop()
+    }
+
     override fun onDestroy() {
         super.onDestroy()
+
         mSuperPlayerView.release()
         mSuperPlayerView.resetPlayer()
         _viewBinding = null
@@ -186,6 +175,15 @@ class PlayerActivity : AppCompatActivity(),
         playVideoModel(index)
     }
 
+    override fun onTimeout(timeout: Boolean) {
+        if (!timeout) {
+            if (mSuperPlayerView.playingVideoModel == null) {
+                playVideoModel(10)
+                mSuperPlayerView.setStartTime(20.0)
+            }
+        }
+    }
+
     private fun playVideoModel(index: Int) {
         mSuperPlayerView.maybePlayIndexModel(index)
     }
@@ -248,10 +246,10 @@ class PlayerActivity : AppCompatActivity(),
     }
 
     override fun onPlayPrepare(index: Int, model: SuperPlayerModel) {
-        launch {
-            viewModel.checkCanPlay {
-                mSuperPlayerView.playIndexModel(index)
-            }
+        if (viewModel.checkCanPlay()) {
+            mSuperPlayerView.playIndexModel(index)
+        } else {
+            Toast.makeText(this, "定时时间到了~", Toast.LENGTH_SHORT).show()
         }
     }
 
@@ -272,8 +270,13 @@ class PlayerActivity : AppCompatActivity(),
         }
     }
 
-    override fun onOnceHistory(start: Long, end: Long, sectionId: String) {
-        viewModel.saveHistory(start, end, sectionId)
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+        if (requestCode == 0) {
+            if (resultCode == RESULT_OK) {
+                TimersUtil.sendNewTimer(0, 0)
+            }
+        }
     }
 
     override fun onResumePlay() {

+ 19 - 0
ui/src/main/kotlin/com/tencent/liteav/demo/player/PlayerWheel.kt

@@ -0,0 +1,19 @@
+package com.tencent.liteav.demo.player
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import com.tencent.liteav.demo.superplayer.ui.view.WheelView
+
+class PlayerWheel:AppCompatActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_wheel)
+        findViewById<WheelView>(R.id.layout_wheel).apply {
+            setItems(mutableListOf("a", "b", "c"))
+        }
+
+        findViewById<WheelView>(R.id.layout_wheel2).apply {
+            setItems(mutableListOf("d", "e", "f"))
+        }
+    }
+}

+ 9 - 9
ui/src/main/kotlin/com/tencent/liteav/demo/player/menu/PlayerTimer.kt

@@ -4,7 +4,7 @@ import android.os.Bundle
 import android.view.View
 import androidx.appcompat.app.AppCompatActivity
 import com.tencent.liteav.demo.player.databinding.FragmentTimerBinding
-import com.tencent.liteav.demo.player.util.PlayerTimerUtil
+import com.tencent.liteav.demo.superplayer.util.TimersUtil
 
 class PlayerTimer : AppCompatActivity(), View.OnClickListener {
     private var _viewBinding: FragmentTimerBinding? = null
@@ -47,28 +47,28 @@ class PlayerTimer : AppCompatActivity(), View.OnClickListener {
                 finish()
             }
             none.id -> {
-                PlayerTimerUtil.setTimer(0, 0)
+                TimersUtil.sendNewTimer(0, 0)
             }
             ep1.id -> {
-                PlayerTimerUtil.setTimer(1, 1)
+                TimersUtil.sendNewTimer(1, 1)
             }
             ep2.id -> {
-                PlayerTimerUtil.setTimer(1, 2)
+                TimersUtil.sendNewTimer(1, 2)
             }
             ep3.id -> {
-                PlayerTimerUtil.setTimer(1, 3)
+                TimersUtil.sendNewTimer(1, 3)
             }
             dt10.id -> {
-                PlayerTimerUtil.setTimer(2, 10)
+                TimersUtil.sendNewTimer(2, 10)
             }
             dt20.id -> {
-                PlayerTimerUtil.setTimer(2, 20)
+                TimersUtil.sendNewTimer(2, 20)
             }
             dt30.id -> {
-                PlayerTimerUtil.setTimer(2, 30)
+                TimersUtil.sendNewTimer(2, 30)
             }
             custom.id -> {
-                PlayerTimerUtil.setTimer(2, 100)
+                TimersUtil.sendNewTimer(2, 100)
             }
         }
 

+ 131 - 80
ui/src/main/kotlin/com/tencent/liteav/demo/player/viewmodel/PlayerViewModel.kt

@@ -6,15 +6,11 @@ import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.viewModelScope
-import com.tencent.liteav.demo.player.util.PlayerTimerUtil
-import com.tencent.liteav.demo.superplayer.SuperPlayerModel
 import com.tencent.liteav.demo.superplayer.database.entity.CountDown
 import com.tencent.liteav.demo.superplayer.database.entity.History
 import com.tencent.liteav.demo.superplayer.database.repo.PlayerRepository
-import com.tencent.liteav.demo.superplayer.util.CountDownUtil
+import com.tencent.liteav.demo.superplayer.util.*
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.async
-import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import java.text.SimpleDateFormat
@@ -22,34 +18,52 @@ import java.util.*
 
 class PlayerViewModel(
     private val courseId: String,
-    private val repository: PlayerRepository
-) : ViewModel(), PlayerTimerUtil.Listener, CountDownUtil.OnTickListener {
+    private val repository: PlayerRepository,
+) : ViewModel(),
+    TimersUtil.Listener,
+    CountDownUtil.OnTickListener,
+    HistoryUtil.Listener,
+    PlayerUtil.Listener {
+    companion object {
+         val TAG = PlayerViewModel::class.java.simpleName.toString()
+    }
 
     init {
-        PlayerTimerUtil.addListener(this)
+        TimersUtil.addListener(this)
+        HistoryUtil.addListener(this)
+        PlayerUtil.addListener(this)
+        CountDownUtil.addListener(this)
     }
 
-    private lateinit var countDown: CountDown
+    override fun onCleared() {
+        super.onCleared()
 
-    private var countDownUtil: CountDownUtil? = null
+        HistoryUtil.removeListener(this)
+        TimersUtil.removeListener(this)
+        PlayerUtil.removeListener(this)
+        CountDownUtil.removeListener(this)
+    }
 
-    val toastStr: MutableLiveData<String> = MutableLiveData()
+    private var isPlaying: Boolean = false
 
-    val timeout: MutableLiveData<Boolean> = MutableLiveData()
+    private lateinit var countDown: CountDown
+
+    val toastStr: MutableLiveData<String> = MutableLiveData()
 
-    fun loadConfig(context: Context) = viewModelScope.launch {
+    fun loadConfig(context: Context) = viewModelScope.launch(Dispatchers.IO) {
         val common = context.getSharedPreferences("player_timer", Context.MODE_PRIVATE)
         val defaultType = common.getInt("type", 0)
         val defaultValue = common.getInt("value", 0)
         val defaultDate = common.getString("datetime", "20220831000000")!!
 
         val default = CountDown(
-            courseId = courseId,
+            id = "global",
             type = defaultType,
             value = defaultValue,
+            rest = defaultValue,
             datetime = defaultDate,
         )
-        val special = repository.findHasCountDown(courseId).first()
+        val special = repository.findHasCountDown(courseId)
 
         if (special == null) {
             countDown = default
@@ -57,39 +71,42 @@ class PlayerViewModel(
             countDown = special
         }
 
-        setCountDown(countDown)
-    }
-
-    /* 保存对该门课程单独设置的定时 */
-    private fun saveCountDown(countDown: CountDown) = viewModelScope.launch(Dispatchers.IO) {
-        repository.deleteCountDown(courseId)
-        repository.insertCountDown(countDown)
+        setCountDown(countDown, false)
     }
 
     /* 开启定时器 */
-    private fun setCountDown(countDown: CountDown) {
-        countDownUtil?.cancel()
-        countDownUtil = null
-
-        checkTimeout(countDown)
-    }
+    private fun setCountDown(countDown: CountDown, forceSend: Boolean) {
+        if (countDown.type == CountDown.TYPE_DURATION) {
+            CountDownUtil.getInstance().reset(countDown.rest.toLong(), isPlaying)
+        } else {
+            CountDownUtil.getInstance().pause()
+        }
 
-    fun resumeTimer() {
-        countDownUtil?.startOrResume()
+        // 如果定时器已经到时间了 则直接进入锁定状态
+        if (!forceSend && countDown.type != 0 && countDown.rest <= 0) {
+            sendTimeout(true)
+        } else {
+            sendTimeout(false)
+        }
     }
 
-    fun pauseTimer() {
-        countDownUtil?.pause()
+    fun checkCanPlay(): Boolean {
+        return if (checkTimeout(countDown)) {
+            sendTimeout(false)
+            true
+        } else {
+            sendTimeout(true)
+            false
+        }
     }
 
-    /* 保存一条观看时间段的记录 */
-    fun saveHistory(start: Long, end: Long, sectionId: String) {
+    override fun onHistory(start: Long, end: Long, sectionId: String) {
         val history = History(
             start = start,
             end = end,
             courseId = courseId,
             sectionId = sectionId,
-            date = SimpleDateFormat("yyyy-MM-dd").format(Date()),
+            date = SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(Date()),
             countDown = countDown.datetime,
         )
 
@@ -98,91 +115,125 @@ class PlayerViewModel(
         }
     }
 
-    suspend fun checkCanPlay(whenCan: () -> Unit) = viewModelScope.launch(Dispatchers.IO) {
-        checkTimeout(countDown)
+    override fun onNewTimer(type: Int, value: Int) {
+        Log.d("PVM", "onListen: $type , $value")
 
-        withContext(Dispatchers.Main) {
-            whenCan.invoke()
+        var rest = value
+        if (type == CountDown.TYPE_EPISODE) {
+            rest = value - 1
         }
-    }
-
-    override fun onListen(type: Int, value: Int) {
-        Log.d("PVM", "onListen: $type , $value")
 
         countDown = CountDown(
-            courseId = courseId,
+            id = courseId,
             type = type,
             value = value,
-            datetime = SimpleDateFormat("yyyyMMddhhmmss").format(Date())
+            rest = rest,
+            datetime = SimpleDateFormat("yyyy-MM-dd").format(Date())
         )
 
         countDown.let {
-            setCountDown(it)
+            setCountDown(it, true)
             saveCountDown(it)
         }
     }
 
-    override fun onCleared() {
-        super.onCleared()
-        PlayerTimerUtil.removeListener(this)
-    }
 
     override fun onTimeout(util: CountDownUtil?) {
-        util?.cancel()
+        util?.pause()
         sendTimeout(true)
     }
 
-    private fun checkTimeout(countDown: CountDown) {
+
+    private fun checkTimeout(countDown: CountDown): Boolean {
         countDown.also {
-            viewModelScope.launch {
-                if (it.type == CountDown.TYPE_DURATION) {
-                    checkDurationTimeout()
-                } else if (it.type == CountDown.TYPE_EPISODE) {
-                    checkEpisodeTimeout()
-                } else {
-                    sendTimeout(false)
-                }
+            if (it.type == CountDown.TYPE_DURATION) {
+                return checkDurationTimeout()
+            } else if (it.type == CountDown.TYPE_EPISODE) {
+                return checkEpisodeTimeout()
+            } else {
+                return true
             }
+
         }
     }
 
-    private suspend fun checkEpisodeTimeout() = viewModelScope.launch(Dispatchers.IO) {
-        val history = repository.queryTodayEpisodeHistory(courseId, countDown.datetime!!)
-
-        if (history >= countDown.value) {
-            sendTimeout(true)
-        } else {
-            sendTimeout(false)
-        }
+    private fun checkEpisodeTimeout(): Boolean {
+        val rest = countDown.rest - 1
+        countDown = countDown.copyWith(rest = rest)
+        saveCountDown(countDown)
+        return countDown.rest >= 0
     }
 
-    private suspend fun checkDurationTimeout() = viewModelScope.launch(Dispatchers.IO) {
-        val history = repository.queryTodayDurationHistory(courseId, countDown.datetime!!)
 
-        val result = history.fold(0L) { r, h ->
-            r + (h.end - h.start)
-        }
+    private fun checkDurationTimeout(): Boolean {
+        val rest = countDown.rest - CountDownUtil.getInstance().count
+        countDown = countDown.copyWith(rest = rest.toInt())
+        setupTimer(countDown.rest.toLong())
+        saveCountDown(countDown)
+        return countDown.rest > 0
+    }
 
-        if (result >= countDown.value) {
-            sendTimeout(true)
-        } else {
-            sendTimeout(false)
-            setupTimer(countDown.value - result)
+    fun onStop() {
+        Log.d(TAG, "onExit")
+
+        if(countDown.type == CountDown.TYPE_DURATION) {
+            val rest = countDown.rest - CountDownUtil.getInstance().count
+            countDown = countDown.copyWith(rest = rest.toInt())
+            saveCountDown(countDown)
         }
     }
 
     private fun setupTimer(count: Long) {
         if (countDown.type == CountDown.TYPE_DURATION) {
-            countDownUtil = CountDownUtil.build(1000, count, this)
+            CountDownUtil.getInstance().reset(count, isPlaying)
         }
     }
 
+    /* 保存对该门课程单独设置的定时 */
+    private fun saveCountDown(countDown: CountDown) = viewModelScope.launch(Dispatchers.IO) {
+        repository.insertCountDown(countDown)
+    }
+
     private fun sendTimeout(state: Boolean) {
         viewModelScope.launch(Dispatchers.Main) {
-            timeout.value = state
+            TimeoutUtil.sendTimeout(state)
+        }
+    }
+
+    override fun onStatePlay() {
+        isPlaying = true
+
+        if (countDown.type == CountDown.TYPE_DURATION) {
+            CountDownUtil.getInstance().startOrResume()
+        }
+    }
+
+    override fun onStateResume() {
+        isPlaying = true
+
+        if (countDown.type == CountDown.TYPE_DURATION) {
+            CountDownUtil.getInstance().startOrResume()
         }
     }
 
+    override fun onStatePause() {
+        isPlaying = false
+
+        if (countDown.type == CountDown.TYPE_DURATION) {
+            (CountDownUtil.getInstance()).pause()
+        }
+    }
+
+    override fun onStateStop() {
+        isPlaying = false
+
+        if (countDown.type == CountDown.TYPE_DURATION) {
+            (CountDownUtil.getInstance()).pause()
+        }
+    }
+
+    override fun onStateLoading() {
+    }
 }
 
 class PlayerViewModelFactory(

+ 77 - 0
ui/src/main/res/layout/activity_wheel.xml

@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="273dp"
+        android:layout_height="209dp"
+        android:background="@android:color/white"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="24dp"
+            android:layout_marginBottom="7dp"
+            android:text="自定义停止播放时间"
+            android:textColor="#ff333333"
+            android:textSize="16sp"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/gl1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            app:layout_constraintGuide_begin="53dp" />
+
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/gl2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            app:layout_constraintGuide_end="61dp" />
+
+        <LinearLayout
+            android:layout_width="167dp"
+            android:layout_height="0dp"
+            app:layout_constraintBottom_toTopOf="@id/gl2"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/gl1">
+
+            <LinearLayout
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1">
+
+                <com.tencent.liteav.demo.superplayer.ui.view.WheelView
+                    android:id="@+id/layout_wheel"
+                    android:layout_width="62dp"
+                    android:layout_height="match_parent" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1">
+
+                <com.tencent.liteav.demo.superplayer.ui.view.WheelView
+                    android:id="@+id/layout_wheel2"
+                    android:layout_width="62dp"
+                    android:layout_height="match_parent" />
+
+            </LinearLayout>
+        </LinearLayout>
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>