@sentinel-lab/video-player-core
v4.0.0
Published
Sentinel Video Player 播放核心:xgplayer 抽象 + 11 个生产稳定性插件(选源 / 重连 / 自动播放 / 兼容 / 卡顿测量等)
Readme
@sentinel-lab/video-player-core
Sentinel Video Player 的播放核心(L1)。包装 xgplayer 3.0.26,对外提供统一的命令 / 事件接口,把生产坑点挡在这一层。
依赖:@sentinel-lab/video-protocol + xgplayer 3.0.26(精确锁)+ xgplayer-hls.js + xgplayer-flv.js。不依赖 React / Vue。
已实现范围
11 个稳定性插件全部实现(P0×5 + P1×3 + P2×3),逻辑均有单元测试覆盖:
| 插件 | 优先级 | 解决的 pitfall |
|:---|:---:|:---|
| safeDestroy | P0 | #3(destroy 后监听残留)、#4(root 样式残留)、#23(反复 destroy 崩溃) |
| SourceRouter | P0 | #22(Firefox 不原生支持 HLS)、#27/#28/#29(FLV 在 iOS / 微信 / UC / 夸克 播不了)、#34(带 query 的 HLS URL 推断失败) |
| AutoplayGuard | P0 | #16(autoplay 被拒未告知业务) |
| Reconnect | P0 | #1(断网无重连)、#2(换源失败无重试) |
| Visibility | P0 | #5 / #24(iOS 后台切回卡死) |
| Compat | P1 | #9(UC 劫持播放器)、#10(夸克控件消失) |
| WakeLock | P1 | #15(播放期间屏幕熄灭) |
| HealthMonitor | P1 | #13 / #14(卡顿测量,经 stalled 事件上报) |
| ErrorRecovery | P2 | #17 / #18(点播坏 ts 跳过、MP4 abort 续播) |
| FullscreenGuard | P2 | #7(iOS 微信全屏崩溃) |
| ZIndexGuard | P2 | 层级冲突(归一 player 根节点 z-index) |
清晰度 / ABR 全链路已实现:setQuality 命令、qualitychange 事件、ready.quality 档位清单
(从 hls.js 填充,单码率源为空数组)。字幕(setSubtitle / subtitlechange / ready.subtitles,
ADR-027)、弹幕(pushDanmaku / setDanmakuEnabled / clearDanmaku + config.danmaku,ADR-028)、
LL-HLS 显式开启(source.hls.lowLatencyMode,ADR-024)也已接线。
用法
import { createPlayer } from '@sentinel-lab/video-player-core'
const player = createPlayer({
el: container,
config: {
source: {
sources: [
{ url: 'https://live.example.com/s.flv', type: 'flv' },
{ url: 'https://live.example.com/s.m3u8', type: 'hls' },
],
live: true,
},
autoplay: true,
muted: true,
},
onEvent: (e) => console.log(e.event, e.payload),
})
await player.play()
player.seek(30)
player.destroy() // 幂等,反复调用安全设计上值得知道的几件事
SourceRouter 不是 xgplayer 的 BasePlugin,是纯函数。 选源必须发生在 new Player() 之前——内核插件(hls.js / flv.js)得在构造时就注册进去,而 BasePlugin 的生命周期最早只到 beforeCreate,拿不到这个时机。纯函数也更好测:UA 直接传进来,不用为了测 iOS 去改 navigator。
MP4 永远走浏览器原生 <video>。 xgplayer-mp4 是硬阻塞(坑 #30/#31/#32,Issue #1872 卡死 / #1578 iOS 17+ 不兼容 / #964 SourceBuffer 溢出),永不引入。
destroy() 先摘监听,再销毁 player。 顺序反了的话,xgplayer 在销毁过程中还会抛 pause / ended,消费方会在组件已经卸载之后收到事件(坑 #3)。这个顺序有测试守着。
safeDestroy 没有 enabled 开关。 ARCHITECTURE § 10.2 明确它"强制,不可关"——一个能被关掉的内存泄漏防护没有意义。
换内核会明确报错。 hls.js / flv.js 的内核插件在构造时注册,运行时换不了。load() 一个需要不同内核的源时抛 E_METHOD_NOT_SUPPORTED,而不是悄悄播不出来。消费方需要销毁 player 用新 source 重建。
直播的 duration 是 0,不是 Infinity。 Infinity 过不了 JSON 序列化(变成 null),而事件要跨 iframe 传,所以在源头就归一成 0。
环境探测与测试
detectEnv({ userAgent, hasMediaSource }) 是纯函数,UA 从外部注入。SSR 场景(没有 window / navigator)保守降级成"无 MSE、强制 HLS"——在服务端误判成能播 FLV 会让首屏白屏。
单元测试用轻量 stub,不实例化真实 Player(见 tests/create-player.test.ts 的 MockPlayer)。
pnpm --filter @sentinel-lab/video-player-core test