268 lines
7.7 KiB
TypeScript
268 lines
7.7 KiB
TypeScript
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<string, unknown>).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, unknown>): 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<string, unknown>) : {}
|
||
const screenShare = stateInfo.studentScreenShareState
|
||
if (!screenShare || typeof screenShare !== 'object') return
|
||
|
||
const share = screenShare as Record<string, unknown>
|
||
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)
|
||
if (monitorState === 1 && sessionKey && monitorSessionId === sessionKey) {
|
||
applyStudentShareStart(msg)
|
||
return
|
||
}
|
||
|
||
stopClassMonitor()
|
||
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,
|
||
})
|
||
}
|