Your own UI

Restyle the default panel, build controls on your own canvas or world objects without code, or write a part in C#.

Updated 2026-10-22

The default panel is not special. It is plain uGUI with six small components on it, each pointing at the VideoPlayer, and anything it does your own UI can do the same way.

Restyle the panel

Everything under the player's Controls object is yours to change: colours, sprites, fonts, layout, which buttons exist. Delete a button and the rest keep working. The parts find nothing by name; each one holds its own references, set in the Inspector.

Build controls without code

Put these on your own canvas, a world-space menu or physical objects, and set player:

Part Does
VideoButton Runs a command from a UI Button, or from Interact on a world object with a collider. Shows its state with an icon and disables itself when the local player may not use it
VideoLabel Writes a field into a TextMeshPro text or a legacy Text, with an optional prefix and a text for when it is empty
VideoSlider A Slider that seeks for everyone, or sets this player's own volume
VideoLinkField A VRCUrlInputField: Enter queues the link, or plays it now with enterPlaysNow
VideoShowWhen Shows objects while a condition holds, and can spin a loading indicator
VideoQueueRow One queue entry: its title, who added it, play now and remove

VideoButton takes a command: TogglePause, Play, Pause, Skip, Stop, Reload, ToggleLoop, ToggleLock, ToggleMute or ClearQueue. Give it an icon and two sprites and it swaps between them, so one button shows play or pause, a padlock shows locked or open. Leave button empty on a world object and pressing it in VRChat runs the command instead.

VideoLabel takes a field: Title, AddedBy, Status, Time, Position, Duration, Remaining, Volume, QueueCount, NextTitle, Owner or Url. Time fields update a few times a second; the rest change only when the player does.

VideoShowWhen takes a condition: Idle, Loading, Playing, Paused, Failed, Live, HasVideo, Showing, CanSeek, Locked, CanControl, CanModerate, Muted, Looping, HasQueue or HasProblem, with invert for the opposite. It shows and hides its targets, so put it on an object that stays active: a part that hides its own GameObject stops updating and never comes back.

A cinema seat with one physical button and a sign above it needs no code at all: a VideoButton on the button with TogglePause, and a VideoLabel on the sign with Title.

Write a part in C#

Derive from VideoPart and implement Refresh. It runs once the player is ready and again whenever anything a UI shows has changed:

public class NowPlayingSign : VideoPart
{
    public TextMeshProUGUI sign;

    protected override void Refresh()
    {
        sign.text = player.IsPlaying ? player.Title : "";
    }
}

Tick runs every frame after it, for things that move on their own, such as a clock. Read Position there and keep the work small.

Call the player from anywhere: PlayUrl, Enqueue, Play, Pause, TogglePause, SeekTo, Skip, Stop and the rest are in the reference. Commands check the lock for you: a locked-out player's call does nothing but show a short notice in Status.

Reading the player from your own code

Read it from a VideoPart and you never have to think about timing: the part waits until the player is ready. Code that reads the player from another behaviour's Start or Update should first check that player.player is set. Commands can be called from anywhere, including Refresh.

Something went wrong Reload