开播页:预览放大;信令重连上限与健康检查提示 502
Made-with: Cursor
This commit is contained in:
@@ -57,6 +57,14 @@ function liveWsURLPublish(token) {
|
|||||||
|
|
||||||
const defaultIce = [{ urls: 'stun:stun.l.google.com:19302' }]
|
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) {
|
function buildCameraConstraints(publishKey, videoDeviceId) {
|
||||||
const preset = QUALITY_MEDIA[publishKey] || QUALITY_MEDIA.source
|
const preset = QUALITY_MEDIA[publishKey] || QUALITY_MEDIA.source
|
||||||
const dev = videoDeviceId ? { deviceId: { exact: videoDeviceId } } : {}
|
const dev = videoDeviceId ? { deviceId: { exact: videoDeviceId } } : {}
|
||||||
@@ -130,6 +138,7 @@ export function startPublishing(opts = {}) {
|
|||||||
let reconnectTimer = null
|
let reconnectTimer = null
|
||||||
let reconnectAttempt = 0
|
let reconnectAttempt = 0
|
||||||
let wsGen = 0
|
let wsGen = 0
|
||||||
|
let reconnectStopped = false
|
||||||
|
|
||||||
function clearReconnectTimer() {
|
function clearReconnectTimer() {
|
||||||
if (reconnectTimer) {
|
if (reconnectTimer) {
|
||||||
@@ -254,20 +263,36 @@ export function startPublishing(opts = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function scheduleReconnect() {
|
function scheduleReconnect() {
|
||||||
if (closedByLocal) return
|
if (closedByLocal || reconnectStopped) return
|
||||||
clearReconnectTimer()
|
clearReconnectTimer()
|
||||||
const delay = Math.min(2000 * Math.pow(1.45, reconnectAttempt), 28000)
|
|
||||||
reconnectAttempt += 1
|
reconnectAttempt += 1
|
||||||
onStatus(`信令断开,${Math.round(delay / 1000)} 秒后重连(${reconnectAttempt})…`)
|
if (reconnectAttempt > MAX_SIGNAL_RECONNECT) {
|
||||||
reconnectTimer = window.setTimeout(() => {
|
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
|
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()
|
openSignalingSocket()
|
||||||
}, delay)
|
}, delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
function openSignalingSocket() {
|
function openSignalingSocket() {
|
||||||
if (closedByLocal) return
|
if (closedByLocal || reconnectStopped) return
|
||||||
const myGen = ++wsGen
|
const myGen = ++wsGen
|
||||||
clearReconnectTimer()
|
clearReconnectTimer()
|
||||||
if (ws) {
|
if (ws) {
|
||||||
@@ -321,6 +346,7 @@ export function startPublishing(opts = {}) {
|
|||||||
|
|
||||||
function stop() {
|
function stop() {
|
||||||
closedByLocal = true
|
closedByLocal = true
|
||||||
|
reconnectStopped = true
|
||||||
wsGen += 1
|
wsGen += 1
|
||||||
clearReconnectTimer()
|
clearReconnectTimer()
|
||||||
if (ws) {
|
if (ws) {
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ onBeforeRouteLeave(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.live-broadcast {
|
.live-broadcast {
|
||||||
max-width: 720px;
|
max-width: min(1200px, 100%);
|
||||||
}
|
}
|
||||||
.status {
|
.status {
|
||||||
color: #409eff;
|
color: #409eff;
|
||||||
@@ -160,21 +160,23 @@ onBeforeRouteLeave(() => {
|
|||||||
}
|
}
|
||||||
.preview-wrap {
|
.preview-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
max-width: 720px;
|
width: 100%;
|
||||||
|
max-width: 1100px;
|
||||||
}
|
}
|
||||||
.preview-main {
|
.preview-main {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 70vh;
|
min-height: 320px;
|
||||||
|
max-height: min(85vh, 900px);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: #000;
|
background: #000;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
.preview-pip {
|
.preview-pip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 12px;
|
right: 14px;
|
||||||
bottom: 12px;
|
bottom: 14px;
|
||||||
width: min(28%, 200px);
|
width: min(32%, 280px);
|
||||||
aspect-ratio: 4 / 3;
|
aspect-ratio: 4 / 3;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 2px solid #409eff;
|
border: 2px solid #409eff;
|
||||||
|
|||||||
Reference in New Issue
Block a user