You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6.8 KiB
6.8 KiB
直播 play-url 按 session 缓存与合并
日期:2026-09-03
状态:已确认(待写实现计划)
范围:仅前端 laic-frontend(Monitor + Media)
1. 背景
开播进入 streaming 后,openLive、phase poll、heartbeat 都可能触发 GET /v1/live/{dockId}/play-url。现状:
| 位置 | 已有能力 | 缺口 |
|---|---|---|
useMonitorLive |
live.opening 防抖;playURLRequest 全局 in-flight 合并 |
不按 sessionId;不复用未过期 playUrl;多入口仍可能在短窗口内各打一次 |
MediaView |
open 后单次 getLivePlayURL;openSeq 丢弃过期回写 |
无 opening 锁;无 in-flight 合并;无 TTL 缓存 |
LivePlayer |
启动期 404 同 URL 退避,不上冒刷 play-url | 已满足,本轮仅回归 |
后端建议前端按 session 做 ensurePlayUrl:合并同 session 的 in-flight,并在地址仍有效时直接复用。
2. 目标
- 同一
streamSessionId的/play-urlin-flight coalesce(并发只打一枪)。 - 当前 session 已有
playUrl,且expiresAt * 1000 - Date.now() > 30_000时 直接复用,不重打。 - Monitor 的
openLive(streaming)、pollPhaseOnce、heartbeat(需补地址时)、临期续期、有限错误重建,统一走ensurePlayUrl。 - Media 同样按 session 缓存/合并;
openLive增加 opening 连点忽略锁。 - 仅
force、临期续期、或明确需要强制刷新的路径才重新 GET。 - 请求完成后再次确认当前 session 未变再写入;页面关闭/切换清空 promise 与缓存。
3. 非目标
- 不改后端
/play-url契约与鉴权。 - 不抽 Monitor/Media 公共模块(两页各自实现同构逻辑)。
- 不改
LivePlayer启动 404 同 URL 策略、stop phase gate、57006rejoin、TLS/403 致命不刷 URL。 - 不改设备状态轮询等无关模块。
4. 方案
4.1 核心语义 ensurePlayUrl(sessionId, { force = false })
伪代码(两页各自落地,字段名可本地化):
const PLAY_URL_FRESH_MS = 30_000
let playUrlPromise = null
let playUrlSessionId = ''
async function ensurePlayUrl(sessionId, { force = false } = {}) {
if (
!force &&
activeSessionId === sessionId &&
playUrl &&
playUrlExpiresAt * 1000 - Date.now() > PLAY_URL_FRESH_MS
) {
return { playUrl, expiresAt: playUrlExpiresAt }
}
if (playUrlPromise && playUrlSessionId === sessionId) {
return playUrlPromise
}
playUrlSessionId = sessionId
playUrlPromise = getLivePlayURL(dockId, sessionId)
.then((play) => {
// 回写前调用方仍须再校验 session;此处仅作提示性守卫
if (activeSessionId !== sessionId) return play
return play
})
.finally(() => {
if (playUrlSessionId === sessionId) playUrlPromise = null
})
return playUrlPromise
}
常量:PLAY_URL_FRESH_MS = 30_000(与现有临期续期 lead 对齐)。写入 playUrl / expiresAt / schedulePlayRefresh 由调用方在 ensure resolve 且 session 仍匹配后执行(Monitor 可把写入收进 ensure 内部,但必须含二次 session 校验)。
4.2 Monitor:useMonitorLive.js
- 将现有
refreshPlayURL/playURLRequest升级为按 session 的 ensure 语义:- 默认
force=false(复用 + coalesce) fromRetry、临期schedulePlayRefresh、显式需要换址 →force=true
- 默认
- 调用点统一:
openLive在phase === 'streaming'pollPhaseOnce进入 streaming 后取址- heartbeat:仅当
phase === 'streaming' && !live.playUrl(现状),内部走 ensure(通常命中缓存或 in-flight) schedulePlayRefresh到期 → forceonPlayError有限次重建 → force
- 保留
live.opening锁(已有)。 clearPlaybackOnly/resetLocal/ dispose:清空playUrlPromise、playUrlSessionId,并清playUrl/expiresAt(与现有一致)。- ensure 返回后若
live.session.id !== sessionId或 disposed/stopping → 不写入。
可选:对外仍导出/保留函数名 refreshPlayURL({ force, fromRetry }),内部转 ensure,减少调用点改名噪音。
4.3 Media:MediaView.vue
- 增加
opening(或复用 busy 标志):openLive入口若已 opening → 直接 return;finally 解锁。 - 增加与 Monitor 同构的
playUrlPromise/playUrlSessionId/playUrlExpiresAt(若尚未存 expiresAt,从 play-url 响应写入)。 openLive取址改为ensurePlayUrl(sessionId);若后续 heartbeat 在无 playUrl 时补址,同样走 ensure。closeActiveLive/ tab 切离 live /pagehide:清空 promise、session 键、playUrl、expiresAt(bumpopenSeq逻辑保留)。- 回写前校验
openSeq+activeLive.sessionId === sessionId。
4.4 与 LivePlayer 的边界
- 启动期 m3u8 404:仍由
LivePlayer同 URL 窗口消化,recoverable不上冒 → 不触发 ensure/force。 - TLS/403:Monitor 仍
markPlayFatal,禁止刷 play-url。 - 仅非 HlsError 的有限重试 / 临期 / 显式 force 才重新 GET。
5. 错误与并发
| 场景 | 行为 |
|---|---|
| 同 session 并发 ensure | 共享同一 promise |
| 不同 session | 不复用旧 promise;旧回写被 session 校验丢弃 |
| TTL 内非 force | 返回缓存,零 HTTP |
| force / 临期 | 新 GET;可与旧 in-flight 并存时以新 session 键为准,旧结果丢弃 |
| ensure 失败 | 抛给现有 onPlayError / Media toast 路径;不清掉「仍有效」的旧地址,除非调用方 close |
| 401 | 现有 http 拦截器 |
6. 验收
- Monitor 开播至出画:同一 session 短时间内
/play-url不应被 open + phase + heartbeat 打成多份并发;未过期无故不重拉。 - Media 开播同样;连点 open 不会并行多个 join。
- 启动期 HLS 404 仍只同 URL 重载,Network 无因此刷出的
/play-url。 - 临期(剩余 ≤30s)或 force 重建才会重新 GET;完成后 session 已变则不写入。
- 离开监控直播 / Media 直播 tab / 关页后,无过期 in-flight 回写到新会话。
- stop gate、
57006、TLS/403 行为与改前一致。 npm run build通过。
7. 风险
30s新鲜阈值与临期续期 lead 相同;若后端expiresAt很短,force 会更频繁——可接受。- Monitor / Media 两份同构逻辑可能漂移;本轮不抽公共模块,实现计划里用同一段伪代码约束。
8. 实现落点(预告)
| 文件 | 变更 |
|---|---|
src/composables/useMonitorLive.js |
ensure 语义升级 refreshPlayURL;统一调用点;清理 promise |
src/views/MediaView/MediaView.vue |
opening 锁 + ensurePlayUrl + expiresAt + 清理 |
src/components/LivePlayer.vue |
不改;回归启动 404 |
完成后:实现计划 → 编码 → 构建部署 → Network 人工看开播 /play-url 次数。