Remotion LabRemotion Lab
影片依序播放多段影片

依序播放多段影片

學習如何在 Remotion 中使用 Series 和 OffthreadVideo 將多段影片依序串接播放,並透過 calculateMetadata 動態計算總時長

依序播放多段影片

在 Remotion 中,你可以輕鬆將多段影片串接起來依序播放。本文介紹完整的實作方式,包含基礎範例、動態時長計算,以及讓預覽播放更流暢的預先掛載技巧。

實作架構概覽

整體流程分為三個步驟:

  1. 建立一個使用 <Series><OffthreadVideo> 渲染多段影片的元件
  2. 撰寫 calculateMetadata() 函式,自動抓取每段影片的時長
  3. <Composition> 中註冊元件並傳入影片清單

基礎範例

建立序列影片元件

首先建立一個元件,透過 <Series><OffthreadVideo> 渲染影片清單:

VideosInSequence.tsx
import React from 'react';
import {OffthreadVideo, Series} from 'remotion';
 
type VideoToEmbed = {
  src: string;
  durationInFrames: number | null;
};
 
type Props = {
  videos: VideoToEmbed[];
};
 
export const VideosInSequence: React.FC<Props> = ({videos}) => {
  return (
    <Series>
      {videos.map((vid) => {
        if (vid.durationInFrames === null) {
          throw new Error('Could not get video duration');
        }
 
        return (
          <Series.Sequence key={vid.src} durationInFrames={vid.durationInFrames}>
            <OffthreadVideo src={vid.src} />
          </Series.Sequence>
        );
      })}
    </Series>
  );
};

撰寫 calculateMetadata 函式

在同一個檔案中,建立計算合成元資料的函式:

  1. 呼叫 parseMedia() 取得每段影片的時長
  2. 將所有時長加總,算出合成的總時長
VideosInSequence.tsx
import {CalculateMetadataFunction} from 'remotion';
import {parseMedia} from '@remotion/media-parser';
 
export const calculateMetadata: CalculateMetadataFunction<Props> = async ({props}) => {
  const fps = 30;
  const videos = await Promise.all([
    ...props.videos.map(async (video): Promise<VideoToEmbed> => {
      const {slowDurationInSeconds} = await parseMedia({
        src: video.src,
        fields: {
          slowDurationInSeconds: true,
        },
      });
 
      return {
        durationInFrames: Math.floor(slowDurationInSeconds * fps),
        src: video.src,
      };
    }),
  ]);
 
  const totalDurationInFrames = videos.reduce(
    (acc, video) => acc + (video.durationInFrames ?? 0),
    0
  );
 
  return {
    props: {
      ...props,
      videos,
    },
    fps,
    durationInFrames: totalDurationInFrames,
  };
};

在根元件中註冊合成

在你的根元件(Root.tsx)中,建立一個使用 VideosInSequence 元件的 <Composition>

Root.tsx
import React from 'react';
import {Composition, staticFile} from 'remotion';
import {VideosInSequence, calculateMetadata} from './VideosInSequence';
 
export const Root: React.FC = () => {
  return (
    <Composition
      id="VideosInSequence"
      component={VideosInSequence}
      width={1920}
      height={1080}
      defaultProps={{
        videos: [
          {
            durationInFrames: null,
            src: 'https://remotion.media/BigBuckBunny.mp4',
          },
          {
            durationInFrames: null,
            src: staticFile('localvideo.mp4'),
          },
        ],
      }}
      calculateMetadata={calculateMetadata}
    />
  );
};

加入預先掛載(Premounting)

若你只在意渲染輸出的品質而不在意瀏覽器預覽的流暢度,可以跳過此步驟。

若想讓 Player 元件的預覽播放更流暢,需要考慮以下問題:

影片只有在即將播放時才會開始載入,這會導致在切換到下一段影片時出現短暫的空白或卡頓。

解決方式:對所有影片做兩件事:

  1. <Series.Sequence> 加上 premountFor prop,讓影片標籤在播放前就在背景悄悄載入
  2. 加上 pauseWhenBuffering prop,讓 Player 在影片尚未載入完成時自動進入緩衝狀態
VideosInSequence.tsx
import {useVideoConfig} from 'remotion';
 
export const VideosInSequence: React.FC<Props> = ({videos}) => {
  const {fps} = useVideoConfig();
 
  return (
    <Series>
      {videos.map((vid) => {
        if (vid.durationInFrames === null) {
          throw new Error('Could not get video duration');
        }
 
        return (
          <Series.Sequence
            key={vid.src}
            premountFor={4 * fps}
            durationInFrames={vid.durationInFrames}
          >
            <OffthreadVideo pauseWhenBuffering src={vid.src} />
          </Series.Sequence>
        );
      })}
    </Series>
  );
};
  • premountFor={4 * fps} 表示在影片開始播放前 4 秒就預先掛載 DOM 元素
  • pauseWhenBuffering 確保影片未就緒時 Player 會暫停等待,而非強行播放

重點整理

  • <Series><Series.Sequence> 是 Remotion 官方建議的多段影片串接方案
  • calculateMetadata 讓合成時長動態對應所有影片加總後的實際時長
  • premountForpauseWhenBuffering 搭配使用,可大幅改善 Player 的播放流暢度
  • 渲染輸出永遠是逐幀精確的,這些最佳化只影響瀏覽器預覽體驗

延伸閱讀