Remotion 视频嵌入与处理:在 React 合成中使用 @remotion/media 实现裁剪、变速、循环与变调
导读
本文围绕 Remotion 官方技能文档(packages/skills/skills/remotion-markup/embedding-videos.md)展开,系统讲解如何通过 @remotion/media 包中的 <Video> 组件,在 React 合成中嵌入本地与远程视频,并完成时间裁剪(trim)、延迟出场(Sequence)、尺寸定位、音量控制、变速播放、无限循环以及不改变语速的变调(pitch)处理。读完本文后,你将掌握以帧为粒度控制视频媒体行为的完整 API 用法,并了解这些 props 在 Remotion 源码中的校验逻辑与底层实现路径,可直接应用到剪辑类或数据可视化类视频项目的搭建中。
@remotion/media 是什么,何时使用它
@remotion/media 是 Remotion 生态中的一个实验性媒体标签包,其核心导出即 <Audio> 与 <Video> 组件(见 packages/media/src/index.ts)。在 packages/media/package.json 中,该包被描述为 "Experimental WebCodecs-based media tags",当前版本为 4.0.521,依赖 remotion、mediabunny 与 zod。
与传统 <OffthreadVideo> / HTML5 <Video> 相比,它基于 WebCodecs 做解码与帧调度,为精准的逐帧编辑、loop 播放、变速与循环裁剪提供了更可控的时间模型。包内目录结构也印证了这一点:src/video/ 下同时存在 video-for-preview.tsx 与 video-for-rendering.tsx,分别承担预览与最终渲染两条不同的执行路径。
安装与版本对齐
使用 <Video> 前必须先安装 @remotion/media。文档给出了四种常见包管理器的安装命令,可任选其一:
npx remotion add @remotion/media # 项目使用 npm
bunx remotion add @remotion/media # 项目使用 bun
yarn remotion add @remotion/media # 项目使用 yarn
pnpm exec remotion add @remotion/media # 项目使用 pnpm
安装时需要注意两个工程约束(来自 packages/media/package.json):
- 保持
remotion与所有@remotion/*包的版本一致,避免版本漂移导致的运行时冲突; - 建议使用精确版本号(去掉
^),例如npm install @remotion/media --save-exact。
基本用法:本地文件与远程 URL
从 @remotion/media 导入 <Video>,用 src 指定来源即可把视频嵌入合成。配合 staticFile() 可以引用 public/ 目录下的静态资源:
import { Video } from "@remotion/media";
import { staticFile } from "remotion";
export const MyComposition = () => {
return <Video src={staticFile("video.mp4")} />;
};
远程 URL 同样受支持,@remotion/media 组件会通过网络加载媒体流:
<Video src="https://remotion.media/video.mp4" />
从源码看,src 是 VideoProps 中的必填属性,类型为 string;若传入非字符串会直接抛出类型错误(见 packages/media/src/video/video.tsx)。audioStreamIndex 默认取 0,即默认使用第一个音频轨道。
预览与渲染双路径:组件如何工作
<Video> 实际是 Interactive.withSchema() 包装后的组件(见 packages/media/src/video/video.tsx),内部通过 useRemotionEnvironment() 判断当前所处环境:
- 处于服务端渲染(
environment.isRendering === true)时,走VideoForRendering,由离屏(Offthread)渲染管线逐帧输出; - 处于预览/交互环境时,走
VideoForPreview,依赖浏览器的媒体播放与迭代器同步(相关实现位于 video-preview-iterator.ts)。
理解这条分叉很重要:它解释了后文提到的 变调(pitch)仅在渲染阶段生效,也解释了为什么预览与最终成片对 loop、trim 后的音量曲线表现一致——两组 props 会被同一份校验逻辑处理后再分发到两条路径。
裁剪视频:trimBefore 与 trimAfter
使用 trimBefore 与 trimAfter 可以裁掉视频首尾的片段。注意:这两个值的单位是"帧"而不是秒,因此通常结合 useVideoConfig() 取出的 fps 换算:
const { fps } = useVideoConfig();
return (
<Video
src={staticFile("video.mp4")}
trimBefore={2 * fps} // 跳过前 2 秒
trimAfter={10 * fps} // 在第 10 秒处结束
/>
);
校验规则定义在 packages/core/src/validate-start-from-props.ts,Video 在 packages/media/src/video/video.tsx 中调用 validateMediaTrimProps 与 resolveTrimProps 完成统一校验,违反以下任一条件都会抛错:
trimBefore必须是非负的有限数值(不能为NaN、Infinity,不能小于 0);trimAfter必须是大于 0 的数值;trimAfter必须严格大于trimBefore;- 不能同时使用已废弃的
startFrom/endAt与trimBefore/trimAfter。
此外,源码还提供了 getVideoSequenceDuration()(见 get-video-sequence-duration.ts)来计算裁剪与变速后的有效时长,包内测试 loop-trim-time-calculation.test.ts、trim-change-seek.test.ts 对 trim 与 loop 叠加后的时间计算做了大量回归验证。
延迟出现:用 <Sequence> 包装
让视频延后数秒再出现,只需把它放进 <Sequence> 并设置 from。例如从第 1 秒开始播放:
import { Sequence, staticFile } from "remotion";
import { Video } from "@remotion/media";
const { fps } = useVideoConfig();
return (
<Sequence from={1 * fps}>
<Video src={staticFile("video.mp4")} />
</Sequence>
);
<Sequence> 会平移其内部所有内容的时间起点,因此该视频相当于从第 1 * fps 帧才开始可见。结合文档《Remotion 视频剪辑思路》中的建议,在多片段剪辑场景里每个 <Video> 都应保持独立的 JSX 节点,直接传入自己的 from、trimBefore 与 durationInFrames,而不是用 .map() 批量生成,这样才便于在 Studio 时间轴中进行独立的编辑操作。
尺寸与位置控制:style 与 objectFit
<Video> 透传标准 React style,可精确控制尺寸与绝对定位:
<Video
src={staticFile("video.mp4")}
style={{
width: 500,
height: 300,
position: "absolute",
top: 100,
left: 50,
}}
objectFit="cover"
/>
objectFit 的可选值与 CSS object-fit 对齐,定义在 packages/media/src/video/props.ts:
| 取值 | 行为 |
|---|---|
fill |
拉伸填满容器,可能变形 |
contain |
完整显示,保持宽高比(默认值) |
cover |
铺满容器并裁掉超出部分 |
none |
保持原始尺寸 |
scale-down |
取 none 与 contain 中较小的结果 |
源码中 objectFit 的默认值为 'contain'(见 packages/media/src/video/video.tsx),如果设成 cover 而容器没有按视频宽高比设计,超出部分会被裁掉——这是预览与成片都遵循的一致行为。
音量控制:静态值、动态回调与静音
设置固定音量(0 到 1):
<Video src={staticFile("video.mp4")} volume={0.5} />
也可以传回调函数,按当前帧号动态计算音量。配合 interpolate 即可实现淡入淡出:
import { interpolate } from "remotion";
const { fps } = useVideoConfig();
return (
<Video
src={staticFile("video.mp4")}
volume={(f) =>
interpolate(f, [0, 1 * fps], [0, 1], { extrapolateRight: "clamp" })
}
/>
);
上面例子让音量在第 1 秒内从 0 线性过渡到 1。volume 的类型是 VolumeProp(即固定数字或 (frame: number) => number)。
彻底静音用 muted:
<Video src={staticFile("video.mp4")} muted />
两点补充说明(来自 video.tsx 中的 schema 定义与默认值):
volume默认值为1,schema 中标记的范围是0到20(步进0.01),但常规做法仍建议保持在0~1区间;- 配合
muted时底层仍会保留对音量状态的管理,便于在 Studio 时间轴中展示媒体轨道的音量信息。
播放速度:playbackRate
playbackRate 直接控制播放倍率,无需修改 fps 或 durationInFrames 即可整体变速:
// 2 倍速
<Video src={staticFile("video.mp4")} playbackRate={2} />
// 半速(慢动作)
<Video src={staticFile("video.mp4")} playbackRate={0.5} />
相关约束与实现事实:
- 校验在
validateMediaProps中执行,schema 规定playbackRate最小值0.1、步进0.01、默认1,且不可逐帧关键帧化(keyframable: false); - 反向播放不受支持,即
playbackRate不能为负数; - 变速会联动影响
trimBefore/trimAfter/loop组合下的总时长,getVideoSequenceDuration会把这些参数统一折算; - 包内
html5-playback-rate.test.tsx对该行为做了专门验证。
无限循环:loop 与 loopVolumeCurveBehavior
使用 loop 让视频无限循环:
<Video src={staticFile("video.mp4")} loop />
循环时若还想用基于帧号的音量回调做"跨多轮循环"的包络(如整体淡出),必须指定 loopVolumeCurveBehavior 以控制循环期间帧号如何递增:
"repeat":每轮循环都把帧号重置回 0(volume回调在每个循环周期内重新播放曲线),这是默认值;"extend":帧号跨循环持续递增,音量曲线可跨越多轮循环计算。
<Video
src={staticFile("video.mp4")}
loop
loopVolumeCurveBehavior="extend"
volume={(f) => interpolate(f, [0, 300], [1, 0])} // 跨多轮循环逐渐淡出
/>
这个例子中 loopVolumeCurveBehavior="extend" 让 f 从 0 一路累加,volume 曲线在 300 帧内从 1 衰减到 0,实现跨越多次循环的整体淡出。若改用默认的 "repeat",音量会在每轮循环内反复重置。loopVolumeCurveBehavior 的默认值与 loop 的默认值(false)都定义在 packages/media/src/video/video.tsx,对应测试见 looping.test.ts、looping-audio-timestamps.test.ts、player-loop-frame-accuracy.test.tsx(后者专门校验 player 中 loop + trim 的逐帧精度)。
变调(不改变语速):toneFrequency
toneFrequency 用来在不改变播放速度的前提下调整音调,取值范围为 0.01 到 2,1 表示原始音调,小于 1 变低沉、大于 1 变尖细:
<Video
src={staticFile("video.mp4")}
toneFrequency={1.5} // 提高音调
/>
<Video
src={staticFile("video.mp4")}
toneFrequency={0.8} // 降低音调
/>
校验逻辑位于 packages/media/src/validate-tone-frequency.ts:toneFrequency 必须是介于 0.01 与 2 之间的有限数值,否则 <Video>(或 <Audio>)会抛出类型错误。相关实现由 pitch-shift.ts 完成,包内 pitch-shift.test.ts、pitch-shift-preview.test.tsx 分别覆盖了离线处理与预览路径。
关键限制:变调仅在服务端渲染阶段生效(对应 VideoForRendering 路径),在 Remotion Studio 实时预览或 <Player /> 中不会生效。这正是前文"渲染/预览双路径"设计带来的行为差异,排片时若发现预览与成片音调不一致,属预期行为而非 bug。
Props 速查总表
综合本文档与 props.ts / video.tsx,核心 props 一览:
| Prop | 类型 / 默认值 | 说明 |
|---|---|---|
src |
string(必填) |
视频源:staticFile() 本地路径或远程 URL |
trimBefore |
number,默认 undefined |
从开头裁掉的帧数(单位:帧) |
trimAfter |
number,默认 undefined |
在此时刻结束(单位:帧) |
volume |
number | (f) => number,默认 1 |
静态或随帧变化的音量 |
muted |
boolean,默认 false |
完全静音 |
playbackRate |
number,默认 1 |
播放倍率,最小 0.1,不支持反向 |
loop |
boolean,默认 false |
是否无限循环 |
loopVolumeCurveBehavior |
"repeat" | "extend",默认 "repeat" |
循环时帧号是否跨轮累计 |
toneFrequency |
number,默认 1,范围 0.01~2 |
变调倍率,仅渲染阶段生效 |
objectFit |
'fill'|'contain'|'cover'|'none'|'scale-down',默认 'contain' |
画面适配方式 |
style |
React.CSSProperties |
尺寸、位置、透明度等样式 |
audioStreamIndex |
number,默认 0 |
选择视频内的音频轨道 |
关联文档与源码延伸
- 技能根文档:SKILL.md;
- 多片段剪辑思路:video-editing.md,可结合本文的
trimBefore与<Sequence>组织多条视频轨; - 包入口与类型导出:packages/media/src/index.ts、packages/media/src/video/props.ts;
- 组件实现与默认值:packages/media/src/video/video.tsx;
- 校验逻辑:packages/core/src/validate-start-from-props.ts、packages/media/src/validate-tone-frequency.ts;
- 行为回归测试:looping.test.ts、loop-trim-time-calculation.test.ts、pitch-shift.test.ts、html5-playback-rate.test.tsx。
掌握以上 API 后,你已经可以在 Remotion 中实现剪辑软件级别的视频时间轴:逐帧裁剪素材、控制出场时机、叠加音量和淡入淡出、变速与循环,甚至在渲染阶段独立调整音调——所有行为都保持"预览所见即成片所得"(除变调这一明确标注的例外)。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00