CRIWARE Unity Plugin Manual  Last Updated: 2024-04-24
プレーヤークラスを用いた動画再生
CriManaMovieMaterial や CriManaMovieController 等のコンポーネントは、Unity アプリケーションのライフサイクルに合わせた CRI Sofdec の動画再生処理を実装しています。
これらを利用することで、動画再生を簡単に実現できます。

プラグインが用意しているコンポーネントを用いず、プレーヤー( CriWare.CriMana.Player )クラスを用いて同様の実装を独自に行うことも可能です。
アプリケーションの仕様に応じて細かく画面更新のタイミングを制御した場合など、必要に応じて利用しましょう。

本章ではプレーヤークラスを用いた動画再生処理について解説します。

動画再生処理の実装

処理の全体像

プレーヤークラスを用いた動画再生処理は、以下のようなフローで実現されます。
  • プレーヤーを生成する
  • 再生対象のファイル/データを設定する
  • 再生を開始する
  • プレーヤーの状態とフレームを定期的に更新する
  • 再生を停止し、プレーヤーを破棄する

プレーヤーを生成する

スクリプトから CriWare.CriMana.Player を生成します。
再生を管理するクラスでインスタンスを保持するようにしてください。

CriMana.Player player;
void SetupPlayer()
{
player = new CriMana.Player();
}

再生対象のファイル/データをプレーヤーに設定する

プレーヤーに対して再生するファイル、またはメモリ上のデータを設定します。
メモリ上のデータを設定する場合は、ガベージコレクションによるメモリ移動を避けるため、明示的にメモリ固定を行ってください。
// ファイルパスを指定する場合
void SetFilePath()
{
player.SetFile(null, System.Path.Combine(Application.temporaryCachePath, "FileName.usm"));
}

System.Runtime.InteropServices.GCHandle gcHandle;
// メモリ上のデータを指定する場合
void SetData()
{
gcHandle = System.Runtime.InteropServices.GCHandle.Alloc(buffer, System.Runtime.InteropServices.GCHandle.Pinned);
player.SetData(gcHandle.AddrOfPinnedObject(), buffer.LongLength);
}

プレーヤーに再生を命令する

プレーヤーに対して再生準備を命令し、準備完了に合わせて再生を開始します。
CRI Sofdec による動画再生は、再生開始命令( Start 関数)から実際に描画ができるようになるまでにタイムラグがあります。
そのため、描画準備も含めた再生準備命令( PrepareForRendering 関数 )を呼び出した上で、Start 関数呼び出しと描画有効化を同時に行うように実装することをおすすめします。

IEnumerator StartPlaybackVideo()
{
// 再生準備
player.PrepareForRendering();
var status = player.status;
// 準備完了を待つ
while (status != CriWare.CriMana.Player.Status.ReadyForRendering)
{
if (status == CriWare.CriMana.Player.Status.Error)
{
player.Stop();
yield break;
}
yield return null;
// 状態更新
UpdatePlayer();
status = player.status;
}
// 再生開始と表示の開始
player.Start();
SetMaterialToTargetObject();
}

プレーヤーの状態とフレームを更新する

プレーヤークラスは動画の再生に関して以下のような処理を行っています。
  1. プレーヤーの状態更新
  2. テクスチャの描画更新
  3. マテリアルへの描画反映
これらに対応するように、以下のように更新関数を呼び出してください。
更新関数は、「再生準備中」「再生中」「再生終了処理中」のあらゆる状態において、MonoBehaviour.Update 内などで定期的に呼び出す必要があります。

Material material;
void UpdatePlayer()
{
// 1. プレーヤーの状態更新
player.Update();
// 2. テクスチャの描画更新
player.OnWillRenderObject(null);
// 3. マテリアルへの描画反映
isMaterialUpdated = player.UpdateMaterial(material);
}

CriWare.CriMana.Player.UpdateMaterial 関数の戻り値は、引数として渡した Material に対して適切な設定やテクスチャの反映がなされたかを表します。
この戻り値が true であれば、Material を描画に利用しても問題ありません。
false の場合は不正な描画がなされる可能性があるので表示しないように注意してください。

UnityEngine.UI.Graphic target;
Material originalMaterial;
void SetMaterialToTargetObject()
{
// UpdateMaterial が成功した状態のときに呼び出すべし
UnityEngine.Debug.Assert(isMaterialUpdated);
if (originalMaterial == null)
{
// 元の Material を記憶
originalMaterial = target.material;
}
target.material = material;
}

再生を停止し、プレーヤーを破棄する

CriWare.CriMana.Player.Stop 関数を呼び出すことで、動画の再生を停止します。
再生開始時と同じく停止完了までにもタイムラグがあるため、状態を確認するように注意してください。
また、Stop 関数から始まる停止処理は描画に関わるリソースの破棄も行うことから、呼び出した時点から Material の状態は不定になります。
Stop 関数の呼び出しと同時に Material を利用した表示も無効にしてください。

停止状態のプレーヤーは再利用することもできますが、利用が終了した場合は明示的に CriWare.CriMana.Player.Dispose 関数を呼び出して破棄してください。

void StopAndDisposePlayer()
{
// 停止命令と同時に Material の表示を無効にする
player.Stop();
ResetMaterialOfTargetObject()
var status = player.status;
// 停止完了を待つ
while (status != CriWare.CriMana.Player.Status.Stop)
{
yield return null;
// 状態更新
UpdatePlayer();
status = player.status;
}
// プレーヤーを破棄する
player.Dispose();
player = null;
if (gcHandle.IsAllocated())
{
gcHandle.Free();
}
}
void ResetMaterialOfTargetObject()
{
if (originalMaterial != null)
{
// 元の Material を設定
target.material = originalMaterial;
originalMaterial = null;
}
}

アプリケーションの状態変化への対応

Unity アプリケーションはユーザー操作やシステムの挙動によって状態が変化します。
安定した動画再生のために、このような状態変化にも適切に対応する必要があります。

ポーズへの対応

動画の再生中にアプリケーションのポーズが発生した場合には、動画再生もポーズする必要があります。
正しくポーズしなかった場合、CRI Sofdec ライブラリが参照する時刻が進んでしまい、アプリケーションのポーズ解除後に動画が早回しになってしまうなどの問題が発生します。

プラットフォームによってはサスペンドとレジュームに応じて特殊な処理を行う必要があるため、プレーヤークラスは専用のポーズ関数( CriWare.CriMana.Player.PauseOnApplicationPause )を用意しています。
こちらを MonoBehaviour.OnApplicationPause 関数内で呼び出すようにしましょう。

void OnApplicationPause(bool flag)
{
player.PauseOnApplicationPause(flag);
}

応用的な使い方

再生停止時も描画を継続したい

先述の通り、 CriWare.CriMana.Player.Stop 関数を呼び出すと Material の描画内容は不定になります。
しかしながら以下のように、動画の再生を停止しても Material の表示を継続したいケースがあります。
  • シークにより再生位置を移動してから再生しなおす
  • 別の動画に切り替えて再生しなおす
このようなケースでは、 CriWare.CriMana.Player.StopForSeek 関数での停止を行ってください。
Material は引き続き表示可能な状態( CriWare.CriMana.Player.UpdateMaterial が true を返す状態)で、その後の再生処理を開始することができます。

注意点として、StopForSeek 呼び出し後に再生するファイルは、停止前のファイルと**コーデックと解像度が同一**となるようにしてください。
異なる場合、内部リソースの再生成が必要となり、不正な描画がなされる可能性があります。

フレームの更新タイミングを細かく制御したい

Sofdec の動画再生では、システム時刻か動画ファイルに含まれる音声の再生時刻に同期して表示されるフレームが更新されます。
ゲーム中の演出によっては、以下のように独自のタイミングでフレームを更新したいケースがあります。
  • 動画ファイル外の音声再生時刻に合わせて再生を進めたい
  • 他の演出と同期させるため、アプリケーションのフレーム更新ごとに1フレームずつ再生を進めたい
このようなケースでは、プレーヤーのマスタータイマー種別を設定した上で、対応する状態更新関数を呼び出してください。

注意点として、マスタータイマー種別をデフォルトから変更して利用する場合は、動画ファイルに音声を含まないようにしてください。
動画フレームと音声の進行が同期しなくなるため、正常に再生が行えなくなる可能性があります。

ユーザー時刻による更新

他モジュールの再生時刻などの独自タイマーと同期して動画を再生したい場合は、ユーザー時刻による状態更新を行います。
通常の Update 関数の代わりに CriWare.CriMana.Player.UpdateWithUserTime 関数を呼び出してください。

// 再生開始前:ユーザー時刻を利用するように設定
player.SetMasterTimerType(CriWare.CriMana.Player.TimerType.User);

// 再生開始時刻を記録
float startTime;
void UpdatePlayer()
{
// 更新時:ユーザー時刻を指定して更新
var playbackTime = Time.time - startTime;
var playbackTimeUlong = (ulong)(playbackTime * 1000.0f);
player.UpdateWithUserTime(playbackTimeUlong, 1000ul);
player.OnWillRenderObject(null);
isMaterialUpdated = player.UpdateMaterial(material);
}

マニュアルタイマーによる更新

毎アプリケーションフレームに必ず1フレームなど、更新の度に固定幅で再生を進めたい場合は、マニュアルタイマーによる更新を行います。
通常の Update 関数の代わりに CriWare.CriMana.Player.UpdateWithManualTimeAdvanced 関数を呼び出してください。
UpdateWithManualTimeAdvanced 関数を呼び出す度に時刻が進むため、再生開始後は適切なタイミングでのみ更新関数を呼び出すように注意してください。

// 再生開始前:マニュアルタイマーを利用するように設定
player.SetMasterTimerType(CriWare.CriMana.Player.TimerType.Manual);
// 再生開始前:マニュアルタイマーの時間更新単位を設定
var fps = (ulong)UnityEngine.Application.targetFrameRate;
player.SetManualTimerUnit(1ul, fps);

void UpdatePlayer()
{
// 更新時:マニュアルタイマーを進めつつ更新
player.UpdateWithManualTimeAdvanced();
player.OnWillRenderObject(null);
isMaterialUpdated = player.UpdateMaterial(material);
}