mirror of
https://github.com/zoriya/v10.git
synced 2026-08-05 05:37:21 +00:00
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:
co-authored by
Claude Opus 4.6
Darius Cepulis
parent
2f612b5f52
commit
b4746f7122
@@ -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
|
||||
|
||||
@@ -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>
|
||||
```
|
||||
|
||||
@@ -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] -->
|
||||
```
|
||||
|
||||
@@ -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>
|
||||
```
|
||||
|
||||
@@ -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>
|
||||
```
|
||||
|
||||
@@ -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" />
|
||||
@@ -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' },
|
||||
|
||||
Reference in New Issue
Block a user