mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(site): API reference pages for media elements (#1342)
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { generateComponentReferences, generateFeatureReferences, generatePresetReferences } from './pipeline.js';
|
||||
import { MediaReferenceSchema } from '../../../src/types/media-reference.js';
|
||||
import {
|
||||
generateComponentReferences,
|
||||
generateFeatureReferences,
|
||||
generateMediaElementReferences,
|
||||
generatePresetReferences,
|
||||
} from './pipeline.js';
|
||||
import { ComponentReferenceSchema, FeatureReferenceSchema, PresetReferenceSchema } from './types.js';
|
||||
import { generateUtilReferences } from './util-handler.js';
|
||||
|
||||
@@ -19,6 +25,7 @@ const MONOREPO_ROOT = path.resolve(import.meta.dirname, '../../../../');
|
||||
const COMPONENT_OUTPUT_PATH = path.join(MONOREPO_ROOT, 'site/src/content/generated-component-reference');
|
||||
const UTIL_OUTPUT_PATH = path.join(MONOREPO_ROOT, 'site/src/content/generated-util-reference');
|
||||
const FEATURE_OUTPUT_PATH = path.join(MONOREPO_ROOT, 'site/src/content/generated-feature-reference');
|
||||
const MEDIA_OUTPUT_PATH = path.join(MONOREPO_ROOT, 'site/src/content/generated-media-reference');
|
||||
const PRESET_OUTPUT_PATH = path.join(MONOREPO_ROOT, 'site/src/content/generated-preset-reference');
|
||||
|
||||
/**
|
||||
@@ -36,7 +43,13 @@ function main() {
|
||||
};
|
||||
|
||||
// Ensure output directories exist
|
||||
for (const dir of [COMPONENT_OUTPUT_PATH, UTIL_OUTPUT_PATH, FEATURE_OUTPUT_PATH, PRESET_OUTPUT_PATH]) {
|
||||
for (const dir of [
|
||||
COMPONENT_OUTPUT_PATH,
|
||||
UTIL_OUTPUT_PATH,
|
||||
FEATURE_OUTPUT_PATH,
|
||||
MEDIA_OUTPUT_PATH,
|
||||
PRESET_OUTPUT_PATH,
|
||||
]) {
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
@@ -116,6 +129,38 @@ function main() {
|
||||
|
||||
log.info(`Done! Generated ${featureSuccessCount} feature files.`);
|
||||
|
||||
// Generate media element references
|
||||
const mediaResults = generateMediaElementReferences(MONOREPO_ROOT);
|
||||
|
||||
if (mediaResults.length === 0) {
|
||||
log.info('No media elements found.');
|
||||
} else {
|
||||
log.info(`Found ${mediaResults.length} media elements. Processing...`);
|
||||
}
|
||||
|
||||
let mediaSuccessCount = 0;
|
||||
for (const result of mediaResults) {
|
||||
const validated = MediaReferenceSchema.safeParse(result.reference);
|
||||
if (!validated.success) {
|
||||
log.error(`Schema validation failed for media element ${result.name}:`);
|
||||
for (const issue of validated.error.issues) {
|
||||
log.error(` - ${issue.path.join('.')}: ${issue.message}`);
|
||||
}
|
||||
errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const outputFile = path.join(MEDIA_OUTPUT_PATH, `${validated.data.tagName}.json`);
|
||||
const json = `${JSON.stringify(validated.data, null, 2)}\n`;
|
||||
fs.writeFileSync(outputFile, json);
|
||||
|
||||
log.success(`✅ Generated ${path.basename(outputFile)}`);
|
||||
mediaSuccessCount++;
|
||||
successCount++;
|
||||
}
|
||||
|
||||
log.info(`Done! Generated ${mediaSuccessCount} media element files.`);
|
||||
|
||||
// Generate preset references
|
||||
const presetResults = generatePresetReferences(MONOREPO_ROOT);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -582,16 +582,29 @@ export interface HostPropertyDef {
|
||||
type: string;
|
||||
description?: string;
|
||||
readonly: boolean;
|
||||
overridesNative?: boolean;
|
||||
/** Serialized default value from the host's `*DefaultProps` export. */
|
||||
default?: string;
|
||||
}
|
||||
|
||||
export interface MediaEventDef {
|
||||
name: string;
|
||||
/** Description from a `@fires` JSDoc tag on the dispatching class or mixin. */
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface MediaElementReference {
|
||||
name: string;
|
||||
tagName: string;
|
||||
mediaType: 'video' | 'audio';
|
||||
hostProperties: Record<string, HostPropertyDef>;
|
||||
nativeAttributes: string[];
|
||||
events: string[];
|
||||
events: {
|
||||
native: string[];
|
||||
elementSpecific: MediaEventDef[];
|
||||
};
|
||||
methods: string[];
|
||||
cssCustomProperties: Record<string, { description: string }>;
|
||||
slots: string[];
|
||||
}
|
||||
|
||||
export interface MediaElementResult {
|
||||
|
||||
@@ -64,11 +64,12 @@
|
||||
* simple-video — Simple media element. Exercises: discovery via static
|
||||
* tagName in define/media/*.ts, minimal host (src rw,
|
||||
* engine readonly), shared attributes/events/CSS vars
|
||||
* from custom-media-element, slots parsed from template HTML.
|
||||
* from custom-media-element.
|
||||
* complex-video — Complex media element. Exercises: host with JSDoc
|
||||
* descriptions, multiple property types (string, boolean,
|
||||
* Record), host-vs-native attribute deduplication
|
||||
* (src, preload in host → omitted from nativeAttributes).
|
||||
* Record), and the intentional content-attribute vs
|
||||
* IDL-property overlap (src, preload appear in BOTH
|
||||
* hostProperties and nativeAttributes — no dedup).
|
||||
* extending-video — Extending media element. Exercises: host inheritance
|
||||
* (ExtendingHost extends ComplexHost). Builder must
|
||||
* walk the extends chain to include inherited properties.
|
||||
@@ -1069,7 +1070,6 @@ describe('Preset pipeline (end-to-end)', () => {
|
||||
// - Host properties by following the CustomMediaElement(tag, Host) call to the
|
||||
// host class and walking its getter/setter pairs
|
||||
// - Shared native attributes from static properties, events, and CSS vars
|
||||
// - Slots parsed from the template HTML (getVideoTemplateHTML / getCommonTemplateHTML)
|
||||
// - JSDoc descriptions from host getter/setter pairs
|
||||
//
|
||||
// Key behaviors:
|
||||
@@ -1077,8 +1077,13 @@ describe('Preset pipeline (end-to-end)', () => {
|
||||
// - Exclusion: container.ts (re-exports, no inline class), background-video.ts
|
||||
// (no CustomMediaElement — uses MediaAttachMixin(HTMLElement) directly)
|
||||
// - Host inheritance: child host extends parent, builder walks the chain
|
||||
// - Deduplication: properties in the host that overlap with native attributes
|
||||
// (e.g., src, preload) appear in hostProperties and are omitted from nativeAttributes
|
||||
// - Attribute overlap: nativeAttributes is the COMPLETE markup-settable set
|
||||
// (no dedup). Host-owned names (e.g., src, preload) appear in BOTH
|
||||
// hostProperties and nativeAttributes (content-attribute vs IDL-property).
|
||||
// - Methods: native media methods are extracted ONCE per media type from the
|
||||
// shared base host classes (media-host + video-host/audio-host).
|
||||
// - Event buckets: element-specific (@fires-tagged) events live ONLY in
|
||||
// elementSpecific, never in native.
|
||||
|
||||
describe('Media element pipeline (end-to-end)', () => {
|
||||
const results = generateMediaElementReferences(FIXTURE_ROOT);
|
||||
@@ -1094,7 +1099,7 @@ describe('Media element pipeline (end-to-end)', () => {
|
||||
describe('Discovery', () => {
|
||||
it('discovers media elements from define/media/ files', () => {
|
||||
const names = results.map((r) => r.name).sort();
|
||||
expect(names).toEqual(['ComplexVideo', 'ExtendingVideo', 'SimpleVideo']);
|
||||
expect(names).toEqual(['ComplexVideo', 'ExtendingVideo', 'MixinVideo', 'SimpleVideo', 'SpfAudio']);
|
||||
});
|
||||
|
||||
it('excludes container (re-export, not inline class declaration)', () => {
|
||||
@@ -1108,7 +1113,7 @@ describe('Media element pipeline (end-to-end)', () => {
|
||||
});
|
||||
|
||||
it('produces one result per media element', () => {
|
||||
expect(results.length).toBe(3);
|
||||
expect(results.length).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1125,6 +1130,7 @@ describe('Media element pipeline (end-to-end)', () => {
|
||||
it('extracts the tag name', () => {
|
||||
const ref = findElement('SimpleVideo')!.reference;
|
||||
expect(ref.tagName).toBe('simple-video');
|
||||
expect(ref.mediaType).toBe('video');
|
||||
});
|
||||
|
||||
it('extracts host properties with types and readonly flags', () => {
|
||||
@@ -1137,7 +1143,8 @@ describe('Media element pipeline (end-to-end)', () => {
|
||||
});
|
||||
expect(props.src.description).toBeUndefined();
|
||||
|
||||
// engine: readonly object
|
||||
// engine: readonly, unannotated getter — type inferred by the checker
|
||||
// (would be 'unknown' if the builder only read syntactic annotations).
|
||||
expect(props.engine).toMatchObject({
|
||||
type: 'object',
|
||||
readonly: true,
|
||||
@@ -1151,9 +1158,12 @@ describe('Media element pipeline (end-to-end)', () => {
|
||||
expect(props.destroy).toBeUndefined();
|
||||
});
|
||||
|
||||
it('includes native attributes from static properties', () => {
|
||||
it('includes the COMPLETE set of native attributes from static properties', () => {
|
||||
const ref = findElement('SimpleVideo')!.reference;
|
||||
// src is in the host, so it should be omitted from nativeAttributes
|
||||
// nativeAttributes is the full markup-settable set from `static
|
||||
// properties` — no dedup against host props. `src` is settable as an
|
||||
// attribute even though the host also exposes it as a richer property,
|
||||
// so it appears in BOTH places (MDN content-attribute vs IDL-property).
|
||||
expect(ref.nativeAttributes).toEqual(
|
||||
expect.arrayContaining([
|
||||
'autoplay',
|
||||
@@ -1166,14 +1176,24 @@ describe('Media element pipeline (end-to-end)', () => {
|
||||
'preload',
|
||||
])
|
||||
);
|
||||
expect(ref.nativeAttributes).not.toContain('src');
|
||||
expect(ref.nativeAttributes).toContain('src');
|
||||
});
|
||||
|
||||
it('extracts native media methods from the shared base host classes', () => {
|
||||
const ref = findElement('SimpleVideo')!.reference;
|
||||
// Video methods = media-host methods + video-host methods, deduped + sorted.
|
||||
// Lifecycle methods (attach/detach/destroy) and accessors are excluded.
|
||||
expect(ref.methods).toEqual(['canPlayType', 'load', 'pause', 'play', 'requestFullscreen']);
|
||||
});
|
||||
|
||||
it('includes events derived from VideoEvents capability contracts', () => {
|
||||
const ref = findElement('SimpleVideo')!.reference;
|
||||
// Events are extracted from VideoEvents in types.ts, which extends
|
||||
// all capability event interfaces including TextTrackListEvents
|
||||
expect(ref.events).toEqual([
|
||||
// Events are extracted from VideoEvents in types.ts, which extends all
|
||||
// capability event interfaces including TextTrackListEvents. Custom
|
||||
// Video.js events from MediaStreamTypeEvents/MediaLiveEvents
|
||||
// (streamtypechange) are NOT native and are excluded here — they only
|
||||
// appear in elementSpecific, and only on elements that @fires them.
|
||||
expect(ref.events.native).toEqual([
|
||||
'play',
|
||||
'playing',
|
||||
'waiting',
|
||||
@@ -1198,6 +1218,20 @@ describe('Media element pipeline (end-to-end)', () => {
|
||||
'changetrack',
|
||||
'trackmodechange',
|
||||
]);
|
||||
// SimpleHost dispatches no events of its own.
|
||||
expect(ref.events.elementSpecific).toEqual([]);
|
||||
});
|
||||
|
||||
it('omits custom events entirely when the element does not @fires them', () => {
|
||||
// Regression guard: streamtypechange lives in the VideoEvents contract via
|
||||
// MediaStreamTypeEvents, but SimpleVideo has no @fires tag for it (and no
|
||||
// streamType capability). A custom event must never leak into `native`
|
||||
// (which points readers at MDN) — with no @fires it appears in NEITHER
|
||||
// bucket. Mirrors dash-video / simple-hls-video in the real monorepo.
|
||||
const ref = findElement('SimpleVideo')!.reference;
|
||||
expect(ref.events.native).not.toContain('streamtypechange');
|
||||
const elementSpecificNames = ref.events.elementSpecific.map((e) => e.name);
|
||||
expect(elementSpecificNames).not.toContain('streamtypechange');
|
||||
});
|
||||
|
||||
it('includes CSS custom properties from VideoCSSVars', () => {
|
||||
@@ -1209,11 +1243,6 @@ describe('Media element pipeline (end-to-end)', () => {
|
||||
description: 'Border radius of the video element.',
|
||||
});
|
||||
});
|
||||
|
||||
it('includes slots parsed from the video template HTML', () => {
|
||||
const ref = findElement('SimpleVideo')!.reference;
|
||||
expect(ref.slots).toEqual(expect.arrayContaining(['media', '']));
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
@@ -1234,7 +1263,16 @@ describe('Media element pipeline (end-to-end)', () => {
|
||||
it('extracts all host properties', () => {
|
||||
const props = findElement('ComplexVideo')!.reference.hostProperties;
|
||||
const propNames = Object.keys(props).sort();
|
||||
expect(propNames).toEqual(['config', 'debug', 'engine', 'preferPlayback', 'preload', 'src', 'type']);
|
||||
expect(propNames).toEqual([
|
||||
'config',
|
||||
'debug',
|
||||
'engine',
|
||||
'preferPlayback',
|
||||
'preload',
|
||||
'src',
|
||||
'streamType',
|
||||
'type',
|
||||
]);
|
||||
});
|
||||
|
||||
it('extracts JSDoc descriptions from host getters', () => {
|
||||
@@ -1261,19 +1299,46 @@ describe('Media element pipeline (end-to-end)', () => {
|
||||
expect(props.config.type).toContain('Record');
|
||||
});
|
||||
|
||||
it('deduplicates host props from nativeAttributes', () => {
|
||||
it('keeps host-owned attributes in BOTH hostProperties and nativeAttributes', () => {
|
||||
const ref = findElement('ComplexVideo')!.reference;
|
||||
// src and preload are in both the host AND native attributes.
|
||||
// They should appear in hostProperties...
|
||||
// src and preload are richer host properties AND genuinely settable as
|
||||
// markup attributes — the intentional content-attribute vs IDL-property
|
||||
// overlap. They appear in hostProperties...
|
||||
expect(ref.hostProperties.src).toBeDefined();
|
||||
expect(ref.hostProperties.preload).toBeDefined();
|
||||
// ...and be omitted from nativeAttributes
|
||||
expect(ref.nativeAttributes).not.toContain('src');
|
||||
expect(ref.nativeAttributes).not.toContain('preload');
|
||||
// ...and ALSO in nativeAttributes (no dedup).
|
||||
expect(ref.nativeAttributes).toContain('src');
|
||||
expect(ref.nativeAttributes).toContain('preload');
|
||||
// Other native attrs remain
|
||||
expect(ref.nativeAttributes).toContain('autoplay');
|
||||
expect(ref.nativeAttributes).toContain('controls');
|
||||
});
|
||||
|
||||
it('extracts defaults from the co-located defaultProps export', () => {
|
||||
const props = findElement('ComplexVideo')!.reference.hostProperties;
|
||||
// Literal values are emitted as source text (strings keep their quotes).
|
||||
expect(props.src.default).toBe("''");
|
||||
expect(props.debug.default).toBe('false');
|
||||
expect(props.preload.default).toBe("'metadata'");
|
||||
expect(props.preferPlayback.default).toBe("'mse'");
|
||||
// `undefined` defaults are omitted — they convey nothing beyond the
|
||||
// table's "—" placeholder.
|
||||
expect(props.type.default).toBeUndefined();
|
||||
// Empty object literals stay literal.
|
||||
expect(props.config.default).toBe('{}');
|
||||
});
|
||||
|
||||
it('resolves const-object member defaults through imports', () => {
|
||||
// streamType: MediaStreamTypes.UNKNOWN — the builder resolves the member
|
||||
// access to its literal value in the imported `as const` object.
|
||||
const props = findElement('ComplexVideo')!.reference.hostProperties;
|
||||
expect(props.streamType.default).toBe("'unknown'");
|
||||
});
|
||||
|
||||
it('omits defaults for properties without a defaultProps entry', () => {
|
||||
const props = findElement('ComplexVideo')!.reference.hostProperties;
|
||||
expect(props.engine.default).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
@@ -1327,6 +1392,31 @@ describe('Media element pipeline (end-to-end)', () => {
|
||||
// engine is readonly in ComplexHost and not overridden
|
||||
expect(props.engine.readonly).toBe(true);
|
||||
});
|
||||
|
||||
it('resolves spread defaults through the parent defaultProps import', () => {
|
||||
// extendingMediaDefaultProps = { ...complexMediaDefaultProps, ... } —
|
||||
// the builder must follow the spread to the imported object literal.
|
||||
const props = findElement('ExtendingVideo')!.reference.hostProperties;
|
||||
expect(props.src.default).toBe("''");
|
||||
expect(props.debug.default).toBe('false');
|
||||
expect(props.streamType.default).toBe("'unknown'");
|
||||
});
|
||||
|
||||
it('extracts own defaults alongside spread defaults', () => {
|
||||
const props = findElement('ExtendingVideo')!.reference.hostProperties;
|
||||
expect(props.playbackId.default).toBe("''");
|
||||
expect(props.maxResolution.default).toBe('1080');
|
||||
});
|
||||
|
||||
it('abbreviates non-empty object defaults', () => {
|
||||
const props = findElement('ExtendingVideo')!.reference.hostProperties;
|
||||
expect(props.tokens.default).toBe('{…}');
|
||||
});
|
||||
|
||||
it('omits defaults for properties without an entry', () => {
|
||||
const props = findElement('ExtendingVideo')!.reference.hostProperties;
|
||||
expect(props.customDomain.default).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
@@ -1340,18 +1430,205 @@ describe('Media element pipeline (end-to-end)', () => {
|
||||
describe('Event extraction from capability contracts', () => {
|
||||
it('video elements include text track events from VideoEvents', () => {
|
||||
const ref = findElement('SimpleVideo')!.reference;
|
||||
expect(ref.events).toContain('addtrack');
|
||||
expect(ref.events).toContain('removetrack');
|
||||
expect(ref.events).toContain('changetrack');
|
||||
expect(ref.events).toContain('trackmodechange');
|
||||
expect(ref.events.native).toContain('addtrack');
|
||||
expect(ref.events.native).toContain('removetrack');
|
||||
expect(ref.events.native).toContain('changetrack');
|
||||
expect(ref.events.native).toContain('trackmodechange');
|
||||
});
|
||||
|
||||
it('all video elements share the same event list', () => {
|
||||
const simple = findElement('SimpleVideo')!.reference.events;
|
||||
const complex = findElement('ComplexVideo')!.reference.events;
|
||||
const extending = findElement('ExtendingVideo')!.reference.events;
|
||||
it('all video elements share the same native event list', () => {
|
||||
const simple = findElement('SimpleVideo')!.reference.events.native;
|
||||
const complex = findElement('ComplexVideo')!.reference.events.native;
|
||||
const extending = findElement('ExtendingVideo')!.reference.events.native;
|
||||
expect(complex).toEqual(simple);
|
||||
expect(extending).toEqual(simple);
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// MIXIN MEDIA ELEMENT: MixinVideo
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// A media element whose host extends a chain of mixins
|
||||
// (`MixinBVolumeMixin(MixinAFooMixin(MixinBaseHost))` — mirrors
|
||||
// `MuxDataMediaMixin(GoogleCastMixin(HlsMedia))`). 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.
|
||||
//
|
||||
// Also exercises:
|
||||
// - overridesNative tagging for properties whose name matches an
|
||||
// HTMLMediaElement member (volume)
|
||||
// - Description fallback through the chain (src has JSDoc on the base,
|
||||
// overridden without JSDoc by MixinB)
|
||||
// - Element-specific event extraction via this.dispatchEvent(new Event(...))
|
||||
// in mixin code (foochange dispatched by MixinAFooMixin)
|
||||
|
||||
describe('MixinVideo (mixin chain)', () => {
|
||||
it('extracts the tag name', () => {
|
||||
const ref = findElement('MixinVideo')!.reference;
|
||||
expect(ref.tagName).toBe('mixin-video');
|
||||
});
|
||||
|
||||
it('walks function-declaration mixin (Shape A)', () => {
|
||||
const props = findElement('MixinVideo')!.reference.hostProperties;
|
||||
expect(props.foo).toMatchObject({
|
||||
type: 'string',
|
||||
readonly: false,
|
||||
description: 'Mixin A documentation.',
|
||||
});
|
||||
});
|
||||
|
||||
it('walks const-arrow mixin (Shape B)', () => {
|
||||
const props = findElement('MixinVideo')!.reference.hostProperties;
|
||||
expect(props.volume).toBeDefined();
|
||||
expect(props.volume.type).toBe('number');
|
||||
expect(props.volume.readonly).toBe(false);
|
||||
});
|
||||
|
||||
it('includes leaf-class own properties', () => {
|
||||
const props = findElement('MixinVideo')!.reference.hostProperties;
|
||||
expect(props.bar).toMatchObject({
|
||||
type: 'number',
|
||||
readonly: false,
|
||||
description: 'Leaf class own property.',
|
||||
});
|
||||
});
|
||||
|
||||
it('marks volume as overridesNative (HTMLMediaElement member)', () => {
|
||||
const props = findElement('MixinVideo')!.reference.hostProperties;
|
||||
expect(props.volume.overridesNative).toBe(true);
|
||||
});
|
||||
|
||||
it('does not mark non-native properties as overridesNative', () => {
|
||||
const props = findElement('MixinVideo')!.reference.hostProperties;
|
||||
expect(props.foo.overridesNative).toBeUndefined();
|
||||
expect(props.bar.overridesNative).toBeUndefined();
|
||||
});
|
||||
|
||||
it('inherits parent description when child override has no JSDoc', () => {
|
||||
// src has JSDoc on MixinBaseHost; MixinB overrides without JSDoc.
|
||||
// The description should fall through from the base.
|
||||
const props = findElement('MixinVideo')!.reference.hostProperties;
|
||||
expect(props.src.description).toBe('Source URL of the media.');
|
||||
});
|
||||
|
||||
it('documents a @fires event ONLY in element-specific, never in native', () => {
|
||||
// streamtypechange is in VideoEvents (via MediaStreamTypeEvents) AND carries
|
||||
// a @fires tag on the mixin — mirrors HlsMedia. Element-specific events live
|
||||
// ONLY in the elementSpecific bucket (where they carry their description);
|
||||
// they are excluded from native so they are never listed twice.
|
||||
const ref = findElement('MixinVideo')!.reference;
|
||||
expect(ref.events.native).not.toContain('streamtypechange');
|
||||
expect(ref.events.elementSpecific).toContainEqual({
|
||||
name: 'streamtypechange',
|
||||
description: 'Fired when the detected stream type changes.',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not document a dispatched-but-untagged event', () => {
|
||||
// foochange is dispatched via this.dispatchEvent(new Event('foochange')) but
|
||||
// has no @fires tag, so it is not surfaced — documentation requires a tag.
|
||||
const ref = findElement('MixinVideo')!.reference;
|
||||
const elementSpecificNames = ref.events.elementSpecific.map((e) => e.name);
|
||||
expect(elementSpecificNames).not.toContain('foochange');
|
||||
});
|
||||
|
||||
it('separates native events from element-specific events', () => {
|
||||
const ref = findElement('MixinVideo')!.reference;
|
||||
const elementSpecificNames = ref.events.elementSpecific.map((e) => e.name);
|
||||
expect(ref.events.native).toContain('play');
|
||||
expect(ref.events.native).not.toContain('foochange');
|
||||
expect(elementSpecificNames).not.toContain('play');
|
||||
});
|
||||
|
||||
it('extracts defaults declared in a mixin file', () => {
|
||||
const props = findElement('MixinVideo')!.reference.hostProperties;
|
||||
expect(props.foo.default).toBe("''");
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// CROSS-PACKAGE MIXIN AUDIO ELEMENT: SpfAudio
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// An audio element whose host's only mixin lives in a different workspace
|
||||
// package (spf), reached through that package's barrel file — mirrors
|
||||
// SimpleHlsAudioOnlyMedia extending SimpleHlsAudioOnlyMediaMixin from
|
||||
// @videojs/spf/hls.
|
||||
//
|
||||
// Also exercises:
|
||||
// - @fires-declared event descriptions for events outside the native
|
||||
// contract (audiomodechange also has a dispatch site, manifestparsed does
|
||||
// not — the @fires tag alone surfaces both)
|
||||
// - Defaults co-located with the mixin (spfAudioOnlyMediaDefaultProps)
|
||||
// - AudioEvents capability contract
|
||||
|
||||
describe('SpfAudio (cross-package mixin, audio host)', () => {
|
||||
it('extracts the tag name and audio media type', () => {
|
||||
const ref = findElement('SpfAudio')!.reference;
|
||||
expect(ref.tagName).toBe('spf-audio');
|
||||
expect(ref.mediaType).toBe('audio');
|
||||
});
|
||||
|
||||
it('resolves the mixin through another package barrel', () => {
|
||||
const props = findElement('SpfAudio')!.reference.hostProperties;
|
||||
expect(props.src).toMatchObject({
|
||||
type: 'string',
|
||||
readonly: false,
|
||||
description: 'Source URL of the HLS manifest.',
|
||||
});
|
||||
expect(props.preload).toMatchObject({
|
||||
type: 'string',
|
||||
readonly: false,
|
||||
description: 'Preload hint forwarded to the internal audio element.',
|
||||
});
|
||||
});
|
||||
|
||||
it('extracts defaults declared next to the cross-package mixin', () => {
|
||||
const props = findElement('SpfAudio')!.reference.hostProperties;
|
||||
expect(props.src.default).toBe("''");
|
||||
expect(props.preload.default).toBe("''");
|
||||
});
|
||||
|
||||
it('uses AudioEvents for native events (no text track events)', () => {
|
||||
const ref = findElement('SpfAudio')!.reference;
|
||||
expect(ref.events.native).toContain('play');
|
||||
expect(ref.events.native).not.toContain('addtrack');
|
||||
});
|
||||
|
||||
it('surfaces a @fires event with its tag description', () => {
|
||||
const ref = findElement('SpfAudio')!.reference;
|
||||
expect(ref.events.elementSpecific).toContainEqual({
|
||||
name: 'audiomodechange',
|
||||
description: 'Fired when the audio-only rendition changes.',
|
||||
});
|
||||
});
|
||||
|
||||
it('includes @fires-declared events without a scanned dispatch site', () => {
|
||||
const ref = findElement('SpfAudio')!.reference;
|
||||
expect(ref.events.elementSpecific).toContainEqual({
|
||||
name: 'manifestparsed',
|
||||
description: 'Fired after the multivariant playlist is parsed.',
|
||||
});
|
||||
});
|
||||
|
||||
it('sorts element-specific events by name', () => {
|
||||
const ref = findElement('SpfAudio')!.reference;
|
||||
const names = ref.events.elementSpecific.map((e) => e.name);
|
||||
expect(names).toEqual([...names].sort());
|
||||
});
|
||||
|
||||
it('has empty AudioCSSVars', () => {
|
||||
const ref = findElement('SpfAudio')!.reference;
|
||||
expect(ref.cssCustomProperties).toEqual({});
|
||||
});
|
||||
|
||||
it('extracts audio methods from the shared base host (no video-only methods)', () => {
|
||||
const ref = findElement('SpfAudio')!.reference;
|
||||
// Audio methods = media-host methods + audio-host methods. The fixture
|
||||
// audio host adds none, so video-only methods (requestFullscreen) are absent.
|
||||
expect(ref.methods).toEqual(['canPlayType', 'load', 'pause', 'play']);
|
||||
expect(ref.methods).not.toContain('requestFullscreen');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+18
-1
@@ -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,
|
||||
|
||||
Vendored
+9
@@ -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 {}
|
||||
+24
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+32
-2
@@ -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;
|
||||
|
||||
Vendored
+33
@@ -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 '';
|
||||
}
|
||||
}
|
||||
+21
@@ -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;
|
||||
}
|
||||
}
|
||||
site/scripts/api-docs-builder/src/tests/fixtures/monorepo/packages/core/src/dom/media/mixin/index.ts
Vendored
+24
@@ -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;
|
||||
}
|
||||
}
|
||||
+39
@@ -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 }>;
|
||||
}
|
||||
+36
@@ -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 }>;
|
||||
};
|
||||
+10
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -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) {}
|
||||
Vendored
+13
@@ -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();
|
||||
}
|
||||
}
|
||||
+10
@@ -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';
|
||||
}
|
||||
+11
@@ -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';
|
||||
}
|
||||
+14
@@ -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)) {}
|
||||
site/scripts/api-docs-builder/src/tests/fixtures/monorepo/packages/html/src/media/spf-audio/index.ts
Vendored
+15
@@ -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)) {}
|
||||
+11
@@ -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 };
|
||||
+50
@@ -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 }>;
|
||||
};
|
||||
Reference in New Issue
Block a user