mirror of
https://github.com/zoriya/v10.git
synced 2026-08-05 05:37:21 +00:00
refactor(packages): simplify createPlayer type signatures (#477)
This commit is contained in:
@@ -1,5 +1,15 @@
|
||||
import type { AnySlice, Slice, Store, UnionSliceState } from '@videojs/store';
|
||||
|
||||
import type {
|
||||
MediaBufferState,
|
||||
MediaFullscreenState,
|
||||
MediaPictureInPictureState,
|
||||
MediaPlaybackState,
|
||||
MediaSourceState,
|
||||
MediaTimeState,
|
||||
MediaVolumeState,
|
||||
} from '../../core/media/state';
|
||||
|
||||
export interface Media extends HTMLMediaElement {}
|
||||
|
||||
export interface MediaContainer extends HTMLElement {}
|
||||
@@ -18,3 +28,29 @@ export type AnyPlayerFeature = AnySlice<PlayerTarget>;
|
||||
export type PlayerStore<Features extends AnyPlayerFeature[] = []> = Store<PlayerTarget, UnionSliceState<Features>>;
|
||||
|
||||
export type AnyPlayerStore = Store<PlayerTarget, object>;
|
||||
|
||||
// ----------------------------------------
|
||||
// Feature Presets
|
||||
// ----------------------------------------
|
||||
|
||||
export type VideoFeatures = [
|
||||
PlayerFeature<MediaPlaybackState>,
|
||||
PlayerFeature<MediaVolumeState>,
|
||||
PlayerFeature<MediaTimeState>,
|
||||
PlayerFeature<MediaSourceState>,
|
||||
PlayerFeature<MediaBufferState>,
|
||||
PlayerFeature<MediaFullscreenState>,
|
||||
PlayerFeature<MediaPictureInPictureState>,
|
||||
];
|
||||
|
||||
export type AudioFeatures = [
|
||||
PlayerFeature<MediaPlaybackState>,
|
||||
PlayerFeature<MediaVolumeState>,
|
||||
PlayerFeature<MediaTimeState>,
|
||||
PlayerFeature<MediaSourceState>,
|
||||
PlayerFeature<MediaBufferState>,
|
||||
];
|
||||
|
||||
export type VideoPlayerStore = PlayerStore<VideoFeatures>;
|
||||
|
||||
export type AudioPlayerStore = PlayerStore<AudioFeatures>;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { AudioFeatures, VideoFeatures } from '../../media/types';
|
||||
import { bufferFeature } from './buffer';
|
||||
import { fullscreenFeature } from './fullscreen';
|
||||
import { pipFeature } from './pip';
|
||||
@@ -17,7 +18,7 @@ export {
|
||||
volumeFeature as volume,
|
||||
};
|
||||
|
||||
export const video = [
|
||||
export const video: VideoFeatures = [
|
||||
playbackFeature,
|
||||
volumeFeature,
|
||||
timeFeature,
|
||||
@@ -25,6 +26,6 @@ export const video = [
|
||||
bufferFeature,
|
||||
fullscreenFeature,
|
||||
pipFeature,
|
||||
] as const;
|
||||
];
|
||||
|
||||
export const audio = [playbackFeature, volumeFeature, timeFeature, sourceFeature, bufferFeature] as const;
|
||||
export const audio: AudioFeatures = [playbackFeature, volumeFeature, timeFeature, sourceFeature, bufferFeature];
|
||||
|
||||
@@ -3,7 +3,7 @@ import { MediaElement } from '@/ui/media-element';
|
||||
import { createPlayer } from '../../player/create-player';
|
||||
|
||||
const { PlayerMixin } = createPlayer({
|
||||
features: [...features.video],
|
||||
features: features.video,
|
||||
});
|
||||
|
||||
export class VideoPlayer extends PlayerMixin(MediaElement) {}
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import type { AnyPlayerFeature, PlayerStore, PlayerTarget } from '@videojs/core/dom';
|
||||
import type {
|
||||
AnyPlayerFeature,
|
||||
AudioFeatures,
|
||||
AudioPlayerStore,
|
||||
PlayerStore,
|
||||
PlayerTarget,
|
||||
VideoFeatures,
|
||||
VideoPlayerStore,
|
||||
} from '@videojs/core/dom';
|
||||
import { combine, createStore } from '@videojs/store';
|
||||
|
||||
import { type ContainerMixin, createContainerMixin } from '../store/container-mixin';
|
||||
@@ -45,7 +53,7 @@ export interface CreatePlayerResult<Store extends PlayerStore> {
|
||||
* import { createPlayer, MediaElement } from '@videojs/html';
|
||||
*
|
||||
* const { PlayerElement, PlayerController, context } = createPlayer({
|
||||
* features: [...features.video],
|
||||
* features: features.video,
|
||||
* });
|
||||
*
|
||||
* // Simple: register pre-composed PlayerElement
|
||||
@@ -60,23 +68,26 @@ export interface CreatePlayerResult<Store extends PlayerStore> {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function createPlayer(config: CreatePlayerConfig<VideoFeatures>): CreatePlayerResult<VideoPlayerStore>;
|
||||
|
||||
export function createPlayer(config: CreatePlayerConfig<AudioFeatures>): CreatePlayerResult<AudioPlayerStore>;
|
||||
|
||||
export function createPlayer<const Features extends AnyPlayerFeature[]>(
|
||||
config: CreatePlayerConfig<Features>
|
||||
): CreatePlayerResult<PlayerStore<Features>> {
|
||||
type Store = PlayerStore<Features>;
|
||||
): CreatePlayerResult<PlayerStore<Features>>;
|
||||
|
||||
const slice = combine<PlayerTarget, Features>(...config.features);
|
||||
export function createPlayer(config: CreatePlayerConfig<AnyPlayerFeature[]>): CreatePlayerResult<PlayerStore> {
|
||||
const slice = combine<PlayerTarget, AnyPlayerFeature[]>(...config.features);
|
||||
|
||||
function create(): Store {
|
||||
function create(): PlayerStore {
|
||||
return createStore<PlayerTarget>()(slice);
|
||||
}
|
||||
|
||||
const ctx = playerContext as PlayerContext<Store>;
|
||||
|
||||
const PlayerMixin = createPlayerMixin<Store>(ctx, create);
|
||||
const ctx = playerContext;
|
||||
const PlayerMixin = createPlayerMixin<PlayerStore>(ctx, create);
|
||||
const PlayerElement = PlayerMixin(MediaElement);
|
||||
const ProviderMixin = createProviderMixin<Store>(ctx, create);
|
||||
const ContainerMixin = createContainerMixin<Store>(ctx);
|
||||
const ProviderMixin = createProviderMixin<PlayerStore>(ctx, create);
|
||||
const ContainerMixin = createContainerMixin<PlayerStore>(ctx);
|
||||
|
||||
return {
|
||||
context: ctx,
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import type { AudioPlayerStore, PlayerStore, PlayerTarget, VideoPlayerStore } from '@videojs/core/dom';
|
||||
import { definePlayerFeature, features } from '@videojs/core/dom';
|
||||
import type { Slice } from '@videojs/store';
|
||||
import { assertType, describe, it } from 'vitest';
|
||||
|
||||
import { type CreatePlayerResult, createPlayer } from '../create-player';
|
||||
|
||||
describe('createPlayer', () => {
|
||||
it('resolves video features to VideoPlayerStore', () => {
|
||||
const result = createPlayer({ features: features.video });
|
||||
|
||||
assertType<CreatePlayerResult<VideoPlayerStore>>(result);
|
||||
});
|
||||
|
||||
it('resolves audio features to AudioPlayerStore', () => {
|
||||
const result = createPlayer({ features: features.audio });
|
||||
|
||||
assertType<CreatePlayerResult<AudioPlayerStore>>(result);
|
||||
});
|
||||
|
||||
it('resolves spread video features to VideoPlayerStore', () => {
|
||||
const result = createPlayer({ features: [...features.video] });
|
||||
|
||||
assertType<CreatePlayerResult<VideoPlayerStore>>(result);
|
||||
});
|
||||
|
||||
it('resolves custom features to generic PlayerStore', () => {
|
||||
interface CustomState {
|
||||
custom: boolean;
|
||||
}
|
||||
|
||||
const customFeature = definePlayerFeature({
|
||||
state: (): CustomState => ({ custom: true }),
|
||||
});
|
||||
|
||||
const result = createPlayer({ features: [customFeature] });
|
||||
|
||||
assertType<CreatePlayerResult<PlayerStore<[Slice<PlayerTarget, CustomState>]>>>(result);
|
||||
});
|
||||
|
||||
it('resolves extended video features to generic PlayerStore', () => {
|
||||
interface AnalyticsState {
|
||||
events: string[];
|
||||
}
|
||||
|
||||
const analyticsFeature = definePlayerFeature({
|
||||
state: (): AnalyticsState => ({ events: [] }),
|
||||
});
|
||||
|
||||
const result = createPlayer({
|
||||
features: [...features.video, analyticsFeature],
|
||||
});
|
||||
|
||||
// Extended features fall through to the generic overload
|
||||
assertType<CreatePlayerResult<PlayerStore<[...typeof features.video, typeof analyticsFeature]>>>(result);
|
||||
|
||||
// The store has both video and analytics state
|
||||
const store = result.create();
|
||||
|
||||
assertType<boolean>(store.paused);
|
||||
assertType<number>(store.volume);
|
||||
assertType<string[]>(store.events);
|
||||
});
|
||||
|
||||
it('resolves extended audio features to generic PlayerStore', () => {
|
||||
interface AnalyticsState {
|
||||
events: string[];
|
||||
}
|
||||
|
||||
const analyticsFeature = definePlayerFeature({
|
||||
state: (): AnalyticsState => ({ events: [] }),
|
||||
});
|
||||
|
||||
const result = createPlayer({
|
||||
features: [...features.audio, analyticsFeature],
|
||||
});
|
||||
|
||||
const store = result.create();
|
||||
|
||||
assertType<boolean>(store.paused);
|
||||
assertType<number>(store.volume);
|
||||
assertType<string[]>(store.events);
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,7 @@ import { createPlayer } from '../create-player';
|
||||
|
||||
describe('createPlayer', () => {
|
||||
it('returns expected exports', () => {
|
||||
const result = createPlayer({ features: [...features.video] });
|
||||
const result = createPlayer({ features: features.video });
|
||||
|
||||
expect(result.context).toBeDefined();
|
||||
expect(result.create).toBeInstanceOf(Function);
|
||||
@@ -17,7 +17,7 @@ describe('createPlayer', () => {
|
||||
});
|
||||
|
||||
it('create() returns a store instance', () => {
|
||||
const { create } = createPlayer({ features: [...features.video] });
|
||||
const { create } = createPlayer({ features: features.video });
|
||||
const store = create();
|
||||
|
||||
expect(store.attach).toBeInstanceOf(Function);
|
||||
@@ -26,7 +26,7 @@ describe('createPlayer', () => {
|
||||
});
|
||||
|
||||
it('PlayerElement is a valid custom element class', () => {
|
||||
const { PlayerElement } = createPlayer({ features: [...features.video] });
|
||||
const { PlayerElement } = createPlayer({ features: features.video });
|
||||
|
||||
expect(typeof PlayerElement).toBe('function');
|
||||
expect(PlayerElement.prototype).toBeDefined();
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
'use client';
|
||||
|
||||
import type { AnyPlayerFeature, Media, PlayerStore, PlayerTarget } from '@videojs/core/dom';
|
||||
import type { UnionSliceState } from '@videojs/store';
|
||||
import type {
|
||||
AnyPlayerFeature,
|
||||
AnyPlayerStore,
|
||||
AudioFeatures,
|
||||
AudioPlayerStore,
|
||||
Media,
|
||||
PlayerStore,
|
||||
PlayerTarget,
|
||||
VideoFeatures,
|
||||
VideoPlayerStore,
|
||||
} from '@videojs/core/dom';
|
||||
import type { InferStoreState } from '@videojs/store';
|
||||
import { combine, createStore } from '@videojs/store';
|
||||
import { useStore } from '@videojs/store/react';
|
||||
import type { FC, ReactNode } from 'react';
|
||||
@@ -18,24 +28,27 @@ export interface ProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export interface CreatePlayerResult<Features extends AnyPlayerFeature[]> {
|
||||
export interface CreatePlayerResult<Store extends PlayerStore> {
|
||||
Provider: FC<ProviderProps>;
|
||||
Container: typeof Container;
|
||||
usePlayer: UsePlayerHook<Features>;
|
||||
usePlayer: UsePlayerHook<Store>;
|
||||
useMedia: () => Media | null;
|
||||
}
|
||||
|
||||
type UsePlayerHook<Features extends AnyPlayerFeature[]> = {
|
||||
(): PlayerStore<Features>;
|
||||
<R>(selector: (state: UnionSliceState<Features>) => R): R;
|
||||
export type UsePlayerHook<Store extends PlayerStore> = {
|
||||
(): Store;
|
||||
<R>(selector: (state: InferStoreState<Store>) => R): R;
|
||||
};
|
||||
|
||||
export function createPlayer(config: CreatePlayerConfig<VideoFeatures>): CreatePlayerResult<VideoPlayerStore>;
|
||||
|
||||
export function createPlayer(config: CreatePlayerConfig<AudioFeatures>): CreatePlayerResult<AudioPlayerStore>;
|
||||
|
||||
export function createPlayer<const Features extends AnyPlayerFeature[]>(
|
||||
config: CreatePlayerConfig<Features>
|
||||
): CreatePlayerResult<Features> {
|
||||
type Store = PlayerStore<Features>;
|
||||
type State = UnionSliceState<Features>;
|
||||
): CreatePlayerResult<PlayerStore<Features>>;
|
||||
|
||||
export function createPlayer(config: CreatePlayerConfig<AnyPlayerFeature[]>): CreatePlayerResult<AnyPlayerStore> {
|
||||
function Provider({ children }: ProviderProps): ReactNode {
|
||||
const [store] = useState(() => createStore<PlayerTarget>()(combine(...config.features)));
|
||||
const [media, setMedia] = useState<Media | null>(null);
|
||||
@@ -49,9 +62,7 @@ export function createPlayer<const Features extends AnyPlayerFeature[]>(
|
||||
Provider.displayName = `${config.displayName}.Provider`;
|
||||
}
|
||||
|
||||
function usePlayer(): Store;
|
||||
function usePlayer<R>(selector: (state: State) => R): R;
|
||||
function usePlayer<R>(selector?: (state: State) => R): Store | R {
|
||||
function usePlayer<R>(selector?: (state: object) => R): AnyPlayerStore | R {
|
||||
const { store } = usePlayerContext();
|
||||
return useStore(store, selector as any);
|
||||
}
|
||||
@@ -59,7 +70,7 @@ export function createPlayer<const Features extends AnyPlayerFeature[]>(
|
||||
return {
|
||||
Provider,
|
||||
Container,
|
||||
usePlayer: usePlayer as UsePlayerHook<Features>,
|
||||
usePlayer,
|
||||
useMedia,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { AudioPlayerStore, PlayerStore, PlayerTarget, VideoPlayerStore } from '@videojs/core/dom';
|
||||
import { definePlayerFeature, features } from '@videojs/core/dom';
|
||||
import type { Slice } from '@videojs/store';
|
||||
import { assertType, describe, it } from 'vitest';
|
||||
|
||||
import { type CreatePlayerResult, createPlayer } from '../create-player';
|
||||
|
||||
describe('createPlayer', () => {
|
||||
it('resolves video features to VideoPlayerStore', () => {
|
||||
const result = createPlayer({ features: features.video });
|
||||
|
||||
assertType<CreatePlayerResult<VideoPlayerStore>>(result);
|
||||
});
|
||||
|
||||
it('resolves audio features to AudioPlayerStore', () => {
|
||||
const result = createPlayer({ features: features.audio });
|
||||
|
||||
assertType<CreatePlayerResult<AudioPlayerStore>>(result);
|
||||
});
|
||||
|
||||
it('resolves spread video features to VideoPlayerStore', () => {
|
||||
const result = createPlayer({ features: [...features.video] });
|
||||
|
||||
assertType<CreatePlayerResult<VideoPlayerStore>>(result);
|
||||
});
|
||||
|
||||
it('resolves custom features to generic PlayerStore', () => {
|
||||
interface CustomState {
|
||||
custom: boolean;
|
||||
}
|
||||
|
||||
const customFeature = definePlayerFeature({
|
||||
state: (): CustomState => ({ custom: true }),
|
||||
});
|
||||
|
||||
const result = createPlayer({ features: [customFeature] });
|
||||
|
||||
assertType<CreatePlayerResult<PlayerStore<[Slice<PlayerTarget, CustomState>]>>>(result);
|
||||
});
|
||||
|
||||
it('resolves extended video features to generic PlayerStore', () => {
|
||||
interface AnalyticsState {
|
||||
events: string[];
|
||||
}
|
||||
|
||||
const analyticsFeature = definePlayerFeature({
|
||||
state: (): AnalyticsState => ({ events: [] }),
|
||||
});
|
||||
|
||||
const result = createPlayer({
|
||||
features: [...features.video, analyticsFeature],
|
||||
});
|
||||
|
||||
// Extended features fall through to the generic overload
|
||||
assertType<CreatePlayerResult<PlayerStore<[...typeof features.video, typeof analyticsFeature]>>>(result);
|
||||
});
|
||||
|
||||
it('resolves extended audio features to generic PlayerStore', () => {
|
||||
interface AnalyticsState {
|
||||
events: string[];
|
||||
}
|
||||
|
||||
const analyticsFeature = definePlayerFeature({
|
||||
state: (): AnalyticsState => ({ events: [] }),
|
||||
});
|
||||
|
||||
const result = createPlayer({
|
||||
features: [...features.audio, analyticsFeature],
|
||||
});
|
||||
|
||||
assertType<CreatePlayerResult<PlayerStore<[...typeof features.audio, typeof analyticsFeature]>>>(result);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user