Files

8.2 KiB

HTML API

HTML-specific concerns: element naming, imports, skins.

Element Naming

Layer Pattern Examples
Player <{mediatype}-player> <video-player>, <audio-player>
Skin <{mediatype}-skin> <video-skin>, <minimal-video-skin>
Media <{source}-{mediatype}> <hls-video>, <dash-video>, <hls-audio>
UI <media-{component}> <media-play-button>, <media-slider>

Special Players

Only fundamentally different behaviors get their own player:

<background-video-player>  <!-- no controls, autoplay, loop -->

Import Paths

Grouped by use case, then by concern:

Video (default)

import '@videojs/html/video/player';       // includes features.video
import '@videojs/html/video/skin';
import '@videojs/html/video/skin.css';

Audio (default)

import '@videojs/html/audio/player';       // includes features.audio
import '@videojs/html/audio/skin';
import '@videojs/html/audio/skin.css';

Background Video

import '@videojs/html/background/player';  // includes features.background
import '@videojs/html/background/skin';
import '@videojs/html/background/skin.css';

Features (additive)

import '@videojs/html/feature/streaming';
import '@videojs/html/feature/ads';
import '@videojs/html/feature/live';
import '@videojs/html/feature/chapters';

// Granular
import '@videojs/html/feature/quality-selection';
import '@videojs/html/feature/audio-tracks';
import '@videojs/html/feature/text-tracks';

Media

import '@videojs/html/media/hls-video';
import '@videojs/html/media/hls-audio';
import '@videojs/html/media/dash-video';

UI Primitives (for custom skins)

import '@videojs/html/ui/menu';
import '@videojs/html/ui/radio-group';
import '@videojs/html/ui/slider';

Registration Models

Side-Effect Imports (Declarative)

Elements register globally when imported:

import '@videojs/html/video/player';
import '@videojs/html/video/skin';
<video-player>
  <video-skin>
    <video src="video.mp4">
  </video-skin>
</video-player>

createPlayer (Programmatic)

For custom element names or full control:

import { createPlayer } from '@videojs/html';
import { features } from '@videojs/core/dom';

const { PlayerElement, PlayerController, context } = createPlayer({
  features: [...features.video],
});

customElements.define('my-video-player', PlayerElement);
<my-video-player>
  <video src="video.mp4">
</my-video-player>

Mixins (Advanced)

When you need to extend behavior or split provider/container:

import { createPlayer, MediaElement } from '@videojs/html';
import { features } from '@videojs/core/dom';

const { PlayerMixin, ProviderMixin, ContainerMixin } = createPlayer({
  features: [...features.video],
});

// Combined player (provider + container)
class MyPlayer extends PlayerMixin(MediaElement) {
  // Custom logic
}

// Or split for advanced cases
class MyProvider extends ProviderMixin(MediaElement) {}
class MyContainer extends ContainerMixin(MediaElement) {}

PlayerElement

The simplest way to create a player element:

import { createPlayer } from '@videojs/html';
import { features } from '@videojs/core/dom';

const { PlayerElement } = createPlayer({
  features: [...features.video],
});

customElements.define('video-player', PlayerElement);

PlayerElement is a complete player that:

  • Creates and manages the store
  • Provides context to descendants
  • Auto-attaches media elements
  • Handles cleanup on disconnect

Skins

Adaptive Skins

Default skin adapts to available features:

import '@videojs/html/video/player';
import '@videojs/html/feature/streaming';  // skin will show quality menu
import '@videojs/html/video/skin';
<video-skin>  <!-- adapts to registered features -->

Named Skins

Specific UI compositions and style variants:

<video-skin>           <!-- default style -->
<minimal-video-skin>   <!-- stripped down -->
<cinematic-video-skin> <!-- dark/immersive -->

Skin Customization

Default skin handles optional feature UI via lazy loading.

Ejected skin structure:

skin/
  video/
    video-skin.ts
    video-skin.css
    features.ts

MediaElement Base Class

Base class for UI primitives. No shadow DOM — the host element IS the control.

import { MediaElement, PlayerController } from '@videojs/html';
import { selectPlayback } from '@videojs/core/dom';

export class MediaPlayButton extends MediaElement {
  #playback = new PlayerController(this, context, selectPlayback);

  override connectedCallback() {
    super.connectedCallback();
    this.addEventListener('click', this.#handleClick);
  }

  #handleClick = () => {
    this.#playback.value?.toggle();
  };

  override update() {
    const playback = this.#playback.value;
    if (!playback) return;

    this.setAttribute('aria-pressed', String(!playback.paused));
  }
}
// define/ui/media-play-button.ts (side-effect import)
import { MediaPlayButton } from '../src/ui/media-play-button.js';

customElements.define('media-play-button', MediaPlayButton);

Usage:

import '@videojs/html/ui/media-play-button';

PlayerController

Reactive controller for accessing player state. Triggers update() when subscribed state changes.

Constructor

// Without selector — store access only, no subscription
new PlayerController(host, context)

// With selector — subscribes, triggers update on change
new PlayerController(host, context, selector)

Example

import { MediaElement, PlayerController } from '@videojs/html';
import { selectVolume } from '@videojs/core/dom';

export class MediaVolumeSlider extends MediaElement {
  #volume = new PlayerController(this, context, selectVolume);

  override connectedCallback() {
    super.connectedCallback();
    this.addEventListener('input', this.#handleInput);
  }

  #handleInput = (e: Event) => {
    this.#volume.value?.setVolume(parseFloat((e.target as HTMLInputElement).value));
  };

  override update() {
    const volume = this.#volume.value;
    if (!volume) return;

    (this as unknown as HTMLInputElement).value = String(volume.volume);
  }
}

Controller API

Property Type Description
store Store Direct store access
value R | undefined Selected state (with selector) or snapshot (without)

Mixins

For advanced customization when you need to extend behavior.

PlayerMixin

Combined provider + container in one element:

const { PlayerMixin } = createPlayer({
  features: [...features.video],
});

class MyPlayer extends PlayerMixin(MediaElement) {
  // Custom player logic
}

ProviderMixin

Provider-only — creates store and provides context:

const { ProviderMixin } = createPlayer({
  features: [...features.video],
});

class MyProvider extends ProviderMixin(MediaElement) {
  // Custom provider logic
}

ContainerMixin

Container-only — consumes context and auto-attaches media:

const { ContainerMixin } = createPlayer({
  features: [...features.video],
});

class MyContainer extends ContainerMixin(MediaElement) {
  // Custom container logic
}

Split Provider/Container

When media element and container need different DOM locations:

<my-provider>
  <video src="video.mp4">
  <my-container>
    <!-- Controls here, separate from media -->
  </my-container>
</my-provider>

Full Example

// main.ts
import '@videojs/html/video/player';
import '@videojs/html/feature/streaming';
import '@videojs/html/media/hls-video';
import '@videojs/html/video/skin.css';
import '@videojs/html/video/skin';
<!DOCTYPE html>
<html>
<head>
  <script type="module" src="main.ts"></script>
</head>
<body>
  <video-player>
    <video-skin>
      <hls-video src="https://example.com/stream.m3u8"></hls-video>
    </video-skin>
  </video-player>
</body>
</html>