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

@@ -368,6 +368,35 @@ func (s *MeetingSignalService) OnConsumeStart(ctx context.Context, userID int64,
return info, nil
}
// ConsumeResumePayload meeting.consume.resume 请求载荷Task 9
// 前端 recv Transport 与 track 就绪后发送,用于把 Node 侧 paused Consumer 切到 active
type ConsumeResumePayload struct {
RoomCode string `json:"room_code"`
ConsumerID string `json:"consumer_id"`
}
// OnConsumeResume 处理 meeting.consume.resume 事件Task 9
// 语义:告知 media-server 把指定 Consumer 从 paused 切到 active
// 权限:仅当 userID 是会议活跃成员且 consumerID 归属该用户时允许
// 幂等Node 对已 active Consumer 再次 resume 不报错Consumer 不存在则 ACK 返回友好错误
func (s *MeetingSignalService) OnConsumeResume(ctx context.Context, userID int64, payload *ConsumeResumePayload) error {
if payload.ConsumerID == "" {
return fmt.Errorf("consumer_id 不能为空")
}
if _, err := s.loadRoomAndParticipant(ctx, payload.RoomCode, userID); err != nil {
return err
}
if err := s.mediaOrchestrator.ResumeConsumer(ctx, payload.ConsumerID); err != nil {
logs.Warn(ctx, "service.meeting_signal_service.OnConsumeResume", "恢复 Consumer 失败",
zap.String("room_code", payload.RoomCode),
zap.Int64("user_id", userID),
zap.String("consumer_id", payload.ConsumerID),
zap.Error(err))
return err
}
return nil
}
// ProducerClosePayload meeting.producer.close 请求载荷
type ProducerClosePayload struct {
RoomCode string `json:"room_code"`