Răsfoiți Sursa

combine record

zhaoyadi 2 ani în urmă
părinte
comite
84d5f569c2

+ 158 - 0
kit/schemas/com.tencent.liteav.demo.superplayer.database.PlayerDatabase/1.json

@@ -0,0 +1,158 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "fe898d5cfe7a453225cbced50ee01829",
+    "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 NOT NULL, `date` TEXT NOT NULL, `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": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "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` TEXT NOT NULL, `type` INTEGER NOT NULL, `value` INTEGER NOT NULL, `rest` INTEGER NOT NULL, `datetime` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "value",
+            "columnName": "value",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "rest",
+            "columnName": "rest",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "datetime",
+            "columnName": "datetime",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Record",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`aiCourseItemId` TEXT NOT NULL, `intervals` TEXT NOT NULL, `lastTime` INTEGER NOT NULL, `validDuration` INTEGER NOT NULL, `courseId` TEXT NOT NULL, PRIMARY KEY(`aiCourseItemId`))",
+        "fields": [
+          {
+            "fieldPath": "aiCourseItemId",
+            "columnName": "aiCourseItemId",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "intervals",
+            "columnName": "intervals",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "lastTime",
+            "columnName": "lastTime",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "validDuration",
+            "columnName": "validDuration",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "courseId",
+            "columnName": "courseId",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "aiCourseItemId"
+          ],
+          "autoGenerate": false
+        },
+        "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, 'fe898d5cfe7a453225cbced50ee01829')"
+    ]
+  }
+}

+ 4 - 14
kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/PlayerApplication.java

@@ -10,35 +10,25 @@ import com.tencent.liteav.demo.superplayer.database.repo.PlayerRepository;
 import com.tencent.liteav.demo.superplayer.util.HttpUtils;
 
 public class PlayerApplication extends Application implements PlayerDatabaseProvider {
-    private PlayerDatabase database;
-    private PlayerRepository repository;
+    private PlayerDatabaseProvider.Instance provider;
 
     @Override
     public void onCreate() {
         super.onCreate();
+        provider = new PlayerDatabaseProvider.Instance(this);
         HttpUtils.INSTANCE.init(this);
     }
 
     @NonNull
     @Override
     public PlayerDatabase getPlayerDatabase() {
-        if (database == null) {
-            database = PlayerDatabase.Companion.getDatabase(this);
-        }
-
-        return database;
+        return provider.getPlayerDatabase();
     }
 
 
     @NonNull
     @Override
     public PlayerRepository getPlayerRepository() {
-        if (repository == null) {
-            repository = new PlayerRepository(
-                    getPlayerDatabase().historyDao(),
-                    getPlayerDatabase().countDownDao()
-            );
-        }
-        return repository;
+        return provider.getPlayerRepository();
     }
 }

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

@@ -10,17 +10,20 @@ import androidx.room.migration.Migration
 import androidx.sqlite.db.SupportSQLiteDatabase
 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.dao.RecordDao
 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.entity.Record
 
 @Database(
     version = 1,
-    entities = [History::class, CountDown::class],
+    entities = [History::class, CountDown::class, Record::class],
     exportSchema = true,
 )
 abstract class PlayerDatabase : RoomDatabase() {
     abstract fun historyDao(): HistoryDao
     abstract fun countDownDao(): CountDownDao
+    abstract fun recordDao(): RecordDao
 
     companion object {
         @Volatile

+ 29 - 0
kit/src/main/kotlin/com/tencent/liteav/demo/superplayer/database/PlayerDatabaseProvider.kt

@@ -1,8 +1,37 @@
 package com.tencent.liteav.demo.superplayer.database
 
+import android.app.Application
+import com.tencent.liteav.demo.superplayer.database.PlayerDatabase.Companion.getDatabase
 import com.tencent.liteav.demo.superplayer.database.repo.PlayerRepository
 
 interface PlayerDatabaseProvider {
     fun getPlayerDatabase(): PlayerDatabase
     fun getPlayerRepository(): PlayerRepository
+
+    public class Instance(
+        val context: Application,
+    ) : PlayerDatabaseProvider {
+        private var database: PlayerDatabase? = null
+        private var repository: PlayerRepository? = null
+
+        override fun getPlayerDatabase(): PlayerDatabase {
+            if (database == null) {
+                database = getDatabase(context)
+            }
+
+            return database!!
+        }
+
+        override fun getPlayerRepository(): PlayerRepository {
+            if (repository == null) {
+                repository = PlayerRepository(
+                    getPlayerDatabase().historyDao(),
+                    getPlayerDatabase().countDownDao(),
+                    getPlayerDatabase().recordDao()
+                )
+            }
+            return repository!!
+        }
+
+    }
 }

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

@@ -0,0 +1,23 @@
+package com.tencent.liteav.demo.superplayer.database.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import com.tencent.liteav.demo.superplayer.database.entity.Record
+
+@Dao
+interface RecordDao {
+
+    @Query("SELECT * FROM record WHERE courseId = :courseId")
+    fun queryRecord(courseId: String): List<Record>
+
+    @Query("SELECT * FROM record WHERE courseId = :courseId AND aiCourseItemId = :sectionId")
+    fun queryRecord(courseId: String, sectionId: String): Record?
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun insertRecord(record: Record)
+
+    @Query("DELETE FROM record WHERE courseId = :courseId")
+    fun deleteRecord(courseId: String)
+}

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

@@ -0,0 +1,23 @@
+package com.tencent.liteav.demo.superplayer.database.entity
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+data class Record(
+    @PrimaryKey
+    var aiCourseItemId: String,
+
+    @ColumnInfo(name = "intervals")
+    val intervals: String,
+
+    @ColumnInfo(name = "lastTime")
+    val lastTime: Long,
+
+    @ColumnInfo(name = "validDuration")
+    val validDuration: Long,
+
+    @ColumnInfo(name = "courseId")
+    val courseId: String,
+)

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

@@ -1,10 +1,15 @@
 package com.tencent.liteav.demo.superplayer.database.repo
 
 import android.util.Log
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
 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.dao.RecordDao
 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.entity.Record
 import kotlinx.coroutines.flow.Flow
 import java.text.SimpleDateFormat
 import java.util.*
@@ -12,6 +17,7 @@ import java.util.*
 public class PlayerRepository(
     private val historyDao: HistoryDao,
     private val countDownDao: CountDownDao,
+    private val recordDao: RecordDao,
 ) {
     companion object {
         val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.SIMPLIFIED_CHINESE)
@@ -66,7 +72,23 @@ public class PlayerRepository(
         return historyDao.queryAllSectionHistory(courseId, sectionId)
     }
 
-    fun deleteAllHistory(courseId:String){
+    fun deleteAllHistory(courseId: String) {
         historyDao.deleteAllHistory(courseId)
     }
+
+    fun queryRecord(courseId: String): List<Record> {
+        return recordDao.queryRecord(courseId)
+    }
+
+    fun queryRecord(courseId: String, sectionId: String): Record? {
+        return recordDao.queryRecord(courseId, sectionId)
+    }
+
+    fun insertRecord(record: Record) {
+        recordDao.insertRecord(record)
+    }
+
+    fun deleteRecord(courseId: String) {
+        recordDao.deleteRecord(courseId)
+    }
 }

+ 57 - 45
ui/src/main/assets/data.json

@@ -1,7 +1,7 @@
 {
   "id": "1540203667959226370",
   "name": "小鸡彩虹英文版第二季",
-  "imgCover": "https://video.training.luojigou.vip/Fjer1kevKlYNJKXE80imW1cJv0kf_low.mp4",
+  "imgCover": "https://img.luojigou.vip/Fmsz81vTpT9iVWuws2tVOuDwI9kY",
   "imgCoverMini": "https://img.luojigou.vip/FnV8CLD1Ci-oF2SAiDERZ6yV7Ez-",
   "createTime": "2022-06-24 13:22:24",
   "updateTime": "2022-06-24 13:22:56",
@@ -26,7 +26,19 @@
   "courseTags": [],
   "paidCount": 2,
   "latestLearnedRecordId": null,
-  "latestLearnedRecord": null,
+  "latestLearnedRecord": {
+    "id": "1541617205059674114",
+    "userId": "1364835254064881665",
+    "createTime": "2022-06-28 10:59:18",
+    "updateTime": "2022-08-26 18:54:32",
+    "aiCourseItemId": "1540205198213292034",
+    "aiCourseSkuId": "1540203667959226370",
+    "second": 33,
+    "hasFinished": 0,
+    "validDuration": 46,
+    "intervals": "[[0, 1], [113, 152], [212, 215]]",
+    "lastTime": 215
+  },
   "abilityList": [],
   "commentCount": 0,
   "shareCount": 22,
@@ -34,7 +46,7 @@
   "isCollect": 0,
   "hasPaid": 0,
   "isComment": 0,
-  "isShare": 0,
+  "isShare": 1,
   "imgCoverWidth": null,
   "imgCoverHeight": null,
   "shareUrl": "https://zaojiao.net/luojigou_web_shop_html/#/growTogether/group/course?id=1540203667959226370",
@@ -88,8 +100,8 @@
           "aiCourseSkuId": "1540203667959226370",
           "sort": 0,
           "createTime": "2022-06-24 13:28:29",
-          "updateTime": "2022-09-14 14:19:44",
-          "readCount": 251,
+          "updateTime": "2022-09-15 14:58:35",
+          "readCount": 260,
           "aiCourseItemChapterId": "1540205198192320513",
           "type": 0,
           "payType": 1,
@@ -104,13 +116,13 @@
           "hasReply": 0,
           "totalStarCount": 0,
           "getRecord": {
-            "validDuration": null,
-            "intervals": null,
-            "lastTime": null,
+            "validDuration": 46,
+            "intervals": "[[0, 1], [113, 152], [212, 215]]",
+            "lastTime": 215,
             "aiCourseItemId": null
           },
           "starList": [],
-          "readCountStr": "251",
+          "readCountStr": "260",
           "video": {
             "id": "1540205198259429378",
             "videoUrl": "http://video.training.luojigou.vip/lm6giVlUMxuYKuVaBRTxRr6SNiO__low.mp4",
@@ -132,8 +144,8 @@
           "aiCourseSkuId": "1540203667959226370",
           "sort": 1,
           "createTime": "2022-06-24 13:28:29",
-          "updateTime": "2022-09-14 14:19:44",
-          "readCount": 4,
+          "updateTime": "2022-09-15 14:56:41",
+          "readCount": 8,
           "aiCourseItemChapterId": "1540205198192320513",
           "type": 0,
           "payType": 0,
@@ -154,7 +166,7 @@
             "aiCourseItemId": null
           },
           "starList": [],
-          "readCountStr": "4",
+          "readCountStr": "8",
           "video": {
             "id": "1540205198364286977",
             "videoUrl": null,
@@ -176,8 +188,8 @@
           "aiCourseSkuId": "1540203667959226370",
           "sort": 2,
           "createTime": "2022-06-24 13:28:29",
-          "updateTime": "2022-09-14 14:19:45",
-          "readCount": 4,
+          "updateTime": "2022-09-15 14:56:41",
+          "readCount": 8,
           "aiCourseItemChapterId": "1540205198192320513",
           "type": 0,
           "payType": 0,
@@ -198,7 +210,7 @@
             "aiCourseItemId": null
           },
           "starList": [],
-          "readCountStr": "4",
+          "readCountStr": "8",
           "video": {
             "id": "1540205198464950273",
             "videoUrl": null,
@@ -220,8 +232,8 @@
           "aiCourseSkuId": "1540203667959226370",
           "sort": 3,
           "createTime": "2022-06-24 13:28:29",
-          "updateTime": "2022-09-14 14:19:45",
-          "readCount": 4,
+          "updateTime": "2022-09-15 14:56:42",
+          "readCount": 8,
           "aiCourseItemChapterId": "1540205198192320513",
           "type": 0,
           "payType": 0,
@@ -242,7 +254,7 @@
             "aiCourseItemId": null
           },
           "starList": [],
-          "readCountStr": "4",
+          "readCountStr": "8",
           "video": {
             "id": "1540205198565613570",
             "videoUrl": null,
@@ -264,8 +276,8 @@
           "aiCourseSkuId": "1540203667959226370",
           "sort": 4,
           "createTime": "2022-06-24 13:28:29",
-          "updateTime": "2022-09-14 14:19:45",
-          "readCount": 4,
+          "updateTime": "2022-09-15 14:56:42",
+          "readCount": 8,
           "aiCourseItemChapterId": "1540205198192320513",
           "type": 0,
           "payType": 0,
@@ -286,7 +298,7 @@
             "aiCourseItemId": null
           },
           "starList": [],
-          "readCountStr": "4",
+          "readCountStr": "8",
           "video": {
             "id": "1540205198670471170",
             "videoUrl": null,
@@ -308,8 +320,8 @@
           "aiCourseSkuId": "1540203667959226370",
           "sort": 5,
           "createTime": "2022-06-24 13:28:29",
-          "updateTime": "2022-09-14 14:19:45",
-          "readCount": 4,
+          "updateTime": "2022-09-15 14:56:42",
+          "readCount": 8,
           "aiCourseItemChapterId": "1540205198192320513",
           "type": 0,
           "payType": 0,
@@ -330,7 +342,7 @@
             "aiCourseItemId": null
           },
           "starList": [],
-          "readCountStr": "4",
+          "readCountStr": "8",
           "video": {
             "id": "1540205198771134465",
             "videoUrl": null,
@@ -352,8 +364,8 @@
           "aiCourseSkuId": "1540203667959226370",
           "sort": 6,
           "createTime": "2022-06-24 13:28:29",
-          "updateTime": "2022-09-14 14:19:46",
-          "readCount": 4,
+          "updateTime": "2022-09-15 14:56:42",
+          "readCount": 8,
           "aiCourseItemChapterId": "1540205198192320513",
           "type": 0,
           "payType": 0,
@@ -374,7 +386,7 @@
             "aiCourseItemId": null
           },
           "starList": [],
-          "readCountStr": "4",
+          "readCountStr": "8",
           "video": {
             "id": "1540205198871797762",
             "videoUrl": null,
@@ -396,8 +408,8 @@
           "aiCourseSkuId": "1540203667959226370",
           "sort": 7,
           "createTime": "2022-06-24 13:28:29",
-          "updateTime": "2022-09-14 14:19:46",
-          "readCount": 4,
+          "updateTime": "2022-09-15 14:56:42",
+          "readCount": 8,
           "aiCourseItemChapterId": "1540205198192320513",
           "type": 0,
           "payType": 0,
@@ -418,7 +430,7 @@
             "aiCourseItemId": null
           },
           "starList": [],
-          "readCountStr": "4",
+          "readCountStr": "8",
           "video": {
             "id": "1540205198976655361",
             "videoUrl": null,
@@ -440,8 +452,8 @@
           "aiCourseSkuId": "1540203667959226370",
           "sort": 8,
           "createTime": "2022-06-24 13:28:29",
-          "updateTime": "2022-09-14 14:19:46",
-          "readCount": 4,
+          "updateTime": "2022-09-15 14:56:43",
+          "readCount": 8,
           "aiCourseItemChapterId": "1540205198192320513",
           "type": 0,
           "payType": 0,
@@ -462,7 +474,7 @@
             "aiCourseItemId": null
           },
           "starList": [],
-          "readCountStr": "4",
+          "readCountStr": "8",
           "video": {
             "id": "1540205199073124354",
             "videoUrl": null,
@@ -484,8 +496,8 @@
           "aiCourseSkuId": "1540203667959226370",
           "sort": 9,
           "createTime": "2022-06-24 13:28:29",
-          "updateTime": "2022-09-14 14:19:46",
-          "readCount": 4,
+          "updateTime": "2022-09-15 14:56:43",
+          "readCount": 8,
           "aiCourseItemChapterId": "1540205198192320513",
           "type": 0,
           "payType": 0,
@@ -506,7 +518,7 @@
             "aiCourseItemId": null
           },
           "starList": [],
-          "readCountStr": "4",
+          "readCountStr": "8",
           "video": {
             "id": "1540205199173787649",
             "videoUrl": null,
@@ -528,8 +540,8 @@
           "aiCourseSkuId": "1540203667959226370",
           "sort": 10,
           "createTime": "2022-06-24 13:28:29",
-          "updateTime": "2022-09-14 14:19:46",
-          "readCount": 4,
+          "updateTime": "2022-09-15 14:56:43",
+          "readCount": 8,
           "aiCourseItemChapterId": "1540205198192320513",
           "type": 0,
           "payType": 0,
@@ -550,7 +562,7 @@
             "aiCourseItemId": null
           },
           "starList": [],
-          "readCountStr": "4",
+          "readCountStr": "8",
           "video": {
             "id": "1540205199274450945",
             "videoUrl": null,
@@ -572,8 +584,8 @@
           "aiCourseSkuId": "1540203667959226370",
           "sort": 11,
           "createTime": "2022-06-24 13:28:29",
-          "updateTime": "2022-09-14 14:19:47",
-          "readCount": 4,
+          "updateTime": "2022-09-15 14:56:43",
+          "readCount": 8,
           "aiCourseItemChapterId": "1540205198192320513",
           "type": 0,
           "payType": 0,
@@ -594,7 +606,7 @@
             "aiCourseItemId": null
           },
           "starList": [],
-          "readCountStr": "4",
+          "readCountStr": "8",
           "video": {
             "id": "1540205199375114242",
             "videoUrl": null,
@@ -616,8 +628,8 @@
           "aiCourseSkuId": "1540203667959226370",
           "sort": 12,
           "createTime": "2022-06-24 13:28:29",
-          "updateTime": "2022-09-14 14:19:47",
-          "readCount": 4,
+          "updateTime": "2022-09-15 14:56:44",
+          "readCount": 8,
           "aiCourseItemChapterId": "1540205198192320513",
           "type": 0,
           "payType": 0,
@@ -638,7 +650,7 @@
             "aiCourseItemId": null
           },
           "starList": [],
-          "readCountStr": "4",
+          "readCountStr": "8",
           "video": {
             "id": "1540205199475777537",
             "videoUrl": null,

+ 11 - 0
ui/src/main/java/com/tencent/liteav/demo/player/util/ModelCourse.java

@@ -1,5 +1,7 @@
 package com.tencent.liteav.demo.player.util;
 
+import androidx.annotation.Nullable;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -37,6 +39,7 @@ public class ModelCourse {
         public Video video;
         public int hasClockIn;
         public int getStarCount;
+        public Records getRecord;
     }
 
     public static class Course {
@@ -126,8 +129,16 @@ public class ModelCourse {
     }
 
     public static class Records {
+        @Nullable
         public String aiCourseItemId;
+
+        @Nullable
         public String intervals;
+
+        @Nullable
+        public Long lastTime;
+
+        @Nullable
         public Long validDuration;
     }
 }

+ 13 - 1
ui/src/main/java/com/tencent/liteav/demo/player/util/ModelProvider.kt

@@ -11,7 +11,19 @@ object ModelProvider {
     }
 
     val courseId: String get() = course.id
-    val cccccc:Course get() = course
+    val cccccc: Course get() = course
+
+    val records: List<ModelCourse.Records>
+        get() {
+            val list = ArrayList<ModelCourse.Records>()
+            for (chapter in course.chapterList) {
+                for (section in chapter.itemList) {
+                    section.getRecord.aiCourseItemId = section.id
+                    list.add(section.getRecord)
+                }
+            }
+            return list
+        }
 
     val list: List<SuperPlayerModel>
         get() {

+ 1 - 1
ui/src/main/kotlin/com/tencent/liteav/demo/player/http/AuthorizationInterceptor.kt

@@ -19,7 +19,7 @@ class AuthorizationInterceptor : Interceptor {
             token = sp.getString("flutter.token", null)
         }
 
-        token ="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxNTQ0NjMxNTYwNjcyMDE4NDM0IiwiZXhwIjoxNjYyOTUxMzY4fQ.aLHyj0f5Y5rKLNfLtRS2yUMYD7KMyF1gCCxTIhECDJ0"
+        token ="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxMzY0ODM1MjU0MDY0ODgxNjY1IiwiZXhwIjoxNjYzODE3MDMxfQ.d_79J8e6yZnRpS8O6RXLfKPDWS6V4lkq1Q8NLekyMuE"
 
         val newRequest = chain.request()
             .newBuilder()

+ 1 - 1
ui/src/main/kotlin/com/tencent/liteav/demo/player/http/CourseApi.kt

@@ -14,6 +14,6 @@ interface CourseApi {
 
     @POST("v1/AICourse/item/read/record/v1")
     suspend fun uploadHistory(
-        @Body data: Map<String, @JvmSuppressWildcards Any>,
+        @Body data: List<@JvmSuppressWildcards ModelCourse.Records>,
     ): HttpResult<Int>
 }

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

@@ -6,8 +6,10 @@ import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.viewModelScope
+import com.tencent.liteav.demo.player.util.ModelProvider
 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.entity.Record
 import com.tencent.liteav.demo.superplayer.database.repo.PlayerRepository
 import com.tencent.liteav.demo.superplayer.util.*
 import kotlinx.coroutines.Dispatchers
@@ -73,6 +75,21 @@ class PlayerViewModel(
 
         resetCountDown()
         setCountDown(countDown, false)
+
+        for (record in ModelProvider.records) {
+            if (!record.aiCourseItemId.isNullOrBlank()
+                && !record.intervals.isNullOrBlank()
+                && record.lastTime != null
+                && record.validDuration != null
+            )
+                repository.insertRecord(record = Record(
+                    aiCourseItemId = record.aiCourseItemId!!,
+                    intervals = record.intervals!!,
+                    lastTime = record.lastTime!!,
+                    validDuration = record.validDuration!!,
+                    courseId = ModelProvider.courseId,
+                ))
+        }
     }
 
     private fun resetCountDown() {

+ 71 - 11
ui/src/main/kotlin/com/tencent/liteav/demo/player/viewmodel/UploadWorker.kt

@@ -3,11 +3,15 @@ package com.tencent.liteav.demo.player.viewmodel
 import android.content.Context
 import androidx.work.CoroutineWorker
 import androidx.work.WorkerParameters
+import com.alibaba.fastjson2.JSON
+import com.alibaba.fastjson2.JSONArray
+import com.alibaba.fastjson2.JSONObject
 import com.tencent.liteav.demo.player.http.CourseApi
 import com.tencent.liteav.demo.player.http.HttpManager
 import com.tencent.liteav.demo.player.util.ModelCourse
 import com.tencent.liteav.demo.superplayer.database.PlayerDatabaseProvider
 import com.tencent.liteav.demo.superplayer.database.entity.History
+import com.tencent.liteav.demo.superplayer.database.entity.Record
 import java.util.*
 
 class UploadWorker(
@@ -23,13 +27,16 @@ class UploadWorker(
         val records = mutableListOf<ModelCourse.Records>()
         for (section in sectionList) {
             val history = repository.queryAllSectionHistory(courseId, section)
-            val result = processPerSection(history)
+            val lastRecord = repository.queryRecord(courseId, section)
+            val result = processPerSection(history, lastRecord)
+
             val record = ModelCourse.Records().apply {
                 aiCourseItemId = section
                 intervals = result.toString()
                 validDuration = result.fold(0L) { t, p ->
                     t + p[1] - p[0] + 1
                 }
+                lastTime = history.lastOrNull()?.end ?: lastRecord?.lastTime ?: 0
             }
 
             records.add(record)
@@ -40,21 +47,25 @@ class UploadWorker(
     }
 
     /* 针对每一节处理 */
-    private fun processPerSection(historyList: List<History>): List<List<Long>> {
+    private fun processPerSection(
+        historyList: List<History>,
+        lastRecord: Record?,
+    ): List<List<Long>> {
         if (historyList.isEmpty()) return listOf()
 
+        // 先把本地的记录处理一遍
         Collections.sort(historyList)
-        val result = mutableListOf<Interval>()
+        var combineHistory = mutableListOf<Interval>()
 
         var left = historyList[0].start
         var right = historyList[0].end
 
-        for (i in 0 until historyList.size) {
+        for (i in historyList.indices) {
             if (historyList[i].start <= right) {
                 right = Math.max(historyList[i].end, right)
             } else {
                 val p = Interval(left, right)
-                result.add(p)
+                combineHistory.add(p)
 
                 left = historyList[i].start
                 right = historyList[i].end
@@ -62,11 +73,41 @@ class UploadWorker(
         }
 
         val p = Interval(left, right)
-        result.add(p)
+        combineHistory.add(p)
+        // 处理本地记录完成
+
+        // 检查有没有上次记录并准备合并处理
+        lastRecord?.apply {
+            val lastResult = JSON.parseArray(this.intervals, List::class.java)
+            for (item in lastResult) {
+                val s = item[0] as Int
+                val e = item[1] as Int
+                combineHistory.add(Interval(s.toLong(), e.toLong()))
+            }
+
+            combineHistory.sort()
+
+            val sortRecord = mutableListOf<Interval>()
+            var lt = combineHistory[0].start
+            var rt = combineHistory[0].end
+
+            for (i in combineHistory.indices) {
+                if (combineHistory[i].start <= rt) {
+                    rt = Math.max(combineHistory[i].end, rt)
+                } else {
+                    sortRecord.add(Interval(lt, rt))
+                    lt = combineHistory[i].start
+                    rt = combineHistory[i].end
+                }
+            }
+
+            sortRecord.add(Interval(lt, rt))
+            combineHistory = sortRecord
+        }
 
         val list = mutableListOf<List<Long>>()
 
-        for (interval in result) {
+        for (interval in combineHistory) {
             list.add(listOf(interval.start, interval.end))
         }
 
@@ -76,9 +117,7 @@ class UploadWorker(
     /* 分批次上传历史记录 */
     private suspend fun batchUpload(courseId: String, records: List<ModelCourse.Records>) {
         val api: CourseApi = HttpManager.create()
-        val result = api.uploadHistory(mapOf(
-            "readRecordDTOS" to records
-        ))
+        val result = api.uploadHistory(records)
         if (result.status == 200) {
             repository.deleteAllHistory(courseId = courseId)
         }
@@ -87,5 +126,26 @@ class UploadWorker(
     private data class Interval(
         public val start: Long,
         public val end: Long,
-    )
+    ) : Comparator<Interval>, Comparable<Interval> {
+        override fun compare(o1: Interval, o2: Interval): 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: Interval): 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;
+        }
+
+    }
 }