Imaginantia

思ったことを書きます

UdonでStreamPlayerを作る

メモです。

twitchとかの配信系をUdonで流す時にやったことについて書きます。

どっかに既に書かれているのかもしれない。

前提

VRChat Examples/Prefabs/VideoPlayer/UdonSyncPlayer (AVPro).prefab が元々サンプルとして入ってます。

ですがソースを覗いてみるとどうやら通常のビデオ再生用っぽい仕組みが入っていて。

「ownerの再生時刻から一定時間ズレてると再同期」するんですが、ストリームの場合の再生時刻は各ローカルで変わるのでこのままだと「owner以外無限にリロードし続ける」感じになっちゃいます。なってました。

USharpVideoにはその防止はちゃんと含まれてるんですが、なんか音が鳴らなかったりした (私が悪いのかもしれない) しまぁそこまでの機能は要らない。

ということで自分で作ってみることにしました。

基本設定

VRCAVProVideoPlayer が使うメインのコンポーネントです。こいつは適当なところに置いておきます。

諸々 (スクリーン・スピーカー) がpull型の振る舞いをします (たしかにね)。その辺の設定はExamplesのを拾ってくるのが早いと思います。

f:id:phi16_ind:20210101143423p:plain

まぁそうなる。†立体音響†はお好みで。

f:id:phi16_ind:20210101144218p:plain

Use Low Latency はtwitchにはうまく効かなかったみたいです。まぁその辺は詳しい方が居るでしょう…

重要なんですが、まずこの時点でちゃんとストリームは再生されます。それくらい単純に使えるようには出来ています。すごい。

ただ配信が止まったりローカルで不安定なこともあるので、いつでもリロード出来るようにする為にUdonを取り付けます。

 

f:id:phi16_ind:20210101144307p:plain

予め public で VideoPlayer を参照しておく必要が多分あります (GetComponentできなかったので)。

Reloadする為に Url も教えておきました (VRCUrl型)。AutoPlayされるものと同じものにしています。今回は配信先が一定なので全部ベタですけど変わるなら変えればいいと思います。

Textは現在状態表示用です。こういうのは内部で何が起きてるかわからないがちなので気軽に出力できる場所を用意しとくと気持ちが楽になります。私は。

f:id:phi16_ind:20210101144018p:plain

うどん

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

using VRC.SDK3.Video.Components.AVPro;
using VRC.SDK3.Components.Video;

public class StreamPlayer : UdonSharpBehaviour
{
    public VRCAVProVideoPlayer videoPlayer;
    private bool waitForResponse;
    public Text display;
    [Space(10)] public VRCUrl url;
    public void Sync() {
        if(waitForResponse) return;
        waitForResponse = true;
        display.text = "Sync";
        videoPlayer.LoadURL(url);
    }
    public override void OnVideoReady() {
        waitForResponse = false;
        display.text = "Ready";
        videoPlayer.Play();
    }
    public override void OnVideoStart() {
        display.text = "Start";
    }
    public override void OnVideoError(VideoError videoError) {
        waitForResponse = false;
        display.text = $"Error: {videoError}";
    }
    public override void OnVideoEnd() {
        display.text = "End";
    }
}

基本的に勝手にイベントが引かれるのでそれを読むだけです。

LoadUrl でリロードが行われます。成功すると OnVideoReady、失敗すると OnVideoError が呼ばれます。リロード中に LoadUrl すると怒られる (Rate Limit Exceeded) し無意味なのでそれを防ぐために waitForResponse を立てています。

OnVideoReady が来たらさっさと再生を開始。そのうち OnVideoStart が呼ばれます。

配信が終了すると OnVideoEnd が呼ばれます。

これで終わり。

同期ボタン

Sync を実行するUdonを作ります。やるだけです。

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

public class SyncButton : UdonSharpBehaviour
{
    public StreamPlayer streamPlayer;
    public bool local;
    public bool restrictOperator;
    public string operatorName;
    public override void Interact() {
        if(local) streamPlayer.Sync();
        else {
            if(!restrictOperator || Networking.LocalPlayer.displayName == operatorName) {
                streamPlayer.SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, "Sync");
            }
        }
    }
}

ええと、何をしているかというと、「ローカルで同期・グローバルで同期」の切り替えと、「同期機構を特定の人だけが使える仕組み」が入ってます。

ローカルの場合は直接 streamPlayer.Sync を呼べばいいです。グローバルの場合は SendCustomNetworkEvent 経由で全員に飛ばします。

あと特定の人しか使えないようにする為に適当に Networking.LocalPlayer.displayName を拾っています。displayName被りが起きるとヤバい気はしますがまぁ大丈夫でしょう。

「誰でもローカルでリロードする」ボタンと「ワールド主が全員強制リロードさせる」ボタンがあると便利かなという気持ちがあります。配信停止したりすると自動再開とかはしないので、ちゃんとリロードは自前で発火しないといけません。

まぁ機能は各々好きに付け足せばいいと思います。Sync はローカルで作られてるのでどうしようかは外側の自由です。

おわり

簡単でした。自分でつくろうと思ってからは。

VRCSDK2と違ってお節介をしないことが何よりもありがたいです。