后台直播:修复信令误断(onclose 提示与严格模式 onUnmounted)

Made-with: Cursor
This commit is contained in:
whm
2026-03-25 16:01:22 +08:00
parent 7811adca66
commit 996dc3778d
2 changed files with 31 additions and 3 deletions

View File

@@ -33,6 +33,8 @@ export function startPublishing(opts = {}) {
const ws = new WebSocket(wsUrl) const ws = new WebSocket(wsUrl)
const pc = new RTCPeerConnection({ iceServers: defaultIce }) const pc = new RTCPeerConnection({ iceServers: defaultIce })
let stream = null let stream = null
/** 本地主动 stop / 摄像头失败关闭,避免 onclose 覆盖真实错误提示 */
let closedByLocal = false
const send = (o) => { const send = (o) => {
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(o)) if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(o))
@@ -56,7 +58,14 @@ export function startPublishing(opts = {}) {
send({ type: 'offer', sdp: offer.sdp }) send({ type: 'offer', sdp: offer.sdp })
onStatus('已发起推流协商,等待服务端应答…') onStatus('已发起推流协商,等待服务端应答…')
} catch (err) { } catch (err) {
onStatus(err.message || '无法打开摄像头') const name = err && err.name
let tip = err.message || '无法打开摄像头'
if (name === 'NotAllowedError' || name === 'PermissionDeniedError') {
tip = '已拒绝摄像头权限:请在浏览器地址栏允许摄像头,并确认本页为 HTTPS。'
} else if (name === 'NotFoundError') {
tip = '未检测到摄像头设备。'
}
onStatus(tip)
stop() stop()
} }
} }
@@ -86,10 +95,18 @@ export function startPublishing(opts = {}) {
} }
} }
ws.onerror = () => onStatus('信令连接失败(请确认已登录且 Nginx 已配置 WebSocket') ws.onerror = () => {
ws.onclose = () => onStatus('信令已断开') if (!closedByLocal) {
onStatus('信令连接失败(请确认已登录且 Nginx 已配置 WebSocket')
}
}
ws.onclose = () => {
if (closedByLocal) return
onStatus('信令已断开:服务端关闭连接或网络中断。若刚能连上即断,请查服务端日志或配置 TURNLIVE_ICE_SERVERS。')
}
function stop() { function stop() {
closedByLocal = true
try { try {
ws.close() ws.close()
} catch (_) {} } catch (_) {}

View File

@@ -20,6 +20,7 @@
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue' import { ref, computed, onMounted, onUnmounted } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'
import { useAuthStore } from '../../stores/auth' import { useAuthStore } from '../../stores/auth'
import { startPublishing } from '../../utils/liveWebRTC' import { startPublishing } from '../../utils/liveWebRTC'
@@ -54,11 +55,21 @@ function stop() {
status.value = '已停止' status.value = '已停止'
} }
function onBeforeUnload() {
session.value?.stop()
}
onMounted(() => { onMounted(() => {
document.title = '视频直播开播 - 管理后台' document.title = '视频直播开播 - 管理后台'
window.addEventListener('beforeunload', onBeforeUnload)
}) })
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('beforeunload', onBeforeUnload)
})
/** 离开本页时再结束推流;勿在 onUnmounted 里 stop避免 Vue 开发严格模式双挂载误关 WebSocket */
onBeforeRouteLeave(() => {
stop() stop()
}) })
</script> </script>