# VideoPlayer

Composable video player built on [media-chrome](https://media-chrome.org)
(Mux, MIT, ~12 KB). One `<MediaController>` shell drives **YouTube**,
**Vimeo**, **HLS**, native **MP4/WebM**, and arbitrary **iframe**
embeds — the source element swaps, the UI stays the same.

```tsx
import { VideoPlayer } from '@djangocfg/ui-tools/video-player';

// Structured source
<VideoPlayer source={{ type: 'youtube', videoId: 'sGbxmsDFVnE' }} />

// Raw URL — auto-classified (YouTube / Vimeo / HLS / MP4 / iframe)
<VideoPlayer source="https://youtu.be/sGbxmsDFVnE?t=90" />
```

## Why media-chrome

Native HTML5 `<video>` can't play YouTube. Hand-rolling the YouTube
IFrame API and keeping it in sync with a custom control bar is fragile.
media-chrome solves both: its `<media-controller>` speaks one protocol,
and provider elements (`<youtube-video>`, `<vimeo-video>`,
`<hls-video>`) plug into the same slot. Our control parts are thin
React wrappers themed through CSS custom properties.

## Sources

`VideoSource` is a discriminated union — pass an object, or a raw URL
string that `parseEmbedUrl` classifies for you.

| `type` | Shape | Engine |
|---|---|---|
| `url` | `{ type:'url', url, mimeType?, poster?, title? }` | native `<video>` |
| `youtube` | `{ type:'youtube', videoId, startTime?, playlistId?, poster?, title? }` | `youtube-video-element` |
| `vimeo` | `{ type:'vimeo', videoId, startTime?, poster?, title? }` | `vimeo-video-element` |
| `hls` | `{ type:'hls', url, poster?, title? }` | `hls-video-element` (native in Safari, `hls.js` elsewhere) |
| `iframe` | `{ type:'iframe', url, allow?, poster?, title? }` | plain `<iframe>` — control bar auto-hidden |

```tsx
import { parseEmbedUrl } from '@djangocfg/ui-tools/video-player';

parseEmbedUrl('https://www.youtube.com/watch?v=ID&t=42s');
// → { type: 'youtube', videoId: 'ID', startTime: 42 }

parseEmbedUrl('https://vimeo.com/76979871');
// → { type: 'vimeo', videoId: '76979871' }

parseEmbedUrl('https://stream.mux.com/abc.m3u8');
// → { type: 'hls', url: '…' }
```

## Props

| Prop | Type | Default | Notes |
|---|---|---|---|
| `source` | `VideoSource \| string` | — | Object or raw URL (auto-parsed). |
| `controls` | `boolean` | `true` | `false` → no built-in control bar. |
| `aspectRatio` | `number \| 'auto' \| 'fill'` | `16/9` | `'fill'` stretches to parent height; `'auto'` keeps intrinsic. |
| `autoPlay` | `boolean` | `false` | Browsers require `muted` for autoplay to start. |
| `muted` / `loop` / `playsInline` | `boolean` | — | Forwarded to the engine. |
| `preload` | `'none' \| 'metadata' \| 'auto'` | — | Native sources only. |
| `crossOrigin` | `'' \| 'anonymous' \| 'use-credentials'` | `'anonymous'` | Native sources only. |
| `className` | `string` | — | On the `<MediaController>` wrapper. |
| `children` | `ReactNode` | — | Replaces the default control bar entirely. |

## Composable layout

Bring your own control bar by passing `children`. Compose any
media-chrome element or our restyled parts:

```tsx
import {
  VideoPlayer, ControlsBar, PlayButton, SeekBar,
  Volume, PlaybackRate, Pip, Fullscreen,
} from '@djangocfg/ui-tools/video-player';

<VideoPlayer source={{ type: 'url', url: '/clip.mp4' }}>
  <ControlsBar>
    <PlayButton />
    <SeekBar />
    <Volume />
    <PlaybackRate />
    <Pip />
    <Fullscreen />
  </ControlsBar>
</VideoPlayer>
```

## Parts

`PlayButton` · `SeekBar` · `Volume` · `PlaybackRate` · `Pip` ·
`Fullscreen` · `ControlsBar` · `Poster` — thin wrappers over
media-chrome components, restyled through semantic tokens. Each accepts
the props of the media-chrome element it wraps.

## Theming

media-chrome reads CSS custom properties; `styles/video-player.css`
binds them to ui-core semantic tokens (`--primary`, `--popover`, …) via
`color-mix`, so the player follows light/dark and any active preset.
The video surface itself stays dark (`bg-black`) by convention.

## Notes

- **YouTube/Vimeo branding** — embed providers enforce a minimal logo;
  `modestbranding` / `rel=0` reduce it but it cannot be fully removed.
- **PiP over YouTube** — depends on the embed; the button hides itself
  when the engine reports no support.
- **HLS** — native in Safari; elsewhere `hls-video-element` lazy-loads
  `hls.js` (~110 KB) on first HLS mount only.
- **`LazyVideoPlayer`** — kept as a synchronous alias of `VideoPlayer`
  for back-compat. No lazy boundary is needed; engines tree-shake per
  source type.

## Storybook

`UI Tools / Video Player / Player` — `NativeMp4`, `YouTubeStarWars`,
`YouTubeStarTrek`, `YouTubeAutoParseUrl`, `YouTubeWithTimestamp`,
`Vimeo`, `Sintel`, `HlsStream`, `ComposableLayout`, `Fill`,
`NoControls`, `LazyAlias`.
