扫码点歌优化: 对话框显示ip:端口+隐藏提示; 手机连接时自动隐藏全屏右上角二维码

- showMobileRemoteDialog: 二维码下显示当前服务 ip:端口 与提示文字
- LocalRemoteServer: 记录请求活跃时间 hasRecentActivity()
- MainActivity: qrAutoHideMonitor 每秒检测, 手机连接点歌(4s内有请求)自动隐藏
  全屏右上角二维码, 断开后恢复显示
This commit is contained in:
zijunchiang
2026-09-05 23:10:37 +08:00
parent b5e605bade
commit 9dc0d4b994
2 changed files with 44 additions and 0 deletions
@@ -39,6 +39,7 @@ class LocalRemoteServer(private val context: Context) {
@Volatile private var boundAddress: String? = null
@Volatile private var boundPort = -1
@Volatile private var lastError: String? = null
@Volatile private var lastActivityAtMs = 0L
private var server: ServerSocket? = null
private var callback: Callback? = null
@@ -98,6 +99,13 @@ class LocalRemoteServer(private val context: Context) {
fun port(): Int = boundPort
fun address(): String? = boundAddress
fun error(): String? = lastError
/** 是否在 [withinMs] 内有手机端请求(扫码点歌页面可见时每秒轮询 state)。 */
fun hasRecentActivity(withinMs: Long): Boolean {
val last = lastActivityAtMs
return last > 0 && System.currentTimeMillis() - last <= withinMs
}
fun url(): String? = if (running && boundAddress != null && boundPort > 0) {
"http://$boundAddress:$boundPort/mobile"
} else null
@@ -142,6 +150,8 @@ class LocalRemoteServer(private val context: Context) {
val path = target.substringBefore('?')
val query = parseQuery(target.substringAfter('?', ""))
val body = String(bodyBytes, 0, offset, Charsets.UTF_8)
// 手机端已成功建立会话并访问页面/接口 → 记录活跃(用于控制二维码显隐)。
lastActivityAtMs = System.currentTimeMillis()
val response = when {
path == "/" || path == "/mobile" || path == "/mobile/" -> asset("mobile/index.html", "text/html; charset=utf-8")
path == "/mobile/app.css" -> asset("mobile/app.css", "text/css; charset=utf-8")
@@ -163,6 +163,26 @@ class MainActivity : AppCompatActivity() {
main.postDelayed(this, 5000)
}
}
/** 全屏右上角扫码点歌二维码显隐控制: 手机连接点歌(页面可见时每秒轮询 state)
* 期间自动隐藏, 断开(无请求超过约 4 )后恢复显示 */
private val qrAutoHideMonitor = object : Runnable {
private var hiddenByClient = false
override fun run() {
val overlay = fullScreenQrOverlay
if (overlay != null && isFullScreen) {
val active = remoteServer.hasRecentActivity(4000)
if (active && !hiddenByClient) {
hiddenByClient = true
overlay.visibility = View.GONE
} else if (!active && hiddenByClient) {
hiddenByClient = false
overlay.visibility = View.VISIBLE
}
}
main.postDelayed(this, 1000)
}
}
/** 周期向 hbeat.ktvsky.com 上报设备身份,维持歌曲源对"合法设备"的判定。 */
private val ktvHeartbeatRunnable = object : Runnable {
override fun run() {
@@ -471,6 +491,7 @@ class MainActivity : AppCompatActivity() {
startRemoteServer()
updateMobileQrOverlay()
main.postDelayed(remoteNetworkMonitor, 5000)
main.postDelayed(qrAutoHideMonitor, 1000)
// 设备心跳:开机立即上报一次(boot=1),之后每 3600s 保活,维持歌曲源合法设备判定。
sendDeviceHeartbeat(boot = true)
main.postDelayed(ktvHeartbeatRunnable, HEARTBEAT_INTERVAL_MS)
@@ -564,6 +585,7 @@ class MainActivity : AppCompatActivity() {
main.removeCallbacks(homeBannerRunnable)
main.removeCallbacks(autoFullscreenRunnable)
main.removeCallbacks(remoteNetworkMonitor)
main.removeCallbacks(qrAutoHideMonitor)
stopRecording(false)
releaseVocalPlayer()
if (::playbackEngine.isInitialized) playbackEngine.release()
@@ -2963,6 +2985,18 @@ class MainActivity : AppCompatActivity() {
val tip = label("手机连接同一局域网后扫码点歌", 18, Color.WHITE)
tip.gravity = Gravity.CENTER
box.addView(tip, LinearLayout.LayoutParams(dp(380), dp(54)))
// 显示当前服务的 ip:端口 地址(去掉路径,便于对照路由器/局域网排查)。
val serverHost = remoteServer.address().orEmpty()
val serverPort = remoteServer.port()
if (serverHost.isNotEmpty() && serverPort > 0) {
val addressLine = label("$serverHost:$serverPort", 16, GOLD)
addressLine.gravity = Gravity.CENTER
box.addView(addressLine, LinearLayout.LayoutParams(dp(380), dp(34)))
}
// 提示:扫码连上后播放界面右上角二维码会自动隐藏。
val hint = label("扫码点歌后自动隐藏播放界面二维码", 14, Color.rgb(193, 193, 193))
hint.gravity = Gravity.CENTER
box.addView(hint, LinearLayout.LayoutParams(dp(420), dp(30)))
AlertDialog.Builder(this)
.setTitle("扫码点歌")
.setView(box)