VRChatのSDK3+Udonで動画プレイヤーのPlayback Speedを動的に変更する方法

# ゴール
これ。

https://twitter.com/Yodokor0/status/1523303172399583233

動画のPlayback Speed(再生速度)はSDK2の頃には普通にコンポーネントのプロパティとして存在したのだが、どういうわけかSDK3では廃止されてしまった。
復帰を願うCannyもここに作られている。

https://vrchat.canny.io/feature-requests/p/udon-video-playback-speedrate

が、なんやかんやで無理やり再生速度を変えることができてしまった。
のでここに詳しいやり方を記す。

# 使うもの

+ VRC Unity VideoPlayerコンポーネント
+ ↑を操作するAnimatorコンポーネント
+ ↑のAnimatorを操作するUdon

# やり方

## わかる人向けの要約
+ VRC Unity VideoPlayerコンポーネントは実行時にUnity標準のVideo PlayerコンポーネントをAttachする
+ このVideo PlayerのPlayback SpeedプロパティをAnimatorで操作してやると再生速度が変わる
    + Video PlayerはUdonではNot ExposedなのでAnimatorからしか触れない
    + あと同期も取れてないので値の同期も自分で面倒見ること
+ シーン開始時には存在しないコンポーネントなので、Animator.Rebind()を呼んでから操作すること

## わからん人向け詳細

### ざっくりやること概要
動画プレイヤーの核となるVRC Unity Video Playerコンポーネントは、実行時にUnity標準のVideo PlayerコンポーネントをAttachする。
図のようにコンポーネントが増えるのだ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627722/e0fb379d-0aab-7dcb-7c47-12e7adfd57d4.png)
そこには見ての通りPlayback Speedというプロパティがある。この数値が「何倍の速度で再生するか」なので、例えばこれを2にすれば倍速再生ということになる。
ではどうやって値を操作するか。残念ながら、このVideo PlayerコンポーネントはUdonに解放されておらず、直接操作はできない。しかし、Animatorを使って間接的に操作することができる。

### AnimatorとAnimationを作る
まず、VRC Unity Video PlayerコンポーネントがあるGameObjectか、その親以上の階層のGameObjectに、AnimatorコンポーネントをAttachする。
そして空のAnimator Controllerを作ってセットしておこう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627722/49c4efaa-d11f-8869-9cd1-47831e7f57ea.png)
次に、VRC Unity Video PlayerコンポーネントがあるGameObjectに手動でVideo PlayerコンポーネントをAttachしておく。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627722/f2b0dd42-a4aa-c3fe-a2cb-a5f656f9af96.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627722/000a3bec-7d13-2774-68d2-57a29a032c62.png)
そして、Playback Speedを好きなように操作するAnimationを作る。倍速だけだと面白くないので、試しに無段階調整できるようにしてみた。こんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627722/9be6aa02-62d3-f28f-5f8b-2b7f1944fc9e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627722/d9a6a6c7-9a64-1fd2-e75e-72cf0666878c.png)
で、Animator ControllerにFloat変数を1つ追加して、このAnimationがMotion Timeで動くようにする。
単なるスイッチにするなら普通にBoolで切り替えとかでもいいと思う。お好みで。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627722/ed8710ec-e95a-6a93-5c67-6c32a28726dd.png)

Animationを作り終わったら、手動で追加したVideo Playerコンポーネントは削除しておくこと。
つけっぱなしだと実行時にVideo Playerコンポーネントが2つになってしまい動かない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627722/251e7dfd-1281-5c13-390e-4dce77017c00.png)

### AnimatorをUdonで操作する
ここまで来たらあとはスイッチなりスライダーなりでAnimatorのパラメタを操作するだけ……なのだが1つ注意がある。
Video Playerコンポーネントは実行時に追加されるため、そのままではその追加されたコンポーネントをAnimatorが検知できず、Animationできない。
そこでStart()の中でAnimator.Rebind()というメソッドを呼んでやる必要がある。

https://docs.unity3d.com/ScriptReference/Animator.Rebind.html

それさえ忘れなければあとはいつものようにやるだけである。
例としてuGUIのスライダーから操作するU#を書いてみた。こんな感じ。
```CS

using UdonSharp;
using UnityEngine;
using UnityEngine.UI;
using VRC.SDKBase;
using VRC.Udon;

namespace Yodokorochan
{
    [UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
    public class SliderAnimatorController : UdonSharpBehaviour
    {
        [UdonSynced]
        public float CurrentValue;  // 再生速度は同期しないのでスライダーを同期することで疑似的に同じにする

        [SerializeField]
        private Animator TargetAnimator;
        [SerializeField]
        private string TargetFloatVariable;
        [SerializeField]
        private Slider TargetSlider;

        void Start()
        {
            CurrentValue = TargetSlider.value;
            TargetAnimator.Rebind();    // これが重要！ やらないとAnimationが正しくても動かない
        }

        public void OnSliderValueChanged()  // これをSliderのOnValueChangedから呼ぶ
        {
            Networking.SetOwner(Networking.LocalPlayer, gameObject);
            CurrentValue = TargetSlider.value;
            RequestSerialization();
            UpdateAnimator();
        }

        public override void OnDeserialization()
        {
            TargetSlider.SetValueWithoutNotify(CurrentValue);
            UpdateAnimator();
        }

        private void UpdateAnimator()
        {
            TargetAnimator.SetFloat(TargetFloatVariable, CurrentValue);
        }
    }
}

```

適当にSliderを作ってInspectorからこのUdonの各項目を入れよう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627722/4ef155eb-6dc5-d5e5-690a-dbee9ce82159.png)

### 完成
こうしてできたのが冒頭のこれである。

https://twitter.com/Yodokor0/status/1523303172399583233


# おわりに
「そういえばSDK3の動画プレイヤーに再生速度ないね」ってtweetしたらbd_ちゃんがヒントをくれました。bd_ちゃんは最高！
倍速再生がtwitterでトレンド入りするご時世、 2022/05/09 現在主要なプレイヤーアセットに再生速度変更機能が搭載されていないということは多分まだ知られていないテクノロジーなのだと思ってここに記事にしました。

有力アセット作者各位、何卒よろしくお願いします。

# 2022/05/10 追記
VRChatのサンプル動画プレイヤーだと正常動作したが、[iwaSync3_v3.2.0](https://booth.pm/ja/items/2666275)に仕掛けたところ、何秒かに1回、数秒分巻き戻ってしまうという現象が起きた。
多分同期のために定期的に調整してるっぽい？　その辺の何かが再生速度変更と競合してるっぽい。

[KineL式VideoPlayer](https://kinel.booth.pm/items/2758684)の中の人が捕捉してくれたのでこちらにも期待……！

https://twitter.com/ni_rilana/status/1523470592548425728

また、TwitchのLive Streamを読ませて再生速度を変えたらどうなるか試してみたところ、こちらは反映されなかった。
Liveの時だけ1倍速に戻すとかの処理を考えなくていいということなのでこれは助かるね。

# 2022/05/14 追記
iwaSync3.3.0で倍速機能を搭載して頂けました。やったぜ。みんなアプデしよう！

https://twitter.com/magi_ikuko/status/1525386923455696898