diff --git a/internal/design/reactive-element-lit-compat.md b/internal/design/reactive-element-lit-compat.md index 2824348e..7bf7d9cc 100644 --- a/internal/design/reactive-element-lit-compat.md +++ b/internal/design/reactive-element-lit-compat.md @@ -35,7 +35,7 @@ Composes `DestroyMixin(ReactiveElement)` and bridges `destroyCallback()` to ### HlsCustomMedia Uses `DestroyMixin(HTMLElement)` as the base for `CustomMediaMixin`. The `DelegateMedia` -class (from `MediaDelegateMixin`) overrides `destroyCallback()` to call +class (from `DelegateMixin`) overrides `destroyCallback()` to call `this.#delegate.destroy?.()`, which destroys the HLS engine. ## Why a Mixin diff --git a/packages/core/src/core/media/delegate.ts b/packages/core/src/core/media/delegate.ts index 8476097e..67633e72 100644 --- a/packages/core/src/core/media/delegate.ts +++ b/packages/core/src/core/media/delegate.ts @@ -2,7 +2,7 @@ import type { Constructor } from '@videojs/utils/types'; import { defineClassPropHooks } from '../utils/define-class-prop-hooks'; -export interface MediaDelegate { +export interface Delegate { attach?(target: EventTarget): void; detach?(): void; } @@ -12,13 +12,13 @@ export interface MediaDelegate { * and method calls to an instance of `DelegateClass` before falling through * to the base class implementation. * - * Works with both `CustomMediaMixin` and `MediaProxyMixin`. + * Works with both `CustomMediaMixin` and `ProxyMixin`. */ -export function MediaDelegateMixin, Delegate extends Constructor>( +export function DelegateMixin, D extends Constructor>( BaseClass: Base, - DelegateClass: Delegate + DelegateClass: D ) { - class DelegateMedia extends (BaseClass as Constructor) { + class DelegateImpl extends (BaseClass as Constructor) { #delegate = new DelegateClass(); get(prop: string): any { @@ -55,12 +55,12 @@ export function MediaDelegateMixin, Delegate exten } for (let proto = DelegateClass.prototype; proto && proto !== Object.prototype; proto = Object.getPrototypeOf(proto)) { - defineClassPropHooks(DelegateMedia, proto); + defineClassPropHooks(DelegateImpl, proto); } - return DelegateMedia as unknown as Constructor< + return DelegateImpl as unknown as Constructor< InstanceType & - InstanceType & { + InstanceType & { attach(target: EventTarget): void; detach(): void; } diff --git a/packages/core/src/core/media/proxy.ts b/packages/core/src/core/media/proxy.ts index ffe60464..1a3414d5 100644 --- a/packages/core/src/core/media/proxy.ts +++ b/packages/core/src/core/media/proxy.ts @@ -1,8 +1,6 @@ import type { AnyConstructor, Constructor } from '@videojs/utils/types'; import { defineClassPropHooks } from '../utils/define-class-prop-hooks'; -export interface MediaApiProxyTarget extends EventTarget {} - /** * This mixin creates an API from the passed classes and proxies the methods and properties to the attached target. * @@ -14,33 +12,33 @@ export interface MediaApiProxyTarget extends EventTarget {} * * The `get`, `set`, and `call` methods can be overridden to provide catch-all custom behavior. */ -export const MediaProxyMixin = ( +export const ProxyMixin = ( PrimaryClass: AnyConstructor, ...AdditionalClasses: AnyConstructor[] ) => { - class MediaApiProxy { - #target: MediaApiProxyTarget | null = null; + class MediaProxy { + #target: EventTarget | null = null; get target() { return this.#target; } - get(prop: keyof MediaApiProxyTarget): any { + get(prop: keyof EventTarget): any { return this.target?.[prop]; } - set(prop: keyof MediaApiProxyTarget, val: any): void { + set(prop: keyof EventTarget, val: any): void { if (this.target) { this.target[prop] = val; } } - call(prop: keyof MediaApiProxyTarget, ...args: any[]): any { + call(prop: keyof EventTarget, ...args: any[]): any { const nativeFn = this.target?.[prop] as ((...args: any[]) => any) | undefined; return nativeFn?.apply(this.target, args); } - attach(target: MediaApiProxyTarget): void { + attach(target: EventTarget): void { if (!target || this.#target === target) return; this.#target = target; } @@ -52,8 +50,8 @@ export const MediaProxyMixin = ( } for (const Class of [PrimaryClass, ...AdditionalClasses]) { - defineClassPropHooks(MediaApiProxy, Class.prototype); + defineClassPropHooks(MediaProxy, Class.prototype); } - return MediaApiProxy as unknown as Constructor; + return MediaProxy as unknown as Constructor; }; diff --git a/packages/core/src/dom/media/dash/index.ts b/packages/core/src/dom/media/dash/index.ts index 03f8b404..c60f8763 100644 --- a/packages/core/src/dom/media/dash/index.ts +++ b/packages/core/src/dom/media/dash/index.ts @@ -1,10 +1,10 @@ import * as dashjs from 'dashjs'; -import { type MediaDelegate, MediaDelegateMixin } from '../../../core/media/delegate'; -import { MediaProxyMixin } from '../../../core/media/proxy'; +import { type Delegate, DelegateMixin } from '../../../core/media/delegate'; import { CustomMediaMixin } from '../custom-media-element'; +import { MediaProxyMixin } from '../proxy'; -export class DashMediaDelegateBase implements MediaDelegate { +export class DashMediaDelegateBase implements Delegate { #engine: dashjs.MediaPlayerClass; constructor() { @@ -39,17 +39,10 @@ export class DashMediaDelegateBase implements MediaDelegate { } // This is used by the web component because it needs to extend HTMLElement! -export class DashCustomMedia extends MediaDelegateMixin( +export class DashCustomMedia extends DelegateMixin( CustomMediaMixin(globalThis.HTMLElement ?? class {}, { tag: 'video' }), DashMediaDelegateBase ) {} // This is used by the React component. -export class DashMedia extends MediaDelegateMixin( - MediaProxyMixin( - globalThis.HTMLVideoElement ?? class {}, - globalThis.HTMLMediaElement ?? class {}, - globalThis.EventTarget ?? class {} - ), - DashMediaDelegateBase -) {} +export class DashMedia extends DelegateMixin(MediaProxyMixin, DashMediaDelegateBase) {} diff --git a/packages/core/src/dom/media/hls/index.ts b/packages/core/src/dom/media/hls/index.ts index 7f8906e4..2ae498a6 100644 --- a/packages/core/src/dom/media/hls/index.ts +++ b/packages/core/src/dom/media/hls/index.ts @@ -1,8 +1,8 @@ import Hls from 'hls.js'; -import { type MediaDelegate, MediaDelegateMixin } from '../../../core/media/delegate'; -import { MediaProxyMixin } from '../../../core/media/proxy'; +import { type Delegate, DelegateMixin } from '../../../core/media/delegate'; import { CustomMediaMixin } from '../custom-media-element'; +import { MediaProxyMixin } from '../proxy'; import { HlsMediaTextTracksMixin } from './text-tracks'; const defaultConfig = { @@ -13,7 +13,7 @@ const defaultConfig = { capLevelOnFPSDrop: true, }; -export class HlsMediaDelegateBase implements MediaDelegate { +export class HlsMediaDelegateBase implements Delegate { #engine = Hls.isSupported() ? new Hls(defaultConfig) : null; get engine(): Hls | null { @@ -44,17 +44,10 @@ export class HlsMediaDelegateBase implements MediaDelegate { const HlsMediaDelegate = HlsMediaTextTracksMixin(HlsMediaDelegateBase); // This is used by the web component because it needs to extend HTMLElement! -export class HlsCustomMedia extends MediaDelegateMixin( +export class HlsCustomMedia extends DelegateMixin( CustomMediaMixin(globalThis.HTMLElement ?? class {}, { tag: 'video' }), HlsMediaDelegate ) {} // This is used by the React component. -export class HlsMedia extends MediaDelegateMixin( - MediaProxyMixin( - globalThis.HTMLVideoElement ?? class {}, - globalThis.HTMLMediaElement ?? class {}, - globalThis.EventTarget ?? class {} - ), - HlsMediaDelegate -) {} +export class HlsMedia extends DelegateMixin(MediaProxyMixin, HlsMediaDelegate) {} diff --git a/packages/core/src/dom/media/proxy.ts b/packages/core/src/dom/media/proxy.ts new file mode 100644 index 00000000..f685d91d --- /dev/null +++ b/packages/core/src/dom/media/proxy.ts @@ -0,0 +1,7 @@ +import { ProxyMixin } from '../../core/media/proxy'; + +export const MediaProxyMixin = ProxyMixin( + globalThis.HTMLVideoElement ?? class {}, + globalThis.HTMLMediaElement ?? class {}, + globalThis.EventTarget ?? class {} +); diff --git a/packages/core/src/dom/media/simple-hls/index.ts b/packages/core/src/dom/media/simple-hls/index.ts index ed3b580a..74910f4f 100644 --- a/packages/core/src/dom/media/simple-hls/index.ts +++ b/packages/core/src/dom/media/simple-hls/index.ts @@ -1,20 +1,13 @@ import { SpfMedia as SpfMediaDelegate } from '@videojs/spf/dom'; -import { MediaDelegateMixin } from '../../../core/media/delegate'; -import { MediaProxyMixin } from '../../../core/media/proxy'; +import { DelegateMixin } from '../../../core/media/delegate'; import { CustomMediaMixin } from '../custom-media-element'; +import { MediaProxyMixin } from '../proxy'; // This is used by the web component because it needs to extend HTMLElement! -export class SimpleHlsCustomMedia extends MediaDelegateMixin( +export class SimpleHlsCustomMedia extends DelegateMixin( CustomMediaMixin(globalThis.HTMLElement ?? class {}, { tag: 'video' }), SpfMediaDelegate ) {} // This is used by the React component. -export class SimpleHlsMedia extends MediaDelegateMixin( - MediaProxyMixin( - globalThis.HTMLVideoElement ?? class {}, - globalThis.HTMLMediaElement ?? class {}, - globalThis.EventTarget ?? class {} - ), - SpfMediaDelegate -) {} +export class SimpleHlsMedia extends DelegateMixin(MediaProxyMixin, SpfMediaDelegate) {}