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/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/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,
selectPlayback,
selectTime,
selectVolume,
} 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 #playback = new PlayerController(this, context, selectPlayback);
readonly #time = new PlayerController(this, context, selectTime);
readonly #volume = new PlayerController(this, context, selectVolume);
protected override update(changed: Map<string, unknown>): void {
super.update(changed);
const playback = this.#playback.value;
const time = this.#time.value;
const volume = this.#volume.value;
if (!playback) return;
const el = this.querySelector('.state-text');
if (el) {
el.textContent = `Paused: ${playback.paused ? 'Yes' : 'No'} | Time: ${(time?.currentTime ?? 0).toFixed(1)}s | Volume: ${Math.round((volume?.volume ?? 0) * 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 |