Appearance
FAQ 速查
本页摘自完整使用手册的 FAQ 章节,构建时 @include 抽取,零漂移同步。 答案里的
§ N是完整手册的章节编号,点上方链接对照查看。
按你现在处于哪个阶段分三层。每层内部按被问到的频率排。
答案尽量自包含 —— 只在确实需要展开时才指向章节。
第一层 · 还在选型(要不要用)
Q1:什么时候用团队封装组件,什么时候直接用 SDK? A:业务代码永远用团队封装组件(TeamVideoPlayer)。直接用 SDK 只在两个场景: 1)你正在做团队封装;2)极特殊技术验证。
Q2:SDK 带 UI 吗? A:不带。默认只有 xgplayer 原生控件。所有品牌按钮、菜单、toast、覆盖层 都要你自己写(ADR-021)。我们给了 8 个场景的可运行参考实现,但那是范例不是成品。
如果你期待的是「装上就有一个和 B 站一样的播放器」,那不是本项目。
Q3:和直接用 xgplayer 比,到底多了什么? A:35 个生产环境坑里的 27 个已处理(19 个做成插件、1 个在 create-player、7 个决策规避), 外加五种接入方式行为一致的契约保证。如果你只跑桌面 Chrome 内网点播,这些基本撞不上, 直接用 xgplayer 更划算 —— 我们不劝你用。
Q4:v1.0 有什么明确不做的? A:DRM(Widevine / FairPlay)、DASH、Cast / AirPlay 投屏。这三个是 v1.0 红线, 需求真到了走 ADR 重新决策,但现在没有。另外本 SDK 是 Web-only,原生 App 用不上。
⚠️ v4.1 起,AirPlay 这条从「我们没做」变成「iOS 上确实没有了」——两件事,别读混。
在此之前 iOS Safari 用户能投屏:那不是我们做的,是走原生 HLS 时平台白送的。 ADR-056 把 iOS 的 HLS 换成 hls.js(经
ManagedMediaSource)之后, hls.js 自己会把video.disableRemotePlayback置为true并删光<source>备选, 投屏入口因此关闭。这是库的行为,不是平台限制,而且绕不过去 —— 按其源码注释把这一位改回false,ManagedMediaSource 就打不开,播放直接坏。这一条已真机实测(iPhone 16 Pro / iOS 26.6.1 / 2026-08-25,Safari + Chrome for iOS
- 微信 WKWebView 三者一致):SDK 路径
disableRemotePlayback === true, 同一页面的原生对照=== false。不是从源码推的。换来的是:
setQuality/onQualityChange/onReady.quality在 iOS 上真的生效 (此前静默失效),以及两个真机缺陷被根治(高倍速 seek 死锁、弹幕预载漏条)。桌面 Safari 不受影响 —— 它有标准
MediaSource,SDK 显式让 hls.js 走普通 MSE,disableRemotePlayback根本不会被碰,清晰度控制和 AirPlay 两者都在。iOS 上还想投屏:走系统级屏幕镜像(控制中心),它搬的是整块屏幕,不经过这个开关。 ⚠️ 这条降级路径尚未真机验证(需要 AirPlay 接收端硬件),见 #167。
Q5:插件能不能关? A:不能,11 个全部默认开启,PlayerConfig 里没有任何插件开关。
它们解决的是断网、iOS 后台挂死、浏览器劫持、反复 destroy 崩溃这类确定性故障, 允许关闭等于允许消费方给自己挖坑,而坑最终会以「你们 SDK 有 bug」的形式回来。
Q6:能透传 xgplayer 的原始配置吗? A:不能,刻意不提供逃生舱(ADR-025)。通用 passthrough 会击穿 cross-mode-parity —— 五种接入方式就没法再保证行为一致。真需要某个能力,走 ADR 把它建成一等契约字段。
Q7:业务封装组件放在 SDK 仓库里吗? A:不放。团队封装组件在业务项目内部(如 src/shared/video/),不进 SDK monorepo。 将来多个项目要复用,抽出来发内部 npm 私服,基础 SDK 保持通用可对外。
第二层 · 正在接入(代码怎么写)
Q8:切集 / 换视频怎么做? A:改 source prop,声明式的:
vue
<VideoPlayerFrame :source="currentSource" />
<!-- currentSource 变了 → 内部自动换源,不重建 iframe -->⚠️ 换源的正规路径是改 source prop。四个可编程组件面都会把它下发为 load(); 静态 iframe 则替换 src 重建。不要把高级句柄上的 load() 当作业务的默认换源入口。
唯一的例外是「用同一个 URL 重新拉一次」(prop 没变化不会触发),这时走 ref.getPlayerHandle()?.load(source)。
⚠️ 换源有四类构造期配置换不了,都会抛 E_METHOD_NOT_SUPPORTED,要销毁重建播放器:
| 换不了的 | 什么时候撞上 |
|---|---|
| 内核 | HLS/MP4(native / hls.js)↔ FLV(flv.js)互换 |
| 直播/点播语义 | 新旧 source 的 live 不一样(两个方向都拦) |
| LL-HLS 策略 | 新旧 source.hls.lowLatencyMode 不一样 |
| 字幕轨配置 | source.subtitles 的轨道、字段或顺序不一样 |
四类同一个原因:内核插件、isLive、HLS 配置与 TextTrack 都在播放器构造期确定, 运行时改不了。undefined 的 live / lowLatencyMode 与 false 等价;空字幕数组与未传字幕等价。
不要在宿主复制一份“能否热切”的预判函数。 它不仅要比较选中的内核和
live,还要和 SDK 保持 LL-HLS、字幕的归一化规则同步;重复维护会再次制造“预判能切、实际被拒”或相反的漂移。 直接消费统一的E_METHOD_NOT_SUPPORTED,递增组件 key(或替换静态 iframesrc)重建即可。
四条消费面上
code都是E_METHOD_NOT_SUPPORTED,可以放心按它分支。这一点在 2026-08-26 之前不成立:两条 iframe 接入方式上它会退化成
E_INTERNAL+category: 'env',只有message保真。原因是命令失败走的是跨 iframe 的 RPC rejection, 而 penpal 序列化 rejection 时只保留name/message/stack。 修法是换载具(不再抛Error,改抛普通对象,penpal 不剥它的自定义属性), 已在 #356 落地,e2e/specs/integration/source-switch.spec.ts四条消费面同一个期望钉着。用 4.0.x 或更早版本的 iframe 内容(
origin/version锁在旧目录上)仍会看到E_INTERNAL—— 这一条取决于 iframe 里跑的是哪个版本,不取决于宿主 SDK 版本。
live 这条尤其要注意 —— 它在契约上挂在 source 上,看起来像是「跟着源走」的。 换源时它确实被用来选源(直播优先 FLV,见 § 4.3),但应用不到播放器上: isLive 决定的是 iOS 后台切回的自愈策略、弹幕模式、以及进度条能不能拖。 与其让这三样静默按旧值跑,不如当场报错(#220)。
改变任一上述构造期配置,就重建播放器(改 key 让框架重新挂载,或先 unmount 再 mount)。
Q9:清晰度怎么切? A:后端出 master.m3u8,SDK 自动 ABR。要做手动菜单:ready 事件的 quality 给你档位清单,ref.setQuality(level) 切换,qualitychange 事件回报结果。
注意回报里的 auto 字段:true 表示是 ABR 自己切的,不该拿它更新 「用户已选择」的高亮,否则用户选了 720p、网络一抖菜单就跟着跳。
Q10:字幕怎么接? A:源里传 subtitles[](mode: 'url' 或 'content'),ready 事件回报可切轨道清单, ref.setSubtitle(id) 切换、'off' 关闭,subtitlechange 回报当前轨(null = 已关)。
ready.subtitles 是可选字段(契约 1.2.0 才加),旧 producer 不发,按 [] 处理。
直播也遵循同一条字幕链路:由宿主把已对齐的 WebVTT URL / content 放进 source.subtitles[]。 SDK 不会自动发现 HLS 内嵌字幕轨、不解析 SEI、也不替宿主对齐直播时间;直播弹幕的 pushDanmaku() 同样不是字幕输入,详见 §6.3「直播字幕边界」。
字幕资源 404 / 过期时会收到 E_SUBTITLE_LOAD_FAILED;主视频应继续播放,业务仅显示非阻断的 “字幕暂不可用”提示并允许关字幕或换轨。静态 iframe URL 没有字幕数组配置和切换命令,不适合需要字幕菜单的场景。
Q11:弹幕数据从哪来? A:你自己拉。SDK 只负责渲染(ADR-028):ref.pushDanmaku(item) 逐条推进来, setDanmakuEnabled 开关,clearDanmaku 清屏。WebSocket 连接、去重、敏感词、限流都归你。
Q12:自定义 header 不生效? A:SDK 已移除 headers / getHeaders(ADR-022)。
不是没做,是做不到 —— HLS 分片请求由浏览器发起,iOS 上更是完全在系统层,JS 拦不到。 留着这个 API 只会让人以为能用。用签名 URL,所有平台都成立。
Q13:签名过期了怎么办? A:拿到 E_AUTH_EXPIRED(retryable: false —— 原样重试必然再次 401)。 正确做法是重新问后端要完整新签名源,然后以新的 revision/key 重建播放器;点播在新流 ready 后恢复位置与播放意图,直播回 live edge。权限拒绝显示业务终态,不能伪装为 Loading 或重连旧 URL。
签名有效期仍应覆盖合理播放窗口,以降低换源频率。
Q14:直播延迟太高? A:开 source.hls.lowLatencyMode: true,延迟能到 2–5s。
⚠️ 所有浏览器都必须显式开:hls.js 的默认值是 true,但 SDK 会把计算后的值显式传下去, 所以你不写就是关的(ADR-024)。
从前这里写的是「非 Safari 必须显式开」 —— 那条的前提是 Safari 走原生、自己识别 LL 标签, 而 ADR-056 把内核改成按能力选,Safari / iOS 现在也走 hls.js。升级后原本靠 Safari 自动 低延迟的直播会静默退回普通 HLS 延迟,迁移动作就是统一传 lowLatencyMode: true。
Q15:什么时候用 sources[] 多源? A:直播 FLV + HLS 双路。日常点播单个 URL 就够 —— SDK 会按环境挑 (iOS / 微信 / UC / 夸克 / 无 MSE 强制 HLS)。
Q16:iOS 上 FLV 播不了? A:flv.js 需要标准 window.MediaSource,iOS 没有这条路径;ManagedMediaSource 只让 HLS 的 hls.js 路径可用,不能兜底 FLV。直播请用 sources[] 同时提供 FLV + HLS;iOS 选 HLS,桌面/Android 可选 FLV。
Q17:MP4 大文件卡? A:后端做 -movflags +faststart + 支持 Range,或直接转 HLS(推荐)。 >20MB 强烈建议转 HLS。
Q18:iframe 里能放我自己的 React 组件当覆盖层吗? A:不能,iframe 不支持函数 props。在 iframe 外面叠加你的组件 —— 这正是团队封装的标准模式,覆盖层本来就该在宿主页。
Q19:官方 CDN 能换成自己的吗? A:能,origin prop 覆盖(ADR-023)。SDK 里没有 hardcode 任何 CDN 域名到不可覆盖的地方。
Q20:静态 iframe 场景怎么发命令? A:发不了。静态嵌入没有 Penpal 握手,没有命令通道,发过去没有回执。
⚠️ embed-helper 不能让你发命令 —— 它只做事件订阅(收 message → 校验 envelope → 分发)。要下命令必须换成 React/Vue inline 或 React/Vue iframe 四种可编程接入方式之一。
播放行为只能在嵌入时用 URL 参数(12 个)定死。
Q21:静态嵌入的 helper.js 从哪引? A:和 iframe src 同目录 —— 把 URL 末尾换成 helper.js:
html
<iframe src="https://sentinel-video.pages.dev/embed/v1.0.25/?src=..."></iframe>
<script src="https://sentinel-video.pages.dev/embed/v1.0.25/helper.js"></script>这样版本锁定(v4 / v4.2 / v4.2.1)对 helper 自动生效,不用另记地址。 gzip 约 25KB(大头是打进去的 zod)。嫌重可以自己写十几行 addEventListener('message'), 代价是没有 schema 校验 —— 但 origin 校验不能省。
Q22:埋点怎么接? A:SDK 只发事件,不上报。你在团队封装层里接自己的埋点栈。
卡顿率用 stalled 算(带 durationMs、能抓冻帧型卡顿、后台不计), 不要用 waiting;错误看板按 category 聚合(6 个且稳定),不要按 code (会随版本增删)。
Q23:waiting / playing 和 stalled 有什么区别? A:前两个是 HTML5 事件透传的瞬间症状;stalled 是 HealthMonitor 测量后的结论, 带时长、能抓到「画面卡死但 xgplayer 根本不发 waiting」的冻帧型卡顿,而且切后台时不计入。
SDK 与业务 UI 都应使用聚合后的 playablechange,而不是用前两者直接控制转圈;埋点使用 stalled、recovery、错误上下文等可归因事件。
Q24:主题定制? A:SDK 不做主题。团队封装组件用 CSS 变量做。
第三层 · 已上线(出问题了)
Q25:PC / Android / iOS 表现不一致? A:先看平台能力矩阵(§ 12)和 iOS 硬限制(§ 11)。SDK 已经把大部分平台差异吃掉了, 剩下的多半是 iOS 无 MSE、微信/UC/夸克劫持这类硬约束。
Q26:直播断线重连后,进度没追到最新? A:这是已知缺口(坑 #34),SDK 目前不处理。
ReconnectPlugin 只做断网自动重连(坑 #1 #2),重连逻辑里没有直播分支。 而且这个落后 1 倍速永远追不上。互动直播场景建议自己刷新:
js
onReconnectSuccess() {
if (isLive) ref.getPlayerHandle()?.load(liveSource)
}Q27:自动播放没生效? A:浏览器普遍禁止有声自动播放。加 muted 通常就能过。
被拒时会收到 autoplayblocked 事件 + E_AUTOPLAY_BLOCKED(retryable: false —— 重试一百次都会被拒)。它需要的不是重试,是一个用户点得到的按钮。
Q28:为什么错误码从 29 个变成了 17 个? A:ADR-034 的审计发现其中 12 个从来没有任何代码发射过 —— 消费方照着写的分支 是永远进不去的死代码,而且没有任何报错提示。
原则是「能删不留」:留一个我们检测不到的错误码在契约里是谎言; 删掉之后将来能检测了再加回来,是向后兼容的 minor,代价极低。
现在有静态护栏保证每个码都有真实发射点。
Q29:E_AUTH_EXPIRED 为什么 retryable: false? A:因为 SDK 不做签名刷新(ADR-022),原样重试用的还是那个过期 URL,必然再次 401。 自动重试在这里不但没用,还会连打几次鉴权失败请求。
正确路径是重新取签名 → 改 source prop。
Q30:preset 为什么只剩一个值? A:ADR-032 把原来的五个收敛成了唯一的 homepage-preview。其余四个没有真实使用场景, 留着只是让人在五个里犹豫。目前传别的值过不了 schema 校验。
Q31:怎么开 debug? A:debug: true。iframe 场景会在 console 打出握手过程、每一次 postMessage、 命令超时(默认 10s)、xgplayer 内部错误。
排查 iframe 问题基本靠它。
Q32:提工单要带什么? A:见 § 31.5 的清单 —— SDK 版本、接入方式、浏览器 + OS 版本、Console 完整报错、 Network 面板的 manifest / 分片请求、debug 输出、复现步骤。
少了这些基本没法定位。