Appearance
API 按场景查
左边导航里的「命令 / 事件 / 错误码」是契约的完整清单,按类型排。 这一页反过来,按你想做的事排 —— 做一个功能通常要同时用到命令 + 事件 + 错误码, 分三页查很难拼起来。
怎么用这页
先找到你在做的场景 → 抄那一节的代码 → 需要精确签名时再点进参考页。
五种接入方式的命令能力矩阵
先说清楚边界,免得抄了代码发现调不通。
| React inline | React iframe | Vue iframe | 静态 iframe URL | |
|---|---|---|---|---|
| 收事件 | ✅ 29 个 | ✅ 29 个 | ✅ 29 个 | ✅ 29 个 |
| 发命令 | ✅ | ✅ | ✅ | ❌ 没有命令通道 |
29 个里有一个不完全一致
playablechange(聚合播放状态,ADR-043)的 reason='frame_disconnected' 那一支由宿主侧产生, 静态 iframe 那条路没有宿主侧代码(iframe 是嵌入方自己写的标签),所以收不到那一支。 其余八个 reason 五种方式完全一致。
静态 iframe URL 是单向的
它没有 Penpal 握手,宿主页只能 addEventListener('message') 收事件, 发不了命令 —— 发出去也没有回执。所有 ref.play() 这类写法在这个模式下都不适用。
播放行为只能在嵌入时通过 URL 参数定死(autoplay / muted / preset 等 12 个), 之后无法改变。需要运行时控制,就得换成三种 SDK 接入方式之一。
句柄上有 15 个方法,契约有 16 个命令
差的那个是 load(换源),三个消费面都刻意不暴露在句柄上 —— 换源的正规方式是改 source prop,声明式的:
vue
<VideoPlayerFrame :source="currentSource" />
<!-- currentSource 变了 → 内部自动 load,不重建 iframe -->只有一种情况需要绕过它:用同一个 URL 重新拉一次(prop 没变化,不会触发)。 这时走逃生舱:
js
ref.value?.getPlayerHandle()?.load(source)getPlayerHandle() 在 inline 模式返回 PlayerCoreHandle,在两个 iframe 模式返回 PlayerFrameHandle —— 两者都有 load。
场景 1 · 起播与基本控制
最常见的一组。注意 play() 是唯一返回 Promise 且可能 reject 的命令。
js
await ref.play() // 可能 reject —— 见场景 2
ref.pause()
ref.seek(30) // 越界值由 player-core 钳制,不报错
ref.setVolume(0.8) // 0–1
ref.setMuted(true)
ref.setPlaybackRate(1.5) // 0.25–4| 你要的信号 | 监听 |
|---|---|
| 元信息就绪(时长 / 清晰度 / 字幕轨) | onReady |
| 播 / 停 / 放完 | onPlay onPause onEnded |
| 进度(~250ms 一次) | onTimeUpdate |
| 音量或静音变了 | onVolumeChange |
直播场景不要读 duration
timeupdate 的 duration 在直播下是 0,不是 Infinity —— 后者过不了 JSON 序列化(变成 null),而契约事件要跨 iframe 传, 所以 player-core 在源头就归一成 0。直播不要画进度条。
场景 2 · 自动播放被拒
浏览器普遍禁止有声自动播放。这不是异常,是必然会发生的正常路径。
jsx
<VideoPlayer
autoplay
muted // 静音自动播放通常被允许
onAutoplayBlocked={() => setShowPlayButton(true)}
/>E_AUTOPLAY_BLOCKED 的 retryable 是 false —— 自动重试一百次都会被拒, 它需要的不是重试,是一个用户点得到的按钮。
js
// 用户点击 = 有了手势,这时 play() 才会成功
async function onUserClick() {
await ref.play()
setShowPlayButton(false)
}场景 3 · 做加载 / 缓冲 UI
这里有两套信号,用途不同,别混:
jsx
// ① 做 UI —— 瞬间信号,立刻转圈
<VideoPlayer
onWaiting={() => setBuffering(true)}
onPlaying={() => setBuffering(false)}
/>
// ② 做埋点 —— 测量结果,带时长
<VideoPlayer
onStalled={({ phase, position, durationMs }) => {
if (phase === 'end') report('stall', { position, durationMs })
}}
/>waiting / playing | stalled | |
|---|---|---|
| 来源 | HTML5 事件透传 | HealthMonitor 测量后 |
| 带时长 | ❌ | ✅ durationMs(phase: 'end' 时) |
| 抓得到冻帧型卡顿 | ❌ | ✅ 主动轮询 currentTime |
| 后台切走时 | 照发 | 不计(不污染指标) |
| 用途 | UI 转圈 | 卡顿率埋点 |
「冻帧型卡顿」是指画面卡死但 xgplayer 根本不发 waiting 的情况 —— 只靠 waiting 做监控会漏掉这一类。
stalled 无条件发
没有任何开关能关掉它。早期的 consent.analytics 门控已按 ADR-040 移除 —— 它会在冻帧时掐掉你唯一的信号。合规过滤请放在你自己的上报函数里, 见 埋点接入。
场景 4 · 拖进度条
jsx
<VideoPlayer
onSeeking={({ time }) => setScrubbing(true)}
onSeeked={({ time }) => { setScrubbing(false); setCurrent(time) }}
/>onSeeked 的 time 是实际落点,不一定等于你传给 seek() 的值 —— 越界会被钳制到有效范围。以 onSeeked 回报的为准,别乐观更新。
场景 5 · 清晰度与字幕菜单
两者结构对称:ready 给你清单,命令切换,再有一个事件回报结果。
js
// ① ready 里拿清单
onReady({ quality, subtitles }) {
this.qualityList = quality // [{ level, label?, height?, bitrate? }]
this.subtitleList = subtitles // [{ id, locale, label }],可能是 undefined
}
// ② 切换
ref.setQuality(2) // 或 'auto' 交给 ABR
ref.setSubtitle(0) // 或 'off' 关闭
// ③ 回报
onQualityChange({ level, auto }) // auto: true = ABR 自己切的,false = 用户切的
onSubtitleChange({ id }) // id: null = 已关闭两个易错点
subtitles是可选字段(契约 1.2.0 才加,ADR-027)。旧版本 producer 不发, 按[]处理,别直接.map()。onQualityChange的auto要用上:ABR 自动切的档位不该去更新「用户已选择」的高亮状态, 否则用户选了 720p,ABR 一降档菜单就跟着跳。
字幕加载失败会拿到 E_SUBTITLE_LOAD_FAILED(retryable: true)。 这个错误码的存在本身有故事 —— xgplayer 用一个空 catch 把它吞掉了, 详见 35 个坑与 11 个插件 · #35。
场景 6 · 直播断线重连
SDK 自动重连(3 次,指数退避 1s → 2s → 4s,封顶 15s),你只需要接三个事件:
js
onReconnectStart({ attempt, maxAttempts }) // 可做「重连中 2/3」提示
onReconnectSuccess()
onReconnectFailed() // 到这里才该打扰用户在 reconnectfailed 之前别弹错误框 —— SDK 还在努力,弹了又自己恢复,体验更差。
手动重连:
js
ref.reconnect({ resetCounter: true }) // resetCounter 把重试次数归零直播场景要自己追最新(坑 #34)
ReconnectPlugin 没有直播分支,重连后可能停在断点而不是追到最新,而且 1 倍速永远追不上。互动直播建议:
js
onReconnectSuccess() {
if (isLive) ref.value?.getPlayerHandle()?.load(liveSource)
}详见 点播和直播差在哪。
场景 7 · 弹幕
数据源归你,渲染归 SDK(ADR-028)。SDK 不负责拉弹幕,只负责把你推进来的渲染出去。
js
ref.setDanmakuEnabled(true)
ref.pushDanmaku({ /* DanmakuItem */ }) // 直播逐条推
ref.clearDanmaku() // 清空当前屏推送失败拿 E_DANMAKU_SEND_FAILED(retryable: true)。
贯穿各场景 · 错误怎么接
所有错误走同一个出口 onError,每个错误自带 category 和 retryable —— 照着分支写就行,不用维护自己的错误码表:
js
onError({ code, message, category, retryable }) {
if (retryable) {
scheduleRetry() // 契约保证:这个错重试有意义
} else {
showError(message) // 重试没用,直接告诉用户
}
report('player_error', { code, category }) // 埋点按 category 聚合,升级不会乱
}retryable 是从 ERROR_META 推出来的,不接受调用方传入 —— 避免同一个 code 在不同包里被标成不同的值。
几个容易判断错的:
| 错误码 | retryable | 为什么 |
|---|---|---|
E_AUTOPLAY_BLOCKED | ❌ | 重试还是会被拒,要的是用户手势 |
E_AUTH_EXPIRED | ❌ | SDK 不做签名刷新(ADR-022),原样重试必然再次 401 |
E_MEDIA_NOT_SUPPORTED | ❌ | 环境不支持这个格式,重试不会变 |
E_SUBTITLE_LOAD_FAILED | ✅ | 多半是临时网络问题 |
E_MEDIA_DECODE | ✅ | 局部自愈有机会(ErrorRecovery 会先试) |
完整 17 个见 错误码参考。
接下来
- 命令 Methods · 事件 Events · 错误码 Errors —— 精确签名
- 在播放器上做 UI —— 8 个场景的可运行覆盖层实现
- 选择接入入口 —— 按 React、Vue 或静态 iframe 路线进入对应资料