Skip to content

API 按场景查

左边导航里的「命令 / 事件 / 错误码」是契约的完整清单,按类型排。 这一页反过来,按你想做的事排 —— 做一个功能通常要同时用到命令 + 事件 + 错误码, 分三页查很难拼起来。

怎么用这页

先找到你在做的场景 → 抄那一节的代码 → 需要精确签名时再点进参考页。


五种接入方式的命令能力矩阵

先说清楚边界,免得抄了代码发现调不通。

React inlineReact iframeVue 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

timeupdateduration 在直播下是 0,不是 Infinity —— 后者过不了 JSON 序列化(变成 null),而契约事件要跨 iframe 传, 所以 player-core 在源头就归一成 0。直播不要画进度条。


场景 2 · 自动播放被拒

浏览器普遍禁止有声自动播放。这不是异常,是必然会发生的正常路径

jsx
<VideoPlayer
  autoplay
  muted            // 静音自动播放通常被允许
  onAutoplayBlocked={() => setShowPlayButton(true)}
/>

E_AUTOPLAY_BLOCKEDretryablefalse —— 自动重试一百次都会被拒, 它需要的不是重试,是一个用户点得到的按钮

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 / playingstalled
来源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) }}
/>

onSeekedtime实际落点,不一定等于你传给 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()
  • onQualityChangeauto 要用上: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,每个错误自带 categoryretryable —— 照着分支写就行,不用维护自己的错误码表:

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_EXPIREDSDK 不做签名刷新(ADR-022),原样重试必然再次 401
E_MEDIA_NOT_SUPPORTED环境不支持这个格式,重试不会变
E_SUBTITLE_LOAD_FAILED多半是临时网络问题
E_MEDIA_DECODE局部自愈有机会(ErrorRecovery 会先试)

完整 17 个见 错误码参考


接下来