Commit Graph
9 Commits
Author SHA1 Message Date
zijunchiang 1687f87eca V1.4.7: 新增原生取址兜底 —— 修复 WebView 不可用的老盒子无法点歌
证据(用户导出的 1.4.6 远程日志):
- 两条失败记录只有 Kotlin 侧那一行
  「点歌编号=... 详情=无法生成下载地址」,
  而 JS ...(本版新加的 console 桥)、RAW-ERR、NET-ERR、点歌失败 一条都没有。
- 即 JS 侧从未执行 —— 隐藏 WebView 在这台盒子上从未就绪
  (每条失败前正好有约 15s 的「等桥就绪」超时,时间吻合)。
- 于是 SongApiClient 只能回退 DB 的 cloud_url,取不到就报「无法生成下载地址」。

根因: 取址完全依赖 WebView 里的 ktv_api.js;GMUI2.0/Android 5.1.1 上
WebView 不可用(无法导出 window.KtvApi),桥永远 not ready。

修复:
- 新增 SongUrlResolver: 用 Kotlin 复刻 ktv_api.js 的协议
  (GET <host>/i.php 取 token → GET <host>/music/do.php 取地址),
  镜像多源排序(失败源沉底)与 isDemoUrl 校验(wb66/demo/纯IP/r2.dev/ts编号不一致)。
- 走与 JS 完全相同的裸 Socket「干净请求」通道(KtvJsBridge.rawCleanGet),
  请求特征一致故风控表现一致,且不依赖 WebView 是否就绪。
- SongApiClient.getSongDownloadUrl 三处回退点改为:
  桥未就绪 / JS 未取到地址 / 主线程调用 → 先走原生解析,再回退 DB。
- 补齐桥的生命周期可观测性: WebView 初始化失败、KtvApi 未导出、
  15s 未就绪 均写入远程日志(以前只在 logcat,这就是本轮一开始没有线索的原因)。

已用 App 的真实身份( mac=800b510bf4fd / sn=2d7bb4a7d7f7 / VER=2.0 )
从本机复现协议: i.php 200 → do.php code=200 且返回
  http://txog.ktvsky.com/.../480p/7303235.ts (与点歌编号一致),
证明参数顺序与签名算法正确。

版本 1.4.6(26) 到 1.4.7(27);签名证书不变。ktv_api.js 未改动,KTV_BRIDGE_API 仍为 8。
2026-09-26 17:08:38 +08:00
zijunchiang 0cffd08789 V1.4.6: 点歌取址失败——修裸 Socket 通道两处缺陷 + 打通 JS 侧日志
现象: 曲库正常后点歌失败「详情=无法生成下载地址」,且远程日志里没有任何线索。

排查(本地复现源站协议):
- 用同一套签名从本机请求 i.php / music/do.php 全部正常,能拿到这首歌的真实地址;
  换 App 注入的版本号(VER=2.0/VN=4.1.3.03281430) 也正常 → 源站没问题,是设备侧。
- 根因在 KtvJsBridge.rawRequest() 这条裸 Socket 通道(点歌取址正是走它):
  1. 连接阶段没有超时: `Socket(host, port)` 不受 soTimeout 约束,遇路由黑洞按系统
     TCP 超时(~2min)干等;7 个源叠加远超 JS_REQUEST_TIMEOUT_MS(180s) →
     桥返回空 → 「无法生成下载地址」,而日志里连失败都没有(请求还没失败);
  2. HTTPS 分支用 SSLSocketFactory.getDefault(),既不受
     HttpsURLConnection.setDefaultSSLSocketFactory 影响,也不受
     network_security_config 约束 → 老设备缺 ISRG 新根时握手必失败。

修复:
- rawRequest 连接改为显式 connect(timeout): Socket().connect(InetSocketAddress, t);
  HTTPS 改用 TlsCompat.sslSocketFactory()(系统信任 ∪ 内置根证书)。
- TlsCompat 暴露 sslSocketFactory(),并按 API 分级: 裸通道始终用增强工厂,
  HttpsURLConnection 默认工厂仅在 API<24 接管。
- 裸通道失败上报 RAW-ERR(原来只在 logcat)。
- 打通 JS 侧日志: wrapHtml 注入 console 桥,把 ktv_api.js 的 _log()/console.log
  转发到 android.log → MiliLog("JS ...");点歌失败时可通过
  「JS ...」逐步过程 + 「点歌失败 ... 详情=」汇总原因定位。

版本 1.4.5(25) 到 1.4.6(26);签名证书不变。ktv_api.js 未改动,KTV_BRIDGE_API 仍为 8。
2026-09-26 16:59:43 +08:00
zijunchiang 62216b0fb8 V1.4.3: 修复老系统(API<24)不认 Let's Encrypt 新根,导致无法下载曲库
根因(用远程日志的 STORAGE 行 + 本地复现 Muse 协议定位):
- 曲库服务 mconn.cherryonline.cn 与点歌登录 mws.cherryonline.cn 都用 Let's Encrypt 新链
  leaf ← YR2 ← Root YR ← ISRG Root X1。
- res/xml/network_security_config.xml 里虽然已把这三个根加为信任锚,但
  **该配置只在 Android 7.0(API 24)+ 被系统读取**;Android 5.1.1 上被完全忽略,
  系统信任库又没有 ISRG 新根 → SSLHandshakeException: Trust anchor for certification
  path not found → login 失败 → fetchVersion()=null → 根本不触发下载,
  界面只剩兜底文案「未找到可用曲库」,而失败只写 logcat,远程日志里看不到。
- 这也解释了为什么只有这台老设备出问题(其它设备都 >= API 24)。

修复:
- 新增 TlsCompat: 在 API<24 上把「系统默认信任 ∪ 内置根证书」的复合 X509TrustManager
  装到全局 HttpsURLConnection.setDefaultSSLSocketFactory,覆盖 App 内所有 HttpsURLConnection
  (曲库同步/JS 热更新下载/更新检查/头像下载)。主机名校验保持系统默认,不放行任意证书。
- WebView 的 TLS 无法用 SSLContext 覆盖(JS 会直连 mws): 在 KtvJsBridge 的
  onReceivedSslError 里不盲目 proceed,先用 TlsCompat.isHostTrustedByOurStore(host)
  对同一主机独立校验,通过才放行。
- TlsCompat.install 在 KtvApplication.attachBaseContext 调用(早于任何网络请求)。
- 补齐可观测性: 曲库同步每一步失败改为 MiliLog 上报
  (MUSE-DB login/sync ...、DB-SYNC download failed/空间不足/成功);
  fetchRemoteVersion 失败时设置明确的 bootstrapError,不再只显示兜底文案。
- AGENTS.md 增补「老系统 TLS 根证书」条目。

版本 1.4.2(22) → 1.4.3(23);签名证书不变。
2026-09-26 16:06:27 +08:00
zijunchiang 40d1fec719 V1.4.1: 修复 Android 5.1.1 设备一打开就闪退(API 守卫缺失)
根因(极米 H1S / MStar 6A928 / GMUI2.0 / Android 5.1.1):
- MainActivity 把 API 23 才有的 android.media.AudioDeviceCallback 写成了
  **字段初始化器**(private val micDeviceCallback = object : ...)。
  字段初始化在 Activity 构造期执行,早于 onCreate 里的 SDK_INT 判断,
  于是下面的 registerMicInputWatcher() 版本守卫形同虚设 ——
  构造 MainActivity 即抛 NoClassDefFoundError,表现为一打开就闪退。
- 顺带修复 36 处同类未守卫的高版本 API 调用(lint NewApi 全量清零):
  * AudioDeviceCallback / AudioDeviceInfo.isSource/getType (API 23)
    → 改为可空字段 + 仅在 SDK_INT>=23 分支按需创建(@TargetApi)
  * HashMap.putIfAbsent ×5 (API 24) → MutableMap.getOrPut
  * Comparator.comparing ×3 + comparingInt ×1 (API 24) → Kotlin compareBy
    (同时移除 java.util.function 导入)
  * ConcurrentHashMap.newKeySet ×2 (API 24) → Collections.newSetFromMap
    (连带 16 处 KeySetView.add/remove/contains)
  * ConcurrentHashMap.computeIfAbsent (API 24) → ConcurrentHashMap.putIfAbsent(API 1)
  * URLConnection.getContentLengthLong ×5 (API 24) → 新增 Compat.contentLengthCompat()
  * ByteArrayOutputStream.toString(Charset) (API 33) → String(bytes, UTF_8)

防回归:
- app/build.gradle 打开 lint 闸门: checkReleaseBuilds true + abortOnError true +
  fatal += NewApi(并 disable ExpiredTargetSdkVersion,避免它挤占闸门)。
  已实测: 植入一个 NewApi 违规后 assembleRelease 直接 FAILED。
- AGENTS.md 新增「低版本设备闪退(API 守卫)」条目,记录根因、替代写法与自查命令。

版本号 1.4.0(16) → 1.4.1(17);签名证书不变,可覆盖安装。
2026-09-26 13:59:51 +08:00
zijunchiang 942ec527f1 V1.4.0: 修复断网重连后无法下载 + 网络状态检测维护
根因(断网重连后再也下载不了、日志却显示"歌曲源获取成功"):
SongApiClient.clearTokenCache() 一直是**空实现**。JS 侧 window.api.tokens 与
_apiInitPromise 因此被一直复用; 而歌曲源按「出口 IP + device」识别、试用窗口
也记在出口 IP 上(见 Eac19WindowGuard), 断网/重连(尤其换出口 IP)后旧 token 已失效 ——
取址这一步看起来成功, 真正下载却 403/失败。

1) 网络状态检测(新增 KtvNetworkMonitor)
   走 ConnectivityManager 回调(不轮询)维护全局 online, 状态变化才回调主线程;
   全局可读 online 供下载层判断失败是否由断网引起。

2) 播放页右上角「网络未连接」提示
   全屏播放页控制层 header 内、标题右侧新增红色徽标(不遮挡右上角二维码),
   断网显示、恢复自动隐藏。

3) 网络恢复/换网后的统一维护 refreshNetworkState()
   - 重建歌曲源会话: clearTokenCache() 真正下发 KtvJsBridge.resetApiSession()
     (置空 window.api 与 _apiInitPromise, 下次取址重新构造 API 实例、回到默认源顺序
      并重新登录拿新 token);
   - 复位 Eac19WindowGuard 窗口守卫; 重解析网卡 MAC + 刷新日志上下文;
   - 重启本地点歌服务并刷新二维码;
   - 自动重试断网期间失败的下载(最多 8 首)。
   断网恢复走该路径(带提示), 换网换 IP 也走同一路径(静默)。

4) 下载层网络失败跟踪(SongOkDownloadManager)
   结合 KtvNetworkMonitor.online 与异常类型(UnknownHost/Connect/SocketTimeout/
   NoRouteToHost/SSL/EOF)及 HTTP 0 判定网络类失败, 记入 networkFailed;
   成功后清除、手动取消不入集合; retryNetworkFailed() 供恢复时调用。

5) 顺带修掉一个隐患: 离线时 localIp() 为空会让 remoteNetworkMonitor 每 5s 误判
   "IP 变化"而反复重启本地点歌服务; 现已加空值短路, 离线只等网络回调。

版本号 1.3.9 -> 1.4.0(versionCode 15 -> 16)。按约定本次不发布 Release, 仅本地打包测试。
2026-09-21 03:34:29 +08:00
zijunchiang 485235e96e V1.3.1: 设备MAC改取当前网卡 + 播放界面当前/下一首实时刷新 + 日志IP与启动信息调整
1) 设备 MAC: 新增 DeviceMac 解析「当前在用网卡」硬件地址(优先 wlan/eth/en,
   忽略虚拟/蜂窝/VPN; 直接读 NetworkInterface.getHardwareAddress, 规避 Android 6+
   WifiInfo 恒返回 02:00:00:00:00:00 的限制), 读出无效值或失败时回退历史抓包值。
   结果按进程缓存, 网络切换时由 remoteNetworkMonitor 重解析。
   消费方: KtvJsBridge.DEVICE_MAC(JS 端 mac)、whoami 心跳 _mac/androidid/_cid。
2) 播放界面: 抽出 refreshNowPlayingLabels() 作为「当前播放/下一首」的唯一刷新入口,
   只在文本变化时写 View, 并挂到 lyricTicker(500ms), 使播放界面左上角、首页播放条、
   旧版播放区与顶部状态栏在本地点歌/扫码点歌/自动切歌后都能实时同步;
   顶部状态栏的「下首」改为按当前曲目位置取其后一首(原先固定取队列第 2 首)。
3) 远程日志: serverip 改为主机 IP(公网出口 IP, 查询后缓存 5 分钟、失败 1 分钟后重试,
   后台线程执行), 新增 clientip 字段承载设备网卡获取到的局域网 IP; 启动上报改为
   采集 10 项: 包名、版本号、安装时间、品牌、型号、设备名、Android 版本、CPU 架构、
   内核版本、屏幕分辨率。
4) 版本号 1.3.0 -> 1.3.1(versionCode 6 -> 7)。本次不发布 Release, 仅本地打包测试。
2026-09-14 02:59:21 +08:00
zijunchiang ba31caaa38 全量迁移到 Gitea: App更新检查/JS热更新端点上链 Gitea, 文档同步
- AppUpdateManager: releases/latest 改用 gitea.kasugano.cn API
- KtvJsBridge: JS 热更新改用 Gitea raw(/raw/branch/master/...)
- MainActivity/SongApiClient/DatabaseBootstrapper/MuseDbSync: 文案与注释更新
- README.md/README.en.md: 标注本仓库地址(Gitea), 保留上游框架归属链接
- AGENTS.md: 运行时端点迁移状态
2026-09-11 23:08:46 +08:00
zijunchiang 2d3751064f 歌曲源: 新增6源(desktok.top/mm.kk456.top/w.w345.my:66/z.w345.my/b.sktv.top/w345.my) + 多源自动排序
- ktv_api.js: HOSTS 扩为 7 源(同后台多入口), 缓存标记 7→8
- 自动排序: 解析失败的源 demote 到队尾, 成功源 promote 到队首(sourceOrder 运行时可变)
- JS→Kotlin 上报实际命中源(reportSongResult), 远程日志请求源不再写死
- SongApiClient/KtvJsBridge: lastSongSourceHost 供日志使用
2026-09-04 23:06:05 +08:00
zijunchiang 0c26f6da68 麦动KTV: 移除Gitee数据库(改官方Muse同步), 歌曲源切w.w345.my, 远程日志+广告屏蔽
- DatabaseBootstrapper: 删除 Gitee 分片/manifest/blob 下载逻辑, 曲库只走官方 MuseDbSync
- build.gradle: 移除 GITEE_DATABASE_* 构建参数
- MainActivity: 重置数据库文案改为官方同步
- ktv_api.js: 歌曲源仅 w.w345.my, 缓存标记 7, 广告/错号资源拦截+重试
- 远程日志: 点歌记录 编号/标题/请求源/状态
2026-09-04 21:23:21 +08:00