Remotion @remotion/player 本地测试床实战:从 bun 启动命令到 Player API 全量演练
【免费下载链接】remotion🎥 Make videos programmatically with React项目地址: https://gitcode.com/GitHub_Trending/re/remotion
本篇技术文章以仓库中 SKILL.md 定义的工作流为主线,讲解如何在 Remotion 单体仓库中准备环境、启动 packages/player-example 的@remotion/player示例应用并在浏览器中打开http://localhost:3000。读完本文,你将掌握该测试床的完整启动步骤、页面路由结构,以及测试床内部对Player组件 props、PlayerRef方法、事件系统与自定义控件/缩略图 API 的全量演练方式。
这个技能文档定义了怎样的工作流
SKILL.md 是为 Agent 编写的操作技能(skill),其用途描述为:“启动@remotion/player示例应用,并在 Codex 浏览器中打开它”。文档给出了一条五步工作流:
- 在仓库根目录执行
bun i && bun run build,安装依赖并构建整个 monorepo; - 进入示例包目录启动开发服务器:
cd packages/player-example && bun run dev; - 保持服务器进程运行,并读取其输出中的本地 URL。文档指出默认预期地址是
http://localhost:3000,但若 Next.js 选择了其他端口,应以终端实际打印的 URL 为准; - 在内置浏览器中打开该 URL;
- 向用户告知 player example 的访问地址,并说明这是新启动的服务器还是此前已在运行的服务器。
这个流程看似简单,但它触及了 Remotion monorepo 的两个关键机制:示例包依赖workspace:*内部包(必须先构建),以及 Next.js 开发服务器的动态端口行为。下面结合仓库源码逐一展开。
环境准备:为什么必须先 bun i && bun run build
技能文档第一步要求先执行:
bun i && bun run build原因在于 packages/player-example/package.json 的依赖结构:@remotion/player、remotion、@remotion/bundler、@remotion/canvas、@remotion/google-fonts、@remotion/preload、@remotion/gif、@remotion/media等核心包全部以workspace:*的形式引用,即指向 monorepo 内的本地源码包(如 packages/player)。这些包的产物需要先通过 monorepo 根目录的构建流程生成。
查看仓库根目录 package.json 可以确认对应关系:
"build": "turbo run make --no-update-notifier"——bun run build实际是通过 Turbo 对各个包执行make任务;- 根脚本还提供了更细粒度的变体,例如
"makeplayer": "turbo run make --filter='@remotion/player'"和"watchplayer": "turbo watch make --filter='@remotion/player'",前者只构建 Player 包,后者在开发 Player 包本身时可用 watch 模式持续重建; - 仓库声明了
"packageManager": "bun@1.3.3",说明官方工具链是 Bun,这也是技能文档使用bun i而非npm install的原因; - 依赖版本通过
workspaces.catalog统一管理,其中react为19.2.3、next为16.2.11——这解释了示例包中catalog:版本号的实际取值。
因此“先构建、再启动”的顺序是硬约束:跳过bun run build时,workspace:*依赖包没有构建产物,next dev会因解析不到产物而失败。
启动 player-example:dev 脚本与端口约定
第二步:
cd packages/player-example && bun run dev对应 package.json 中的三个脚本:
| 脚本 | 命令 | 用途 |
|---|---|---|
dev | next dev | 启动 Next.js 开发服务器,日常调试使用 |
build-site | next build | 构建静态站点 |
testnextjs | next build | 被根目录turbo run testnextjs调用的 CI 校验任务,验证该示例能在 Next.js 构建中通过 |
技能文档特别强调“预期默认是http://localhost:3000,但要以打印出的 URL 为准”——这是 Next.js 的标准行为:当 3000 端口被占用时,next dev会自动顺延到 3001、3002 等端口,并在终端输出Ready on http://localhost:300x。文档同时要求保持该进程持续运行(dev 服务器是前台进程,不能像一次性构建那样结束),并区分“新启动”与“已在运行”两种状态,避免对同一个示例端口重复起服务。
页面路由:一个多场景的 Player 演示站
启动后打开首页,会看到 pages/index.tsx 渲染的路由索引页,列出了 8 个聚焦场景,每个都是pages/目录下的一个独立页面:
| 路由 | 页面文件 | 演示内容 |
|---|---|---|
/player | pages/player.tsx | 主测试床:带自定义控件面板的 Player 与 Thumbnail |
/canvas | pages/canvas.tsx | 挂载中的时间轴层实时列表 |
/audio | pages/audio.tsx | 在含音频的合成之间切换 |
/audio-switching | pages/audio-switching.tsx | 切换合成与预取的音频源 |
/autoplay-muted-video | pages/autoplay-muted-video.tsx | 静音自动播放视频 |
/autoplay-unmuted-video | pages/autoplay-unmuted-video.tsx | 演练浏览器自动播放拦截与回退行为 |
/fullscreen | pages/fullscreen.tsx | 占满浏览器视口的 Player |
/video-ssr | pages/video-ssr.tsx | 通过 Next.js 服务端渲染输出视频 Player |
/issue-7183 | pages/issue-7183.tsx | Player 在 3D 变换父元素内的测量问题复现 |
这个路由组织方式本身值得借鉴:每个页面验证一个特定的 Player 边界行为(自动播放策略、SSR 兼容、3D 变换下的尺寸测量、音频切换),而不是把所有功能堆在一个页面里。全局样式方面,pages/_app.tsx 仅引入了一份 fullscreen.css 用于全屏场景。
主测试床 App.tsx:PlayerRef 方法与事件系统全量演练
/player页面是核心,它渲染 src/App.tsx 导出的App组件,并传入合成组件CarSlideshow与durationInFrames={500}。App由两部分组成:PlayerOnly(负责渲染<Player>)和ControlsOnly(负责一整套交互按钮)。这两部分合在一起,实际上就是一份@remotion/playerAPI 的可执行清单。
Player 组件的核心 props
PlayerOnly中的<Player>用法(App.tsx 第 648–690 行附近)覆盖了这些关键 props:
<Player ref={playerRef} controls acknowledgeRemotionLicense compositionWidth={1920} compositionHeight={1080} fps={30} component={CarSlideshow} // 或 lazyComponent 懒加载形式 durationInFrames={500} loop={loop} clickToPlay={clickToPlay} doubleClickToFullscreen={doubleClickToFullscreen} spaceKeyToPlayOrPause={spaceKeyToPlayOrPause} moveToBeginningWhenEnded={moveToBeginningWhenEnded} playbackRate={playbackRate} inputProps={inputProps} initialFrame={30} showPosterWhenUnplayed={...} showPosterWhenBuffering={...} showPosterWhenBufferingAndPaused={...} showPosterWhenEnded={...} showPosterWhenPaused={...} inFrame={inFrame} outFrame={outFrame} alwaysShowControls={alwaysShowControls} showVolumeControls={showVolumeControls} showPlaybackRateControl={showPlaybackRateControl} hideControlsWhenPointerDoesntMove={...} renderLoading={renderLoading} renderPoster={renderPoster} errorFallback={errorFallback} />几个值得注意的实现细节:
- 合成组件支持两种注入方式。
CompProps类型定义了component(直接传入)或lazyComponent(返回Promise<{default: Component}>)二选一,测试床两种模式都可通过不同页面复用同一个App; inputProps是响应式的。标题、文字颜色、背景色来自 React state,通过useMemo组装后传给 Player,修改输入框会实时驱动合成内容变化——这正是@remotion/player与渲染服务最大的差异:合成是“活的”;renderLoading/renderPoster/errorFallback三个自定义回调分别演示了加载态(黄色底 +Loading动画,文案 “Loading for 3 seconds...”)、海报态(区分isBuffering显示 “Buffering” 或 “Click to play”)和错误兜底(Sorry about this! An error occurred: {error.message});inFrame/outFrame用于裁剪播放区间,界面上通过两个可启用的滑块(取值范围0到durationInFrames)演示如何限制 Player 只播放某一段;- Player 的
style设置了resize: 'both'、maxWidth/maxHeight: 550、minWidth/minHeight: 300,让用户可以直接拖拽调整 Player 尺寸来验证响应式缩放。
PlayerRef 命令式方法
ControlsOnly中通过useRef<PlayerRef>(null)拿到实例后,演练了这些命令式 API:
| 方法 | 界面对应按钮 |
|---|---|
play(e) | ▶️ Play(传入事件对象以支持自动播放策略) |
pause() | ⏸️ Pause |
toggle() | ⏯️ Toggle |
seekTo(frame) | seekTo 10 and pause / seekTo 50 / 5 seconds forward(seekTo(getCurrentFrame() + fps * 5)) |
| 越界 seek | seekTo(10000)与seekTo(-10000),验证边界外帧号的处理 |
mute()/unmute()/setVolume(v) | 静音、取消静音、音量 0 / 0.5 / 1 |
getCurrentFrame()/isMuted()/getVolume() | 写入日志面板的只读查询 |
inputProps相关的三个属性(title、color、bgColor)通过顶部的文本输入框和两个<input type="color">颜色选择器实时修改,演示了 Player 对 props 变更的热更新能力。
事件系统:13 种 Player 事件
ControlsOnly的useEffect中注册了完整的事件监听清单,每个事件触发时都向日志面板追加一行带时间戳的记录,并在组件卸载时逐一removeEventListener清理:
play、pause、seeked(含e.detail.frame)、ended、error、timeupdate(e.detail.frame)、frameupdate(e.detail.frame)、ratechange(e.detail.playbackRate)、scalechange(e.detail.scale)、volumechange(e.detail.volume)、mutechange(e.detail.isMuted)、fullscreenchange(e.detail.isFullscreen)、waiting、resume。
日志面板只保留最近 10 条(logs.slice(0).reverse().slice(0, 10).reverse())。这种“事件 → 日志”的映射方式是调试 Player 时序问题的有效模板:你能直观看到一次 seek 会先触发哪个事件、暂停期间timeupdate是否还在派发等。
合成主体 CarSlideshow 与自定义控件
CarSlideshow:被播放器驱动的合成
/player页面传入的合成为 src/CarSlideshow.tsx。它展示了 Player 与 Remotion 核心 API 的配合:
- 通过
useCurrentFrame()与useVideoConfig()读取当前帧和{width, height, durationInFrames},再用interpolate(frame, [0, durationInFrames], [width, width * -1])让标题文字从右向左横穿画面; staticFile('/logo.png')加载静态资源(对应 public/logo.png);- 第 10 帧起通过
<Sequence from={10}>挂载一段远程<Html5Video>视频源,用于在 Player 中验证视频元素的播放行为; - 一个隐藏的“错误触发器”:模块级
playerExampleCompref 通过useImperativeHandle暴露triggerError(),调用后合成内部抛出Error('some error')——这正是主测试床上 “trigger error” 按钮的后端,用来验证errorFallback兜底 UI 是否生效。
CustomControlsExample:renderCustomControls 的位置
src/CustomControlsExample.tsx 演示了renderCustomControlsprop:它接收PlayerRef,监听play/pause事件维护按钮文案(Custom Play / Custom Pause),点击时调用toggle()。页面注释说明了该自定义控件的渲染位置——位于主控件区与全屏按钮之间,适合放置“重播”“字幕开关”等业务按钮。
Thumbnail:静态帧的另一面
pages/player.tsx 页面还渲染了 src/ThumbnailDemo.tsx 以及一个直接使用<Thumbnail>的实例:
<Thumbnail component={CarSlideshow} frameToDisplay={480} compositionWidth={500} compositionHeight={200} durationInFrames={5000} fps={30} inputProps={{title: 'Hi there', bgColor: 'black', color: 'white'}} style={{border: '4px solid red'}} />Thumbnail与Player共享合成组件和inputProps契约,但只渲染frameToDisplay指定的静态帧——适合作为视频封面、编辑器时间轴缩略图。同一页面还包含 src/FontPicker.tsx 提供的字体选择器,用于验证不同字体在 Player 中的加载表现。
实践要点小结
结合 SKILL.md 的工作流与源码结构,本地演练@remotion/player的正确姿势可以归纳为:
- 在仓库根目录执行
bun i && bun run build(或先用bun run makeplayer只构建 Player 包以节省时间); cd packages/player-example && bun run dev,以终端打印的实际地址(默认http://localhost:3000,端口冲突时会顺延)为准;- 从首页路由索引进入
/player主测试床,用按钮面板逐项验证Playerprops 的热更新、PlayerRef命令式方法与 13 种事件; - 需要验证特定边界行为时,切换到
/fullscreen、/video-ssr、/autoplay-unmuted-video等专项页面; - 该包为内部示例(README.md 明确标注 “internal package and has no documentation”),其价值不在于对外文档,而在于作为 Player API 变更时的可执行回归测试床——根脚本
testnextjs还会通过next build保证它在 Next.js 16 生产构建下始终可用。
【免费下载链接】remotion🎥 Make videos programmatically with React项目地址: https://gitcode.com/GitHub_Trending/re/remotion
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考