From 2da9c45b61d7e4483b1c1278f50283f35a33249e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=E6=98=B5=E7=A7=B0=E9=87=8D=E8=A6=81=E5=90=97O?= Date: Sun, 15 Mar 2026 21:01:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(websocket):=20=E6=B7=BB=E5=8A=A0=E6=8A=95?= =?UTF-8?q?=E5=B1=8F=E5=AE=9E=E9=99=85=E5=A3=B0=E7=BD=91UID=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在MeetingRedisService中扩展setScreenSharing方法以支持screenShareActualUid参数 - 更新RoomState类型定义以包含screenShareActualUid字段 - 修改MeetingWebSocketGateway以传递screenShareActualUid数据 - 扩展MeetingJoinRoomSuccessData接口以包含screenShareActualUid - 在学生端组件中移除计算逻辑并直接使用后端返回的screenShareActualUid - 更新投屏开始和结束事件处理以正确设置screenShareActualUid状态 - 完善房间加入时的screenShareActualUid数据初始化逻辑 --- .../websocket/meeting-redis.service.ts | 13 +++++++++-- .../modules/websocket/meeting.websocket.ts | 5 ++-- node_api/src/modules/websocket/types.ts | 2 ++ .../meeting-room/meeting-room-student.vue | 23 ++++++++----------- src/views/meeting/meeting-room/types.ts | 2 ++ 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/node_api/src/modules/websocket/meeting-redis.service.ts b/node_api/src/modules/websocket/meeting-redis.service.ts index fb00a0a..0df0925 100644 --- a/node_api/src/modules/websocket/meeting-redis.service.ts +++ b/node_api/src/modules/websocket/meeting-redis.service.ts @@ -248,13 +248,19 @@ export class MeetingRedisService { * 设置投屏状态 * @param roomId - 房间 ID * @param screenShareOwnerUid - 投屏人短 UID,null 表示停止投屏 + * @param screenShareActualUid - 投屏的实际声网 UID(可选) */ - async setScreenSharing(roomId: string, screenShareOwnerUid: number | null): Promise { + async setScreenSharing(roomId: string, screenShareOwnerUid: number | null, screenShareActualUid?: number | null): Promise { const key = `${this.KEY_PREFIX.ROOM_STATE}${roomId}`; if (screenShareOwnerUid === null) { await this.getClient().hdel(key, 'screenShareOwnerUid'); + await this.getClient().hdel(key, 'screenShareActualUid'); } else { - await this.getClient().hset(key, { screenShareOwnerUid: String(screenShareOwnerUid) }); + const data: Record = { screenShareOwnerUid: String(screenShareOwnerUid) }; + if (typeof screenShareActualUid === 'number') { + data.screenShareActualUid = String(screenShareActualUid); + } + await this.getClient().hset(key, data); await this.getClient().expire(key, 24 * 60 * 60); } } @@ -293,6 +299,7 @@ export class MeetingRedisService { classStatus: 'finished' | 'in_class' | 'not_started'; speakerUid?: number; screenShareOwnerUid?: number; + screenShareActualUid?: number; teacherUid?: number; endTimestamp?: number; }> { @@ -301,12 +308,14 @@ export class MeetingRedisService { const status = (map?.classStatus as any) || 'not_started'; const speakerUid = map?.speakerUid ? Number(map.speakerUid) : undefined; const screenShareOwnerUid = map?.screenShareOwnerUid ? Number(map.screenShareOwnerUid) : undefined; + const screenShareActualUid = map?.screenShareActualUid ? Number(map.screenShareActualUid) : undefined; const teacherUid = map?.teacherUid ? Number(map.teacherUid) : undefined; const endTimestamp = map?.endTimestamp ? Number(map.endTimestamp) : undefined; return { classStatus: status, ...(Number.isFinite(speakerUid) ? { speakerUid } : {}), ...(Number.isFinite(screenShareOwnerUid) ? { screenShareOwnerUid } : {}), + ...(Number.isFinite(screenShareActualUid) ? { screenShareActualUid } : {}), ...(Number.isFinite(teacherUid) ? { teacherUid } : {}), ...(Number.isFinite(endTimestamp) ? { endTimestamp } : {}), }; diff --git a/node_api/src/modules/websocket/meeting.websocket.ts b/node_api/src/modules/websocket/meeting.websocket.ts index 0a7368c..60606fe 100644 --- a/node_api/src/modules/websocket/meeting.websocket.ts +++ b/node_api/src/modules/websocket/meeting.websocket.ts @@ -364,6 +364,7 @@ export class MeetingWebSocketGateway implements OnGatewayConnection, OnGatewayDi isVideoMuted: userState.isVideoMuted, classStatus: roomState.classStatus, screenShareOwnerUid: roomState.screenShareOwnerUid, + screenShareActualUid: roomState.screenShareActualUid, speakerUid, teacherUid, ...(resolvedEndTimestamp ? { endTimestamp: resolvedEndTimestamp } : {}), @@ -715,8 +716,8 @@ export class MeetingWebSocketGateway implements OnGatewayConnection, OnGatewayDi if (!Number.isFinite(screenUid) || screenUid <= 0) { return; } - // 保存投屏状态到 Redis - await this.redisService.setScreenSharing(data.roomId, targetUid); + // 保存投屏状态到 Redis(同时保存短 UID 和实际 UID) + await this.redisService.setScreenSharing(data.roomId, targetUid, screenUid); // 广播给整个房间:有人开始投屏 this.server?.to(data.roomId).emit('message', { type: 'sev_start_screen_share', data: { fromRoomId: data.roomId, targetUid, screenUid } }); this.log(`创建者开始投屏:roomId=${data.roomId}, shortUid=${targetUid}, screenUid=${screenUid}`); diff --git a/node_api/src/modules/websocket/types.ts b/node_api/src/modules/websocket/types.ts index 8316ef3..8542c93 100644 --- a/node_api/src/modules/websocket/types.ts +++ b/node_api/src/modules/websocket/types.ts @@ -163,6 +163,8 @@ export interface MeetingJoinRoomSuccessData { classStatus: 'finished' | 'in_class' | 'not_started'; /** 投屏状态:正在投屏的用户短 UID,undefined表示无人投屏 */ screenShareOwnerUid?: number; + /** 投屏的实际声网 UID(仅在有投屏时存在) */ + screenShareActualUid?: number; /** 上台的短 uid,默认为老师的 uid */ speakerUid: number; /** 老师的短 uid */ diff --git a/src/views/meeting/meeting-room/meeting-room-student.vue b/src/views/meeting/meeting-room/meeting-room-student.vue index 0a9fcda..29793b5 100644 --- a/src/views/meeting/meeting-room/meeting-room-student.vue +++ b/src/views/meeting/meeting-room/meeting-room-student.vue @@ -116,6 +116,8 @@ const speakerUid = ref(null); /** 投屏用户 ID */ const screenShareOwnerUid = ref(null); + /** 投屏的实际声网 UID(由后端返回) */ + const screenShareActualUid = ref(null); const studentPrimaryOverrideUid = ref(null); const meeting = useAgoraMeeting( @@ -176,19 +178,6 @@ const isTeacherDualStream = computed(() => Boolean(teacherUid.value && screenShareOwnerUid.value)); - /** 根据投屏用户短 UID 计算实际的声网 UID */ - function calculateScreenActualUid(uid: number): number { - return uid + 1000000; - } - - /** 获取当前投屏的实际声网 UID(如果有) */ - const screenShareActualUid = computed(() => { - if (typeof screenShareOwnerUid.value === 'number' && screenShareOwnerUid.value > 0) { - return calculateScreenActualUid(screenShareOwnerUid.value); - } - return null; - }); - const teacherPreferredPrimaryUid = computed(() => { // 如果有投屏,优先使用投屏的实际 UID if (screenShareActualUid.value) { @@ -554,9 +543,11 @@ } case 'sev_start_screen_share': { const targetUid = packet.data?.targetUid; + const screenUid = packet.data?.screenUid; if (typeof targetUid === 'number') { screenShareOwnerUid.value = targetUid; - console.log('[屏幕共享] 收到开始投屏消息:', { targetUid, calculatedActualUid: calculateScreenActualUid(targetUid) }); + screenShareActualUid.value = typeof screenUid === 'number' ? screenUid : null; + console.log('[屏幕共享] 收到开始投屏消息:', { targetUid, screenUid }); Toast.info?.(`用户 ${targetUid} 开始投屏`); } break; @@ -565,6 +556,7 @@ const targetUid = packet.data?.targetUid; if (typeof targetUid === 'number') { screenShareOwnerUid.value = null; + screenShareActualUid.value = null; console.log('[屏幕共享] 收到停止投屏消息:', { targetUid }); Toast.info?.(`用户 ${targetUid} 停止投屏`); } @@ -656,6 +648,9 @@ if (typeof roomData.screenShareOwnerUid === 'number') { screenShareOwnerUid.value = roomData.screenShareOwnerUid; } + if (typeof roomData.screenShareActualUid === 'number') { + screenShareActualUid.value = roomData.screenShareActualUid; + } if (classStatus.value === 'in_class') { await meeting.initAndJoin(roomData); // ✅ 加入后先检查是否有已存在的远程用户 diff --git a/src/views/meeting/meeting-room/types.ts b/src/views/meeting/meeting-room/types.ts index 8583ff2..7406d86 100644 --- a/src/views/meeting/meeting-room/types.ts +++ b/src/views/meeting/meeting-room/types.ts @@ -262,6 +262,8 @@ export interface OnJoinRoomData { classStatus?: ClassStatus; /** 投屏状态:正在投屏的用户短 UID,undefined表示无人投屏 */ screenShareOwnerUid?: number; + /** 投屏的实际声网 UID(仅在有投屏时存在) */ + screenShareActualUid?: number; /** 主讲人短 UID(默认等于 teacherUid) */ speakerUid: number; /** 老师短 UID */