ReferenceLayout
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.
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):
If you need a custom feature set or tag name, use createPlayer() to get the configured PlayerElement. Extend ContainerElement separately when you need custom container behavior:
What lives inside it
Everything that needs player state goes inside <video-player>: skins, containers, UI components, and your own custom components. Anything inside can access the store.
No visual presence
Packaged skins make <video-player> boxless with display: contents. Width, height, positioning, transforms, and getBoundingClientRect() do not describe a visible player box there. Put layout and measurements on the <media-container> instead.
If you omit <video-skin> and import only the player and media components, those skin styles are not loaded. The player element still owns state, not layout, so add and style a <media-container> instead of using the player itself as the layout surface.
Accessing state
Use the preset’s typed PlayerController to subscribe to store state from any custom element inside the player:
Extended player layouts
The player’s scope can extend beyond the fullscreen target. Playlists, transcripts, sidebars, and other supplementary UI can live inside <video-player> but outside <media-container>. They still have full access to the store, but they won’t go fullscreen with the video.
Lifecycle
Video.js elements clean up after themselves when you remove them from the page:
<video-player>and the UI elements destroy themselves two animation frames after they’re removed. Destroying the player also destroys its store.- Media components such as
<hlsjs-video>tear down their playback engine in a microtask after they’re removed.
Moving an element synchronously, removing it and inserting it elsewhere with no await in between, triggers neither. To keep an element through a longer move, add the keep-alive attribute:
keep-alive applies only to the element that carries it; descendants don’t inherit it. Set it on every Video.js element in the tree you move. Packaged skins don’t pass it to the controls inside them, so move a player that uses a packaged skin synchronously. An element with keep-alive is never destroyed automatically. When you’re finished with it, call destroy() on the player and UI elements, and adapter.destroy() on media components, which release their engine that way. A destroyed element can’t be reconnected; create a new one instead.