# 直播会话管理器（livestream-session-manager）任务说明

## 背景

为解决多人同时观看同一路直播时，单个用户关闭页面触发停流导致全员断流的问题，后端引入了基于 Redis 的观看会话管理。

## 主要改动

### 1) 会话模型从 userId 调整为 sessionId
- 每次 `/streams/start` 成功后，服务端生成唯一 `session_id`（UUID）并返回前端。
- 同一用户多个页面/标签页拥有独立 `session_id`，互不影响。

### 2) 直播控制接口增强
- `/streams/stop`：按 `videoId + session_id` 仅注销当前观看会话。
- `/streams/heartbeat`：按 `videoId + session_id` 刷新 TTL。
- `/streams/stop2`：强制停流，清空该视频流全部会话并执行真实停流。

### 3) Redis 会话管理
- Key 前缀：`live_session:`
- Key 结构：`live_session:{droneSn}_{payloadIndex}:{sessionId}`
- TTL：30 秒（建议前端 10 秒心跳）

### 4) 设备离线清理策略（按设备）
- 任一可直播设备（机场/无人机）离线后，清理该设备对应的会话 key。
- 清理按设备 SN 精确匹配（`live_session:{sn}_*`），不会清理其他正常设备会话。

## 关键参数与时序（上线必看）

### Redis 参数

- 常量位置：`sample/src/main/java/com/dji/sample/component/redis/RedisConst.java`
- `LIVE_SESSION_PREFIX = "live_session:"`
- `LIVE_SESSION_TTL_SECOND = 30`

### 会话 Key 规则

- 生成位置：`LiveStreamServiceImpl.buildSessionKey(...)`
- 格式：`live_session:{droneSn}_{payloadIndex}:{sessionId}`
- 示例：`live_session:5CGAC123ABCD_0-52-0:3f3e6b9b-0dd8-4f6a-9aab-1f9b6e3efc5e`

说明：
- `{droneSn}_{payloadIndex}` 代表同一路视频源（videoId）
- `sessionId` 是单次观看会话唯一标识（UUID）

### 心跳与超时建议

- 前端心跳周期：**10 秒**
- Redis 会话 TTL：**30 秒**
- 建议重试：
  - 心跳失败可立即重试 1 次；
  - 连续失败后停止心跳并提示用户重开直播。

容错窗口：
- 10 秒心跳 + 30 秒 TTL，允许短时网络抖动，不会立即误判离线。

## 接口参数说明（关键字段）

### 1) 开始直播 `/streams/start` 或 `/streams/start2`

请求关键字段（`LiveTypeDTO`）：
- `video_id`：包含 `drone_sn` 与 `payload_index`
- `url_type`：推流协议类型
- `video_quality`：清晰度

响应关键字段（`LiveDTO`）：
- `url`：播放器地址
- `session_id`：本次观看会话 ID（后续 heartbeat/stop 必传）

### 2) 心跳 `/streams/heartbeat`

请求必填：
- `video_id`
- `session_id`

后端行为：
- 若 session 存在：刷新 TTL 为 30 秒
- 若 session 不存在：返回错误（提示前端需重新 start）

### 3) 停止 `/streams/stop`

请求必填：
- `video_id`
- `session_id`

后端行为：
1. 删除当前 `session_id` 对应 key
2. 统计同一路流剩余会话数
3. 若剩余为 0：调用真实停流（MQTT `liveStopPush`）
4. 若仍有其他会话：不执行真实停流

### 4) 强制停止 `/streams/stop2`

请求关键字段：
- `video_id`

后端行为：
- 删除该路流全部 `live_session` key
- 立即执行真实停流

## 离线清理规则（当前实现）

位置：`DeviceServiceImpl`

- 触发点：
  - `subDeviceOffline(deviceSn)`
  - `gatewayOffline(gatewaySn)`
- 处理逻辑：
  - 任一可直播设备离线后，按 SN 清理该设备会话：`live_session:{sn}_*`
  - 在 `gatewayOffline` 中，会同时清理 `gatewaySn` 与 `childDeviceSn` 的会话（均为按 SN 范围清理）

保障：
- 清理按 SN 前缀精确过滤，不会误删其他设备会话。

## 前端协同要求（细化）

1. 调用 `/streams/start` 成功后，保存 `session_id`（建议按 `video_id` 维度缓存）。
2. 每 10 秒调用一次 `/streams/heartbeat`，请求体必须带当前 `session_id`。
3. 关闭播放器时调用 `/streams/stop`，并携带同一 `session_id`。
4. 监听设备离线状态后：
	- 立即停止该设备心跳；
	- 清理本地该设备对应 `session_id`；
	- 不再继续对该设备发 heartbeat/stop。
5. 设备重新上线后必须重新 `/streams/start` 获取新 `session_id`，禁止复用旧会话。

## 回归测试建议

1. **同一用户双标签页**
	- A/B 各自 start，拿到不同 `session_id`
	- 关闭 A（stop A）后 B 仍可看
	- 关闭 B 后才真实停流

2. **心跳超时**
	- start 后停止 heartbeat
	- 等待 >30 秒，会话自动过期
	- 再 stop 应保持幂等，不影响其他设备

3. **设备离线清理**
	- 某设备离线后，验证该 SN 的 `live_session:{sn}_*` 被清理
	- 其他设备直播会话不受影响

## 涉及文件（核心）

- `sample/src/main/java/com/dji/sample/manage/model/dto/LiveTypeDTO.java`
- `sample/src/main/java/com/dji/sample/manage/model/dto/LiveDTO.java`
- `sample/src/main/java/com/dji/sample/manage/service/ILiveStreamService.java`
- `sample/src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java`
- `sample/src/main/java/com/dji/sample/manage/controller/LiveStreamController.java`
- `sample/src/main/java/com/dji/sample/component/redis/RedisConst.java`
- `sample/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java`

## 前端协同要求

1. 调用 `/streams/start` 后保存返回的 `session_id`。
2. 心跳与停止接口必须携带该 `session_id`。
3. 设备离线（前端状态判定）后停止该设备心跳，并清理本地会话缓存。
4. 设备重新上线后重新 `start`，不要复用旧 `session_id`。
