mirror of
https://github.com/zoriya/v10.git
synced 2026-08-05 05:37:21 +00:00
7.5 KiB
7.5 KiB
status
| status |
|---|
| draft |
Player API Design
Unified API for Media and Container concerns. Two stores internally, one API for users.
Contents
| Document | Purpose |
|---|---|
| index.md | Overview, quick start, surface API |
| decisions.md | Design decisions and rationale |
| architecture.md | Two-store architecture, internals |
| examples.md | Usage examples (React, HTML, Lit) |
Problem
Two concerns, one player:
- Media — play, pause, volume, time. Owned by
<video>. - Container — fullscreen, keyboard, gestures, idle. Owned by the player UI wrapper.
Different targets, different lifecycles. Internally, they need separate stores.
But users want one API. This doc defines:
- How
createPlayerexposes a unified API in React and HTML - What it returns — hooks, controllers, providers
- How presets bundle features for common use cases
- How to extend with custom features
Quick Start
React
import { createPlayer, presets } from '@videojs/react';
const { Provider, Container, usePlayer } = createPlayer(presets.website);
function App() {
return (
<Provider>
<Container>
<Video src="video.mp4" />
<Controls />
</Container>
</Provider>
);
}
function Controls() {
const player = usePlayer();
// Flat access — state and requests on same object
// Proxy-based tracking — accessing .paused subscribes automatically
return <button onClick={player.paused ? player.play : player.pause}>{player.paused ? 'Play' : 'Pause'}</button>;
}
HTML / Lit
import { createPlayer, presets, VjsElement } from '@videojs/html';
const { ProviderElement, PlayerController } = createPlayer(presets.website);
customElements.define('vjs-website-provider', ProviderElement);
class VjsPlayButton extends VjsElement {
#player = new PlayerController(this);
render() {
// .value returns tracking proxy — accessing .paused subscribes
const { paused, play, pause } = this.#player.value;
return html` <button @click=${paused ? play : pause}>${paused ? 'Play' : 'Pause'}</button> `;
}
}
Declarative (Skin)
<script type="module" src="@videojs/html/presets/website/skins/frosted.js"></script>
<vjs-website-provider>
<vjs-frosted-skin>
<video src="video.mp4"></video>
</vjs-frosted-skin>
</vjs-website-provider>
Surface API
createPlayer
// Shorthand — preset or feature array
createPlayer(presets.website);
createPlayer([features.playback, features.fullscreen]);
// Config object — extensible
createPlayer({
features: presets.website,
// future: devTools, middleware, etc.
});
Returns (React)
const {
Provider, // Creates both stores
Container, // Attaches container to player store
usePlayer, // Player state + requests (flattened)
useMedia, // Media state + requests (escape hatch)
} = createPlayer(presets.website);
Returns (HTML)
const {
// Ready-to-use elements
ProviderElement, // Both stores, auto-attaches media
ContainerElement, // Attaches container to player store
// Mixins (customization)
ProviderMixin, // Both stores
ContainerMixin, // Container attachment
MediaProviderMixin, // Media store only (escape hatch)
// Controllers
PlayerController, // Player state + requests (like usePlayer)
MediaController, // Media state + requests (escape hatch)
} = createPlayer(presets.website);
Returns (HTML) — createMedia
const {
ProviderElement, // Ready-to-use element
ProviderMixin, // Customization
MediaController, // Media state + requests
} = createMedia([features.playback, features.time]);
usePlayer (React)
function Controls() {
const player = usePlayer();
// State — proxy tracks access, subscribes automatically
player.paused;
player.volume;
player.isFullscreen;
// Requests — call directly
player.play();
player.pause();
player.setVolume(0.5);
player.toggleFullscreen();
}
PlayerController (HTML/Lit)
class VjsControls extends VjsElement {
#player = new PlayerController(this);
render() {
const { paused, isFullscreen, play, pause, toggleFullscreen } = this.#player.value;
return html`
<button @click=${paused ? play : pause} />
<button @click=${toggleFullscreen} />
`;
}
}
useMedia (Escape Hatch)
Direct media access. Rarely needed — use when player features don't expose what you need.
function DebugPanel() {
const media = useMedia();
// Direct media state (bypasses player layer)
return <pre>{JSON.stringify({ readyState: media.readyState })}</pre>;
}
Features
Features bundle related state and requests. Include only what you need.
import { features } from '@videojs/react';
features.playback; // play, pause, ended
features.volume; // volume, muted
features.time; // currentTime, duration, seeking
features.fullscreen; // isFullscreen, enter/exit
features.keyboard; // keyboard shortcuts
features.idle; // idle detection
features.gestures; // touch gestures
Presets
Presets are curated feature collections. Pick one, get the right features.
createPlayer(presets.website); // full-featured
createPlayer(presets.background); // minimal (autoplay, loop)
| Preset | Use Case |
|---|---|
presets.website |
Default website player (default) |
presets.background |
Background/hero video |
presets.news |
Article embeds |
presets.creator |
Creator platforms (YouTube) |
presets.swipe |
Short-form video (TikTok) |
presets.streaming |
Streaming apps (Netflix) |
presets.live |
Interactive live (Twitch) |
Extending Presets
createPlayer({
features: [...presets.background, features.keyboard],
});
Creating Features
import { createPlayerFeature } from '@videojs/react';
const analytics = createPlayerFeature({
initialState: { events: [] },
getSnapshot: ({ target, initialState }) => initialState,
subscribe: ({ target, update, signal }) => {
target.media.subscribe(
(s) => s.paused,
() => {
track(target.media.state.paused ? 'pause' : 'play');
update();
},
{ signal }
);
},
request: {
trackEvent: (event, { target }) => {
// Access media state and requests
target.media.state.paused;
target.media.request.play();
},
},
});
createPlayer({
features: [...presets.website, analytics],
});
Naming Convention
State and requests share the same flat namespace. Follow this convention to avoid collisions:
| Type | Convention | Examples |
|---|---|---|
| State | Nouns, adjectives, past participles | paused, volume, muted, isFullscreen, currentTime |
| Requests | Verbs, imperative | play, pause, setVolume, toggleMute, enterFullscreen |
Runtime validation throws if duplicate keys are detected.
Related Docs
- decisions.md — Why these choices were made
- architecture.md — Two-store internals
- examples.md — Full usage examples