拆开 Remotion,看一个 props 如何把 React 组件逐帧变成 MP4
- 帧号作为 props 贯穿整个 React 组件树,让 React 生态的布局、动画、图表能力全部自动变成视频能力
- 渲染核心依赖 delayRender 就绪原语与双等待轮询循环,逐帧驱动无头 Chrome 截图,配合 CPU 核数一半的并发提升吞吐
- 合成管线由 Rust 进程承担,Node 通过 stdin/stdout 的 JSON 命令通信,编码落盘交给 ffmpeg-next
- 面向 Agent 时代的转型是实打实的基础设施,包含 11 个 Agent Skill、MCP 服务器和给机器阅读的 AGENTS.md
- License 为 source-available 而非开源,4 人以上公司需付费,逐帧渲染模式只适合离线批量出片不适合实时场景
2020 年 6 月,一个叫 Jonny Burger 的瑞士开发者往 GitHub 推了个新仓库,想法在当时看来相当离谱,视频不应该在时间轴上拖拖拽拽,应该用 React 组件写出来。六年过去,这个仓库攒到 59459 个 Star、4552 个 Fork,前几天我去翻它的 README,发现第一行的自我介绍已经从那句著名的 Make videos programmatically with React,换成了 Video tools for the agent era。
一家靠「用 React 写视频」起家的公司,现在把自己定位成 Agent 时代的视频工具。这个转向背后发生了什么,得从它的渲染管线一层层拆开看。项目地址在 Remotion,仓库大得吓人,浅克隆下来 14086 个文件,packages 目录下塞了 138 个子包。
帧是一个 props,整个 React 生态因此变成视频生态
先说它最核心的那个设计决策。
传统视频软件的世界观是时间轴和图层,你把素材拖到轨道上,调关键帧,预览,导出。Remotion 的世界观完全不同,一段视频就是一个 React 组件树,时间被抽象成一个整数 props,帧号。你写的每个组件都通过 useCurrentFrame() 拿到当前帧,剩下的交给 React 的响应式渲染。
import {useCurrentFrame} from 'remotion';
export const Title = () => {
const frame = useCurrentFrame();
return (
<h1 style={{opacity: frame / 30, transform: `translateY(${30 - frame}px)`}}>
你好,视频
</h1>
);
};
前 30 帧里,标题从下方 30 像素处浮上来,透明度从 0 渐变到 1。没有时间轴,没有关键帧面板,动画就是 frame 这个变量的函数。
我一直觉得这个抽象是 Remotion 全部魔法的起点。你想想看,React 生态里已有的东西,flex 布局、CSS 动画、SVG、Canvas、Three.js、数据请求、组件复用,在「帧即 props」的世界观下全部自动变成了视频能力。别人要给视频工具开发图表组件、地图组件、字幕组件,Remotion 什么都不用开发,浏览器能渲染的,视频里就能有。
播放器也是白捡的。同一个组件在浏览器里用 requestAnimationFrame 驱动就是实时预览,在服务器上被逐帧截图就是渲染导出。packages/player/src/calculate-next-frame.ts 里那个不到 50 行的函数负责把挂钟时间换算成帧号,处理快进、倒放、循环的边界,就这么撑起了一个可以嵌进任意网页的播放器组件。
不过说真的,预览和渲染之间隔着一条大河,浏览器里视频是连续播放的,导出时视频是一帧一帧离散截图的。跨过这条河,才是 Remotion 真正下功夫的地方。
逐帧截图的循环,比你想的讲究
渲染一段视频,Remotion 做的事可以缩成一句话,让无头 Chrome 按 30 fps 把组件的每一帧都摆好姿势,拍下来,再交给后面的合成器缝成 MP4。
听起来简单,难处在于「摆好姿势」这四个字。第 47 帧的画面可能依赖一张网络图片、一段字体、一个异步接口,你怎么知道 React 已经把它渲染完了?
Remotion 的答案是一个叫 delayRender 的原语,定义在 packages/core/src/delay-render.ts。任何异步操作开始前调 delayRender(),往 window.remotion_delayRenderHandles 里塞一个随机数句柄,同时把 window.remotion_renderReady 置为 false。异步完成后调 continueRender(handle) 移除句柄,当句柄数组清空,renderReady 回到 true。
渲染端则藏着一个轮询循环。packages/renderer/src/seek-to-frame.ts 里的 seekToFrame 函数,先用 waitForFunction 在页面里等 renderReady 变成 true,然后执行 window.remotion_setFrame(47) 把帧号推进去,接着再等一轮 ready,最后还要等 document.fonts.ready。双等待不是多余的,我翻到 packages/core/src/TimelineContext.tsx 里的实现,remotion_setFrame 自己内部也会先调一次 delayRender,因为切帧会触发新组件挂载、新资源加载,这些又是异步的。
一套截图循环的实际节奏大概是这样的。
翻译成人话,每一帧都要走完「等就绪、设帧号、再等就绪、等字体、截图」五步,任何一步卡住,超时机制就会介入。默认超时 30000 毫秒,代码里还会主动减掉 2000 毫秒留缓冲,超时信息会精确列出所有没清理的 delayRender 句柄和它们的 label。你调过一个 30 秒渲染失败、报错信息直接指着第几个异步任务没完成的场景吗,就是这套机制在兜底。
超时兜住了正确性,吞吐量靠并发。packages/renderer/src/get-concurrency.ts 里的 resolveConcurrency 给出默认值,CPU 核数的一半,上限 8。也就是一台 16 核机器,默认开 8 个浏览器标签页同时截图。
每两百毫秒切换一次前台标签页
并发一开,马上撞上 Chrome 的一个老毛病。
浏览器会给非前台标签页做定时器节流,你开 8 个标签页并行渲染,7 个在后台被 Chrome 限流,并发等于白开。Remotion 的解法朴实到我第一次看到时笑出了声,packages/renderer/src/cycle-browser-tabs.ts 里一个 70 行的函数,起个 200 毫秒的定时器,轮询着把每个标签页轮流 bringToFront() 一下。
谁也别想在后台躺平,雨露均沾,Chrome 的节流策略就这样被一轮「翻台」化解了。这种细节不会出现在任何官方宣传里,但它就是工程和 demo 的区别。
截图之后的另一半管线更有意思。14086 个文件里藏着一个 Rust 工程,packages/compositor 下的 Cargo.toml 写着包名 remotion-renderer,作者是 Jonny Burger 本人,依赖 ffmpeg-next、rayon-core 和一个他自己 fork 的 mp4-rust。这就是 Remotion 的合成器,负责把 PNG 帧序列、音轨、转场缝成最终的视频文件。
你可能会问,TypeScript 这边怎么跟 Rust 进程通信?packages/renderer/src/compositor/compositor.ts 给出了答案,spawn 一个常驻的合成器进程,往它的 stdin 里一行一行写 JSON 命令,每条命令带一个 nonce 作凭证,Rust 那边处理完从 stdout 吐回结果,Node 侧按 nonce 认领。命令面我数了一下,ExtractAudio、GetVideoMetadata、GetSilences、FreeUpMemory 加起来十来个,甚至还有一个 DeliberatePanic,专门用来测试崩溃恢复。
进程退出也有讲究,先往 stdin 写一个 EOF 通知 Rust 侧优雅收尾,同时挂一个 5 秒的定时器,到点还没退就直接 SIGKILL。不惯着,也不冤枉。
整个管线的全貌可以拼出来了。
React 组件由 bundler 打包后喂给无头 Chrome,截图循环逐帧出图,帧缓冲和音视频合成交给 Rust 进程,最后由 ffmpeg-next 编码落盘。顺带一提,驱动 Chrome 的那套 Puppeteer 不是 npm 装的,是整个 vendor 进 packages/renderer/src/browser/ 目录自己维护的 fork,Browser.ts、DOMWorld.ts、FrameManager.ts 一应俱全。render-media.ts 单文件 1151 行,管着这条管线上所有开关。
Agent 时代,仓库自己先变成了 Agent 友好的样子
现在可以回答开头那个问题了,为什么自我介绍改成了 Video tools for the agent era。
翻一遍仓库你会发现,agent 这条线不是营销话术,是实打实铺开的基础设施。packages/skills 里维护着 11 个 Agent Skill,remotion-create、remotion-render、remotion-captions、remotion-markup 各司其职,best-practices 那个充当路由器,版本号跟主仓库严格同步,都是 4.0.525。用户 npx skills add remotion-dev/skills 一条命令装进 Claude Code 或者 Codex,Agent 就拿到了一整套「怎么用 Remotion 干活」的操作手册。
还有 @remotion/mcp 包,一个 Model Context Protocol 服务器。还有 template-prompt-to-video 模板,CLI 一条命令调 OpenAI 生成故事脚本、ElevenLabs 生成配音,直接出一条 TikTok 风格的成片。
连仓库本身都在为 Agent 改造。根目录的 AGENTS.md 写着构建用 Bun、测试用 turbo、版本号在哪个文件里改,甚至规定了编码风格,「保持在单个函数内,除非逻辑可复用,不要预先提取只用一次的辅助函数」。这份给机器看的贡献指南,比大多数给人看的都直白。
其实吧,这个转向逻辑上完全自洽。视频制作的难点从来不是创意而是工程量,而工程量恰恰是 Agent 最擅长吞掉的。Remotion 用六年时间把视频制作变成写 React 代码,现在写代码这件事正在被 Agent 接管,那视频制作自然就被 Agent 接管了。React 是中间那层完美的胶水,比任何「自然语言直接生成视频」的尝试都多了一层可验证、可版本控制的结构。
Star 数遮住的那些事
聊完好的,按老规矩说说我挖到的另一面。
绕不开的是 license。Remotion 的 GitHub 仓库挂着 59459 个 Star,但它的 license 字段是 NOASSERTION,不是任何 OSI 认可的开源协议。LICENSE.md 写得清楚,个人、非营利组织、3 人及以下的营利公司免费商用,4 人及以上必须购买 Company License。5.0 版本的 masterplan issue 里作者亲口写了条变更,自由职业者也要计入公司人数。价格我查了下官网,创作者版 25 美元一席一个月,公司版按渲染量计费、每月最低消费 100 美元。协议还明确禁止拷贝或修改 Remotion 代码去做你自己的衍生品拿去卖。
坦白讲,我个人不反感这种 source-available 模式,v4.0.x 的补丁号已经滚到 525,近期 release 记录是 9 月 5 日、7 日、9 日、13 日、15 日各发一版,这个维护强度总得有人发工资。但你如果是在公司项目里引入,先确认团队规模和预算,别等法务找上门。README 里作者自己都加粗提醒了这一点,这份坦率我给好评,可比某些挂着开源旗号玩文字游戏的项目体面多了。
比 license 更隐蔽的,是渲染模式的先天成本。逐帧截图意味着每帧都要走一遍完整的 React 渲染加截图循环,30 秒 30 fps 的视频就是 900 次循环。动画简单时无头 Chrome 跑得飞快,组件树一旦重起来,渲染时间会实打实地涨。它天生适合离线批量出片,不是实时出片工具,想拿它做直播类的场景,方向就选错了。
还有个数字值得玩味。Rust 合成器的 Cargo.toml 作者栏就 Jonny Burger 一个人,核心贡献者高度集中。525 个补丁版本这个数字的另一面,是补丁洪流,使用方如果不锁版本,依赖升级的跟进会变成日常。
坑也有藏在模板里的。template-prompt-to-video 看着是免费的起点,实际依赖 OpenAI 和 ElevenLabs 的付费 API,跑起来每次都有真金白银的成本。issue 区还有些老伤口,比如 Lambda 私有存储桶的访问问题从 2023 年开到现在,Chrome 152 的升级计划处于 blocked 状态。都不致命,但选型前值得知道。
把领域变量提升为唯一的自变量
拆到这里,我觉得 Remotion 最值得带走的不是「用 React 写视频」这个结论,而是一个可迁移的设计模式。
我管它叫「单一自变量驱动渲染树」。把领域里最本质的那个变量,视频的时间、幻灯片的页码、PDF 的页、游戏的 tick,提升为整个渲染体系的唯一自变量,让它以 props 或者状态的形式流入组件树根部。做到这一步,你会白捡三样东西,整个既有组件生态自动变成领域生态,任意「时刻」可以离散化、可寻址地复现(第 47 帧就是一个纯函数调用),预览与生产共用同一份代码只是换驱动器。
Remotion 用 delayRender 这个就绪原语补上了最后一块拼图,让异步世界里的每个「摆好姿势」都有确定的完成信号,离散截图才敢放心地一帧帧拍下去。顺带还有个教训性的细节,cycle-browser-tabs 告诉我们,再漂亮的架构也会撞上平台层面的阴沟(Chrome 后台节流),而 200 毫秒翻一次台这种土办法,有时候就是最优解。
什么场景该用它?数据驱动的批量视频(千人千面的个性化短视频、财报可视化、代码讲解视频)、产品里要内嵌视频生成能力的 SaaS、想用 Agent 自动化出片的团队,闭眼选,这个领域它没有同量级的对手。什么场景别碰?一次性的手工剪辑、需要实时流式出片的场景、还有 4 人以上又不想掏 license 钱的公司。
讲真,个人玩家倒是完全没门槛,免费用,商业用途都行。装一个玩玩,你会发现「前端工程师会拍视频」这件事,可能比想象中近得多。

评论互动