diff --git a/packages/google-cast/src/google-cast.ts b/packages/google-cast/src/google-cast.ts index 8c35b82aa9..1f08e7abde 100644 --- a/packages/google-cast/src/google-cast.ts +++ b/packages/google-cast/src/google-cast.ts @@ -1,5 +1,5 @@ import type { MediaStreamType } from '@videojs/media'; -import type { HTMLMediaElementHost, HTMLMediaTargetLike, MediaComponent } from '@videojs/media/dom'; +import type { HTMLMediaElementHost, HTMLMediaTargetLike, MediaExtension } from '@videojs/media/dom'; import { GoogleCastProvider } from './google-cast-provider'; import { requiresCastFramework } from './utils'; @@ -19,7 +19,7 @@ export interface GoogleCastExtensionProps { customData?: Record | null | undefined; } -export class GoogleCastExtension implements GoogleCastExtensionProps, MediaComponent { +export class GoogleCastExtension implements GoogleCastExtensionProps, MediaExtension { static defaultProps: GoogleCastExtensionProps = { src: undefined, contentType: undefined, diff --git a/packages/google-cast/src/tests/google-cast-provider.test.ts b/packages/google-cast/src/tests/google-cast-provider.test.ts index e7cdd55986..201dfefbfd 100644 --- a/packages/google-cast/src/tests/google-cast-provider.test.ts +++ b/packages/google-cast/src/tests/google-cast-provider.test.ts @@ -1,4 +1,4 @@ -import { addMediaComponent, type HTMLMediaTargetLike, HTMLVideoElementHost } from '@videojs/media/dom'; +import { addMediaExtension, type HTMLMediaTargetLike, HTMLVideoElementHost } from '@videojs/media/dom'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'; import { GoogleCastProvider } from '../google-cast-provider'; @@ -108,7 +108,7 @@ describe('GoogleCastExtension', () => { host.attach(target as Parameters[0]); - addMediaComponent(host, new GoogleCastExtension()); + addMediaExtension(host, new GoogleCastExtension()); expect(ensureCastFramework).not.toHaveBeenCalled(); // The component's override must expose `remote` as an accessor so host @@ -123,7 +123,7 @@ describe('GoogleCastExtension', () => { const host = new HTMLVideoElementHost(); - addMediaComponent(host, new GoogleCastExtension()); + addMediaExtension(host, new GoogleCastExtension()); void host.remote; diff --git a/packages/google-cast/src/tests/google-cast.test.ts b/packages/google-cast/src/tests/google-cast.test.ts index 1d0257ddbd..64a6bc9110 100644 --- a/packages/google-cast/src/tests/google-cast.test.ts +++ b/packages/google-cast/src/tests/google-cast.test.ts @@ -1,4 +1,4 @@ -import { addMediaComponent, HTMLVideoElementHost } from '@videojs/media/dom'; +import { addMediaExtension, HTMLVideoElementHost } from '@videojs/media/dom'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'; import { GoogleCastExtension } from '../index'; @@ -49,7 +49,7 @@ function setup() { const googleCast = new GoogleCastExtension(); - addMediaComponent(host, googleCast); + addMediaExtension(host, googleCast); const provider = mocks.FakeProvider.instances.at(-1)!; diff --git a/packages/hlsjs-video/src/tests/hls-media.test.ts b/packages/hlsjs-video/src/tests/hls-media.test.ts index abdb069740..014568d5c4 100644 --- a/packages/hlsjs-video/src/tests/hls-media.test.ts +++ b/packages/hlsjs-video/src/tests/hls-media.test.ts @@ -1,5 +1,5 @@ import { MediaError, type RemotePlaybackLike } from '@videojs/media'; -import { addMediaComponent, CustomMediaElement, type MediaComponent } from '@videojs/media/dom'; +import { addMediaExtension, CustomMediaElement, type MediaExtension } from '@videojs/media/dom'; import { NativeHlsMedia } from '@videojs/native-hls-video'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'; @@ -807,13 +807,13 @@ describe('HlsJsMedia', () => { media.attach(video); - const component: MediaComponent = { + const component: MediaExtension = { get targetOverride() { return { remote: { state: 'connected' } as RemotePlaybackLike, load }; }, }; - addMediaComponent(media, component); + addMediaExtension(media, component); return { media }; } diff --git a/packages/html/src/extensions/google-cast/extension.ts b/packages/html/src/extensions/google-cast/extension.ts index 494ab9acab..4a6189640d 100644 --- a/packages/html/src/extensions/google-cast/extension.ts +++ b/packages/html/src/extensions/google-cast/extension.ts @@ -1,7 +1,7 @@ import type { PropertyDeclarationMap } from '@videojs/element'; import { GoogleCastExtension as GoogleCastExtensionBase, type GoogleCastExtensionProps } from '@videojs/google-cast'; -import { MediaComponentElement } from '../media-component-element'; +import { MediaExtensionElement } from '../media-extension-element'; /** * Adds the Google Cast extension to the surrounding player's media. @@ -16,7 +16,7 @@ import { MediaComponentElement } from '../media-component-element'; * * ```; */ -export class GoogleCastExtension extends MediaComponentElement { +export class GoogleCastExtension extends MediaExtensionElement { static readonly tagName = 'google-cast'; static override properties = { diff --git a/packages/html/src/extensions/media-component-element.ts b/packages/html/src/extensions/media-extension-element.ts similarity index 84% rename from packages/html/src/extensions/media-component-element.ts rename to packages/html/src/extensions/media-extension-element.ts index 80167dea50..d102624fbe 100644 --- a/packages/html/src/extensions/media-component-element.ts +++ b/packages/html/src/extensions/media-extension-element.ts @@ -1,10 +1,10 @@ import { ContextConsumer } from '@videojs/element/context'; import { - addMediaComponent, + addMediaExtension, HTMLMediaElementHost, type HTMLMediaTargetLike, type Media, - type MediaComponent, + type MediaExtension, } from '@videojs/media/dom'; import { mediaContext } from '../player/context'; @@ -22,27 +22,27 @@ function resolveMediaHost(media: Media | null): MediaHost | null { } /** - * Abstract base for extension elements that register a media component (e.g. Mux Data, Google Cast) with the media + * Abstract base for extension elements that register a media extension (e.g. Mux Data, Google Cast) with the media * provided by the surrounding player. * * Place inside a player, as a sibling of the media element. The component is registered when a media host becomes * available, follows the media when it changes, is removed when this element disconnects, and is destroyed with this * element. */ -export abstract class MediaComponentElement extends UIElement { +export abstract class MediaExtensionElement extends UIElement { #component: Component | null = null; #host: MediaHost | null = null; #removeComponent: (() => void) | null = null; /** - * Create the media component this element registers. Called once, lazily. + * Create the media extension this element registers. Called once, lazily. * * Must be a method rather than a field: upgrading an element that is already in the document runs its constructor * while connected, so the media context callback below can fire before subclass field initializers have run. */ protected abstract createComponent(): Component; - /** The media component instance registered with the media host. */ + /** The media extension instance registered with the media host. */ protected get component(): Component { return (this.#component ??= this.createComponent()); } @@ -78,6 +78,6 @@ export abstract class MediaComponentElement ex this.#removeComponent = null; this.#host = host; - if (host) this.#removeComponent = addMediaComponent(host, this.component); + if (host) this.#removeComponent = addMediaExtension(host, this.component); } } diff --git a/packages/html/src/extensions/mux-data/extension.ts b/packages/html/src/extensions/mux-data/extension.ts index 00061e0bfb..2a1991d40a 100644 --- a/packages/html/src/extensions/mux-data/extension.ts +++ b/packages/html/src/extensions/mux-data/extension.ts @@ -1,7 +1,7 @@ import type { PropertyDeclarationMap } from '@videojs/element'; import { MuxDataExtension as MuxDataExtensionBase, type MuxDataExtensionProps } from '@videojs/mux-data'; -import { MediaComponentElement } from '../media-component-element'; +import { MediaExtensionElement } from '../media-extension-element'; /** * Adds the [Mux Data](https://www.mux.com/data) extension to the surrounding player's media. @@ -22,7 +22,7 @@ import { MediaComponentElement } from '../media-component-element'; * * ```; */ -export class MuxDataExtension extends MediaComponentElement { +export class MuxDataExtension extends MediaExtensionElement { static readonly tagName = 'mux-data'; static override properties = { diff --git a/packages/html/src/extensions/tests/google-cast.test.ts b/packages/html/src/extensions/tests/google-cast.test.ts index ece67a03c7..67e5884d89 100644 --- a/packages/html/src/extensions/tests/google-cast.test.ts +++ b/packages/html/src/extensions/tests/google-cast.test.ts @@ -1,6 +1,6 @@ import { ContextProvider } from '@videojs/element/context'; import { GoogleCastExtension as GoogleCastExtensionBase } from '@videojs/google-cast'; -import { getMediaComponents, HTMLVideoElementHost, type Media } from '@videojs/media/dom'; +import { getMediaExtensions, HTMLVideoElementHost, type Media } from '@videojs/media/dom'; import { afterEach, describe, expect, it } from 'vite-plus/test'; import { mediaContext } from '../../player/context'; @@ -42,7 +42,7 @@ describe('GoogleCastExtension', () => { provider.setMedia(host as unknown as Media); - expect(getMediaComponents(host).get(GoogleCastExtensionBase)).toBeInstanceOf(GoogleCastExtensionBase); + expect(getMediaExtensions(host).get(GoogleCastExtensionBase)).toBeInstanceOf(GoogleCastExtensionBase); }); it('leaves the component to the base class lazy getter', () => { @@ -57,7 +57,7 @@ describe('GoogleCastExtension', () => { provider.setMedia({ host } as unknown as Media); - expect(getMediaComponents(host).get(GoogleCastExtensionBase)).toBeInstanceOf(GoogleCastExtensionBase); + expect(getMediaExtensions(host).get(GoogleCastExtensionBase)).toBeInstanceOf(GoogleCastExtensionBase); }); it('ignores media that is not a media host', () => { @@ -66,7 +66,7 @@ describe('GoogleCastExtension', () => { provider.setMedia(host as unknown as Media); provider.setMedia(document.createElement('video') as unknown as Media); - expect(getMediaComponents(host).get(GoogleCastExtensionBase)).toBeUndefined(); + expect(getMediaExtensions(host).get(GoogleCastExtensionBase)).toBeUndefined(); }); it('forwards attributes to the component', () => { @@ -79,7 +79,7 @@ describe('GoogleCastExtension', () => { el.setAttribute('stream-type', 'live'); el.setAttribute('src', 'https://example.com/stream.m3u8'); - const component = getMediaComponents(host).get(GoogleCastExtensionBase)!; + const component = getMediaExtensions(host).get(GoogleCastExtensionBase)!; expect(component.receiver).toBe('APP_ID'); expect(component.contentType).toBe('application/x-mpegURL'); @@ -105,8 +105,8 @@ describe('GoogleCastExtension', () => { provider.setMedia(host as unknown as Media); provider.setMedia(nextHost as unknown as Media); - expect(getMediaComponents(host).get(GoogleCastExtensionBase)).toBeUndefined(); - expect(getMediaComponents(nextHost).get(GoogleCastExtensionBase)).toBeInstanceOf(GoogleCastExtensionBase); + expect(getMediaExtensions(host).get(GoogleCastExtensionBase)).toBeUndefined(); + expect(getMediaExtensions(nextHost).get(GoogleCastExtensionBase)).toBeInstanceOf(GoogleCastExtensionBase); }); it('removes the component when the element disconnects', () => { @@ -116,7 +116,7 @@ describe('GoogleCastExtension', () => { el.remove(); - expect(getMediaComponents(host).get(GoogleCastExtensionBase)).toBeUndefined(); + expect(getMediaExtensions(host).get(GoogleCastExtensionBase)).toBeUndefined(); }); it('removes the component on destroy', () => { @@ -126,6 +126,6 @@ describe('GoogleCastExtension', () => { el.destroy(); - expect(getMediaComponents(host).get(GoogleCastExtensionBase)).toBeUndefined(); + expect(getMediaExtensions(host).get(GoogleCastExtensionBase)).toBeUndefined(); }); }); diff --git a/packages/html/src/extensions/tests/media-component-element.test.ts b/packages/html/src/extensions/tests/media-extension-element.test.ts similarity index 77% rename from packages/html/src/extensions/tests/media-component-element.test.ts rename to packages/html/src/extensions/tests/media-extension-element.test.ts index f77ea22ee2..51249eceff 100644 --- a/packages/html/src/extensions/tests/media-component-element.test.ts +++ b/packages/html/src/extensions/tests/media-extension-element.test.ts @@ -1,12 +1,12 @@ import { ContextProvider } from '@videojs/element/context'; -import { getMediaComponents, HTMLVideoElementHost, type Media, type MediaComponent } from '@videojs/media/dom'; +import { getMediaExtensions, HTMLVideoElementHost, type Media, type MediaExtension } from '@videojs/media/dom'; import { afterEach, describe, expect, it } from 'vite-plus/test'; import { mediaContext } from '../../player/context'; import { UIElement } from '../../ui/ui-element'; -import { MediaComponentElement } from '../media-component-element'; +import { MediaExtensionElement } from '../media-extension-element'; -class FakeComponent implements MediaComponent { +class FakeComponent implements MediaExtension { destroyed = false; destroy() { this.destroyed = true; @@ -24,7 +24,7 @@ class TestMediaProvider extends UIElement { } } -class TestMediaComponentElement extends MediaComponentElement { +class TestMediaExtensionElement extends MediaExtensionElement { static readonly tagName = 'test-media-component'; /** @@ -44,15 +44,15 @@ class TestMediaComponentElement extends MediaComponentElement { } customElements.define('test-media-component-provider', TestMediaProvider); -customElements.define(TestMediaComponentElement.tagName, TestMediaComponentElement); +customElements.define(TestMediaExtensionElement.tagName, TestMediaExtensionElement); afterEach(() => { document.body.innerHTML = ''; }); -describe('MediaComponentElement', () => { +describe('MediaExtensionElement', () => { it('resolves the component before subclass fields initialize', () => { - const el = new TestMediaComponentElement(); + const el = new TestMediaExtensionElement(); expect(el.componentDuringFieldInit).toBeInstanceOf(FakeComponent); // Created once and reused, not re-created per access. @@ -62,19 +62,19 @@ describe('MediaComponentElement', () => { it('registers the extension component with the media host from context', () => { const host = new HTMLVideoElementHost(); const provider = new TestMediaProvider(); - const el = new TestMediaComponentElement(); + const el = new TestMediaExtensionElement(); provider.append(el); document.body.append(provider); provider.setMedia(host as unknown as Media); - expect(getMediaComponents(host).get(FakeComponent)).toBe(el.instance); + expect(getMediaExtensions(host).get(FakeComponent)).toBe(el.instance); }); it('destroys the component when the element is destroyed', () => { const host = new HTMLVideoElementHost(); const provider = new TestMediaProvider(); - const el = new TestMediaComponentElement(); + const el = new TestMediaExtensionElement(); provider.append(el); document.body.append(provider); @@ -83,15 +83,15 @@ describe('MediaComponentElement', () => { el.destroy(); expect(el.instance.destroyed).toBe(true); - expect(getMediaComponents(host).get(FakeComponent)).toBeUndefined(); + expect(getMediaExtensions(host).get(FakeComponent)).toBeUndefined(); }); it('does not create a component when destroyed before use', () => { - const el = new TestMediaComponentElement(); + const el = new TestMediaExtensionElement(); // `componentDuringFieldInit` already forced creation, so assert through a // subclass that never touches it. - class Untouched extends MediaComponentElement { + class Untouched extends MediaExtensionElement { created = 0; protected createComponent(): FakeComponent { this.created++; diff --git a/packages/html/src/extensions/tests/mux-data.test.ts b/packages/html/src/extensions/tests/mux-data.test.ts index 7759a2fee4..54422f53fd 100644 --- a/packages/html/src/extensions/tests/mux-data.test.ts +++ b/packages/html/src/extensions/tests/mux-data.test.ts @@ -1,5 +1,5 @@ import { ContextProvider } from '@videojs/element/context'; -import { getMediaComponents, HTMLVideoElementHost, type Media } from '@videojs/media/dom'; +import { getMediaExtensions, HTMLVideoElementHost, type Media } from '@videojs/media/dom'; import { MuxDataExtension as MuxDataExtensionBase } from '@videojs/mux-data'; import { afterEach, describe, expect, it } from 'vite-plus/test'; @@ -49,7 +49,7 @@ describe('MuxDataExtension', () => { provider.innerHTML = ''; - expect(getMediaComponents(host).get(MuxDataExtensionBase)).toBeInstanceOf(MuxDataExtensionBase); + expect(getMediaExtensions(host).get(MuxDataExtensionBase)).toBeInstanceOf(MuxDataExtensionBase); }); it('leaves the component to the base class lazy getter', () => { @@ -63,7 +63,7 @@ describe('MuxDataExtension', () => { provider.setMedia(host as unknown as Media); - expect(getMediaComponents(host).get(MuxDataExtensionBase)).toBeInstanceOf(MuxDataExtensionBase); + expect(getMediaExtensions(host).get(MuxDataExtensionBase)).toBeInstanceOf(MuxDataExtensionBase); }); it('forwards attributes to the component', () => { @@ -77,7 +77,7 @@ describe('MuxDataExtension', () => { el.setAttribute('debug', ''); el.setAttribute('disable-cookies', ''); - const component = getMediaComponents(host).get(MuxDataExtensionBase)!; + const component = getMediaExtensions(host).get(MuxDataExtensionBase)!; expect(component.envKey).toBe('test-key'); expect(component.playerSoftwareName).toBe('mux-video'); @@ -97,7 +97,7 @@ describe('MuxDataExtension', () => { el.metadata = metadata; - expect(getMediaComponents(host).get(MuxDataExtensionBase)!.metadata).toEqual(metadata); + expect(getMediaExtensions(host).get(MuxDataExtensionBase)!.metadata).toEqual(metadata); }); it('removes the component when the element disconnects', () => { @@ -107,6 +107,6 @@ describe('MuxDataExtension', () => { el.remove(); - expect(getMediaComponents(host).get(MuxDataExtensionBase)).toBeUndefined(); + expect(getMediaExtensions(host).get(MuxDataExtensionBase)).toBeUndefined(); }); }); diff --git a/packages/media/src/core/types.ts b/packages/media/src/core/types.ts index 83689ddb1e..c65c94e7ca 100644 --- a/packages/media/src/core/types.ts +++ b/packages/media/src/core/types.ts @@ -575,7 +575,7 @@ export interface Media +export interface CommonMedia extends Media, MediaPauseCapability, @@ -609,20 +609,20 @@ export interface MediaFull, + CommonMedia, MediaPlaysInlineCapability, MediaPosterCapability, MediaFullscreenCapability, MediaPictureInPictureCapability, MediaVideoDimensionsCapability {} -export interface AudioEvents extends MediaFullEvents {} +export interface AudioEvents extends CommonMediaEvents {} -export interface Audio extends MediaFull {} +export interface Audio extends CommonMedia {} // ---------------------------------------- // Target shapes diff --git a/packages/media/src/dom/media-host/media-host.ts b/packages/media/src/dom/media-host/media-host.ts index d7a3a98282..7000c848dd 100644 --- a/packages/media/src/dom/media-host/media-host.ts +++ b/packages/media/src/dom/media-host/media-host.ts @@ -3,23 +3,23 @@ import type { EventListenerFor, EventType, QueriedElement } from '@videojs/utils import { EMPTY_REMOTE, EMPTY_TEXT_TRACKS, EMPTY_TIME_RANGES } from '../../core/constants'; import { type EventLike, - type MediaFull, + type CommonMedia, type MediaStreamType, MediaStreamTypes, type MediaTargetLike, type TextTrackKind, type TextTrackLike, } from '../../core/types'; -import { getMediaComponents, getMediaOwner, getMediaProp, setMediaProp } from '../utils'; +import { getMediaExtensions, getMediaOwner, getMediaProp, setMediaProp } from '../utils'; -export { addMediaComponent, getMediaComponents, getMediaOwner, getMediaProp, setMediaProp } from '../utils'; +export { addMediaExtension, getMediaExtensions, getMediaOwner, getMediaProp, setMediaProp } from '../utils'; export interface HTMLMediaTargetLike extends MediaTargetLike, EventTarget { querySelector(selectors: string): E | null; querySelectorAll(selectors: string): NodeListOf | never[]; } -export interface MediaComponent { +export interface MediaExtension { readonly targetOverride?: Partial | null; setMedia?(host: HTMLMediaElementHost): void; attach?(target: Target): void; @@ -27,18 +27,18 @@ export interface MediaComponent { +export interface MediaExtensionConstructor { new (...args: any[]): T; } -export interface MediaComponents extends Map { - get(component: MediaComponentConstructor): T | undefined; - set(component: MediaComponentConstructor, instance: T): this; +export interface MediaExtensions extends Map { + get(component: MediaExtensionConstructor): T | undefined; + set(component: MediaExtensionConstructor, instance: T): this; } export class HTMLMediaElementHost extends EventTarget - implements MediaFull + implements CommonMedia { #target: Target | null = null; #eventTypes = new Set(); @@ -57,7 +57,7 @@ export class HTMLMediaElementHost`, + // Media extensions are owned by whoever registered them (e.g. ``, // ``), which may outlive this host. `detach()` above releases // them from the target, so only drop the registrations here and leave // destruction to the owner. - getMediaComponents(this).clear(); + getMediaExtensions(this).clear(); } querySelectorAll(selectors: S) { diff --git a/packages/media/src/dom/tests/media-components.test.ts b/packages/media/src/dom/tests/media-extensions.test.ts similarity index 79% rename from packages/media/src/dom/tests/media-components.test.ts rename to packages/media/src/dom/tests/media-extensions.test.ts index bb6d1f49aa..c5498138a7 100644 --- a/packages/media/src/dom/tests/media-components.test.ts +++ b/packages/media/src/dom/tests/media-extensions.test.ts @@ -1,28 +1,28 @@ import { afterEach, describe, expect, it } from 'vite-plus/test'; import { HTMLAudioElementHost } from '../audio-host'; -import type { MediaComponent } from '../media-host'; -import { addMediaComponent, getMediaProp } from '../utils'; +import type { MediaExtension } from '../media-host'; +import { addMediaExtension, getMediaProp } from '../utils'; afterEach(() => { document.body.innerHTML = ''; }); -class DetachableComponent implements MediaComponent { +class DetachableComponent implements MediaExtension { detachCount = 0; detach() { this.detachCount++; } } -describe('addMediaComponent', () => { +describe('addMediaExtension', () => { it('detaches the component when it is unregistered', () => { const host = new HTMLAudioElementHost(); host.attach(document.createElement('audio')); const component = new DetachableComponent(); - const remove = addMediaComponent(host, component); + const remove = addMediaExtension(host, component); remove(); @@ -36,9 +36,9 @@ describe('addMediaComponent', () => { const first = new DetachableComponent(); const second = new DetachableComponent(); - const removeFirst = addMediaComponent(host, first); + const removeFirst = addMediaExtension(host, first); - addMediaComponent(host, second); + addMediaExtension(host, second); removeFirst(); expect(first.detachCount).toBe(1); diff --git a/packages/media/src/dom/tests/media-host.test.ts b/packages/media/src/dom/tests/media-host.test.ts index 72c95bdfca..21ce5335b1 100644 --- a/packages/media/src/dom/tests/media-host.test.ts +++ b/packages/media/src/dom/tests/media-host.test.ts @@ -1,37 +1,37 @@ import { afterEach, describe, expect, it, vi } from 'vite-plus/test'; import { HTMLAudioElementHost } from '../audio-host'; -import { addMediaComponent, type MediaComponent } from '../media-host'; +import { addMediaExtension, type MediaExtension } from '../media-host'; afterEach(() => { document.body.innerHTML = ''; }); -class MutedOverride implements MediaComponent { +class MutedOverride implements MediaExtension { get targetOverride() { return { muted: true }; } } -class VolumeOverride implements MediaComponent { +class VolumeOverride implements MediaExtension { get targetOverride() { return { volume: 0.5 }; } } -class ContentDataOverride implements MediaComponent { +class ContentDataOverride implements MediaExtension { get targetOverride() { return { contentData: { title: 'Component title' } }; } } -class AttachTracking implements MediaComponent { +class AttachTracking implements MediaExtension { attach = vi.fn(); detach = vi.fn(); destroy = vi.fn(); } -class CastLikeOverride implements MediaComponent { +class CastLikeOverride implements MediaExtension { readonly api = { muted: false, playCount: 0, @@ -55,7 +55,7 @@ describe('HTMLMediaElementHost', () => { audio.muted = false; host.attach(audio); - addMediaComponent(host, new MutedOverride()); + addMediaExtension(host, new MutedOverride()); expect(host.muted).toBe(true); }); @@ -67,7 +67,7 @@ describe('HTMLMediaElementHost', () => { audio.defaultMuted = true; host.attach(audio); - addMediaComponent(host, new MutedOverride()); + addMediaExtension(host, new MutedOverride()); // `defaultMuted` isn't overridden, so it reads from the target. expect(host.defaultMuted).toBe(true); @@ -90,7 +90,7 @@ describe('HTMLMediaElementHost', () => { audio.muted = true; host.attach(audio); - addMediaComponent(host, new VolumeOverride()); + addMediaExtension(host, new VolumeOverride()); expect(host.volume).toBe(0.5); expect(host.muted).toBe(true); @@ -113,7 +113,7 @@ describe('HTMLMediaElementHost', () => { expect(host.contentData).toBeUndefined(); - addMediaComponent(host, new ContentDataOverride()); + addMediaExtension(host, new ContentDataOverride()); expect(host.contentData).toEqual({ title: 'Component title' }); expect(host.title).toBe('Legacy title'); @@ -128,7 +128,7 @@ describe('HTMLMediaElementHost', () => { const component = new CastLikeOverride(); - addMediaComponent(host, component); + addMediaExtension(host, component); host.muted = true; @@ -155,7 +155,7 @@ describe('HTMLMediaElementHost', () => { const component = new AttachTracking(); - addMediaComponent(host, component); + addMediaExtension(host, component); expect(component.attach).toHaveBeenCalledWith(audio); }); @@ -165,7 +165,7 @@ describe('HTMLMediaElementHost', () => { const component = new AttachTracking(); - addMediaComponent(host, component); + addMediaExtension(host, component); expect(component.attach).not.toHaveBeenCalled(); }); @@ -179,8 +179,8 @@ describe('HTMLMediaElementHost', () => { const component = new AttachTracking(); - addMediaComponent(host, component); - addMediaComponent(host, new MutedOverride()); + addMediaExtension(host, component); + addMediaExtension(host, new MutedOverride()); host.destroy(); @@ -198,7 +198,7 @@ describe('HTMLMediaElementHost', () => { const component = new AttachTracking(); - addMediaComponent(host, component); + addMediaExtension(host, component); host.destroy(); @@ -214,7 +214,7 @@ describe('HTMLMediaElementHost', () => { const component = new CastLikeOverride(); - addMediaComponent(host, component); + addMediaExtension(host, component); await host.play(); diff --git a/packages/media/src/dom/utils/index.ts b/packages/media/src/dom/utils/index.ts index d06fbf8d5f..bac7c20615 100644 --- a/packages/media/src/dom/utils/index.ts +++ b/packages/media/src/dom/utils/index.ts @@ -1,4 +1,4 @@ export { serializeEmbedParams } from './embed-params'; -export { addMediaComponent, getMediaComponents, getMediaOwner, getMediaProp, setMediaProp } from './media-components'; +export { addMediaExtension, getMediaExtensions, getMediaOwner, getMediaProp, setMediaProp } from './media-extensions'; export { createTimeRange } from './time-ranges'; diff --git a/packages/media/src/dom/utils/media-components.ts b/packages/media/src/dom/utils/media-extensions.ts similarity index 80% rename from packages/media/src/dom/utils/media-components.ts rename to packages/media/src/dom/utils/media-extensions.ts index 1a6692f53e..c41a430143 100644 --- a/packages/media/src/dom/utils/media-components.ts +++ b/packages/media/src/dom/utils/media-extensions.ts @@ -1,27 +1,27 @@ import type { HTMLMediaElementHost, - MediaComponent, - MediaComponentConstructor, - MediaComponents, + MediaExtension, + MediaExtensionConstructor, + MediaExtensions, HTMLMediaTargetLike as TargetLike, } from '../media-host'; export type MediaHost = HTMLMediaElementHost; -const componentRegistry = new WeakMap(); +const componentRegistry = new WeakMap(); -export function getMediaComponents(host: MediaHost) { +export function getMediaExtensions(host: MediaHost) { let map = componentRegistry.get(host); - if (!map) componentRegistry.set(host, (map = new Map() as MediaComponents)); + if (!map) componentRegistry.set(host, (map = new Map() as MediaExtensions)); return map; } -export function addMediaComponent(host: MediaHost, component: T) { - const components = getMediaComponents(host); +export function addMediaExtension(host: MediaHost, component: T) { + const components = getMediaExtensions(host); // Get the component's constructor to use as the key for the component in the registry. - const ctor = component.constructor as MediaComponentConstructor; + const ctor = component.constructor as MediaExtensionConstructor; const previous = components.get(ctor); @@ -57,7 +57,7 @@ export function setMediaProp(host: Medi * target. */ export function getMediaOwner(host: MediaHost, prop: keyof T): Partial | null { - for (const component of getMediaComponents(host).values()) { + for (const component of getMediaExtensions(host).values()) { const override = component.targetOverride as Partial | null | undefined; if (override?.[prop] !== undefined) return override; } diff --git a/packages/react/src/extensions/google-cast/extension.tsx b/packages/react/src/extensions/google-cast/extension.tsx index e88cabd880..0365fc78a9 100644 --- a/packages/react/src/extensions/google-cast/extension.tsx +++ b/packages/react/src/extensions/google-cast/extension.tsx @@ -3,7 +3,7 @@ import { GoogleCastExtension, type GoogleCastExtensionProps } from '@videojs/google-cast'; import type { ReactNode } from 'react'; -import { useMediaComponent } from '../../utils/use-media-component'; +import { useMediaExtension } from '../../utils/use-media-extension'; import { useSyncProps } from '../../utils/use-sync-props'; export type GoogleCastProps = Partial; @@ -23,7 +23,7 @@ export type GoogleCastProps = Partial; * ```; */ export function GoogleCast(props: GoogleCastProps): ReactNode { - const component = useMediaComponent(GoogleCastExtension); + const component = useMediaExtension(GoogleCastExtension); useSyncProps(component, props, GoogleCastExtension.defaultProps); diff --git a/packages/react/src/extensions/mux-data/extension.tsx b/packages/react/src/extensions/mux-data/extension.tsx index ba116a04cc..158b56ea3d 100644 --- a/packages/react/src/extensions/mux-data/extension.tsx +++ b/packages/react/src/extensions/mux-data/extension.tsx @@ -3,7 +3,7 @@ import { MuxDataExtension, type MuxDataExtensionProps } from '@videojs/mux-data'; import type { ReactNode } from 'react'; -import { useMediaComponent } from '../../utils/use-media-component'; +import { useMediaExtension } from '../../utils/use-media-extension'; import { useSyncProps } from '../../utils/use-sync-props'; export type MuxDataProps = Partial; @@ -29,7 +29,7 @@ export type MuxDataProps = Partial; * ```; */ export function MuxData(props: MuxDataProps): ReactNode { - const component = useMediaComponent(MuxDataExtension); + const component = useMediaExtension(MuxDataExtension); const { MuxDataSdk, ...rest } = props; // `useSyncProps` treats an `undefined` prop as "reset to the default", but diff --git a/packages/react/src/extensions/tests/google-cast.test.tsx b/packages/react/src/extensions/tests/google-cast.test.tsx index 765a2105a3..a29f8936e2 100644 --- a/packages/react/src/extensions/tests/google-cast.test.tsx +++ b/packages/react/src/extensions/tests/google-cast.test.tsx @@ -2,7 +2,7 @@ import { render } from '@testing-library/react'; import { GoogleCastExtension } from '@videojs/google-cast'; import { HlsJsMedia } from '@videojs/hlsjs-video'; import type { Media } from '@videojs/media'; -import { getMediaComponents } from '@videojs/media/dom'; +import { getMediaExtensions } from '@videojs/media/dom'; import { describe, expect, it } from 'vite-plus/test'; import { createPlayerWrapper } from '../../testing/mocks'; @@ -21,7 +21,7 @@ describe('GoogleCast', () => { render(, { wrapper: Wrapper }); - expect(getMediaComponents(media as HlsJsMedia).get(GoogleCastExtension)).toBeInstanceOf(GoogleCastExtension); + expect(getMediaExtensions(media as HlsJsMedia).get(GoogleCastExtension)).toBeInstanceOf(GoogleCastExtension); }); it('syncs props to the component', () => { @@ -31,7 +31,7 @@ describe('GoogleCast', () => { wrapper: Wrapper, }); - const component = getMediaComponents(media as HlsJsMedia).get(GoogleCastExtension)!; + const component = getMediaExtensions(media as HlsJsMedia).get(GoogleCastExtension)!; expect(component.receiver).toBe('APP_ID'); expect(component.contentType).toBe('application/x-mpegURL'); @@ -45,7 +45,7 @@ describe('GoogleCast', () => { rerender(); - expect(getMediaComponents(media as HlsJsMedia).get(GoogleCastExtension)!.receiver).toBeUndefined(); + expect(getMediaExtensions(media as HlsJsMedia).get(GoogleCastExtension)!.receiver).toBeUndefined(); }); it('removes the component on unmount', () => { @@ -55,7 +55,7 @@ describe('GoogleCast', () => { unmount(); - expect(getMediaComponents(media as HlsJsMedia).get(GoogleCastExtension)).toBeUndefined(); + expect(getMediaExtensions(media as HlsJsMedia).get(GoogleCastExtension)).toBeUndefined(); }); it('ignores media that is not a media host', () => { @@ -64,6 +64,6 @@ describe('GoogleCast', () => { render(, { wrapper: Wrapper }); - expect(getMediaComponents(video as any).get(GoogleCastExtension)).toBeUndefined(); + expect(getMediaExtensions(video as any).get(GoogleCastExtension)).toBeUndefined(); }); }); diff --git a/packages/react/src/extensions/tests/mux-data.test.tsx b/packages/react/src/extensions/tests/mux-data.test.tsx index 13d4953400..95d4e824fa 100644 --- a/packages/react/src/extensions/tests/mux-data.test.tsx +++ b/packages/react/src/extensions/tests/mux-data.test.tsx @@ -1,6 +1,6 @@ import { render } from '@testing-library/react'; import type { Media } from '@videojs/media'; -import { addMediaComponent, getMediaComponents } from '@videojs/media/dom'; +import { addMediaExtension, getMediaExtensions } from '@videojs/media/dom'; import { MuxDataExtension } from '@videojs/mux-data'; import { MuxMedia } from '@videojs/mux-video'; import { describe, expect, it, vi } from 'vite-plus/test'; @@ -22,7 +22,7 @@ describe('MuxData', () => { render(, { wrapper: Wrapper }); - expect(getMediaComponents(media).get(MuxDataExtension)).toBeInstanceOf(MuxDataExtension); + expect(getMediaExtensions(media).get(MuxDataExtension)).toBeInstanceOf(MuxDataExtension); }); it('syncs props to the component', () => { @@ -30,7 +30,7 @@ describe('MuxData', () => { render(, { wrapper: Wrapper }); - const component = getMediaComponents(media).get(MuxDataExtension)!; + const component = getMediaExtensions(media).get(MuxDataExtension)!; expect(component.envKey).toBe('test-key'); expect(component.playerSoftwareName).toBe('mux-video'); @@ -45,7 +45,7 @@ describe('MuxData', () => { } as unknown as NonNullable; const { rerender } = render(, { wrapper: Wrapper }); - const component = getMediaComponents(media).get(MuxDataExtension)!; + const component = getMediaExtensions(media).get(MuxDataExtension)!; expect(component.MuxDataSdk).toBe(MuxDataSdk); @@ -63,7 +63,7 @@ describe('MuxData', () => { rerender(); - expect(getMediaComponents(media).get(MuxDataExtension)!.disableCookies).toBe(false); + expect(getMediaExtensions(media).get(MuxDataExtension)!.disableCookies).toBe(false); }); it('keeps the component alive when the media host is destroyed while mounted', () => { @@ -71,19 +71,19 @@ describe('MuxData', () => { const destroy = vi.spyOn(MuxDataExtension.prototype, 'destroy'); render(, { wrapper: Wrapper }); - const component = getMediaComponents(media).get(MuxDataExtension)!; + const component = getMediaExtensions(media).get(MuxDataExtension)!; media.destroy(); // The host detaches and unregisters components it doesn't own; this one is // owned by the still-mounted `MuxData` and follows the next media. expect(destroy).not.toHaveBeenCalled(); - expect(getMediaComponents(media).get(MuxDataExtension)).toBeUndefined(); + expect(getMediaExtensions(media).get(MuxDataExtension)).toBeUndefined(); const next = new MuxMedia(); - addMediaComponent(next, component); - expect(getMediaComponents(next).get(MuxDataExtension)).toBe(component); + addMediaExtension(next, component); + expect(getMediaExtensions(next).get(MuxDataExtension)).toBe(component); destroy.mockRestore(); }); @@ -95,6 +95,6 @@ describe('MuxData', () => { unmount(); - expect(getMediaComponents(media).get(MuxDataExtension)).toBeUndefined(); + expect(getMediaExtensions(media).get(MuxDataExtension)).toBeUndefined(); }); }); diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 64e4874bdc..40b5c895d2 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -175,6 +175,6 @@ export { useAttachMedia } from './utils/use-attach-media'; export { composeRefs, useComposedRefs } from './utils/use-composed-refs'; export { useDestroy } from './utils/use-destroy'; export { useLatestRef } from './utils/use-latest-ref'; -export { useMediaComponent } from './utils/use-media-component'; +export { useMediaExtension } from './utils/use-media-extension'; export { useMediaInstance } from './utils/use-media-instance'; export { renderElement } from './utils/use-render'; diff --git a/packages/react/src/utils/use-media-component.ts b/packages/react/src/utils/use-media-component.ts deleted file mode 100644 index 6e9a60df15..0000000000 --- a/packages/react/src/utils/use-media-component.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - addMediaComponent, - HTMLMediaElementHost, - type HTMLMediaTargetLike, - type MediaComponent, -} from '@videojs/media/dom'; -import { useEffect, useState } from 'react'; - -import { useMedia } from '../player/context'; -import { useDestroy } from './use-destroy'; - -/** - * Create a media component for a custom extension and register it with the media provided by the surrounding player - * context. - * - * Instantiates the component class once, registers it when a media host is available, follows the media when it - * changes, and destroys the component on unmount. Media that is not a media host (e.g. a plain `