Appearance
Frame 播放器接入与初始化
面向业务开发者:本页说明怎样嵌入和驱动已经部署好的 iframe 播放器。运维如何构建、托管、验真和回滚,请读iframe 构建、部署与升级。
先选对接入面
| 选择 | 适合什么 | 需要接受的边界 |
|---|---|---|
| React / Vue Inline | 最在意首帧,且可与宿主页面同进程运行 | 播放器与宿主共享 DOM、样式和运行时 |
| React Frame / Vue Frame | 需要 CSS、第三方内容或故障隔离,也需要命令和完整事件 | iframe 建立和握手有额外成本;命令经通信通道异步完成 |
| 静态 iframe URL | CMS、Markdown 或没有 npm 构建链 | 只能订阅之后发生的事件,没有 SDK 命令句柄和加载期聚合状态 |
Frame 是隔离边界,不是“更安全”或“更快”的绝对选项。视频源签名、CORS、业务鉴权和品牌 UI 仍属于宿主和服务端。
React Frame:最小可播放接入
tsx
import { useRef } from 'react'
import { VideoPlayerFrame, type VideoPlayerFrameHandle } from '@sentinel-lab/video-react-frame'
const source = 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8'
export function ArticlePlayer() {
const playerRef = useRef<VideoPlayerFrameHandle>(null)
return (
<>
<VideoPlayerFrame
ref={playerRef}
source={source}
controls
onReady={({ duration }) => console.info('metadata ready', duration)}
onPlayableChange={({ playable, recoverable, reason }) => {
console.info({ playable, recoverable, reason })
}}
onError={(error) => console.error(error.code, error.message)}
/>
<button type="button" onClick={() => void playerRef.current?.play()}>
播放
</button>
</>
)
}play()、pause()、seek()、全屏、清晰度、字幕和 reconnect() 都返回 Promise:它们要跨 iframe 发送命令,必须处理失败或至少避免把未处理 Promise 留给浏览器。完整句柄见Methods 参考。
Vue Frame:最小可播放接入
vue
<script setup lang="ts">
import { ref } from 'vue'
import { VideoPlayerFrame } from '@sentinel-lab/video-vue-frame'
const source = 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8'
const player = ref<InstanceType<typeof VideoPlayerFrame> | null>(null)
async function play() {
await player.value?.play()
}
</script>
<template>
<VideoPlayerFrame
ref="player"
:source="source"
controls
@ready="({ duration }) => console.info('metadata ready', duration)"
@playable-change="({ playable, recoverable, reason }) => console.info({ playable, recoverable, reason })"
@error="(error) => console.error(error.code, error.message)"
/>
<button type="button" @click="play">播放</button>
</template>Vue 模板使用 kebab-case 事件名,例如 @playable-change;暴露在模板 ref 上的方法同样是异步命令。组件自带 SSR 客户端守卫,不必额外套一层 <ClientOnly>。
静态 iframe:手工权限与事件过滤
静态嵌入不经过 Frame SDK。给 iframe 有意义的标题、所需权限和 allowfullscreen,先订阅事件,再设置 src,否则早期事件不会回放。
html
<iframe
id="article-player"
title="文章视频"
allow="autoplay; fullscreen; picture-in-picture; encrypted-media; screen-wake-lock"
allowfullscreen
></iframe>
<script src="https://sentinel-video.pages.dev/embed/v1.0.25/helper.js"></script>
<script>
const iframe = document.getElementById('article-player')
const player = SentinelEmbed.createEmbedListener({
origin: 'https://sentinel-video.pages.dev',
iframe,
})
player.on('ended', () => console.info('show next article'))
player.on('error', (error) => console.error(error.code, error.message))
iframe.src = 'https://sentinel-video.pages.dev/embed/v1.0.25/?src=https%3A%2F%2Ftest-streams.mux.dev%2Fx36xhzz%2Fx36xhzz.m3u8'
</script>createEmbedListener({ origin, iframe }) 同时校验消息来源、该 iframe 的窗口身份和协议结构。它只接收事件,不能 play()、seek() 或报告 Frame 的 frame_disconnected 聚合状态;这些需求应选 React / Vue Frame。
URL、版本与调试
| 字段 | 作用 | 不是什么 |
|---|---|---|
origin | iframe 产物所在源;可指向私有 CDN 或本地 embed-app | 哪些父页面被允许连接的白名单 |
version | 路径段,如 v4 或已留存的精确版本 | protocol 字段,也不是 npm 版本的替代品 |
debug | 打开 Penpal 的握手和消息日志,URL 会带 ?debug=1 | 播放引擎性能诊断或生产遥测 |
默认 URL 是 https://sentinel-video.pages.dev/embed/v1.0.25/。本地开发可使用 origin="http://localhost:5174" 与 version="" 指向 embed-app 开发服务器;业务环境使用其他地址时,由部署方提供实际 origin 和精确 iframe 应用版本目录,再写进宿主配置。也可在宿主构建期通过 VITE_SENTINEL_EMBED_ORIGIN 和 VITE_SENTINEL_EMBED_VERSION 统一覆盖。
Loading、握手与失败处置
用 playablechange 的 { playable, recoverable, reason } 驱动 UI,不要自己拼 waiting、重连和错误事件。Frame 在连接中先给出 frame_disconnected,连接完成后通常进入 initializing;可恢复时展示加载或重试提示,不可恢复时展示业务 CTA 并保留错误码给监控。
version_incompatible:宿主与 iframe 的契约 major 不兼容;选择匹配的 SDK 与 iframe 版本,不要继续发送命令。csp_denied:父页面的 CSP 拦截了 iframe;让站点管理员检查frame-src等策略。frame_disconnected且不可恢复:iframe 失联或已崩溃;保留业务错误页,必要时由用户动作触发重新挂载。- 可恢复的播放失败:在业务 CTA 中调用
await ref.current?.reconnect({ resetCounter: true });不要自动无限重试。
权限与安全:四层不要混淆
| 层 | 谁配置 | 解决的问题 |
|---|---|---|
Frame SDK allow | React / Vue Frame 自动写入 autoplay; fullscreen; picture-in-picture; encrypted-media; screen-wake-lock | iframe 可请求这些浏览器能力 |
宿主静态 iframe allow / allowfullscreen | 静态嵌入开发者 | 静态 HTML 的同类权限声明 |
VITE_SENTINEL_ALLOWED_PARENT_ORIGINS | 运维在 embed-app 构建前设置 | 哪些精确父 origin 可与 iframe Penpal 握手、接收静态事件 |
CSP frame-ancestors / 父页 Permissions Policy | 服务端或站点平台 | 谁可嵌入该页面、浏览器是否最终授予功能 |
| 视频源 CORS | 视频 CDN / 源站 | iframe 内播放器是否能读取 HLS、MP4、FLV 及其分片 |
自动 allow 不是成功保证:浏览器支持、用户手势、父页面更严格的 Permissions Policy 仍会限制自动播放、全屏或 PiP。也不要直接复制未经实测的 sandbox 配置;它会改变 origin、脚本或表单能力,应按业务安全模型单独设计和验收。
VITE_SENTINEL_ALLOWED_PARENT_ORIGINS 是部署方的构建期值:公开 CDN 不配时允许任意父站,受限部署由部署方写入精确 origin 后重新构建。它不取代 CSP,不是 origin prop,也不替代视频源 CORS。
和运维的交接
| 开发者交给运维 | 运维回传给开发者 |
|---|---|
| 父页面 origin 清单、所需权限(autoplay / fullscreen / PiP)、预期 iframe origin 与 version | 实际 iframe origin、可用版本路径、白名单已写入的构建版本 |
业务的 frame-src / Permissions Policy 约束和验收场景 | 已生效的 frame-ancestors / 边缘权限策略、MIME / CORS / 版本路径验真结果 |
| 握手、播放、命令、失败 CTA 的测试结果 | 部署 ID、回滚目标和变更窗口 |
部署、HTTPS、缓存、MIME、SPA fallback 与回滚由iframe 构建、部署与升级负责;播放器的全量参数、事件、方法和错误码由API 按场景查负责。