【Unity】VideoPlayerを使った動画操作入門

お久しぶりです。 またUnityを使う機会があったので、記事を更新します。最近はもっぱらswiftでiOS開発をしていました。   今回取り扱うVideoPlayerなんですが、これを使った記事はちょくちょく見かけてはいたのですが、大体再生までの流れぐらいでした。ただいざ実装すると往々にしていろいろタイミングが知りたくものですよね。あまりそこらへんの記事がなかったので今回記事にします。最後にサンプルのGitリポジトリおいておくので必要であれば見てみてください。  

再生までの流れ

ここらへんは記事も多かったのであまり詳しくは書きません。    とりあえずの流れとしては

  • VideoPlayerのアタッチ(動画はVideoPlayerのインスペクタから設定しておく)

  • RawImageの配置 を行ったうえで以下のように設定すれば再生までの流れは概ねオッケーではないでしょうか?

private void SetupVideoPlayer()
{
    // 動画表示用のレンダーテクスチャーを作成
    var renderTexture = new RenderTexture(1920, 1080,0);
    videoPlayer.renderMode = VideoRenderMode.RenderTexture;
    videoPlayer.targetTexture = renderTexture;
    displayRawImage.texture = renderTexture;

    videoPlayer.Prepare();
}

再生する動画をスクリプトで設定する

こちらは動画ファイルのURLを設定するだけで大丈夫です。   なので上のSetupVideoPlayer()の前に  

    videoPlayer.url = "動画のファイルパス";

を記述するだけでできます。

再生・停止・シーク機能

こちらに関してもほとんどVideoPlayerの機能として持っています。
関数化していますが基本的にはVideoPlayerの機能だけでできます。
シーク機能の関数はないのですがVideoPlayer.frameを指定するだけでシークすることが出来るのでSliderとの紐付けも簡単です。
以下にサンプルを載せます。

/// <summary>
/// 動画再生
/// </summary>
public void Play()
{
    videoPlayer.Play();
}

/// <summary>
/// 一時停止
/// </summary>
public void Pause()
{
    if (videoPlayer.isPlaying)
    {
        videoPlayer.Pause();
    }
}

/// <summary>
/// シーク
/// </summary>
/// <param name="frameIndex">飛ばしたいフレーム</param>
public void Seek(int frameIndex)
{
    if (frameIndex > 0 && frameIndex <= (int)videoPlayer.frameCount)
    {
        videoPlayer.frame = frameIndex;
    }
}

コマ送り・コマ戻し(おまけ)

こちらは先ほどのVideoPlayer.frameでシークする機能を応用すればコマ送りなどの機能も簡単に実装できるということです。
(ちなみにコマ送りは専用の関数が用意されています)

/// <summary>
/// コマ送り
/// </summary>
public void ForwardFrame()
{
    // 念のため一時停止
    Pause();

    var currentFrame = (int)videoPlayer.frame;

    // フレームの整合性はシークの関数で行う
    Seek(currentFrame + 1);
}

/// <summary>
/// コマ戻し
/// </summary>
public void BackFrame()
{
    // 念のため一時停止
    Pause();

    var currentFrame = (int)videoPlayer.frame;

    // フレームの整合性はシークの関数で行う
    Seek(currentFrame - 1);
}

サムネイルの設定

スクリプトから再生する動画を選択してRenderTexureもスクリプトで作成している場合は、表示するRawImageのTextureが再生されるまで更新されないためサムネイルが表示できない問題があります。
サムネイルの設定はデフォルトで入っているものはないです。そして設定方法も割りと強引というかスマートな方法が私の調べた限りではありませんでした。
以下の強引な方法でサムネイルを表示させます。

public void SetThumNail()
{
    Play();
    Pause();
    // サムネイルにしたいフレームも設定可能(ここでは100フレーム目をサムネイルに設定)
    Seek(100);
}

ほんと強引ですよね。でもこれしか思いつきませんでした。笑

各種イベントの設定

やっと本題なのですが、私がVideoPlayerを用いたときに動画のフレームが更新されるたびに何かしらの処理を行う必要がありました。そのためupdate()などを駆使してすごいめんどくさいことを行っていたんですが、実はVideoPlayerにはもともと最低限必要そうなイベントがちゃんとあります。
ここではサンプルとして3つ紹介します。イベント登録などがわからない方は参考にしていただければと思います。
書き方としては登録したいイベントに+=で関数を追加してあげることでできます。登録した場合はOndestoryなどのタイミングでキチッと-=をします。

  • 動画が最終フレームに到達したとき

        videoPlayer.loopPointReached += OnLoopPointReached;
    
  • 動画の再生準備が出来たとき

        videoPlayer.prepareCompleted += OnPrepareCompleted;
    
  • フレームが更新したとき

        videoPlayer.frameReady += OnFrameReady;
        // この設定をしないとFrameReadyのイベントは発火されないので注意
        videoPlayer.sendFrameReadyEvents = true;
    

こちらなんですがsendFrameReadyEvents を設定しないとイベントが飛びませんので注意です。 またこれはフレームのインデックスも取得できるので、動画のシークバーを連動させたい場合などはこれをつかうと実装ができます。

private void Awake()
{
    videoPlayer.loopPointReached += OnLoopPointReached;
    videoPlayer.prepareCompleted += OnPrepareCompleted;

    videoPlayer.frameReady += OnFrameReady;
    // この設定をしないとFrameReadyのイベントは発火されないので注意
    videoPlayer.sendFrameReadyEvents = true;
}

/// <summary>
/// 動画が最終フレームに到達したとき
/// </summary>
/// <param name="vp"></param>
private void OnLoopPointReached(VideoPlayer vp)
{
    // 例として、自動で一時停止させる
    videoPlayer.Pause();
}

/// <summary>
/// 再生準備が完了したとき
/// </summary>
/// <param name="vp"></param>
private void OnPrepareCompleted(VideoPlayer vp)
{
    SetThumNail();
}

/// <summary>
/// 表示するフレームの準備が完了したとき
/// </summary>
/// <param name="vp"></param>
/// <param name="frameIndex"></param>
private void OnFrameReady(VideoPlayer vp,long frameIndex)
{
    // 再生・シークなど更新が入るたびに叩かれる関数
    // たとえばスライダーだと(slider.maxValueが1の場合)
    // slider.value = (float)frameIndex / (float)vp.frameCount
}

private void OnDestroy()
{
    videoPlayer.loopPointReached -= OnLoopPointReached;
    videoPlayer.prepareCompleted -= OnPrepareCompleted;
    videoPlayer.frameReady -= OnFrameReady;
}

こんな感じで最低限動かせるサンプルを作ったのでUnityProjectが見たい場合は見てみてください。

github.com