Skip to main content

数据获取

在渲染之前获取数据v4.0.0

您可以使用<Composition />组件的calculateMetadata属性来修改传递给您的React组件的props。

何时使用

使用calculateMetadata()获取的数据必须是JSON可序列化的。这意味着它适用于API响应,但不适用于二进制格式的资产。

用法

传递一个回调函数,该函数接受未转换的props,并返回一个具有新props的对象。

src/Root.tsx
tsx
import { Composition } from "remotion";
 
type ApiResponse = {
title: string;
description: string;
};
type MyCompProps = {
id: string;
data: ApiResponse | null;
};
 
const MyComp: React.FC<MyCompProps> = () => null;
 
export const Root: React.FC = () => {
return (
<Composition
id="MyComp"
component={MyComp}
durationInFrames={300}
fps={30}
width={1920}
height={1080}
defaultProps={{
id: "1",
data: null,
}}
calculateMetadata={async ({ props }) => {
const data = await fetch(`https://example.com/api/${props.id}`);
const json = await data.json();
 
return {
props: {
...props,
data: json,
},
};
}}
/>
);
};
src/Root.tsx
tsx
import { Composition } from "remotion";
 
type ApiResponse = {
title: string;
description: string;
};
type MyCompProps = {
id: string;
data: ApiResponse | null;
};
 
const MyComp: React.FC<MyCompProps> = () => null;
 
export const Root: React.FC = () => {
return (
<Composition
id="MyComp"
component={MyComp}
durationInFrames={300}
fps={30}
width={1920}
height={1080}
defaultProps={{
id: "1",
data: null,
}}
calculateMetadata={async ({ props }) => {
const data = await fetch(`https://example.com/api/${props.id}`);
const json = await data.json();
 
return {
props: {
...props,
data: json,
},
};
}}
/>
);
};

传递给calculateMetadata()props输入props与默认props合并在一起的结果。
除了props之外,defaultProps也可以从同一对象中读取。

在转换时,输入和输出必须是相同的TypeScript类型。 考虑为您的数据使用可空类型,并在组件内部抛出错误以处理null类型:

MyComp.tsx
tsx
type MyCompProps = {
id: string;
data: ApiResponse | null;
};
 
const MyComp: React.FC<MyCompProps> = ({ data }) => {
if (data === null) {
throw new Error("Data was not fetched");
}
 
return <div>{data.title}</div>;
};
MyComp.tsx
tsx
type MyCompProps = {
id: string;
data: ApiResponse | null;
};
 
const MyComp: React.FC<MyCompProps> = ({ data }) => {
if (data === null) {
throw new Error("Data was not fetched");
}
 
return <div>{data.title}</div>;
};

TypeScript类型

您可以使用remotion中的CalculateMetadataFunction类型来为您的回调函数指定类型。将您的props的类型作为泛型值(<>)传递。

src/Root.tsx
tsx
import { CalculateMetadataFunction } from "remotion";
 
export const calculateMyCompMetadata: CalculateMetadataFunction<
MyCompProps
> = ({ props }) => {
return {
props: {
...props,
data: {
title: "Hello world",
description: "This is a description",
},
},
};
};
 
export const MyComp: React.FC<MyCompProps> = () => null;
src/Root.tsx
tsx
import { CalculateMetadataFunction } from "remotion";
 
export const calculateMyCompMetadata: CalculateMetadataFunction<
MyCompProps
> = ({ props }) => {
return {
props: {
...props,
data: {
title: "Hello world",
description: "This is a description",
},
},
};
};
 
export const MyComp: React.FC<MyCompProps> = () => null;

协同定位

以下是如何在同一文件中定义模式、组件和获取器函数的示例:

MyComp.tsx
tsx
import { CalculateMetadataFunction } from "remotion";
import { z } from "zod";
 
const apiResponse = z.object({ title: z.string(), description: z.string() });
 
export const myCompSchema = z.object({
id: z.string(),
data: z.nullable(apiResponse),
});
 
type Props = z.infer<typeof myCompSchema>;
 
export const calcMyCompMetadata: CalculateMetadataFunction<Props> = async ({
props,
}) => {
const data = await fetch(`https://example.com/api/${props.id}`);
const json = await data.json();
 
return {
props: {
...props,
data: json,
},
};
};
 
export const MyComp: React.FC<Props> = ({ data }) => {
if (data === null) {
throw new Error("Data was not fetched");
}
 
return <div>{data.title}</div>;
};
MyComp.tsx
tsx
import { CalculateMetadataFunction } from "remotion";
import { z } from "zod";
 
const apiResponse = z.object({ title: z.string(), description: z.string() });
 
export const myCompSchema = z.object({
id: z.string(),
data: z.nullable(apiResponse),
});
 
type Props = z.infer<typeof myCompSchema>;
 
export const calcMyCompMetadata: CalculateMetadataFunction<Props> = async ({
props,
}) => {
const data = await fetch(`https://example.com/api/${props.id}`);
const json = await data.json();
 
return {
props: {
...props,
data: json,
},
};
};
 
export const MyComp: React.FC<Props> = ({ data }) => {
if (data === null) {
throw new Error("Data was not fetched");
}
 
return <div>{data.title}</div>;
};
src/Root.tsx
tsx
import React from "react";
import { Composition } from "remotion";
import { MyComp, calcMyCompMetadata, myCompSchema } from "./MyComp";
 
export const Root = () => {
return (
<Composition
id="MyComp"
component={MyComp}
durationInFrames={300}
fps={30}
width={1920}
height={1080}
defaultProps={{
id: "1",
data: null,
}}
schema={myCompSchema}
calculateMetadata={calcMyCompMetadata}
/>
);
};
src/Root.tsx
tsx
import React from "react";
import { Composition } from "remotion";
import { MyComp, calcMyCompMetadata, myCompSchema } from "./MyComp";
 
export const Root = () => {
return (
<Composition
id="MyComp"
component={MyComp}
durationInFrames={300}
fps={30}
width={1920}
height={1080}
defaultProps={{
id: "1",
data: null,
}}
schema={myCompSchema}
calculateMetadata={calcMyCompMetadata}
/>
);
};

通过实现这种模式,props编辑器中的id现在可以进行调整,并且当数据更改时,Remotion将重新获取数据。

根据数据设置持续时间

您可以通过在回调函数中返回这些键来设置durationInFramesfpswidthheight

tsx
import { CalculateMetadataFunction } from "remotion";
 
type MyCompProps = {
durationInSeconds: number;
};
 
export const calculateMyCompMetadata: CalculateMetadataFunction<
MyCompProps
> = ({ props }) => {
const fps = 30;
const durationInSeconds = props.durationInSeconds;
 
return {
durationInFrames: durationInSeconds * fps,
fps,
};
};
tsx
import { CalculateMetadataFunction } from "remotion";
 
type MyCompProps = {
durationInSeconds: number;
};
 
export const calculateMyCompMetadata: CalculateMetadataFunction<
MyCompProps
> = ({ props }) => {
const fps = 30;
const durationInSeconds = props.durationInSeconds;
 
return {
durationInFrames: durationInSeconds * fps,
fps,
};
};

变量元数据页面了解更多关于此功能的信息。

中止过时请求

在属性编辑器中,属性可能会快速变化,例如快速输入。
最佳实践是使用传递给calculateMetadata()函数的abortSignal来取消过时的请求:

src/MyComp.tsx
tsx
export const calculateMyCompMetadata: CalculateMetadataFunction<
MyCompProps
> = async ({ props, abortSignal }) => {
const data = await fetch(`https://example.com/api/${props.id}`, {
signal: abortSignal,
});
const json = await data.json();
 
return {
props: {
...props,
data: json,
},
};
};
 
export const MyComp: React.FC<MyCompProps> = () => null;
src/MyComp.tsx
tsx
export const calculateMyCompMetadata: CalculateMetadataFunction<
MyCompProps
> = async ({ props, abortSignal }) => {
const data = await fetch(`https://example.com/api/${props.id}`, {
signal: abortSignal,
});
const json = await data.json();
 
return {
props: {
...props,
data: json,
},
};
};
 
export const MyComp: React.FC<MyCompProps> = () => null;

这个abortSignal是由 Remotion 使用AbortController API 创建的。

防抖请求

如果您正在向一个昂贵的 API 发送请求,您可能希望只在用户停止输入一段时间后才发出请求。您可以使用以下函数来实现:

src/wait-for-no-input.ts
tsx
import { getRemotionEnvironment } from "remotion";
export const waitForNoInput = (signal: AbortSignal, ms: number) => {
// Don't wait during rendering
if (getRemotionEnvironment().isRendering) {
return Promise.resolve();
}
 
if (signal.aborted) {
return Promise.reject(new Error("stale"));
}
 
return Promise.race<void>([
new Promise<void>((_, reject) => {
signal.addEventListener("abort", () => {
reject(new Error("stale"));
});
}),
new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, ms);
}),
]);
};
src/wait-for-no-input.ts
tsx
import { getRemotionEnvironment } from "remotion";
export const waitForNoInput = (signal: AbortSignal, ms: number) => {
// Don't wait during rendering
if (getRemotionEnvironment().isRendering) {
return Promise.resolve();
}
 
if (signal.aborted) {
return Promise.reject(new Error("stale"));
}
 
return Promise.race<void>([
new Promise<void>((_, reject) => {
signal.addEventListener("abort", () => {
reject(new Error("stale"));
});
}),
new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, ms);
}),
]);
};
src/MyComp.tsx
tsx
export const calculateMyCompMetadata: CalculateMetadataFunction<
MyCompProps
> = async ({ props, abortSignal }) => {
await waitForNoInput(abortSignal, 750);
const data = await fetch(`https://example.com/api/${props.id}`, {
signal: abortSignal,
});
const json = await data.json();
 
return {
props: {
...props,
data: json,
},
};
};
 
export const MyComp: React.FC<MyCompProps> = () => null;
src/MyComp.tsx
tsx
export const calculateMyCompMetadata: CalculateMetadataFunction<
MyCompProps
> = async ({ props, abortSignal }) => {
await waitForNoInput(abortSignal, 750);
const data = await fetch(`https://example.com/api/${props.id}`, {
signal: abortSignal,
});
const json = await data.json();
 
return {
props: {
...props,
data: json,
},
};
};
 
export const MyComp: React.FC<MyCompProps> = () => null;

时间限制

当 Remotion 调用calculateMetadata()函数时,它会将其包装在一个默认超时时间为 30 秒的delayRender()中。

在渲染过程中获取数据

使用delayRender()continueRender(),您可以告诉 Remotion 在渲染帧之前等待异步操作完成。

何时使用

使用这种方法来加载不是 JSON 可序列化的资源,或者如果您使用的 Remotion 版本低于 4.0。

用法

尽早调用delayRender(),例如在组件内部初始化状态时。

tsx
import { useCallback, useEffect, useState } from "react";
import { cancelRender, continueRender, delayRender } from "remotion";
 
export const MyComp = () => {
const [data, setData] = useState(null);
const [handle] = useState(() => delayRender());
 
const fetchData = useCallback(async () => {
try {
const response = await fetch("http://example.com/api");
const json = await response.json();
setData(json);
 
continueRender(handle);
} catch (err) {
cancelRender(err);
}
}, [handle]);
 
useEffect(() => {
fetchData();
}, [fetchData]);
 
return (
<div>
{data ? (
<div>This video has data from an API! {JSON.stringify(data)}</div>
) : null}
</div>
);
};
tsx
import { useCallback, useEffect, useState } from "react";
import { cancelRender, continueRender, delayRender } from "remotion";
 
export const MyComp = () => {
const [data, setData] = useState(null);
const [handle] = useState(() => delayRender());
 
const fetchData = useCallback(async () => {
try {
const response = await fetch("http://example.com/api");
const json = await response.json();
setData(json);
 
continueRender(handle);
} catch (err) {
cancelRender(err);
}
}, [handle]);
 
useEffect(() => {
fetchData();
}, [fetchData]);
 
return (
<div>
{data ? (
<div>This video has data from an API! {JSON.stringify(data)}</div>
) : null}
</div>
);
};

一旦数据被获取,您可以调用continueRender()告诉 Remotion 继续渲染视频。
如果数据获取失败,您可以调用cancelRender()取消渲染而不必等待超时。

时间限制

您需要在页面打开后的 30 秒内清除由delayRender()创建的所有句柄。您可以增加超时时间。```

防止过度获取

在渲染过程中,会打开多个无头浏览器选项卡以加快渲染速度。
在 Remotion Lambda 中,渲染并发性 可以高达 200 倍。
这意味着如果您在组件内部获取数据,数据获取将会执行多次。

如果可能的话,最好在渲染之前获取数据。
2
确保您有高请求速率的权限,而不会遇到速率限制。
3
API 返回的数据在所有线程上必须相同,否则可能会发生闪烁
4
确保 frame 不是 useEffect() 的依赖项,直接或间接地,否则数据将在每帧中获取,导致减速并有可能遇到速率限制。

另请参阅