v8.8.0 切台提速:每频道独立锁(预取不再阻塞)+TTL30min+只预取高亮频道+缓存失效自动刷新源

This commit is contained in:
Hermes Agent committed 2026-10-08 13:26:35 +08:00
1 parent bf4c32badc
commit f1efc5dc5d
3 files changed
+58 -21

No files matched your search

@@ -67,7 +67,8 @@ class MainActivity : Activity() {
override fun run() {
if (loadingMask.visibility != View.VISIBLE) return
val s = ((System.currentTimeMillis() - loadStart) / 1000 + 1).toInt()
loadingText.text = "正在加载 $s 秒,精彩继续…"
loadingText.text = if (loadChannelName.isEmpty()) "正在加载 $s 秒,精彩继续…"
else "正在加载「$loadChannelName」$s 秒…"
handler.postDelayed(this, 1000)
}
}
@@ -81,6 +82,8 @@ class MainActivity : Activity() {
private var isVip = false
private var lastPlayError = ""
private var lastSelfTest = ""
private var loadChannelName = ""
private var triedFresh = false
private var failureReport = ""
private var wasStopped = false
private var resumeBtn: TextView? = null
@@ -228,10 +231,8 @@ class MainActivity : Activity() {
if (savedInstanceState == null && channels.isNotEmpty()) {
val defIdx = channels.indexOfFirst { it.name.contains("TVB翡翠台") }.let { if (it >= 0) it else 0 }
playChannel(defIdx)
// 启动后台预取默认频道所在分组,之后切台秒开
val g = channels.getOrNull(defIdx)?.group
val gi = ChannelData.groups().indexOfFirst { it.name == g }
if (gi >= 0) handler.postDelayed({ prefetchGroup(gi) }, 2500)
// 启动后预取默认频道的相邻频道(很轻量,1 个一个来)
handler.postDelayed({ schedulePrefetch(channels.getOrNull(defIdx)) }, 2000)
}
}
@@ -422,6 +423,13 @@ class MainActivity : Activity() {
if (ev.action == MotionEvent.ACTION_DOWN || ev.action == MotionEvent.ACTION_MOVE) resetHideTimer()
v.onTouchEvent(ev)
}
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, v: View?, pos: Int, id: Long) {
val list = channelsOfGroup(curGroupPos)
if (pos in list.indices) schedulePrefetch(list[pos])
}
override fun onNothingSelected(parent: AdapterView<*>?) { }
}
setOnScrollListener(object : AbsListView.OnScrollListener {
override fun onScrollStateChanged(v: AbsListView, s: Int) { resetHideTimer() }
override fun onScroll(v: AbsListView, a: Int, b: Int, c: Int) { resetHideTimer() }
@@ -432,26 +440,27 @@ class MainActivity : Activity() {
}
private fun updateChannelListOfGroup(pos: Int) {
prefetchGroup(pos)
val list = channelsOfGroup(pos)
channelList?.adapter = RowAdapter(list.map { "%3d %s".format(it.num, it.name) })
}
private fun hidePanel() { handler.removeCallbacks(hidePanelTask); panelLayout?.visibility = View.GONE }
/** 后台预取某分组所有频道的源,让切台变秒切(每次运行只做一轮) */
private var prefetchedGroups = HashSet<Int>()
private fun prefetchGroup(gpos: Int) {
if (prefetchedGroups.contains(gpos)) return
prefetchedGroups.add(gpos)
val list = channelsOfGroup(gpos)
/** 只预取"当前高亮的那一个频道"(1.2 秒去抖动)——源站很脆,不做整组预取 */
private val prefetchTask = Runnable {
val ch = prefetchTarget ?: return@Runnable
if (SourceResolver.hasCache(ch.tid, ch.id)) return@Runnable
Thread {
for (ch in list) {
try { SourceResolver.resolveLines(ch.tid, ch.id) } catch (e: Exception) { }
try { Thread.sleep(250) } catch (ie: InterruptedException) { }
}
try { SourceResolver.resolveLines(ch.tid, ch.id) } catch (e: Exception) { }
}.start()
}
private var prefetchTarget: ChannelData.Channel? = null
private fun schedulePrefetch(ch: ChannelData.Channel?) {
if (ch == null) return
prefetchTarget = ch
handler.removeCallbacks(prefetchTask)
handler.postDelayed(prefetchTask, 1200)
}
private fun resetHideTimer() {
handler.removeCallbacks(hidePanelTask)
handler.postDelayed(hidePanelTask, 5000)
@@ -473,6 +482,8 @@ class MainActivity : Activity() {
val isSameChannel = (idx == currentChannel)
currentChannel = idx; currentLine = line
val ch = channels[idx]
if (!isSameChannel) triedFresh = false
loadChannelName = ch.name
if (!isSameChannel || loadingMask.visibility != View.VISIBLE) {
beginLoading() // 只有换台才重置计时;同台换线路时继续计时
}
@@ -565,8 +576,25 @@ class MainActivity : Activity() {
val ch = channels.getOrNull(currentChannel) ?: return
currentLine++
if (currentLine > 12) {
// 可能是缓存里的 token 过期了 → 强制刷新一次源再试一轮
if (!triedFresh) {
triedFresh = true
statusText.text = "线路失效,正在刷新源…"
Thread {
val fresh = try { SourceResolver.resolveFresh(ch.tid, ch.id) } catch (e: Exception) { null }
runOnUiThread {
if (!fresh.isNullOrEmpty()) { currentLine = 0; playChannel(currentChannel, 0) }
else {
val vn = try { packageManager.getPackageInfo(packageName, 0).versionName } catch (e: Exception) { "?" }
statusText.text = "⚠ ${ch.num} ${ch.name} 全部线路失败\n$lastPlayError\n解析: ${SourceResolver.lastError}\n版本: v$vn"
hideLoading()
}
}
}.start()
return
}
val vn = try { packageManager.getPackageInfo(packageName, 0).versionName } catch (e: Exception) { "?" }
statusText.text = "⚠ ${ch.num} ${ch.name} 全部线路失败\n$lastPlayError\n解析: ${SourceResolver.lastError}\n$failureReport\n版本: v$vn"
statusText.text = "⚠ ${ch.num} ${ch.name} 全部线路失败\n$lastPlayError\n解析: ${SourceResolver.lastError}\n版本: v$vn"
hideLoading()
return
}
@@ -27,7 +27,9 @@ object SourceResolver {
@Volatile private var cache: MutableMap<String, Cached>? = null
private data class Cached(val lines: List<String>, val ts: Long)
private const val TTL = 8 * 60 * 1000L
/** 每频道独立锁:避免后台预取把用户切台的请求挡住 */
private val locks = ConcurrentHashMap<String, Any>()
private const val TTL = 30 * 60 * 1000L
private const val BASE = "https://m.iptv807.com/"
private const val UA = "Mozilla/5.0 (Linux; Android 12; SM-S901B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36"
@@ -56,6 +58,12 @@ object SourceResolver {
/** 兼容之前的 method signature */
fun resolve(tid: String, id: String): List<String>? = resolveLines(tid, id)
/** 该频道是否已有可用缓存(不触发网络请求) */
fun hasCache(tid: String, id: String): Boolean {
val c = cache?.get("$tid-$id") ?: return false
return System.currentTimeMillis() - c.ts < TTL
}
/** 强制丢弃缓存重新抓源(用于"缓存线路全失效"时) */
fun resolveFresh(tid: String, id: String): List<String>? {
cache?.remove("$tid-$id")
@@ -66,7 +74,8 @@ object SourceResolver {
val key = "$tid-$id"
cache?.get(key)?.let { if (System.currentTimeMillis() - it.ts < TTL) return it.lines }
synchronized(this) {
val lock = locks.getOrPut(key) { Any() }
synchronized(lock) {
// double-check
cache?.get(key)?.let { if (System.currentTimeMillis() - it.ts < TTL) return it.lines }
try {