修复开播卡正在连接:移除未定义 quality;画质改官网选择+localStorage

Made-with: Cursor
This commit is contained in:
whm
2026-03-26 10:22:00 +08:00
parent 106e6e1f16
commit 10a842b4ef
4 changed files with 111 additions and 45 deletions

View File

@@ -1,14 +1,10 @@
/**
* 管理后台 WebRTC 开播(需登录 token与 /api/web/live/ws?role=publish&token= 一致)
* 画质由官网 /live 写入 localStorageyh_live_capture_quality同浏览器开播时生效默认原画约束最少。
*/
const apiBase = (import.meta.env.VITE_API_BASE || '').replace(/\/$/, '')
export const LIVE_QUALITY_OPTIONS = [
{ value: 'source', label: '原画(设备默认)' },
{ value: 'high', label: '高清 720p' },
{ value: 'mid', label: '标清 480p' },
{ value: 'low', label: '流畅 360p' }
]
const LIVE_CAPTURE_QUALITY_STORAGE_KEY = 'yh_live_capture_quality'
const QUALITY_MEDIA = {
source: { video: true, audio: true },
@@ -38,8 +34,16 @@ const QUALITY_MEDIA = {
}
}
function liveWsURLPublish(token, quality) {
const q = QUALITY_MEDIA[quality] ? quality : 'high'
function effectivePublishQualityKey() {
try {
const v = localStorage.getItem(LIVE_CAPTURE_QUALITY_STORAGE_KEY)
if (v && QUALITY_MEDIA[v]) return v
} catch (_) {}
return 'source'
}
function liveWsURLPublish(token) {
const q = effectivePublishQualityKey()
const path = `/api/web/live/ws?role=publish&token=${encodeURIComponent(token)}&quality=${encodeURIComponent(q)}`
if (apiBase) {
const base = apiBase.replace(/\/$/, '')
@@ -52,7 +56,6 @@ function liveWsURLPublish(token, quality) {
const defaultIce = [{ urls: 'stun:stun.l.google.com:19302' }]
/** 将 getUserMedia 异常转为中文说明(含 Edge 英文 “Could not start video source” */
function humanizeGetUserMediaError(err) {
const name = err && err.name
const raw = ((err && err.message) || '').toLowerCase()
@@ -71,7 +74,7 @@ function humanizeGetUserMediaError(err) {
return '无法启动摄像头:可能被 Zoom、Teams、腾讯会议、OBS 等占用;或在 Windows「设置 → 隐私和安全性 → 相机」中未允许浏览器/桌面应用访问。请关闭占用软件后刷新页面重试。'
}
if (name === 'OverconstrainedError') {
return '摄像头不满足当前参数约束,请换用其他摄像头或更新驱动。'
return '摄像头不满足当前参数约束:请到官网「直播」页换一档画质(或选原画)后再开播。'
}
if (name === 'AbortError') {
return '打开摄像头被系统中断,请重试。'
@@ -84,25 +87,19 @@ function humanizeGetUserMediaError(err) {
* @param {string} opts.token 管理员 JWT
* @param {(s: string) => void} [opts.onStatus]
* @param {(stream: MediaStream) => void} [opts.onLocalStream]
* @param {'source'|'high'|'mid'|'low'} [opts.quality] 推流画质(约束摄像头采集分辨率)
*/
export function startPublishing(opts = {}) {
const {
token = '',
quality = 'high',
onStatus = () => {},
onLocalStream = () => {}
} = opts
const { token = '', onStatus = () => {}, onLocalStream = () => {} } = opts
if (!token) {
onStatus('未登录,无法开播')
return { stop: () => {} }
}
const wsUrl = liveWsURLPublish(token, quality)
const publishKey = effectivePublishQualityKey()
const wsUrl = liveWsURLPublish(token)
const ws = new WebSocket(wsUrl)
const pc = new RTCPeerConnection({ iceServers: defaultIce })
let stream = null
/** 本地主动 stop / 摄像头失败关闭,避免 onclose 覆盖真实错误提示 */
let closedByLocal = false
const send = (o) => {
@@ -116,8 +113,7 @@ export function startPublishing(opts = {}) {
ws.onopen = async () => {
onStatus('信令已连接,正在采集摄像头…')
try {
// 后台多为 PC不要用 facingMode:'user',部分机器会直接导致 “Could not start video source
const cons = QUALITY_MEDIA[quality] || QUALITY_MEDIA.high
const cons = QUALITY_MEDIA[publishKey] || QUALITY_MEDIA.source
stream = await navigator.mediaDevices.getUserMedia(cons)
onLocalStream(stream)
stream.getTracks().forEach((t) => pc.addTrack(t, stream))