# Primitives API Guide for library authors building UI primitives (like ``, ``). ## Problem Primitives don't know which features the user configured: ```tsx // Inside @videojs/react — shipped to users export function PlayButton() { // User might use features.video or a custom subset // We don't know if playback slice is included } ``` They need: 1. Access to the player store 2. A way to check if a feature exists 3. Type narrowing when the feature is present ## Solution: Selector-Based Access ### React ```tsx import { selectPlayback } from '@videojs/core/dom'; export function PlayButton() { const playback = usePlayer(selectPlayback); if (!playback) return null; // TypeScript knows playback shape return ( ); } ``` ### HTML ```ts import { MediaElement } from '@videojs/html'; import { selectPlayback } from '@videojs/core/dom'; class MediaPlayButton extends MediaElement { #playback = new PlayerController(this, context, selectPlayback); override connectedCallback() { super.connectedCallback(); this.addEventListener('click', this.#handleClick); } #handleClick = () => { this.#playback.value?.toggle(); }; override update() { const playback = this.#playback.value; if (!playback) return; this.setAttribute('aria-pressed', String(!playback.paused)); } } ``` ## Selector Types Selectors extract typed state from the store: ```ts import { createSelector } from '@videojs/store'; const selectPlayback = createSelector(playbackSlice); // Type: (state: Record) => PlaybackState | undefined ``` Returns `undefined` when the slice isn't configured. ## Patterns ### Required Feature If a primitive requires a feature to function: ```tsx export function VolumeSlider() { const volume = usePlayer(selectVolume); // Return nothing if feature not available if (!volume) return null; return ( volume.setVolume(Number(e.target.value))} /> ); } ``` ### Optional Feature Enhancement If a primitive works without a feature but enhances with it: ```tsx export function TimeSlider() { const time = usePlayer(selectTime); const playback = usePlayer(selectPlayback); if (!time) return null; return ( time.seek(Number(e.target.value))} // Optional: pause during drag onMouseDown={() => playback?.pause()} onMouseUp={() => playback?.play()} /> ); } ``` ### Multiple Required Features ```tsx export function Controls() { const playback = usePlayer(selectPlayback); const volume = usePlayer(selectVolume); const fullscreen = usePlayer(selectFullscreen); // All required if (!playback || !volume || !fullscreen) return null; return (
); } ``` ### Custom Selectors Derive values from state: ```tsx // Select specific value const paused = usePlayer((s) => s.paused); // Derive value const isPlaying = usePlayer((s) => !s.paused && !s.ended); // Combine multiple properties const state = usePlayer((s) => ({ paused: s.paused, volume: s.volume, })); ``` ## Cross-Framework Consistency Same pattern works in React and HTML: | Concept | React | HTML | | --------------- | -------------------------- | -------------------------------------------- | | Hook/Controller | `usePlayer(selector)` | `new PlayerController(this, ctx, selector)` | | Get state | returns state | `controller.value` | | Check existence | `if (!state)` | `if (!value)` | | Access state | `state.paused` | `value.paused` | | Call action | `state.play()` | `value.play()` | ## Feature Availability Features may target capabilities the platform doesn't support. ```tsx const volume = usePlayer(selectVolume); volume?.volumeAvailability; // 'available' | 'unavailable' | 'unsupported' ``` | Value | Meaning | | --------------- | ------------------------------------------------------ | | `'unsupported'` | Platform can never do this (e.g., iOS volume) | | `'unavailable'` | Could work, not ready yet (e.g., waiting for manifest) | | `'available'` | Ready to use | ### Handling Unavailable Features ```tsx export function VolumeSlider() { const volume = usePlayer(selectVolume); if (!volume) return null; // Hide if platform doesn't support volume control if (volume.volumeAvailability === 'unsupported') return null; // Disable if temporarily unavailable const disabled = volume.volumeAvailability !== 'available'; return ( volume.setVolume(Number(e.target.value))} disabled={disabled} /> ); } ``` ## Package Exports ### @videojs/store ```ts import { shallowEqual, createSelector } from '@videojs/store'; ``` ### @videojs/core/dom ```ts import { features, selectPlayback, selectVolume, selectTime, selectSource, selectBuffer, } from '@videojs/core/dom'; ``` ### @videojs/react ```ts import { createPlayer } from '@videojs/react'; // From createPlayer result const { Provider, Container, usePlayer, useMedia } = createPlayer({ ... }); ``` ### @videojs/html ```ts import { createPlayer, MediaElement } from '@videojs/html'; // From createPlayer result const { context, PlayerElement, PlayerController, PlayerMixin, ProviderMixin, ContainerMixin, } = createPlayer({ ... }); ```