mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(html): add PlayerElement to createPlayer (#376)
This commit is contained in:
@@ -4,6 +4,7 @@ export * from '@videojs/core/dom';
|
||||
// Store
|
||||
export type { Comparator, Selector } from '@videojs/store';
|
||||
export { createSelector, shallowEqual } from '@videojs/store';
|
||||
|
||||
// Player
|
||||
export * from './player/context';
|
||||
export * from './player/create-player';
|
||||
@@ -11,8 +12,7 @@ export * from './player/player-controller';
|
||||
export * from './player/player-mixin';
|
||||
export * from './store/container-mixin';
|
||||
export * from './store/provider-mixin';
|
||||
// Store bindings
|
||||
export * from './store/types';
|
||||
|
||||
// Primitives
|
||||
export { MediaElement } from './ui/media-element';
|
||||
export * from './ui/media-element';
|
||||
|
||||
@@ -3,6 +3,8 @@ import { combine, createStore } from '@videojs/store';
|
||||
|
||||
import { type ContainerMixin, createContainerMixin } from '../store/container-mixin';
|
||||
import { createProviderMixin, type ProviderMixin } from '../store/provider-mixin';
|
||||
import type { PlayerElementConstructor } from '../store/types';
|
||||
import { MediaElement } from '../ui/media-element';
|
||||
import { type PlayerContext, playerContext } from './context';
|
||||
import { PlayerController } from './player-controller';
|
||||
import { createPlayerMixin, type PlayerMixin } from './player-mixin';
|
||||
@@ -21,6 +23,9 @@ export interface CreatePlayerResult<Store extends PlayerStore> {
|
||||
/** Player controller bound to this player's context. */
|
||||
PlayerController: PlayerController.Constructor<Store>;
|
||||
|
||||
/** Pre-composed player element ready for customElements.define(). */
|
||||
PlayerElement: PlayerElementConstructor<Store>;
|
||||
|
||||
/** Mixin for a complete player element (provider + container). */
|
||||
PlayerMixin: PlayerMixin<Store>;
|
||||
|
||||
@@ -37,28 +42,21 @@ export interface CreatePlayerResult<Store extends PlayerStore> {
|
||||
* @example
|
||||
* ```ts
|
||||
* import { features } from '@videojs/core/dom';
|
||||
* import { createPlayer, MediaElement } from '@videojs/html';
|
||||
*
|
||||
* const {
|
||||
* context,
|
||||
* create,
|
||||
* PlayerController,
|
||||
* PlayerMixin,
|
||||
* ProviderMixin,
|
||||
* ContainerMixin,
|
||||
* } = createPlayer({
|
||||
* const { PlayerElement, PlayerController, context } = createPlayer({
|
||||
* features: [...features.video],
|
||||
* });
|
||||
*
|
||||
* // Complete player element
|
||||
* class VideoPlayer extends PlayerMixin(ReactiveElement) {}
|
||||
* // Simple: register pre-composed PlayerElement
|
||||
* customElements.define('video-player', PlayerElement);
|
||||
*
|
||||
* // Or separate provider/container
|
||||
* class MyPlayer extends ProviderMixin(ReactiveElement) {}
|
||||
* class MyContainer extends ContainerMixin(ReactiveElement) {}
|
||||
* // Custom: extend with PlayerMixin
|
||||
* class MyPlayer extends PlayerMixin(MediaElement) {}
|
||||
*
|
||||
* // Control element
|
||||
* class PlayButton extends ReactiveElement {
|
||||
* #playback = new PlayerController(this, selectPlayback);
|
||||
* // Control element with selector
|
||||
* class PlayButton extends MediaElement {
|
||||
* #playback = new PlayerController(this, context, selectPlayback);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@@ -76,14 +74,15 @@ export function createPlayer<const Features extends AnyPlayerFeature[]>(
|
||||
const ctx = playerContext as PlayerContext<Store>;
|
||||
|
||||
const PlayerMixin = createPlayerMixin<Store>(ctx, create);
|
||||
const PlayerElement = PlayerMixin(MediaElement);
|
||||
const ProviderMixin = createProviderMixin<Store>(ctx, create);
|
||||
const ContainerMixin = createContainerMixin<Store>(ctx);
|
||||
const BoundPlayerController = PlayerController as unknown as PlayerController.Constructor<Store>;
|
||||
|
||||
return {
|
||||
context: ctx,
|
||||
create,
|
||||
PlayerController: BoundPlayerController,
|
||||
PlayerController,
|
||||
PlayerElement,
|
||||
PlayerMixin,
|
||||
ProviderMixin,
|
||||
ContainerMixin,
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { ReactiveController, ReactiveControllerHost } from '@lit/reactive-e
|
||||
import type { PlayerStore } from '@videojs/core/dom';
|
||||
import type { InferStoreState, Selector } from '@videojs/store';
|
||||
import { StoreController } from '@videojs/store/lit';
|
||||
import type { Constructor as Ctor } from '@videojs/utils/types';
|
||||
|
||||
import type { PlayerContext, PlayerContextValue } from './context';
|
||||
|
||||
@@ -18,7 +17,7 @@ export type PlayerControllerHost = ReactiveControllerHost & HTMLElement;
|
||||
* @example
|
||||
* ```ts
|
||||
* // Store access (no subscription)
|
||||
* class Controls extends ReactiveElement {
|
||||
* class Controls extends MediaElement {
|
||||
* #player = new PlayerController(this, playerContext);
|
||||
*
|
||||
* handleClick() {
|
||||
@@ -27,7 +26,7 @@ export type PlayerControllerHost = ReactiveControllerHost & HTMLElement;
|
||||
* }
|
||||
*
|
||||
* // Selector-based subscription
|
||||
* class PlayButton extends ReactiveElement {
|
||||
* class PlayButton extends MediaElement {
|
||||
* #playback = new PlayerController(this, playerContext, selectPlayback);
|
||||
* }
|
||||
* ```
|
||||
@@ -95,7 +94,8 @@ export class PlayerController<Store extends PlayerStore, Result = Store> impleme
|
||||
export namespace PlayerController {
|
||||
export type Host = PlayerControllerHost;
|
||||
|
||||
export type Constructor<Store extends PlayerStore = PlayerStore, Result = Store> = Ctor<
|
||||
typeof PlayerController<Store, Result>
|
||||
export type Constructor<Store extends PlayerStore = PlayerStore, Result = Store> = typeof PlayerController<
|
||||
Store,
|
||||
Result
|
||||
>;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import type { ReactiveElement } from '@lit/reactive-element';
|
||||
import type { PlayerStore } from '@videojs/core/dom';
|
||||
import type { Constructor } from '@videojs/utils/types';
|
||||
|
||||
import type { MediaElementConstructor } from '@/ui/media-element';
|
||||
import { createContainerMixin } from '../store/container-mixin';
|
||||
import { createProviderMixin } from '../store/provider-mixin';
|
||||
import type { PlayerProvider } from '../store/types';
|
||||
import type { PlayerProviderConstructor } from '../store/types';
|
||||
import type { PlayerContext } from './context';
|
||||
|
||||
export interface PlayerElement<Store extends PlayerStore> extends PlayerProvider<Store> {}
|
||||
type Result<Class extends MediaElementConstructor, Store extends PlayerStore> = Class &
|
||||
PlayerProviderConstructor<Store>;
|
||||
|
||||
type Base = Constructor<ReactiveElement>;
|
||||
|
||||
type Result<Class extends Base, Store extends PlayerStore> = Class & PlayerElement<Store>;
|
||||
|
||||
export type PlayerMixin<Store extends PlayerStore> = <Class extends Base>(BaseClass: Class) => Result<Class, Store>;
|
||||
export type PlayerMixin<Store extends PlayerStore> = <Class extends MediaElementConstructor>(
|
||||
BaseClass: Class
|
||||
) => Result<Class, Store>;
|
||||
|
||||
/**
|
||||
* Creates a mixin that combines provider and container functionality.
|
||||
@@ -27,7 +24,7 @@ export function createPlayerMixin<Store extends PlayerStore>(
|
||||
const ProviderMixin = createProviderMixin<Store>(context, factory);
|
||||
const ContainerMixin = createContainerMixin<Store>(context);
|
||||
|
||||
return <Class extends Base>(BaseClass: Class) => {
|
||||
return <Class extends MediaElementConstructor>(BaseClass: Class) => {
|
||||
return ContainerMixin(ProviderMixin(BaseClass)) as unknown as Result<Class, Store>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { features } from '@videojs/core/dom';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { createPlayer } from '../create-player';
|
||||
|
||||
describe('createPlayer', () => {
|
||||
it('returns expected exports', () => {
|
||||
const result = createPlayer({ features: [...features.video] });
|
||||
|
||||
expect(result.context).toBeDefined();
|
||||
expect(result.create).toBeInstanceOf(Function);
|
||||
expect(result.PlayerController).toBeDefined();
|
||||
expect(result.PlayerElement).toBeDefined();
|
||||
expect(result.PlayerMixin).toBeInstanceOf(Function);
|
||||
expect(result.ProviderMixin).toBeInstanceOf(Function);
|
||||
expect(result.ContainerMixin).toBeInstanceOf(Function);
|
||||
});
|
||||
|
||||
it('create() returns a store instance', () => {
|
||||
const { create } = createPlayer({ features: [...features.video] });
|
||||
const store = create();
|
||||
|
||||
expect(store.attach).toBeInstanceOf(Function);
|
||||
expect(store.subscribe).toBeInstanceOf(Function);
|
||||
expect(store.destroy).toBeInstanceOf(Function);
|
||||
});
|
||||
|
||||
it('PlayerElement is a valid custom element class', () => {
|
||||
const { PlayerElement } = createPlayer({ features: [...features.video] });
|
||||
|
||||
expect(typeof PlayerElement).toBe('function');
|
||||
expect(PlayerElement.prototype).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,22 +1,18 @@
|
||||
import { ContextConsumer } from '@lit/context';
|
||||
import type { ReactiveElement } from '@lit/reactive-element';
|
||||
import type { MediaContainer, PlayerStore, PlayerTarget } from '@videojs/core/dom';
|
||||
import { listen, querySlot } from '@videojs/utils/dom';
|
||||
import { Disposer } from '@videojs/utils/events';
|
||||
import { noop } from '@videojs/utils/function';
|
||||
import type { Constructor } from '@videojs/utils/types';
|
||||
|
||||
import type { MediaElementConstructor } from '@/ui/media-element';
|
||||
import type { PlayerContext } from '../player/context';
|
||||
import type { PlayerConsumer } from './types';
|
||||
import type { PlayerConsumer, PlayerConsumerConstructor } from './types';
|
||||
|
||||
type Base = Constructor<ReactiveElement>;
|
||||
|
||||
type Result<Class extends Base, Store extends PlayerStore> = Class & Constructor<PlayerConsumer<Store>>;
|
||||
|
||||
export type ContainerMixin<Store extends PlayerStore> = <Class extends Base>(BaseClass: Class) => Result<Class, Store>;
|
||||
export type ContainerMixin<Store extends PlayerStore> = <Class extends MediaElementConstructor>(
|
||||
BaseClass: Class
|
||||
) => Class & PlayerConsumerConstructor<Store>;
|
||||
|
||||
export function createContainerMixin<Store extends PlayerStore>(context: PlayerContext<Store>): ContainerMixin<Store> {
|
||||
return <Class extends Base>(BaseClass: Class) => {
|
||||
return <Class extends MediaElementConstructor>(BaseClass: Class) => {
|
||||
class PlayerContainerElement extends BaseClass implements PlayerConsumer<Store>, MediaContainer {
|
||||
#detach = noop;
|
||||
#disposer = new Disposer();
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
import { ContextProvider } from '@lit/context';
|
||||
import type { ReactiveElement } from '@lit/reactive-element';
|
||||
import type { Media, PlayerStore } from '@videojs/core/dom';
|
||||
import { isNull } from '@videojs/utils/predicate';
|
||||
import type { Constructor } from '@videojs/utils/types';
|
||||
|
||||
import type { MediaElementConstructor } from '@/ui/media-element';
|
||||
import type { PlayerContext } from '../player/context';
|
||||
import type { PlayerProvider } from './types';
|
||||
import type { PlayerProvider, PlayerProviderConstructor } from './types';
|
||||
|
||||
type Base = Constructor<ReactiveElement>;
|
||||
|
||||
type Result<Class extends Base, Store extends PlayerStore> = Class & Constructor<PlayerProvider<Store>>;
|
||||
|
||||
export type ProviderMixin<Store extends PlayerStore> = <Class extends Base>(BaseClass: Class) => Result<Class, Store>;
|
||||
export type ProviderMixin<Store extends PlayerStore> = <Class extends MediaElementConstructor>(
|
||||
BaseClass: Class
|
||||
) => Class & PlayerProviderConstructor<Store>;
|
||||
|
||||
export function createProviderMixin<Store extends PlayerStore>(
|
||||
context: PlayerContext<Store>,
|
||||
factory: () => Store
|
||||
): ProviderMixin<Store> {
|
||||
return <Class extends Base>(BaseClass: Class) => {
|
||||
return <Class extends MediaElementConstructor>(BaseClass: Class) => {
|
||||
class PlayerProviderElement extends BaseClass implements PlayerProvider<Store> {
|
||||
#store: Store | null = null;
|
||||
#media: Media | null = null;
|
||||
|
||||
@@ -1,10 +1,32 @@
|
||||
import type { Media, PlayerStore } from '@videojs/core/dom';
|
||||
import type { Constructor } from '@videojs/utils/types';
|
||||
import type { MediaElement } from '@/ui/media-element';
|
||||
|
||||
export interface PlayerProvider<Store extends PlayerStore> {
|
||||
// ----------------------------------------
|
||||
// PlayerElement
|
||||
// ----------------------------------------
|
||||
|
||||
export interface PlayerElement<Store extends PlayerStore> extends MediaElement, PlayerProvider<Store> {}
|
||||
|
||||
export interface PlayerElementConstructor<Store extends PlayerStore> extends Constructor<PlayerElement<Store>> {}
|
||||
|
||||
// ----------------------------------------
|
||||
// PlayerProvider
|
||||
// ----------------------------------------
|
||||
|
||||
export interface PlayerProvider<Store extends PlayerStore> extends MediaElement {
|
||||
readonly store: Store;
|
||||
media: Media | null;
|
||||
}
|
||||
|
||||
export interface PlayerConsumer<Store extends PlayerStore> {
|
||||
export interface PlayerProviderConstructor<Store extends PlayerStore> extends Constructor<PlayerProvider<Store>> {}
|
||||
|
||||
// ----------------------------------------
|
||||
// PlayerConsumer
|
||||
// ----------------------------------------
|
||||
|
||||
export interface PlayerConsumer<Store extends PlayerStore> extends MediaElement {
|
||||
readonly store: Store | null;
|
||||
}
|
||||
|
||||
export interface PlayerConsumerConstructor<Store extends PlayerStore> extends Constructor<PlayerConsumer<Store>> {}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { ReactiveElement } from '@lit/reactive-element';
|
||||
import type { Constructor } from '@videojs/utils/types';
|
||||
|
||||
export class MediaElement extends ReactiveElement {}
|
||||
|
||||
export interface MediaElementConstructor extends Constructor<MediaElement> {}
|
||||
|
||||
Reference in New Issue
Block a user