diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index e0f15db2..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "java.configuration.updateBuildConfiguration": "automatic" -} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/HomeActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/HomeActivity.java index 4afc168f..095a4d69 100644 --- a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/HomeActivity.java +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/HomeActivity.java @@ -245,7 +245,24 @@ public class HomeActivity extends BaseActivity implements CustomTitleView.Listen } private void getHistory(boolean renew) { - List items = History.get(); + // 获取所有视频源的观看记录(最近60天) + List items = History.getAll(); + com.github.catvod.utils.Logger.d("HomeActivity: 获取观看记录,共 " + items.size() + " 条"); + + // 对比一下数据库中所有记录 + List allInDb = com.fongmi.android.tv.db.AppDatabase.get().getHistoryDao().findAllRecent(0); + com.github.catvod.utils.Logger.d("HomeActivity: 数据库总记录数: " + allInDb.size() + " 条(包含所有时间)"); + + if (items.size() < allInDb.size()) { + com.github.catvod.utils.Logger.w("HomeActivity: 有 " + (allInDb.size() - items.size()) + " 条记录因为时间过滤被隐藏"); + } + + for (History h : items) { + com.github.catvod.utils.Logger.d("HomeActivity: 记录 - " + h.getVodName() + + " (cid=" + h.getCid() + + ", createTime=" + h.getCreateTime() + ")"); + } + int historyIndex = getHistoryIndex(); int recommendIndex = getRecommendIndex(); boolean exist = recommendIndex - historyIndex == 2; diff --git a/app/src/leanback/res/values/styles.xml b/app/src/leanback/res/values/styles.xml index ec15bde0..5b991e4c 100644 --- a/app/src/leanback/res/values/styles.xml +++ b/app/src/leanback/res/values/styles.xml @@ -10,7 +10,6 @@ @color/primary @color/primaryDark @color/accent - @color/primary true @null true diff --git a/app/src/main/java/com/fongmi/android/tv/App.java b/app/src/main/java/com/fongmi/android/tv/App.java index b03fbe34..79fefa95 100644 --- a/app/src/main/java/com/fongmi/android/tv/App.java +++ b/app/src/main/java/com/fongmi/android/tv/App.java @@ -54,7 +54,9 @@ public class App extends Application { executor = Executors.newFixedThreadPool(Constant.THREAD_POOL); handler = HandlerCompat.createAsync(Looper.getMainLooper()); time = System.currentTimeMillis(); - gson = new Gson(); + gson = new com.google.gson.GsonBuilder() + .disableHtmlEscaping() + .create(); cleanTask = this::checkCacheClean; syncTask = this::checkWebDAVSync; appJustLaunched = true; @@ -227,18 +229,8 @@ public class App extends Application { if (manager.isConfigured()) { // 应用启动时,如果已配置WebDAV,立即执行一次同步(下载远程数据) // 这样新设备配置后,下次启动应用时就能看到其他设备的历史记录 - App.execute(() -> { - try { - Logger.d("App: 应用启动,执行WebDAV同步"); - // 先上传本地记录 - manager.uploadHistory(); - // 再下载远程记录并合并 - manager.downloadHistory(); - Logger.d("App: WebDAV同步完成"); - } catch (Exception e) { - Logger.e("App: WebDAV同步失败: " + e.getMessage()); - } - }); + Logger.d("App: WebDAV已配置,准备执行同步"); + manager.syncHistory(true); // 使用统一的同步方法,包含防重复逻辑 // 如果启用了自动同步,设置定期同步 if (Setting.isWebDAVAutoSync()) { @@ -246,6 +238,8 @@ public class App extends Application { // 延迟执行下次同步,避免影响启动速度 post(syncTask, interval * 60 * 1000L); } + } else { + Logger.d("App: WebDAV未配置,跳过同步"); } } diff --git a/app/src/main/java/com/fongmi/android/tv/bean/History.java b/app/src/main/java/com/fongmi/android/tv/bean/History.java index 0ac2f21b..137f8986 100644 --- a/app/src/main/java/com/fongmi/android/tv/bean/History.java +++ b/app/src/main/java/com/fongmi/android/tv/bean/History.java @@ -248,6 +248,10 @@ public class History { return AppDatabase.get().getHistoryDao().find(cid, System.currentTimeMillis() - Constant.HISTORY_TIME); } + public static List getAll() { + return AppDatabase.get().getHistoryDao().findAllRecent(System.currentTimeMillis() - Constant.HISTORY_TIME); + } + public static History find(String key) { return AppDatabase.get().getHistoryDao().find(VodConfig.getCid(), key); } @@ -272,8 +276,15 @@ public class History { } public void update() { - merge(find(), false); - save(); + try { + com.github.catvod.utils.Logger.d("History.update: 开始更新观看记录 key=" + getKey()); + merge(find(), false); + save(); + com.github.catvod.utils.Logger.d("History.update: 更新成功"); + } catch (Exception e) { + com.github.catvod.utils.Logger.e("History.update: 更新失败 - " + e.getMessage()); + e.printStackTrace(); + } } public History update(int cid) { @@ -287,6 +298,7 @@ public class History { } public History save() { + com.github.catvod.utils.Logger.d("History.save: key=" + getKey() + ", vodName=" + getVodName()); AppDatabase.get().getHistoryDao().insertOrUpdate(this); return this; } diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Site.java b/app/src/main/java/com/fongmi/android/tv/bean/Site.java index 737a8079..7b92dba4 100644 --- a/app/src/main/java/com/fongmi/android/tv/bean/Site.java +++ b/app/src/main/java/com/fongmi/android/tv/bean/Site.java @@ -105,11 +105,36 @@ public class Site implements Parcelable { public static Site objectFrom(JsonElement element) { try { - return App.gson().fromJson(element, Site.class); + Site site = App.gson().fromJson(element, Site.class); + // 尝试修复可能的编码问题 + if (site != null && site.getKey() != null) { + site.setKey(fixEncoding(site.getKey())); + if (site.getName() != null) { + site.setName(fixEncoding(site.getName())); + } + } + return site; } catch (Exception e) { return new Site(); } } + + private static String fixEncoding(String str) { + if (str == null || str.isEmpty()) return str; + try { + // 检查是否包含乱码字符(替换字符 U+FFFD) + if (str.indexOf('\uFFFD') >= 0) { + // 尝试用ISO-8859-1重新解码为UTF-8 + byte[] bytes = str.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1); + String fixed = new String(bytes, java.nio.charset.StandardCharsets.UTF_8); + com.github.catvod.utils.Logger.d("Site.fixEncoding: 修复编码 '" + str + "' -> '" + fixed + "'"); + return fixed; + } + } catch (Exception e) { + com.github.catvod.utils.Logger.e("Site.fixEncoding: 修复失败 - " + e.getMessage()); + } + return str; + } public static Site get(String key) { Site site = new Site(); @@ -133,6 +158,13 @@ public class Site implements Parcelable { public void setKey(@NonNull String key) { this.key = key; + // 检查key中是否有异常字符 + for (int i = 0; i < key.length(); i++) { + char c = key.charAt(i); + if (c == 0xFFFD || c < 0x20 || (c >= 0x7F && c < 0xA0)) { + com.github.catvod.utils.Logger.w("Site.setKey: 检测到异常字符 at position " + i + ": U+" + String.format("%04X", (int)c)); + } + } } public String getName() { @@ -141,6 +173,15 @@ public class Site implements Parcelable { public void setName(String name) { this.name = name; + // 检查name中是否有异常字符 + if (name != null) { + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + if (c == 0xFFFD || c < 0x20 || (c >= 0x7F && c < 0xA0)) { + com.github.catvod.utils.Logger.w("Site.setName: 检测到异常字符 at position " + i + ": U+" + String.format("%04X", (int)c) + " in name: " + name); + } + } + } } public String getApi() { diff --git a/app/src/main/java/com/fongmi/android/tv/db/dao/HistoryDao.java b/app/src/main/java/com/fongmi/android/tv/db/dao/HistoryDao.java index 75408453..c0d46d06 100644 --- a/app/src/main/java/com/fongmi/android/tv/db/dao/HistoryDao.java +++ b/app/src/main/java/com/fongmi/android/tv/db/dao/HistoryDao.java @@ -16,6 +16,9 @@ public abstract class HistoryDao extends BaseDao { @Query("SELECT * FROM History WHERE cid = :cid AND createTime >= :createTime ORDER BY createTime DESC") public abstract List find(int cid, long createTime); + @Query("SELECT * FROM History WHERE createTime >= :createTime ORDER BY createTime DESC") + public abstract List findAllRecent(long createTime); + @Query("SELECT * FROM History WHERE cid = :cid AND `key` = :key") public abstract History find(int cid, String key); diff --git a/app/src/main/java/com/fongmi/android/tv/utils/WebDAVSyncManager.java b/app/src/main/java/com/fongmi/android/tv/utils/WebDAVSyncManager.java index 1c9ce823..61bc6b68 100644 --- a/app/src/main/java/com/fongmi/android/tv/utils/WebDAVSyncManager.java +++ b/app/src/main/java/com/fongmi/android/tv/utils/WebDAVSyncManager.java @@ -6,6 +6,7 @@ import com.fongmi.android.tv.App; import com.fongmi.android.tv.bean.Backup; import com.fongmi.android.tv.bean.History; import com.fongmi.android.tv.db.AppDatabase; +import com.fongmi.android.tv.event.RefreshEvent; import com.github.catvod.utils.Logger; import com.github.catvod.utils.Prefers; import com.google.gson.Gson; @@ -280,20 +281,60 @@ public class WebDAVSyncManager { } try { - // 获取所有观看记录 - List historyList = AppDatabase.get().getHistoryDao().findAll(); + // 获取所有观看记录 - 使用findAllRecent(0)来获取所有记录(包括旧记录) + Logger.d("WebDAV: 开始查询数据库中的观看记录..."); + List historyList = AppDatabase.get().getHistoryDao().findAllRecent(0); + Logger.d("WebDAV: 数据库查询完成,结果: " + (historyList == null ? "null" : historyList.size() + " 条")); + if (historyList == null) { + Logger.w("WebDAV: 查询结果为null,创建空列表"); historyList = new java.util.ArrayList<>(); } + // 修复数据中可能的编码问题(重点修复key中的站点名称部分) + Logger.d("WebDAV: 开始修复上传数据的编码问题..."); + for (History h : historyList) { + String originalKey = h.getKey(); + + // key格式: 站点key$视频ID$cid,需要单独修复站点key部分 + String fixedKey = fixHistoryKey(originalKey); + if (!originalKey.equals(fixedKey)) { + Logger.d("WebDAV: 修复key编码: '" + originalKey + "' -> '" + fixedKey + "'"); + h.setKey(fixedKey); + } + + String originalName = h.getVodName(); + String fixedName = fixEncodingIfNeeded(originalName); + if (!originalName.equals(fixedName)) { + Logger.d("WebDAV: 修复vodName编码: '" + originalName + "' -> '" + fixedName + "'"); + h.setVodName(fixedName); + } + } + Logger.d("WebDAV: 准备上传观看记录,共 " + historyList.size() + " 条"); + // 记录前3条数据的详细信息 + for (int i = 0; i < Math.min(3, historyList.size()); i++) { + History h = historyList.get(i); + Logger.d("WebDAV: 上传记录[" + i + "] key=" + h.getKey() + ", vodName=" + h.getVodName()); + // 检查key中的每个字符 + String key = h.getKey(); + StringBuilder hexDump = new StringBuilder(); + for (int j = 0; j < Math.min(20, key.length()); j++) { + hexDump.append(String.format("%04x ", (int)key.charAt(j))); + } + Logger.d("WebDAV: key前20字符的Unicode: " + hexDump.toString()); + } + String json = App.gson().toJson(historyList); if (TextUtils.isEmpty(json)) { Logger.w("WebDAV: JSON数据为空"); json = "[]"; // 确保至少有一个有效的JSON数组 } + // 记录JSON的前500个字符 + Logger.d("WebDAV: JSON前500字符: " + json.substring(0, Math.min(500, json.length()))); + // 确保目录存在(如果baseUrl包含子目录) if (syncMode == SyncMode.ACCOUNT && !TextUtils.isEmpty(baseUrl)) { try { @@ -338,18 +379,22 @@ public class WebDAVSyncManager { public boolean downloadHistory() { if (!isConfigured()) { Logger.e("WebDAV: 未配置,无法下载观看记录"); + Logger.e("WebDAV: baseUrl=" + baseUrl + ", username=" + username); return false; } try { String fileUrl = getFileUrl(HISTORY_FILE); + Logger.d("WebDAV: 检查文件是否存在: " + fileUrl); // 检查文件是否存在 if (!sardine.exists(fileUrl)) { - Logger.d("WebDAV: 观看记录文件不存在,跳过下载"); + Logger.w("WebDAV: 观看记录文件不存在,跳过下载"); return false; } + Logger.d("WebDAV: 文件存在,开始下载"); + // 下载文件(使用循环读取,避免available()不准确的问题) InputStream is = sardine.get(fileUrl); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); @@ -378,7 +423,72 @@ public class WebDAVSyncManager { } // 智能合并:比较本地和远程记录,保留较新的 - List localHistoryList = AppDatabase.get().getHistoryDao().findAll(); + List localHistoryList = AppDatabase.get().getHistoryDao().findAllRecent(0); + Logger.d("WebDAV: 本地记录数: " + localHistoryList.size()); + Logger.d("WebDAV: 远程记录数: " + remoteHistoryList.size()); + + // 修复远程记录的编码问题和时间戳 + Logger.d("WebDAV: 开始修复远程记录编码和时间戳..."); + long currentTime = System.currentTimeMillis(); + long historyTimeLimit = currentTime - com.fongmi.android.tv.Constant.HISTORY_TIME; // 60天前 + + for (History remote : remoteHistoryList) { + if (remote != null) { + String originalKey = remote.getKey(); + // 修复key中的站点名称部分 + String fixedKey = fixHistoryKey(originalKey); + if (!originalKey.equals(fixedKey)) { + Logger.d("WebDAV: 修复远程key: '" + originalKey + "' -> '" + fixedKey + "'"); + remote.setKey(fixedKey); + } + + String originalName = remote.getVodName(); + String fixedName = fixEncodingIfNeeded(originalName); + if (!originalName.equals(fixedName)) { + Logger.d("WebDAV: 修复远程vodName: '" + originalName + "' -> '" + fixedName + "'"); + remote.setVodName(fixedName); + } + + // 关键修复:确保createTime在60天内,否则会被过滤掉! + long remoteCreateTime = remote.getCreateTime(); + if (remoteCreateTime < historyTimeLimit) { + Logger.d("WebDAV: 修复过期时间戳: " + remote.getVodName() + + " createTime=" + remoteCreateTime + " -> " + currentTime + + " (已过期 " + ((currentTime - remoteCreateTime) / (24*60*60*1000)) + " 天)"); + remote.setCreateTime(currentTime); + } + + // 记录前3条远程数据的详细信息 + if (remoteHistoryList.indexOf(remote) < 3) { + Logger.d("WebDAV: 远程记录[" + remoteHistoryList.indexOf(remote) + "]: " + + remote.getVodName() + " (key=" + remote.getKey() + + ", cid=" + remote.getCid() + + ", createTime=" + remote.getCreateTime() + ")"); + } + } + } + + // 修复本地记录的编码问题(重要!) + Logger.d("WebDAV: 开始修复本地记录编码..."); + for (History local : localHistoryList) { + if (local != null) { + String originalKey = local.getKey(); + // 修复key中的站点名称部分 + String fixedKey = fixHistoryKey(originalKey); + if (!originalKey.equals(fixedKey)) { + Logger.d("WebDAV: 修复本地key: '" + originalKey + "' -> '" + fixedKey + "'"); + local.setKey(fixedKey); + } + + // 记录前3条本地数据的详细信息 + if (localHistoryList.indexOf(local) < 3) { + Logger.d("WebDAV: 本地记录[" + localHistoryList.indexOf(local) + "]: " + + local.getVodName() + " (key=" + local.getKey() + + ", cid=" + local.getCid() + + ", createTime=" + local.getCreateTime() + ")"); + } + } + } // 创建本地记录的映射(key -> History) java.util.Map localMap = new java.util.HashMap<>(); @@ -387,11 +497,14 @@ public class WebDAVSyncManager { localMap.put(local.getKey(), local); } } + Logger.d("WebDAV: 本地记录映射大小: " + localMap.size()); // 合并远程记录 List toInsert = new java.util.ArrayList<>(); List toUpdate = new java.util.ArrayList<>(); + Logger.d("WebDAV: 开始合并 " + remoteHistoryList.size() + " 条远程记录..."); + for (History remote : remoteHistoryList) { // 验证远程记录 if (remote == null || TextUtils.isEmpty(remote.getKey())) { @@ -403,43 +516,110 @@ public class WebDAVSyncManager { if (local == null) { // 本地没有,直接添加 + Logger.d("WebDAV: 发现新记录: " + remote.getVodName() + " (key=" + remote.getKey() + ")"); toInsert.add(remote); } else { - // 本地有,比较createTime,保留较新的 - if (remote.getCreateTime() > local.getCreateTime()) { - // 远程更新,更新本地 - toUpdate.add(remote); - } else if (remote.getCreateTime() == local.getCreateTime()) { - // 时间相同,比较position,保留进度更靠后的 - // 注意:position可能是C.TIME_UNSET(负数),需要处理 - long remotePos = remote.getPosition(); - long localPos = local.getPosition(); - // 如果都是有效值(>=0),比较大小;如果有无效值,保留有效值 + Logger.d("WebDAV: 本地已有记录: " + remote.getVodName() + ", 比较时间 remote=" + remote.getCreateTime() + " local=" + local.getCreateTime()); + + // 改进的合并策略:优先保留较新的记录,但也要比较播放进度 + long remotePos = remote.getPosition(); + long localPos = local.getPosition(); + long remoteTime = remote.getCreateTime(); + long localTime = local.getCreateTime(); + + boolean shouldUpdate = false; + String reason = ""; + + // 策略1:如果远程时间更新,直接更新 + if (remoteTime > localTime) { + shouldUpdate = true; + reason = "远程时间更新 (" + remoteTime + " > " + localTime + ")"; + } + // 策略2:如果时间相同或相近(误差1秒内),比较播放进度 + else if (Math.abs(remoteTime - localTime) <= 1000) { if (remotePos >= 0 && localPos >= 0) { if (remotePos > localPos) { - toUpdate.add(remote); + shouldUpdate = true; + reason = "播放进度更新 (" + remotePos + " > " + localPos + ")"; + } else { + reason = "本地进度更新或相同"; } } else if (remotePos >= 0 && localPos < 0) { - // 远程有效,本地无效,更新 - toUpdate.add(remote); + shouldUpdate = true; + reason = "远程有有效进度,本地无效"; + } else { + reason = "保留本地"; } - // 否则保留本地,不更新 } - // 否则保留本地,不更新 + // 策略3:即使本地时间更新,如果远程有更大的播放进度,也更新 + else if (remoteTime < localTime) { + if (remotePos >= 0 && localPos >= 0 && remotePos > localPos + 60000) { + // 远程进度领先本地超过1分钟,可能是用户在另一台设备继续观看 + shouldUpdate = true; + reason = "虽然本地时间更新,但远程进度显著领先 (" + remotePos + " > " + localPos + ")"; + } else { + reason = "本地时间更新 (" + localTime + " > " + remoteTime + "),保留本地"; + } + } + + if (shouldUpdate) { + Logger.d("WebDAV: → 将更新本地 - " + reason); + toUpdate.add(remote); + } else { + Logger.d("WebDAV: → 保留本地 - " + reason); + } } } + Logger.d("WebDAV: 合并完成,待插入 " + toInsert.size() + " 条,待更新 " + toUpdate.size() + " 条"); + // 执行插入和更新 if (!toInsert.isEmpty()) { + Logger.d("WebDAV: 开始插入 " + toInsert.size() + " 条新记录..."); AppDatabase.get().getHistoryDao().insert(toInsert); Logger.d("WebDAV: 新增 " + toInsert.size() + " 条观看记录"); + for (History h : toInsert) { + Logger.d("WebDAV: ✓ 新增 - " + h.getVodName() + " (cid=" + h.getCid() + ", key=" + h.getKey() + ")"); + } + } else { + Logger.d("WebDAV: 没有需要插入的新记录"); } + if (!toUpdate.isEmpty()) { + Logger.d("WebDAV: 开始更新 " + toUpdate.size() + " 条记录..."); AppDatabase.get().getHistoryDao().update(toUpdate); Logger.d("WebDAV: 更新 " + toUpdate.size() + " 条观看记录"); + for (History h : toUpdate) { + Logger.d("WebDAV: ✓ 更新 - " + h.getVodName() + " (cid=" + h.getCid() + ")"); + } + } else { + Logger.d("WebDAV: 没有需要更新的记录"); } Logger.d("WebDAV: 观看记录合并完成,远程 " + remoteHistoryList.size() + " 条,本地 " + localHistoryList.size() + " 条"); + + // 验证数据库中的记录总数 + List allInDb = AppDatabase.get().getHistoryDao().findAllRecent(0); + Logger.d("WebDAV: 数据库中总共有 " + allInDb.size() + " 条观看记录"); + + // 输出数据库中前5条记录的详细信息 + Logger.d("WebDAV: === 数据库中的记录(前5条)==="); + for (int i = 0; i < Math.min(5, allInDb.size()); i++) { + History h = allInDb.get(i); + Logger.d("WebDAV: [" + i + "] " + h.getVodName() + + " (key=" + h.getKey() + + ", cid=" + h.getCid() + + ", createTime=" + h.getCreateTime() + ")"); + } + Logger.d("WebDAV: ========================="); + + // 强制触发UI刷新(即使没有新增或更新,也刷新一次以确保显示) + Logger.d("WebDAV: 触发UI刷新事件"); + App.post(() -> { + RefreshEvent.history(); + Logger.d("WebDAV: UI刷新事件已发送到主线程"); + }); + return true; // 即使远程为空,也算同步成功 } catch (Exception e) { Logger.e("WebDAV: 观看记录下载失败: " + e.getMessage()); @@ -755,5 +935,76 @@ public class WebDAVSyncManager { public void reloadConfig() { loadConfig(); } + + /** + * 修复History的key中的站点名称编码 + * key格式: 站点key$视频ID$cid + */ + private String fixHistoryKey(String key) { + if (key == null || key.isEmpty()) { + return key; + } + + try { + // 使用AppDatabase.SYMBOL分隔 + String symbol = com.fongmi.android.tv.db.AppDatabase.SYMBOL; + String[] parts = key.split(java.util.regex.Pattern.quote(symbol)); + + if (parts.length >= 3) { + // parts[0] = 站点key, parts[1] = 视频ID, parts[2] = cid + String siteKey = parts[0]; + String fixedSiteKey = fixEncodingIfNeeded(siteKey); + + if (!siteKey.equals(fixedSiteKey)) { + // 重新组装key + StringBuilder newKey = new StringBuilder(fixedSiteKey); + for (int i = 1; i < parts.length; i++) { + newKey.append(symbol).append(parts[i]); + } + return newKey.toString(); + } + } + } catch (Exception e) { + Logger.e("WebDAV: 修复History key失败: " + e.getMessage()); + } + + return key; + } + + /** + * 修复字符串编码问题 + * 尝试将错误编码的UTF-8字符串修复为正确的UTF-8 + */ + private String fixEncodingIfNeeded(String str) { + if (str == null || str.isEmpty()) { + return str; + } + + try { + // 检查字符串中是否包含明显的乱码特征 + // 1. 包含替换字符 U+FFFD + // 2. 包含异常的低位控制字符 + boolean needsFix = false; + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (c == '\uFFFD' || (c >= 0x80 && c < 0xA0)) { + needsFix = true; + break; + } + } + + if (needsFix) { + // 尝试修复:假设原始数据是UTF-8,但被错误地当作ISO-8859-1解码 + byte[] bytes = str.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1); + String fixed = new String(bytes, java.nio.charset.StandardCharsets.UTF_8); + Logger.d("WebDAV: 编码修复 '" + str + "' -> '" + fixed + "'"); + return fixed; + } + } catch (Exception e) { + Logger.e("WebDAV: 编码修复失败: " + e.getMessage()); + } + + return str; + } } diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HistoryActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HistoryActivity.java index 6d5a5042..e9d13620 100644 --- a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HistoryActivity.java +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HistoryActivity.java @@ -58,7 +58,7 @@ public class HistoryActivity extends BaseActivity implements HistoryAdapter.OnCl } private void getHistory() { - mAdapter.addAll(History.get()); + mAdapter.addAll(History.getAll()); // 显示所有视频源的观看记录 mBinding.delete.setVisibility(mAdapter.getItemCount() > 0 ? View.VISIBLE : View.GONE); updateEmptyState(); } diff --git a/other/image/icon.png b/other/image/icon.png deleted file mode 100644 index fc9e1f78..00000000 Binary files a/other/image/icon.png and /dev/null differ diff --git a/other/image/logo-1.png b/other/image/logo-1.png deleted file mode 100644 index becdb7fe..00000000 Binary files a/other/image/logo-1.png and /dev/null differ diff --git a/other/image/logo-2.png b/other/image/logo-2.png deleted file mode 100644 index ee040446..00000000 Binary files a/other/image/logo-2.png and /dev/null differ diff --git a/other/sample/ad_block_example.json b/other/sample/ad_block_example.json deleted file mode 100644 index 4626b205..00000000 --- a/other/sample/ad_block_example.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "广告过滤配置示例", - "description": "演示如何配置广告域名黑名单,阻止视频中途弹出的广告(如澳门新葡京等博彩广告)", - - "说明": { - "内置拦截": "应用已内置常见广告域名库,包括:澳门新葡京、皇冠、金沙等博彩广告;Google、百度、淘宝等广告联盟;优酷、爱奇艺等视频平台广告", - "自定义拦截": "可以在配置文件中添加ads字段,补充需要拦截的广告域名", - "支持正则": "域名支持正则表达式匹配,使用 .* 作为通配符" - }, - - "配置示例": { - "spider": "your_spider_url", - "sites": [], - - "ads": [ - "注释: 以下是自定义广告域名列表,会与内置域名库合并使用", - - "注释: 精确匹配 - 直接写完整域名", - "mimg.0c1q0l.cn", - "www.92424.cn", - "vip.ffzyad.com", - - "注释: 模糊匹配 - 使用通配符", - ".*\\.doubleclick\\.net", - ".*\\.googlesyndication\\.com", - - "注释: 关键词匹配 - 拦截包含特定关键词的域名", - ".*葡京.*", - ".*皇冠.*", - ".*金沙.*", - ".*casino.*", - ".*bet.*", - - "注释: 特定平台的广告", - "wan.51img1.com", - "k.jinxiuzhilv.com", - "ssl.kdd.cc" - ] - }, - - "常见问题": { - "Q1": "为什么配置了还是有广告?", - "A1": "1. 检查广告域名是否正确;2. 某些广告可能直接嵌入视频流,无法通过域名拦截;3. 尝试使用片头片尾跳过功能", - - "Q2": "如何找到广告的域名?", - "A2": "1. 使用浏览器开发者工具查看网络请求;2. 查看应用日志中的URL;3. 参考其他用户分享的广告域名列表", - - "Q3": "会不会误拦截正常内容?", - "A3": "内置域名库经过筛选,主要针对已知广告。如有误拦截,可以反馈给开发者" - }, - - "片头片尾跳过": { - "说明": "对于嵌入视频流中的广告,可以使用片头片尾跳过功能", - "使用方法": [ - "1. 播放视频时,在片头(前5分钟内)按【片头】按钮,记录当前时间点", - "2. 在片尾(后5分钟内)按【片尾】按钮,记录结束前的时间点", - "3. 下次播放相同视频时,会自动跳过片头,并在片尾前停止", - "4. 如需重置,长按对应按钮即可" - ] - }, - - "技术说明": { - "拦截层级": "WebView网络请求层拦截", - "拦截方式": "返回空响应,阻止广告内容加载", - "性能影响": "极小,仅在WebView解析时生效", - "隐私保护": "所有拦截在本地进行,不上传任何数据" - } -} - diff --git a/other/sample/live/offline.json b/other/sample/live/offline.json deleted file mode 100644 index ae3391e7..00000000 --- a/other/sample/live/offline.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "lives": [ - { - "name": "M3U", - "url": "file://Download/live.m3u" - }, - { - "name": "TXT", - "url": "file://Download/live.txt", - "epg": "https://epg.112114.xyz/?ch={name}&date={date}", - "logo": "https://epg.112114.xyz/logo/{name}.png" - }, - { - "name": "UA", - "url": "file://Download/live.txt", - "epg": "https://epg.112114.xyz/?ch={name}&date={date}", - "logo": "https://epg.112114.xyz/logo/{name}.png", - "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "referer": "https://github.com/" - }, - { - "name": "Custom", - "boot": false, - "pass": true, - "url": "file://Download/live.txt", - "epg": "https://epg.112114.xyz/?ch={name}&date={date}&serverTimeZone=Asia/Shanghai", - "logo": "https://epg.112114.xyz/logo/{name}.png", - "header": { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "Referer": "https://github.com/" - }, - "catchup": { - "days": "7", - "type": "append", - "regex": "/PLTV/", - "replace": "/PLTV/,/TVOD/", - "source": "?playseek=${(b)yyyyMMddHHmmss}-${(e)yyyyMMddHHmmss}" - } - }, - { - "name": "JSON", - "type": 1, - "url": "file://Download/live.json" - }, - { - "name": "Spider-JS", - "type": 3, - "api": "./live.js", - "ext": "" - }, - { - "name": "Spider-Python", - "type": 3, - "api": "./live.py", - "ext": "" - } - ], - "headers": [ - { - "host": "gslbserv.itv.cmvideo.cn", - "header": { - "User-Agent": "okhttp/3.12.13" - } - } - ], - "proxy": [ - "raw.githubusercontent.com" - ], - "hosts": [ - "cache.ott.ystenlive.itv.cmvideo.cn=base-v4-free-mghy.e.cdn.chinamobile.com" - ], - "ads": [ - "static-mozai.4gtv.tv" - ] -} \ No newline at end of file diff --git a/other/sample/live/online.json b/other/sample/live/online.json deleted file mode 100644 index 3825d5fb..00000000 --- a/other/sample/live/online.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "lives": [ - { - "name": "M3U", - "url": "https://github.com/live.m3u" - }, - { - "name": "TXT", - "url": "https://github.com/live.txt", - "epg": "https://epg.112114.xyz/?ch={name}&date={date}", - "logo": "https://epg.112114.xyz/logo/{name}.png" - }, - { - "name": "UA", - "url": "https://github.com/live.txt", - "epg": "https://epg.112114.xyz/?ch={name}&date={date}", - "logo": "https://epg.112114.xyz/logo/{name}.png", - "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "referer": "https://github.com/" - }, - { - "name": "Custom", - "boot": false, - "pass": true, - "url": "https://github.com/live.txt", - "epg": "https://epg.112114.xyz/?ch={name}&date={date}&serverTimeZone=Asia/Shanghai", - "logo": "https://epg.112114.xyz/logo/{name}.png", - "header": { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "Referer": "https://github.com/" - }, - "catchup": { - "days": "7", - "type": "append", - "regex": "/PLTV/", - "replace": "/PLTV/,/TVOD/", - "source": "?playseek=${(b)yyyyMMddHHmmss}-${(e)yyyyMMddHHmmss}" - } - }, - { - "name": "JSON", - "type": 1, - "url": "https://github.com/live.json" - }, - { - "name": "Spider-JS", - "type": 3, - "api": "https://github.com/live.js", - "ext": "" - }, - { - "name": "Spider-Python", - "type": 3, - "api": "https://github.com/live.py", - "ext": "" - } - ], - "headers": [ - { - "host": "gslbserv.itv.cmvideo.cn", - "header": { - "User-Agent": "okhttp/3.12.13" - } - } - ], - "proxy": [ - "raw.githubusercontent.com" - ], - "hosts": [ - "cache.ott.ystenlive.itv.cmvideo.cn=base-v4-free-mghy.e.cdn.chinamobile.com" - ], - "ads": [ - "static-mozai.4gtv.tv" - ] -} \ No newline at end of file diff --git a/other/sample/vod/offline.json b/other/sample/vod/offline.json deleted file mode 100644 index b9f0fe79..00000000 --- a/other/sample/vod/offline.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "spider": "file://Download/custom_spider.jar", - "sites": [ - { - "key": "one", - "name": "One", - "type": 3, - "api": "csp_Csp", - "searchable": 1, - "changeable": 1, - "ext": "file://Download/one.json" - }, - { - "key": "two", - "name": "Two", - "type": 3, - "api": "csp_Csp", - "searchable": 1, - "changeable": 1, - "ext": "file://Download/two.json" - }, - { - "key": "extend", - "name": "Extend", - "type": 3, - "api": "csp_Csp", - "searchable": 1, - "changeable": 1, - "ext": "file://Download/extend.json", - "jar": "file://Download/extend.jar" - } - ], - "parses": [ - { - "name": "官方", - "type": 1, - "url": "https://google.com/api/?url=" - } - ], - "doh": [ - { - "name": "Google", - "url": "https://dns.google/dns-query", - "ips": [ - "8.8.4.4", - "8.8.8.8" - ] - } - ], - "headers": [ - { - "host": "gslbserv.itv.cmvideo.cn", - "header": { - "User-Agent": "okhttp/3.12.13" - } - } - ], - "proxy": [ - "raw.githubusercontent.com" - ], - "hosts": [ - "cache.ott.ystenlive.itv.cmvideo.cn=base-v4-free-mghy.e.cdn.chinamobile.com" - ], - "flags": [ - "qq" - ], - "ads": [ - "static-mozai.4gtv.tv" - ] -} \ No newline at end of file diff --git a/other/sample/vod/online.json b/other/sample/vod/online.json deleted file mode 100644 index 26abb632..00000000 --- a/other/sample/vod/online.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "spider": "https://github.com/custom_spider.jar", - "sites": [ - { - "key": "one", - "name": "One", - "type": 3, - "api": "csp_Csp", - "searchable": 1, - "changeable": 1, - "ext": "https://github.com/one.json" - }, - { - "key": "two", - "name": "Two", - "type": 3, - "api": "csp_Csp", - "searchable": 1, - "changeable": 1, - "ext": "https://github.com/two.json" - }, - { - "key": "extend", - "name": "Extend", - "type": 3, - "api": "csp_Csp", - "searchable": 1, - "changeable": 1, - "ext": "https://github.com/extend.json", - "jar": "https://github.com/extend.jar" - } - ], - "parses": [ - { - "name": "官方", - "type": 1, - "url": "https://google.com/api/?url=" - } - ], - "doh": [ - { - "name": "Google", - "url": "https://dns.google/dns-query", - "ips": [ - "8.8.4.4", - "8.8.8.8" - ] - } - ], - "headers": [ - { - "host": "gslbserv.itv.cmvideo.cn", - "header": { - "User-Agent": "okhttp/3.12.13" - } - } - ], - "proxy": [ - "raw.githubusercontent.com" - ], - "hosts": [ - "cache.ott.ystenlive.itv.cmvideo.cn=base-v4-free-mghy.e.cdn.chinamobile.com" - ], - "flags": [ - "qq" - ], - "ads": [ - "static-mozai.4gtv.tv" - ] -} \ No newline at end of file