mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
5.6 KiB
5.6 KiB
Architecture
Internal structure of the Player API. Implementation detail for feature authors.
Overview
createPlayer()
│
filters by feature.type
│
┌───────────┴───────────┐
▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ Media Store │ │ Player Store │
│ target: <video> │◄──│ target: container│
│ │ │ │
│ playback, volume │ │ fullscreen, idle │
│ time, etc. │ │ keyboard, etc. │
└───────────────────┘ └───────────────────┘
│
store.get(feature)
│
Cross-feature access
Key insight: Player store holds reference to media store. Features use store.get() for cross-feature access, abstracting away which store a feature lives on.
Two Stores
Why Two Stores
| Reason | Explanation |
|---|---|
| Different targets | Media features target HTMLMediaElement. Player features target container. |
| Attachment timing | Video element and container mount at different times. |
| Type safety | Features declare which target they need. TypeScript catches mismatches. |
| Standalone media | Headless player, audio-only, programmatic control. Media store works alone. |
Media Store
interface MediaTarget {
element: HTMLMediaElement;
}
Media features observe and control the <video> or <audio> element directly.
Player Store
interface PlayerTarget {
container: HTMLElement;
mediaStore: MediaStore;
}
Player features can:
- Control the container element (fullscreen, focus)
- Access media features via
store.get()for coordination
Feature Registry
Each store maintains a feature registry:
interface Store {
features: Map<symbol, Feature>;
get(feature: Feature | FeatureKey | string): Slice | undefined;
has(feature: Feature | FeatureKey | string): boolean;
}
Features are keyed by their feature.key (symbol).
Cross-Feature Access
Features access other features via store.get():
const keyboardFeature = createPlayerFeature({
subscribe: ({ store, update, signal }) => {
// Access playback feature (might be on media store)
const playback = store.get(features.playback);
document.addEventListener('keydown', (e) => {
if (e.key === ' ') playback?.toggle();
}, { signal });
},
});
The store handles looking up features across both stores transparently.
Reactive System
Selector-based subscriptions via useSyncExternalStore (React) or ReactiveElement's update cycle (HTML).
Subscription Flow
- Subscribe —
usePlayer(feature, selector)subscribes to store - Snapshot — On change,
feature.getSnapshot()called - Compare — Selector result compared with
shallowEqual - Update — If different, trigger re-render
Feature Subscriptions
When using usePlayer(feature):
- Subscribe scoped to that feature's state keys
- Only re-render when that feature's state changes
- Other features updating don't cause re-render
// Only subscribes to playback state
const playback = usePlayer(features.playback);
// currentTime updates (time feature) don't affect this component
File Structure
| Path | Purpose |
|---|---|
packages/store/src/ |
Core store, features, selectors |
packages/core/src/dom/features/media/ |
Media features (playback, volume, time) |
packages/core/src/dom/features/player/ |
Player features (fullscreen, keyboard, idle) |
packages/html/src/ |
HTML player + skins |
packages/react/src/ |
React player + skins |
Progressive Complexity
| Level | Example | Sees internal stores? |
|---|---|---|
| Use skin | <VideoSkin> |
No |
| Use features | createPlayer({ features: [features.video] }) |
No |
| Custom features | createPlayer({ features: [..., myFeature] }) |
No |
| Use hooks | usePlayer(features.playback) |
No |
| Write feature | createPlayerFeature({ ... }) |
Yes (store.get) |
Internal stores are implementation details until you author features.
Constraints
- Media features live in
@videojs/core/dom/features/media - Player features live in
@videojs/core/dom/features/player createPlayerlives in@videojs/htmland@videojs/react- Two stores internally, one API externally
store.get()andstore.has()are the feature access primitives