HIGHLIGHT_MP4_SPEC.md 7.1 KB

WebScreen 高光时刻 MP4 录制 Spec

1. 目标

WebScreen 根据 SaaS 高光记录,把每个 beginTime/endTime 时间段录制成独立 MP4。

已确认:

  • 一个课堂可以有多条高光。
  • 一个高光时间段生成一个 MP4;N 条高光生成 N 个文件。
  • 当前只在 xdyui2 测试,验证后再增加 CrazyTalk。
  • WebScreen 只生成本地文件;服务器已有任务定时移动到 OSS。
  • 高光定时任务使用独立的 POST /highlight/recording/scheduled,不占用整堂录制入口。
  • 任务模式暂时只处理 status=0;后端状态回写规则待确认。

2. 兼容原则

原 GET /recording 始终执行整堂录制逻辑,不读取高光开关。

{
  "HIGHLIGHTCONFIG": {
    "enabled": false
  }
}

只有显式配置 enabled: true 才能调用 /highlight/* 高光业务接口。关闭高光不会关闭或改变整堂录制。

以下接口保持原行为:

  • GET /recording
  • POST /recording
  • POST /recordingTask
  • POST /fileExists
  • POST /mp4record/recording/:id

高光文件查询使用独立接口 POST /highlight/fileExists,不改变原 /fileExists 单文件响应。

3. 定时触发

WebScreen 本身不创建 cron。高光需要独立 cron:

57 7 * * * curl -fsS -X POST http://127.0.0.1:3001/highlight/recording/scheduled >/dev/null 2>&1

POST /highlight/recording/scheduled 读取 HIGHLIGHTCONFIG.sourceMode:

  • site:全量高光模式。
  • task:指定课堂任务模式。

这里“全量”表示录制指定站点前一天的全部高光,不表示录制整堂课堂。

4. 配置

当前 xdyui2 测试配置:

{
  "HIGHLIGHTCONFIG": {
    "enabled": true,
    "sourceMode": "site",
    "siteIds": ["xdyui2"],
    "apiBaseUrl": "https://saas.xuedianyun.com",
    "pageSize": 100,
    "taskPageSize": 100,
    "maxPages": 1000,
    "maxConcurrent": 2,
    "maxDurationMs": 21600000,
    "apiTimeoutMs": 10000,
    "apiRetryCount": 2,
    "apiRetryBaseDelayMs": 500,
    "loadGraceMs": 60000,
    "endGraceMs": 10000,
    "taskRetentionMs": 86400000,
    "outputNamespace": "",
    "outputBaseUrl": "https://xdymp4.xuedianyun.com"
  }
}

BACKMEDIACONFIG.url 继续由服务器配置决定,代码不硬编码 dev、devback 或 release。

5. 全量高光模式

配置:

"sourceMode": "site"

流程:

  1. cron 调用 POST /highlight/recording/scheduled。
  2. 按 Asia/Shanghai 计算前一天 00:00:00.000 至 23:59:59.999。
  3. 遍历 HIGHLIGHTCONFIG.siteIds。
  4. 分页调用 getBySitePrivate.do。
  5. 每条有效高光加入录制队列。
  6. 一个时间段生成一个本地 MP4。

接口:

POST /3m/api/highlight/getBySitePrivate.do
Content-Type: application/x-www-form-urlencoded

签名:

authId = MD5(siteId + timestamp)

6. 指定课堂任务模式

配置:

"sourceMode": "task"

流程:

  1. 分页调用 getRecordingTasksPrivate.do。
  2. 只保留 status=0 && onlyHighlight=1。
  3. 只保留 siteIds 白名单内的任务。
  4. taskList.meetingNumber 作为 classId。
  5. 调用 getByClassPrivate.do 获取该课堂全部高光。
  6. 每条高光加入录制队列。

任务接口签名:

authId = MD5(pageNo + pageSize + timestamp)

课堂高光接口签名:

authId = MD5(classId + timestamp)

代码必须保留:

// TODO: 等后端明确录制任务状态流转及完成回写接口。

任务状态回写接口确定前,使用内存队列、本地文件和 OSS 文件共同避免重复录制。

7. 字段映射

SaaS 高光记录:

{
  "id": 4,
  "meetingNumber": "1486758620",
  "siteId": "xdyui2",
  "beginTime": 1779159793000,
  "endTime": 1779159893000
}

WebScreen 内部统一为:

highlightId = id
classId     = meetingNumber

校验:

  • id 为正整数。
  • meetingNumber、siteId 只包含安全字符。
  • 时间为 13 位毫秒时间戳。
  • endTime > beginTime。
  • 时长不超过 maxDurationMs。
  • 站点必须属于 HIGHLIGHTCONFIG.siteIds。

唯一键:

siteId:highlightId

8. 回放录制地址

使用 URLSearchParams 在现有 BACKMEDIACONFIG.url 上增加:

classId={meetingNumber}
recordMp4=true
playRecord=1
recBeginTime={beginTime}
recEndTime={endTime}

recBeginTime 和 recEndTime 都是绝对毫秒时间戳,不换算为相对秒数。

调用 web_capture_c 时使用参数数组和 spawn(..., { shell: false }),不把接口字段拼接进 shell 命令。

9. 文件与上传

文件名:

{classId}_highlight_{highlightId}.mp4

路径:

本地:media/{siteId}/{yyyyMMdd}/{classId}_highlight_{highlightId}.mp4
OSS: oss/{siteId}/{yyyyMMdd}/{classId}_highlight_{highlightId}.mp4
URL: https://xdymp4.xuedianyun.com/oss/{siteId}/{yyyyMMdd}/{fileName}

日期按高光 beginTime 的 Asia/Shanghai 日期计算。

录制过程先写入 PROJECTCATALOG/.highlight_tmp,完成后原子移动到 media,避免 OSS 搬运程序读取半成品。队列完成后在涉及的日期目录写入 download.json,兼容原搬运机制。

WebScreen 不主动上传 OSS。

10. 队列和去重

  • 全局并发由 maxConcurrent 控制。
  • 同一课堂高光串行,避免同时加载同一课堂回放。
  • 不同课堂可以并行。
  • 队列中已有相同 siteId:highlightId 时不重复加入。
  • 本地最终文件存在时不重复录制。
  • OSS 文件存在时不重复录制。
  • OSS 查询异常时停止本轮处理,不能把查询失败当作文件不存在。

11. 高光多文件查询

POST /highlight/fileExists
Content-Type: application/json

请求:

{
  "siteId": "xdyui2",
  "classId": "1486758620"
}

WebScreen 调用 getByClassPrivate.do 获取该课堂全部高光,再逐条检查本地任务状态和 OSS。

响应:

{
  "code": 0,
  "message": "文件已生成",
  "onlyHighlight": 1,
  "classUrlList": [
    {
      "highlightId": 4,
      "classId": "1486758620",
      "siteId": "xdyui2",
      "beginTime": 1779159793000,
      "generated": true,
      "status": "generated",
      "url": "https://xdymp4.xuedianyun.com/oss/xdyui2/20260519/1486758620_highlight_4.mp4"
    }
  ]
}

全部生成时 code=0;无文件或存在未生成文件时 code=1,但仍返回每条记录状态。

12. 验收标准

  1. xdyui2 一个课堂有 N 条高光时生成 N 个不同 MP4。
  2. 每个录制 URL 包含正确的 recBeginTime/recEndTime。
  3. 重复执行 cron 不重复录制本地或 OSS 已存在文件。
  4. sourceMode=site 只查询前一天 xdyui2 高光。
  5. sourceMode=task 只处理 status=0 && onlyHighlight=1。
  6. /highlight/fileExists 返回课堂全部高光文件状态和地址。
  7. 原 /fileExists 响应不变。
  8. 删除或关闭 HIGHLIGHTCONFIG 后,高光接口拒绝处理,原 GET /recording 仍执行整堂录制。

13. 已知待办

  • 后端确认任务领取、成功、失败状态及回写接口。
  • xdyui2 验证通过后再增加 CrazyTalk 的准确 siteId。
  • 正式部署前确认服务器 OSS 搬运任务会处理新文件名和 download.json。