import studentScreenShareApi from '@/api/subject/studentScreenShare' import { $XXL } from '@/utils' import { userStore } from '@/stores' import { useTips } from './useTips' let monitorSessionId = '' let monitorState: 0 | 1 = 0 let closeMonitorTip: (() => void) | null = null /** 旧版课堂监控 WS 类型 */ const MONITOR_MSG_TYPE = 'monitorStart' const MONITOR_STOP_MSG_TYPE = 'monitorStop' /** 新版学生投屏 WS 类型 */ const STUDENT_SCREEN_SHARE_START = 'studentScreenShareStart' const STUDENT_SCREEN_SHARE_STOP = 'studentScreenShareStop' const MONITOR_WS_TYPES = new Set([ MONITOR_MSG_TYPE, MONITOR_STOP_MSG_TYPE, STUDENT_SCREEN_SHARE_START, STUDENT_SCREEN_SHARE_STOP, ]) const MONITOR_LOG_PREFIX = '[class-monitor]' const monitorLog = (...args: unknown[]) => console.log(MONITOR_LOG_PREFIX, ...args) const monitorWarn = (...args: unknown[]) => console.warn(MONITOR_LOG_PREFIX, ...args) const showMonitorTip = (tips: string) => { closeMonitorTip?.() closeMonitorTip = useTips({ tips, showCancel: false, }) } const mergeMonitorPayload = (data: anyObj): anyObj => { const msg = data?.msg && typeof data.msg === 'object' && !Array.isArray(data.msg) ? data.msg : {} return { ...(data && typeof data === 'object' ? data : {}), ...msg, } } const getMonitorSessionKey = (msg: anyObj) => { const key = msg?.sessionId ?? msg?.startedAt ?? msg?.classId ?? msg?.monitorSessionId ?? msg?.classMonitorSessionId return key !== undefined && key !== null ? String(key).trim() : '' } const applyStudentShareStart = (msg: anyObj): boolean => { const host = msg.ip !== undefined && msg.ip !== null ? String(msg.ip) : '' const port = Number(msg.port) if (!host || !Number.isFinite(port)) { monitorWarn('startStudentShareScreen missing ip/port', msg) return false } const u = userStore().userInfo const config = { host, port, name: String(u?.name || u?.nickName || ''), id: String(u?.userId ?? ''), } monitorLog('startStudentShareScreen config', config) $XXL.startStudentShareScreen(config) return true } const startStudentShareScreenLive = (msg: anyObj) => { const sessionKey = getMonitorSessionKey(msg) monitorLog('start student screen share', msg) if (monitorState === 1 && sessionKey && monitorSessionId === sessionKey) { monitorLog('start ignored, session already running', sessionKey) return } stopClassMonitor() monitorSessionId = sessionKey monitorState = 1 // showMonitorTip('已进入课堂监控状态') if (!applyStudentShareStart(msg)) { monitorState = 0 monitorSessionId = '' } } /** 与老师讲解 WS 等场景兼容:外层 type 可能是 push/classMessage,监控类型在 msg.type 或 data.type */ export const resolveMonitorWsType = (data: anyObj): string | undefined => { const candidates = [data?.msg?.type, data?.type] const inner = (data as Record).data if (inner && typeof inner === 'object') { candidates.push((inner as anyObj).type) } for (const t of candidates) { if (typeof t === 'string' && MONITOR_WS_TYPES.has(t)) return t } return undefined } export const stopClassMonitor = (showTip = false) => { const isMonitoring = !!monitorSessionId || monitorState === 1 monitorLog('stop monitor', { showTip, isMonitoring, monitorSessionId, monitorState }) if (monitorState === 1) { $XXL.stopStudentShareScreen() } if (showTip && isMonitoring) { // showMonitorTip('已结束课堂监控') } monitorSessionId = '' monitorState = 0 } const startClassMonitor = (msg: anyObj) => { const sessionId = msg?.sessionId monitorLog('start monitor message', msg) if (!sessionId) { monitorWarn('start monitor ignored because sessionId is empty') return } if (monitorState === 1 && monitorSessionId === String(sessionId)) { monitorLog('start monitor ignored because session is already running', sessionId) return } stopClassMonitor() monitorSessionId = String(sessionId) monitorState = 1 // showMonitorTip('已进入课堂监控状态') if (!applyStudentShareStart(msg)) { monitorState = 0 monitorSessionId = '' } } const getMonitorSessionId = (data: anyObj) => { const sessionId = data?.sessionId || data?.monitorSessionId || data?.classMonitorSessionId || data?.monitorInfo?.sessionId || data?.classMonitor?.sessionId return typeof sessionId === 'string' || typeof sessionId === 'number' ? String(sessionId).trim() : '' } export const handleClassMonitorMessage = (data: anyObj) => { const monitorType = resolveMonitorWsType(data) monitorLog('handle monitor ws message', { monitorType, data }) if (!monitorType) return false const msg = mergeMonitorPayload(data) if (monitorType === STUDENT_SCREEN_SHARE_STOP || monitorType === MONITOR_STOP_MSG_TYPE) { stopClassMonitor(true) return true } if (monitorType === STUDENT_SCREEN_SHARE_START) { startStudentShareScreenLive(msg) return true } if (monitorType !== MONITOR_MSG_TYPE) return false const monitoring = msg?.monitoring if (monitoring === true || monitoring === 'true') { startClassMonitor(msg) return true } if (monitoring === false || monitoring === 'false') { stopClassMonitor(true) return true } return false } const resolveStudentScreenShareClassId = (msg: Record): string => { if (msg.classId !== undefined && msg.classId !== null && String(msg.classId).trim() !== '') { return String(msg.classId) } const userClassId = userStore().userInfo?.classId if (userClassId !== undefined && userClassId !== null && String(userClassId).trim() !== '') { return String(userClassId) } return '' } /** * 同步 getHomePage 返回的 studentScreenShareState * sharing 为 true 时开启学生监控投屏,否则停止 */ export const syncStudentScreenShareState = async (state: unknown) => { const stateInfo = state && typeof state === 'object' ? (state as Record) : {} const screenShare = stateInfo.studentScreenShareState if (!screenShare || typeof screenShare !== 'object') return const share = screenShare as Record if (share.sharing !== true) { stopClassMonitor(false) return } let msg: anyObj = { ...share } const host = msg.ip !== undefined && msg.ip !== null ? String(msg.ip) : '' const port = Number(msg.port) if (!host || !Number.isFinite(port)) { const classId = resolveStudentScreenShareClassId(msg) if (!classId) return try { const res = await studentScreenShareApi.state(classId) if (!res?.sharing) return msg = { ...msg, ip: res.ip, port: res.port, classId: res.classId ?? classId } } catch (err) { monitorWarn('首页学生监控状态补全失败:', err) return } } const sessionKey = getMonitorSessionKey(msg) // 已在监控中:仅刷新连接参数,不先 stop 再 start(与老师 screenShareState 同步一致) if (monitorState === 1) { if (sessionKey) monitorSessionId = sessionKey applyStudentShareStart(msg) return } monitorSessionId = sessionKey monitorState = 1 if (!applyStudentShareStart(msg)) { monitorState = 0 monitorSessionId = '' } } export const syncClassMonitorState = (state: unknown) => { const stateInfo = state && typeof state === 'object' ? (state as anyObj) : {} const nextState = Number(stateInfo.monitorState ?? state) if (nextState !== 0 && nextState !== 1) return if (nextState === 0) { stopClassMonitor(true) return } const sessionId = getMonitorSessionId(stateInfo) if (!sessionId) { monitorWarn('sync monitor state ignored because sessionId is empty', stateInfo) if (monitorState !== 1 && applyStudentShareStart(stateInfo)) { monitorState = 1 } return } startClassMonitor({ ...stateInfo, sessionId, }) }