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。
This commit is contained in:
@@ -24,9 +24,13 @@ object SongApiClient {
|
||||
fun init(context: Context) {
|
||||
if (bridge != null) return
|
||||
bridge = KtvJsBridge(context)
|
||||
// 原生兜底取址通道:复用桥的「干净请求」裸 Socket(与 JS 同一条路,风控表现一致),
|
||||
// 但**不依赖 WebView 是否可用** —— 老盒子上 WebView 不可用时唯一能取到地址的路径。
|
||||
SongUrlResolver.install { url -> bridge?.rawCleanGet(url) }
|
||||
bridge?.init { ok ->
|
||||
ready = ok
|
||||
Log.i(TAG, "JS Bridge init: $ok")
|
||||
if (!ok) MiliLog.error("JS-BRIDGE 初始化返回 false,取址将走原生兜底")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,10 +51,10 @@ object SongApiClient {
|
||||
fun getSongDownloadUrl(musicno: String, resolution: String, h265: Boolean): String? {
|
||||
// 主线程同步等待 WebView 回调会死锁;当前播放/下载流程会在 IO 线程调用这里。
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
Log.w(TAG, "Synchronous JS request on main thread; using DB fallback")
|
||||
return fetchFromDb(musicno)
|
||||
Log.w(TAG, "Synchronous JS request on main thread; using native/DB fallback")
|
||||
return resolveNatively(musicno, resolution, h265) ?: fetchFromDb(musicno)
|
||||
}
|
||||
val bridge = bridge ?: return fetchFromDb(musicno)
|
||||
val bridge = bridge ?: return resolveNatively(musicno, resolution, h265) ?: fetchFromDb(musicno)
|
||||
// 等待 JS 桥"当前"真正就绪。首次冷启动时隐藏 WebView 的初始化在低性能设备
|
||||
// (极米投影等)上可能超过 3 秒;桥热更新重载期间 isReady() 也会短暂为 false。
|
||||
// 这里放宽到 15 秒,避免第一首歌因撞上就绪窗口而回退到不可靠的 DB cloud_url。
|
||||
@@ -62,8 +66,11 @@ object SongApiClient {
|
||||
}
|
||||
}
|
||||
if (!bridge.isReady()) {
|
||||
Log.w(TAG, "Bridge not ready after wait, DB fallback")
|
||||
return fetchFromDb(musicno)
|
||||
// ⚠️ 老盒子(WebView 不可用)会一直走到这里。以前直接回退 DB,结果就是
|
||||
// 「无法生成下载地址」且远程日志里连一条 JS 日志都没有。现在改走原生解析器。
|
||||
Log.w(TAG, "Bridge not ready after wait, native resolver")
|
||||
MiliLog.warn("JS-BRIDGE 15s 未就绪,改用原生取址通道")
|
||||
return resolveNatively(musicno, resolution, h265) ?: fetchFromDb(musicno)
|
||||
}
|
||||
val result = AtomicReference<String?>()
|
||||
val latch = CountDownLatch(1)
|
||||
@@ -74,7 +81,9 @@ object SongApiClient {
|
||||
} catch (_: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
}
|
||||
val url = result.get() ?: fetchFromDb(musicno)
|
||||
// 桥可用但 JS 没取到地址(或超时)时,同样再给原生解析器一次机会,
|
||||
// 避免 JS 侧偶发失败直接把第一首歌判死。
|
||||
val url = result.get() ?: resolveNatively(musicno, resolution, h265) ?: fetchFromDb(musicno)
|
||||
if (url != null) {
|
||||
// 成功取到歌曲 URL → 歌曲源窗口已恢复/未到期, 复位到期提示状态。
|
||||
Eac19WindowGuard.noteUrlOk()
|
||||
@@ -82,9 +91,17 @@ object SongApiClient {
|
||||
return url
|
||||
}
|
||||
|
||||
/**
|
||||
* 原生兜底取址:WebView 不可用(老盒子)或 JS 没取到地址时使用。
|
||||
* 走的是与 JS 完全相同的裸 Socket 干净请求通道,见 [SongUrlResolver]。
|
||||
*/
|
||||
private fun resolveNatively(musicno: String, resolution: String, h265: Boolean): String? =
|
||||
runCatching { SongUrlResolver.resolve(musicno, resolution, h265) }
|
||||
.onFailure { Log.w(TAG, "native resolver failed", it) }
|
||||
.getOrNull()
|
||||
|
||||
/** DB cloud_url fallback — 必须通过 demo 检测才能使用 */
|
||||
private fun fetchFromDb(musicno: String): String? {
|
||||
try {
|
||||
private fun fetchFromDb(musicno: String): String? { try {
|
||||
val db = MuseDatabase()
|
||||
if (db.open()) {
|
||||
val url = db.getCloudUrl(musicno)
|
||||
|
||||
Reference in New Issue
Block a user