# boss-web-player-sdk

A unified, high-performance OTT web video player SDK for adaptive streaming — HLS/DASH, token-based DRM, dynamic watermarking, captions, chapters, casting, and premium UI controls.

> **Heads up:** the high-level `<BossVideoPlayer>` component is **tenant-gated**. It validates your GoBOSS player key before playback starts, and resolves its feature set (subtitles, DRM, chromecast, etc.) from your plan. Obtain a player key from the GoBOSS dashboard and supply it from your app config (never commit real keys). For a generic, ungated player use the [headless core entry](#headless-core-no-react).

## Features

- **Adaptive streaming** — HLS (bundled `hls.js`) and DASH (bundled `dashjs`) with automatic quality switching
- **DRM** — token-based authorization, FairPlay / Widevine, dynamic watermarking
- **Rich controls** — quality, playback speed, subtitles, PiP, fullscreen, chapters/key-moments
- **Casting** — Chromecast and AirPlay
- **Server-side ads (SSAI)** — AWS MediaTailor ad-break tracking and skip-ad flow
- **Episodic playback** — Skip Intro and an auto-advancing Next Episode chip, for series content
- **Analytics** — first-party session/event telemetry, plus an `onAnalyticsEvent` callback
- **Framework-agnostic** — React component, a vanilla `createPlayer()` mount, and a zero-React headless core

## Installation

```bash
npm install boss-web-player-sdk
```

### Peer dependencies

| Package | Required? | Notes |
|---|---|---|
| `react`, `react-dom` (`^18` or `^19`) | **Required** for the React component & `createPlayer` | Not needed if you only use the headless `/core` entry |

> **DASH works out of the box.** `dashjs` is bundled into the SDK (lazily, like `hls.js`) — you do **not** need to install it separately. The DASH engine is only fetched by the browser when a `.mpd` stream actually plays, so HLS-only apps never download it.

### CSS — required, import once

> **The player stylesheet must be imported separately.** The SDK ships CSS as a standalone
> file so your bundler can handle it correctly (critical for SSR frameworks like Next.js).
> Forgetting this import will result in a broken, unstyled player.

```ts
import 'boss-web-player-sdk/css';
```

Import it once at your app entry point (e.g. `main.tsx`, `_app.tsx`, `layout.tsx`) and it
applies globally — you do not need to import it in every file that uses `<BossVideoPlayer>`.

## Quick start (React)

```tsx
import { BossVideoPlayer } from 'boss-web-player-sdk';
import 'boss-web-player-sdk/css';

export default function App() {
  return (
    <BossVideoPlayer
      playerKey={process.env.NEXT_PUBLIC_PLAYER_KEY!} // from your app config / env (required)
      src="https://cdn.example.com/video.m3u8" // HLS (.m3u8) or DASH (.mpd)
      title="My Video"
      themeColor="#6366f1"
      autoplay
      muted
    />
  );
}
```

The component shows a loading spinner while it validates the player key, then renders the
player. If the key is invalid or the API is unreachable, it renders an error state instead.

## Next.js

The SDK works in Next.js (both Pages Router and App Router). The player is entirely
client-side — it uses browser APIs (`HTMLVideoElement`, `window`, `navigator`) that do not
exist on the server.

**App Router** — mark any file that imports the player as a Client Component:

```tsx
// app/watch/page.tsx
'use client';

import { BossVideoPlayer } from 'boss-web-player-sdk';

export default function WatchPage() {
  return (
    <BossVideoPlayer
      playerKey={process.env.NEXT_PUBLIC_PLAYER_KEY!}
      src="https://cdn.example.com/video.m3u8"
      autoplay
      muted
    />
  );
}
```

Import the CSS in your root layout so it loads once for the whole app:

```tsx
// app/layout.tsx
import 'boss-web-player-sdk/css';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return <html><body>{children}</body></html>;
}
```

**Pages Router** — import the CSS in `_app.tsx` (or `_app.js`):

```tsx
// pages/_app.tsx
import 'boss-web-player-sdk/css';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}
```

No `'use client'` directive is needed in the Pages Router — all pages are client-rendered
by default.

## `<BossVideoPlayer>` props

```ts
interface BossVideoPlayerProps {
  // ── Required ──────────────────────────────────────────────
  playerKey: string;                 // from your GoBOSS dashboard — supply via app config/env
  src: string;                       // HLS (.m3u8) or DASH (.mpd) URL

  // ── Identity / theming ────────────────────────────────────
  title?: string;                    // Title shown in the player UI
  watermark?: string;                // Watermark text stamped on the video
  themeColor?: string;               // Accent color (hex). Default '#6366f1'
  licenseKey?: string;               // Required by DRM-enabled plans (presence-checked)

  // ── Networking ────────────────────────────────────────────
  apiBaseUrl?: string;               // API base override: reverse proxy path, or the staging host
  proxyDomains?: string;             // CORS proxy map, "host=/path,host2=/path2" (see CORS)

  // ── Ads (AWS MediaTailor SSAI) ────────────────────────────
  adTrackingUrl?: string;            // Ad-break tracking URL
  overlayAdUrl?: string;             // Overlay ad tracking URL
  enableSkipAd?: boolean;
  skipAdAfterSeconds?: number;

  // ── Series / episodic ─────────────────────────────────────
  introStartTime?: number;           // Content-relative intro start, in seconds
  introEndTime?: number;             // Content-relative intro end, in seconds — Skip Intro seeks here
  hasNextEpisode?: boolean;          // Whether another episode is available to auto-advance into
  outroStartTime?: number;           // Content-relative outro/credits start, in seconds — reveals the Next Episode chip
  nextEpisodeLabel?: string;         // Label on the Next Episode chip. Default 'Next Episode'
  nextEpisodeAutoAdvanceSeconds?: number; // Seconds before auto-advancing once the chip is shown. Default 5
  onNextEpisode?: () => void;        // Called on chip click, or when the auto-advance timer fires

  // ── Behavior / UI ─────────────────────────────────────────
  autoplay?: boolean;                // Default false
  muted?: boolean;                   // Default false (use with autoplay — browsers block unmuted autoplay)
  autoFullscreen?: boolean;
  enableControls?: boolean;          // Default true
  chaptersDisplayMode?: 'strip' | 'panel';   // Default 'strip'
  volumeControlType?: 'slider' | 'button';    // Default 'slider'
  subtitleStyle?: { size?: 'small'|'medium'|'large'; background?: 'none'|'dim'|'solid'; color?: string };

  // ── Integrations ──────────────────────────────────────────
  drmConfig?: DRMConfig;             // DRM license / token configuration

  // ── Callbacks ─────────────────────────────────────────────
  onAnalyticsEvent?: (event: AnalyticsEvent) => void;   // Every analytics event
  onKeyMomentsLoaded?: (keyMoments: Chapter[]) => void; // When chapters parse from the stream
  onTimeUpdate?: (currentTime: number) => void;         // Every timeupdate — use to persist watch progress

  // ── Resume playback ───────────────────────────────────────
  initialTime?: number;               // Seek to this position (seconds) once the video is ready
}
```

### Skip Intro / Next Episode (series)

Both are driven entirely by props — the SDK owns the timing, the chip UI, and the seek/auto-advance
behavior. All times are **content-relative** (the original episode timeline), so they stay correct
even if the stream has spliced ad breaks.

```tsx
<BossVideoPlayer
  playerKey={playerKey}
  src="https://cdn.example.com/episode-04.m3u8"
  introStartTime={0}
  introEndTime={78}                 // Skip Intro shows between 0s–78s, seeks to 78s on click
  hasNextEpisode={true}
  outroStartTime={1230}             // Next Episode chip appears once playback reaches 1230s
  nextEpisodeLabel="S2 · E5"        // optional — defaults to "Next Episode"
  nextEpisodeAutoAdvanceSeconds={5} // optional — auto-advances 5s after the chip appears
  onNextEpisode={() => loadNextEpisode()}
/>
```

> Feature availability (subtitles, chromecast, DRM, multiple resolutions, etc.) is **resolved from your
> plan** by the GoBOSS API at init — it is not toggled by individual props on this component.

## Use without JSX — `createPlayer()`

For Vue, Angular, Svelte, or vanilla JS. Renders the same gated `<BossVideoPlayer>` into any element.

```ts
import { createPlayer } from 'boss-web-player-sdk';
import 'boss-web-player-sdk/css';

const player = createPlayer(document.getElementById('player')!, {
  playerKey, // from your app config / env
  src: 'https://cdn.example.com/video.m3u8',
  autoplay: true,
  muted: true,
});

player.update({ src: 'https://cdn.example.com/other.m3u8' }); // re-render with new props
player.destroy();                                             // unmount & release resources
```

## Headless core (no React)

Import from the `/core` subpath for a zero-React, zero-UI player and modules. This entry is **not**
player-key gated — you drive everything yourself.

```ts
import { VideoPlayerSDK } from 'boss-web-player-sdk/core';

const player = new VideoPlayerSDK({
  container: document.getElementById('player')!,
  src: 'https://cdn.example.com/video.m3u8',
  autoplay: false,
  muted: false,
});

// Playback
player.play();
player.pause();
player.seek(30);
player.setVolume(0.8);
player.setMute(false);
player.setPlaybackRate(1.5);
player.setQuality(qualityId);     // id from a `qualitylevels` event entry

// Events (EventEmitter-style)
player.on('play', () => {});
player.on('qualitychange', (q) => {});
player.on('subtitlesloaded', (tracks) => {});

player.destroy();                 // clean up
```

**Emitted events:** `play`, `pause`, `ended`, `timeupdate`, `seeking`, `buffering`, `volumechange`,
`ratechange`, `pipchange`, `error`, `qualitylevels`, `qualitychange`, `subtitlesloaded`,
`audiotracksloaded`, `audiotrackchange`, `keymomentsloaded`, `chaptersloaded`, `adbreaksloaded`,
`adbreakchange`.

The `/core` entry also exposes the individual modules: `AnalyticsModule`,
`SubtitlesModule`, `ThumbnailsModule`, `KeyMomentsModule`, `ChaptersModule`, `CastingModule`,
`DRMModule`, and `BasePlayerPlugin`.

## CORS & proxying

The player fetches the manifest and media segments **from the browser**, so those origins must send CORS
headers. Two tools help:

- **`apiBaseUrl`** — optional override for GoBOSS API calls. Use it for a same-origin reverse proxy
  during local development, or to point a non-production build at the staging API
  (`apiBaseUrl="https://dev-api.go-boss.com"`). Omit it in production, where the default is the
  production API host.
- **`proxyDomains`** — a comma-separated `"<host>=/<proxyPath>"` map. Any stream URL whose host matches is
  rewritten to `<proxyPath><pathname><search>`, and applied to the manifest **and** every segment/key
  request. Back each `<proxyPath>` with a same-origin dev proxy.

```tsx
<BossVideoPlayer
  playerKey={playerKey}
  src="https://master.example.com/index.m3u8"
  apiBaseUrl="/api-proxy"
  proxyDomains="segments.example.com=/seg-proxy,master.example.com=/mt-proxy"
/>
```

> Proxying is a **development** convenience. For production, configure CORS on the content origin
> (S3 / CloudFront / MediaTailor) — you cannot proxy every viewer's traffic.

## DRM

```ts
const drmConfig = {
  authToken: 'bearer-token',          // sent with license/segment requests
  watermarkText: 'Your Brand',
  fairplayLicenseUrl: 'https://...',  // Safari / Apple
  widevineServiceUrl: 'https://...',  // Chrome / Android
};
```

`licenseKey` is a separate, plan-level gate: when your resolved plan has DRM enabled, a non-empty
`licenseKey` must be provided or the player shows a "Playback restricted" overlay.

## Analytics

Every player event flows through `onAnalyticsEvent`:

```tsx
<BossVideoPlayer
  playerKey={playerKey}
  src="https://cdn.example.com/video.m3u8"
  onAnalyticsEvent={(e) => console.log(e.eventType, e)}
/>
```

To forward events anywhere else — your own warehouse, a third-party analytics product — use
`onAnalyticsEvent` and send them on from there. The SDK reports session and event telemetry to the
GoBOSS backend on its own; `onAnalyticsEvent` is purely additive and does not replace it.

## Keyboard shortcuts

| Key | Action |
|---|---|
| `Space` / `K` | Play / Pause |
| `J` / `←` | Seek back 10s |
| `L` / `→` | Seek forward 10s |
| `↑` / `↓` | Volume ±10% |
| `M` | Mute toggle |
| `F` | Fullscreen toggle |
| `P` | Picture-in-Picture toggle |
| `C` | Subtitles toggle |

## Exports

```ts
// React component (gated, drop-in)
import { BossVideoPlayer, type BossVideoPlayerProps } from 'boss-web-player-sdk';

// Framework-agnostic mount
import { createPlayer, type PlayerProps, type PlayerInstance } from 'boss-web-player-sdk';

// Lower-level React UI / headless core
import { WebVideoPlayer, VideoPlayerSDK } from 'boss-web-player-sdk';

// Advanced: raw SDK API calls
import { initSdk, startSession, sendHeartbeat, endSession, sendEvent } from 'boss-web-player-sdk';

// Types
import type {
  AnalyticsEvent, AnalyticsCallback, Chapter, KeyMoment, SubtitleTrack,
  VideoQuality, ThumbnailConfig, DRMConfig, PlayerSDKOptions,
  FeatureFlags, SubtitleStyle,
} from 'boss-web-player-sdk';
```

## Browser support

| Browser | Version |
|---|---|
| Chrome / Edge | 90+ |
| Firefox | 88+ |
| Safari (incl. iOS) | 14+ |
| Android Chrome | 90+ |

## Module formats

Ships **ES Module** (`dist/index.js`) and **CommonJS** (`dist/index.cjs`) builds with bundled TypeScript
definitions and an extracted stylesheet (`boss-web-player-sdk/css`). `react` and `react-dom` are
externalized (see peer dependencies); `hls.js` and `dashjs` are bundled (lazily).

## License

MIT — see [LICENSE](./LICENSE).
