You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+TypeScript中返回<Series.Sequence/>报[object Object]错误排查

解决Remotion中Series组件子组件类型校验问题

问题

在Remotion的React+TypeScript项目中封装了NarratedSeriesSequence组件,内部返回<Series.Sequence/>用于根据旁白音频时长自动设置序列时长,但将该组件作为<Series/>的子组件使用时,抛出错误:

The <Series /> component only accepts a list of <Series.Sequence /> components as it's children, but got [object Object] instead

错误源于Remotion源码的子组件类型校验逻辑:

if (castedChild.type !== SeriesSequence) {
  throw new TypeError(`The <Series /> component only accepts a list of <Series.Sequence /> components as it's children, but got ${castedChild} instead`);
}

原因

Remotion的Series组件会直接校验子组件的type是否等于内部的SeriesSequence类型(即<Series.Sequence/>的类型)。而自定义的NarratedSeriesSequence是独立的React函数组件,它的type是自定义函数,并非SeriesSequence,因此即使组件内部返回<Series.Sequence/>,外层的自定义组件依然无法通过校验。

解决方案

方案1:修改自定义组件的type属性

直接将NarratedSeriesSequence组件的type属性指向<Series.Sequence/>的type,让Remotion的校验逻辑认为它是合法的Series.Sequence组件。

修改NarratedSeriesSequence.tsx,在组件定义后添加一行代码:

import React, {useCallback, useEffect, useState} from 'react';
import {staticFile, Series, useVideoConfig} from 'remotion';
import {getAudioDurationInSeconds} from '@remotion/media-utils';

export interface NarratedSeriesSequenceProps {
    narrationAudioFile: string;
    children: JSX.Element;
    framePaddingBefore?: number;
    framePaddingAfter?: number;
}

export const NarratedSeriesSequence: React.FC<NarratedSeriesSequenceProps> = ({
    narrationAudioFile,
    framePaddingBefore = 0,
    framePaddingAfter = 0,
    children,
}: NarratedSeriesSequenceProps) => {
    const {fps} = useVideoConfig();
    const [durationFrames, setDurationFrames] = useState(0);

    const getDurationFrames = useCallback(async () => {
        try {
            const duration = await getAudioDurationInSeconds(
                staticFile(narrationAudioFile)
            );
            const durationInFrames =
                duration * fps + framePaddingBefore + framePaddingAfter;
            setDurationFrames(durationInFrames);
        } catch (err) {
            console.log(err);
        }
    }, [narrationAudioFile, framePaddingBefore, framePaddingAfter, fps]);

    useEffect(() => {
        getDurationFrames();
    }, [getDurationFrames]);

    if (durationFrames === 0) {
        return null;
    }

    return (
        <Series.Sequence durationInFrames={durationFrames}>
            {/* 可在此添加旁白音频组件 */}
            {children}
        </Series.Sequence>
    );
};

// 添加此行,让自定义组件通过Series的类型校验
(NarratedSeriesSequence as any).type = Series.Sequence.type;

修改后无需调整Composition.tsx的使用方式,<NarratedSeriesSequence/>可直接作为<Series/>的子组件。

方案2:直接调用组件函数而非JSX语法

跳过React组件的渲染包装,直接调用NarratedSeriesSequence函数,让它返回的<Series.Sequence/>直接作为<Series/>的子元素。

修改Composition.tsx中的使用代码:

<Series>
    <Series.Sequence durationInFrames={220}>
        <AbsoluteFill className="bg-gray-100 items-center justify-center">
            <div className="m-10" />
            <Logo logoColor={propThree} />
            <div className="m-3" />
            <Title titleText={propOne} titleColor={propTwo} />
            <Subtitle />
        </AbsoluteFill>
    </Series.Sequence>
    {/* 直接调用组件函数,返回Series.Sequence作为子元素 */}
    {NarratedSeriesSequence({
        narrationAudioFile: "audio/narration/00-this-video-was.mp3",
        children: (
            <div>
                This video was designed and rendered using React and Remotion.
            </div>
        )
    })}
    {NarratedSeriesSequence({
        narrationAudioFile: "audio/narration/01-remotion-gives-web-developers.mp3",
        children: (
            <div>
                Remotion gives web developers and designers a set of libraries to
                bring familiar tools into video projects, allowing for innovative
                creativity that traditional video production tools handle very
                differently.
            </div>
        )
    })}
</Series>

这种方式下,<Series/>接收到的子元素就是直接的<Series.Sequence/>,自然通过校验。

方案3:预计算音频时长(构建时同步获取)

如果旁白音频是静态文件,可以在项目构建前预计算所有音频的时长,避免组件内部的异步状态,让组件同步返回<Series.Sequence/>。

  1. 编写预计算脚本precompute-audio-durations.ts:
import {getAudioDurationInSeconds} from '@remotion/media-utils';
import fs from 'fs';
import path from 'path';

async function precomputeDurations() {
    const audioPaths = [
        "audio/narration/00-this-video-was.mp3",
        "audio/narration/01-remotion-gives-web-developers.mp3"
    ];
    const durationMap: Record<string, number> = {};

    for (const relativePath of audioPaths) {
        const fullPath = path.join(process.cwd(), 'public', relativePath);
        const duration = await getAudioDurationInSeconds(fullPath);
        durationMap[relativePath] = duration;
    }

    fs.writeFileSync(
        path.join(process.cwd(), 'src', 'audio-durations.json'),
        JSON.stringify(durationMap, null, 2)
    );
}

precomputeDurations();
  1. 在package.json中添加脚本,在构建前执行:
{
  "scripts": {
    "precompute-durations": "ts-node src/precompute-audio-durations.ts",
    "build": "npm run precompute-durations && remotion render"
  }
}
  1. 修改NarratedSeriesSequence.tsx,使用预计算的时长:
import React from 'react';
import {Series, useVideoConfig} from 'remotion';
import audioDurations from './audio-durations.json';

export interface NarratedSeriesSequenceProps {
    narrationAudioFile: string;
    children: JSX.Element;
    framePaddingBefore?: number;
    framePaddingAfter?: number;
}

export const NarratedSeriesSequence: React.FC<NarratedSeriesSequenceProps> = ({
    narrationAudioFile,
    framePaddingBefore = 0,
    framePaddingAfter = 0,
    children,
}) => {
    const {fps} = useVideoConfig();
    const duration = audioDurations[narrationAudioFile];

    if (!duration) {
        throw new Error(`未找到音频文件 ${narrationAudioFile} 的时长数据`);
    }

    const durationInFrames = duration * fps + framePaddingBefore + framePaddingAfter;

    return (
        <Series.Sequence durationInFrames={durationInFrames}>
            {/* 可在此添加旁白音频组件 */}
            {children}
        </Series.Sequence>
    );
};

// 同样需要添加此行以通过类型校验,或使用方案2的调用方式
(NarratedSeriesSequence as any).type = Series.Sequence.type;

这种方式避免了组件内部的异步状态,同时保证了组件的性能和稳定性。

内容的提问来源于stack exchange,提问作者Christopher Stevens

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 21:30:58