直播页:弹幕 WebSocket 自动重连与失败提示

Made-with: Cursor
This commit is contained in:
whm
2026-03-26 10:30:30 +08:00
parent 10a842b4ef
commit 8d730a2a75

View File

@@ -53,6 +53,7 @@
/>
<button type="button" class="live-dm-send" @click="sendDm">发送</button>
</div>
<p v-if="dmHint" class="live-dm-hint">{{ dmHint }}</p>
</section>
<section class="live-block live-block--divider" aria-label="外链直播间">
@@ -86,9 +87,13 @@ const qualityOptions = LIVE_QUALITY_OPTIONS
const captureQualityPref = ref('source')
const liveInfoLine = ref('')
const dmDraft = ref('')
const dmHint = ref('')
const dmItems = ref([])
let dmIdSeq = 0
let dmWs = null
let dmIntentionalClose = false
let dmReconnectTimer = null
let dmReconnectAttempt = 0
const enterUrl = computed(() => (rawLiveUrl.value || '').trim())
@@ -185,11 +190,42 @@ function pushDmLine(text) {
}, 12000)
}
function scheduleDmReconnect() {
if (dmIntentionalClose) return
if (dmReconnectTimer) return
const delay = Math.min(2500 * Math.pow(1.4, dmReconnectAttempt), 28000)
dmReconnectTimer = window.setTimeout(() => {
dmReconnectTimer = null
if (dmIntentionalClose) return
connectDanmaku()
}, delay)
}
function connectDanmaku() {
if (dmIntentionalClose) return
try {
dmWs?.close()
} catch (_) {}
dmWs = new WebSocket(liveDanmakuWsURL())
dmWs = null
const url = liveDanmakuWsURL()
dmHint.value = dmReconnectAttempt > 0 ? `弹幕重连中(第 ${dmReconnectAttempt + 1} 次)…` : '弹幕通道连接中…'
try {
dmWs = new WebSocket(url)
} catch (e) {
dmHint.value = '无法创建弹幕连接,请检查网络或地址配置'
dmReconnectAttempt += 1
scheduleDmReconnect()
return
}
dmWs.onopen = () => {
dmReconnectAttempt = 0
dmHint.value = ''
}
dmWs.onerror = () => {
if (!dmIntentionalClose) {
dmHint.value = '弹幕 WebSocket 异常(多为网关未放行,见下方说明)'
}
}
dmWs.onmessage = (ev) => {
let j
try {
@@ -203,18 +239,32 @@ function connectDanmaku() {
}
dmWs.onclose = () => {
dmWs = null
if (dmIntentionalClose) return
dmHint.value = '弹幕已断开,自动重连中…'
dmReconnectAttempt += 1
scheduleDmReconnect()
}
}
function sendDm() {
const t = dmDraft.value.trim()
if (!t) return
if (!dmWs || dmWs.readyState !== WebSocket.OPEN) return
if (!dmWs || dmWs.readyState !== WebSocket.OPEN) {
dmHint.value =
'弹幕未连接,无法发送。请确认:① 已部署含弹幕接口的 API② Nginx 已为 /api/web/live/danmaku/ws 配置 WebSocket 反代Upgrade③ 与直播信令使用同一域名/网关。'
return
}
try {
dmWs.send(JSON.stringify({ text: t }))
dmDraft.value = ''
dmHint.value = ''
} catch (_) {
dmHint.value = '发送失败,请稍后重试或刷新页面'
}
}
onMounted(async () => {
dmIntentionalClose = false
loadCaptureQualityPref()
loadHomepage()
await nextTick()
@@ -230,6 +280,11 @@ onMounted(async () => {
})
onUnmounted(() => {
dmIntentionalClose = true
if (dmReconnectTimer) {
clearTimeout(dmReconnectTimer)
dmReconnectTimer = null
}
if (liveInfoTimer) {
clearInterval(liveInfoTimer)
liveInfoTimer = null
@@ -397,6 +452,14 @@ onUnmounted(() => {
.live-dm-send:hover {
background: rgba(0, 212, 255, 0.25);
}
.live-dm-hint {
max-width: 480px;
margin: 10px auto 0;
font-size: 12px;
line-height: 1.55;
color: #ffb86c;
text-align: left;
}
.live-video-toolbar {
display: flex;
flex-wrap: wrap;