Browse Source

docs: add monitor HLS playback design

Cover hls.js playback, phase polling, playUrl renewal,
error rebuild, and stop-vs-leave controls for monitor live.
main
xiaosi 3 weeks ago
parent
commit
cc397f708a
  1. 207
      docs/superpowers/specs/2026-09-02-monitor-hls-playback-design.md

207
docs/superpowers/specs/2026-09-02-monitor-hls-playback-design.md

@ -0,0 +1,207 @@
# 监控直播 HLS 真实播放补齐
日期:2026-09-02
状态:已确认(待实现计划)
## 背景
后端直播播放地址是 HLS:
```text
https://vpull.jiagutech.com/dock-live/<streamSessionId>.m3u8?auth_key=...
```
当前前端缺口:
1. `LivePlayer.vue``.m3u8` 直接赋给原生 `<video src>`,项目无 `hls.js`。Safari 通常可播,Chrome/Edge/Firefox 通常不能。
2. `useMonitorLive` 创建会话后只靠观看租约心跳刷新状态。默认租约约 30s,第一次刷新约 15s;即使流已就绪,页面也会长时间停在「启动中」。
3. `GET play-url` 返回 `expiresAt`(默认约 300s),前端只在「尚无 playUrl」时拉一次;过期后播放失败,仅 toast,不换新地址。
4. 播放器错误只向上抛,没有重建路径。
5. 关闭按钮语义只有「退出观看」(`leaveLive`);后端已有 `POST /v1/live/:dockId/stop`,前端未接。
相关后端契约:
- `GET /v1/live/:dockId/sessions/:streamSessionId` → 会话状态
- `GET /v1/live/:dockId/play-url?streamSessionId=``{ playUrl, expiresAt, streamName }`
- `POST /v1/live/:dockId/sessions/:id/heartbeat` → 续观看租约
- `DELETE .../viewers/me` → 退出观看
- `POST /v1/live/:dockId/stop` → 停止直播推流
## 目标
监控详情实时画面:
1. Chrome/Edge 用 `hls.js` 播放 HLS;Safari / 原生可播 HLS 时走原生 `<video>`
2. join 后每 2–3 秒查询会话,直到 `streaming` / `failed` / 超时,再尽快取 playUrl。
3. 记录 playUrl `expiresAt`,到期前约 30 秒自动刷新地址并重建播放器。
4. HLS 网络/鉴权/解码错误时限流重试:重新拉 play-url 并重建播放器。
5. 区分:
- **退出观看**:现有中心按钮 / `leaveLive`
- **停止直播**:详情面板新增按钮,调 `POST /stop`
## 非目标
- 不改 Media 页(仍可 `window.open`
- 不改后端协议 / 鉴权
- 不做 WebRTC / FLV 播放器
- 不把停止直播做成播放器内复杂手势
- 不在本轮处理多路同屏播放
## 方案
采用:**LivePlayer 负责媒体;`useMonitorLive` 负责会话/轮询/续期/退出与停止;详情面板加停止按钮。**
### 1. 依赖
`package.json` 增加 `hls.js`(运行时依赖)。
### 2. API
文件:`src/api/live.js`、`src/config/urls.js`
新增:
```js
export function getLiveSession(dockId, sessionId) {
return request.get(`/v1/live/${dockId}/sessions/${sessionId}`)
}
export function stopLive(dockId) {
return request.post(`/v1/live/${dockId}/stop`)
}
```
现有 `getLivePlayURL(dockId, sessionId)` 保持;调用方必须消费返回的 `expiresAt`
### 3. LivePlayer
文件:`src/components/LivePlayer.vue`
职责:
- props 仍接收 `playUrl` / `phase` / `active` / `cameraLabel`
- `playable` 判定不变:有 `playUrl` 且非 `fake://`
- 当 `playable`
- 若 `video.canPlayType('application/vnd.apple.mpegurl')` 真 → 原生 `src = playUrl`
- 否则动态/静态引入 `hls.js`:`Hls.isSupported()` 时 `new Hls()``loadSource``attachMedia`
- 不支持则 emit `error`,展示失败态
- `playUrl` 变化或组件卸载:destroy 旧 Hls 实例,清空 `video.src` / `removeAttribute('src')` + `load()`
- 监听 `video.error` 与 hls `ERROR`(`fatal`)→ emit `error`
- 中心按钮语义不变:emit `toggle`(由上层解释为退出观看 / 打开)
不在 LivePlayer 内请求 API。
### 4. useMonitorLive
文件:`src/composables/useMonitorLive.js`
状态扩展:
```js
live = {
session, dockId, playUrl,
playUrlExpiresAt, // unix 秒;缺省时用拿到时刻 + 270s 兜底
heartbeatTimer,
phasePollTimer,
playRefreshTimer,
playRetryCount,
}
```
对外新增:`stopLiveStream`。
#### 4.1 打开
1. `joinLive(dockId)`
2. 写 `session` / `dockId`
3. 启动观看租约心跳(逻辑保留;与状态轮询分离)
4. 若已 `streaming``refreshPlayURL()`
5. 否则启动 phase 轮询:每 **2.5s** `getLiveSession`
- 响应经 http 解包后直接是 `LiveSession`(含 `phase`/`id`),不是 `{ session }` 包装
- `streaming` → 停轮询,`refreshPlayURL()`
- `failed` / `stopped` → toast,清本地态(不强制 stop)
- 超过 **60s** 仍非终态 → toast「直播启动超时」,清本地态
#### 4.2 playUrl 刷新
`refreshPlayURL()`
1. 调 `getLivePlayURL`
2. 写 `live.playUrl`
3. 写 `live.playUrlExpiresAt = play.expiresAt || nowSec + 270`
4. 调度续期:`delay = max(5000, expiresAt*1000 - Date.now() - 30000)`
5. 到期触发时再次 `refreshPlayURL()`;失败走播放错误重试路径
仅当本地仍持有同一 `session.id` 时回填,避免竞态。
#### 4.3 播放错误重建
详情面板 `@live-error` → composable `onPlayError()`
- `playRetryCount < 3`:递增,重新 `refreshPlayURL()`(新 URL 触发 LivePlayer 重建)
- 达到 3:toast「播放失败」,重置计数但不 leave;心跳可继续
成功拿到并切换新 `playUrl` 后把 `playRetryCount` 置 0。
#### 4.4 退出观看
现有 `closeLive(true)`
- 清所有定时器
- 清 `session/playUrl/expires`
- `leaveLive`
切换设备 / 关详情 / 卸载:仍走退出观看。
#### 4.5 停止直播
`stopLiveStream()`
1. 无 active session 直接 return
2. `ui.confirm` 确认(文案:停止后所有观看者失去画面)
3. `POST /v1/live/:dockId/stop`
4. 成功:清本地播放态(等同 close 本地部分);可再尝试 leave(忽略错误)
5. 失败:toast,保留当前播放与心跳
### 5. 详情面板 UI
文件:`src/components/MonitorDetailPanel.vue` + `MonitorView` 接线
在「实时画面」标题行(状态文案 / 全屏旁)增加:
- 按钮文案:`停止直播`
- 仅 `liveView.active` 时可见/可点
- emit `stop-live` → MonitorView → `useMonitorLive.stopLiveStream`
中心播放按钮保持 toggle:有 session 时退出观看。
### 6. 验收
1. Chrome/Edge:join → streaming 后页内可见真实 HLS 画面(非仅黑框)
2. Safari:原生路径可播,不强制挂 hls.js
3. join 后非 streaming:约 2.5s 内开始轮询;流就绪后数秒内出现画面,不再干等 ~15s 心跳
4. playUrl 到期前约 30s 自动换新地址,播放不中断或短暂重建后恢复
5. 人为弄坏/过期 URL 后,最多自动重建 3 次
6. 「停止直播」调 `/stop`;中心关闭只 leave,不 stop
7. 切设备 / 关面板:销毁 hls、清定时器,无残留请求风暴
8. `fake://` 行为与现网一致
9. `npm run build` 通过
## 风险
- 后端 `GetLiveSession` 返回解包后的 `LiveSession` 本体(handler `OKWithData(session)`),不是 `LiveSessionVO`
- 云端 phase 推进依赖后端 reconcile;前端轮询只能反映后端状态,不能替代设备推流
- 多标签页同时观看:停止直播会影响所有观看者——必须确认框
- hls.js:LivePlayer 内 **动态 import**,避免拖大非监控首屏 chunk
## 文件清单
| 文件 | 变更 |
|------|------|
| `package.json` / lock | 加 `hls.js` |
| `src/api/live.js` | `getLiveSession` / `stopLive` |
| `src/config/urls.js` | 对应常量 |
| `src/components/LivePlayer.vue` | HLS 适配 + 销毁 |
| `src/composables/useMonitorLive.js` | 轮询 / 续期 / 重试 / stop |
| `src/components/MonitorDetailPanel.vue` | 停止按钮 |
| `src/views/MonitorView/MonitorView.vue` | 接线 stop / live-error |
Loading…
Cancel
Save