feat(phase2e-2): 前端 mediasoup-client 集成 + Pinia meeting Store(Task 9)

- frontend/src/api/meeting.js:12 个 REST 接口封装,统一 unwrap envelope.data
- frontend/src/services/websocket.js:新增 sendWithAck(Promise 化 + 超时 + 序列号)
- frontend/src/utils/mediasoup-client.js:MediaEngine 包装 Device/Transport/Producer/Consumer
- frontend/src/store/meeting.js:Pinia 会议状态机,桥接 14 个 WS 事件 + cleanupStaleMeetings
- frontend/src/constants/meeting.js:状态枚举 + 事件名集中管理
- frontend/src/pages/meeting/debug.vue:临时调试页(H5 原生 video/audio DOM 绕过 uni 组件限制)
- backend:meeting.consume.resume WS 事件 + create/join 响应透传 router_id + rtp_capabilities
- 文档:frontend/meeting.md、websocket.md、CURRENT_STATUS、plan 全部同步 Task 9 落地

Made-with: Cursor
This commit is contained in:
bujinyuan
2026-04-22 11:22:49 +08:00
parent e7f6a32dfe
commit f97fec24a8
22 changed files with 2314 additions and 60 deletions

View File

@@ -0,0 +1,163 @@
/**
* 会议模块前端常量
*
* 与后端 backend/go-service/app/constants/meeting.go 严格一致
* 新增/修改任一常量时必须同步维护两端,否则运行时会因事件名不匹配而静默失败
*
* 版本说明:
* - Task 62026-04-20落地 13 个 WS 事件
* - Task 92026-04-22新增 meeting.consume.resume总计 14 个事件
*/
// ==================== 会议类型 ====================
export const MEETING_TYPE_INSTANT = 1 // 即时会议MVP 仅此)
export const MEETING_TYPE_SCHEDULED = 2 // 预约会议Phase 2e-3
export const MEETING_TYPE_LABEL = {
[MEETING_TYPE_INSTANT]: '即时会议',
[MEETING_TYPE_SCHEDULED]: '预约会议'
}
// ==================== 会议状态 ====================
export const MEETING_STATUS_PENDING = 0 // 未开始(仅预约会议)
export const MEETING_STATUS_ACTIVE = 1 // 进行中
export const MEETING_STATUS_ENDED = 2 // 已结束
export const MEETING_STATUS_LABEL = {
[MEETING_STATUS_PENDING]: '未开始',
[MEETING_STATUS_ACTIVE]: '进行中',
[MEETING_STATUS_ENDED]: '已结束'
}
// ==================== 参与者角色 ====================
export const MEETING_ROLE_PARTICIPANT = 0 // 普通参会人
export const MEETING_ROLE_HOST = 1 // 主持人
export const MEETING_ROLE_COHOST = 2 // 联合主持人保留MVP 不用)
export const MEETING_ROLE_LABEL = {
[MEETING_ROLE_PARTICIPANT]: '参会人',
[MEETING_ROLE_HOST]: '主持人',
[MEETING_ROLE_COHOST]: '联合主持人'
}
// ==================== 会议结束原因 ====================
export const MEETING_ENDED_REASON_HOST_ENDED = 'host_ended'
export const MEETING_ENDED_REASON_EMPTY_TTL = 'empty_ttl'
export const MEETING_ENDED_REASON_ADMIN_FORCE = 'admin_force'
export const MEETING_ENDED_REASON_SYSTEM_ERROR = 'system_error'
export const MEETING_ENDED_REASON_LABEL = {
[MEETING_ENDED_REASON_HOST_ENDED]: '主持人结束',
[MEETING_ENDED_REASON_EMPTY_TTL]: '空房超时',
[MEETING_ENDED_REASON_ADMIN_FORCE]: '管理员强制结束',
[MEETING_ENDED_REASON_SYSTEM_ERROR]: '系统异常'
}
// ==================== 离会原因 ====================
export const MEETING_LEFT_REASON_SELF = 'self'
export const MEETING_LEFT_REASON_KICKED = 'kicked'
export const MEETING_LEFT_REASON_HOST_END = 'host_end'
export const MEETING_LEFT_REASON_EMPTY_TTL = 'empty_ttl'
export const MEETING_LEFT_REASON_DISCONNECT = 'disconnect'
export const MEETING_LEFT_REASON_LABEL = {
[MEETING_LEFT_REASON_SELF]: '主动离开',
[MEETING_LEFT_REASON_KICKED]: '被移除',
[MEETING_LEFT_REASON_HOST_END]: '会议结束',
[MEETING_LEFT_REASON_EMPTY_TTL]: '空房超时',
[MEETING_LEFT_REASON_DISCONNECT]: '网络断开'
}
// ==================== 主持人变更自动转让原因Task 8 引入) ====================
export const MEETING_HOST_AUTO_REASON_GRACE_EXPIRED = 'host_grace_expired' // 主持人宽限期到期自动转让
export const MEETING_HOST_AUTO_REASON_HOST_KICKED = 'host_kicked' // (保留)主持人被管理员踢出自动转让
export const MEETING_HOST_AUTO_REASON_LABEL = {
[MEETING_HOST_AUTO_REASON_GRACE_EXPIRED]: '主持人掉线超时自动转让',
[MEETING_HOST_AUTO_REASON_HOST_KICKED]: '主持人被移除自动转让'
}
// ==================== 默认配置常量 ====================
export const MEETING_MVP_MAX_MEMBERS = 8 // MVP 单会议硬上限
export const MEETING_HOST_GRACE_SECONDS = 120 // 主持人掉线宽限期Task 8
export const MEETING_EMPTY_ROOM_TTL_SECONDS = 300 // 空房销毁 TTLTask 8
export const MEETING_WS_ACK_TIMEOUT_MS = 10000 // WS 事件 ACK 等待超时Task 9
// ==================== WS 事件名14 个,与后端 constants/meeting.go 一一对应) ====================
// 房间组3 个)
export const MEETING_WS_ROOM_JOIN = 'meeting.room.join' // C→S
export const MEETING_WS_ROOM_LEAVE = 'meeting.room.leave' // C→S
export const MEETING_WS_ROOM_ENDED = 'meeting.room.ended' // S→C
// 成员组6 个)
export const MEETING_WS_MEMBER_JOINED = 'meeting.member.joined' // S→C
export const MEETING_WS_MEMBER_LEFT = 'meeting.member.left' // S→C
export const MEETING_WS_MEMBER_STATE_CHANGED = 'meeting.member.state.changed' // 双向
export const MEETING_WS_MEMBER_KICKED = 'meeting.member.kicked' // S→C定向
export const MEETING_WS_MEMBER_PRODUCER_NEW = 'meeting.member.producer.new' // S→C
export const MEETING_WS_HOST_CHANGED = 'meeting.host.changed' // S→C
// 媒体组6 个,含 Task 9 新增 consume.resume
export const MEETING_WS_TRANSPORT_CREATE = 'meeting.transport.create' // C→S
export const MEETING_WS_TRANSPORT_CONNECT = 'meeting.transport.connect' // C→S
export const MEETING_WS_PRODUCE_START = 'meeting.produce.start' // C→S
export const MEETING_WS_CONSUME_START = 'meeting.consume.start' // C→S
export const MEETING_WS_CONSUME_RESUME = 'meeting.consume.resume' // C→STask 9 新增)
export const MEETING_WS_PRODUCER_CLOSE = 'meeting.producer.close' // C→S
// 聊天1 个)
export const MEETING_WS_CHAT_MESSAGE = 'meeting.chat.message' // S→C
/** C→S 客户端可主动发起的事件白名单 */
export const MEETING_WS_CLIENT_EVENTS = [
MEETING_WS_ROOM_JOIN,
MEETING_WS_ROOM_LEAVE,
MEETING_WS_MEMBER_STATE_CHANGED,
MEETING_WS_TRANSPORT_CREATE,
MEETING_WS_TRANSPORT_CONNECT,
MEETING_WS_PRODUCE_START,
MEETING_WS_CONSUME_START,
MEETING_WS_CONSUME_RESUME,
MEETING_WS_PRODUCER_CLOSE
]
/** S→C 服务端广播事件白名单(用于 Store 注册监听时统一处理) */
export const MEETING_WS_SERVER_EVENTS = [
MEETING_WS_ROOM_ENDED,
MEETING_WS_MEMBER_JOINED,
MEETING_WS_MEMBER_LEFT,
MEETING_WS_MEMBER_STATE_CHANGED,
MEETING_WS_MEMBER_KICKED,
MEETING_WS_MEMBER_PRODUCER_NEW,
MEETING_WS_HOST_CHANGED,
MEETING_WS_CHAT_MESSAGE
]
// ==================== 本地会议状态机(前端专用,与后端 meeting_rooms.status 无关) ====================
// Task 9 用于 store/meeting.js 驱动 UI 的本地状态
export const MEETING_LOCAL_STATE_IDLE = 'idle' // 未进入会议
export const MEETING_LOCAL_STATE_JOINING = 'joining' // REST JoinRoom 中
export const MEETING_LOCAL_STATE_CONNECTING = 'connecting' // Device load + Transport 创建中
export const MEETING_LOCAL_STATE_CONNECTED = 'connected' // 媒体层就绪,可推流/收流
export const MEETING_LOCAL_STATE_RECONNECTING = 'reconnecting' // WS 断线重连期间Task 8 host 宽限期共用)
export const MEETING_LOCAL_STATE_LEAVING = 'leaving' // REST LeaveRoom 中
export const MEETING_LOCAL_STATE_ENDED = 'ended' // 会议已结束room.ended 收到后进入)
export const MEETING_LOCAL_STATE_LABEL = {
[MEETING_LOCAL_STATE_IDLE]: '未入会',
[MEETING_LOCAL_STATE_JOINING]: '加入中',
[MEETING_LOCAL_STATE_CONNECTING]: '连接中',
[MEETING_LOCAL_STATE_CONNECTED]: '已连接',
[MEETING_LOCAL_STATE_RECONNECTING]: '重连中',
[MEETING_LOCAL_STATE_LEAVING]: '离开中',
[MEETING_LOCAL_STATE_ENDED]: '已结束'
}