# Video Component

The Video component supports HubSpot-hosted video (via `@hubspot/video-player-core`) and embedded video (oEmbed URLs or raw HTML embeds). Both modes are fully client-side interactive via Islands. HubSpot video includes play analytics tracking to populate key performance metrics.

## Import path

```tsx
import Video from '@hubspot/cms-component-library/Video';
```

## Purpose

The Video component provides a unified interface for rendering video content in HubSpot CMS modules. It handles two distinct video types, each rendered through a dedicated client-side Island:

- **`hubspot_video`**: Renders a HubSpot-hosted video via Mux player with a custom theme (from `@hubspot/video-player-core`). Supports server-side video data prefetching to avoid a client-side fetch on load. Includes play analytics tracking (initial play, seconds viewed, completed play).
- **`embed`**: Renders third-party video via oEmbed URL or raw HTML embed. For oEmbed, supports an optional custom thumbnail with a play button overlay that defers iframe load until clicked.

## Component Structure

```
Video/
├── index.tsx                          # Main component — dispatches to HSVideoIsland or EmbedVideoIsland
├── index.module.scss                  # CSS module for the video wrapper
├── types.ts                           # TypeScript type definitions for all Video types
├── ContentFields.tsx                  # HubSpot field definitions for content
├── StyleFields.tsx                    # HubSpot field definitions for styling
├── serverUtils.ts                     # Server-side video fetch utility (fetchHSVideoServerSide)
├── TrackHSVideoAnalytics.tsx          # Analytics tracking component (used inside HSVideoIsland)
├── hooks/
│   ├── usePageMeta.tsx                # Hook to collect page metadata for analytics
│   ├── useQueryParam.tsx              # Hook to read URL query params client-side
│   └── useUserToken.tsx               # Hook to read HubSpot contact UTK with privacy consent
├── utils/
│   └── videoAnalytics.ts              # Analytics event tracking APIs
└── islands/
    ├── HSVideoIsland.tsx              # Client Island: HubSpot video player + analytics
    ├── EmbedVideoIsland.tsx           # Client Island: oEmbed/HTML embed video
    └── index.module.scss              # CSS module for embed video island styles
```

## Props

```tsx
{
  videoType: 'hubspot_video' | 'embed';            // Which video mode to render
  hubspotVideoParams?: HubSpotVideoParams;         // Required when videoType is 'hubspot_video'
  embedVideoParams?: EmbedVideoParams;             // Required when videoType is 'embed'
  oembedThumbnail?: { src: string; alt?: string }; // Optional custom thumbnail for oEmbed videos
  playButtonColor?: ColorFieldValue;               // Play button color (HubSpot video, and oEmbed with custom thumbnail)
  video?: VideoCrmObject;                          // Server-prefetched video data (optional; falls back to client-side fetch)
}
```

## Server-Side Data Fetching

For HubSpot video, the component supports prefetching video data server-side to avoid a client fetch on page load. Use `fetchHSVideoServerSide` from `serverUtils.ts` inside your module's `getServerSideProps`. Note that `getServerSideProps` is only available in Professional and Enterprise portals.

If `video` is not passed (or `undefined`), `HSVideoIsland` automatically falls back to fetching it client-side via `useVideo` from `@hubspot/video-player-core`.

```tsx
import { fetchHSVideoServerSide } from '../../componentLibrary/Video/serverUtils.js';

export const getServerSideProps = withModuleProps(async ({ fieldValues }) => {
  const video = await fetchHSVideoServerSide(
    fieldValues.videoType,
    fieldValues.hubspotVideo
  );
  return { serverSideProps: { video }, caching: {} };
});
```

## HubSpot CMS Integration

### ContentFields

```tsx
<Video.ContentFields
  videoTypeLabel="Video type"
  videoTypeName="videoType"
  hsVideoLabel="HubSpot video"
  hsVideoName="hubspotVideo"
  embedVideoLabel="Embed using"
  embedVideoName="embedVideo"
  includeOembedThumbnailLabel="Include custom thumbnail"
  includeOembedThumbnailName="includeOembedThumbnail"
  oembedThumbnailLabel="Custom thumbnail"
  oembedThumbnailName="oembedThumbnail"
/>
```

**Fields:**
- `videoType`: ChoiceField (pill display) — choices are `['embed', 'Embedded']` and `['hubspot_video', 'HubSpot hosted']`. Defaults to `'embed'`. Only shown to portals with the `marketing-video` scope (Pro+ tier), or when currently set to `hubspot_video`.
- `hubspotVideo`: VideoField — shown only when `videoType === 'hubspot_video'`. Default overridable via `hsVideoDefault` prop.
- `embedVideo`: EmbedField — supports `oembed` and `html` source types; shown only when `videoType === 'embed'`. Default overridable via `embedVideoDefault` prop.
- `includeOembedThumbnail`: BooleanField (toggle) — shown only for oEmbed videos with a URL; controls visibility of the custom thumbnail image field. Defaults to `false`. Default overridable via `includeOembedThumbnailDefault` prop.
- `oembedThumbnail`: ImageField — shown only when `includeOembedThumbnail` is true. Default overridable via `oembedThumbnailDefault` prop.

**Important:** The `videoType` field defaults to `'embed'` because the field is hidden from portals without the `marketing-video` scope.

### StyleFields

```tsx
<Video.StyleFields
  playButtonColorLabel="Play button color"
  playButtonColorName="playButtonColor"
  playButtonColorDefault={{ color: '#F7761F', opacity: 100 }}
/>
```

**Fields:**
- `playButtonColor`: ColorField — shown for HubSpot video, and for oEmbed video when a custom thumbnail with a URL is set

### Module Usage Example

```tsx
// fields.tsx
import { FieldGroup, ModuleFields } from '@hubspot/cms-components/fields';
import Video from '../../componentLibrary/Video/index.js';

export const fields = (
  <ModuleFields>
    <Video.ContentFields />
    <FieldGroup label="Design" name="groupDesign" tab="STYLE">
      <Video.StyleFields />
    </FieldGroup>
  </ModuleFields>
);
```

```tsx
// index.tsx
import { withModuleProps } from '@hubspot/cms-components';
import { fetchHSVideoServerSide } from '../../componentLibrary/Video/serverUtils.js';
import Video from '../../componentLibrary/Video/index.js';

type ComponentProps = {
  fieldValues: {
    videoType: 'hubspot_video' | 'embed';
    hubspotVideo?: HubSpotVideoParams;
    embedVideo?: EmbedVideoParams;
    oembedThumbnail?: { src: string; alt?: string };
    groupDesign: { playButtonColor: typeof ColorFieldDefaults };
  };
  serverSideProps: {
    video?: VideoCrmObject;
  };
};

export const Component = ({ fieldValues, serverSideProps }: ComponentProps) => {
  const { videoType, hubspotVideo, embedVideo, oembedThumbnail, groupDesign } = fieldValues;

  return (
    <Video
      videoType={videoType}
      hubspotVideoParams={hubspotVideo}
      embedVideoParams={embedVideo}
      oembedThumbnail={oembedThumbnail}
      playButtonColor={groupDesign.playButtonColor}
      video={serverSideProps.video}
    />
  );
};

export const getServerSideProps = withModuleProps(async ({ fieldValues }) => {
  const video = await fetchHSVideoServerSide(
    fieldValues.videoType,
    fieldValues.hubspotVideo
  );
  return { serverSideProps: { video }, caching: {} };
});
```

## Embed Video Behavior

### oEmbed

When `source_type === 'oembed'` and an `oembed_url` is present:

- If the `oembed_response` is pre-populated by HubSpot (typical in production), it is used directly without a client-side fetch.
- If `oembed_response` is absent, `EmbedVideoIsland` fetches it client-side from `/_hcms/oembed`.
- If `oembedThumbnail.src` is provided, the iframe is deferred: a thumbnail + play button is shown, and the iframe (with `autoplay=1`) is only injected on click.  Note: the play button click is a no-op if `oembed_response` has not yet resolved (i.e., `iframeRef.current` is null) — this can occur briefly when `oembed_response` is absent and the client-side fetch is still in progress.
- `playButtonColor` controls the play button overlay SVG fill color via an inline style.

### HTML Embed

When `source_type === 'html'` and `embed_html` is present, the HTML is rendered directly via `dangerouslySetInnerHTML`.

## Play Analytics (HubSpot Video Only)

`HSVideoIsland` renders `TrackHSVideoAnalytics` when video data is resolved. It tracks:

- **Initial play** (`trackPlayEvent`): Fired once on first playback start
- **Seconds viewed** (`trackSecondsViewed`): Reported in 10-second intervals and on page visibility change, using 1-second chunk tracking
- **Completed play** (`trackCompletedPlay`): Fired on video end, visibility change, or when all chunks are viewed (for looped videos)

Analytics require HubSpot tracking code (`_hsq`) and privacy consent to be present. Events will not be tracked if the contact UTK or page metadata is unavailable.

## Best Practices

- **Always use `getServerSideProps` (in Pro/Enterprise accounts) with `fetchHSVideoServerSide`** for HubSpot video modules. This avoids a visible client-side fetch delay on page load. If the server fetch fails, `HSVideoIsland` falls back to fetching client-side automatically.
- **Pass `video` from `serverSideProps`** through to the `Video` component prop — this is how the server-fetched data reaches the Island.
- **Field name alignment**: The `VideoField` field name (default `hubspotVideo`) maps to `hubspotVideo` in `fieldValues`, but the `Video` component prop is `hubspotVideoParams`. Pass `fieldValues.hubspotVideo` as `hubspotVideoParams`.
- **`playButtonColor` visibility**: The play button color field is conditionally shown — only for HubSpot video or oEmbed with a custom thumbnail. Do not expect it to always be visible in the editor.
- **`videoType` default**: The `videoType` field defaults to `'embed'` for all portals. This cannot currently be changed per-scope at field definition time.
