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:

```csharp
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](/docs/video/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`.
