Remotion LabRemotion Lab
影片隨時間動態加速影片

隨時間動態加速影片

學習如何在 Remotion 中讓影片的播放速度隨時間動態變化,透過累加播放率的方式正確計算每一幀應播放的影片位置

隨時間動態加速影片

有時你會想讓影片的播放速度隨時間變化——例如從正常速度開始,然後逐漸加速。本文說明正確的實作方式,以及為何直接插值 playbackRate 不可行。

注意: 此方法目前尚不支援 @remotion/media 中的 <Video> 元件。

為何不能直接插值 playbackRate

你可能會直觉地想這樣寫:

// 錯誤做法
<OffthreadVideo
  playbackRate={interpolate(frame, [0, 100], [1, 5])}
  src="https://remotion.media/BigBuckBunny.mp4#disable"
/>;

這樣做是錯的。 原因在於 Remotion 的渲染機制:每一幀都是獨立計算的,不依賴前一幀的狀態。

以第 100 幀為例:

  • playbackRate 插值結果為 5
  • Remotion 會播放影片的第 100 * 5 = 500
  • 但實際上,速度是從 1 漸增到 5,真正應播放的影片位置遠不到第 500 幀

這種做法沒有把「之前所有幀的速度累積」考慮進去。

正確做法:累加播放率

正確的思路是:計算從第 0 幀到當前幀為止,所有幀的播放率加總,這樣就能得出影片實際應該播放到哪個位置。

AcceleratedVideo.tsx
import React from 'react';
import {interpolate, Sequence, useCurrentFrame, OffthreadVideo} from 'remotion';
 
/**
 * 計算到第 frame 幀為止,實際應播放的影片位置(以幀為單位)
 * @param frame 當前合成幀號
 * @param speed 速度函式:輸入幀號,輸出該幀的播放速度
 */
const remapSpeed = (frame: number, speed: (fr: number) => number) => {
  let framesPassed = 0;
  for (let i = 0; i <= frame; i++) {
    framesPassed += speed(i);
  }
 
  return framesPassed;
};
 
export const AcceleratedVideo: React.FC = () => {
  const frame = useCurrentFrame();
 
  // 速度函式:從第 0 幀的 1 倍速,線性增加到第 500 幀的 5 倍速
  const speedFunction = (f: number) => interpolate(f, [0, 500], [1, 5]);
 
  // 計算到目前為止實際播放的影片幀數
  const remappedFrame = remapSpeed(frame, speedFunction);
 
  return (
    <Sequence from={frame}>
      <OffthreadVideo
        trimBefore={Math.round(remappedFrame)}
        playbackRate={speedFunction(frame)}
        src="https://remotion.media/BigBuckBunny.mp4#disable"
      />
    </Sequence>
  );
};

程式碼深度解析

remapSpeed 函式

這個輔助函式透過迴圈累加每一幀的播放速度:

const remapSpeed = (frame: number, speed: (fr: number) => number) => {
  let framesPassed = 0;
  for (let i = 0; i <= frame; i++) {
    framesPassed += speed(i);
  }
  return framesPassed;
};
  • 從第 0 幀迭代到當前幀
  • 每次將該幀的速度值加入累計總量
  • 回傳值代表「到目前為止,影片實際前進了多少幀」

範例計算(假設速度函式為線性 1→5,共 500 幀):

合成幀該幀速度累計影片幀
01.01
1001.8~140
2503.0~500
5005.0~1500

Sequence from={frame} 的作用

<Sequence from={frame}>
  <OffthreadVideo trimBefore={Math.round(remappedFrame)} ... />
</Sequence>
  • <Sequence from={frame}> 讓 Sequence 的起始點跟隨當前幀移動
  • 搭配 trimBefore,確保影片從正確的位置開始播放
  • 在 Remotion Studio 中,你會看到時間軸隨播放移動——這是正常現象,只要計算是冪等的就沒問題

#disable 片段提示

src="https://remotion.media/BigBuckBunny.mp4#disable"

通常 Remotion 會自動在 URL 後面加上媒體片段提示(如 #t=10,20),告訴瀏覽器只需載入哪段影片。加上 #disable 可以停用這個行為,因為我們是透過 trimBefore 手動控制播放位置的。

不同速度模式

固定倍速(簡單情況)

若只需固定速度播放,直接使用 playbackRate 即可:

<OffthreadVideo
  playbackRate={2}
  src={staticFile('video.mp4')}
/>

分段速度(不同片段不同速度)

可以結合跳切技術,讓不同片段以不同速度播放。詳見 影片跳切

自定義速度曲線

speedFunction 可以是任意函式,不僅限於線性插值:

// 先慢後快(緩入加速)
const speedFunction = (f: number) =>
  interpolate(f, [0, 100], [0.5, 3], {
    easing: Easing.in(Easing.quad),
  });
 
// 固定三段速度
const speedFunction = (f: number) => {
  if (f < 100) return 1;
  if (f < 200) return 2;
  return 4;
};

效能考量

remapSpeed 中的迴圈對於幀號較大時會有線性時間複雜度。若合成幀數很多,可考慮使用積分近似或預先計算速度表來優化效能。

延伸閱讀