feat: 直播中调用查询直播状态接口
This commit is contained in:
parent
3ba6221bd2
commit
efe57930aa
@ -1,5 +1,5 @@
|
||||
VITE_WS_URL = ws://43.136.52.196:27001/device
|
||||
# VITE_WS_URL = ws://ai.xuexiaole.com/ws/device
|
||||
# VITE_WS_URL = ws://43.136.52.196:27001/device
|
||||
VITE_WS_URL = ws://ai.lingxixue.com/ws/device
|
||||
VITE_OSS_URL = https://lxx-web-1313840333.cos.ap-chengdu.myqcloud.com
|
||||
VITE_CDN_URL = https://dmgcdn-1313840333.cos.ap-guangzhou.myqcloud.com
|
||||
VITE_SERVER_URL = http://127.0.0.1:9053/api/main
|
||||
|
||||
20
src/api/subject/teacherScreenShare.ts
Normal file
20
src/api/subject/teacherScreenShare.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { get } from '../request'
|
||||
|
||||
export type TeacherScreenShareState = {
|
||||
classId: number
|
||||
ip: string
|
||||
lastHeartbeatAt: number
|
||||
port: number
|
||||
sharing: boolean
|
||||
startedAt: number
|
||||
ttlSeconds: number
|
||||
}
|
||||
|
||||
const teacherScreenShareApi = {
|
||||
/** 查询当前班级老师投屏状态(直播中轮询) */
|
||||
state: async (classId: string) => {
|
||||
return await get<TeacherScreenShareState>('/school/teacherScreenShare/state', { classId })
|
||||
},
|
||||
}
|
||||
|
||||
export default teacherScreenShareApi
|
||||
@ -8,10 +8,12 @@
|
||||
* 4. 每 30 秒发送一次心跳保活
|
||||
* 5. 收到的消息统一处理,支持注册自定义消息回调
|
||||
* 6. 断线自动重连
|
||||
* 7. 老师投屏进行中每 30s 查询 /school/teacherScreenShare/state,sharing 为 false 时结束投屏并停止轮询
|
||||
*/
|
||||
import { ref, shallowRef } from 'vue'
|
||||
import questionExplainApi from '@/api/subject/questionExplain'
|
||||
import { getCache } from '@/utils'
|
||||
import teacherScreenShareApi from '@/api/subject/teacherScreenShare'
|
||||
import { getCache, $XXL } from '@/utils'
|
||||
import { userStore } from '@/stores'
|
||||
import { handleClassMonitorMessage, stopClassMonitor } from './useClassMonitor'
|
||||
|
||||
@ -20,6 +22,9 @@ import { handleClassMonitorMessage, stopClassMonitor } from './useClassMonitor'
|
||||
/** 心跳发送间隔(毫秒) */
|
||||
const HEARTBEAT_INTERVAL = 30_000
|
||||
|
||||
/** 接收老师投屏时,查询服务端投屏状态的间隔(毫秒) */
|
||||
const TEACHER_SCREEN_SHARE_STATE_POLL_INTERVAL = 30_000
|
||||
|
||||
/** 断线重连延迟(毫秒) */
|
||||
const RECONNECT_DELAY = 5_000
|
||||
|
||||
@ -58,6 +63,11 @@ let reconnectAttempts = 0
|
||||
let isManualClose = false
|
||||
let visibilityHandler: (() => void) | null = null
|
||||
|
||||
/** 投屏状态轮询定时器(仅在收到 teacherScreenShareStart 后存在) */
|
||||
let screenSharePollTimer: ReturnType<typeof setInterval> | null = null
|
||||
/** 当前轮询对应的班级 id,用于丢弃过期异步结果 */
|
||||
let screenSharePollClassId: string | null = null
|
||||
|
||||
/** 外部注册的消息处理回调集合 */
|
||||
const messageHandlers: Set<MessageHandler> = new Set()
|
||||
|
||||
@ -70,6 +80,100 @@ const lastMessage = shallowRef<TeacherWsMessage | null>(null)
|
||||
/** 班级信息(响应式) */
|
||||
const classInfo = ref<any>(null)
|
||||
|
||||
const TEACHER_SCREEN_SHARE_TYPES = new Set(['teacherScreenShareStart', 'teacherScreenShareStop'])
|
||||
|
||||
/** 兼容顶层或嵌套在 `data` 下的投屏消息 */
|
||||
function resolveTeacherScreenSharePayload(data: TeacherWsMessage): TeacherWsMessage | null {
|
||||
if (TEACHER_SCREEN_SHARE_TYPES.has(data.type)) return data
|
||||
const inner = (data as Record<string, unknown>).data
|
||||
if (
|
||||
inner &&
|
||||
typeof inner === 'object' &&
|
||||
TEACHER_SCREEN_SHARE_TYPES.has((inner as TeacherWsMessage).type)
|
||||
) {
|
||||
return inner as TeacherWsMessage
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function parseMsgObject(msg: unknown): Record<string, unknown> | null {
|
||||
if (msg && typeof msg === 'object') return msg as Record<string, unknown>
|
||||
if (typeof msg === 'string') {
|
||||
try {
|
||||
return JSON.parse(msg) as Record<string, unknown>
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function stopTeacherScreenSharePolling() {
|
||||
if (screenSharePollTimer) {
|
||||
clearInterval(screenSharePollTimer)
|
||||
screenSharePollTimer = null
|
||||
}
|
||||
screenSharePollClassId = null
|
||||
}
|
||||
|
||||
async function fetchTeacherScreenShareStateOnce(classId: string) {
|
||||
try {
|
||||
const res = await teacherScreenShareApi.state(classId)
|
||||
if (screenSharePollClassId !== classId) return
|
||||
if (res?.sharing === false) {
|
||||
$XXL.stopTeacherShareScreen()
|
||||
stopTeacherScreenSharePolling()
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[TeacherExplain] 投屏状态查询失败:', err)
|
||||
}
|
||||
}
|
||||
|
||||
/** 仅在直播中轮询;直播结束或未直播不启动 */
|
||||
function startTeacherScreenSharePolling(classId: string) {
|
||||
const cid = String(classId).trim()
|
||||
if (!cid) return
|
||||
stopTeacherScreenSharePolling()
|
||||
screenSharePollClassId = cid
|
||||
screenSharePollTimer = setInterval(() => {
|
||||
fetchTeacherScreenShareStateOnce(cid).catch(() => {})
|
||||
}, TEACHER_SCREEN_SHARE_STATE_POLL_INTERVAL)
|
||||
}
|
||||
|
||||
/** 老师投屏开始/结束:调原生桥接 */
|
||||
function handleTeacherScreenShareMessage(data: TeacherWsMessage) {
|
||||
const root = resolveTeacherScreenSharePayload(data)
|
||||
if (!root) return
|
||||
|
||||
if (root.type === 'teacherScreenShareStart') {
|
||||
const msg = parseMsgObject(root.msg)
|
||||
if (!msg) return
|
||||
const host = msg.ip !== undefined && msg.ip !== null ? String(msg.ip) : ''
|
||||
const port = Number(msg.port)
|
||||
if (!host || !Number.isFinite(port)) return
|
||||
const u = userStore().userInfo
|
||||
$XXL.startTeacherShareScreen({
|
||||
host,
|
||||
port,
|
||||
name: String(u?.name || u?.nickName || ''),
|
||||
id: String(u?.userId ?? ''),
|
||||
})
|
||||
const pollClassId =
|
||||
msg.classId !== undefined && msg.classId !== null && String(msg.classId).trim() !== ''
|
||||
? String(msg.classId)
|
||||
: classInfo.value?.classId !== undefined && classInfo.value?.classId !== null
|
||||
? String(classInfo.value.classId)
|
||||
: ''
|
||||
if (pollClassId) startTeacherScreenSharePolling(pollClassId)
|
||||
return
|
||||
}
|
||||
|
||||
if (root.type === 'teacherScreenShareStop') {
|
||||
stopTeacherScreenSharePolling()
|
||||
$XXL.stopTeacherShareScreen()
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 内部方法 ========================
|
||||
|
||||
/** 获取带 token 的 WebSocket 连接地址 */
|
||||
@ -196,6 +300,7 @@ function handleMessage(data: TeacherWsMessage) {
|
||||
if (data.type === 'pong') return
|
||||
|
||||
handleClassMonitorMessage(data)
|
||||
handleTeacherScreenShareMessage(data)
|
||||
|
||||
// 调用所有已注册的外部消息回调
|
||||
messageHandlers.forEach(handler => {
|
||||
@ -288,6 +393,7 @@ async function init() {
|
||||
function close() {
|
||||
isManualClose = true
|
||||
stopHeartbeat()
|
||||
stopTeacherScreenSharePolling()
|
||||
stopClassMonitor()
|
||||
clearReconnectTimer()
|
||||
stopVisibilityListener()
|
||||
|
||||
@ -104,6 +104,11 @@ export const $XXL = {
|
||||
writeError: (error: string) => packSync('writeError', error),
|
||||
uninstallApp: (pkgName: string) => packSync('uninstallApp', pkgName),
|
||||
openExternalURL: (url: string) => packSync('openExternalURL', url),
|
||||
/** 老师投屏开始:参数为 JSON 字符串,与原生 `startTeacherShareScreen` 约定一致 */
|
||||
startTeacherShareScreen: (config: { host: string; port: number; name: string; id: string }) =>
|
||||
packSync('startTeacherShareScreen', JSON.stringify(config)),
|
||||
/** 老师投屏结束 */
|
||||
stopTeacherShareScreen: () => packSync('stopTeacherShareScreen'),
|
||||
// openApp: (config: anyObj) => pack('openApp', config),
|
||||
}
|
||||
|
||||
|
||||
@ -148,16 +148,16 @@ export default defineConfig({
|
||||
open: true,
|
||||
proxy: {
|
||||
'/api/campus': {
|
||||
target: 'http://43.136.52.196:9053',
|
||||
// target: 'http://43.136.52.196:9053',
|
||||
// target: 'http://test.web.xuexiaole.com',
|
||||
// target: 'https://ai.xuexiaole.com',
|
||||
target: 'https://ai.lingxixue.com',
|
||||
changeOrigin: true,
|
||||
rewrite: path => path.replace('/api/campus', '/'),
|
||||
},
|
||||
'/api': {
|
||||
target: 'http://43.136.52.196:9053',
|
||||
// target: 'http://43.136.52.196:9053',
|
||||
// target: 'http://test.web.xuexiaole.com',
|
||||
// target: 'https://ai.xuexiaole.com',
|
||||
target: 'https://ai.lingxixue.com',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user