Appearance
35 个坑与 11 个插件
这是本项目存在的理由。xgplayer 是个好内核,但把它放进真实业务会撞上一批 内核本身不负责解决的问题。我们逐个编号、复现、归档,能做的做成插件。
35 个里 27 个已处理(19 个插件 + 1 个在 create-player + 7 个决策规避), 8 个只登记未实现 —— 后者在本页末尾如实列出。
插件为什么不能关
先说这个,因为它常被问到。
11 个插件全部默认开启,消费方无法关闭。 PlayerConfig 里没有任何插件开关, preset 也只含 6 个通用播放字段。
理由:这些插件解决的是生产环境的确定性故障——断网、iOS 后台挂死、 浏览器劫持、反复 destroy 崩溃。允许关闭等于允许消费方给自己挖坑, 而坑最终会以「你们 SDK 有 bug」的形式回到我们这里。
插件全部静默工作、不产生额外契约事件(除了该报的警告),关掉它们没有正当收益。
早期设计曾计划用「逃生舱」透传关闭开关,已被 ADR-025 否决—— 通用 passthrough 会让 5 种接入方式无法再保证行为一致。 见 为何没有逃生舱。
P0 · 不装就会出事的五个
safeDestroy — 坑 #3 #4 #23
症状:组件卸载后,xgplayer 内部的 timer 和事件监听器还在跑。 用户反复切页面,内存一路涨,最后崩溃。
为什么内核不管:player.destroy() 不保证清干净外部注册的监听器。 残留的监听会在 player 销毁后继续拿到事件,消费方于是对着一个死播放器 setState。
做法:自己记账。所有 player.on() 都过一层 listen() 记录下来, destroy 时逐个摘掉,顺序是「先摘监听 → 再 destroy player → 再清 DOM」。顺序错就会残留。
ReconnectPlugin — 坑 #1 #2
症状:断网超过 10 秒,播放器就此躺平,既不重连也不报错。
做法:指数退避重试。默认 3 次,基数 1 秒,封顶 15 秒:
第 1 次:1s 后
第 2 次:2s 后
第 3 次:4s 后
用尽 → 发 reconnectfailed,停止自动重连三个事件对应三个阶段:reconnectstart(带 attempt / maxAttempts,可以做"重连中 2/3"提示)、 reconnectsuccess、reconnectfailed。
收到 reconnectfailed 才该打扰用户——在那之前 SDK 还在努力。
VisibilityPlugin — 坑 #5 #24
症状:iOS 切后台再回来,播放器挂死或音频丢失。
做法:重建 player,而不是 pause/resume——后者在 iOS 上会崩。 详见 iOS 为什么特殊。
AutoplayGuardPlugin — 坑 #16
症状:自动播放被浏览器拒绝,但没人告诉业务层。用户看到一个静止的黑框。
做法:捕获 NotAllowedError,转成 autoplayblocked 契约事件。
这也是 E_AUTOPLAY_BLOCKED 标记为不可重试的原因: 自动重试一百次都会被拒,它需要的不是重试,是一个用户点得到的按钮。
SourceRouter — 坑 #22 #33 + 平台强制分流
整个 SDK 里最重要的一个。它决定"在这个环境下,该用哪个源、哪个内核"。
requiresHls = isIOS || isWeChat || isUC || isQuark || !hasMediaSource命中任何一条 → 强制 HLS。否则按 live 字段决定优先级:
直播:[flv, hls, mp4] ← FLV 优先,延迟最低
点播:[hls, mp4, flv] ← HLS 优先,有 ABR这就是"你只管把源都传进来"的实现。不用自己写 UA 判断。
P1 · 体验层面的三个
CompatPlugin — 坑 #9 #10
只认 UC / 夸克两个 UA,命中抛 compatwarning。只上报,不补救。 为什么不对抗,见 浏览器劫持。
WakeLockPlugin — 坑 #15
症状:看着看着屏幕自己灭了。因为浏览器不认为"正在播视频"是活跃状态。
做法:用 W3C Screen Wake Lock API,play 时申请、pause / ended 时释放 (不播就不该占着锁耗电)。
一个容易漏的细节:锁在页面切后台时会被系统自动释放,所以切回前台且仍在播时 要重新申请 —— 插件监听 visibilitychange 补拿。
不支持该 API 的环境(iOS Safari < 16.4、老 WebView、SSR)直接 no-op。
HealthMonitorPlugin — 坑 #13
症状:卡了,但业务侧完全不知道,没法上报也没法优化。
做法:它是 11 个插件里唯一的观察者 —— 不修任何东西,只测量,通过 stalled 事件上报。
两条测量路径喂同一个状态机,避免重复计数:
- 显性卡顿:
waiting进入 →playing结束 - 隐性卡顿(冻帧):轮询
currentTime,播放中却连续不推进 —— 这类卡死 xgplayer 根本不发waiting,只能主动抓
还会排除后台卡顿(document.hidden 时不计):切后台 currentTime 本就停, 那不是卡顿,计进去会污染指标。
这个插件是埋点的基础设施——没有它,你的监控里只有"播放失败率", 没有"卡顿率"和"卡顿时长"。
⚠️ 坑 #14(seek 后 currentTime 抖动)本版不做,源码注释里留给后续
healthreport。
P2 · 长尾但真实的三个
ErrorRecoveryPlugin — 坑 #17 #18
点播 HLS 中间一个 ts 分片坏了,整段播放中断(#17);MP4 中途网络 abort 无恢复(#18)。
和 ReconnectPlugin 按错误 category 划清分工:
network/manifest类(断网、拉流失败)→ Reconnect 全量reload()media类(解码 / abort)→ 本插件做局部自愈,如 hls.js 的recoverMediaError()
为什么不全量 reload:坏 ts 全量 reload 会再次撞上同一个坏片段,得跳过而不是重来。 Reconnect 里加了守卫跳过 media 类,避免两者对同一错误重复动作。
FullscreenGuardPlugin — 坑 #7
iOS 微信里调 webkitEnterFullscreen() 直接崩溃。只在这个环境 把该方法换成 no-op,阻断会崩的路径,destroy 时还原。
ZIndexGuardPlugin — 坑 #28 #29
部分 Android 浏览器 / WebView 把视频层的 z-index 抬得过高,盖住宿主的弹窗 / 抽屉 / toast。
做法:把 player 根节点的内联 z-index 归一到受控基值(默认 0), 让它留在宿主正常层叠流里,宿主更高 z-index 的浮层就能正常盖上去。destroy 时还原原值。
只管内联态。全屏是另一套层叠上下文(xgplayer 全屏容器自己设高 z-index), 本插件刻意不介入,避免破坏全屏。
不由插件处理的:#35 字幕加载失败被吞
这个值得单独说,因为它是上游库主动隐藏错误的典型。
xgplayer 3.0.26 的 TextTrack 插件内部:
js
this.subTitles.switch(subtitle).catch(function (e) {
});一个空 catch,把字幕加载失败原地吞掉,而且不返回 promise。 字幕 URL 404 或解析失败时,插件层面完全静默——用户看到的是 "点了切字幕但没反应",你拿不到任何错误。
处置:仍调 switchSubTitle(它负责更新图标/菜单状态), 再直接调一次引擎的 switch() 接 rejection,转成 E_SUBTITLE_LOAD_FAILED。 引擎对同一轨的 switch 是幂等的,重复调用安全。
还没做的 8 个
不粉饰:
| # | 症状 | 现状 |
|---|---|---|
| #6 | 微信 WebView 竖屏点全屏没反应 | 无代码 |
| #8 | 微信小程序 WebView 播放中止 | 无代码 |
| #11 | 老版本 Android WebView canvas 白屏 | 无代码 |
| #12 | 三星浏览器 fullscreen 请求异常 | 无代码 |
| #14 | seek 后 currentTime 抖动 | 源码注释:留给后续 healthreport |
| #19 | 微信 Android 需要 x5-video-* 属性 | 无代码 |
| #27 | iOS 画中画状态残留 | 无代码 |
| #34 | 直播断流重连后进度跳回 | 重连逻辑无 live 分支 |
#34 最值得注意:互动直播场景下,断线重连后播放位置可能停在断点而不是追到最新。 处置见 点播和直播的区别。
其余多数需要真机复现、收益不确定。如果你的主战场正好是微信 WebView 深度交互, 这 8 个里有 3 个和你相关,请把这一点计入选型判断。
这份清单是怎么来的
不是照抄文档,是逐个读插件源码的 解决的 pitfall JSDoc 声明得出的。
2026-07-18 的审计发现文档此前把 8 个坑记在了没做它们的插件名下 (如 #19「x5-video 属性注入」记在 CompatPlugin 名下,而全仓库搜不到 x5-video)。 现在以源码声明为准。
回归测试的现状
插件逻辑由 player-core 单测全覆盖。e2e 回归目前落地 4 个 spec, 只覆盖 chromium 里纯浏览器可复现的坑(#9 UC / #10 夸克 / #23 反复 destroy / #28 层级)。
需要真机或需要真实解码的坑不强凑 —— 凑了只能 skip, 反而掩盖真实覆盖率。这类留给真机灰度。
完整清单在哪
本页是面向接入方的坑点编号、影响与处理结论总览;若业务命中未处理项,应按宿主验收流程 评估风险并决定是否接入。