mirror of
https://github.com/zoriya/v10.git
synced 2026-08-05 13:48:14 +00:00
6.0 KiB
6.0 KiB
Features
Features are the primary abstraction. They bundle related state and actions as slices.
Slice Definition
defineSlice
import { defineSlice } from '@videojs/store';
import type { PlayerTarget } from '@videojs/core/dom';
const playbackSlice = defineSlice<PlayerTarget>()({
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:
interface StateContext<Target> {
task: TaskFactory<Target>; // Create async actions
target: () => Target; // Get current target (throws if not attached)
}
Attach Context
The attach function receives a context for syncing state:
interface AttachContext<Target, State> {
target: Target; // The attached target
signal: AbortSignal; // Cleanup signal
get: () => State; // Read current state
set: (partial: Partial<State>) => void; // Update state
store: { state: State; subscribe: ... }; // Store access
}
Combining Slices
Slices are combined into a single store:
import { combine, createStore } from '@videojs/store';
const playerSlice = combine(
playbackSlice,
volumeSlice,
timeSlice,
sourceSlice,
bufferSlice,
);
const store = createStore<PlayerTarget>()(playerSlice);
Feature Bundles
Bundles are pre-composed arrays of slices for common use cases.
Base Bundles
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
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)
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:
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:
import {
selectPlayback,
selectVolume,
selectTime,
selectSource,
selectBuffer,
} from '@videojs/core/dom';
Usage
import { selectPlayback } from '@videojs/core/dom';
function PlayButton() {
const playback = usePlayer(selectPlayback);
if (!playback) return null;
return (
<button onClick={playback.toggle}>
{playback.paused ? 'Play' : 'Pause'}
</button>
);
}
Feature Availability
Features may target capabilities the platform doesn't support.
const volumeSlice = defineSlice<PlayerTarget>()({
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
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
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 (
<input
type="range"
value={volume.volume}
onChange={(e) => volume.setVolume(Number(e.target.value))}
disabled={disabled}
/>
);
}
Future Work
Feature Keys
Typed symbols for feature identity (not yet implemented):
const PLAYBACK_KEY = Symbol.for('@videojs/playback');
export const playbackKey: FeatureKey<typeof playbackSlice> = 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.