【VRChat】動画プレイヤーを置く:全員で同じ場面を見るスクリーン

作成: 2026-09-08最終更新: 2026-09-09

スクリーンに動画を流し、あとから入った人も同じ場面から見られるようにします。同期するのはURLと開始時刻だけであること、読み込みを待ってから位置を合わせる理由を解説します。

部屋にスクリーンを置いて、みんなで同じ動画を見たい。

自分の画面では再生できました。ところが友達と入ると、相手の画面では最初から流れています。あとから入ってきた人にいたっては、全員がもう20分見ているのに0秒から始まります。

動画は同期変数のようには共有されません。この記事では、入ってきた時間が違っても、全員が同じ場面を見ている スクリーンを作ります。

2人が同じ場面のスクリーンを並んで見ている

この記事でわかること

  • 配られるのはURLと開始時刻だけであること
  • 読み込みを待ってから位置を合わせる理由
  • URLを実行中に作れない理由
  • 部品のつなぎ方

ネットワーク同期入門 を試した状態から始めます。

Sponsored


映像は配られない。配られるのは待ち合わせ場所

まず、いちばん大事なところです。

映像そのものは共有されません。 各自のパソコンが、それぞれ動画を取りに行って、それぞれ再生します。ワールドが配っているのは、待ち合わせの約束だけです。

共有するのはURLと開始時刻だけで、映像は各自が取りに行く

同期変数として持つのは、この3つだけです。

同期する値意味
再生中かどうかいま流れているか
どのURLか何を流しているか
開始したサーバー時刻「0秒の地点」がいつだったか

3つ目が仕掛けの中心です。開始した時刻さえ分かれば、いま何秒目を見るべきかは、各自が引き算で出せます。

いま見るべき秒数 = いまのサーバー時刻 - 開始したサーバー時刻

20分前に始まった動画なら、あとから入った人は「1200秒目」と計算して、そこへ飛べばよいわけです。

サーバー時刻を使うのがポイントです。各自のパソコンの時計はばらばらですが、Networking.GetServerTimeInMilliseconds() はVRChatのサーバーが持つ共通の時計を返します。全員が同じ物差しで測れます。

URLについても、押さえておくことがあります。

PlayURL() に渡すのは文字列ではなく VRCUrl という型です。しかも、実行中に文字列から作ることができません。

// これでは再生できない
videoPlayer.PlayURL(new VRCUrl("https://example.com/movie.mp4"));

安全のための制限です。ワールドの中で自由にURLを組み立てられると、作者の意図しない場所へ接続させられます。使える入手経路は2つだけです。

  • インスペクタに置いた VRCUrl(この記事はこちら)
  • VRC Url Input Field にプレイヤーが貼ったURL
Sponsored

読み込みが終わるまで、位置は動かせない

「開始時刻から引き算して SetTime() すればいい」。考え方は正しいのですが、呼ぶ場所を間違えると効きません。

読み込み前のSetTimeは無視され、Readyのあとなら効く

LoadURL() を呼んだ直後、動画はまだ届いていません。この状態で SetTime(1200) としても、動かす対象がないので何も起こらず、読み込みが終わったあとに 0秒から 流れ始めます。

「あとから入った人だけ最初から見てしまう」の正体が、これです。

正しい順番はこうです。

  1. LoadURL() で読み込みを始める
  2. OnVideoReady() が呼ばれるのを待つ
  3. そこで SetTime() して Play()

OnVideoReady() は「準備できました」という知らせです。位置を触っていいのは、ここから先だけ と覚えてください。

実践:全員で同じ場面を見るスクリーン

ボタンを押すと動画が始まり、あとから入った人も同じ場面から見られるスクリーンを作ります。

1. 画面と音を組み立てる

動画は「読み込む係」「映す板」「鳴らす係」の3つに分かれています。まず映す板から用意します。

Projectウィンドウで右クリックし、「Create → Render Texture」で ScreenTexture を作ります。Sizeを 1280 × 720 にします。

続いて「Create → Material」で ScreenMaterial を作り、Shaderを Unlit/Texture にして、テクスチャの欄に ScreenTexture をドラッグします。

Unlitにするのは、部屋の明るさに影響されないため です。Standardのままだと、暗い部屋で画面まで暗くなってしまいます。

シーンに次を置きます。

名前作り方と設定
Screen3D Object → Quad。(0, 2, 4)、Scale (3.2, 1.8, 1)。マテリアルに ScreenMaterial
VideoRootCreate Empty。(0, 0, 0)
ScreenAudioCreate Empty に Audio Source。(0, 2, 4)、Spatial Blend 0
PlayButtonCube。(0, 1, 2)、Scale (0.4, 0.4, 0.4)
スクリーン・音源・再生ボタンの配置

Quadの 3.2 × 1.8 は16対9の比率です。ここがずれると映像が伸びます。

VideoRoot に「Add Component」で VRC Unity Video Player を追加して、次を設定します。

指定するもの
Target TextureScreenTexture
Target Audio SourceScreenAudio
Auto Playオフ(押されたときに始めたいため)
映す先と鳴らす先を割り当て、Auto Playは切る

Spatial Blend を 0 にしたのは映画館を想定しているからです。部屋のどこにいても同じ音量で聞こえます。クラブのモニターのように「近づくと聞こえる」ほうがよければ、1 にします。

2. コードを書く

Assets/ScriptsSharedScreen を作ります。

using UdonSharp;
using UnityEngine;
using VRC.SDK3.Components.Video;
using VRC.SDK3.Video.Components.Base;
using VRC.SDKBase;

[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class SharedScreen : UdonSharpBehaviour
{
    [SerializeField] private BaseVRCVideoPlayer videoPlayer;
    [SerializeField] private VRCUrl videoUrl;

    [UdonSynced] private bool isPlaying;
    [UdonSynced] private int startServerTime;   // 0秒の地点にあたるサーバー時刻

    // 再生ボタンから呼ぶ
    public void RequestPlay()
    {
        if (!Networking.IsOwner(gameObject))
        {
            Networking.SetOwner(Networking.LocalPlayer, gameObject);
        }
        if (!Networking.IsOwner(gameObject)) return;

        isPlaying = true;
        startServerTime = Networking.GetServerTimeInMilliseconds();
        RequestSerialization();

        StartLoading();
    }

    // 値が届いた人も、同じ処理を通る
    public override void OnDeserialization()
    {
        StartLoading();
    }

    private void StartLoading()
    {
        if (!isPlaying || videoPlayer == null) return;

        // ここではまだ位置を合わせられない
        videoPlayer.LoadURL(videoUrl);
    }

    // 読み込みが終わって、初めて位置を動かせる
    public override void OnVideoReady()
    {
        int elapsedMs = Networking.GetServerTimeInMilliseconds() - startServerTime;
        float seconds = elapsedMs / 1000f;
        if (seconds < 0f) seconds = 0f;

        videoPlayer.SetTime(seconds);
        videoPlayer.Play();

        Debug.Log("[SharedScreen] " + seconds + " 秒から再生");
    }

    public override void OnVideoError(VideoError videoError)
    {
        Debug.LogWarning("[SharedScreen] 再生できません: " + videoError);
    }
}

構造は ネットワーク同期入門 と同じです。押した人が所有権を取り、値を変えて送る。受け取った人は同じ処理を通る。

違うのは、受け取ったあとの処理が2段に分かれている ところだけです。読み込みを始めて、準備ができたら位置を合わせる。動画には時間がかかるので、こうなります。

型を BaseVRCVideoPlayer にしているのも意図があります。あとでAVProに差し替えても、コードを書き直さずに済みます。

3. つなぐ

VideoRoot に Udon Behaviour を追加し、SharedScreen を指定します。

指定するもの
Video PlayerVideoRoot 自身(VRC Unity Video Player)
Video UrlMP4の直リンク

Video Urlには、.mp4 で終わる直リンクを入れます。動画サイトの視聴ページのURLではありません。テスト用なら、公開されているサンプル動画のURLで構いません。

PlayButton には短い中継のスクリプトを付けます。

using UdonSharp;

[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class ScreenPlayButton : UdonSharpBehaviour
{
    [SerializeField] private SharedScreen screen;

    public override void Interact()
    {
        if (screen != null) screen.RequestPlay();
    }
}

4. 2人で確かめる

Build & Test で Number of Clients を 2 にして起動します。

順序操作期待する結果
1Aが再生ボタンを押す少し待って、両方の画面で動画が始まる
2音を聞くどこにいても同じ音量で聞こえる
3Bが退出して、30秒後に入り直すBの画面は30秒目あたりから始まる
4Consoleを見るBの側に「30.x 秒から再生」と出ている

3番目が、この記事でいちばん大事な確認です。 再生ボタンを押していないBが、押した人と同じ場面を見ています。

秒数がぴったり一致しないのは正常です。読み込みにかかる時間の分だけ、わずかにずれます。

Sponsored

うまくいかないときは

  • 画面が真っ黒 → Render Textureがマテリアルに入っているか、Video PlayerのTarget Textureが正しいか確認します
  • 画面が暗い → マテリアルのShaderがStandardのままです。Unlitにします
  • 音が出ない → Target Audio SourceにAudio Sourceを割り当てているか確認します
  • あとから入った人だけ最初からSetTime()OnVideoReady() の外で呼んでいます
  • 何も再生されない → URLが視聴ページになっていないか、.mp4 の直リンクか確認します。OnVideoError のログも見ます
  • 映像が伸びる → Quadの縦横比が動画と合っていません。16対9なら 3.2 × 1.8 のような比にします
  • Unityのエディタでだけ映らない → AVProはエディタで再生できません。Unity Videoに切り替えて試します

おまけ:先に知っておくと良いこと

  • ライブ配信は位置を合わせられない: 生放送には「20分目」という地点がないので、SetTime() は効きません。流して見るだけになります
  • URLの許可には条件がある: 見る側の設定でUntrusted URLを許可していないと再生されません。Publicのインスタンスでは、ワールド側にも許可するドメインの登録が要ります。まずは自分だけのインスタンスで確かめてください
  • ずれの補正は後回しでよい: 長時間見ていると、少しずつずれていきます。定期的に位置を測り直す作りもありますが、最初の1回を合わせるだけで体験は大きく変わります
  • 一時停止を足すなら値を1つ増やす: 停止した秒数を同期変数に持たせて、再開時にそこから計算し直します。仕組みは同じです
  • 使いどころはいろいろ: 映画館ワールド、イベント会場の中継、作品の紹介映像、チュートリアルの再生。どれも同じ形で組めます

まとめ

動画の共有は、映像ではなく待ち合わせの共有です。

  • 同期するのは、再生中かどうか、URL、開始したサーバー時刻の3つ
  • いま見るべき秒数は、各自が引き算で出す
  • 位置を合わせるのは OnVideoReady() のあと。読み込み前の SetTime() は効かない
  • URLは実行中に作れない。インスペクタか入力欄から渡す

作るときの問いかけは、「この動画は、いつ始まったことになっているか」 です。そこさえ共有できていれば、あとは全員が自分で追いつけます。

外の文章を持ってくるなら 外部テキストで掲示板を更新する、外の画像を貼るなら 外部画像でポスターを貼り替える へ進んでください。

VRChat このセクションのノート63