UnityのVideoPlayerでオープニングムービーを再生する

Unity5.5までは公式の機能でiOS,Androidで動画を再生するにはHandheld.PlayFullScreenMovieを使うしかなく、

よくあるオープニングムービーを再生する場合も、動画の前面にUnityのUIを表示することもできませんでした。


lc_title_movie from Vimeo.
Unity5.6から登場したVideoPlayerを使うと、このように動画の前面にUIを出すことも簡単に実現できます。

このVideoPlayerを利用して、オープニングムービー再生を試してみたので、その実装方法を紹介します。

VideoPlayerの基本的な使い方については探せばいくつか記事がでてくるので割愛します。

サンプルにある動画は以下のゲームのものです。よければダウンロードしてください。

サンプルの仕様

  • 全画面の動画用に利用する
  • UI等を動画の前面に表示する可能性があることを考慮する
  • 複数の動画を続けて再生する
  • コードだけで再生可能にする

Usage

Usage
video = sample.VideoPlay.Create( camera );
video.Preload( Application.streamingAssetsPath + "ファイルパス" );
 ( 完了待ち : video.State == sample.VideoPlay.PlayState.Loaded) )
video.OnEnd = ( 再生停止時の次の処理 );
video.OnErrorReceived = ( エラ発生時の処理 );
video.OnStarted  = ( 再生開始時の処理、インジケタ非表示等 );
video.PlayPrepared( false(プしない) , true(タップによってスキップする) );

簡単に使用例を書くと上記のような形になります。詳細はサンプルプロジェクトの「Sample」クラスを見てもらえればと思います。

では、サンプルプロジェクトの「Art.Sample.VideoPlay」の実装について説明していきます。

インスタンスの生成

インスタンスの生成
/// <summary>インスタンスの生成</summary>
public static VideoPlay Create(Camera camera = null)
{
    // シーンを横断する場合はDontDestroyOnLoadにする
    GameObject movieGameObject = new GameObject(kMovieGameObjectName);
    VideoPlay movieBehaviour = movieGameObject.AddComponent<VideoPlay>();
    movieBehaviour.Init(movieGameObject, camera);

    return movieBehaviour;
}

VideoPlayerのインスタンス1つで、clipやurlを再度指定して Play し、2つの動画を連続で再生することはできましたが、つなぎ目のところでゴミ(前の動画の1コマ)が表示されてしまいました。

そのため、動画ファイル1つ毎にVideoPlayerのインスタンスを1つ生成します。

設定

初期設定
private void Init(GameObject gameObject, Camera camera)
{
    _movieGameObject = gameObject;

    _audioSource = gameObject.AddComponent<AudioSource>();
    _videoPlayer = gameObject.AddComponent<VideoPlayer>();

    // 表示設定
    _videoPlayer.renderMode = VideoRenderMode.CameraNearPlane;
    _videoPlayer.targetCamera = camera;
    _videoPlayer.aspectRatio = VideoAspectRatio.FitInside;
    _videoPlayer.playOnAwake = false;

    // サウンド設定
    _audioSource.playOnAwake = false;
    _videoPlayer.audioOutputMode = VideoAudioOutputMode.AudioSource;
    _videoPlayer.EnableAudioTrack (0, true);
    _videoPlayer.SetTargetAudioSource(0, _audioSource);

    // イベント設定
    _videoPlayer.errorReceived += ErrorReceived;
    _videoPlayer.prepareCompleted += PrepareCompleted;
    _videoPlayer.started += PlayStart;
    _videoPlayer.loopPointReached += PlayEnd;
}

表示設定

  • VideoRenderModeにはCameraNearPlaneを設定

    カメラの一番手前に表示しますが、例えばOverlayに設定されたUI等はその全面に表示されます。

  • VideoAspectRatioはFitInsideを設定

    動画が画面外にはみでないような設定にします。

  • playOnAwakeはfalseに

    Prepareで事前にロードしたり、再生タイミングをコントロールするためにplayOnAwakeは無効にします。

サウンド設定

iOS,AndroidだとVideoAudioOutputModeはAudioSourceにしないと再生ができないようです。

また、エディタだとUnity2017.2.1f1で音が鳴らなくなって困ってます…。

イベント設定

  • errorReceived

    何かしらエラーが発生した場合に通知され、その場合停止通知がこないのでこの通知を受け取ったら停止と判断します。

  • prepareCompleted

    事前にローディングし、そのタイミングをハンドリングできるように通知を受け取ります。

  • started

    再生が開始されるまでにインジゲータ表示等を行うため、通知を受け取ります。

  • loopPointReached

    再生停止のタイミングを判断するために通知を受け取ります。

イベント設定
private void ErrorReceived(VideoPlayer player, string message)
{
    if (OnErrorReceived != null)
    {
        OnErrorReceived(message);
    }
}
private void PrepareCompleted(VideoPlayer player)
{
    _playState = PlayState.Loaded;
    if (OnPrepareCompleted != null)
    {
        OnPrepareCompleted();
    }
}
private void PlayStart(VideoPlayer player)
{
    if (OnStarted != null)
    {
        OnStarted();
    }
}
private void PlayEnd(VideoPlayer player)
{
    if (_playState == PlayState.Stoped)
    {
        return;
    }
    _playState = PlayState.Stoped;

    if (OnEnd != null)
    {
        OnEnd();
    }
}

事前ロード

事前ロード
/// <summary>事前ローディング</summary>
public void Preload(string filePath)
{
    _playState = PlayState.Loading;
#if WITH_DEVELOP
    _movieGameObject.name = kMovieGameObjectName + System.IO.Path.GetFileName(filePath);
#endif
    _videoPlayer.url = filePath;
    _videoPlayer.Prepare();
}

ファイルパスを指定して事前ロードを行います。

後述しますが、プラットフォームによってはアプリのバックグラウンドに遷移した際の挙動が不安定なので自前で状態管理しています。

またデバッグ時はヒエラルキー上で認識しやすくするためにGameObject名を変更しています。

再生開始

再生開始
/// <summary>事前ロードした動画の再生開始</summary>
public void PlayPrepared(bool loop = false, bool tapSkip = true)
{
    if (_playState != PlayState.Loaded)
    {
        const string message = "not Prepared";
        ErrorReceived(_videoPlayer, message);
        return;
    }

    _tapSkip = tapSkip;
    _videoPlayer.isLooping = loop;
    _playState = PlayState.Playing;
    _videoPlayer.Play();
}
/// <summary>動画の再生開始(パス指定)</summary>
public void Play(string filePath, bool loop = false, bool tapSkip = true)
{
    if (_playState == PlayState.Playing || _playState == PlayState.Paused)
    {
        const string message = "already playing";
        ErrorReceived(_videoPlayer, message);
        return;
    }

    if (_playState == PlayState.Loading || _playState == PlayState.Loaded)
    {
        const string message = "already Prepared";
        ErrorReceived(_videoPlayer, message);
        return;
    }
#if WITH_DEVELOP
    _movieGameObject.name = kMovieGameObjectName + System.IO.Path.GetFileName(filePath);
#endif
    _tapSkip = tapSkip;
    _videoPlayer.url = filePath;
    _videoPlayer.isLooping = loop;

    _playState = PlayState.Playing;
    _videoPlayer.Play();
}

ループ再生と画面タップによるスキップの設定を行った後、再生を開始します。

一応用意したものの、オープニングムービーでループ再生することはまずなさそうですが。

タップによるスキップ(再生停止)は以下のようにしています。

void Update ()
{
    if (_playState != PlayState.Playing || !_tapSkip)
    {
        return;
    }

    // タップ確認
    if ( Input.GetMouseButtonUp(0) )
    {
        Stop();
    }
}

問答無用でスキップしてしまうので、ダイアログ表示とかを挟む場合は改良が必要です。

再生停止と一時停止

/// <summary>再生停止</summary>
public void Stop()
{
    if (_videoPlayer.isPlaying)
    {
        _videoPlayer.Stop();
    }

    PlayEnd(_videoPlayer);
}
/// <summary>再生一時停止</summary>
public void Pause()
{
    if (_playState != PlayState.Playing)
    {
        return;
    }

    _playState = PlayState.Paused;
    _videoPlayer.Pause();
}
/// <summary>再生一時停止再開</summary>
public void Resume()
{
    if (_playState != PlayState.Paused)
    {
        return;
    }

    _playState = PlayState.Playing;
    _videoPlayer.Play();
}
/// <summary>表示の有効無効設定</summary>
public void SetEnabled(bool enabled)
{
    _videoPlayer.enabled = enabled;
}

VideoPlayerのインスタンスに対してStopとPauseを呼び出します。

一時停止再開は再度Playを呼び出します。

また、再生停止をした場合もインスタンスが有効な間は最後のフレームが表示されるので、enabledをfalseにすることで非表示にできます。

その他

不具合っぽいものなど

  • バックグラウンドに遷移し、戻ってきた際に再生が継続しないケースがある
    • 原因不明で、暫定対策としてバックグラウンド遷移時に一時停止、フォアグラウンドの際に一時停止再開すると発生が軽減することを確認
  • エディタ(Mac)で動作させた場合、OnApplicationPauseで判定するバックグラウンド遷移時に再生中にもかかわらずVideoPlayer.isPlayingがfalse判定なってしまう
    • state管理にisPlayingを利用しないことで解決
  • エディタ(Mac)だとUnity2017.2.1f1で音が鳴らなくなった
  • 外部のhttp URL指定で再生するとiOS,Androidの実機で再生できない
  • Androidでヒドいコマ落ちが発生。Unityのバージョンアップか何かのタイミングで気がついたら再現しなくなった…