docs: 更正草原指南倍速播放缓冲死锁
这个提交包含在:
@@ -0,0 +1,108 @@
|
||||
# 草原指南切换 2x 后永久缓冲更正
|
||||
|
||||
> 日期:2026-07-16
|
||||
>
|
||||
> 前端仓库:`mmg/hl-ui`
|
||||
>
|
||||
> 影响组件:`src/views/h5/grassland-guide/components/GuideVideoPlayer.vue`
|
||||
>
|
||||
> 影响辅助逻辑:`src/views/h5/grassland-guide/components/playbackBuffer.js`
|
||||
>
|
||||
> 本次后端接口、字段、OSS 地址和视频文件均无变化
|
||||
|
||||
## 1. 现象
|
||||
|
||||
视频在 `1x` 已经开始播放后切换到 `2x`,页面停在“正在缓冲”,即使等待较长时间也不能恢复。
|
||||
|
||||
Network 中可以看到同一个 MP4 存在多条大小不同的 `206 Partial Content` 请求。这是 Chrome 原生媒体加载器根据 MP4 元数据、当前播放位置和缓存状态发起的 HTTP Range 请求,Range 大小不固定是正常行为,不能据此判断 OSS 分片异常。
|
||||
|
||||
## 2. 已确认根因
|
||||
|
||||
线上播放器当前调用链为:
|
||||
|
||||
```text
|
||||
切换 2x
|
||||
→ 设置 video.playbackRate = 2
|
||||
→ evaluateBuffer({ initial: true })
|
||||
→ 要求 bufferAhead >= 15 秒
|
||||
→ 未达到阈值
|
||||
→ pauseForBuffering()
|
||||
→ video.pause()
|
||||
```
|
||||
|
||||
播放器暂停后,浏览器可以降低甚至停止后续媒体预取。当前代码又只依赖 `progress`、`canplay` 等媒体事件重新执行 `evaluateBuffer()`,没有保证这些事件一定继续产生,因此可能永远达不到 `15` 秒阈值,形成状态机死锁。
|
||||
|
||||
同类问题还存在于:
|
||||
|
||||
- 首次播放前等待固定 `8` 秒。
|
||||
- `timeupdate` 检测到低水位后主动 `pause()`。
|
||||
- `waiting`/`stalled` 事件再次主动 `pause()`。
|
||||
|
||||
这些逻辑把浏览器原生的“缺数据时等待并继续下载”变成了应用层“暂停后等待浏览器继续下载”,两者行为并不等价。
|
||||
|
||||
## 3. 必须修改的前端逻辑
|
||||
|
||||
### 3.1 播放与切换倍速
|
||||
|
||||
```js
|
||||
async function play() {
|
||||
playbackRequested.value = true
|
||||
await videoRef.value?.play()
|
||||
}
|
||||
|
||||
function togglePlaybackRate() {
|
||||
const video = videoRef.value
|
||||
if (!video) return
|
||||
|
||||
playbackRate.value = playbackRate.value === 1 ? 2 : 1
|
||||
video.playbackRate = playbackRate.value
|
||||
|
||||
// 禁止在这里 pause()
|
||||
// 禁止在这里重新执行固定秒数的初始缓冲门槛
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 缓冲状态由原生事件驱动
|
||||
|
||||
```js
|
||||
function handleWaiting() {
|
||||
if (!playbackRequested.value) return
|
||||
buffering.value = true
|
||||
recordPlaybackEvent('waiting')
|
||||
}
|
||||
|
||||
function handleStalled() {
|
||||
const video = videoRef.value
|
||||
if (playbackRequested.value && video?.readyState < HTMLMediaElement.HAVE_FUTURE_DATA) {
|
||||
buffering.value = true
|
||||
}
|
||||
recordPlaybackEvent('stalled')
|
||||
}
|
||||
|
||||
function handlePlaying() {
|
||||
buffering.value = false
|
||||
recordPlaybackEvent('playing')
|
||||
}
|
||||
```
|
||||
|
||||
事件处理器内不得调用 `video.pause()`。只要用户没有主动暂停,就保留原生播放意图,让浏览器在缺数据时自动等待、继续 Range 取流,并在数据恢复后自行继续播放。
|
||||
|
||||
### 3.3 删除主动低水位暂停
|
||||
|
||||
- 删除 `pauseForBuffering()` 对自动缓冲流程的使用。
|
||||
- `handleTimeUpdate()` 只同步时间、记录 `bufferAhead` 和掉帧指标,不得检测低水位后暂停。
|
||||
- `evaluateBuffer()` 不再阻塞首次播放或倍速切换;如保留该方法,只能用于诊断,不能控制 `play/pause`。
|
||||
- 删除 `BUFFER_POLICIES` 中作为播放硬门槛的 `initial/low/resume`,避免后续重新引入死锁。
|
||||
|
||||
## 4. 验收要求
|
||||
|
||||
- [ ] `1x` 点击播放后能直接进入原生播放流程,不等待固定 `8` 秒。
|
||||
- [ ] 播放中切换 `2x` 不触发 `pause` 事件。
|
||||
- [ ] 切换 `2x` 后,即使触发 `waiting`,后续 Range 请求仍继续。
|
||||
- [ ] 数据恢复后触发 `playing`,缓冲遮罩自动消失,播放继续。
|
||||
- [ ] `1x ↔ 2x` 连续切换 10 次,不出现永久缓冲。
|
||||
- [ ] 拖动进度后仍可重新播放,用户主动暂停不会被自动恢复。
|
||||
- [ ] Console 中不再出现 `ratechange → initial-buffering → pause` 的调用序列。
|
||||
- [ ] 使用 DevTools 测试真实用户表现时关闭 `Disable cache`;需要模拟弱网时单独选择网络限速,不把禁用缓存结果当作正常生产表现。
|
||||
|
||||
修复状态机后,若 `1x` 或 `2x` 仍出现能够自行恢复的短时 `waiting`,再根据 `bufferAhead`、实际下载速度和掉帧数判断是用户网络还是设备解码能力问题。播放器逻辑修复不能提高用户带宽;需要跨地区稳定承载原始 4K 高码率视频时,应另行评估 OSS 前置 CDN Range 缓存。
|
||||
在新工单中引用
屏蔽一个用户