PlayerController
Reactive controller for accessing player store state in HTML custom elements
PlayerController is a reactive controller that consumes the player store from playerContext . Without a selector it returns the store instance directly (no subscription — use this for actions). With a selector it returns the selected value and subscribes to changes, triggering a host update on shallow-equal change. Access the current value via .value, which returns undefined until connected to a provider.
Examples
Basic Usage
<demo-ctrl-player class="html-player-controller-basic">
<media-container>
<video
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
autoplay
muted
playsinline
></video>
<div class="html-player-controller-basic__panel">
<demo-ctrl-actions class="html-player-controller-basic__actions">
<button type="button" class="action-play">Play</button>
<button type="button" class="action-pause">Pause</button>
<button type="button" class="action-volume">50% Volume</button>
</demo-ctrl-actions>
<demo-ctrl-state class="html-player-controller-basic__state">
<span class="state-text">Paused: Yes | Time: 0.0s | Volume: 100%</span>
</demo-ctrl-state>
</div>
</media-container>
</demo-ctrl-player>
.html-player-controller-basic {
position: relative;
}
.html-player-controller-basic video {
width: 100%;
}
.html-player-controller-basic__panel {
display: flex;
gap: 16px;
padding: 12px;
background: rgba(0, 0, 0, 0.05);
border-top: 1px solid rgba(0, 0, 0, 0.1);
align-items: center;
}
.html-player-controller-basic__actions {
display: flex;
gap: 6px;
}
.html-player-controller-basic__actions button {
padding: 4px 12px;
border-radius: 6px;
border: 1px solid #ccc;
background: white;
cursor: pointer;
font-size: 0.8125rem;
}
.html-player-controller-basic__state {
font-size: 0.8125rem;
color: #374151;
font-variant-numeric: tabular-nums;
}
import { applyElementProps, createButton, createPlayer, MediaElement } from '@videojs/html';
import { videoFeatures } from '@videojs/html/video';
import '@videojs/html/media/container';
const { ProviderMixin, PlayerController, context } = createPlayer({
features: videoFeatures,
});
class DemoPlayer extends ProviderMixin(MediaElement) {
static readonly tagName = 'demo-ctrl-player';
}
class PlayerActions extends MediaElement {
static readonly tagName = 'demo-ctrl-actions';
readonly #player = new PlayerController(this, context);
#disconnect: AbortController | null = null;
override connectedCallback(): void {
super.connectedCallback();
this.#disconnect = new AbortController();
const signal = this.#disconnect.signal;
const playBtn = this.querySelector<HTMLButtonElement>('.action-play')!;
const pauseBtn = this.querySelector<HTMLButtonElement>('.action-pause')!;
const volumeBtn = this.querySelector<HTMLButtonElement>('.action-volume')!;
const bind = (el: HTMLElement, action: () => void) => {
const props = createButton({ onActivate: action, isDisabled: () => !this.#player.value });
applyElementProps(el, props, signal);
};
bind(playBtn, () => this.#player.value?.play());
bind(pauseBtn, () => this.#player.value?.pause());
bind(volumeBtn, () => this.#player.value?.setVolume(0.5));
}
override disconnectedCallback(): void {
super.disconnectedCallback();
this.#disconnect?.abort();
this.#disconnect = null;
}
}
class PlayerState extends MediaElement {
static readonly tagName = 'demo-ctrl-state';
readonly #state = new PlayerController(this, context, (s) => ({
paused: s.paused,
currentTime: s.currentTime,
volume: s.volume,
}));
protected override update(): void {
super.update();
const state = this.#state.value;
if (!state) return;
const el = this.querySelector('.state-text');
if (el) {
el.textContent = `Paused: ${state.paused ? 'Yes' : 'No'} | Time: ${state.currentTime.toFixed(1)}s | Volume: ${Math.round(state.volume * 100)}%`;
}
}
}
customElements.define(DemoPlayer.tagName, DemoPlayer);
customElements.define(PlayerActions.tagName, PlayerActions);
customElements.define(PlayerState.tagName, PlayerState);
API Reference
Without Selector
Parameters
| Parameter | Type | Default | |
|---|---|---|---|
host* | PlayerControllerHost | — | |
| |||
context* | PlayerContext<Store> | — | |
| |||
Return Value
| Property | Type | |
|---|---|---|
value | Result | undefined | |
displayName | string | undefined |
With Selector
Parameters
| Parameter | Type | Default | |
|---|---|---|---|
host* | PlayerControllerHost | — | |
| |||
context* | PlayerContext<Store> | — | |
| |||
selector* | Selector<InferStoreState<Store>, Result> | — | |
| |||
Return Value
| Property | Type | |
|---|---|---|
value | Result | undefined | |
displayName | string | undefined |