docs: update site for context-based media discovery (#1018)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Darius Cepulis <dcepulis@mux.com>
This commit is contained in:
rahim
2026-03-19 12:21:32 -05:00
committed by GitHub
co-authored by Claude Opus 4.6 Darius Cepulis
parent 2f612b5f52
commit b4746f7122
37 changed files with 142 additions and 77 deletions
@@ -1,7 +1,6 @@
<video-player class="html-buffering-indicator-basic">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -1,7 +1,6 @@
<video-player class="html-captions-button-basic">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -1,7 +1,6 @@
<video-player class="html-controls-basic">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -1,7 +1,6 @@
<video-player class="html-fullscreen-button-basic">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -1,7 +1,6 @@
<demo-video-player class="html-create-player-basic">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -1,7 +1,6 @@
<video-player class="html-mute-button-basic">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -1,7 +1,6 @@
<video-player class="html-mute-button-volume-levels">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -1,7 +1,6 @@
<video-player class="html-pip-button-basic">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -1,7 +1,6 @@
<video-player class="html-play-button-basic">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -1,6 +1,5 @@
<video-player class="html-playback-rate-button-basic">
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -1,7 +1,6 @@
<demo-ctrl-player class="html-player-controller-basic">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -1,7 +1,6 @@
<video-player class="html-popover-basic">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -2,7 +2,6 @@
<video-player class="html-poster-basic">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
playsinline
></video>
@@ -1,7 +1,6 @@
<video-player class="html-seek-button-basic">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -2,7 +2,6 @@
<video-player>
<media-container>
<video
slot="media"
class="html-thumbnail-text-track__media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
preload="auto"
@@ -1,7 +1,6 @@
<video-player class="html-time-slider-parts">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -1,7 +1,6 @@
<video-player class="html-time-current-duration">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -1,7 +1,6 @@
<video-player class="html-time-current-time">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -1,7 +1,6 @@
<video-player class="html-time-custom-negative-sign">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -1,7 +1,6 @@
<video-player class="html-time-custom-separator">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -1,7 +1,6 @@
<video-player class="html-time-remaining">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
@@ -1,7 +1,6 @@
<video-player class="html-volume-slider-parts">
<media-container>
<video
slot="media"
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
+1 -1
View File
@@ -12,7 +12,7 @@ export function generateHTMLCode(skin: Skin): string {
<video-player>
<${skinTag}>
<video slot="media" src="${VJS10_DEMO_VIDEO.mp4}" playsinline></video>
<video src="${VJS10_DEMO_VIDEO.mp4}" playsinline></video>
</${skinTag}>
</video-player>`;
}
@@ -57,7 +57,7 @@ function getRendererElement(renderer: Renderer, url: string): string {
const tag = getRendererTag(renderer);
const src = url.trim() || getDefaultSourceUrl(renderer);
const playsInline = isVideoLikeRenderer(renderer) ? ' playsinline' : '';
return `<${tag} slot="media" src="${src}"${playsInline}></${tag}>`;
return `<${tag} src="${src}"${playsInline}></${tag}>`;
}
function getDefaultSourceUrl(renderer: Renderer): string {
@@ -23,7 +23,7 @@ We're pretty sure it works differently from what you've come to expect of a web
```html
<video-player>
<video-skin>
<video slot="media" src="video.mp4"></video>
<video src="video.mp4"></video>
</video-skin>
</video-player>
```
@@ -259,7 +259,7 @@ A default video preset (general website video, the kind of thing you might other
```html
<video-player>
<video-skin>
<video slot="media" src="video.mp4"></video>
<video src="video.mp4"></video>
</video-skin>
</video-player>
```
+1 -2
View File
@@ -8,7 +8,6 @@ import DocsLink from '@/components/docs/DocsLink.astro';
import DocsLinkCard from '@/components/docs/DocsLinkCard.astro';
import Aside from '@/components/Aside.astro';
Not every player needs to support every use case. To address this, Video.js has a concept of **features** — self-contained units of player functionality. Each one adds state properties and actions to the player. For example, "playback" adds `paused` and `play()`, "volume" adds `volume` and `setVolume()`, and so on.
## Using features
@@ -36,7 +35,7 @@ For example, if you import `<video-player>` from the `/video` path, you'll has p
<script type="module" src="@videojs/html/video/player"></script> <!-- [!code focus] -->
<video-player> <!-- [!code focus] -->
<video-skin>
<video slot="media" src="movie.mp4"></video>
<video src="movie.mp4"></video>
</video-skin>
</video-player> <!-- [!code focus] -->
```
+6 -8
View File
@@ -34,7 +34,7 @@ State is handled by a `video-player` element, which creates a central state stor
<!-- All components inside automatically connect to state --> <!-- [!code focus] -->
<video-player> <!-- [!code focus] -->
<video-skin>
<video slot="media" src="video.mp4"></video>
<video src="video.mp4"></video>
</video-skin>
</video-player> <!-- [!code focus] -->
```
@@ -44,12 +44,10 @@ You can access state and actions from anywhere within `<video-player>` with <Doc
<DocsLinkCard slug="concepts/features">Learn more about state and actions</DocsLinkCard>
## 2. User interface
Use a prebuilt **skin** or build your own from the individual **UI components**.
### Skins
Skins are complete, pre-designed player UIs that package components and styles together:
@@ -66,7 +64,7 @@ Skins are complete, pre-designed player UIs that package components and styles t
```html
<video-player>
<video-skin> <!-- [!code focus] -->
<video slot="media" src="video.mp4"></video>
<video src="video.mp4"></video>
</video-skin> <!-- [!code focus] -->
</video-player>
```
@@ -95,7 +93,7 @@ If you want more control than skins offer you, you can build your own UI from ou
```html
<video-player>
<media-container> <!-- [!code focus] -->
<video slot="media" src="video.mp4"></video>
<video src="video.mp4"></video>
<media-controls> <!-- [!code focus] -->
<media-play-button></media-play-button> <!-- [!code focus] -->
<!-- ... --> <!-- [!code focus] -->
@@ -116,7 +114,7 @@ Media components are the components that actually display your media. They're es
Media components can be format specific (HLS, DASH), service specific (YouTube, Vimeo, Mux), or use case specific (background video).
<FrameworkCase frameworks={["html"]}>
Media elements are detected via a `slot="media"` attribute. Always include it on your `<video>`, `<audio>`, or custom media element.
Media elements are discovered automatically. Plain `<video>` and `<audio>` elements are found via a `querySelector`, while custom media elements like `<hls-video>` register themselves.
</FrameworkCase>
<Aside type="note">
@@ -136,7 +134,7 @@ DASH, YouTube, Vimeo, Mux, and more media elements are currently under developme
```html
<video-player>
<video-skin>
<hls-video slot="media" src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM.m3u8"></hls-video> <!-- [!code focus] -->
<hls-video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM.m3u8"></hls-video> <!-- [!code focus] -->
</video-skin>
</video-player>
```
@@ -179,7 +177,7 @@ function Hero() {
<background-video-player>
<background-video-skin>
<background-video slot="media" src="hero.mp4"></background-video>
<background-video src="hero.mp4"></background-video>
</background-video-skin>
</background-video-player>
```
+3 -3
View File
@@ -40,7 +40,7 @@ function Hero() {
<background-video-player>
<background-video-skin>
<background-video slot="media" src="hero.mp4"></background-video>
<background-video src="hero.mp4"></background-video>
</background-video-skin>
</background-video-player>
```
@@ -129,7 +129,7 @@ function Hero() {
<my-player>
<media-container>
<background-video slot="media" src="hero.mp4"></background-video>
<background-video src="hero.mp4"></background-video>
<media-play-button>Play / Pause</media-play-button>
</media-container>
</my-player>
@@ -157,7 +157,7 @@ A preset's default media element is just the starting point. You can replace it
```html
<video-player>
<video-skin>
<hls-video slot="media" src="https://example.com/stream.m3u8"></hls-video> <!-- [!code focus] -->
<hls-video src="https://example.com/stream.m3u8"></hls-video> <!-- [!code focus] -->
</video-skin>
</video-player>
```
+1 -1
View File
@@ -21,7 +21,7 @@ In prior versions of Video.js, skins were CSS-only themes applied to the same se
<video-player>
<video-skin><!-- [!code focus] -->
<!-- wraps the media element --> <!-- [!code focus] -->
<video slot="media" src="video.mp4"></video>
<video src="video.mp4"></video>
</video-skin><!-- [!code focus] -->
</video-player>
```
@@ -1,18 +1,25 @@
---
title: ContainerMixin
description: Mixin that consumes player context and auto-attaches media elements
description: Mixin that consumes player context and registers the element as the media container
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
`ContainerMixin` creates a class that consumes the player store from context and automatically attaches `<video>` or `<audio>` elements found within it. It uses a `MutationObserver` to watch for media element changes and calls `store.attach()` to keep the store's media target in sync.
`ContainerMixin` creates a class that consumes the player store from context and registers itself as the media container with the <DocsLink slug="reference/provider-mixin">provider</DocsLink>. The provider uses this reference as the fullscreen target and passes it to `store.attach()`.
### What ContainerMixin does
{/* TODO: link containerContext to concept page once written (https://github.com/videojs/v10/issues/1033) */}
On `connectedCallback`, the element registers itself with the provider via `containerContext`. The provider stores the container reference and uses it when calling `store.attach({ media, container })`. On `disconnectedCallback`, the element deregisters itself.
Media discovery is owned by the provider — `ContainerMixin` does not search for or attach media elements.
### When to use ContainerMixin
Use `ContainerMixin` when the <DocsLink slug="reference/provider-mixin">provider</DocsLink> and container live in different elements. This is common when the store owner sits higher in the tree (e.g., a layout shell) while the media element sits deeper in a content area.
For a single element that both owns the store and attaches media, compose both mixins on the same base class:
For a single element that both owns the store and attaches the container, compose both mixins on the same base class:
```ts
const { ProviderMixin, ContainerMixin } = createPlayer({ features: videoFeatures });
@@ -20,13 +27,9 @@ const { ProviderMixin, ContainerMixin } = createPlayer({ features: videoFeatures
class VideoPlayer extends ProviderMixin(ContainerMixin(MediaElement)) {}
```
### How media discovery works
On `connectedCallback`, `ContainerMixin` sets up a `MutationObserver` on its children. When a `<video>` or `<audio>` element is added or removed, it calls `store.attach({ media, container })` or cleans up the previous attachment. The observer is torn down on `disconnectedCallback`.
### Split composition
Pair `ContainerMixin` with <DocsLink slug="reference/provider-mixin">`ProviderMixin`</DocsLink> for full control over which element owns the store and which discovers the media:
Pair `ContainerMixin` with <DocsLink slug="reference/provider-mixin">`ProviderMixin`</DocsLink> when the store owner is a different element from the container:
```ts
const { ProviderMixin, ContainerMixin } = createPlayer({ features: videoFeatures });
@@ -34,7 +37,7 @@ const { ProviderMixin, ContainerMixin } = createPlayer({ features: videoFeatures
// Provider owns the store, sits at the top
class PlayerShell extends ProviderMixin(MediaElement) {}
// Container discovers media, sits deeper in the tree
// Container registers as the layout/fullscreen target
class MediaRegion extends ContainerMixin(MediaElement) {}
```
@@ -0,0 +1,56 @@
---
title: MediaAttachMixin
description: Mixin for custom media elements to register themselves with the provider via context
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
`MediaAttachMixin` creates a class that registers itself with the <DocsLink slug="reference/provider-mixin">provider</DocsLink> on connect. Apply it to custom media elements so they automatically wire to the store.
Built-in elements, like `<hls-video>`, `<dash-video>`, `<simple-hls-video>`, and `<background-video>`, all use this mixin internally.
```ts
import { MediaAttachMixin } from '@videojs/html';
import { MyCustomMedia } from './my-custom-media';
export class MyMedia extends MediaAttachMixin(MyCustomMedia) {
// inherits context registration
}
customElements.define('my-media', MyMedia);
```
## Who needs this
You only need `MediaAttachMixin` when your base class doesn't already register as a media element with the provider. For example, elements that wrap a third-party player or use a canvas renderer.
For plain `<video>` and `<audio>` elements, the provider discovers them automatically via `querySelector` — no mixin needed.
## Overriding the media target
By default, `MediaAttachMixin` registers `this` as the media element. Override `getMediaTarget()` if the actual media element is something other than the element itself — for example, an inner `<video>` in a shadow root:
```ts
import { MediaAttachMixin } from '@videojs/html';
export class BackgroundVideo extends MediaAttachMixin(HTMLElement) {
getMediaTarget() {
return this.shadowRoot?.querySelector('video') ?? null;
}
}
```
## Factory function
{/* TODO: link mediaContext to concept page once written (https://github.com/videojs/v10/issues/1033) */}
`MediaAttachMixin` is pre-configured with the default `mediaContext`. To use a different context, call `createMediaAttachMixin`:
```ts
import { createMediaAttachMixin } from '@videojs/html';
import { myCustomContext } from './my-player/context';
export const MyMediaAttachMixin = createMediaAttachMixin(myCustomContext);
```
<UtilReference util="MediaAttachMixin" />
@@ -30,7 +30,7 @@ The `<media-container>` is the player's physical surface. It defines the visual
```html
<video-player>
<media-container> <!-- [!code focus] -->
<video slot="media" src="video.mp4"></video>
<video src="video.mp4"></video>
<media-controls>...</media-controls>
</media-container> <!-- [!code focus] -->
</video-player>
@@ -62,7 +62,7 @@ import '@videojs/html/video/player';
```html
<video-player>
<media-container>
<video slot="media" src="video.mp4"></video>
<video src="video.mp4"></video>
</media-container>
</video-player>
```
@@ -100,7 +100,7 @@ The container is the visual box around your media and controls. Sizing, aspect r
<FrameworkCase frameworks={["html"]}>
```html
<media-container style="width: 640px; aspect-ratio: 16/9;">
<video slot="media" src="video.mp4"></video>
<video src="video.mp4"></video>
<media-controls>...</media-controls>
</media-container>
```
@@ -111,18 +111,10 @@ When the user goes fullscreen, the **container** goes fullscreen — not the vid
### Media attachment
<FrameworkCase frameworks={["react"]}>
When a media component like `<Video>` registers itself via context, the container picks it up and attaches it to the store — wiring the media element to all of the player's features.
Media discovery is handled by the provider, not the container. When a media component like `<Video>` registers itself via context, the provider wires it to the store and all of the player's features.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
The container searches its subtree for a media element — `<video>`, `<audio>`, or a custom media element — and attaches it to the store. You can also use `slot="media"` to explicitly mark which element is the media:
```html
<media-container>
<video slot="media" src="video.mp4"></video>
</media-container>
```
This applies even when using a <DocsLink slug="concepts/skins">skin</DocsLink>, since skins contain a container internally.
Media discovery is handled by the provider, not the container. Custom media elements like `<hls-video>` register themselves via context when they connect. Plain `<video>` and `<audio>` elements are picked up automatically via a `querySelector` fallback. No `slot="media"` attribute is needed.
</FrameworkCase>
### Interaction surface
@@ -160,14 +152,14 @@ A <DocsLink slug="concepts/skins">skin</DocsLink> is a container plus UI control
<!-- Packaged skin — container is inside video-skin -->
<video-player>
<video-skin>
<video slot="media" src="video.mp4"></video>
<video src="video.mp4"></video>
</video-skin>
</video-player>
<!-- Custom UI — you use media-container directly -->
<video-player>
<media-container>
<video slot="media" src="video.mp4"></video>
<video src="video.mp4"></video>
<media-play-button></media-play-button>
</media-container>
</video-player>
@@ -195,7 +187,7 @@ The <DocsLink slug="reference/player-provider">provider</DocsLink> gives compone
```html
<video-player>
<media-container>
<video slot="media" src="video.mp4"></video>
<video src="video.mp4"></video>
<media-controls>...</media-controls> <!-- fullscreen, activity detection, gestures -->
</media-container>
@@ -41,7 +41,7 @@ function App() {
<video-player>
<!-- Everything inside can access the player store -->
<media-container>
<video slot="media" src="video.mp4"></video>
<video src="video.mp4"></video>
</media-container>
</video-player>
```
@@ -76,7 +76,7 @@ import '@videojs/html/video/player';
```html
<video-player>
<media-container>
<video slot="media" src="video.mp4"></video>
<video src="video.mp4"></video>
</media-container>
</video-player>
```
@@ -115,7 +115,7 @@ Everything that needs player state goes inside the provider: skins, containers,
```html
<video-player>
<video-skin> <!-- skin — includes container + controls -->
<video slot="media" src="..."></video> <!-- media element -->
<video src="..."></video> <!-- media element -->
</video-skin>
<my-custom-overlay></my-custom-overlay> <!-- your own element — can use PlayerController -->
</video-player>
@@ -193,7 +193,7 @@ The provider's scope can extend beyond the fullscreen target. Playlists, transcr
```html
<video-player>
<media-container>
<video slot="media" src="video.mp4"></video>
<video src="video.mp4"></video>
<media-controls>...</media-controls> <!-- goes fullscreen with the video -->
</media-container>
@@ -1,22 +1,30 @@
---
title: ProviderMixin
description: Mixin that provides player context to descendant elements
description: Mixin that creates the player store and manages the full media attach lifecycle
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
`ProviderMixin` creates a class that provides the player store to descendant elements via context. It owns the store lifecycle: creates the store lazily on first access and destroys it on disconnect.
`ProviderMixin` creates a class that owns the player store and the media attach lifecycle. It publishes the store to context so descendants can consume it, and calls `store.attach()` when both a media element and a container are available.
### Store lifecycle
The store is created lazily using the factory from `createPlayer`. On `connectedCallback`, the mixin publishes the store to context so descendant elements can consume it. On `disconnectedCallback`, it calls `store.destroy()` and cleans up.
The store is created when the element is constructed. On `connectedCallback`, the mixin publishes three context values to its descendants:
Any <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink> or <DocsLink slug="reference/container-mixin">`ContainerMixin`</DocsLink> element below the provider in the DOM tree receives the store automatically through context.
{/* TODO: link context keys to concept page once written (https://github.com/videojs/v10/issues/1033) */}
- **`playerContext`** — the store, consumed by <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink> and other controllers
- **`mediaContext`** — a setter callback for media elements to register themselves
- **`containerContext`** — a setter callback for container elements to register themselves
When a media element and a container both register, the provider calls `store.attach({ media, container })`. If no media element registers via context within a [microtask](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide), the provider falls back to `querySelector('video, audio')` to support plain `<video>` and `<audio>` elements.
{/* TODO: link destroyCallback to lifecycle reference once written (https://github.com/videojs/v10/issues/1034) */}
On `disconnectedCallback`, the mixin detaches the current media target but keeps the store alive. An element moved in the DOM reconnects without losing state. The store is destroyed in `destroyCallback`.
### When to split provider and container
Use `ProviderMixin` separate from <DocsLink slug="reference/container-mixin">`ContainerMixin`</DocsLink> when the store owner is a different element from the one containing the media. This is common in complex layouts:
Use `ProviderMixin` separate from <DocsLink slug="reference/container-mixin">`ContainerMixin`</DocsLink> when the store owner is a different element from the container:
```ts
const { ProviderMixin, ContainerMixin } = createPlayer({ features: videoFeatures });
@@ -24,11 +32,11 @@ const { ProviderMixin, ContainerMixin } = createPlayer({ features: videoFeatures
// Layout shell owns the store
class AppShell extends ProviderMixin(MediaElement) {}
// Content region discovers and attaches the media element
// Content region is the fullscreen target and container reference
class VideoRegion extends ContainerMixin(MediaElement) {}
```
When a single element should handle both responsibilities, compose the mixins directly:
When a single element handles both responsibilities, compose the mixins directly:
```ts
const { ProviderMixin, ContainerMixin } = createPlayer({ features: videoFeatures });
@@ -55,7 +55,7 @@ import jsonSpriteHtmlTs from "@/components/docs/demos/thumbnail/html/css/JsonSpr
<FrameworkCase frameworks={["html"]}>
```html
<video slot="media" src="video.mp4">
<video src="video.mp4">
<track
kind="metadata"
label="thumbnails"
@@ -0,0 +1,29 @@
---
title: useContainerAttach
description: Hook to register a custom container element with the player context
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
`useContainerAttach` returns a setter function for registering a DOM element as the player's container. The built-in `Player.Container` uses this internally; you only need it when building a custom container element.
```tsx title="CustomContainer.tsx"
import { useContainerAttach } from "@videojs/react";
function CustomContainer({ children }: { children: React.ReactNode }) {
const setContainer = useContainerAttach();
return <div ref={setContainer}>{children}</div>;
}
```
## Who needs this
You only need `useContainerAttach` if you're replacing the built-in `Player.Container` with a custom element. For example, if you're building a container with custom fullscreen behavior or a non-standard layout boundary.
For standard player layouts, use the built-in `Player.Container`.
## Safe outside Provider
Returns `undefined` when called outside a Player `Provider`. Passing `undefined` to `ref` is harmless in React. Check the return value if your component needs to know whether it's inside a provider.
<UtilReference util="useContainerAttach" />
+2
View File
@@ -77,6 +77,7 @@ export const sidebar: Sidebar = [
contents: [
{ slug: 'reference/render-element' },
{ slug: 'reference/use-button' },
{ slug: 'reference/use-container-attach' },
{ slug: 'reference/use-media-attach' },
{ slug: 'reference/use-player-context' },
{ slug: 'reference/use-selector' },
@@ -96,6 +97,7 @@ export const sidebar: Sidebar = [
defaultOpen: false,
contents: [
{ slug: 'reference/container-mixin' },
{ slug: 'reference/media-attach-mixin' },
{ slug: 'reference/player-context' },
{ slug: 'reference/provider-mixin' },
{ slug: 'reference/snapshot-controller' },