Skip to content
React Native Skia ガイドブック
Esc
navigateopen⌘Jpreview
On this page

動画の描画

useVideoフックで動画のフレームをSkiaの画像として取得し、Image・ImageShader・Atlasに描画する方法と、再生・シーク・ループなどの制御を解説します。

React Native Skiaは、動画のフレームを1枚のSkia画像として取得する仕組みを持っています。取得したフレームはImageImageShaderAtlasなど、通常のSkia画像が使える場所ならどこにでも渡せます。動画にカラーフィルターをかけたり、円形にトリミングしたり、他の図形と合成したりといった表現が可能です。

前提として、Reanimated 3以降が必要です。Androidではフレーム取得の実装上、API level 26以上が必要になります。

useVideoの基本

useVideoフックに動画のURL(リモートのhttp://...、ローカルのfile://、またはバンドル内のアセット)を渡すと、現在のフレームを表す共有値(Shared Value)が得られます。

import { Canvas, ColorMatrix, Fill, ImageShader, useVideo } from "@shopify/react-native-skia";
import { Pressable, useWindowDimensions } from "react-native";
import { useSharedValue } from "react-native-reanimated";

const VideoExample = () => {
  const paused = useSharedValue(false);
  const { width, height } = useWindowDimensions();
  const { currentFrame } = useVideo("https://example.com/video.mp4", { paused });

  return (
    <Pressable style={{ flex: 1 }} onPress={() => (paused.value = !paused.value)}>
      <Canvas style={{ flex: 1 }}>
        <Fill>
          <ImageShader
            image={currentFrame}
            x={0}
            y={0}
            width={width}
            height={height}
            fit="cover"
          />
          <ColorMatrix
            matrix={[
              0.95, 0, 0, 0, 0.05, 0.65, 0, 0, 0, 0.15, 0.15, 0, 0, 0, 0.5, 0,
              0, 0, 1, 0,
            ]}
          />
        </Fill>
      </Canvas>
    </Pressable>
  );
};

currentFrameは共有値なので、これまでの章と同じくJavaScriptの再レンダリングを介さずにUIスレッド上でフレームが更新されます。タップでpausedを反転させているのも、指のタップというユーザーイベントをイベントハンドラーで処理しているだけで、Effectは登場しません。

Skiaを読み込み中…

このデモでは自作のグラデーション動画を再生しています。プレビュー部分をタップすると再生・一時停止が切り替わり、下のコントロールで反転カラーマトリクスのON/OFFを切り替えられます。

戻り値

useVideoが返すオブジェクトの内容です。

名前 説明
currentFrame SharedValue<SkImage | null> 現在のフレーム画像。読み込み前はnull
currentTime SharedValue<number> 現在の再生位置(ミリ秒)
duration number 動画の長さ(ミリ秒)
framerate number フレームレート
rotation 0 | 90 | 180 | 270 動画の回転角度
size { width: number; height: number } 動画の解像度

再生オプション

第2引数のオプションは、いずれも共有値またはプレーンな値として渡せます。

名前 説明
paused boolean 一時停止するかどうか
looping boolean ループ再生するかどうか
volume number 音量(0〜1)
seek number | null 指定ミリ秒へシーク。デフォルトはnull

タップのたびに2秒地点へシークする例です。

const seek = useSharedValue<null | number>(null);
const { currentFrame, currentTime } = useVideo(videoURL, { seek, looping: true });

<Pressable style={{ flex: 1 }} onPress={() => (seek.value = 2000)}>
  {/* ... */}
</Pressable>

seekは「ミリ秒を書き込むと1度だけシークが起き、実行後は自動的にnullへ戻る」という一回きりの共有値です。連続的な値ではなくトリガーとして使う点が、pausedloopingのような状態を表す共有値と異なります。

Skiaを読み込み中…

このデモではスライダーの値をそのままseek.valueに書き込み、動画の任意の位置へジャンプできることを確認できます。durationuseVideoの戻り値で、動画のメタデータ読み込みが完了するまでは0のままです。

回転した動画への対応

スマートフォンで縦向きに撮影した動画などはrotationが0以外になることがあります。fitbox関数を使うと、回転とフィット計算をまとめてtransformpropに変換できます。

import { Canvas, Image, useVideo, fitbox, rect } from "@shopify/react-native-skia";

const VideoExample = () => {
  const { currentFrame, rotation, size } = useVideo(videoURL);
  const src = rect(0, 0, size.width, size.height);
  const dst = rect(0, 0, width, height);
  const transform = fitbox("cover", src, dst, rotation);

  return (
    <Canvas style={{ flex: 1 }}>
      <Image image={currentFrame} x={0} y={0} width={width} height={height} fit="none" transform={transform} />
    </Canvas>
  );
};

バンドル内の動画を使う

expo-assetを使うと、アプリにバンドルした動画ファイルをuseVideoに渡せます。

import { useVideo } from "@shopify/react-native-skia";
import { useAssets } from "expo-asset";

export const useVideoFromAsset = (
  mod: number,
  options?: Parameters<typeof useVideo>[1]
) => {
  const [assets, error] = useAssets([mod]);
  if (error) {
    throw error;
  }
  return useVideo(assets ? assets[0].localUri : null, options);
};

注意点

Web上での動画対応には、ネイティブとは異なる制約があります。

内部的にはブラウザの<video>要素を使ってフレームをデコードしているため、動画はブラウザが再生できるコーデック(H.264のMP4など)である必要があります。加えて、<video>要素はデフォルトでcrossOrigin="anonymous"を要求するため、動画の配信元がCORSヘッダーを返さないと読み込みに失敗します。多くの動画CDNや汎用のファイルストレージはCORSを許可していないため、Web版では同一オリジンで配信できるローカルの動画ファイルを使うのが安全です(この章のデモの動画も本書と同一オリジンで配信しています)。実際、React Native Skia公式のサンプルアプリでも「Web版はCORSの制約があるためローカルの動画を使う」という分岐がコード中に明記されています。

ネイティブ(iOS/Android)ではCORSの制約は受けませんが、代わりにAndroidのAPIレベル制約があります。動画を多用する画面は、実機での動作確認を特に意識してください。

動画のエンコード

React Native Skiaは動画のデコード(読み込み・フレーム取得)には対応していますが、Skiaの描画結果を動画としてエンコードする機能は持っていません。エンコードが必要な場合はffmpegを使うか、react-native-skia-videoのようなサードパーティライブラリの利用を検討してください。

公式ドキュメントのVideoも参照してください。