feat(site): API reference pages for media elements (#1342)

This commit is contained in:
Darius Cepulis
2026-06-19 09:44:54 -07:00
committed by GitHub
parent 1512729365
commit d799be1063
95 changed files with 3219 additions and 210 deletions
@@ -6,6 +6,15 @@
* AudioEvents extends a subset (no text track events).
*/
// Mirrors the real MediaStreamTypes const object. Exercises default-value
// resolution of property-access expressions (e.g. MediaStreamTypes.UNKNOWN)
// through an import to a `... as const` object literal.
export const MediaStreamTypes = {
ON_DEMAND: 'on-demand',
LIVE: 'live',
UNKNOWN: 'unknown',
} as const;
export interface EventLike<Detail = void> {
readonly type: string;
readonly timeStamp: number;
@@ -62,6 +71,13 @@ export interface TextTrackListEvents {
trackmodechange: EventLike;
}
// Video.js-specific event promoted into the typed contract — mirrors the real
// MediaStreamTypeEvents. The host that fires it also carries an `@fires` tag, so
// it appears in BOTH the native list and the described element-specific list.
export interface MediaStreamTypeEvents {
streamtypechange: EventLike;
}
export interface VideoEvents
extends MediaPlaybackEvents,
MediaPauseEvents,
@@ -71,7 +87,8 @@ export interface VideoEvents
MediaPlaybackRateEvents,
MediaBufferEvents,
MediaErrorEvents,
TextTrackListEvents {}
TextTrackListEvents,
MediaStreamTypeEvents {}
export interface AudioEvents
extends MediaPlaybackEvents,
@@ -0,0 +1,9 @@
/**
* Mock audio host base — mirrors the real audio-host.ts.
*
* Adds no methods of its own: audio elements get only the shared media-host
* methods.
*/
import { HTMLMediaElementHost } from './media-host';
export class HTMLAudioElementHost extends HTMLMediaElementHost {}
@@ -3,10 +3,23 @@
*
* Exercises: multiple getter/setter pairs with JSDoc descriptions,
* readonly properties, boolean type, overlap with native attributes
* (src, preload) that should be deduplicated by the builder.
* (src, preload) that should be deduplicated by the builder, and default
* values declared in a co-located `*DefaultProps` export (mirrors
* hlsMediaDefaultProps) including a const-object member reference.
*/
import { MediaStreamTypes } from '../../../core/media/types';
import { HTMLVideoElementHost } from '../simple';
export const complexMediaDefaultProps = {
src: '',
type: undefined,
preferPlayback: 'mse',
config: {},
debug: false,
preload: 'metadata',
streamType: MediaStreamTypes.UNKNOWN,
};
export class ComplexHost extends HTMLVideoElementHost {
#src: string = '';
#type: string | undefined;
@@ -15,6 +28,7 @@ export class ComplexHost extends HTMLVideoElementHost {
#debug: boolean = false;
#preload: string = 'metadata';
#engine: object | null = null;
#streamType: string = complexMediaDefaultProps.streamType;
get src(): string {
return this.#src;
@@ -71,4 +85,13 @@ export class ComplexHost extends HTMLVideoElementHost {
get engine(): object | null {
return this.#engine;
}
/** Current stream type. */
get streamType(): string {
return this.#streamType;
}
set streamType(value: string) {
this.#streamType = value;
}
}
@@ -3,13 +3,25 @@
*
* Exercises: host inheritance. The builder must walk the extends chain
* to extract properties from both this class and its parent (ComplexHost).
* Child properties override parent properties of the same name.
* Child properties override parent properties of the same name. Defaults
* spread the parent's defaultProps (mirrors muxMediaDefaultProps) — the
* builder must resolve the spread through the import. `customDomain`
* deliberately has no default.
*/
import { ComplexHost } from '../complex';
import { ComplexHost, complexMediaDefaultProps } from '../complex';
export const extendingMediaDefaultProps = {
...complexMediaDefaultProps,
playbackId: '',
tokens: { drm: '' },
maxResolution: 1080,
};
export class ExtendingHost extends ComplexHost {
#playbackId: string = '';
#customDomain: string = '';
#tokens: Record<string, string> = { ...extendingMediaDefaultProps.tokens };
#maxResolution: number = extendingMediaDefaultProps.maxResolution;
/** The playback ID for the video. */
get playbackId(): string {
@@ -29,6 +41,24 @@ export class ExtendingHost extends ComplexHost {
this.#customDomain = value;
}
/** Playback tokens keyed by purpose. */
get tokens(): Record<string, string> {
return this.#tokens;
}
set tokens(value: Record<string, string>) {
this.#tokens = value;
}
/** Maximum rendition height to request. */
get maxResolution(): number {
return this.#maxResolution;
}
set maxResolution(value: number) {
this.#maxResolution = value;
}
/** Overrides parent debug — adds network logging. */
get debug(): boolean {
return super.debug;
@@ -0,0 +1,33 @@
/**
* Mock shared media host base — mirrors the real media-host.ts.
*
* Exercises method extraction: the builder collects public instance methods
* from this class (per media type) for the reference's `methods` field.
* Lifecycle methods (attach/detach/destroy) and accessors are excluded.
*/
export class HTMLMediaElementHost {
// Lifecycle methods — excluded from `methods`.
attach(_target: EventTarget): void {}
detach(): void {}
destroy(): void {}
// Internal — excluded by the `_` prefix.
_forward(): void {}
// Accessor — excluded (getters/setters are properties, not methods).
get src(): string {
return '';
}
play(): Promise<void> {
return Promise.resolve();
}
pause(): void {}
load(): void {}
canPlayType(_type: string): string {
return '';
}
}
@@ -0,0 +1,21 @@
/**
* Mock base host for the mixin chain fixture.
*
* Exercises: parent class providing a JSDoc-described property that the
* mixin chain may override without re-declaring the description (tests
* description fallback through the chain).
*/
import { HTMLVideoElementHost } from '../simple';
export class MixinBaseHost extends HTMLVideoElementHost {
#src: string = '';
/** Source URL of the media. */
get src(): string {
return this.#src;
}
set src(value: string) {
this.#src = value;
}
}
@@ -0,0 +1,24 @@
/**
* Mock mixin-chain leaf class — mirrors MuxVideoMedia / NativeHlsMedia.
*
* Exercises: a class extending MixinB(MixinA(BaseHost)) — a chain of two
* mixins of different syntactic shapes. The builder must walk the
* call-expression extends, follow each mixin to its source file, and
* collect getters/setters from each mixin's inner class.
*/
import { MixinBaseHost } from './base-host';
import { MixinAFooMixin } from './mixin-a';
import { MixinBVolumeMixin } from './mixin-b';
export class MixinHost extends MixinBVolumeMixin(MixinAFooMixin(MixinBaseHost)) {
#bar: number = 0;
/** Leaf class own property. */
get bar(): number {
return this.#bar;
}
set bar(value: number) {
this.#bar = value;
}
}
@@ -0,0 +1,39 @@
/**
* Mock mixin (Shape A — function declaration).
*
* Exercises:
* - Function-declaration mixin walking
* - Property addition with JSDoc
* - A dispatched-but-untagged event (foochange) is NOT documented — only
* `@fires`-tagged events surface in the element-specific list.
* - A `@fires` event that is ALSO part of the native contract (streamtypechange,
* in VideoEvents via MediaStreamTypeEvents) — mirrors HlsMedia. It must surface
* in the described element-specific list even though it is a native event.
* - Defaults declared in the mixin's own file (mirrors muxDataMediaDefaultProps)
*/
type Constructor<T = object> = new (...args: any[]) => T;
export const mixinAFooDefaultProps = {
foo: '',
};
/**
* @fires streamtypechange - Fired when the detected stream type changes.
*/
export function MixinAFooMixin<Base extends Constructor>(BaseClass: Base) {
class MixinAFoo extends BaseClass {
#foo: string = '';
/** Mixin A documentation. */
get foo(): string {
return this.#foo;
}
set foo(value: string) {
this.#foo = value;
(this as unknown as EventTarget).dispatchEvent(new Event('foochange'));
}
}
return MixinAFoo as unknown as Base & Constructor<{ foo: string }>;
}
@@ -0,0 +1,36 @@
/**
* Mock mixin (Shape B — const arrow function).
*
* Exercises:
* - Arrow-function mixin walking
* - Override of a native HTMLMediaElement member (volume) without JSDoc → overridesNative
* - Override of a parent property (src) without JSDoc → description fallback
*/
type Constructor<T = object> = new (...args: any[]) => T;
export const MixinBVolumeMixin = <Base extends Constructor>(superclass: Base) => {
class MixinBVolume extends superclass {
#volume: number = 1;
#src: string = '';
// Overrides HTMLMediaElement.volume without JSDoc — exercises overridesNative.
get volume(): number {
return this.#volume;
}
set volume(value: number) {
this.#volume = value;
}
// Overrides parent.src without JSDoc — exercises description fallback.
get src(): string {
return this.#src;
}
set src(value: string) {
this.#src = value;
}
}
return MixinBVolume as unknown as Base & Constructor<{ volume: number; src: string }>;
};
@@ -3,6 +3,8 @@
*
* Exercises: minimal host with just src (read-write) and engine (readonly).
* No JSDoc on properties — tests that missing descriptions produce undefined.
* `engine` has no return-type annotation — tests that the checker infers the
* type (mirrors DashMedia's unannotated `get engine()`).
*/
// Stub — the builder walks the prototype chain and stops here.
@@ -12,6 +14,13 @@ export class HTMLVideoElementHost {
destroy(): void {}
}
// Stub — audio counterpart, also a prototype-chain stop.
export class HTMLAudioElementHost {
attach(_target: EventTarget): void {}
detach(): void {}
destroy(): void {}
}
export class SimpleHost extends HTMLVideoElementHost {
#src: string = '';
#engine: object = {};
@@ -24,7 +33,7 @@ export class SimpleHost extends HTMLVideoElementHost {
this.#src = value;
}
get engine(): object {
get engine() {
return this.#engine;
}
}
@@ -0,0 +1,11 @@
/**
* Mock audio-only host — mirrors SimpleHlsAudioOnlyMedia.
*
* Exercises: a host whose only mixin lives in a different workspace package
* (spf), reached through that package's barrel file, composed onto the
* audio host base.
*/
import { SpfAudioOnlyMediaMixin } from '../../../../../spf/src/hls';
import { HTMLAudioElementHost } from '../simple';
export class SpfAudioHost extends SpfAudioOnlyMediaMixin(HTMLAudioElementHost) {}
@@ -0,0 +1,13 @@
/**
* Mock video host base — mirrors the real video-host.ts.
*
* Exercises video-specific method extraction: requestFullscreen is added on
* top of the shared media-host methods for video elements only.
*/
import { HTMLMediaElementHost } from './media-host';
export class HTMLVideoElementHost extends HTMLMediaElementHost {
requestFullscreen(): Promise<void> {
return Promise.resolve();
}
}
@@ -0,0 +1,10 @@
/**
* Mock mixin-chain element registration mirrors define/media/mux-video.ts.
*
* Exercises: element whose host is a mixin chain (call-expression extends).
*/
import { MixinVideo } from '../../media/mixin-video';
export class MixinVideoElement extends MixinVideo {
static readonly tagName = 'mixin-video';
}
@@ -0,0 +1,11 @@
/**
* Mock audio-only element registration mirrors define/media/simple-hls-audio-only.ts.
*
* Exercises: discovery of an audio element whose host mixin lives in another
* workspace package.
*/
import { SpfAudio } from '../../media/spf-audio';
export class SpfAudioElement extends SpfAudio {
static readonly tagName = 'spf-audio';
}
@@ -0,0 +1,14 @@
/**
* Mock mixin-chain media element mirrors MuxVideo / NativeHlsVideo.
*
* Exercises: standard composition where the host is a mixin chain.
*/
import { CustomMediaElement } from '../../../../core/src/dom/media/custom-media-element';
import { MixinHost } from '../../../../core/src/dom/media/mixin';
// Stub — the builder parses the AST, it doesn't run the code.
function MediaAttachMixin(base: any) {
return base;
}
export class MixinVideo extends MediaAttachMixin(CustomMediaElement('video', MixinHost)) {}
@@ -0,0 +1,15 @@
/**
* Mock audio-only media element mirrors SimpleHlsAudioOnly.
*
* Exercises: audio media type ('audio' tag argument) with a cross-package
* mixin host.
*/
import { CustomMediaElement } from '../../../../core/src/dom/media/custom-media-element';
import { SpfAudioHost } from '../../../../core/src/dom/media/spf-audio';
// Stub — the builder parses the AST, it doesn't run the code.
function MediaAttachMixin(base: any) {
return base;
}
export class SpfAudio extends MediaAttachMixin(CustomMediaElement('audio', SpfAudioHost)) {}
@@ -0,0 +1,11 @@
/**
* Mock spf hls barrel mirrors the @videojs/spf/hls subpath entry.
*
* The import + bare `export { … }` shape matches what tsdown emits in rolled-up
* entry `.d.ts` files (import the implementation, re-export without a module
* specifier). The builder must follow the import binding to the declaration.
*/
import { SpfAudioOnlyMediaMixin } from '../playback/engines/hls/adapter-audio-only';
export { spfAudioOnlyMediaDefaultProps } from '../playback/engines/hls/adapter-audio-only';
export { SpfAudioOnlyMediaMixin };
@@ -0,0 +1,50 @@
/**
* Mock SPF audio-only adapter mixin mirrors SimpleHlsAudioOnlyMediaMixin.
*
* Exercises:
* - Cross-package mixin resolution (host lives in core, mixin in spf)
* - Defaults declared in the mixin's own file (spfAudioOnlyMediaDefaultProps)
* - `@fires`-declared events: `audiomodechange` also has a dispatch site,
* `manifestparsed` is dispatched from a helper the builder never scans
* the @fires tag is its only source.
*/
type Constructor<T = object> = new (...args: any[]) => T;
export const spfAudioOnlyMediaDefaultProps = {
src: '',
preload: '',
};
/**
* Adds SPF audio-only HLS playback to a host.
*
* @fires audiomodechange - Fired when the audio-only rendition changes.
* @fires manifestparsed - Fired after the multivariant playlist is parsed.
*/
export const SpfAudioOnlyMediaMixin = <Base extends Constructor>(BaseClass: Base) => {
class SpfAudioOnlyMedia extends BaseClass {
#src: string = spfAudioOnlyMediaDefaultProps.src;
#preload: string = spfAudioOnlyMediaDefaultProps.preload;
/** Source URL of the HLS manifest. */
get src(): string {
return this.#src;
}
set src(value: string) {
this.#src = value;
(this as unknown as EventTarget).dispatchEvent(new Event('audiomodechange'));
}
/** Preload hint forwarded to the internal audio element. */
get preload(): string {
return this.#preload;
}
set preload(value: string) {
this.#preload = value;
}
}
return SpfAudioOnlyMedia as unknown as Base & Constructor<{ src: string; preload: string }>;
};