From 9dc0d4b9947c06b693a8720f6c2fe4127ced9197 Mon Sep 17 00:00:00 2001 From: zijunchiang Date: Sat, 5 Sep 2026 23:10:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=AB=E7=A0=81=E7=82=B9=E6=AD=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96:=20=E5=AF=B9=E8=AF=9D=E6=A1=86=E6=98=BE=E7=A4=BAip:?= =?UTF-8?q?=E7=AB=AF=E5=8F=A3+=E9=9A=90=E8=97=8F=E6=8F=90=E7=A4=BA;=20?= =?UTF-8?q?=E6=89=8B=E6=9C=BA=E8=BF=9E=E6=8E=A5=E6=97=B6=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E9=9A=90=E8=97=8F=E5=85=A8=E5=B1=8F=E5=8F=B3=E4=B8=8A=E8=A7=92?= =?UTF-8?q?=E4=BA=8C=E7=BB=B4=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - showMobileRemoteDialog: 二维码下显示当前服务 ip:端口 与提示文字 - LocalRemoteServer: 记录请求活跃时间 hasRecentActivity() - MainActivity: qrAutoHideMonitor 每秒检测, 手机连接点歌(4s内有请求)自动隐藏 全屏右上角二维码, 断开后恢复显示 --- .../java/com/local/ktv/LocalRemoteServer.kt | 10 ++++++ .../main/java/com/local/ktv/MainActivity.kt | 34 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/app/src/main/java/com/local/ktv/LocalRemoteServer.kt b/app/src/main/java/com/local/ktv/LocalRemoteServer.kt index 7c36be3..60292f4 100644 --- a/app/src/main/java/com/local/ktv/LocalRemoteServer.kt +++ b/app/src/main/java/com/local/ktv/LocalRemoteServer.kt @@ -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") diff --git a/app/src/main/java/com/local/ktv/MainActivity.kt b/app/src/main/java/com/local/ktv/MainActivity.kt index bc710c4..d3569c6 100644 --- a/app/src/main/java/com/local/ktv/MainActivity.kt +++ b/app/src/main/java/com/local/ktv/MainActivity.kt @@ -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)