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:
@@ -49,7 +49,7 @@ class KtvJsBridge(context: Context) {
|
||||
// mac 取本机「当前在用网卡」的硬件地址(读不到时回退历史抓包值, 见 DeviceMac);
|
||||
// sn 沿用抓包注册值, 保证 mac 变化后仍能对上原有账号体系。
|
||||
private val DEVICE_MAC: String get() = DeviceMac.effective()
|
||||
private const val DEVICE_SN = "2d7bb4a7d7f7"
|
||||
internal const val DEVICE_SN = "2d7bb4a7d7f7"
|
||||
|
||||
/**
|
||||
* 断网重连后重置歌曲源会话。
|
||||
@@ -98,6 +98,7 @@ class KtvJsBridge(context: Context) {
|
||||
updateCache(cached)
|
||||
} catch (error: Throwable) {
|
||||
Log.e(TAG, "WebView init failed", error)
|
||||
MiliLog.error("JS-BRIDGE WebView 初始化失败: ${error.javaClass.simpleName}: ${error.message}")
|
||||
callback(false)
|
||||
}
|
||||
}
|
||||
@@ -138,6 +139,7 @@ class KtvJsBridge(context: Context) {
|
||||
if (result == "true") {
|
||||
ready = true
|
||||
Log.i(TAG, "JS runtime ready (${if (loadedFromCache) "cache" else "asset"})")
|
||||
MiliLog.info("JS-BRIDGE ready (${if (loadedFromCache) "cache" else "asset"})")
|
||||
callback(true)
|
||||
} else if (loadedFromCache && !assetRetryAttempted) {
|
||||
Log.w(TAG, "Cached JS failed to load; falling back to packaged asset")
|
||||
@@ -146,6 +148,9 @@ class KtvJsBridge(context: Context) {
|
||||
createWebView(readAssetScript(), callback)
|
||||
} else {
|
||||
Log.e(TAG, "KtvApi was not exported by JS")
|
||||
// 老电视盒子上 WebView 不可用/被裁剪时常走这里:桥永远不就绪,
|
||||
// 取址只能回退 DB → 「无法生成下载地址」。必须能远程看到。
|
||||
MiliLog.error("JS-BRIDGE KtvApi 未导出,WebView 不可用")
|
||||
callback(false)
|
||||
}
|
||||
}
|
||||
@@ -158,6 +163,18 @@ class KtvJsBridge(context: Context) {
|
||||
/** 桥当前是否可用。热更新重载期间会短暂为 false,调用方应据此等待而非立即回退。 */
|
||||
fun isReady(): Boolean = ready && !destroyed
|
||||
|
||||
/**
|
||||
* 供**原生兜底取址解析器**([SongUrlResolver])复用的「干净请求」通道。
|
||||
*
|
||||
* 与 JS 的 `android.httpGet` 走的是**完全同一条**裸 Socket 路径(只发 Host + Accept,
|
||||
* 不带 UA/Connection/Accept-Encoding),因此请求特征与风控表现一致;
|
||||
* 且**不依赖 WebView 是否就绪** —— 正是 WebView 不可用的老设备所需要的。
|
||||
*/
|
||||
fun rawCleanGet(url: String, timeoutMs: Int = 8_000): String {
|
||||
val headers = JSONObject().put("Accept", "*/*").toString()
|
||||
return NativeBridge().request("GET", url, null, headers, timeoutMs)
|
||||
}
|
||||
|
||||
fun isDestroyed(): Boolean = destroyed
|
||||
|
||||
/**
|
||||
@@ -396,7 +413,8 @@ class KtvJsBridge(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun request(method: String, url: String, body: String?, headersJson: String, timeoutMs: Int): String {
|
||||
/** 供 [rawCleanGet] 与 [NativeBridge.httpGet]/[httpPost] 共用的请求入口。 */
|
||||
internal fun request(method: String, url: String, body: String?, headersJson: String, timeoutMs: Int): String {
|
||||
// 歌曲源(如 w.w345.my)的 do.php 风控只放行"干净"请求(原版仅 Host+Accept;
|
||||
// 带任何 UA/Connection/Accept-Encoding 头都会被拒)。裸 Socket 只发 Host+JS 头。
|
||||
// 任何 HTTP 状态(200/403 等)都直接用裸 Socket 结果,不再用 HttpURLConnection
|
||||
|
||||
Reference in New Issue
Block a user