HyperFrames Player 完整指南:零依赖 Web Component 播放器架构、API 与集成实践
【免费下载链接】hyperframesWrite HTML. Render video. Built for agents.项目地址: https://gitcode.com/GitHub_Trending/hy/hyperframes
@hyperframes/player是 HyperFrames 官方提供的可嵌入 Web Component 播放器,用于在任意网页或前端框架中播放 HyperFrames composition(由 HTML + GSAP 时间线构成的视频合成)。它零依赖、基于 Shadow DOM 与沙箱 iframe 运行,能自动探测 composition 的尺寸与时长、响应式缩放、处理移动端自动播放策略,并暴露一套完整的播放控制 API。读完本文,你将掌握该播放器的安装、属性配置、JavaScript API、运行时数据通道、沙箱隔离模型与底层工作原理,能够把它无缝接入 React、Vue 或纯 HTML 页面。
安装与加载方式
通过 npm 安装(当前仓库版本为 0.8.33,见 packages/player/package.json):
npm install @hyperframes/player也可以直接用 CDN 以 ESM 模块方式加载:
<script type="module" src="https://cdn.jsdelivr.net/npm/@hyperframes/player"></script>如果你的页面需要经典<script>标签而非 ESM,请使用显式的全局构建版本:
<script src="https://cdn.jsdelivr.net/npm/@hyperframes/player/dist/hyperframes-player.global.js"></script>从 package.json 的exports字段可以看出,包还额外暴露了./slideshow子路径(对应dist/slideshow/hyperframes-slideshow.*),用于幻灯片播放器场景。三种分发格式(ESM / CJS / IIFE)均会生成压缩产物与 source map,并包含 TypeScript 类型声明。
基础用法
在最简单的场景下,你只需要一个自定义元素标签:
<hyperframes-player src="./my-composition/index.html" controls></hyperframes-player>播放器会将 composition 加载进一个沙箱 iframe,自动探测其尺寸与时长,并按比例缩放以适配所在容器。
与前端框架集成
自定义元素注册后即可在任意框架中使用。以 TypeScript 为例:
import "@hyperframes/player"; // 自定义元素此时已注册,可直接在标记中使用 // React: <hyperframes-player src="..." controls /> // Vue: <hyperframes-player :src="url" controls />由于底层是标准 Custom Element,React 与 Vue 都会把src、controls等属性原样透传到 DOM,无需任何适配层。
Poster 封面图
在播放开始前显示一张静态封面图:
<hyperframes-player src="./composition/index.html" poster="./thumbnail.jpg" controls ></hyperframes-player>调用play()时封面图会被自动移除。
属性(Attributes)详解
播放器通过以下属性控制行为,全部以 HTML 属性形式声明,并同步暴露为同名的 JS 属性(property):
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
src | string | — | composition HTML 文件的 URL |
audio-src | string | — | 音频 URL,用于父帧播放(移动端场景) |
width | number | 1920 | composition 宽度(像素,用于宽高比计算) |
height | number | 1080 | composition 高度(像素,用于宽高比计算) |
controls | boolean | false | 显示播放/暂停、进度条与时间显示 |
muted | boolean | false | 静音 |
audio-locked | boolean | false | 强制静音并隐藏音量控件,观看者无法开启声音 |
poster | string | — | 播放开始前显示的图片 URL |
playback-rate | number | 1 | 播放速度倍率(0.5 为半速,2 为两倍速) |
autoplay | boolean | false | 就绪后自动开始播放 |
loop | boolean | false | composition 结束时循环重播 |
shader-capture-scale | number | — | 传递给浏览器预览的 shader 转场快照缩放(0.25~1) |
shader-loading | composition \| player \| none | composition | 控制 shader 转场预加载 UI 的归属 |
尺寸与宽高比
width/height定义的是 composition 的原生分辨率(用于宽高比计算),而不是播放器的显示尺寸。播放器实际显示大小由元素或父容器的 CSS 决定(见下文"Sizing"小节)。在 hyperframes-player.ts 中,这两个属性经由readPositiveDimension校验——NaN、零或负数都会被拒绝并回退到默认值 1920×1080,避免scale(NaN)或除零导致播放器空白(该保护逻辑定义在 composition-probe.ts)。
播放速率边界
playback-rate并非任意值都接受。源码中定义了MIN_PLAYBACK_RATE = 0.1与MAX_PLAYBACK_RATE = 5(见 hyperframes-player.ts),超出范围的速率会被钳制,这既与 iframe 内 runtime 的钳制逻辑保持一致,也防止了极端值触发原生HTMLMediaElement.playbackRatesetter 抛错。
Shader 转场预览
当 composition 使用@hyperframes/shader-transitions时,播放器可以接管"仅预览用"的 shader 捕获设置:
<hyperframes-player src="./composition/index.html" shader-capture-scale="1" shader-loading="player" controls ></hyperframes-player>shader-loading="player":由播放器根据 shader 进度消息显示"转场预加载"覆盖层;shader-loading="composition"(默认):保留 composition 自身的回退行为;shader-loading="none":完全抑制加载器。
从实现上看(shader-options.ts),这两个属性会被改写进 composition 的 URL 查询参数(__hf_shader_capture_scale、__hf_shader_loading)或 srcdoc 的<head>脚本(window.__HF_SHADER_CAPTURE_SCALE、window.__HF_SHADER_LOADING)中。注意一个工程细节:改写 URL 时只删除/追加播放器自己的两个 key,其余查询参数按字节原样透传,避免URLSearchParams表单编码(空格变+)破坏 composition 作者原本的查询串。shader-capture-scale会被钳制在0.25~1之间。
音频锁(宿主强制的静音播放)
audio-locked会强制开启muted并隐藏音量控件,观看者没有任何 UI 路径可以重新打开声音。它适用于嵌入聊天宿主(如 Claude.ai、ChatGPT 等)的场景——无论观看者意图如何,音频都必须保持关闭。直接设置muted是不够的,观看者仍可通过控制栏把它翻转回来。
移除audio-locked只会重新显示控件,不会自动取消静音;调用方需要在解锁后显式管理muted。
宿主环境回退机制。某些宿主渲染器(尤其是 Claude 桌面版 Electron 客户端)会在属性到达 DOM 前剥离未知的自定义元素属性,导致该属性失效。作为安全网,播放器还会在检测到此类环境时自行施加锁定——通过navigator.userAgent匹配Claude/\d+Electron特征(见 hyperframes-player.ts),即使属性从未到达,音频也保持静音。公开的audioLocked属性仍然只反映属性本身,因此外部消费者(例如镜像状态的宿主 widget)不会受回退逻辑影响。
移动端音频
移动端浏览器会阻止 iframe 内部的audio.play()——当用户手势发生在父帧时,User Activation 规范不会通过postMessage把激活状态传播到 iframe 边界。播放器对同源 iframe(默认情况,sandbox包含allow-same-origin)自动处理此问题:
- composition 就绪后,播放器从 iframe DOM 中提取所有带时间轴的媒体元素(
audio[data-start]、video[data-start]),并在父帧创建副本; - iframe 内的原件被禁用(移除
src与data-start),runtime 不会尝试播放它们; - 当
play()从用户手势调用时,父帧媒体的.play()在手势调用栈中同步执行,满足移动端自动播放策略; - 父帧媒体与 GSAP 时间线同时启动并自由运行——两者都是实时系统,无需主动同步。
这一机制由ParentMediaManager实现(parent-media.ts):它会为每个代理条目维护镜像的currentTime,仅当连续 2 个采样点漂移超过 50ms(MIRROR_DRIFT_THRESHOLD_SECONDS = 0.05,MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES = 2)才回写校正,从而吸收单次采样抖动而不频繁跳变。它还通过MutationObserver监听 iframe 内动态新增/移除的媒体元素,支持子 composition 激活等运行时变化。消费者无需任何改动——开箱即用。
可选的audio-src属性可以在 iframe 加载前就开始预加载主音轨(慢网速场景有用),但移动端播放并不依赖它。
JavaScript API
const player = document.querySelector("hyperframes-player"); // 播放控制 player.play(); player.pause(); player.seek(2.5); // 跳转到 2.5 秒 // 属性 player.currentTime; // number(可读写) player.duration; // number(只读) player.paused; // boolean(只读) player.ready; // boolean(只读) player.playbackRate; // number(可读写) player.muted; // boolean(可读写) player.audioLocked; // boolean(可读写)——强制静音并隐藏音量控件 player.loop; // boolean(可读写) player.shaderCaptureScale; // number(可读写) player.shaderLoading; // "composition" | "player" | "none"(可读写) // 内部 iframe 访问(进阶用户使用,见"进阶:iframe 访问") player.iframeElement; // HTMLIFrameElement(只读)seek()的实现同时携带秒与帧两个维度:timeSeconds供协议 v1 的 runtime 使用,frame(timeSeconds × runtimeFps)兼容旧版 runtime,保证跨源嵌入可定位播放。此外还有stopMedia()(停止 iframe 与父帧媒体)、volume、scenes(最近一次 runtime timeline 消息中的场景列表)等成员。在支持直接访问 GSAP 时间线的场景中(window.__timelines且无 runtime),seek会直接驱动时间线并在暂停时触发onUpdate,使依赖根时间线onUpdate驱动场景显隐的 composition(如幻灯片 deck)在暂停态也能重绘。
运行时数据通道(Runtime Data Delivery)
setRuntimeData(channel, payload)会先克隆并保留 payload,然后在 composition runtime 就绪后投递。非法 channel 与不可克隆的 payload 会同步抛错;调用返回之后的失败通过runtimedataerror事件上报,成功应用则触发runtimedataapplied。两个事件都携带{ channel, requestId },错误还附带message。当投递成败很关键时,应同时监听两个结果:
player.addEventListener("runtimedataapplied", ({ detail }) => { console.log("applied", detail.channel, detail.requestId); }); player.addEventListener("runtimedataerror", ({ detail }) => { console.error("not applied", detail.channel, detail.requestId, detail.message); }); player.setRuntimeData("captions", captionData);实现细节(hyperframes-player.ts):
- channel 必须匹配
/^[a-z][a-z0-9-]{0,63}$/,否则同步抛出Invalid HyperFrames runtime-data channel; - payload 通过
structuredClone深拷贝后保留,若环境不支持structuredClone则直接拒绝(防止未验证的 payload 进入运行时); - 每个 channel 的投递设有 10 秒超时(
RUNTIME_DATA_DELIVERY_TIMEOUT_MS = 10_000),超时、iframe 被销毁或桥接失败都会发出runtimedataerror,而不是无限挂起;同一 channel 只有最新的一次在途投递可以产生完成事件; - 投递优先走 iframe 内
window.__hyperframes暴露的直接桥接,其次回退到postMessage控制消息;当 runtime 在播放器已发出消息之后才注册监听时(如热缓存重载、Claude 桌面客户端),runtime 的ready信号会触发桥接状态重放,保证消息不丢失。
clearRuntimeData(channel)用于清除某个 channel 的保留数据并同步通知 runtime。
进阶:iframe 访问与沙箱模型
composition 运行在播放器 Shadow DOM 内一个沙箱化<iframe>中。默认沙箱包含allow-same-origin——这是为编辑器、录制器与自定义时间线集成准备的"可信内容"模式,而非隔离边界:同源 composition 代码可以触达嵌入页面。
对于只读或消息桥接型集成,请设置sandbox-origin="opaque"。任何非空值都会被当作 opaque 处理(拼写错误不会削弱隔离)。该属性变更会重新加载当前 composition,因为浏览器沙箱变更只在导航时生效。Opaque 模式会移除allow-same-origin但保留脚本能力,阻止 composition 读取无关的父 DOM;此时contentDocument、__player、__timelines的直接访问均不可用。
构建需要直接访问的可信编辑器集成时,使用iframeElementgetter:
const player = document.querySelector("hyperframes-player"); const iframe = player.iframeElement; // 现在可以深入 composition 的 DOM 与运行时 iframe.contentDocument.querySelectorAll("[data-composition-id]"); iframe.contentWindow.__timelines;这是把播放器桥接到@hyperframes/studio等工具的标准方式。studio 导出的resolveIframehelper 同时兼容 iframe ref 与 web-component ref:
import { useTimelinePlayer, resolveIframe } from "@hyperframes/studio"; const { iframeRef } = useTimelinePlayer(); const player = document.createElement("hyperframes-player"); player.setAttribute("src", src); container.appendChild(player); // 把内部 iframe 转发给 useTimelinePlayer 以驱动播放/暂停/seek。 iframeRef.current = resolveIframe(player);React:声明式 ref 模式
如果你偏好 JSX 而非命令式创建元素,可以把 ref 直接挂到 web component 上,再在 effect 中解析 iframe:
import "@hyperframes/player"; import type { HyperframesPlayer } from "@hyperframes/player"; import { useTimelinePlayer, resolveIframe } from "@hyperframes/studio"; function StudioPreview({ src }: { src: string }) { const { iframeRef, onIframeLoad } = useTimelinePlayer(); const playerRef = useRef<HyperframesPlayer>(null); useEffect(() => { iframeRef.current = resolveIframe(playerRef.current); }); return <hyperframes-player ref={playerRef} src={src} onLoad={onIframeLoad} />; }常见陷阱提醒
如果你把
<hyperframes-player>元素本身(而非iframeElement)传给一个期望<iframe>的 hook,每次.contentWindow/.contentDocument访问都会返回null,因为 iframe 位于播放器的 Shadow DOM 内部。务必先取出iframeElement,或使用能透明处理 iframe 与 web-component 两种宿主的resolveIframe。
事件(Events)
| 事件 | Detail | 触发时机 |
|---|---|---|
ready | { duration } | composition 加载完成且时长已确定 |
play | — | 播放开始 |
pause | — | 播放暂停 |
timeupdate | { currentTime } | 播放位置变化(约 10 fps) |
ended | — | 到达结尾(非循环模式) |
error | { message } | composition 加载失败 |
shadertransitionstate | { compositionId, state } | shader 转场缓存/捕获进度 |
player.addEventListener("ready", (e) => { console.log(`Duration: ${e.detail.duration}s`); }); player.addEventListener("ended", () => { console.log("Done!"); });除上述事件外,源码中还可能派发ratechange、volumechange、scenes、audioownershipchange、playbackerror、runtimeprotocolerror等事件(分别对应播放速率/音量变化、场景列表更新、音频所有权转移、代理播放失败与运行时协议版本不兼容),其中runtimeprotocolerror携带{ code, receivedVersion },用于检测 composition 内 runtime 协议版本与播放器不兼容的情况。
尺寸控制(Sizing)
播放器填满其容器,并在保持宽高比的前提下把 composition 缩放到适配。请在元素或其父容器上设置尺寸:
hyperframes-player { width: 100%; max-width: 800px; aspect-ratio: 16 / 9; }width和height属性定义的是 composition 原生分辨率,仅用于宽高比计算,不决定播放器显示尺寸。底层的缩放实现在 iframe-dom.ts:iframe 保持 composition 原生像素尺寸,通过transform: translate(-50%, -50%) scale(min(w/W, h/H))等比缩放;若播放器尚未获得绘制尺寸(0×0),缩放会安全地空操作,并在 ready 后仍无法缩放时输出一次带诊断信息的console.warn(避免隐藏/零尺寸播放器刷屏)。
工作原理(How It Works)
播放器在 Shadow DOM 内的沙箱<iframe>中渲染 composition,通过postMessage与 HyperFrames runtime 通信。iframe 初始沙箱为allow-scripts; allow-same-origin,allow="autoplay; fullscreen",referrerPolicy="no-referrer"(见 iframe-dom.ts)。
启动探测(Composition Probe)
iframe 加载完成后,CompositionProbe(composition-probe.ts)以 200ms 间隔轮询最多 40 次(约 8 秒),依次识别:
- runtime 桥接(
window.__hf或window.__player)——若存在且getDuration()返回正时长,立即就绪; - GSAP 时间线(
window.__timelines)——解析为"直接时间线适配器",由播放器直接驱动,无需 runtime; - 嵌套 composition(DOM 中存在
[data-composition-src]子元素)——此时必须立即注入 runtime,因为嵌套子场景本身依赖 runtime 懒加载。
Runtime 自动注入
如果 composition 只有 GSAP 时间线而没有 runtime,播放器会从 CDN 自动注入 runtime 脚本。注入时机由 shouldInjectRuntime.ts 决定:
- 嵌套 composition:立即注入。若等待,composition 内常见的"预览期内联
gsap.timeline"会以不完整时长注册到__timelines["main"],导致播放器被锁定在一个残缺时间线上; - 自包含 composition:先给直接时间线适配器 5 个轮询 tick 的宽限,若适配器未出现再注入 runtime 作为回退。
runtime CDN URL 由 runtime-url.ts 统一管理(https://cdn.jsdelivr.net/npm/@hyperframes/core@<version>/dist/hyperframe.runtime.iife.js),保证探测期的晚期注入与 srcdoc 解析期注入不会漂移到不同 URL。
postMessage 协议
iframe 内的 runtime 以source: "hf-preview"向父帧发送消息,消息处理器(runtime-message-handler.ts)只接受来自自身 iframe 的消息,并校验运行时协议版本。主要消息类型包括:
state:{ frame, isPlaying },更新播放位置与暂停状态;timeline:携带durationInFrames/durationSeconds/compositionWidth/compositionHeight/scenes,是跨源 readiness 信号(同源探测无法检查 CDN iframe);stage-size:更新 composition 尺寸(带有限性检查,防止 Infinity 把 iframe 缩到 0);ready:触发桥接状态重放(静音、音量、播放速率等);media-autoplay-blocked:触发父帧媒体代理接管(promoteToParentProxy);shader-transition-state:更新 shader 转场加载进度;runtime-data-applied/runtime-data-error:运行时数据投递的结果确认。
反过来,播放器向 iframe 发送source: "hf-parent"的控制消息(play、pause、seek、set-muted、set-volume、set-playback-rate、tick等)。在父帧可能被 Chromium 节流 iframe 自身 rAF 的场景(如 Electron / Claude 桌面深层嵌套跨源 iframe),播放器会启动一个父帧 RAF 时钟持续发送tick,确保动画持续推进。
分发格式(Distribution)
| 格式 | 文件 | 适用场景 |
|---|---|---|
| ESM | hyperframes-player.js | 打包器(Vite、webpack 等) |
| CJS | hyperframes-player.cjs | Node.js /require() |
| IIFE | hyperframes-player.global.js | <script>标签、CDN |
所有格式均压缩并附带 source map,包含 TypeScript 类型定义。
许可证
MIT。
如果你需要更进一步,可以继续阅读仓库内的 player 源码、浏览器沙箱测试 与 性能测试场景,或查看 studio 集成文档 了解如何把播放器桥接进完整的时间线编辑工作流。
【免费下载链接】hyperframesWrite HTML. Render video. Built for agents.项目地址: https://gitcode.com/GitHub_Trending/hy/hyperframes
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考