サイトアイコン GAMEWORKS LAB

【Unity】動画のアセットバンドル化

動画のアセットバンドル化

Unityでゲームを作っていると動画を再生する必要が出てくることがあります。
そんな時はVideoClipを使うことになるかと思います。

ただ動画はファイルサイズが大きくなりやすいのでアプリに同梱することが難しい場合も多いです。
そんな時は次の選択肢を思いつくと思います。

Web上からのストリーミング再生についてはURLを指定することで対応可能なのでフォーマットさえ対応していれば簡単に実装ができます。
なので今回はアセットバンドル化する対応について説明をしていきます。

Unityの公式サイトにはVideoClipはアセットバンドル化に対応していると記載されています。
なので筆者も何の疑いもなくアセットバンドル化してロード後にVideoPlayerのclipへアサインして再生していました。

しかし、Android環境のROMを作成した際に事件は起きました…。
何故か動画再生のタイミングで再生されずに進行停止が発生するとの報告が!?

Unityの公式サイトには特にその手の記述が無かったので自分の実装ミスだと考えたのですがいくら調査してもバグの原因が見つかりません…。

仕方ないので同様の現象が起こっていないかGoogle先生に助けを求めました。
するとUnityのフォーラム上で海外の方々が同じ状況に陥っていると話していました。

同じ状況なら解決策も誰か握っているはずと記事を追いかけてみたのですが、分かったことは下記の通りでした。

AssetBundle化するとLZ圧縮がかかるのでストリーミング再生ができない。
・結果、Androidでの再生ができなくなる。

なら無圧縮のAssetBundle化すればいけるのでは?

やってみたがやはりダメ。
恐らくアセットバンドル化すると複数のファイルがパックされる形になるので部分的に読み込むことをサポートしていないからだと思われます。

この結果からアセットバンドル化するとAndroid上ではVideoClipでの再生は不可能ということになります。
フォーラムの情報からUnity側の見解でこれはバグではなく仕様とされているらしいので改善も期待できません。

仕方ないので筆者はここでVideoClipを使用することを諦めました。
ただプロジェクト的にはアセットバンドル化は必須課題でしたので別のアプローチが必要でした。

 

アセットバンドル化した動画の再生方法

そこで筆者は動画をローカルからのストリーミング再生にすることでこの問題を解決をしました。
具体的には下記の手順で実装をしています。

  1. 動画を手動でMP4(h264, AAC)に変換
  2. 動画をバイナリデータとしてアセットバンドル化
  3. アセットバンドルから動画を読み込む
  4. 読み込んだ動画を任意のディレクトリに保存
  5. VideoPlayerのurlに保存したパスを指定する
  6. 再生

これで動画の再生を行いました。
手順は長く見えますが実際にはコードも短く大した処理ではないので簡単に実装ができます。

動画を手動でMP4(h264, AAC)に変換

動画はフリーソフトで変換してテストしました。
実際には動画作成者にフォーマットを変換しておくようにお願いすれば問題ないかと思います。

動画をバイナリデータとしてアセットバンドル化

1で変換した動画の拡張子を bytes とすることでバイナリデータとして認識させます。

アセットバンドルから動画の読み込む

単純にアセットバンドルのロード処理です。

読み込んだ動画を任意のディレクトリに保存

バイナリとして読み込んだ動画を下記のディレクトリ内の任意の場所に保存します。
Application.persistentDataPath

persistentDataPathは実行中に保存されるファイルを格納する場所なので動画もそこに保存しています。

VideoPlayerのurlに保存したパスを指定する

ローカルの保存したパスをURLに指定します。
パスの先頭には file:/// を追記する必要があります。

再生

普通に再生します。

 

この手順で再生が完了です。
アセットバンドル化する部分は各々に対応していただく必要がありますが、実際のコードは下記になります。

AssetBundleMovie.cs

using System.Collections;
using UnityEngine;
using UnityEngine.Video;
using UnityEngine.Networking;
using System.IO;

public class AssetBundleMovie : MonoBehaviour
{
    [SerializeField]
    private VideoPlayer videoPlayer;

    IEnumerator LoadAndPlayMovie()
    {
        string path = $"{Application.persistentDataPath}/movie.m4v";
        if (!File.Exists(path))
        {
            // アセットバンドルのダウンロード(今回はStreamingAssetsを使用)
            string uri = $"file:///{Application.dataPath}/AssetBundles/movie.movie";
            UnityWebRequest request = UnityWebRequest.Get(uri);
            yield return request.SendWebRequest();

            AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
            // アセットバンドルからバイナリ化した動画のロード
            var movie = bundle.LoadAsset("movie.bytes");
            // 動画をローカルに保存
            var file = File.OpenWrite(path);
            file.Write(movie.bytes, 0, movie.bytes.Length);
            file.Close();
        }
        // 動画のパスを指定して再生
        videoPlayer.url = $"file:///{path}";
        videoPlayer.Play();
    }
}


今回はテストコードなのでStreamingAssetsからパス直指定で読み込んでいますが、こちらはCDNからダウンロードでも正常に動作します。
動画はローカルに保存済みの場合はローカルのデータをすぐに再生すれば不要なコピーが走ることがないのでいいかと思います。

もし同じようにアセットバンドル化した動画を再生する必要のある方は参考にしてみてください。
そしてもっといい方法を知っている方がいれば教えてもらえると嬉しいです!

モバイルバージョンを終了