diff --git a/admin/src/utils/liveWebRTC.js b/admin/src/utils/liveWebRTC.js index b32deff..b44401d 100644 --- a/admin/src/utils/liveWebRTC.js +++ b/admin/src/utils/liveWebRTC.js @@ -57,6 +57,14 @@ function liveWsURLPublish(token) { const defaultIce = [{ urls: 'stun:stun.l.google.com:19302' }] +const MAX_SIGNAL_RECONNECT = 15 + +function healthCheckUrl() { + if (apiBase) return `${apiBase}/api/health` + if (typeof window !== 'undefined') return `${window.location.origin}/api/health` + return '/api/health' +} + function buildCameraConstraints(publishKey, videoDeviceId) { const preset = QUALITY_MEDIA[publishKey] || QUALITY_MEDIA.source const dev = videoDeviceId ? { deviceId: { exact: videoDeviceId } } : {} @@ -130,6 +138,7 @@ export function startPublishing(opts = {}) { let reconnectTimer = null let reconnectAttempt = 0 let wsGen = 0 + let reconnectStopped = false function clearReconnectTimer() { if (reconnectTimer) { @@ -254,20 +263,36 @@ export function startPublishing(opts = {}) { } function scheduleReconnect() { - if (closedByLocal) return + if (closedByLocal || reconnectStopped) return clearReconnectTimer() - const delay = Math.min(2000 * Math.pow(1.45, reconnectAttempt), 28000) reconnectAttempt += 1 - onStatus(`信令断开,${Math.round(delay / 1000)} 秒后重连(${reconnectAttempt})…`) - reconnectTimer = window.setTimeout(() => { + if (reconnectAttempt > MAX_SIGNAL_RECONNECT) { + reconnectStopped = true + onStatus( + `信令已重试 ${MAX_SIGNAL_RECONNECT} 次仍失败。多为 API 返回 502(进程未启动/崩溃)或 Nginx 未正确反代到 Go。请检查服务与 \`/api/web/live/ws\` 的 WebSocket 配置,修复后刷新本页再点「开始直播」。` + ) + return + } + const delay = Math.min(2000 * Math.pow(1.45, reconnectAttempt - 1), 28000) + onStatus(`信令断开,约 ${Math.round(delay / 1000)} 秒后重试(${reconnectAttempt}/${MAX_SIGNAL_RECONNECT})…`) + reconnectTimer = window.setTimeout(async () => { reconnectTimer = null - if (closedByLocal) return + if (closedByLocal || reconnectStopped) return + try { + const r = await fetch(healthCheckUrl(), { method: 'GET', cache: 'no-store' }) + if (!r.ok) { + onStatus(`API 不可用(HTTP ${r.status}),多为网关 502,推迟重试…`) + } + } catch (_) { + onStatus('无法访问健康检查接口,请确认网络与域名。') + } + if (closedByLocal || reconnectStopped) return openSignalingSocket() }, delay) } function openSignalingSocket() { - if (closedByLocal) return + if (closedByLocal || reconnectStopped) return const myGen = ++wsGen clearReconnectTimer() if (ws) { @@ -321,6 +346,7 @@ export function startPublishing(opts = {}) { function stop() { closedByLocal = true + reconnectStopped = true wsGen += 1 clearReconnectTimer() if (ws) { diff --git a/admin/src/views/sites/LiveBroadcast.vue b/admin/src/views/sites/LiveBroadcast.vue index 5387cae..1b300ae 100644 --- a/admin/src/views/sites/LiveBroadcast.vue +++ b/admin/src/views/sites/LiveBroadcast.vue @@ -133,7 +133,7 @@ onBeforeRouteLeave(() => {