Appearance
五种接入怎么选
五种方式共享同一份契约,行为逐字一致(由 cross-mode-parity 硬红线保证)。 差别不在"能播什么",而在宿主框架、隔离级别,以及你能对播放器做多少事。
一分钟决策
你有构建环境吗?
├─ 没有(CMS / Markdown / 邮件 / 后台富文本)
│ → ⑤ 静态 iframe URL ← 能力最弱,先读下面的警告
└─ 有 → 需要样式 / 脚本隔离吗(第三方页面、多播放器共存、CSS 打架、宿主是 SSR 框架)?
├─ 不需要 → React 项目 → ① React inline ← 能力最全,性能最好
│ Vue 项目 → ② Vue inline
└─ 需要 → React 项目 → ③ React iframe
Vue 项目 → ④ Vue iframe先问隔离、再问框架 —— 框架决定的只是包名,隔离决定的才是你能做什么。
能力对比
| ① React inline | ② Vue inline | ③ React iframe | ④ Vue iframe | ⑤ 静态 iframe URL | |
|---|---|---|---|---|---|
| 安装 | @sentinel-lab/video-react | @sentinel-lab/video-vue | @sentinel-lab/video-react-frame | @sentinel-lab/video-vue-frame | 无需 npm |
| 收事件 | ✅ 全部 | ✅ 全部 | ✅ 全部 | ✅ 全部 | ✅ 全部(需 embed-helper) |
| 下命令(play / pause / seek …) | ✅ 同步 | ✅ 同步 | ✅ 异步 | ✅ 异步 | ❌ 完全不能 |
| 配置方式 | props 完整对象 | props 完整对象 | props 完整对象 | props 完整对象 | ⚠️ 只能 URL query |
| 样式 / 脚本隔离 | ❌ 同 DOM | ❌ 同 DOM | ✅ | ✅ | ✅ |
直接触达 <video> DOM | ✅ | ✅ | ❌ 跨域 | ❌ 跨域 | ❌ 跨域 |
| 首帧速度 | 最快 | 最快 | 慢一点(多一次 CDN 加载) | 慢一点 | 慢一点 |
| 自定义覆盖层 UI | ✅ 最自由 | ✅ 最自由 | ⚠️ 只能在 iframe 外叠 | ⚠️ 只能在 iframe 外叠 | ⚠️ 只能在 iframe 外叠 |
| 版本升级 | 跟你的构建走 | 跟你的构建走 | CDN 路径控制 | CDN 路径控制 | CDN 路径控制 |
「下命令」那行的同步 / 异步是真差异:inline 直连播放内核,
pause()调完就生效; iframe 隔着postMessage,所有命令返回 Promise。从 iframe 换到 inline 时多余的await无害,反过来漏了await就会读到旧状态。
换授权源不是一条消费方命令:四种组件接入更新
source并递增组件key重建;静态 iframe URL 替换整个 iframesrc。这样才能覆盖跨内核和直播/点播变化,详见鉴权与签名失效。
① React inline / ② Vue inline — 能力最全,但没有隔离
选它当:追求首帧速度、要写复杂的自定义覆盖层、要直接摸 <video> 元素。 React 项目用 @sentinel-lab/video-react,Vue 项目用 @sentinel-lab/video-vue —— 两者是镜像关系:同一份 props、同一份事件、同一条数据路径,连产物体积都一样 (实测各 318.6KB gz,差别只在框架桥接那几 KB)。
它的缺点:
- 没有任何隔离。播放器 DOM 就在你的页面里,你的全局 CSS(尤其是
* { box-sizing }、video { width: 100% }这类)会直接影响它,反之亦然。 - 打进你的 bundle。虽然框架桥接那层很薄,但 xgplayer 内核本身不小。
- SSR 宿主要额外处理:播放内核在 Node 里求值会直接报错,Next.js 要
next/dynamic({ ssr: false }),Nuxt / VitePress 这类要异步组件 + 客户端渲染。
不推荐用在
- 你的页面要嵌到第三方站点里(CSS 环境不可控)
- 同页要放很多个播放器(每个都是完整实例,内存吃紧)
- 团队里有人喜欢写全局样式 😅
③ React iframe / ④ Vue iframe — 隔离换一点性能
选它当:需要样式隔离、需要把播放器版本和主应用解耦、宿主是 SSR 框架。
它们的缺点:
- 多一次网络往返。iframe 要从 CDN 加载 embed-app,首帧比 inline 慢。
- 摸不到
<video>。跨域 iframe,想做的一切都得走契约里的命令和事件。 契约里没有的,你就做不了(这是刻意的,见 为何没有逃生舱)。 - 覆盖层 UI 只能叠在 iframe 外面。你没法把一个按钮画进播放器内部的 DOM, 只能在 iframe 上方绝对定位。全屏时要额外处理层级。
- 依赖 iframe 地址可达。默认使用官方 Cloudflare 地址;受管环境应向部署方取得 已验证的
origin与精确版本,再写入配置。
不推荐用在
- 首帧时间是硬指标的场景(比如信息流里的自动播放预览)
- 完全离线 / 无可用 iframe 地址的环境
Vue 以前为什么没有 inline?现在为什么有了?
从前的理由是「inline 要维护一套 Vue 的覆盖层组件体系,和 React 那套是两份实现、 两份测试、两处漂移源」(ADR-002)。
那个前提已经没了。 覆盖层现在是框架无关的 custom elements,React 和 Vue 用的是同一份,「Vue 版覆盖层」这件事根本不存在。ADR-002 因此转 Superseded (见 ADR-052),Vue inline 是正式支持的接入方式。
仍然选 iframe 的理由是隔离和 SSR,不再是「Vue 只能这样」。
⑤ 静态 iframe URL — 最弱的一种,务必读完
选它当:你根本没有构建环境。CMS 正文、Markdown 文档、邮件模板、后台富文本编辑器。
这是唯一不需要 npm 的接入方式,代价是能力大幅缩水。
html
<iframe
src="https://sentinel-video.pages.dev/embed/v1.0.25/?src=https%3A%2F%2Fcdn.example.com%2Fa.m3u8&type=hls&muted=1&autoplay=1"
allow="autoplay; fullscreen; picture-in-picture; encrypted-media; screen-wake-lock"
allowfullscreen
style="width:100%;aspect-ratio:16/9;border:0"
></iframe>⚠️ 它做不到的事
1. 完全没有命令通道。
这是最大的限制。静态场景下宿主和 iframe 没有 Penpal 握手,只有单向的 postMessage 广播。所以:
你不能用代码让它 play、pause、seek、切源、调音量。一次都不能。
用户能点原生控件操作,但你的 JS 碰不到它。想要"点我方按钮播放"这种最基础的交互, 必须换成前面四种里的任意一种。
2. 配置只能靠 URL query。
没法传嵌套对象,所以多码率、字幕、自定义 messages 这些都传不了。 能传的就是下面这 12 个参数。
3. URL 里的东西全是明文。
包括签名 URL 的 token。用户 F12 就能看到。(其实所有接入方式都一样, 但静态场景下 URL 直接写在 HTML 正文里,更容易被复制走。)
能收事件,但要装一个包
事件是单向广播的,能收。但要正确校验和分发,建议用 @sentinel-lab/video-embed-helper:
js
import { on } from '@sentinel-lab/video-embed-helper'
const off = on('ended', () => {
location.href = nextEpisodeUrl
})注意这里的矛盾
这个包只有 on / off / destroy 三个方法——没有任何命令,因为压根没有命令通道。 而且既然你都能 import 了,说明你其实是有构建环境的,那不如直接用 ①~④。
embed-helper 真正的适用场景很窄:宿主页面有一点点 JS 能力(比如能插 <script>), 但没法引入完整框架。
iframe URL 全部参数(12 个)
| 参数 | 类型 | 说明 |
|---|---|---|
src | string | 必填。视频 URL,要 encodeURIComponent |
type | hls | flv | mp4 | 显式指定格式。签名 URL 建议一定传,否则从后缀猜会猜错 |
live | bool | 是否直播。影响源优先级(直播 FLV 优先,点播 HLS 优先) |
lowLatency | bool | 开 LL-HLS。所有浏览器都必须显式开(ADR-024 + ADR-056) |
poster | string | 封面图 URL |
preset | homepage-preview | 预设配置组合(目前只有这一个,见 ADR-032) |
autoplay | bool | 自动播放。几乎必须配 muted=1,否则会被浏览器拒 |
muted | bool | 静音 |
loop | bool | 循环播放 |
controls | bool | 显示原生控件。静态场景下建议保持开启——你没有命令通道,关了用户就没法操作了 |
playsinline | bool | 移动端行内播放(不自动全屏) |
interactive | bool | 是否响应用户交互 |
布尔值写法都一样:1 / true / 空串(如 ?muted)为真,0 / false 为假。
参数非法 = 直接黑屏
URL 是外部输入,一律过 Zod 校验。任何一个参数不合法,整个配置解析失败,播放器不会启动。 这是刻意的——宁可明确失败,也不要拿半残配置创建播放器然后黑屏让你查半天。 上线前务必真机点一遍你拼出来的 URL。
还是拿不定主意?
去选择接入入口:React 和 Vue 的组件示例分别在各自的原生门户中运行;静态 iframe 请直接看嵌入指南。