v8.8.0 切台提速:每频道独立锁(预取不再阻塞)+TTL30min+只预取高亮频道+缓存失效自动刷新源
This commit is contained in:
1 parent
bf4c32badc
commit
f1efc5dc5d
3 files changed
+58
-21
No files matched your search
@@ -11,8 +11,8 @@ android {
|
||||
applicationId = "com.iptv807.tv"
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
versionCode = 870
|
||||
versionName = "8.7.0"
|
||||
versionCode = 880
|
||||
versionName = "8.8.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user