- Project/Environment model with default environments (dev/staging/prod) - Config Runtime: cascade resolution (platform→org→project→env), secrets via vault - Storage Runtime: local provider, sha256 dedup, integrity check, file list/upload/delete - Observability: execution query, per-action summary (success rate, avg, p95), health - Realtime SSE: workspace event stream endpoint (GET /api/v1/v2/realtime/events) - Phase 1 acceptance: core.health.check, core.user.create, core.user.list - Actions: core.project.*, core.config.*, core.file.*, core.user.* (62 total)
761 lines
10 KiB
Markdown
761 lines
10 KiB
Markdown
# 动态 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
|
||
- [ ] 不实现视频流 |