Skip to main content

数据获取

在 Remotion 中,你可以从 API 获取数据以在视频中使用。在此页面,我们记录了使用技巧和最佳实践。

🌐 In Remotion, you may fetch data from an API to use it in your video. On this page, we document recipes and best practices.

在渲染前获取数据v4.0.0

🌐 Fetching data before the renderv4.0.0

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

🌐 You may use the calculateMetadata prop of the <Composition /> component to alter the props that get passed to your React component.

何时使用

🌐 When to use

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

🌐 The data being fetched using calculateMetadata() must be JSON-serializable. That means it is useful for API responses, but not for assets in binary format.

用法

🌐 Usage

传入一个回调函数,该函数接收未转换的 props,并返回一个包含新属性的对象。

🌐 Pass a callback function which takes the untransformed props, and return an object with the new props.

src/Root.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 外,defaultProps 也可以从同一个对象中读取。

🌐 The props being passed to calculateMetadata() are the input props merged together with the default props.
In addition to props, defaultProps can also be read from the same object.

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

🌐 When transforming, the input and the output must be the same TypeScript type. Consider using a nullable type for your data and throw an error inside your component to deal with the null type:

MyComp.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 类型定义

🌐 TypeScript typing

你可以使用来自 remotionCalculateMetadataFunction 类型来为你的回调函数指定类型。作为泛型值 (<>) 传入你的 props 类型。

🌐 You may use the CalculateMetadataFunction type from remotion to type your callback function. Pass as the generic value (<>) the type of your props.

src/Root.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;

共置

🌐 Colocation

下面是一个示例,展示了如何在同一个文件中定义一个模式、一个组件和一个获取函数:

🌐 Here is an example of how you could define a schema, a component and a fetcher function in the same file:

MyComp.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
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 都会重新获取数据。

🌐 By implementing this pattern, The id in the props editor can now be tweaked and Remotion will refetch the data whenever it changes.

根据数据设定持续时间

🌐 Setting the duration based on data

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

🌐 You may set the durationInFrames, fps, width and height by returning those keys in the callback function:

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,
  };
};

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

🌐 Learn more about this feature in the Variable metadata page.

中止过期请求

🌐 Aborting stale requests

在属性编辑器中的属性可能会快速变化,例如通过快速输入。
使用传递给 calculateMetadata() 函数的 abortSignal 取消已经过时的请求是一种良好的做法:

🌐 The props in the props editor may rapidly change for example by typing fast.
It is a good practice to cancel requests which are stale using the abortSignal that gets passed to the calculateMetadata() function:

src/MyComp.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 创建的。

🌐 This abortSignal is created by Remotion using the AbortController API.

防抖请求

🌐 Debouncing requests

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

🌐 If you are making requests to an expensive API, you might want to only fire a request after the user has stopped typing for a while. You may use the following function for doing so:

src/wait-for-no-input.ts
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
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;

时间限制

🌐 Time limit

当 Remotion 调用 calculateMetadata() 函数时,它会将其封装在 delayRender() 中,默认情况下会在 30 秒后超时。

🌐 When Remotion calls the calculateMetadata() function, it wraps it in a delayRender(), which by default times out after 30 seconds.

在渲染过程中获取数据

🌐 Fetching data during the render

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

🌐 Using delayRender() and continueRender() you can tell Remotion to wait for asynchronous operations to finish before rendering a frame.

何时使用

🌐 When to use

使用这种方法来加载无法进行 JSON 序列化的资源,或者如果你使用的 Remotion 版本低于 4.0。

🌐 Use this approach to load assets which are not JSON serializable or if you are using a Remotion version lower than 4.0.

用法

🌐 Usage

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

🌐 Call delayRender() as soon as possible, for example when initializing the state inside your component.

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() 来取消渲染,而无需等待超时。

🌐 Once the data is fetched, you can call continueRender() to tell Remotion to continue rendering the video.
In case the data fetching fails, you can call cancelRender() to cancel the render without waiting for the timeout.

时间限制

🌐 Time limit

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

🌐 You need to clear all handles created by delayRender() within 30 seconds after the page is opened. You may increase the timeout.

防止过度获取

🌐 Prevent overfetching

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

🌐 During rendering, multiple headless browser tabs are opened to speed up rendering.
In Remotion Lambda, the rendering concurrency may be up to 200x.
This means that if you are fetching data inside your component, the data fetching will be performed many times.

如果可能的话,优先在渲染之前获取数据。
2
确保你有权利以高请求速率请求数据,而不会遇到速率限制。
3
API 返回的数据在所有线程上必须相同,否则 可能会出现闪烁
4
确保不要将 frame 作为 useEffect() 的依赖,无论是直接还是间接,否则每一帧都会获取数据,导致性能下降,并可能遇到速率限制。

另请参阅

🌐 See also