# @vizor-vr/player

High-performance VR and 360° video player for the web, built on Web Components and Three.js. Embed immersive content with a single HTML tag — no framework required.

```html
<script
  type="module"
  src="https://cdn.jsdelivr.net/npm/@vizor-vr/player@0.4.0/dist/vizor.js"
></script>

<vz-video src="video.mp4" format="MONO_360" autoplay></vz-video>
```

For production embeds, pin an exact version (as above) and add an `integrity="sha384-..."` attribute with the hash of that pinned file so a CDN compromise cannot alter the script ([SRI](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity)).

## Install

```bash
npm install @vizor-vr/player
```

```js
// Registers all <vz-*> custom elements as a side effect
import '@vizor-vr/player/register';
```

The `/register` entry is safe to import under SSR — registration no-ops when there is no DOM. The bare `@vizor-vr/player` entry exports classes and types without registering anything.

## Elements

| Element         | Purpose                                       |
| --------------- | --------------------------------------------- |
| `<vz-video>`    | 360°/VR video playback                        |
| `<vz-img>`      | 360°/VR image display                         |
| `<vz-tour>`     | Multi-scene virtual tours                     |
| `<vz-cinema>`   | Flat video in a 3D environment                |
| `<vz-live>`     | Live streaming (HLS / DASH)                   |
| `<vz-playlist>` | Sequential content playback                   |
| `<vz-hub>`      | Content lobby: pick from multiple items in 3D |

Data-holder child elements (no UI of their own — parsed by their parent):

| Element            | Parent                | Purpose                                       |
| ------------------ | --------------------- | --------------------------------------------- |
| `<vz-annotation>`  | `vz-video` / `vz-img` | Interactive 3D marker with HTML popup content |
| `<vz-chapter>`     | `vz-video`            | Seek-bar chapter marker                       |
| `<vz-tour-img>`    | `vz-tour`             | One panoramic scene ("probe") in a tour       |
| `<vz-tour-portal>` | `vz-tour-img`         | Navigation hotspot to another probe           |
| `<vz-hub-item>`    | `vz-hub`              | One selectable content entry in a hub         |

## Attributes

All media elements (`vz-video`, `vz-img`, `vz-tour`, `vz-cinema`, `vz-live`) share a common attribute set. Boolean attributes follow HTML semantics: presence means `true`.

### Content and playback

| Attribute                             | Type    | Default    | Description                                                                       |
| ------------------------------------- | ------- | ---------- | --------------------------------------------------------------------------------- |
| `src`                                 | string  | —          | Direct media URL (alternative to `<source>` children)                             |
| `format`                              | string  | `MONO_360` | Projection format, e.g. `MONO_360`, `STEREO_360_TB`, `STEREO_180_LR`, `MONO_FLAT` |
| `title`                               | string  | —          | Title shown in the controls bar                                                   |
| `author` / `author-href`              | string  | —          | Author credit and link                                                            |
| `poster`                              | string  | —          | Poster image URL shown before playback                                            |
| `preview`                             | string  | —          | Preview image URL                                                                 |
| `autoplay`                            | boolean | `false`    | Start playback automatically (muted-autoplay policies apply)                      |
| `loop`                                | boolean | `false`    | Loop playback                                                                     |
| `muted`                               | boolean | `false`    | Start muted                                                                       |
| `preload`                             | string  | `metadata` | `auto`, `metadata`, or `none`                                                     |
| `seekable`                            | boolean | `false`    | Allow seeking on live/limited streams                                             |
| `fps`                                 | number  | `30`       | Content frame rate hint                                                           |
| `crossorigin`                         | string  | —          | CORS mode for media requests                                                      |
| `cors-fallback-url`                   | string  | —          | Fallback page when CORS blocks playback                                           |
| `cors-fallback-redirect-confirmation` | boolean | `true`     | Confirm before redirecting to the fallback URL                                    |
| `audio-format`                        | string  | —          | Spatial audio layout (ambisonic content)                                          |

### Camera and projection

| Attribute                               | Type   | Default      | Description                                                       |
| --------------------------------------- | ------ | ------------ | ----------------------------------------------------------------- |
| `start-lat` / `start-lon`               | number | `0` / `0`    | Initial camera orientation in degrees                             |
| `focal-length`                          | number | `75`         | Initial focal length (zoom)                                       |
| `min-focal-length` / `max-focal-length` | number | `30` / `120` | Zoom limits                                                       |
| `lens-angle`                            | number | `180`        | Capture-lens angle in degrees for fisheye/VR180 content (120–250) |
| `display-mode`                          | string | `inline`     | Display mode                                                      |
| `width` / `height` / `aspect`           | string | —            | Explicit sizing (CSS sizing on the host element also works)       |

### Licensing, analytics, and delivery

| Attribute      | Type   | Description                                  |
| -------------- | ------ | -------------------------------------------- |
| `api-key`      | string | SaaS API key for license validation          |
| `license-key`  | string | Standalone (offline) license key             |
| `license-mode` | string | License validation mode                      |
| `api-endpoint` | string | Override the license/analytics API endpoint  |
| `content-id`   | string | Content ID for hosted delivery and analytics |

### Collaborative viewing

| Attribute                                | Type   | Description          |
| ---------------------------------------- | ------ | -------------------- |
| `collab-server`                          | string | WebSocket server URL |
| `collab-room`                            | string | Room name            |
| `collab-role`                            | string | `host` or `viewer`   |
| `collab-user-id` / `collab-display-name` | string | Participant identity |
| `collab-password`                        | string | Room password        |

### Branding and UI

| Attribute                                                                                                               | Type    | Default    | Description                                                 |
| ----------------------------------------------------------------------------------------------------------------------- | ------- | ---------- | ----------------------------------------------------------- |
| `primary-color`                                                                                                         | string  | `#f43f5e`  | Accent color for controls                                   |
| `secondary-color`                                                                                                       | string  | `#1a1a2e`  | Secondary/background color                                  |
| `brand-name` / `brand-logo` / `brand-url`                                                                               | string  | —          | Custom branding (licensed tiers)                            |
| `controls-behavior`                                                                                                     | string  | `autohide` | `autohide`, `always`, or `minimal`                          |
| `hide-controls`                                                                                                         | boolean | `false`    | Hide the controls bar entirely                              |
| `hide-compass`, `hide-vr-button`, `hide-settings`, `hide-fullscreen`, `hide-title`, `hide-brand-logo`                   | boolean | `false`    | Hide individual UI pieces                                   |

### Element-specific attributes

| Element            | Attributes                                                                                       |
| ------------------ | ------------------------------------------------------------------------------------------------ |
| `<vz-cinema>`      | `room-src`, `room-format`, `screen-width`, `screen-height`, `screen-distance`, `screen-offset-y` |
| `<vz-playlist>`    | `autoplay` (auto-advance), `loop-playlist`, `panel` (`none` / `expandable`)                      |
| `<vz-annotation>`  | `lat`, `lon`, `time-start`, `time-end`, `title`, `icon`, `sort-order`                            |
| `<vz-chapter>`     | `start`, `title`, `sort-order`, `chapter-id`                                                     |
| `<vz-tour-img>`    | `probe-id`, `format`, `src`, `src-left`, `src-right`, `title`, `rotation-y`                      |
| `<vz-tour-portal>` | `to`, `lat`, `lon`, `title`, `distance`                                                          |
| `<vz-hub-item>`    | `src`, `poster`, `title`, `format`, `content-id`                                                 |

## Events

Elements re-dispatch the standard HTML5 media events (`play`, `pause`, `playing`, `ended`, `timeupdate`, `seeking`, `seeked`, `durationchange`, `volumechange`, `canplay`, `waiting`, `error`, …) from the underlying `<video>` on the custom element itself, plus typed `vz-*` CustomEvents (all bubble and cross the shadow boundary):

| Event                                        | `detail`                          | Emitted when                          |
| -------------------------------------------- | --------------------------------- | ------------------------------------- |
| `vz-ready`                                   | —                                 | Player is initialized and ready       |
| `vz-error`                                   | `{ code, message }`               | An error occurred (load, DRM, XR, …)  |
| `vz-quality-change`                          | `{ quality, auto }`               | Adaptive/manual quality level changed |
| `vz-fullscreen-enter` / `vz-fullscreen-exit` | —                                 | Fullscreen toggled                    |
| `vz-xr-enter` / `vz-xr-exit`                 | —                                 | WebXR immersive session started/ended |
| `vz-license`                                 | `{ valid, tier, message? }`       | License validation resolved           |
| `vz-chapter-change`                          | `{ chapterId, title, startTime }` | Playback crossed into a chapter       |
| `vz-tour-navigate`                           | `{ fromProbeId, toProbeId }`      | Tour navigated between scenes         |
| `vz-hub-select`                              | `{ id, src, title, index }`       | A hub item was selected               |
| `vz-playlist-change`                         | `{ index, title, total }`         | Playlist advanced to another item     |
| `vz-playlist-end`                            | —                                 | Playlist finished (non-looping)       |

```js
const player = document.querySelector('vz-video');
player.addEventListener('vz-ready', () => console.log('ready'));
player.addEventListener('vz-error', (e) => console.error(e.detail.code, e.detail.message));
```

## Methods

Available on the video-based elements (`vz-video`, `vz-cinema`, `vz-live`):

```js
player.play(); // Start or resume (returns a Promise)
player.pause();
player.seek(30); // Seek to 30 seconds
player.toggleFullscreen();
```

`<vz-playlist>` adds `next()`, `previous()`, and `goTo(index)`.

## Features

- Multiple projection formats (equirectangular 360/180, cubemap/EAC, fisheye, flat) with mono and stereo variants
- WebXR immersive sessions (via Three.js)
- Adaptive streaming through optional `hls.js` / `dashjs` peer dependencies
- Ambisonic spatial audio via the optional `omnitone` peer dependency
- Progressive enhancement — degrades gracefully without WebXR

## Optional peer dependencies

Install only what you use:

```bash
npm install hls.js dashjs omnitone @noble/ed25519
```

- `hls.js` — HLS adaptive streaming
- `dashjs` — MPEG-DASH adaptive streaming
- `omnitone` — ambisonic spatial audio
- `@noble/ed25519` — required for standalone (offline) license keys. Without it, standalone license verification fails closed and the player runs in the free tier with a watermark. SaaS (`api-key`) licensing does not need it.

## Framework wrappers

- React — [`@vizor-vr/react`](https://www.npmjs.com/package/@vizor-vr/react)
- Vue 3 — [`@vizor-vr/vue`](https://www.npmjs.com/package/@vizor-vr/vue)
- Svelte 5 — [`@vizor-vr/svelte`](https://www.npmjs.com/package/@vizor-vr/svelte)
- Laravel — [`vizor-vr/laravel-vizor`](https://packagist.org/packages/vizor-vr/laravel-vizor)

## Documentation

Full guides, format reference, and API docs: [docs.vizor-vr.com](https://docs.vizor-vr.com). Source lives in the [vizor-vr monorepo](https://github.com/vizor-vr/vizor-vr) on GitHub.

## Security

See the monorepo [security policy](https://github.com/vizor-vr/vizor-vr/blob/main/SECURITY.md) for how to report vulnerabilities (security@utgnetworks.com).

## License

MIT — see [LICENSE](./LICENSE).
