# 动态 FPS 截图推送模块 ## 1. 目标 现有 Browser SSE 已经支持 Screenshot 推送,本次只增加**动态 Screenshot 推送频率**。 核心规则: - 无操作时:每 **2 秒** 推送一张 Screenshot - 用户发生浏览器操作后:进入高频模式 - 高频模式:每 **500ms** 推送一张 Screenshot - 高频模式持续 **5 秒** - 5 秒的计算基于**最后一次浏览器操作** - 新操作发生时,只延长高频模式时间 - 操作本身**不立即触发 Screenshot** - 不使用真正的视频流 - 不使用 WebRTC - 不使用 VNC - Screenshot 仍然是独立 JPEG 图片 - Screenshot 仍然通过现有 SSE 推送 --- # 2. 配置 默认配置: ```ts const screenshotConfig = { quality: 30, normalInterval: 2000, activeInterval: 500, activeDuration: 5000 } ``` 参数: | 参数 | 默认值 | 说明 | |---|---:|---| | `quality` | `30` | JPEG 质量 | | `normalInterval` | `2000` | 普通模式截图间隔,单位 ms | | `activeInterval` | `500` | 活跃模式截图间隔,单位 ms | | `activeDuration` | `5000` | 最后一次操作后保持活跃模式的时间,单位 ms | 所有时间参数统一使用 `ms`。 --- # 3. Screenshot Mode 只有两种状态: ```ts type ScreenshotMode = 'normal' | 'active' ``` ### Normal ```text interval = 2000ms ``` 即: ```text Screenshot ↓ 等待 2 秒 ↓ Screenshot ↓ 等待 2 秒 ↓ ... ``` ### Active ```text interval = 500ms ``` 即: ```text Screenshot ↓ 等待 500ms ↓ Screenshot ↓ 等待 500ms ↓ ... ``` --- # 4. 状态 Scheduler 至少维护: ```ts interface ScreenshotSchedulerState { mode: 'normal' | 'active' lastActivityAt: number activeUntil: number running: boolean } ``` 说明: ### `mode` 当前截图模式: ```text normal active ``` ### `lastActivityAt` 最后一次成功 Browser Action 的时间。 ### `activeUntil` Active Mode 的结束时间。 例如: ```text 当前时间:10:00:00 用户操作 ↓ activeUntil = 10:00:05 ``` --- # 5. 操作触发 Active Mode 任何**成功的浏览器操作**完成后调用: ```ts notifyActivity() ``` 实现: ```ts function notifyActivity() { const now = Date.now() lastActivityAt = now activeUntil = now + activeDuration } ``` 同时: ```ts mode = 'active' ``` --- # 6. 操作不立即 Screenshot 这是必须遵守的规则。 错误: ```text Browser Action ↓ Screenshot ↓ SSE ``` 正确: ```text Browser Action ↓ Action 执行成功 ↓ notifyActivity() ↓ 切换 Active Mode ↓ Scheduler 按 500ms 推送 Screenshot ``` 也就是说: > **Browser Action 只修改 Screenshot Scheduler 状态,不主动调用 Screenshot。** --- # 7. Active Mode 生命周期 例如: ```text 00:00 用户 click ↓ Active Until = 00:05 00:00.5 Screenshot 00:01.0 Screenshot 00:01.5 Screenshot 00:02.0 Screenshot ... 00:05.0 ↓ 恢复 Normal ``` 恢复后: ```text 每 2000ms Screenshot ``` --- # 8. 连续操作 如果 Active Mode 中再次发生操作: ```text 00:00 click ↓ activeUntil = 00:05 00:02 fill ↓ activeUntil = 00:07 00:04 scroll ↓ activeUntil = 00:09 ``` 因此: > **Active Mode 的 5 秒永远从最后一次操作重新计算。** 最终: ```text 00:04 最后一次操作 ↓ 保持 Active 到 00:09 ↓ 恢复 Normal ``` --- # 9. 多次操作不会创建多个 Timer 禁止: ```ts setInterval(...) setInterval(...) setInterval(...) ``` 也禁止每次操作创建一个新的 Screenshot Loop。 整个 Browser Session 只允许存在: ```text 1 个 Screenshot Scheduler ``` 所有操作统一调用: ```ts notifyActivity() ``` --- # 10. Scheduler 推荐采用单一循环。 示例: ```ts class ScreenshotScheduler { private running = false private mode: 'normal' | 'active' = 'normal' private activeUntil = 0 async start() { if (this.running) { return } this.running = true while (this.running) { const now = Date.now() this.mode = now < this.activeUntil ? 'active' : 'normal' await this.capture() const interval = this.mode === 'active' ? this.config.activeInterval : this.config.normalInterval await sleep(interval) } } notifyActivity() { const now = Date.now() this.mode = 'active' this.activeUntil = now + this.config.activeDuration } stop() { this.running = false } } ``` 实际代码可以根据项目结构调整,但必须保持相同的行为。 --- # 11. Screenshot 执行顺序 Scheduler 每次执行: ```text 检查当前 Mode ↓ Screenshot ↓ SSE 推送 ↓ 根据当前 Mode 等待 ↓ 进入下一轮 ``` 例如 Normal: ```text Screenshot ↓ wait 2000ms ↓ Screenshot ↓ wait 2000ms ``` Active: ```text Screenshot ↓ wait 500ms ↓ Screenshot ↓ wait 500ms ``` --- # 12. Screenshot 不允许并发 必须保证同一 Browser Session: ```text 同时最多 1 个 Screenshot ``` 禁止: ```text Screenshot A ↓ 还没完成 Screenshot B ↓ 还没完成 Screenshot C ``` 正确: ```text Screenshot A ↓ 完成 ↓ 等待 interval ↓ Screenshot B ``` 原因: - 避免 CPU 突增 - 避免内存占用增加 - 避免多个 Screenshot 同时进入 SSE - 避免页面状态竞争 --- # 13. Browser Action 与 Scheduler 的关系 Browser Controller 在 Action 成功后: ```ts await executeAction(action) scheduler.notifyActivity() ``` 不要: ```ts await executeAction(action) await screenshot() ``` 也不要: ```ts scheduler.start() ``` Browser Session 创建时只启动一次 Scheduler。 --- # 14. Action 失败 Action 失败: ```text Browser Action ↓ 失败 ↓ 不调用 notifyActivity() ``` 因此不会进入 Active Mode。 例如: ```text click element ↓ Element Not Found ↓ Action Failed ↓ 保持 Normal Mode ``` --- # 15. Navigation Action 以下操作成功后同样进入 Active Mode: ```text goto reload back forward ``` 例如: ```text goto ↓ 页面加载完成 ↓ notifyActivity() ↓ Active Mode ↓ 500ms Screenshot ``` --- # 16. Batch Action Batch Action 只调用一次: ```ts scheduler.notifyActivity() ``` 例如: ```text Action 1 ↓ Action 2 ↓ Action 3 ↓ 全部成功 ↓ notifyActivity() ``` 不要: ```text Action 1 → notifyActivity() Action 2 → notifyActivity() Action 3 → notifyActivity() ``` Batch 本身只需要最终刷新一次 Active Mode 时间。 --- # 17. SSE 动态 FPS 不改变现有 SSE API。 仍然: ```http GET /profiles/:profileId/browser/events ``` Screenshot Event 保持现有格式。 Scheduler 只决定: > **什么时候生成下一张 Screenshot。** SSE 只负责: > **把 Screenshot 推送给前端。** 两者职责保持分离。 --- # 18. SSE State 建议在 `state` event 中增加: ```json { "mode": "active", "normalInterval": 2000, "activeInterval": 500, "activeUntil": 1780000000000 } ``` 前端可以知道当前 Screenshot Scheduler 状态。 如果现有 `state` 已经定义完成,也可以不修改协议,仅在服务端内部维护状态。 --- # 19. SSE Client 数量 如果当前没有 SSE Client: ```text SSE Client = 0 ``` 可以暂停 Screenshot Scheduler。 重新建立 SSE: ```text SSE Connected ↓ 启动 Screenshot Scheduler ↓ 发送当前 Screenshot ↓ 进入 Normal Mode ``` 如果当前项目已经有 Scheduler 生命周期管理,则只增加动态 interval,不修改现有连接管理。 --- # 20. Active Mode 不等于视频流 必须保持: ```text Active Mode ↓ 每 500ms ↓ 生成一张 JPEG ``` 而不是: ```text Active Mode ↓ 视频编码 ↓ 持续视频流 ``` 所以本功能仍然是: > **Adaptive Screenshot Streaming** 而不是 Live View / Video Streaming。 --- # 21. 推荐默认参数 第一版固定默认: ```ts { quality: 30, normalInterval: 2000, activeInterval: 500, activeDuration: 5000 } ``` 即: ```text 无操作: 2 秒 / 张 操作后: 500ms / 张 最后一次操作后: 5 秒恢复正常 JPEG: quality 30 ``` 后续如果需要,再将这些参数开放为系统配置。 --- # 22. 核心流程 ```text Browser Action │ ▼ Playwright │ 操作成功 │ ▼ notifyActivity() │ ▼ Active Mode interval=500ms │ ▼ Screenshot │ ▼ SSE │ ▼ Console │ │ 500ms 后 │ ▼ Screenshot │ ... │ 最后操作 + 5 秒 │ ▼ Normal Mode interval=2000ms │ ▼ Screenshot │ 2 秒后 │ ▼ Screenshot ``` --- # 23. 验收标准 - [ ] Normal Mode 默认 2000ms - [ ] Active Mode 默认 500ms - [ ] Active Duration 默认 5000ms - [ ] Browser Action 成功后进入 Active Mode - [ ] Browser Action 不立即触发 Screenshot - [ ] Action 失败不进入 Active Mode - [ ] Active Mode 从最后一次操作重新计时 - [ ] 连续操作不会创建多个 Timer - [ ] 每个 Browser Session 只有一个 Screenshot Scheduler - [ ] Screenshot 不允许并发执行 - [ ] Active Mode 结束后自动恢复 Normal Mode - [ ] SSE 接口保持不变 - [ ] Screenshot 仍然使用 JPEG - [ ] Screenshot quality 默认 30 - [ ] 不引入 WebSocket - [ ] 不引入 WebRTC - [ ] 不引入 VNC - [ ] 不引入 CDP - [ ] 不实现视频流