优化控制台与设置页切换时的 WebRTC 会话保活与恢复逻辑

This commit is contained in:
mofeng-git
2026-03-27 11:29:27 +08:00
parent 6bcb54bd22
commit 1c5288d783
3 changed files with 118 additions and 33 deletions

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { onMounted, watch } from 'vue'
import { KeepAlive, onMounted, watch } from 'vue'
import { RouterView, useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { useSystemStore } from '@/stores/system'
@@ -56,5 +56,10 @@ watch(
</script>
<template>
<RouterView />
<RouterView v-slot="{ Component, route }">
<KeepAlive v-if="authStore.isAuthenticated">
<component :is="Component" v-if="route.name === 'Console'" />
</KeepAlive>
<component :is="Component" v-if="route.name !== 'Console' || !authStore.isAuthenticated" />
</RouterView>
</template>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed, watch, nextTick } from 'vue'
import { ref, onMounted, onUnmounted, onActivated, onDeactivated, computed, watch, nextTick } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useSystemStore } from '@/stores/system'
@@ -137,6 +137,8 @@ let accumulatedDelta = { x: 0, y: 0 } // For relative mode: accumulate deltas be
// Cursor visibility (from localStorage, updated via storage event)
const cursorVisible = ref(localStorage.getItem('hidShowCursor') !== 'false')
let interactionListenersBound = false
const isConsoleActive = ref(false)
function syncMouseModeFromConfig() {
const mouseAbsolute = configStore.hid?.mouse_absolute
@@ -610,6 +612,7 @@ function waitForVideoFirstFrame(el: HTMLVideoElement, timeoutMs = 2000): Promise
function shouldSuppressAutoReconnect(): boolean {
return videoMode.value === 'mjpeg'
|| !isConsoleActive.value
|| videoSession.localSwitching.value
|| videoSession.backendSwitching.value
|| videoRestarting.value
@@ -1951,6 +1954,10 @@ function handlePointerLockError() {
isPointerLocked.value = false
}
function handleFullscreenChange() {
isFullscreen.value = !!document.fullscreenElement
}
function handleBlur() {
pressedKeys.value = []
activeModifierMask.value = 0
@@ -2003,6 +2010,71 @@ function handleMouseSendIntervalStorage(e: StorageEvent) {
setMouseMoveSendInterval(loadMouseMoveSendIntervalFromStorage())
}
function registerInteractionListeners() {
if (interactionListenersBound) return
window.addEventListener('keydown', handleKeyDown)
window.addEventListener('keyup', handleKeyUp)
window.addEventListener('blur', handleBlur)
window.addEventListener('mouseup', handleWindowMouseUp)
window.addEventListener('hidCursorVisibilityChanged', handleCursorVisibilityChange as EventListener)
window.addEventListener('hidMouseSendIntervalChanged', handleMouseSendIntervalChange as EventListener)
window.addEventListener('storage', handleMouseSendIntervalStorage)
document.addEventListener('pointerlockchange', handlePointerLockChange)
document.addEventListener('pointerlockerror', handlePointerLockError)
document.addEventListener('fullscreenchange', handleFullscreenChange)
interactionListenersBound = true
}
function unregisterInteractionListeners() {
if (!interactionListenersBound) return
window.removeEventListener('keydown', handleKeyDown)
window.removeEventListener('keyup', handleKeyUp)
window.removeEventListener('blur', handleBlur)
window.removeEventListener('mouseup', handleWindowMouseUp)
window.removeEventListener('hidCursorVisibilityChanged', handleCursorVisibilityChange as EventListener)
window.removeEventListener('hidMouseSendIntervalChanged', handleMouseSendIntervalChange as EventListener)
window.removeEventListener('storage', handleMouseSendIntervalStorage)
document.removeEventListener('pointerlockchange', handlePointerLockChange)
document.removeEventListener('pointerlockerror', handlePointerLockError)
document.removeEventListener('fullscreenchange', handleFullscreenChange)
interactionListenersBound = false
}
async function activateConsoleView() {
isConsoleActive.value = true
registerInteractionListeners()
if (videoMode.value !== 'mjpeg' && webrtc.videoTrack.value) {
await nextTick()
await rebindWebRTCVideo()
return
}
if (
videoMode.value !== 'mjpeg'
&& !webrtc.isConnected.value
&& !webrtc.isConnecting.value
&& !videoSession.localSwitching.value
&& !videoSession.backendSwitching.value
&& !initialModeRestoreInProgress
) {
await connectWebRTCOnly(videoMode.value)
}
}
function deactivateConsoleView() {
isConsoleActive.value = false
handleBlur()
exitPointerLock()
unregisterInteractionListeners()
}
// ActionBar handlers
// (MSD and Settings are now handled by ActionBar component directly)
@@ -2074,30 +2146,12 @@ onMounted(async () => {
syncMouseModeFromConfig()
}).catch(() => {})
window.addEventListener('keydown', handleKeyDown)
window.addEventListener('keyup', handleKeyUp)
window.addEventListener('blur', handleBlur)
window.addEventListener('mouseup', handleWindowMouseUp)
setMouseMoveSendInterval(loadMouseMoveSendIntervalFromStorage())
// Listen for cursor visibility changes from HidConfigPopover
window.addEventListener('hidCursorVisibilityChanged', handleCursorVisibilityChange as EventListener)
window.addEventListener('hidMouseSendIntervalChanged', handleMouseSendIntervalChange as EventListener)
window.addEventListener('storage', handleMouseSendIntervalStorage)
watch(() => configStore.hid?.mouse_absolute, () => {
syncMouseModeFromConfig()
})
// Pointer Lock event listeners
document.addEventListener('pointerlockchange', handlePointerLockChange)
document.addEventListener('pointerlockerror', handlePointerLockError)
document.addEventListener('fullscreenchange', () => {
isFullscreen.value = !!document.fullscreenElement
})
const storedTheme = localStorage.getItem('theme')
if (storedTheme === 'dark' || (!storedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
isDark.value = true
@@ -2118,7 +2172,17 @@ onMounted(async () => {
}
})
onActivated(() => {
void activateConsoleView()
})
onDeactivated(() => {
deactivateConsoleView()
})
onUnmounted(() => {
deactivateConsoleView()
// Reset initial device info flag
initialDeviceInfoReceived = false
initialModeRestoreDone = false
@@ -2154,18 +2218,6 @@ onUnmounted(() => {
// Exit pointer lock if active
exitPointerLock()
window.removeEventListener('keydown', handleKeyDown)
window.removeEventListener('keyup', handleKeyUp)
window.removeEventListener('blur', handleBlur)
window.removeEventListener('mouseup', handleWindowMouseUp)
window.removeEventListener('hidCursorVisibilityChanged', handleCursorVisibilityChange as EventListener)
window.removeEventListener('hidMouseSendIntervalChanged', handleMouseSendIntervalChange as EventListener)
window.removeEventListener('storage', handleMouseSendIntervalStorage)
// Remove pointer lock event listeners
document.removeEventListener('pointerlockchange', handlePointerLockChange)
document.removeEventListener('pointerlockerror', handlePointerLockError)
})
</script>

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { useSystemStore } from '@/stores/system'
import { useConfigStore } from '@/stores/config'
import { useAuthStore } from '@/stores/auth'
@@ -83,6 +84,7 @@ import {
} from 'lucide-vue-next'
const { t, te, locale } = useI18n()
const route = useRoute()
const systemStore = useSystemStore()
const configStore = useConfigStore()
const authStore = useAuthStore()
@@ -92,6 +94,21 @@ const activeSection = ref('appearance')
const mobileMenuOpen = ref(false)
const loading = ref(false)
const saved = ref(false)
const SETTINGS_SECTION_IDS = new Set([
'appearance',
'account',
'access',
'video',
'hid',
'msd',
'atx',
'environment',
'ext-ttyd',
'ext-rustdesk',
'ext-rtsp',
'ext-remote-access',
'about',
])
// Navigation structure
const navGroups = computed(() => [
@@ -135,6 +152,10 @@ function selectSection(id: string) {
mobileMenuOpen.value = false
}
function normalizeSettingsSection(value: unknown): string | null {
return typeof value === 'string' && SETTINGS_SECTION_IDS.has(value) ? value : null
}
// Theme
const theme = ref<'light' | 'dark' | 'system'>('system')
@@ -1850,6 +1871,13 @@ watch(() => config.value.hid_backend, () => {
otgSelfCheckResult.value = null
otgSelfCheckError.value = ''
})
watch(() => route.query.tab, (tab) => {
const section = normalizeSettingsSection(tab)
if (section && activeSection.value !== section) {
selectSection(section)
}
}, { immediate: true })
</script>
<template>