mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(html): refactor attach contexts to carry state and setter (#1024)
Co-authored-by: Wesley Luyten <luwes@users.noreply.github.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { containerAttachContext, playerContext } from '../player/context';
|
||||
import { containerContext, playerContext } from '../player/context';
|
||||
import { createContainerMixin } from '../store/container-mixin';
|
||||
import { MediaElement } from '../ui/media-element';
|
||||
|
||||
const ContainerMixin = createContainerMixin({ playerContext, containerAttachContext });
|
||||
const ContainerMixin = createContainerMixin({ playerContext, containerContext });
|
||||
|
||||
export class MediaContainerElement extends ContainerMixin(MediaElement) {
|
||||
static readonly tagName = 'media-container';
|
||||
|
||||
@@ -5,7 +5,7 @@ import { type Context, createContext } from '@videojs/element/context';
|
||||
// Player Context
|
||||
// ----------------------------------------
|
||||
|
||||
export const PLAYER_CONTEXT_KEY = Symbol('@videojs/player');
|
||||
export const PLAYER_CONTEXT_KEY = Symbol.for('@videojs/player');
|
||||
|
||||
export type PlayerContextValue<Store extends PlayerStore = AnyPlayerStore> = Store;
|
||||
|
||||
@@ -22,23 +22,33 @@ export type PlayerContext<Store extends PlayerStore = AnyPlayerStore> = Context<
|
||||
export const playerContext = createContext<PlayerContextValue, typeof PLAYER_CONTEXT_KEY>(PLAYER_CONTEXT_KEY);
|
||||
|
||||
// ----------------------------------------
|
||||
// Attach Contexts
|
||||
// Media Context
|
||||
// ----------------------------------------
|
||||
|
||||
export const MEDIA_ATTACH_KEY = Symbol('@videojs/media-attach');
|
||||
export const MEDIA_CONTEXT_KEY = Symbol.for('@videojs/media');
|
||||
|
||||
export type MediaAttachValue = (media: Media | null) => void;
|
||||
export interface MediaContextValue {
|
||||
media: Media | null;
|
||||
setMedia: (media: Media | null) => void;
|
||||
}
|
||||
|
||||
export type MediaAttachContext = Context<typeof MEDIA_ATTACH_KEY, MediaAttachValue>;
|
||||
export type MediaContext = Context<typeof MEDIA_CONTEXT_KEY, MediaContextValue>;
|
||||
|
||||
export const mediaAttachContext = createContext<MediaAttachValue, typeof MEDIA_ATTACH_KEY>(MEDIA_ATTACH_KEY);
|
||||
export const mediaContext = createContext<MediaContextValue, typeof MEDIA_CONTEXT_KEY>(MEDIA_CONTEXT_KEY);
|
||||
|
||||
export const CONTAINER_ATTACH_KEY = Symbol('@videojs/container-attach');
|
||||
// ----------------------------------------
|
||||
// Container Context
|
||||
// ----------------------------------------
|
||||
|
||||
export type ContainerAttachValue = (container: MediaContainer | null) => void;
|
||||
export const CONTAINER_CONTEXT_KEY = Symbol.for('@videojs/container');
|
||||
|
||||
export type ContainerAttachContext = Context<typeof CONTAINER_ATTACH_KEY, ContainerAttachValue>;
|
||||
export interface ContainerContextValue {
|
||||
container: MediaContainer | null;
|
||||
setContainer: (container: MediaContainer | null) => void;
|
||||
}
|
||||
|
||||
export const containerAttachContext = createContext<ContainerAttachValue, typeof CONTAINER_ATTACH_KEY>(
|
||||
CONTAINER_ATTACH_KEY
|
||||
export type ContainerContext = Context<typeof CONTAINER_CONTEXT_KEY, ContainerContextValue>;
|
||||
|
||||
export const containerContext = createContext<ContainerContextValue, typeof CONTAINER_CONTEXT_KEY>(
|
||||
CONTAINER_CONTEXT_KEY
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ import { combine, createStore } from '@videojs/store';
|
||||
|
||||
import { type ContainerMixin, createContainerMixin } from '../store/container-mixin';
|
||||
import { createProviderMixin, type ProviderMixin } from '../store/provider-mixin';
|
||||
import { containerAttachContext, mediaAttachContext, type PlayerContext, playerContext } from './context';
|
||||
import { containerContext, mediaContext, type PlayerContext, playerContext } from './context';
|
||||
import { PlayerController } from './player-controller';
|
||||
|
||||
export interface CreatePlayerConfig<Features extends AnyPlayerFeature[]> {
|
||||
@@ -89,14 +89,14 @@ export function createPlayer(config: CreatePlayerConfig<AnyPlayerFeature[]>): Cr
|
||||
|
||||
const ProviderMixin = createProviderMixin<PlayerStore>({
|
||||
playerContext,
|
||||
mediaAttachContext,
|
||||
containerAttachContext,
|
||||
mediaContext,
|
||||
containerContext,
|
||||
factory: create,
|
||||
});
|
||||
|
||||
const ContainerMixin = createContainerMixin<PlayerStore>({
|
||||
playerContext,
|
||||
containerAttachContext,
|
||||
containerContext,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { MediaContainer, PlayerStore } from '@videojs/core/dom';
|
||||
import { ContextConsumer } from '@videojs/element/context';
|
||||
import type { MediaElementConstructor } from '@/ui/media-element';
|
||||
import type { ContainerAttachContext, PlayerContext } from '../player/context';
|
||||
import type { ContainerContext, PlayerContext } from '../player/context';
|
||||
import type { PlayerConsumer, PlayerConsumerConstructor } from './types';
|
||||
|
||||
export interface ContainerMixinConfig<Store extends PlayerStore> {
|
||||
playerContext: PlayerContext<Store>;
|
||||
containerAttachContext: ContainerAttachContext;
|
||||
containerContext: ContainerContext;
|
||||
}
|
||||
|
||||
export type ContainerMixin<Store extends PlayerStore> = <Class extends MediaElementConstructor>(
|
||||
@@ -15,9 +15,9 @@ export type ContainerMixin<Store extends PlayerStore> = <Class extends MediaElem
|
||||
|
||||
/**
|
||||
* Create a mixin that consumes player context and registers itself as the
|
||||
* container element with the provider via `containerAttachContext`.
|
||||
* container element with the provider via `containerContext`.
|
||||
*
|
||||
* @param config - Container configuration with player and attach contexts.
|
||||
* @param config - Container configuration with player and container contexts.
|
||||
*/
|
||||
export function createContainerMixin<Store extends PlayerStore>(
|
||||
config: ContainerMixinConfig<Store>
|
||||
@@ -39,9 +39,9 @@ export function createContainerMixin<Store extends PlayerStore>(
|
||||
});
|
||||
|
||||
new ContextConsumer(this, {
|
||||
context: config.containerAttachContext,
|
||||
context: config.containerContext,
|
||||
callback: (value) => {
|
||||
this.#setContainer = value ?? null;
|
||||
this.#setContainer = value?.setContainer ?? null;
|
||||
if (this.isConnected) this.#setContainer?.(this);
|
||||
},
|
||||
subscribe: true,
|
||||
|
||||
@@ -2,20 +2,20 @@ import type { Media } from '@videojs/core/dom';
|
||||
import { ContextEvent } from '@videojs/element/context';
|
||||
import type { CustomElement } from '@videojs/utils/dom';
|
||||
import type { AnyConstructor, Constructor } from '@videojs/utils/types';
|
||||
import { type MediaAttachContext, mediaAttachContext } from '../player/context';
|
||||
import { type MediaContext, mediaContext } from '../player/context';
|
||||
|
||||
export type MediaAttachMixin = <Class extends AnyConstructor<HTMLElement>>(BaseClass: Class) => Class;
|
||||
|
||||
/**
|
||||
* Create a mixin that consumes `mediaAttachContext` and registers the
|
||||
* Create a mixin that consumes `mediaContext` and registers the
|
||||
* element as the media with the provider.
|
||||
*
|
||||
* Uses the raw context-request protocol so it works with any
|
||||
* `HTMLElement` subclass — no `ReactiveControllerHost` required.
|
||||
*
|
||||
* @param context - The media attach context to consume.
|
||||
* @param context - The media context to consume.
|
||||
*/
|
||||
export function createMediaAttachMixin(context: MediaAttachContext): MediaAttachMixin {
|
||||
export function createMediaAttachMixin(context: MediaContext): MediaAttachMixin {
|
||||
return <Class extends AnyConstructor<HTMLElement>>(BaseClass: Class) => {
|
||||
class MediaAttachElement extends (BaseClass as unknown as Constructor<CustomElement>) {
|
||||
#setMedia: ((media: Media | null) => void) | null = null;
|
||||
@@ -34,7 +34,7 @@ export function createMediaAttachMixin(context: MediaAttachContext): MediaAttach
|
||||
this,
|
||||
(value, unsubscribe) => {
|
||||
if (unsubscribe) this.#unsubscribe = unsubscribe;
|
||||
this.#setMedia = value ?? null;
|
||||
this.#setMedia = value?.setMedia ?? null;
|
||||
if (this.isConnected) {
|
||||
this.#setMedia?.(this.getMediaTarget());
|
||||
}
|
||||
@@ -57,4 +57,4 @@ export function createMediaAttachMixin(context: MediaAttachContext): MediaAttach
|
||||
};
|
||||
}
|
||||
|
||||
export const MediaAttachMixin = createMediaAttachMixin(mediaAttachContext);
|
||||
export const MediaAttachMixin = createMediaAttachMixin(mediaContext);
|
||||
|
||||
@@ -2,13 +2,13 @@ import type { Media, MediaContainer, PlayerStore, PlayerTarget } from '@videojs/
|
||||
import { ContextProvider } from '@videojs/element/context';
|
||||
import { isNull } from '@videojs/utils/predicate';
|
||||
import type { MediaElementConstructor } from '@/ui/media-element';
|
||||
import type { ContainerAttachContext, MediaAttachContext, PlayerContext } from '../player/context';
|
||||
import type { ContainerContext, MediaContext, PlayerContext } from '../player/context';
|
||||
import type { PlayerProvider, PlayerProviderConstructor } from './types';
|
||||
|
||||
export interface ProviderMixinConfig<Store extends PlayerStore> {
|
||||
playerContext: PlayerContext<Store>;
|
||||
mediaAttachContext: MediaAttachContext;
|
||||
containerAttachContext: ContainerAttachContext;
|
||||
mediaContext: MediaContext;
|
||||
containerContext: ContainerContext;
|
||||
factory: () => Store;
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ export type ProviderMixin<Store extends PlayerStore> = <Class extends MediaEleme
|
||||
* Create a mixin that provides player context to descendant elements and
|
||||
* owns the `store.attach()` lifecycle.
|
||||
*
|
||||
* Media and container elements register themselves via attach contexts —
|
||||
* setter callbacks flowing downward from the provider. When a media element
|
||||
* is available, the provider calls `store.attach({ media, container })`.
|
||||
* Media and container elements register themselves via media/container
|
||||
* contexts that carry both the current value and a setter. When a media
|
||||
* element is available, the provider calls `store.attach({ media, container })`.
|
||||
*
|
||||
* As a fallback for plain `<video>`/`<audio>` that can't consume context,
|
||||
* the provider queries its subtree after a microtask.
|
||||
@@ -43,12 +43,14 @@ export function createProviderMixin<Store extends PlayerStore>(
|
||||
#setMedia = (media: Media | null): void => {
|
||||
if (this.#media === media) return;
|
||||
this.#media = media;
|
||||
this.#mediaProvider.setValue({ media, setMedia: this.#setMedia });
|
||||
this.#tryAttach();
|
||||
};
|
||||
|
||||
#setContainer = (container: MediaContainer | null): void => {
|
||||
if (this.#container === container) return;
|
||||
this.#container = container;
|
||||
this.#containerProvider.setValue({ container, setContainer: this.#setContainer });
|
||||
this.#tryAttach();
|
||||
};
|
||||
|
||||
@@ -57,14 +59,14 @@ export function createProviderMixin<Store extends PlayerStore>(
|
||||
initialValue: this.store,
|
||||
});
|
||||
|
||||
#mediaAttachProvider = new ContextProvider(this, {
|
||||
context: config.mediaAttachContext,
|
||||
initialValue: this.#setMedia,
|
||||
#mediaProvider = new ContextProvider(this, {
|
||||
context: config.mediaContext,
|
||||
initialValue: { media: this.#media, setMedia: this.#setMedia },
|
||||
});
|
||||
|
||||
#containerAttachProvider = new ContextProvider(this, {
|
||||
context: config.containerAttachContext,
|
||||
initialValue: this.#setContainer,
|
||||
#containerProvider = new ContextProvider(this, {
|
||||
context: config.containerContext,
|
||||
initialValue: { container: this.#container, setContainer: this.#setContainer },
|
||||
});
|
||||
|
||||
get store(): Store {
|
||||
@@ -78,8 +80,8 @@ export function createProviderMixin<Store extends PlayerStore>(
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.#playerProvider.setValue(this.store);
|
||||
this.#mediaAttachProvider.setValue(this.#setMedia);
|
||||
this.#containerAttachProvider.setValue(this.#setContainer);
|
||||
this.#mediaProvider.setValue({ media: this.#media, setMedia: this.#setMedia });
|
||||
this.#containerProvider.setValue({ container: this.#container, setContainer: this.#setContainer });
|
||||
this.#tryAttach();
|
||||
this.#queueFallbackDiscovery();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user