Udon Video Sync Player
Overview
There's two main things we need to sync for people to watch videos together - the URL of the video to watch, and the playback time so people are watching things simultaneously. In order to understand how we sync these two items for everyone, including late joiners - let's walk through a scenario that uses this program.
The flow for someone entering a URL is: Become Owner of UdonSyncPlayer Object▸send new url ▸Try to Load & Play Url ▸When Video Starts, send Sync info out ▸Send new Sync info every syncFrequency seconds
The flow for everyone else is: Receive new url value ▸Try to Load & Play Url ▸Receive Sync Info▸Jump to synced time
Someone Loads a URL
When our hypothetical scene loads, let's say there is no video playing yet, and there are two people in the room. Someone pastes a new URL into the Input Field, which triggers the OnURLChanged event which is wired up in the UI. There's a few 'IsValid' calls in here that we use just to make sure we're not trying to call methods on objects that have been destroyed or improperly set up. We'll skip describing these for the rest of this example these to keep the explanations simpler.
The Local Player has just put in a new URL, so we make them the Owner of the program so they can control its variables. We get the URL from the InputField, then call SetProgramVariable on the url symbol with this new value. This works the same as calling set url with "sendChange" enabled, it's just another way to do it, handy to know about if you want to change the variable on another UdonBehaviour. Once we've updated this variable, we call RequestSerialization to ask Udon to update the value of url for everyone else in the world.
Users Get New URL
Since we have a Variable Change event for url in our graph, this event will be triggered whenever the URL is updated, and it will simply try to play the URL.
The Video Starts
This event is triggered locally when the video actually beings playing. We call the same event for the Owner and everyone else - the different logic is handled in UpdateTimeAndOffset.
Update Time and Offset
First, this logic checks whether it's running on the Owner of the object. If it's not, it runs the Resync event instead. If it is on the owner, the we want to sync both where in the video we are, and when we were there. We should be at the very beginning of the video since this is the first time the logic is running, but by saving both of these values, we can use this for future sync updates as well.
We want to sync two numbers to everyone else, and these two numbers are closely related, so we combine them into a single Vector2 variable in order to keep them together and simplify some of the sync logic. We construct a Vector2 where 'x' is the current time of the video and 'y' is the Server Time observed by the owner when they were at that video time. With this info, everyone else can set themselves to a matching time - see Resync below.
After Requesting Serialization of this synced variable, the owner calls SendCustomEventDelayedSeconds to update this value again. They use the variable syncFrequency to determine how long until they update the value. For a very simple approach, this variable can be left at 0 if the owner never pauses, rewinds or fast-forwards the video, and everyone can sync from the start time of the video instead of updating timeAndOffset every so often.
Resync
When non-owners start playing the video or receive an update to the timeAndOffset variable, they can use the data to figure out where to jump to in the video.
For a simple example, let's say the owner was at video-time 0 at server-time 1000.
- Owners sets timeAndOffset to (0,1000).
- You join 45 seconds later and get this value. Your own server-time is 1045, so you jump to 00:45 in the video by finding the difference in the server time (45 seconds) and adding the video-time (0 seconds).
Improvements and Augmentations
We kept this example pretty simple so it would be understandable and upgradeable. There's lots you could do to improve it and share your changes! Here are some ideas:
- Have non-owners wait to play the video until they receive info from the Owner
- Detect stream urls vs videos and turn off syncing
- Handle Video Error events with helpful notes for users
- Only allow certain players to change videos
- Create video playlists
- Create a video Queueing system