视频会议保存
This commit is contained in:
107
docs/plans/2026-05-18-recording-progress.md
Normal file
107
docs/plans/2026-05-18-recording-progress.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# 会议录制实现进度(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 建 PlainTransport(rtcpMux: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}/finalize(callback)
|
||||
└─ 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.ts`:`POST /internal/v1/recordings`、`DELETE /internal/v1/recordings/:id`、`GET /internal/v1/recordings/:id`
|
||||
- [ ] `schemas/recording.schema.ts`:zod 校验
|
||||
- [ ] `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.go`:StartRecording/StopRecording/ListRecordings/GetRecording
|
||||
- [ ] `controller/meeting_controller.go` 新增 4 个端点
|
||||
- [ ] `router.go` 挂载路由
|
||||
- [ ] `provider.go` wire 注册
|
||||
- [ ] 内部回调端点 `/_internal/recordings/{id}/finalize`:media-server 录完后回调,Go 拉文件→ MinIO → 更新 DB
|
||||
- [ ] MinIO 流式上传
|
||||
|
||||
### Phase B3 — WS 广播 + 自动停录
|
||||
- [ ] `constants/meeting.go`:MeetingWSEventRecordingStarted/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
|
||||
|
||||
```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
|
||||
Reference in New Issue
Block a user