Files
EchoChat/docs/plans/2026-05-18-recording-progress.md
2026-05-18 19:42:55 +08:00

5.0 KiB
Raw Permalink Blame History

会议录制实现进度Phase B1→B4

启动日期2026-05-18 负责人Cascade pair-programming session

全局架构

[host 工具栏] → POST /meeting/rooms/{code}/recording/toggle
   ↓
[Go MeetingRecordingService]
   ├─ 校验 host 权限 + 会议状态
   ├─ 落库 meeting_recordings(status=recording)
   └─ 调 HTTPMediaOrchestrator.StartRecording(roomCode, producerIDs)
       ↓
[media-server /internal/v1/recordings]
   ├─ 为每个 Producer 建 PlainTransportrtcpMux:false, comedia:false
   ├─ consume 该 Producer 拿到 negotiated rtpParameters
   ├─ 选未占用 UDP 端口对rtp+rtcp给 ffmpeg
   ├─ 写 SDP 文件(含 audio/video pt、codec、rtpmap、fmtp
   ├─ spawn ffmpeg: -protocol_whitelist file,udp,rtp -i input.sdp -c copy output.mp4
   └─ transport.connect({ ip, port: ffmpegPort, rtcpPort: ffmpegRtcpPort })
       ↓
[ffmpeg 写盘 → /tmp/recordings/{recordingId}.mp4]
       ↓
[host 再次 toggle → 停止]
   ├─ media-server SIGINT ffmpeg → wait exit → close transports/consumers
   ├─ POST /api/v1/meeting/_internal/recordings/{id}/finalizecallback
   └─ Go 流式拉取 mp4 → MinIO → 落库 status=ready + file_url + size + duration
       ↓
[WS 广播 meeting.recording.started/stopped 给所有成员]

阶段拆分

Phase B1 — 媒体层录制管线 + Go 控制端 ✏️ 进行中

media-server

  • services/recording.service.ts起停录制、SDP 生成、ffmpeg 进程管理、端口池
  • routes/recording.route.tsPOST /internal/v1/recordingsDELETE /internal/v1/recordings/:idGET /internal/v1/recordings/:id
  • schemas/recording.schema.tszod 校验
  • app.ts:注册路由
  • 端口池实现 utils/port-pool.ts

Go 后端

  • app/meeting/model/meeting_recording.go:表 ORM
  • app/meeting/dao/meeting_recording_dao.go
  • app/meeting/service/interfaces.go 扩展 MediaOrchestrator: StartRecording / StopRecording
  • app/meeting/service/http_media_orchestrator.go 实现 RPC
  • cmd/server/main.go AutoMigrate 加入 MeetingRecording

Phase B2 — 业务层 + 持久化

  • service/meeting_recording_service.goStartRecording/StopRecording/ListRecordings/GetRecording
  • controller/meeting_controller.go 新增 4 个端点
  • router.go 挂载路由
  • provider.go wire 注册
  • 内部回调端点 /_internal/recordings/{id}/finalizemedia-server 录完后回调Go 拉文件→ MinIO → 更新 DB
  • MinIO 流式上传

Phase B3 — WS 广播 + 自动停录

  • constants/meeting.goMeetingWSEventRecordingStarted/Stopped
  • meeting_signal_service.go / meeting_broadcaster.go 广播
  • EndRoom / HandleEmptyRoomExpired 自动 StopRecording
  • 前端 constants/meeting.js + store 监听

Phase B4 — 前端 UI

  • MeetingToolbar.vue:录制按钮(仅 host 可见)+ 红点动画
  • room.vue:顶部"REC"指示条(任意成员都可见)
  • 新页面 pages/meeting/recordings.vue:列表 + 下载

已知风险与决策

  1. ffmpeg 必须可用:媒体服务的运行环境(开发:本地 PATH生产Docker 镜像 apt install ffmpeg。Dockerfile 暂不改,由你部署时确认。
  2. PlainTransport 端口分配:用 dgram 试探可用 UDP 端口;初始范围 50000-59999避开 mediasoup 40000-40199
  3. ffmpeg 启停时序:录制启动顺序 = 起 ffmpeg(listen) → 100ms 延迟 → mediasoup transport.connect → consume停止顺序 = SIGINT ffmpeg → wait exit → close transports/consumers。
  4. 录制视频编码:用 -c:v copy -c:a copy 直接复制 RTP 载荷为 mp4不重编码节省 CPU。注意浏览器端 VP8/VP9 muxed 进 mp4 可能不被普通播放器识别,必要时改 -c:v libx264 -preset ultrafast。本期先用 copy。
  5. DB 表 schema:参考下方 §SQL。
  6. Phase B 整体不强制阻塞:每完成一个阶段都可以暂停验证。

SQL

CREATE TABLE meeting_recordings (
  id              BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  room_id         BIGINT UNSIGNED NOT NULL,
  room_code       VARCHAR(32)     NOT NULL,
  started_by      BIGINT UNSIGNED NOT NULL,
  status          VARCHAR(16)     NOT NULL DEFAULT 'recording',  -- recording/uploading/ready/failed
  remote_id       VARCHAR(64),                                   -- media-server 侧的 recordingId
  file_url        VARCHAR(512),
  file_object     VARCHAR(255),
  size_bytes      BIGINT UNSIGNED DEFAULT 0,
  duration_sec    INT UNSIGNED    DEFAULT 0,
  failure_reason  VARCHAR(255),
  started_at      DATETIME        NOT NULL,
  stopped_at      DATETIME,
  created_at      DATETIME        NOT NULL,
  updated_at      DATETIME        NOT NULL,
  KEY idx_room (room_id),
  KEY idx_room_code (room_code),
  KEY idx_started_by (started_by)
);

当前会话进度

  • 调研完成MinIO 已就绪、media-server 架构吃透、HTTPMediaOrchestrator 模式吃透
  • 下一步:写 media-server 端口池 + recording.service.ts