# 问题分析：BUG-fcf18718-350b-49ca-a430-9582e554efda

## 背景（从聊天截图获取）

- 客户是拓课云 Electron 应用
- 进入教室时会先销毁旧检测实例再创建新实例
- 客户**手快操作**或异步逻辑未防抖，导致短时间内连续多次创建 SDK 实例
- 在 2026-05-17 18:21:23 左右出现黑流
- 客户检查 output 流 readyState=live, muted=false, enabled=true，但实际画面黑屏

## 日志时间线还原

### 关键发现：3秒内创建了3个SDK实例

| 时间 | 事件 | 说明 |
|------|------|------|
| 18:21:21.887 | 旧 SDK destroy | 正常销毁旧实例 |
| **18:21:22.960** | **实例 A: new ArSdk** | 第1个新实例 |
| 18:21:23.901 | 实例 A: initCore | 输入 1280x720@15fps |
| 18:21:24.265 | 实例 A: core done | 初始化完成 |
| **18:21:24.954** | **实例 B: new ArSdk** | 第2个（A 还没 getOutput！） |
| **18:21:25.169** | **实例 C: new ArSdk** | 第3个（连续创建！） |
| 18:21:25.820 | 实例 A: getOutput → live | 实例 A 获取 output |
| **18:21:25.820** | **实例 A: 立即 destroy!!** | 刚 getOutput 就被客户 destroy 了 |
| 18:21:25.857 | 实例 B: initCore (160x120) | 分辨率被降低了 |
| 18:21:25.870 | 实例 C: initCore (160x120) | 两个并行 initCore |
| 18:21:26.082 | 实例 B: getOutput → live | |
| 18:21:27.090 | 实例 C: getOutput → live | |
| **18:21:29.091** | **outputVideoTrack muted** | 黑流！距初始化仅3秒 |

### 后续模式重复

- 18:23:53: 两个实例同时 destroy → 重建
- 18:23:58: 再次短时间内创建多实例
- 18:24:01: outputVideoTrack muted（黑流重现）
- 18:25:20: 两个实例同时 destroy → 重建
- 18:25:27: outputVideoTrack muted（黑流重现）

## 根因

**多个 SDK 实例共享同一个 MediaStreamTrack 时，多个 `MediaStreamTrackProcessor` 竞争消费同一 track 的帧数据，导致 generator 虽然 readyState=live 但实际无帧数据写入 → 黑屏。**

具体机制：
1. `MediaStreamTrackProcessor` 绑定一个 track 后，会从该 track 读取 VideoFrame
2. 同一个 track 创建多个 processor 时，帧数据的分发行为不确定（规范未明确定义）
3. 当前面的实例被 destroy 时，SDK 的 `destroy()` → `WorkerCore.destroy()` 不会显式关闭 `processor.readable`
4. 旧 processor 的 readable 被 transfer 到 worker 线程中，主线程无法 cancel 它
5. 新实例创建新 processor 绑定同一 track，但旧 processor 可能仍在消费帧 → 新 processor 拿不到数据 → generator.writable 无数据写入 → output 黑屏

**为什么 readyState=live 但黑屏**：generator track 本身没有 ended（writable 还是 open 的），只是没有 VideoFrame 被写入 → muted 状态 → 黑屏。

## 影响范围

- 任何场景下短时间内连续创建多个 SDK 实例（共享同一 MediaStream/Track）
- Electron + 教室场景：进教室异步逻辑中多次触发 SDK 初始化
- videoframe 模式（MediaStreamTrackProcessor 路径）

## 修复思路

### 方案 1：SDK destroy 时显式关闭旧 processor 的 readable（推荐）

在 `WorkerCore.destroy()` 或 SDK `destroy()` 中，确保旧的 `processor.readable` 被 cancel/close，释放对 track 帧数据的占用，使新实例的 processor 能正常消费。

### 方案 2：SDK 增加防重入保护

在初始化流程中加锁，如果当前 SDK 尚未完成初始化，拒绝或排队新的初始化请求。但这需要客户端配合改造调用方式。

### 方案 3：在 `new MediaStreamTrackProcessor` 之前 clone track

每次创建 processor 时先 `track.clone()` 获得独立的 track 副本，避免多个 processor 竞争同一 track。这样每个实例独立消费自己的 track clone，互不干扰。
