zhaoyadi 2 éve
szülő
commit
5c1518ded8
26 módosított fájl, 609 hozzáadás és 188 törlés
  1. 9 3
      kit/schemas/com.tencent.liteav.demo.superplayer.database.PlayerDatabase/4.json
  2. 114 0
      kit/schemas/com.tencent.liteav.demo.superplayer.database.PlayerDatabase/5.json
  3. 114 0
      kit/schemas/com.tencent.liteav.demo.superplayer.database.PlayerDatabase/6.json
  4. 4 0
      kit/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerDef.java
  5. 2 0
      kit/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerModel.java
  6. 45 8
      kit/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerView.java
  7. 46 2
      kit/src/main/java/com/tencent/liteav/demo/superplayer/player/SuperPlayerImpl.java
  8. 7 1
      kit/src/main/java/com/tencent/liteav/demo/superplayer/player/SuperPlayerObserver.java
  9. 7 23
      kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/FullScreenPlayer.java
  10. 5 0
      kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/Player.java
  11. 7 16
      kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/TimeOutPlayer.java
  12. 0 1
      kit/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/WindowPlayer.java
  13. 25 36
      kit/src/main/java/com/tencent/liteav/demo/superplayer/util/CountDownUtil.java
  14. 1 1
      kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/PlayerApplication.java
  15. 20 6
      kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/database/PlayerDatabase.kt
  16. 4 0
      kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/database/dao/CountDownDao.kt
  17. 12 4
      kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/database/dao/HistoryDao.kt
  18. 15 18
      kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/database/entity/CountDown.kt
  19. 29 3
      kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/database/entity/History.kt
  20. 17 4
      kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/database/repo/PlayerRepository.kt
  21. 1 1
      ui/src/main/AndroidManifest.xml
  22. 6 1
      ui/src/main/java/com/tencent/liteav/demo/player/util/PlayerModelProvider.java
  23. 46 13
      ui/src/main/kotlin/com/tencent/liteav/demo/player/PlayerActivity.kt
  24. 7 3
      ui/src/main/kotlin/com/tencent/liteav/demo/player/PlayerVerify.kt
  25. 66 34
      ui/src/main/kotlin/com/tencent/liteav/demo/player/viewmodel/PlayerViewModel.kt
  26. 0 10
      ui/src/main/res/layout/activity_player.xml

+ 9 - 3
kit/schemas/com.tencent.liteav.demo.superplayer.database.PlayerDatabase/4.json

@@ -2,7 +2,7 @@
   "formatVersion": 1,
   "database": {
     "version": 4,
-    "identityHash": "1fbb5d3e0b17e0f275f418969aff1589",
+    "identityHash": "d24f46db55c3e1e232518d9d7969a99d",
     "entities": [
       {
         "tableName": "History",
@@ -56,7 +56,7 @@
       },
       {
         "tableName": "CountDown",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `course_id` TEXT NOT NULL, `type` INTEGER NOT NULL, `value` INTEGER NOT NULL)",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `course_id` TEXT NOT NULL, `type` INTEGER NOT NULL, `value` INTEGER NOT NULL, `ct_id` TEXT NOT NULL)",
         "fields": [
           {
             "fieldPath": "id",
@@ -81,6 +81,12 @@
             "columnName": "value",
             "affinity": "INTEGER",
             "notNull": true
+          },
+          {
+            "fieldPath": "countDown",
+            "columnName": "ct_id",
+            "affinity": "TEXT",
+            "notNull": true
           }
         ],
         "primaryKey": {
@@ -96,7 +102,7 @@
     "views": [],
     "setupQueries": [
       "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
-      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1fbb5d3e0b17e0f275f418969aff1589')"
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd24f46db55c3e1e232518d9d7969a99d')"
     ]
   }
 }

+ 114 - 0
kit/schemas/com.tencent.liteav.demo.superplayer.database.PlayerDatabase/5.json

@@ -0,0 +1,114 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 5,
+    "identityHash": "e20f17c1d821e7beddcd9674ef318830",
+    "entities": [
+      {
+        "tableName": "History",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `course_id` TEXT NOT NULL, `section_id` TEXT, `date` TEXT, `ct_id` TEXT)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "start",
+            "columnName": "start",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "end",
+            "columnName": "end",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "courseId",
+            "columnName": "course_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "sectionId",
+            "columnName": "section_id",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "countDown",
+            "columnName": "ct_id",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "CountDown",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `course_id` TEXT NOT NULL, `type` INTEGER NOT NULL, `value` INTEGER NOT NULL, `datetime` TEXT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "courseId",
+            "columnName": "course_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "value",
+            "columnName": "value",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "datetime",
+            "columnName": "datetime",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e20f17c1d821e7beddcd9674ef318830')"
+    ]
+  }
+}

+ 114 - 0
kit/schemas/com.tencent.liteav.demo.superplayer.database.PlayerDatabase/6.json

@@ -0,0 +1,114 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 6,
+    "identityHash": "9b2b786a1ead4f080488547b41f26db4",
+    "entities": [
+      {
+        "tableName": "History",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `course_id` TEXT NOT NULL, `section_id` TEXT, `date` TEXT, `ct_id` TEXT)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "start",
+            "columnName": "start",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "end",
+            "columnName": "end",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "courseId",
+            "columnName": "course_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "sectionId",
+            "columnName": "section_id",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "countDown",
+            "columnName": "ct_id",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "CountDown",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `course_id` TEXT NOT NULL, `type` INTEGER NOT NULL, `value` INTEGER NOT NULL, `datetime` TEXT)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "courseId",
+            "columnName": "course_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "value",
+            "columnName": "value",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "datetime",
+            "columnName": "datetime",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9b2b786a1ead4f080488547b41f26db4')"
+    ]
+  }
+}

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

@@ -14,4 +14,8 @@ public class SuperPlayerDef {
         LOADING,    // 缓冲中
         END         // 结束播放
     }
+
+    public enum VerifyReason {
+        TIMEOUT, // 重置定时
+    }
 }

+ 2 - 0
kit/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerModel.java

@@ -10,6 +10,8 @@ package com.tencent.liteav.demo.superplayer;
  * 是URL播放方式扩展,可同时传入多条URL,用于进行码率切换
  */
 public class SuperPlayerModel {
+    public String sectionId = "";
+
     public String url = "";      // 视频URL
 
     public String title = "";    // 视频文件名 (用于显示在UI层);

+ 45 - 8
kit/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerView.java

@@ -146,6 +146,13 @@ public class SuperPlayerView extends RelativeLayout {
         mSuperPlayerModelList = models;
     }
 
+    public void maybePlayIndexModel(int index) {
+        int playIndex = (index) % mSuperPlayerModelList.size();
+        SuperPlayerModel playModel = mSuperPlayerModelList.get(mPlayIndex);
+
+        mPlayerViewCallback.onPlayPrepare(playIndex, playModel);
+    }
+
     /* 播放指定的 */
     public void playIndexModel(int index) {
         playModelInList(index);
@@ -153,17 +160,14 @@ public class SuperPlayerView extends RelativeLayout {
 
     /* 播放下一个 */
     private void playNextVideo() {
-        playModelInList(++mPlayIndex);
+        maybePlayIndexModel(++mPlayIndex);
     }
 
     private void playModelInList(int index) {
-        if ((index >= mSuperPlayerModelList.size())) {
-            return;
-        }
         if (mIsTimeOut) return;
+        if ((index >= mSuperPlayerModelList.size())) return;
 
         mPlayIndex = (index) % mSuperPlayerModelList.size();
-
         mIsPlayInit = false;
         mSuperPlayer.stop();
         mPlayIndex = index;
@@ -186,11 +190,11 @@ public class SuperPlayerView extends RelativeLayout {
     private void playWithModelInner(SuperPlayerModel model) {
         if (mIsTimeOut) return;
 
-        mSuperPlayer.play(model);
-
         if (mPlayerViewCallback != null) {
             mPlayerViewCallback.onPlayIndex(mPlayIndex, model);
         }
+
+        mSuperPlayer.play(model);
         mFullScreenPlayer.preparePlayVideo(model);
         mWindowPlayer.preparePlayVideo(model);
     }
@@ -233,13 +237,16 @@ public class SuperPlayerView extends RelativeLayout {
     public void setTimeOut(final boolean timeout) {
         this.mIsTimeOut = timeout;
 
+        if (timeout) {
+            switchPlayMode(SuperPlayerDef.PlayerMode.FULLSCREEN);
+        }
         mSuperPlayer.setTimeout(timeout);
         mWindowPlayer.setTimeoutState(timeout);
         mFullScreenPlayer.setTimeoutState(timeout);
     }
 
     /**
-     * 设置超级播放器的回
+     * 设置超级播放器的回
      *
      * @param callback
      */
@@ -326,6 +333,12 @@ public class SuperPlayerView extends RelativeLayout {
             handleResume();
         }
 
+        @Override
+        public void verify(SuperPlayerDef.VerifyReason reason) {
+            if (mPlayerViewCallback != null) {
+                mPlayerViewCallback.onVerify(reason);
+            }
+        }
 
         @Override
         public void onSeekTo(int position) {
@@ -390,6 +403,8 @@ public class SuperPlayerView extends RelativeLayout {
          */
         void onPlayIndex(int index, SuperPlayerModel model);
 
+        void onPlayPrepare(int index, SuperPlayerModel model);
+
         /**
          * 播放结束
          */
@@ -401,6 +416,20 @@ public class SuperPlayerView extends RelativeLayout {
          * @param code
          */
         void onError(int code);
+
+        /**
+         * 弹出验证框
+         */
+        void onVerify(SuperPlayerDef.VerifyReason reason);
+
+        /**
+         * 单次播放历史
+         */
+        void onOnceHistory(long start, long end, String sectionId);
+
+        void onResumePlay();
+
+        void onPausePlay();
     }
 
     public void release() {
@@ -454,6 +483,8 @@ public class SuperPlayerView extends RelativeLayout {
             mWindowPlayer.updatePlayState(SuperPlayerDef.PlayerState.PLAYING);
             mFullScreenPlayer.updatePlayState(SuperPlayerDef.PlayerState.PLAYING);
 
+            mPlayerViewCallback.onResumePlay();
+
             mWindowPlayer.hideBackground();
             mFullScreenPlayer.hideBackground();
             notifyCallbackPlaying();
@@ -461,6 +492,7 @@ public class SuperPlayerView extends RelativeLayout {
 
         @Override
         public void onPlayPause() {
+            mPlayerViewCallback.onPausePlay();
             mWindowPlayer.updatePlayState(SuperPlayerDef.PlayerState.PAUSE);
             mFullScreenPlayer.updatePlayState(SuperPlayerDef.PlayerState.PAUSE);
         }
@@ -502,6 +534,11 @@ 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);
+        }
     }
 
     /**

+ 46 - 2
kit/src/main/java/com/tencent/liteav/demo/superplayer/player/SuperPlayerImpl.java

@@ -10,7 +10,6 @@ import com.tencent.liteav.demo.superplayer.SuperPlayerDef;
 import com.tencent.liteav.demo.superplayer.SuperPlayerGlobalConfig;
 import com.tencent.liteav.demo.superplayer.SuperPlayerModel;
 import com.tencent.rtmp.ITXVodPlayListener;
-import com.tencent.rtmp.TXLiveBase;
 import com.tencent.rtmp.TXLiveConstants;
 import com.tencent.rtmp.TXVodPlayConfig;
 import com.tencent.rtmp.TXVodPlayer;
@@ -43,6 +42,12 @@ public class SuperPlayerImpl implements SuperPlayer, ITXVodPlayListener {
     private boolean isNeedResume = false;
     private boolean mNeedToPause = false;
     private boolean mTimeout = false;
+    private boolean isPauseByTimeout = false;
+    private boolean isStartBySeek = false;
+
+    private long mCurrent = 0L;
+    private long mStartPoint = 0L;
+    private long mEndPoint = 0L;
 
     public SuperPlayerImpl(Context context, TXCloudVideoView videoView) {
         mContext = context;
@@ -89,6 +94,8 @@ 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;
+
                 if (duration != 0) {
                     updatePlayProgress(progress / 1000, duration / 1000);
                 }
@@ -241,6 +248,23 @@ public class SuperPlayerImpl implements SuperPlayer, ITXVodPlayListener {
                 mObserver.onPlayStop();
                 break;
         }
+
+        switch (playState) {
+            case PLAYING:
+                if(isStartBySeek){
+                    isStartBySeek = false;
+                }else{
+                    mStartPoint = mCurrent;
+                }
+                break;
+            case PAUSE:
+            case END:
+                mEndPoint = mCurrent;
+                if (mCurrentModel != null) {
+                    mObserver.onOnceHistory(mStartPoint, mEndPoint, mCurrentModel.sectionId);
+                }
+                break;
+        }
     }
 
     private void onError(int code, String message) {
@@ -310,7 +334,24 @@ public class SuperPlayerImpl implements SuperPlayer, ITXVodPlayListener {
     public void setTimeout(boolean timeout) {
         this.mTimeout = timeout;
 
-        if(timeout) pause();
+        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);
+            }
+
+            if (isPauseByTimeout) {
+                resume();
+            }
+
+        }
     }
 
     private void resetPlayer() {
@@ -338,6 +379,9 @@ public class SuperPlayerImpl implements SuperPlayer, ITXVodPlayListener {
 
     @Override
     public void seek(int position) {
+        mStartPoint = position;
+        isStartBySeek = true;
+
         if (mVodPlayer != null) {
             mVodPlayer.seek(position);
             if (!mVodPlayer.isPlaying()) {

+ 7 - 1
kit/src/main/java/com/tencent/liteav/demo/superplayer/player/SuperPlayerObserver.java

@@ -1,6 +1,8 @@
 package com.tencent.liteav.demo.superplayer.player;
 
 
+import com.tencent.liteav.demo.superplayer.database.entity.History;
+
 public abstract class SuperPlayerObserver {
 
     /**
@@ -52,7 +54,11 @@ public abstract class SuperPlayerObserver {
     public void onError(int code, String message) {
     }
 
-    public void onRcvFirstIframe(){
+    public void onRcvFirstIframe() {
+
+    }
+
+    public void onOnceHistory(long start, long end, String sectionId) {
 
     }
 }

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

@@ -23,7 +23,7 @@ import com.tencent.liteav.demo.superplayer.ui.view.UnlockProgressView;
  * <p>
  * 2、触摸事件监听{@link #onTouchEvent(MotionEvent)}
  */
-public class FullScreenPlayer extends AbsPlayer implements UnlockProgressView.OnUnlockListener, TimeOutPlayer.Callback {
+public class FullScreenPlayer extends AbsPlayer implements UnlockProgressView.OnUnlockListener {
     private UnlockProgressView mIvLock;                                // 锁屏按钮
     private TextView mIvLockText;
     private ImageView mImageCover;                            // 封面图
@@ -69,7 +69,12 @@ public class FullScreenPlayer extends AbsPlayer implements UnlockProgressView.On
         mImageCover = (ImageView) findViewById(R.id.superplayer_cover_view);
 
         mTimeoutView = (TimeOutPlayer) findViewById(R.id.superplayer_timeout_view);
-        mTimeoutView.setCallback(this);
+    }
+
+    @Override
+    public void setCallback(Callback callback) {
+        super.setCallback(callback);
+        mTimeoutView.setCallback(this.mControllerCallback);
     }
 
     public void setPlayNextButtonVisibility(boolean isShowing) {
@@ -200,25 +205,4 @@ public class FullScreenPlayer extends AbsPlayer implements UnlockProgressView.On
     public void unlockFailed() {
         postDelayed(mHideViewRunnable, HIDDEN_DELAY);
     }
-
-    @Override
-    public void onBack() {
-        if (mControllerCallback != null) {
-            mControllerCallback.onSwitchPlayMode(SuperPlayerDef.PlayerMode.WINDOW);
-        }
-    }
-
-    @Override
-    public void onResumePlay() {
-        if (mControllerCallback != null) {
-            mControllerCallback.onResume();
-        }
-    }
-
-    @Override
-    public void onReturn() {
-        if (mControllerCallback != null) {
-            mControllerCallback.onSwitchPlayMode(SuperPlayerDef.PlayerMode.WINDOW);
-        }
-    }
 }

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

@@ -121,6 +121,11 @@ public interface Player {
          */
         void playNext();
 
+        /**
+         * 弹出验证框
+         */
+        void verify(SuperPlayerDef.VerifyReason reason);
+
         /**
          * 获得当前剧集播放列表
          */

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

@@ -11,14 +11,13 @@ import android.widget.TextView;
 import androidx.appcompat.widget.LinearLayoutCompat;
 
 import com.tencent.liteav.demo.superplayer.R;
+import com.tencent.liteav.demo.superplayer.SuperPlayerDef;
 
 public class TimeOutPlayer extends AbsPlayer implements View.OnClickListener {
     private ImageView backHome;
     private LinearLayoutCompat resumePlay;
     private TextView returnHome;
 
-    private Callback mCallback;
-
     public TimeOutPlayer(Context context) {
         this(context, null);
     }
@@ -63,9 +62,13 @@ public class TimeOutPlayer extends AbsPlayer implements View.OnClickListener {
         final int id = v.getId();
 
         if (id == R.id.timeout_back || id == R.id.timeout_return) {
-            if (mCallback != null) mCallback.onBack();
+            if (mControllerCallback != null) {
+                mControllerCallback.onBackPressed(SuperPlayerDef.PlayerMode.FULLSCREEN);
+            }
         } else if (id == R.id.timeout_resume) {
-            if (mCallback != null) mCallback.onResumePlay();
+            if (mControllerCallback != null) {
+                mControllerCallback.verify(SuperPlayerDef.VerifyReason.TIMEOUT);
+            }
         }
     }
 
@@ -80,16 +83,4 @@ public class TimeOutPlayer extends AbsPlayer implements View.OnClickListener {
         super.show();
         this.setOnClickListener(this);
     }
-
-    public void setCallback(Callback callback) {
-        this.mCallback = callback;
-    }
-
-    public interface Callback {
-        void onBack();
-
-        void onResumePlay();
-
-        void onReturn();
-    }
 }

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

@@ -287,7 +287,6 @@ public class WindowPlayer extends AbsPlayer implements View.OnClickListener,
     }
 
     public void setTimeoutState(final boolean state) {
-        // DO NOTHING
         mTimeoutView.setVisibility(state ? VISIBLE : GONE);
     }
 

+ 25 - 36
kit/src/main/java/com/tencent/liteav/demo/superplayer/util/CountDownUtil.java

@@ -3,16 +3,15 @@ package com.tencent.liteav.demo.superplayer.util;
 import java.util.Timer;
 import java.util.TimerTask;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 
 public class CountDownUtil {
     // 倒计时的监听者
     public interface OnTickListener {
-        void onPause(int count, long period);
-
-        void onTimeout(int count, long period);
+        void onTimeout(CountDownUtil util);
     }
 
-    public static CountDownUtil build(long period, int maxCount, CountDownUtil.OnTickListener listener) {
+    public static CountDownUtil build(long period, long maxCount, CountDownUtil.OnTickListener listener) {
         return new CountDownUtil(period, maxCount, listener);
     }
 
@@ -27,7 +26,7 @@ public class CountDownUtil {
     private final long period;
 
     // 定时数
-    private final int maxCount;
+    private final long maxCount;
 
     // 回调监听对象
     private final CountDownUtil.OnTickListener listener;
@@ -39,48 +38,38 @@ public class CountDownUtil {
     private TimerTask task;
 
     // 计数器
-    private final AtomicInteger count = new AtomicInteger(0);
+    private final AtomicLong count = new AtomicLong(0L);
 
-    private CountDownUtil(long period, int maxCount, CountDownUtil.OnTickListener listener) {
+    private CountDownUtil(long period, long maxCount, CountDownUtil.OnTickListener listener) {
         this.period = period;
         this.maxCount = maxCount;
         this.listener = listener;
     }
 
-    public void start() {
-        if (STATE_INIT != state) {
-            throw new IllegalStateException("倒计时程序调用start时未处于初始态");
+    public void startOrResume() {
+        if (STATE_INIT == state) {
+            state = STATE_RUNNING;
+            count.set(0);
+            setupTask();
+        }else{
+            if (STATE_PAUSE == state) {
+                state = STATE_RUNNING;
+                setupTask();
+            }
         }
 
-        state = STATE_RUNNING;
-        count.set(0);
-        setupTask();
     }
 
     public void pause() {
-        if (STATE_RUNNING != state) {
-            throw new IllegalStateException("倒计时程序调用pause时未处于运行态");
-        }
-        state = STATE_PAUSE;
-
-        if (task != null) {
-            task.cancel();
-            timer.purge();
-            task = null;
-        }
+        if (STATE_RUNNING == state) {
+            state = STATE_PAUSE;
 
-        if (listener != null) {
-            listener.onPause(count.get(), period);
-        }
-    }
-
-    public void resume() {
-        if (STATE_PAUSE != state) {
-            throw new IllegalStateException("倒计时程序调用resume时未处于暂停态");
+            if (task != null) {
+                task.cancel();
+                timer.purge();
+                task = null;
+            }
         }
-        state = STATE_RUNNING;
-
-        setupTask();
     }
 
     public void cancel() {
@@ -94,8 +83,8 @@ public class CountDownUtil {
             public void run() {
                 count.addAndGet(1);
 
-                if(count.get() >= maxCount){
-                    listener.onTimeout(count.get(),period);
+                if (count.get() >= maxCount) {
+                    listener.onTimeout(CountDownUtil.this);
                 }
             }
         };

+ 1 - 1
kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/database/PlayerApplication.java → kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/PlayerApplication.java

@@ -1,4 +1,4 @@
-package com.tencent.liteav.demo.superplayer.database;
+package com.tencent.liteav.demo.superplayer;
 
 import android.app.Application;
 

+ 20 - 6
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 = 4,
+    version = 6,
     entities = [History::class, CountDown::class],
     exportSchema = true,
 )
@@ -23,21 +23,23 @@ abstract class PlayerDatabase : RoomDatabase() {
     abstract fun countDownDao(): CountDownDao
 
     companion object {
-        // Singleton prevents multiple instances of database opening at the
-        // same time.
         @Volatile
         private var INSTANCE: PlayerDatabase? = null
 
         @JvmStatic
         fun getDatabase(context: Context): PlayerDatabase {
-            // if the INSTANCE is not null, then return it,
-            // if it is, then create the database
             return INSTANCE ?: synchronized(this) {
                 val instance = Room.databaseBuilder(
                     context.applicationContext,
                     PlayerDatabase::class.java,
                     "player_database"
-                ).addMigrations(migration1_2, migration2_3, migration3_4).build()
+                ).addMigrations(
+                    migration1_2,
+                    migration2_3,
+                    migration3_4,
+                    migration4_5,
+                    migration5_6,
+                ).build()
                 INSTANCE = instance
                 // return instance
                 instance
@@ -62,5 +64,17 @@ abstract class PlayerDatabase : RoomDatabase() {
                 database.execSQL("DELETE FROM `CountDown`;")
             }
         }
+
+        private val migration4_5 = object : Migration(4, 5) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                database.execSQL("ALTER TABLE `History` ADD COLUMN `ct_id` TEXT;")
+            }
+        }
+
+        private val migration5_6 = object : Migration(5, 6) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                database.execSQL("ALTER TABLE `CountDown` ADD COLUMN `datetime` TEXT;")
+            }
+        }
     }
 }

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

@@ -1,6 +1,7 @@
 package com.tencent.liteav.demo.superplayer.database.dao
 
 import androidx.room.Dao
+import androidx.room.Delete
 import androidx.room.Insert
 import androidx.room.OnConflictStrategy.REPLACE
 import androidx.room.Query
@@ -15,4 +16,7 @@ interface CountDownDao {
 
     @Insert(onConflict = REPLACE)
     fun insertCountDown(countDown: CountDown)
+
+    @Query("DELETE FROM countdown WHERE course_id = :courseId")
+    fun deleteCountDown(courseId: String)
 }

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

@@ -18,9 +18,17 @@ public interface HistoryDao {
     @Insert
     fun insertHistories(vararg histories: History)
 
-    @Query("SELECT COUNT(DISTINCT(section_id)) FROM History WHERE date = :date")
-    fun queryTodayEpisodeHistory(date: String): Flow<Int>
+    @Query("SELECT COUNT(DISTINCT(section_id)) FROM History WHERE date = :date AND course_id = :courseId AND ct_id = :countDown")
+    fun queryTodayEpisodeHistory(
+        courseId: String,
+        countDown: String,
+        date: String,
+    ): Int
 
-    @Query("SELECT * FROM History WHERE date = :date")
-    fun queryTodayDurationHistory(date: String): Flow<List<History>>
+    @Query("SELECT * FROM History WHERE date = :date AND course_id = :courseId AND ct_id = :countDown;")
+    fun queryTodayDurationHistory(
+        courseId: String,
+        countDown: String,
+        date: String,
+    ): List<History>
 }

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

@@ -12,32 +12,29 @@ data class CountDown(
     @ColumnInfo(name = "course_id")
     val courseId: String,
 
-    /* 定时类型 1集数 2时长 */
+    /**
+     * 定时类型
+     *
+     * 1集数 2时长
+     */
     @ColumnInfo(name = "type")
     val type: Int,
 
-    /** 定时的值
+    /**
+     * 定时的值
+     *
      * 类型为集数时则表示多少集
      * 类型为时长是则表示多少分钟
      */
     @ColumnInfo(name = "value")
-    val value: Int
-) {
-
-    fun copyWith(
-        id: Long? = null,
-        courseId: String?,
-        type: Int?,
-        value: Int?
-    ): CountDown {
-        return CountDown(
-            id = id ?: this.id,
-            courseId = courseId ?: this.courseId,
-            type = type ?: this.type,
-            value = value ?: this.value
-        )
-    }
+    val value: Int,
 
+    /**
+     * 作为count down的唯一的特征
+     */
+    @ColumnInfo(name = "datetime")
+    val datetime: String? = null,
+) {
     companion object {
         @JvmStatic
         public val TYPE_EPISODE = 1;

+ 29 - 3
kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/database/entity/History.kt

@@ -1,10 +1,12 @@
 package com.tencent.liteav.demo.superplayer.database.entity
 
 import androidx.room.ColumnInfo
-import androidx.room.ColumnInfo.TEXT
 import androidx.room.Entity
 import androidx.room.PrimaryKey
-import java.util.*
+import kotlin.Int
+import kotlin.Long
+import kotlin.String
+import kotlin.TODO
 
 @Entity
 data class History(
@@ -25,4 +27,28 @@ data class History(
 
     @ColumnInfo(name = "date")
     val date: String?,
-)
+
+    @ColumnInfo(name = "ct_id")
+    val countDown: String? = null,
+) : Comparator<History>, Comparable<History> {
+    override fun compare(o1: History, o2: History): Int {
+        if (o1.start < o2.start) return -1
+        if (o1.start > o2.start) return 1
+
+        if (o1.end < o2.end) return 1;
+        if (o1.end > o2.end) return -1
+
+        return 0;
+    }
+
+    override fun compareTo(other: History): Int {
+        if (this.start < other.start) return -1
+        if (this.start > other.start) return 1
+
+        if (this.end < other.end) return 1;
+        if (this.end > other.end) return -1
+
+        return 0;
+    }
+
+}

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

@@ -1,5 +1,6 @@
 package com.tencent.liteav.demo.superplayer.database.repo
 
+import android.util.Log
 import com.tencent.liteav.demo.superplayer.database.dao.CountDownDao
 import com.tencent.liteav.demo.superplayer.database.dao.HistoryDao
 import com.tencent.liteav.demo.superplayer.database.entity.CountDown
@@ -21,20 +22,32 @@ public class PlayerRepository(
     }
 
     suspend fun insertCountDown(countDown: CountDown) {
+        Log.d("ZZZ", "insertCountDown")
         countDownDao.insertCountDown(countDown)
     }
 
+    suspend fun deleteCountDown(courseId: String) {
+        Log.d("ZZZ", "deleteCountDown")
+        countDownDao.deleteCountDown(courseId)
+    }
+
     suspend fun findHasCountDown(courseId: String): Flow<CountDown?> {
         return countDownDao.getCountDownByCourseId(courseId)
     }
 
-    suspend fun queryTodayEpisodeHistory(courseId: String): Flow<Int> {
+    suspend fun queryTodayEpisodeHistory(
+        courseId: String,
+        countDown: String,
+    ): Int {
         val date = dateFormat.format(Date())
-        return historyDao.queryTodayEpisodeHistory(date)
+        return historyDao.queryTodayEpisodeHistory(courseId, countDown, date)
     }
 
-    suspend fun queryTodayDurationHistory(courseId: String): Flow<List<History>> {
+    suspend fun queryTodayDurationHistory(
+        courseId: String,
+        countDown: String,
+    ): List<History> {
         val date = dateFormat.format(Date())
-        return historyDao.queryTodayDurationHistory(date)
+        return historyDao.queryTodayDurationHistory(courseId, countDown, date)
     }
 }

+ 1 - 1
ui/src/main/AndroidManifest.xml

@@ -4,7 +4,7 @@
     package="com.tencent.liteav.demo.player">
 
     <application
-        android:name="com.tencent.liteav.demo.superplayer.database.PlayerApplication"
+        android:name="com.tencent.liteav.demo.superplayer.PlayerApplication"
         android:allowBackup="false"
         android:icon="@drawable/ic_launcher"
         android:label="@string/superplayer_app_name"

+ 6 - 1
ui/src/main/java/com/tencent/liteav/demo/player/util/PlayerModelProvider.java

@@ -6,13 +6,17 @@ import java.util.ArrayList;
 import java.util.List;
 
 public class PlayerModelProvider {
-    private static final String VIDEO_URL = "https://www.douyin.com/aweme/v1/play/?video_id=v0200fg10000c9kggtjc77ubekkovilg&line=0&file_id=4465db5b9e48487197f8c0359e94ec46&sign=538e27bb98107b3dbabc0e24af67b0a8&is_play_url=1&source=PackSourceEnum_AWEME_DETAIL&aid=6383";
+    private static final String VIDEO_URL = "https://www.douyin.com/aweme/v1/play/?video_id=v0200fg10000cc5ca8bc77u6euo714d0&line=0&file_id=9c767801265548eaac539800298114a9&sign=c2faa0a73f75bef995e495c4d6688e00&is_play_url=1&source=PackSourceEnum_FEED&aid=6383";
 
     private static final String COVER_URL = "https://p9-pc-sign.douyinpic.com/tos-cn-p-0015/57aaf7f5975247e09c3df35e152055e4_1654159193~tplv-dy-360p.jpeg?biz_tag=pcweb_cover&from=4257465056&se=false&x-expires=1662015600&x-signature=0uL3qAHjGGcvaNmDuopeSCKdfm8%3D";
 
     private PlayerModelProvider() {
     }
 
+    public static String getCourseId() {
+        return "123";
+    }
+
     public static List<SuperPlayerModel> getList() {
         List<SuperPlayerModel> list = new ArrayList<>();
 
@@ -22,6 +26,7 @@ public class PlayerModelProvider {
             model.duration = 1222;
             model.isEnableCache = true;
             model.url = VIDEO_URL;
+            model.sectionId = String.valueOf(i);
 
             if (i % 2 == 0) {
                 model.cover = COVER_URL;

+ 46 - 13
ui/src/main/kotlin/com/tencent/liteav/demo/player/PlayerActivity.kt

@@ -14,6 +14,7 @@ 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
@@ -61,7 +62,7 @@ class PlayerActivity : AppCompatActivity(),
 
     private val viewModel by viewModels<PlayerViewModel> {
         PlayerViewModelFactory(
-            "",
+            PlayerModelProvider.getCourseId(),
             ((application) as PlayerDatabaseProvider).getPlayerRepository()
         )
     }
@@ -120,29 +121,38 @@ class PlayerActivity : AppCompatActivity(),
         mImageLock.setOnClickListener(this)
         mImageMenu.setOnClickListener(this)
         mSuperPlayerView.setPlayerViewCallback(this)
+
         mVodPlayerListView.layoutManager = LinearLayoutManager(this)
         mVodPlayerListAdapter = PlayerListAdapter(this)
         mVodPlayerListAdapter.setOnItemClickListener(this)
         mVodPlayerListView.adapter = mVodPlayerListAdapter
         mVodPlayerListView.addItemDecoration(PlayerListDecoration())
-        viewBinding.superplayerTcVerify.setOnClickListener(this)
-        adjustSuperPlayerViewAndMaskHeight()
-    }
 
-    private fun initData() {
-        initSuperVodGlobalSetting()
+        adjustSuperPlayerViewAndMaskHeight()
     }
 
     /**
      * 初始化超级播放器全局配置
      */
-    private fun initSuperVodGlobalSetting() {
+    private fun initData() {
         val prefs = SuperPlayerGlobalConfig.getInstance()
         prefs.maxCacheItem = 5
         prefs.enableHWAcceleration = true
         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()
         if (mSuperPlayerView.playerState == SuperPlayerDef.PlayerState.PLAYING
@@ -176,7 +186,7 @@ class PlayerActivity : AppCompatActivity(),
     }
 
     private fun playVideoModel(index: Int) {
-        mSuperPlayerView.playIndexModel(index)
+        mSuperPlayerView.maybePlayIndexModel(index)
     }
 
     override fun onClick(v: View) {
@@ -190,9 +200,6 @@ class PlayerActivity : AppCompatActivity(),
             R.id.superplayer_tc_menu -> {
                 PlayerMenu.start(this@PlayerActivity, "")
             }
-            viewBinding.superplayerTcVerify.id -> {
-                PlayerVerify.start(this@PlayerActivity)
-            }
         }
     }
 
@@ -239,17 +246,43 @@ class PlayerActivity : AppCompatActivity(),
         }
     }
 
+    override fun onPlayPrepare(index: Int, model: SuperPlayerModel) {
+        launch {
+            viewModel.checkCanPlay {
+                mSuperPlayerView.playIndexModel(index)
+            }
+        }
+    }
 
     override fun onPlayIndex(index: Int, model: SuperPlayerModel) {
         launch {
-            viewModel.changeModel(model)
             mVodPlayerListAdapter.setIndex(index)
         }
     }
 
     override fun onPlayEnd() {}
 
-    override fun onPlaying() {}
+    override fun onPlaying() {
+        Log.d(TAG, "onPlaying: ")
+    }
+
+    override fun onVerify(reason: SuperPlayerDef.VerifyReason) {
+        if (reason == SuperPlayerDef.VerifyReason.TIMEOUT) {
+            PlayerVerify.start(this@PlayerActivity, 0)
+        }
+    }
+
+    override fun onOnceHistory(start: Long, end: Long, sectionId: String) {
+        viewModel.saveHistory(start, end, sectionId)
+    }
+
+    override fun onResumePlay() {
+        viewModel.resumeTimer()
+    }
+
+    override fun onPausePlay() {
+        viewModel.pauseTimer()
+    }
 
     override fun onError(code: Int) {}
 

+ 7 - 3
ui/src/main/kotlin/com/tencent/liteav/demo/player/PlayerVerify.kt

@@ -5,6 +5,7 @@ import android.os.Bundle
 import android.text.TextUtils
 import android.content.Intent
 import android.view.View
+import androidx.activity.result.contract.ActivityResultContract
 import androidx.appcompat.app.AppCompatActivity
 import com.tencent.liteav.demo.player.PlayerVerify
 import com.tencent.liteav.demo.player.databinding.FragmentVerifyBinding
@@ -40,6 +41,8 @@ class PlayerVerify : AppCompatActivity(), View.OnClickListener {
         binding.verifyBtn9.setOnClickListener(this)
         binding.verifyClose.setOnClickListener(this)
         binding.verifyCheck.setOnClickListener(this)
+
+        setResult(RESULT_CANCELED)
     }
 
     override fun onClick(v: View) {
@@ -82,6 +85,7 @@ class PlayerVerify : AppCompatActivity(), View.OnClickListener {
         if (!TextUtils.isEmpty(result)) {
             val resultNum = Integer.valueOf(result)
             if (resultNum == a * b + c) {
+                setResult(RESULT_OK)
                 finish()
             } else {
                 checkError()
@@ -101,9 +105,9 @@ class PlayerVerify : AppCompatActivity(), View.OnClickListener {
     }
 
     companion object {
-        fun start(context: Context) {
-            val intent = Intent(context, PlayerVerify::class.java)
-            context.startActivity(intent)
+        fun start(activity: AppCompatActivity, requestCode: Int) {
+            val intent = Intent(activity, PlayerVerify::class.java)
+            activity.startActivityForResult(intent, requestCode)
         }
     }
 }

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

@@ -13,8 +13,10 @@ 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 kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import java.text.SimpleDateFormat
 import java.util.*
 
@@ -37,12 +39,15 @@ class PlayerViewModel(
 
     fun loadConfig(context: Context) = viewModelScope.launch {
         val common = context.getSharedPreferences("player_timer", Context.MODE_PRIVATE)
-        val defaultType = common.getInt("type", 0);
-        val defaultValue = common.getInt("value", 0);
+        val defaultType = common.getInt("type", 0)
+        val defaultValue = common.getInt("value", 0)
+        val defaultDate = common.getString("datetime", "20220831000000")!!
+
         val default = CountDown(
             courseId = courseId,
             type = defaultType,
             value = defaultValue,
+            datetime = defaultDate,
         )
         val special = repository.findHasCountDown(courseId).first()
 
@@ -55,50 +60,60 @@ class PlayerViewModel(
         setCountDown(countDown)
     }
 
-    /* 保存一条观看时间段的记录 */
-    fun saveHistory(history: History) = viewModelScope.launch(Dispatchers.IO) {
-        repository.insertHistory(history)
-    }
-
     /* 保存对该门课程单独设置的定时 */
     private fun saveCountDown(countDown: CountDown) = viewModelScope.launch(Dispatchers.IO) {
+        repository.deleteCountDown(courseId)
         repository.insertCountDown(countDown)
     }
 
     /* 开启定时器 */
     private fun setCountDown(countDown: CountDown) {
-        checkTimeout()
+        countDownUtil?.cancel()
+        countDownUtil = null
 
-        if (countDown.type == CountDown.TYPE_EPISODE) {
+        checkTimeout(countDown)
+    }
 
-        } else if (countDown.type == CountDown.TYPE_DURATION) {
-            countDownUtil?.cancel()
-            countDownUtil = CountDownUtil.build(1000, countDown.value, this)
-            countDownUtil?.start()
-        } else {
-            countDownUtil?.cancel()
-        }
+    fun resumeTimer() {
+        countDownUtil?.startOrResume()
     }
 
-    fun changeModel(model: SuperPlayerModel) {
+    fun pauseTimer() {
+        countDownUtil?.pause()
+    }
+
+    /* 保存一条观看时间段的记录 */
+    fun saveHistory(start: Long, end: Long, sectionId: String) {
         val history = History(
-            start = 0,
-            end = 10,
-            courseId = "0",
-            sectionId = model.title!!,
-            date = SimpleDateFormat("yyyy-MM-dd").format(Date())
+            start = start,
+            end = end,
+            courseId = courseId,
+            sectionId = sectionId,
+            date = SimpleDateFormat("yyyy-MM-dd").format(Date()),
+            countDown = countDown.datetime,
         )
 
-        saveHistory(history)
+        viewModelScope.launch(Dispatchers.IO) {
+            repository.insertHistory(history)
+        }
+    }
+
+    suspend fun checkCanPlay(whenCan: () -> Unit) = viewModelScope.launch(Dispatchers.IO) {
+        checkTimeout(countDown)
+
+        withContext(Dispatchers.Main) {
+            whenCan.invoke()
+        }
     }
 
     override fun onListen(type: Int, value: Int) {
         Log.d("PVM", "onListen: $type , $value")
 
-        countDown = countDown.copyWith(
+        countDown = CountDown(
             courseId = courseId,
             type = type,
-            value = value
+            value = value,
+            datetime = SimpleDateFormat("yyyyMMddhhmmss").format(Date())
         )
 
         countDown.let {
@@ -112,22 +127,18 @@ class PlayerViewModel(
         PlayerTimerUtil.removeListener(this)
     }
 
-    override fun onPause(count: Int, period: Long) {
-
-    }
-
-    override fun onTimeout(count: Int, period: Long) {
-        countDownUtil?.cancel()
+    override fun onTimeout(util: CountDownUtil?) {
+        util?.cancel()
         sendTimeout(true)
     }
 
-    private fun checkTimeout() {
+    private fun checkTimeout(countDown: CountDown) {
         countDown.also {
             viewModelScope.launch {
                 if (it.type == CountDown.TYPE_DURATION) {
                     checkDurationTimeout()
                 } else if (it.type == CountDown.TYPE_EPISODE) {
-                    checkEpisodeTimeout();
+                    checkEpisodeTimeout()
                 } else {
                     sendTimeout(false)
                 }
@@ -136,14 +147,35 @@ class PlayerViewModel(
     }
 
     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 suspend fun checkDurationTimeout() = viewModelScope.launch(Dispatchers.IO) {
-        var history = repository.queryTodayDurationHistory(courseId)
+        val history = repository.queryTodayDurationHistory(courseId, countDown.datetime!!)
+
+        val result = history.fold(0L) { r, h ->
+            r + (h.end - h.start)
+        }
 
+        if (result >= countDown.value) {
+            sendTimeout(true)
+        } else {
+            sendTimeout(false)
+            setupTimer(countDown.value - result)
+        }
     }
 
+    private fun setupTimer(count: Long) {
+        if (countDown.type == CountDown.TYPE_DURATION) {
+            countDownUtil = CountDownUtil.build(1000, count, this)
+        }
+    }
 
     private fun sendTimeout(state: Boolean) {
         viewModelScope.launch(Dispatchers.Main) {

+ 0 - 10
ui/src/main/res/layout/activity_player.xml

@@ -43,16 +43,6 @@
             android:gravity="center"
             android:orientation="horizontal">
 
-            <ImageView
-                android:id="@+id/superplayer_tc_verify"
-                android:layout_width="38dp"
-                android:layout_height="39dp"
-                android:src="@mipmap/window_lock" />
-
-            <View
-                android:layout_width="27dp"
-                android:layout_height="0dp" />
-
             <ImageView
                 android:id="@+id/superplayer_tc_lock"
                 android:layout_width="38dp"