# Features Features are the primary abstraction. They bundle related state and actions as slices. ## Slice Definition ### defineSlice ```ts import { defineSlice } from '@videojs/store'; import type { PlayerTarget } from '@videojs/core/dom'; const playbackSlice = defineSlice()({ state: ({ task }) => ({ paused: true, ended: false, play: task('play', async ({ target }) => { await target.media.play(); }), pause: task('pause', ({ target }) => { target.media.pause(); }), toggle: task('toggle', async ({ target }) => { if (target.media.paused) { await target.media.play(); } else { target.media.pause(); } }), }), attach: ({ target, set, signal }) => { const { media } = target; const sync = () => set({ paused: media.paused, ended: media.ended, }); media.addEventListener('play', sync, { signal }); media.addEventListener('pause', sync, { signal }); media.addEventListener('ended', sync, { signal }); sync(); }, }); ``` ### Config Properties | Property | Type | Description | | -------- | ------------------------ | ---------------------------------------- | | `state` | `(ctx) => State` | Initial state factory with task helper | | `attach` | `(ctx) => void` | Set up subscriptions when target attached | ### State Context The `state` function receives a context for defining tasks: ```ts interface StateContext { task: TaskFactory; // Create async actions target: () => Target; // Get current target (throws if not attached) } ``` ### Attach Context The `attach` function receives a context for syncing state: ```ts interface AttachContext { target: Target; // The attached target signal: AbortSignal; // Cleanup signal get: () => State; // Read current state set: (partial: Partial) => void; // Update state store: { state: State; subscribe: ... }; // Store access } ``` ## Combining Slices Slices are combined into a single store: ```ts import { combine, createStore } from '@videojs/store'; const playerSlice = combine( playbackSlice, volumeSlice, timeSlice, sourceSlice, bufferSlice, ); const store = createStore()(playerSlice); ``` ## Feature Bundles Bundles are pre-composed arrays of slices for common use cases. ### Base Bundles ```ts import { features } from '@videojs/core/dom'; // Video player base features.video = [ playbackSlice, volumeSlice, timeSlice, sourceSlice, bufferSlice, ]; // Audio player base features.audio = [ playbackSlice, volumeSlice, timeSlice, sourceSlice, bufferSlice, ]; ``` ### Usage ```ts import { createPlayer } from '@videojs/react'; import { features } from '@videojs/core/dom'; // Use bundle (spread required) createPlayer({ features: [...features.video], }); // Extend bundle createPlayer({ features: [...features.video, myCustomSlice], }); ``` ### Additional Bundles (Future) ```ts features.streaming = [ qualitySelectionSlice, audioTracksSlice, textTracksSlice, ]; features.ads = [ adMarkersSlice, adSkipSlice, adCountdownSlice, ]; features.live = [ liveIndicatorSlice, seekToLiveSlice, dvrSlice, ]; ``` ## Feature Selectors Each slice has a corresponding selector for typed access: ```ts import { createSelector } from '@videojs/store'; export const selectPlayback = createSelector(playbackSlice); export const selectVolume = createSelector(volumeSlice); export const selectTime = createSelector(timeSlice); ``` ### Pre-built Selectors Standard selectors are exported from `@videojs/core/dom`: ```ts import { selectPlayback, selectVolume, selectTime, selectSource, selectBuffer, } from '@videojs/core/dom'; ``` ### Usage ```tsx import { selectPlayback } from '@videojs/core/dom'; function PlayButton() { const playback = usePlayer(selectPlayback); if (!playback) return null; return ( ); } ``` ## Feature Availability Features may target capabilities the platform doesn't support. ```ts const volumeSlice = defineSlice()({ state: () => ({ volume: 1, muted: false, volumeAvailability: 'unsupported' as FeatureAvailability, // ... }), attach: ({ target, set, signal }) => { const { media } = target; // Check platform capability set({ volumeAvailability: canSetVolume(media) }); // ... }, }); ``` ### FeatureAvailability Type ```ts type FeatureAvailability = '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 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} /> ); } ``` ## Future Work ### Feature Keys Typed symbols for feature identity (not yet implemented): ```ts const PLAYBACK_KEY = Symbol.for('@videojs/playback'); export const playbackKey: FeatureKey = PLAYBACK_KEY; // Usage: smaller imports when you don't need the slice import { playbackKey } from '@videojs/core/dom'; const playback = store.get(playbackKey); ``` **Deferred:** `createSelector` provides equivalent type-safe access without the complexity.