直播:画质可选、只读 /live/info、弹幕 WS 透传;Nginx 弹幕路径

Made-with: Cursor
This commit is contained in:
whm
2026-03-26 10:07:49 +08:00
parent 6b3210f714
commit 106e6e1f16
11 changed files with 417 additions and 8 deletions

View File

@@ -3,8 +3,44 @@
*/
const apiBase = (import.meta.env.VITE_API_BASE || '').replace(/\/$/, '')
function liveWsURLPublish(token) {
const path = `/api/web/live/ws?role=publish&token=${encodeURIComponent(token)}`
export const LIVE_QUALITY_OPTIONS = [
{ value: 'source', label: '原画(设备默认)' },
{ value: 'high', label: '高清 720p' },
{ value: 'mid', label: '标清 480p' },
{ value: 'low', label: '流畅 360p' }
]
const QUALITY_MEDIA = {
source: { video: true, audio: true },
high: {
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
frameRate: { ideal: 30 }
},
audio: true
},
mid: {
video: {
width: { ideal: 854 },
height: { ideal: 480 },
frameRate: { ideal: 24 }
},
audio: true
},
low: {
video: {
width: { ideal: 640 },
height: { ideal: 360 },
frameRate: { ideal: 20 }
},
audio: true
}
}
function liveWsURLPublish(token, quality) {
const q = QUALITY_MEDIA[quality] ? quality : 'high'
const path = `/api/web/live/ws?role=publish&token=${encodeURIComponent(token)}&quality=${encodeURIComponent(q)}`
if (apiBase) {
const base = apiBase.replace(/\/$/, '')
const wsOrigin = base.replace(/^https:/i, 'wss:').replace(/^http:/i, 'ws:')
@@ -48,15 +84,21 @@ 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 = '', onStatus = () => {}, onLocalStream = () => {} } = opts
const {
token = '',
quality = 'high',
onStatus = () => {},
onLocalStream = () => {}
} = opts
if (!token) {
onStatus('未登录,无法开播')
return { stop: () => {} }
}
const wsUrl = liveWsURLPublish(token)
const wsUrl = liveWsURLPublish(token, quality)
const ws = new WebSocket(wsUrl)
const pc = new RTCPeerConnection({ iceServers: defaultIce })
let stream = null
@@ -75,7 +117,8 @@ export function startPublishing(opts = {}) {
onStatus('信令已连接,正在采集摄像头…')
try {
// 后台多为 PC不要用 facingMode:'user',部分机器会直接导致 “Could not start video source”
stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
const cons = QUALITY_MEDIA[quality] || QUALITY_MEDIA.high
stream = await navigator.mediaDevices.getUserMedia(cons)
onLocalStream(stream)
stream.getTracks().forEach((t) => pc.addTrack(t, stream))
const offer = await pc.createOffer()