FrameworkStyle

video-player

The state boundary — creates a store and broadcasts it to all descendants.

The <video-player> element is the state boundary of your player. It creates a store and makes it available to every element inside it via context. Every player needs exactly one.

<video-player>
  <!-- Everything inside can access the player store -->
  <media-container>
    <video slot="media" src="video.mp4"></video>
  </media-container>
</video-player>

How it’s created

For most users, importing from @videojs/html/video/player registers a ready-to-use <video-player> element with the standard video features (bundled as a preset):

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

If you need a custom feature set, a custom tag name, or want to combine provider+container into a single element, use createPlayer() to get ProviderMixin and compose your own class:

import { createPlayer, MediaElement } from '@videojs/html';
import { videoFeatures } from '@videojs/html/video';

const { ProviderMixin, ContainerMixin } = createPlayer({ features: videoFeatures });

class MyPlayer extends ProviderMixin(MediaElement) {}
customElements.define('my-player', MyPlayer);

What lives inside it

Everything that needs player state goes inside the provider: skins, containers, UI components, and your own custom components. Anything inside can access the store.

<video-player>
  <video-skin>               <!-- skin — includes container + controls -->
    <video slot="media" src="..."></video> <!-- media element -->
  </video-skin>
  <my-custom-overlay></my-custom-overlay> <!-- your own element — can use PlayerController -->
</video-player>

No visual presence

The <video-player> element uses display: contents, meaning it has no visual box of its own. Sizing, borders, and background go on the <media-container>, not the provider.

Accessing state

Use PlayerController to subscribe to store state from any custom element inside the provider:

import { selectPlayback } from '@videojs/html';

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

  connectedCallback() {
    super.connectedCallback();
    this.addEventListener('click', () => {
      this.#playback.store?.dispatch('toggle-playback');
    });
  }
}

Extended player layouts

The provider’s scope can extend beyond the fullscreen target. Playlists, transcripts, sidebars, and other supplementary UI can live inside the provider but outside the container. They still have full access to the store, they just won’t go fullscreen with the video.

<video-player>
  <media-container>
    <video slot="media" src="video.mp4"></video>
    <media-controls>...</media-controls>  <!-- goes fullscreen with the video -->
  </media-container>

  <media-transcript></media-transcript>     <!-- outside container — still has store access -->
  <playlist-sidebar></playlist-sidebar>     <!-- outside container — still has store access -->
</video-player>