## @opensite/video

High‑performance, native‑like HTML5 video for the OpenSite ecosystem. It accepts a numeric `mediaId`, fetches the optimal variants from the DashTrack CDN, and renders the best source for the viewer’s device and browser. It upgrades to adaptive streaming on demand to keep initial bundles tiny.

Key points
- Renders a plain `<video>` element (no wrapper, no default styles).
- Accepts all native `<video>` props and ref; drop‑in replacement.
- Chooses the best source: native HLS (Safari) → WebM → MP4.
- Optional streaming upgrade using `hls.js` / `dashjs` via dynamic import.
- Poster behavior: `poster` prop overrides, `false` disables, or defaults to CDN `poster_url`.
  
Custom controls
- Renders custom controls when `controls` prop is set; native `<video controls>` are always hidden.
- Autohides controls on inactivity; shows a centered play button overlay when paused.
- Clicking the poster/video toggles play/pause.

---

## Installation

Core (required):

```
npm i @opensite/video
```

Streaming libraries (optional; only needed for non‑Safari HLS/DASH playback):

```
npm i hls.js dashjs
```

Notes
- `hls.js` and `dashjs` are dynamically imported at runtime only when needed.
- The component works without these packages for progressive playback and for native HLS on Safari/iOS.

---

## Quick Start

Minimal (progressive playback, tiny bundle):

```tsx
import { Video } from '@opensite/video';

export function Hero({ mediaId }: { mediaId: number }) {
  return (
    <Video
      mediaId={mediaId}
      autoPlay
      muted
      loop
      controls={false}
      preload="metadata"
      className="hero-video"
    />
  );
}
```

Upgrade to adaptive streaming only on user interaction (e.g., first play):

```tsx
<Video mediaId={mediaId} controls streamingOnInteraction />
```

Force adaptive streaming immediately (if available):

```tsx
<Video mediaId={mediaId} controls adaptiveStreaming />
```

Prefer a progressive codec/size (optional):

```tsx
<Video mediaId={mediaId} preferCodec="WEBM" preferSize="md" />
```

Poster behavior:
- `poster={"https://…"}` → use provided URL.
- `poster={false}` → disable poster entirely.
- Poster omitted → auto‑use `poster_url` from CDN payload if provided.

---

## Props

Required
- `mediaId: number` — DashTrack media record ID used to fetch the media snapshot.

Optional
- `cdnHost?: string` — Override CDN origin. Default: `https://edge.dashtrack.com`.
- `adaptiveStreaming?: boolean` — Immediately attach HLS/DASH adapter when applicable.
- `streamingOnInteraction?: boolean` — Progressive first, upgrade on first play.
- `streamingUpgrade?: boolean` — Allow upgrades at all (default: true). Set false to force progressive.
- `preferCodec?: 'HLS' | 'DASH' | 'WEBM' | 'MP4'` — Preferred codec when multiple are present.
- `preferSize?: 'sm' | 'md' | 'lg' | 'full'` — Preferred progressive size variant.
- `poster?: string | false` — Poster override or disable; omitted defaults to CDN `poster_url`.
- `onVideoData?: (data: VideoData) => void` — Callback when CDN payload is loaded.
  
- All native `<video>` attributes (e.g., `controls`, `muted`, `playsInline`, `style`, `className`, etc.).

Direct src fallback
- You can omit `mediaId` and provide `src` to play a direct progressive asset (MP4/WebM). In this mode, no CDN request is performed and streaming adapters are not attached.

Ref
- The component forwards a `ref` to the underlying `<video>` element for direct control and event subscriptions.

---

## CDN Integration

- Default fetch URL: `https://edge.dashtrack.com/assets/videos/<mediaId>`.
- Override origin via `cdnHost` prop; path shape is fixed by the module.
- The response is cached at the module level to avoid redundant network calls.

Expected payload (subset)
```ts
type VideoData = {
  id: number;
  name?: string;
  media_type?: string;
  poster_url?: string | null;
  meta?: {
    duration?: number;
    duration_iso8601?: string;
    content_manifest?: {
      optimized_filename?: string;
      title?: string;
      summary?: string;
      description?: string;
    };
  };
  variants_data: {
    variants: {
      HLS?: {
        cdn_master_playlist_url?: string;
        origin_master_playlist_url?: string;
        full_size?: string; // chosen playlist URL (CDN or origin)
        rungs?: { height?: number; width?: number; bandwidth?: number; average_bandwidth?: number; bitrate_kbps?: number; segment_count?: number; encode_seconds?: number }[];
      };
      DASH?: {
        cdn_manifest_url?: string;
        origin_manifest_url?: string;
        full_size?: string; // chosen manifest URL (CDN or origin)
      };
      PROGRESSIVE_MP4?: Partial<Record<'sm'|'md'|'lg'|'full', string>>;
      WEBM?: Partial<Record<'sm'|'md'|'lg'|'full', string>>;
    };
    metadata?: { width?: number; height?: number };
  };
};
```

---

## Source Selection Logic

Order of preference (auto mode)
1. Native HLS (Safari/iOS) via `application/vnd.apple.mpegurl`.
2. Progressive WebM (modern browsers) by preferred/available size.
3. Progressive MP4 (universal fallback) by preferred/available size.
4. If only streaming manifests exist and not natively supported, attach `hls.js` or `dashjs` when allowed.

You can override with `preferCodec` and `preferSize` as hints; the component will still ensure the final result is playable on the current browser.

---

## Streaming Options

- `adaptiveStreaming` — Attach streaming adapter immediately if an HLS or DASH manifest exists and the browser needs an adapter.
- `streamingOnInteraction` — Keep progressive first, then upgrade on first `play` event (recommended to minimize initial JS).
- `streamingUpgrade={false}` — Disable all upgrades and stick to progressive assets.

Adapters (dynamic imports)
- HLS (non‑Safari): `hls.js` via `src/streaming/hls-adapter`.
- DASH: `dashjs` via `src/streaming/dash-adapter`.

These imports happen only when required by the chosen streaming path.

---

## Styling & Layout

- The component renders a custom control wrapper around a `<video>` element.
- The `className` and `style` props are applied to the wrapper container.
- Tailwind classes are used for styling; no additional UI libraries required.

## Accessibility & Metadata (videos)

- `aria-label` maps to `meta.content_manifest.summary` (trimmed to ~120 chars).
- `title` maps to `meta.content_manifest.title` when not provided.
- `poster` defaults to `poster_url` when not provided.
- `preload` defaults to `metadata`.
- `width`/`height` default to `variants_data.metadata.{width,height}` when not provided.
- `controlsList` automatically includes `nodownload` when rendering the download link to avoid the browser’s incorrect default file naming.

## Download behavior

- A separate download link is rendered (by default) next to the `<video>` element.
- The link always points to a progressive asset (prefers WebM, then MP4) and uses a filename derived from `meta.content_manifest.optimized_filename` and the actual file extension.
- This ensures correct file extensions and better analytics/SEO when users or crawlers download the asset.
- You can customize visibility/label/style with `showDownloadLink`, `downloadLabel`, and `downloadClassName` props.

Example
```tsx
<Video mediaId={mediaId} className="w-full h-auto rounded-xl shadow" />
```

---

## SSR & Environments

- All network requests and capability detection happen in `useEffect`, so nothing runs on the server.
- On the server, the component renders a bare `<video>`; sources are applied after mount.
- If immediate poster visibility is required with potential network latency, pass `poster` explicitly so it shows before the CDN payload loads.

---

## TypeScript

Types are exported from the root module:

```ts
import type { VideoData, PreferredCodec, PreferredSize } from '@opensite/video';
```

Useful types
- `VideoData`, `Variants`, `VariantRung`
- `BrowserCapabilities`, `SelectedSource`
- `PreferredCodec`, `PreferredSize`

---

## Tree‑Shakable Exports

Recommended
```ts
import { Video } from '@opensite/video';
```

Sub‑paths (advanced)
- `@opensite/video/core` — capability/source helpers
- `@opensite/video/streaming/hls` — HLS adapter (dynamic import normally handles this)
- `@opensite/video/streaming/dash` — DASH adapter (dynamic import normally handles this)

Package.json sets `sideEffects: false` and an `exports` map for optimal tree‑shaking.

---

## Examples

Hero/Background (progressive only)
```tsx
<Video mediaId={mediaId} autoPlay muted loop controls={false} preload="metadata" streamingUpgrade={false} />
```

Interactive B‑Roll (upgrade on play)
```tsx
<Video mediaId={mediaId} controls streamingOnInteraction />
```

Short‑Form (full streaming)
```tsx
<Video mediaId={mediaId} controls adaptiveStreaming />
```

Custom poster and sizing
```tsx
<Video mediaId={mediaId} poster="https://cdn.example.com/posters/123.jpg" style={{ aspectRatio: '16 / 9', width: '100%' }} />
```

Using a ref
```tsx
const ref = useRef<HTMLVideoElement>(null);

<Video mediaId={mediaId} controls ref={ref} />

// later
ref.current?.play();
```

Accessibility (reduced motion)
```tsx
const prefersReducedMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
<Video mediaId={mediaId} autoPlay={!prefersReducedMotion} muted loop />
```

---

## Browser Support

- Safari/iOS: native HLS supported.
- Chrome/Edge/Firefox: progressive WebM/MP4; optional HLS/DASH via adapters.
- The component automatically chooses the best playable source.

---

## Troubleshooting

- No video appears
  - Check `mediaId` and network tab for the CDN request.
  - Verify your CDN returns at least one progressive asset or a streaming manifest.
- Streaming doesn’t start on non‑Safari
  - Install `hls.js` and/or `dashjs`.
  - Ensure `adaptiveStreaming` or `streamingOnInteraction` is set.
- Poster not shown
  - If `poster` is omitted and the payload has no `poster_url`, no poster will be used.

---

## Architecture Notes

- Capability detection and source selection are in `src/core/`.
- Streaming adapters (HLS/DASH) live in `src/streaming/` and are imported dynamically.
- CDN integration is isolated in `src/utils/api.ts` with lightweight caching.
- The module follows the patterns outlined in `ECOSYSTEM_GUIDELINES.md`.

---

## Contributing

- Keep the component free of wrappers and default CSS.
- Preserve progressive‑first behavior; treat streaming as an upgrade.
- Maintain tree‑shakability and small initial bundle size.
- When extending functionality (e.g., custom controls), ensure features are optional and tree‑shakable.

---

## License

Private module for the OpenSite ecosystem.
