Unity標準のVideoPlayerを使わずにWebGLで動画再生する処理を自作したいの。
標準のVideoPlayerの内部実装を参考に自作する方法を見ていくね。
WebGL環境において、Unity標準のVideoPlayerのような動画再生機能を自作する方法の解説記事です。
次のような動画再生をjslibプラグインでネイティブ実装して実現するところを目指します。
Unityでは、動画再生機能はVideoPlayerコンポーネントとして提供されており、WebGL含めたあらゆるプラットフォームでの再生が可能です。
参考:Unity – Scripting API: VideoPlayer
通常の動画再生であれば、Unity標準のVideoPlayerコンポーネントで事足りる場面も多いと考えられますが、動画再生をネイティブ実装することで次のようなことが期待できるかもしれません。
- 動画再生の挙動を細かくカスタマイズできる
- ブラウザ固有の不具合などに対応できる
- VideoPlayerでは再生不可のフォーマットに対応する
単純な不具合回避目的であれば、例えばUnity 2022.1以降ではiOSやSafariなどでも動作するように修正されているため、Unityプロジェクトのアップグレードも一つの選択肢です。
本記事では、WebGL環境においてVideoPlayerコンポーネントのような動画再生機能をネイティブコードから独自実装する方法を解説していきます。
本記事で解説する方法はWebGLプラットフォームでのビルド時のみ有効になります。それ以外のプラットフォームやUnityエディタ上では既存のVideoPlayerで代用して動かすことを目指します。
また、本記事で紹介する実装例は、あくまでも簡易的なものであり、VideoPlayerのような機能を完全に提供するものでない事をご了承ください。
- Unity 2023.2.20f1
目次 非表示
想定する状況
本記事では、前半に簡易的な実装例、後半に応用的な実装例を示す形で解説していきます。
前半では、以下のようにUnity UIで作成されたRaw Imageオブジェクトに対して単純に動画をレンダリングするものとします。
後半では、次のような操作を実装した例を紹介します。
- 再生
- 停止
- 一時停止
- シーク
- コマ送り
- URL変更
- 現在時刻の表示
- ミュートの指定
動画再生の実装方法
本記事では、Unity提供のVideoPlayerコンポーネントの内部実装を参考に独自実装していくものとします。
内部実装のコードは以下ファイルより閲覧できます。
WebGL環境で動画再生を独自実装するために、JavaScript側(jslibプラグイン)でネイティブ実装することとします。
video要素の作成
ここからはUnity C#ではなくJavaScriptでの実装方法の解説になります。
まず、動画再生するためにdocument.createElementメソッドでvideo要素を生成します。
参考:Document: createElement() メソッド – Web API | MDN
video要素はUnity側で内部的に使用したいため、非表示にしておきます。
参考:style – HTML: ハイパーテキストマークアップ言語 | MDN
参考:display – CSS: カスケーディングスタイルシート | MDN
そして、video.src要素に動画再生用のURLを指定します。
UTF8ToString関数はUnity C#のstring型オブジェクトのポインタをJavaScriptの文字列に変換するヘルパー関数です。
参考:Interaction with browser scripting – Unity マニュアル
参考:<video>: 動画埋め込み要素 – HTML: ハイパーテキストマークアップ言語 | MDN
このままでは、モバイルブラウザ等で自動再生ができないため、初期状態ではミュート、インライン再生を有効にしておきます。また、CORSによる取得設定も行っておきます。
動画の再生
video要素の初期化が出来たら、動画の再生を行います。
再生はvideo.playメソッドで行えます。
テクスチャの転送
video要素を再生しただけでは、RenderTexture等に動画内容が転送されないため、video要素の内容が更新されるたびにテクスチャに画像転送する必要があります。
ただし、video要素の内容を直接転送しただけでは上下反転されて描画されるため、転送前にGLctx.pixelStoreiメソッドで反転設定を行います。
参考:WebGLRenderingContext: pixelStorei() method – Web APIs | MDN
そして、GLctx.bindTextureメソッドで描画対象のテクスチャをバインドします。
参考:WebGLRenderingContext: bindTexture() method – Web APIs | MDN
バインドが出来たら、GLctx.texImage2Dメソッドでvideo要素の画像をテクスチャに適用します。
参考:WebGLRenderingContext: texImage2D() method – Web APIs | MDN
適用が完了したら、最後に上下反転設定を元に戻します。
Unity C#側での準備
Unity C#では、JavaScriptに渡すためのテクスチャの準備を行います。
ただし、video要素から得られる画像データはsRGB色空間であるため、リニアワークフローの設定になっている場合、そのままでは正しい色が出力されません。
そのため、リニアワークフロー設定の場合、sRGBからLinearに色空間を変換する処理を施す必要があります。これはピクセルシェーダーで行うのが良いでしょう。
上記処理は、VideoPlayerコンポーネントの内部実装を参考にしています。
色空間のワークフローの設定は、トップメニューのEdit > Project Settings…からProject Settingsウィンドウを開き、左側からPlayerを選択し、Other Settings > Rendering > Color Space*の項目から確認できます。
色空間のワークフローの設定状態は、QualitySettings.activeColorSpaceプロパティから取得できます。
参考:Unity – Scripting API: QualitySettings.activeColorSpace
video要素からの画像転送は、Texture2D.GetNativeTexturePtrメソッドから得られるテクスチャIDをJavaScript側に渡し、Texture2Dオブジェクトに反映してもらえば良いです。
参考:Texture-GetNativeTexturePtr – Unity スクリプトリファレンス
video要素からテクスチャを取得出来たら、RenderTextureに転送して完了です。
Texture2Dの内容をRenderTextureに転送したい場合、Graphics.Blitメソッドを使えば良いです。必要に応じて第3引数にマテリアルを指定できます。
参考:Graphics-Blit – Unity スクリプトリファレンス
動画再生する最低限のスクリプト
ここまでの内容を踏まえて、実際にWebGL環境で再生するための実装例を示します。
1つ目の例はUnityエディタ上では動かず、WebGLビルドでしか動かない点にご注意ください。
スクリプトの実装例
以下、動画を自動再生するための最小限のスクリプトです。2つある事にご注意ください。
サンプルスクリプト(クリックで開きます)
1つ目のスクリプトをSimpleWebGLVideoPlayer.jslibという名前でPluginsフォルダ配下に保存、2つ目のスクリプトをSimpleWebGLVideoPlayer.csとして適当なフォルダに保存してください。
例では、以下階層に保存するものとしました。
sRGBからLinearに変換するシェーダーの実装例
リニアワークフローの場合、sRGBからLinear空間に変換するためのシェーダーを実装して適用する必要があります。
以下、変換シェーダーの実装例です。
シェーダー(クリックで開きます)
上記をsRGBToLinear.shaderという名前で適用な階層に保存してください。例では、以下階層に置くものとしました。
テクスチャの準備
描画先のRenderTextureを作成しておきます。例では、1920×1080の解像度のRenderTextureを予め作成しておくものとします。
UIの準備
例では、RenderTextureの内容を反映するためのRaw Imageオブジェクトを画面上に配置します。ここに先ほど作成したRenderTextureを適用します。
スクリプトの適用
RenderTextureに対して動画再生用のスクリプトを適用していきます。
例では、新しいゲームオブジェクトをヒエラルキー上に配置し、ここに実装例で示したSimpleWebGLVideoPlayerスクリプトを追加します。
そして、追加したスクリプト(コンポーネント)のインスペクターより、以下項目を設定してください。
- Url – 動画のURL
- Target Render Texture – 出力先のRenderTexture
- SRGB To Linear Shader – sRGBからLinearに色空間を変換するシェーダー
実行結果
この状態でWebGLビルドすると、ミュート状態で動画が再生されるようになります。
スクリプトの説明
JavaScript側のコードでは、Unity C#から呼び出される関数として各種処理を実装しています。
例では、video要素のインスタンスを格納するオブジェクトと、インクリメンタルIDを管理する変数を定義しています。
video要素の作成は、以下関数で行っています。
作成したvideo要素をvideosオブジェクトに格納して管理するようにしています。
video要素からテクスチャに反映する処理は、以下関数で行っています。
Unity C#側で保持されたvideo要素のインスタンスIDとテクスチャのIDを受け取り、テクスチャが更新されていたら出力先のテクスチャにvideo要素の現在画像を適用する処理にしています。
その他、video要素のインスタンス破棄や動画再生、動画の幅と高さを取得する関数も定義しています。
ここまで定義した変数を関数から参照できるようにするため、autoAddDeps関数を用いて紐づけています。
そして、関数をネイティブプラグインとしてUnity C#から使えるようにするために、mergeInto関数でマージしています。
Unity C#側では、以下のように[DllImport(“__Internal”)]属性でjslib側の関数をインポートできます。
参考:Interaction with browser scripting – Unity マニュアル
初期化処理では、jslibで定義した作成関数をコールして発行されたvideo要素に紐づくIDを保持するようにします。
また、リニアワークフローの場合は色空間変換用のマテリアルを作成して使えるようにします。
Updateイベントでは、video要素のテクスチャ更新を行います。
まず、video要素の解像度が変更されたタイミングでTexture2Dオブジェクトを再作成(新規の場合は新規作成)します。
毎回作成しても動きますが、解像度に変化が無い場合は余計な処理コストになるため、変化した瞬間に行うのが良いでしょう。
そして、jslibの関数を通じて前述のTexture2Dオブジェクトに動画の内容を反映します。
最後に、RenderTextureにTexture2Dの内容を転送して完了です。この時、リニアワークフローの場合はマテリアルが作成されるので、第3引数に色空間を変換するマテリアル指定しています。
独自VideoPlayerの設計
ここまでは単純な再生例を紹介しました。
2つ目の例では、以下操作に対応した独自VideoPlayerを実装するものとします。
- 再生
- 停止
- 一時停止
- シーク
- コマ送り
- URL変更
- 現在時刻の表示
- ミュートの指定
また、Unityエディタ上でも確認できるように、WebGLビルド実行時以外ではUnity標準のVideo Playerコンポーネントで代用するようにします。
スクリプトの実装例
まず、動画再生部分のスクリプトの完成形を示します。
以下、実装例です。スクリプトが全部で5つあることにご注意ください。
サンプルスクリプト(クリックで開きます)
.jslibファイルはPluginsフォルダ配下に保存することにご注意ください。
例では以下のようなパスに格納するものとします。
UIの準備
1つ目の例に加えて、必要に応じて操作用のUIを追加します。
例では、以下UIを追加するものとします。
- URL入力欄 – TextMeshPro – Input Field
- 再生時刻 – TextMeshPro – Text
- シークバー – Slider
- 各種操作ボタン – Button
- ミュート指定 – Toggle
また、例では、UI操作用に以下スクリプト経由で独自VideoPlayerの動画再生を操作するものとします。
サンプルスクリプト(クリックで開きます)
スクリプトの適用
ここまで実装したスクリプトをオブジェクトに適用していきます。
まず、独自VideoPlayerであるCustomVideoPlayerスクリプトを適当なゲームオブジェクトに追加します。
そして、インスペクターより以下項目を設定してください。
- Url – 動画のURL
- Is Muted – ミュートの有無
- Target Render Texture – 出力先のRenderTexture
- SRGB To Linear Shader – sRGBからLinearに色空間を変換するシェーダー
このCustomVideoPlayerを操作するためのVideoControlUIスクリプトを適当なゲームオブジェクトに追加します。
例では、Canvasに追加するものとしました。
そして、インスペクターより各種要素をアタッチしてください。
UIの指定は任意です。
- Video Player – Custom Video Playerコンポーネント
- Url Input Field – URLの入力欄
- Slider – シークバーのSlider
- Time Text – 再生時刻表示用テキスト
- Mute Toggle – ミュート指定のToggle
最後に、各種ボタンのOn ClickイベントにCustomVideoPlayerのメソッドを登録して完了です。
例では、以下ボタンにそれぞれメソッドを割り当てることとしました。
- Play – CustomVideoPlayer.Play()
- Stop – CustomVideoPlayer.Stop()
- Pause – CustomVideoPlayer.Pause()
- StepForward – CustomVideoPlayer.StepForward()
実行結果
設定したUIで動画再生を操作できるようになりました。
Unityエディタ時では、Unity標準のVideoPlayerコンポーネントが追加されていることが確認できます。
スクリプトの説明
2つ目の例では、WebGLビルド時とそれ以外の場合で挙動が異なるため、次のようなクラス設計にしました。
動画再生に関わるロジックを疎結合にすることで、将来的には異なるプラットフォームへのネイティブ対応も視野に入れた設計にしています。
WebGLビルド時のjslibの実装は1つ目の例と基本的に一緒ですが、操作用の関数が新たに加わっています。
その中で特に厄介なのがコマ送りの実装で、video要素の仕様上フレームレートを取得できません。そのため、Unity標準のVideoPlayerの実装に倣って24フレーム固定として処理しています。
また、動画の準備が完了していない場合はシークできないため、video要素のreadyStateプロパティの値をチェックしています。
これは、メディアの準備状態を返すプロパティで、値がHTMLMediaElement.HAVE_METADATA(=1)以上であればシーク可能とみなすことが出来ます。
参考:HTMLMediaElement: readyState プロパティ – Web API | MDN
シークはcurrentTimeプロパティに値を指定することで行えます。
参考:HTMLMediaElement: currentTime プロパティ – Web API | MDN
どの操作インタフェースを使うかは、CustomVideoPlayerクラスの初期化処理でプラットフォーム環境に応じて分岐しています。
さいごに
本記事では、Unity標準のVideoPlayerの内部実装を参考に、WebGL環境における動画再生の独自実装ができることを示しました。
リニアワークフローの場合は受け取った画像データをそのままレンダリングすると、色空間の不一致で色味がおかしくなるため、シェーダーによる変換が必要です。
Unity標準のVideoPlayerでは、この色空間の変換にも対応しているので、特別な事情が無い限りこれで事足りる場面も多いかもしれません。