You want a screen in the room so everyone can watch the same video together.
It played on your screen. Then you go in with a friend and it's already running from the start on theirs. And a late joiner starts at zero seconds while everyone else is twenty minutes in.
Video isn't shared the way synced variables are. This article builds a screen where everyone sees the same moment, however differently they arrived.
What You'll Learn
- That only the URL and start time get distributed
- Why you align the position after loading finishes
- Why URLs can't be built at runtime
- How the components connect
Start from having tried networking basics.
The video isn't distributed — the meeting point is
Here's the most important part first.
The video itself isn't shared. Each computer goes and fetches the video and plays it itself. What the world distributes is only the arrangement to meet.

Only these three are held as synced variables.
| Synced value | Meaning |
|---|---|
| Whether it's playing | Is it running right now |
| Which URL | What's playing |
| The server time it started | When the "zero second" point was |
The third is the heart of the mechanism. Knowing when it started, each person can work out what second to be at with subtraction.
seconds to be at = current server time - the server time it started
For a video that started twenty minutes ago, a late joiner computes "1200 seconds in" and jumps there.
Using server time is the point. Each computer's clock differs, and Networking.GetServerTimeInMilliseconds() returns the shared clock held by VRChat's servers. Everyone measures with the same ruler.
There's something to know about the URL too.
What you pass to PlayURL() isn't a string but a VRCUrl. And it can't be built from a string at runtime.
// This won't play
videoPlayer.PlayURL(new VRCUrl("https://example.com/movie.mp4"));
It's a safety restriction. If URLs could be assembled freely inside a world, you could be made to connect somewhere the author never intended. There are only two acceptable sources.
- A
VRCUrlplaced in the Inspector (what this article uses) - A URL a player pasted into a VRC Url Input Field
You can't move the position until loading finishes
"Subtract from the start time and call SetTime()." The idea is right, and calling it in the wrong place does nothing.

Right after calling LoadURL(), the video hasn't arrived. Calling SetTime(1200) in that state has nothing to move, so nothing happens, and once loading finishes it starts playing from zero.
That's the true form of "only late joiners watch from the beginning."
The correct order goes like this.
- Start loading with
LoadURL() - Wait for
OnVideoReady()to fire - Then
SetTime()andPlay()
OnVideoReady() is the "ready" notification. Remember that touching the position is only allowed from there on.
Hands-On: A screen everyone watches together
Build a screen where pressing a button starts the video and late arrivals see the same moment.
1. Assemble the picture and sound
Video splits into three parts: the loader, the surface it's shown on, and the thing that plays the sound. Start with the surface.
Right-click in the Project window and make ScreenTexture with "Create → Render Texture." Set Size to 1280 × 720.
Then make ScreenMaterial with "Create → Material," set its Shader to Unlit/Texture, and drag ScreenTexture into the texture field.
Unlit is chosen so the room's brightness doesn't affect it. Left on Standard, the screen goes dark in a dark room.
Place the following in the scene.
| Name | How to make it, and settings |
|---|---|
Screen | 3D Object → Quad. (0, 2, 4), Scale (3.2, 1.8, 1). Material ScreenMaterial |
VideoRoot | Create Empty. (0, 0, 0) |
ScreenAudio | Create Empty with an Audio Source. (0, 2, 4), Spatial Blend 0 |
PlayButton | Cube. (0, 1, 2), Scale (0.4, 0.4, 0.4) |

The Quad's 3.2 × 1.8 is a 16:9 ratio. Get it wrong and the picture stretches.
Add VRC Unity Video Player to VideoRoot via "Add Component" and set the following.
| Field | What to set |
|---|---|
| Target Texture | ScreenTexture |
| Target Audio Source | ScreenAudio |
| Auto Play | Off (we want it to start on press) |

Spatial Blend is 0 because we're picturing a cinema — the same volume wherever you stand. If you'd rather have "audible as you approach," like a club's monitor, set it to 1.
2. Write the code
Create SharedScreen in Assets/Scripts.
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; // The server time of the zero-second point
// Called from the play button
public void RequestPlay()
{
if (!Networking.IsOwner(gameObject))
{
Networking.SetOwner(Networking.LocalPlayer, gameObject);
}
if (!Networking.IsOwner(gameObject)) return;
isPlaying = true;
startServerTime = Networking.GetServerTimeInMilliseconds();
RequestSerialization();
StartLoading();
}
// Receivers go through the same routine
public override void OnDeserialization()
{
StartLoading();
}
private void StartLoading()
{
if (!isPlaying || videoPlayer == null) return;
// The position can't be aligned yet
videoPlayer.LoadURL(videoUrl);
}
// Only once loading finishes can the position be moved
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] playing from " + seconds + "s");
}
public override void OnVideoError(VideoError videoError)
{
Debug.LogWarning("[SharedScreen] can't play: " + videoError);
}
}
The structure matches networking basics. The presser takes ownership, changes values, and sends. Receivers go through the same routine.
The only difference is that the post-receive work splits into two stages. Start loading, and align the position once it's ready. Video takes time, hence the split.
Typing the field as BaseVRCVideoPlayer is deliberate too. Swapping to AVPro later requires no code rewrite.
3. Wire it up
Add a Udon Behaviour to VideoRoot and set SharedScreen.
| Field | What to set |
|---|---|
| Video Player | VideoRoot itself (VRC Unity Video Player) |
| Video Url | A direct MP4 link |
Put a direct link ending in .mp4 into Video Url — not a video site's watch page URL. For testing, a publicly available sample video URL is fine.
Put a short relay script on PlayButton.
using UdonSharp;
[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class ScreenPlayButton : UdonSharpBehaviour
{
[SerializeField] private SharedScreen screen;
public override void Interact()
{
if (screen != null) screen.RequestPlay();
}
}
4. Check with two people
Launch Build & Test with Number of Clients set to 2.
| Order | Action | Expected result |
|---|---|---|
| 1 | A presses the play button | After a short wait, the video starts on both screens |
| 2 | Listen to the sound | It's the same volume wherever you stand |
| 3 | B leaves and re-enters 30 seconds later | B's screen starts around the 30-second mark |
| 4 | Look at the Console | B's side shows "playing from 30.xs" |
Row 3 is the most important check in this article. B, who never pressed play, sees the same moment as the person who did.
The seconds not matching exactly is normal. It drifts slightly by however long loading took.
Common Pitfalls
- The screen is pitch black → Check that the Render Texture is in the material and the Video Player's Target Texture is correct
- The screen is dark → The material's Shader is still Standard. Use Unlit
- No sound → Check that an Audio Source is assigned to Target Audio Source
- Only late joiners start from the beginning → You're calling
SetTime()outsideOnVideoReady() - Nothing plays → Check the URL isn't a watch page, and that it's a direct
.mp4link. Look at theOnVideoErrorlog too - The picture stretches → The Quad's aspect doesn't match the video. For 16:9, use a ratio like
3.2 × 1.8 - It only fails to show in the Unity editor → AVPro doesn't play in the editor. Switch to Unity Video to test
Bonus: Good to Know Up Front
- Live streams can't be position-aligned: A live broadcast has no "20 minutes in" point, so
SetTime()does nothing. You just watch it as it comes - URL permission has conditions: Playback requires the viewer to have Untrusted URLs allowed in their settings. In public instances, the world also needs its allowed domains registered. Test in an instance of your own first
- Drift correction can wait: Watch long enough and it drifts gradually. There are designs that re-measure the position periodically, and just aligning the first time changes the experience enormously
- Adding pause means one more value: Hold the paused seconds in a synced variable and recompute from there on resume. Same mechanism
- It's useful all over: Cinema worlds, event venue broadcasts, work introduction videos, tutorial playback. All the same shape
Summary
Sharing video means sharing a meeting arrangement, not the picture.
- You sync three things: whether it's playing, the URL, and the server time it started
- Each person works out what second to be at with subtraction
- Align the position after
OnVideoReady().SetTime()before loading does nothing - URLs can't be built at runtime. Pass them from the Inspector or an input field
The question to ask while building is: "When is this video considered to have started?" Share that and everyone can catch up on their own.
To bring in external text, go to updating a board from external text. To display external images, go to swapping posters with external images.