Everything lives in `Udonite.Video`; the editor API is `Udonite.Video.Editor.VideoSetup`.

## VideoPlayer

Sits on the same GameObject as VRChat's AVPro or Unity video player, because VRChat sends video
events only to behaviours on that object.

### Inspector

| Field | |
|---|---|
| `player` | The VRChat video player that plays |
| `avPro` | On when `player` is AVPro |
| `capture` | AVPro only: set up by Create Video Player |
| `target` | Unity player only: set up by Create Video Player |
| `speakers` | The AudioSources that volume and mute set |
| `playlist` | Plays when the queue is empty. Optional |
| `autoplay` | Starts the playlist when the instance opens |
| `startLocked` | Opens the instance locked |
| `moderators` | Display names that may always control the player and change the lock |
| `tolerance` | Seconds a client may drift before it seeks back. 1 by default |
| `startVolume` | Each player's volume until they change it, from 0 to 1 |

### Commands

Every command that changes the video checks the lock first. A refused call changes nothing and
shows a notice in `Status` for four seconds.

| Member | |
|---|---|
| `PlayUrl(VRCUrl)` | Plays a link now, keeping the queue |
| `Enqueue(VRCUrl)` | Adds a link to the queue, or plays it when nothing is playing. Up to 20 entries |
| `PlayEntry(int)` | Plays a playlist entry |
| `PlayFromQueue(int)` | Plays a queue entry now and takes it out of the queue |
| `Play()` | Resumes, or starts the next queued or playlist video when nothing is playing |
| `Pause()` | Pauses for everyone. Live streams cannot pause |
| `TogglePause()` | `Pause` while playing, `Play` otherwise |
| `Seek(float)` | Seeks to a fraction of the video, from 0 to 1 |
| `SeekTo(float)` | Seeks to a position in seconds |
| `Skip()` | Plays the next queued or playlist video, or stops |
| `Stop()` | Stops for everyone, keeping the queue |
| `Reload()` | Reloads the link on this client only, and seeks back to the room |
| `SetLooping(bool)` | Loops the current video for everyone |
| `SetLocked(bool)` | Locks or unlocks. Only the instance owner, the master and moderators may |
| `RemoveFromQueue(int)` | Takes one entry out of the queue |
| `ClearQueue()` | Empties the queue |
| `SetVolume(float)` | This player's own volume, from 0 to 1 |
| `SetMuted(bool)` | This player's own mute |

### State

| Member | |
|---|---|
| `int Revision` | Moves whenever anything a UI shows changes, except the clock |
| `int State` | `Idle`, `Loading`, `Playing`, `Paused` or `Failed`, constants on `VideoPlayer` |
| `bool IsIdle`, `IsLoading`, `IsPlaying`, `IsPaused`, `HasFailed` | The same, one at a time |
| `bool HasVideo` | A link is set, whether or not it plays yet |
| `bool IsLive` | Playing a stream with no end, which cannot pause or seek |
| `bool CanSeek` | Playing or paused, and not live |
| `bool Showing` | A picture is on the screens |
| `string Title` | The playlist title, or a short label for a pasted link |
| `string AddedBy` | Who added the current video, or empty for the playlist |
| `string Url` | The current link |
| `string Status` | A notice, a problem, or Loading, Playing, Live, Paused or Nothing playing |
| `string Problem` | Why the last load failed, until the next one |
| `float Position` | Seconds into the video on this client |
| `float Duration` | Its length in seconds, or 0 when it is live |
| `float Progress` | Position over duration, from 0 to 1 |
| `bool Looping`, `bool Locked` | The synced settings |
| `float Volume`, `bool Muted` | This player's own audio |
| `int QueueCount` | Entries waiting |
| `string QueueTitle(int)`, `string QueueAddedBy(int)` | One entry |
| `string NextTitle` | What plays next, from the queue or an unshuffled playlist |
| `int PlaylistEntry` | The playlist entry playing, or `NoEntry` |
| `string OwnerName` | Who owns the player right now |
| `bool CanControl`, `bool CanModerate` | What the local player may do |
| `bool MayControl(VRCPlayerApi)`, `bool MayModerate(VRCPlayerApi)` | The same, for anyone |
| `Texture Picture` | The current frame, for a screen of your own |

## VideoScreen

Put it on any renderer whose material uses `Udonite/Video/Screen`. It letterboxes the video into
`aspect`, the surface's width over its height. Any number of screens can show one player.

## VideoPlaylist

| Field | |
|---|---|
| `urls` | The links, in order |
| `titles` | A title per link. An empty one shows the link |
| `shuffle` | Picks the next entry at random, never the one playing |
| `repeat` | Starts again after the last entry. On by default |

## Parts

Every part has `player`, and derives from `VideoPart`.

| Part | Fields |
|---|---|
| `VideoButton` | `command`, `button`, `icon`, `activeSprite`, `inactiveSprite`, `activeColor`, `inactiveColor`. `Run()` runs the command from code |
| `VideoLabel` | `field`, `text` (TextMeshPro), `legacyText`, `prefix`, `emptyText` |
| `VideoSlider` | `mode` (`Seek` or `Volume`), `slider` |
| `VideoLinkField` | `field`, `enterPlaysNow` |
| `VideoShowWhen` | `condition`, `invert`, `targets`, `spinner`, `spinSpeed` |
| `VideoQueueRow` | `index`, `content`, `title`, `detail`, `detailPrefix`, `playButton`, `removeButton` |

`VideoPart` itself has `Refresh()`, which a subclass must implement, and `Tick()` and `Connect()`,
which it may override. `Connect` runs once when the player is ready, before the first `Refresh`.

## VideoLinks

Plain functions the player uses on link text, usable from your own code too.

| Member | |
|---|---|
| `float StartSeconds(string)` | The start time a link asks for with `t=` or `start=`, or 0 |
| `string YouTubeId(string)` | The video id of any YouTube link shape, or empty |
| `string Label(string)` | The short label a pasted link is shown with |

## Editor

The menu item and the Inspector buttons are wrappers over `VideoSetup`, so an editor script or an
agent can do the same without clicking. Each call returns what it made, and an `Add` call returns
what is already there instead of making a second one.

| Member | |
|---|---|
| `CreatePlayer(Vector3, Quaternion, VideoBackend)` | A full player. `VideoBackend.Unity` for direct files only |
| `AddScreen(VideoPlayer, Renderer)` | Another screen on any renderer |
| `AddControls(VideoPlayer)`, `AddOverlay(VideoPlayer)` | The default panel, or the screen's status overlay, on a player that lacks it |
| `AddPlaylist(VideoPlayer, bool autoplay)` | A playlist on the player |
| `AddToPlaylist(VideoPlaylist, string link, string title)` | One entry |
| `FetchTitles(VideoPlaylist)` | Fills missing titles of YouTube entries from YouTube |
| `SetModerators(VideoPlayer, string[], bool startLocked)` | Who may always control it |
| `ConnectAudioLink(VideoPlayer)` | Points the scene's AudioLink at the speaker |
| `Apply()` | Compiles every Udonite behaviour, so values set from a script reach the Udon programs |

The menu item's path is `VideoMenu.Create`, for `EditorApplication.ExecuteMenuItem`.
