fix: 修复 MJPEG模式可用但状态显示离线的问题

This commit is contained in:
mofeng-git
2026-04-11 15:11:47 +08:00
parent 3e35181583
commit 2d81a071e5
3 changed files with 41 additions and 1 deletions

1
.gitignore vendored
View File

@@ -42,3 +42,4 @@ CLAUDE.md
secrets.toml secrets.toml
.env .env
/docs/ /docs/
web/package-lock.json

View File

@@ -259,6 +259,30 @@ export const useSystemStore = defineStore('system', () => {
} }
} }
async function fetchStreamState() {
try {
const [status, modeResp] = await Promise.all([
streamApi.status(),
streamApi.getMode().catch(() => ({ mode: 'mjpeg' }))
])
stream.value = {
online: status.state === 'streaming',
active: status.state !== 'uninitialized',
device: status.device,
format: status.format,
resolution: status.resolution,
targetFps: status.target_fps,
clients: status.clients,
streamMode: modeResp.mode || 'mjpeg',
error: status.state === 'error' ? 'Stream error' : null,
}
return status
} catch (e) {
console.error('Failed to fetch stream state:', e)
throw e
}
}
async function fetchAllStates() { async function fetchAllStates() {
loading.value = true loading.value = true
error.value = null error.value = null
@@ -266,7 +290,8 @@ export const useSystemStore = defineStore('system', () => {
try { try {
await Promise.all([ await Promise.all([
fetchSystemInfo(), fetchSystemInfo(),
// HID state is updated via WebSocket device_info event fetchStreamState().catch(() => null),
fetchHidState().catch(() => null),
fetchAtxState().catch(() => null), fetchAtxState().catch(() => null),
fetchMsdState().catch(() => null), fetchMsdState().catch(() => null),
]) ])

View File

@@ -96,6 +96,7 @@ const videoLoading = ref(true)
const videoError = ref(false) const videoError = ref(false)
const videoErrorMessage = ref('') const videoErrorMessage = ref('')
const videoRestarting = ref(false) // Track if video is restarting due to config change const videoRestarting = ref(false) // Track if video is restarting due to config change
const mjpegFrameReceived = ref(false) // Whether MJPEG stream has received at least one frame
// Video aspect ratio (dynamically updated from actual video dimensions) // Video aspect ratio (dynamically updated from actual video dimensions)
// Using string format "width/height" to let browser handle the ratio calculation // Using string format "width/height" to let browser handle the ratio calculation
@@ -188,6 +189,11 @@ const videoStatus = computed<'connected' | 'connecting' | 'disconnected' | 'erro
if (webrtc.isConnecting.value) return 'connecting' if (webrtc.isConnecting.value) return 'connecting'
if (webrtc.isConnected.value) return 'connected' if (webrtc.isConnected.value) return 'connected'
} }
// MJPEG: check if frames have actually arrived (frontend-side detection)
// This is more reliable than relying on stream.online from backend,
// which can be stale due to the debounce delay in device_info broadcaster.
// Also handles browsers that don't fire img.onload for multipart MJPEG streams.
if (videoMode.value === 'mjpeg' && mjpegFrameReceived.value) return 'connected'
if (systemStore.stream?.online) return 'connected' if (systemStore.stream?.online) return 'connected'
return 'disconnected' return 'disconnected'
}) })
@@ -680,6 +686,7 @@ function handleVideoLoad() {
// MJPEG video frame loaded successfully - update stream online status // MJPEG video frame loaded successfully - update stream online status
// This fixes the timing issue where device_info event may arrive before stream is fully active // This fixes the timing issue where device_info event may arrive before stream is fully active
if (videoMode.value === 'mjpeg') { if (videoMode.value === 'mjpeg') {
mjpegFrameReceived.value = true
systemStore.setStreamOnline(true) systemStore.setStreamOnline(true)
// Update aspect ratio from MJPEG image dimensions // Update aspect ratio from MJPEG image dimensions
const img = videoRef.value const img = videoRef.value
@@ -758,6 +765,7 @@ function handleVideoError() {
// Show loading state immediately // Show loading state immediately
videoLoading.value = true videoLoading.value = true
mjpegFrameReceived.value = false
// Auto-retry with exponential backoff (infinite retry, capped delay) // Auto-retry with exponential backoff (infinite retry, capped delay)
retryCount++ retryCount++
@@ -1062,6 +1070,7 @@ function refreshVideo() {
backendFps.value = 0 backendFps.value = 0
videoError.value = false videoError.value = false
videoErrorMessage.value = '' videoErrorMessage.value = ''
mjpegFrameReceived.value = false
// Update timestamp to force MJPEG reconnection via reactive URL // Update timestamp to force MJPEG reconnection via reactive URL
isRefreshingVideo = true isRefreshingVideo = true
@@ -2061,6 +2070,11 @@ async function activateConsoleView() {
isConsoleActive.value = true isConsoleActive.value = true
registerInteractionListeners() registerInteractionListeners()
// Ensure HID WebSocket is connected when console becomes active
if (!hidWs.connected.value) {
hidWs.connect().catch(() => {})
}
if (videoMode.value !== 'mjpeg' && webrtc.videoTrack.value) { if (videoMode.value !== 'mjpeg' && webrtc.videoTrack.value) {
await nextTick() await nextTick()
await rebindWebRTCVideo() await rebindWebRTCVideo()