From 870cbb77e4ac45d179d8702d0e08c58face8a2fc Mon Sep 17 00:00:00 2001 From: rahim Date: Mon, 9 Feb 2026 20:49:42 +1100 Subject: [PATCH] feat(html): reorganize import paths by use case (#480) --- .claude/plans/player-api-implementation.md | 4 +- .../skills/docs/templates/component-page.md | 4 +- packages/core/src/dom/media/types.ts | 5 ++ .../src/dom/store/features/feature.parts.ts | 5 +- packages/html/package.json | 23 +++++--- packages/html/src/define/audio/player.ts | 18 ++++++ packages/html/src/define/audio/skin.ts | 1 + packages/html/src/define/background/player.ts | 18 ++++++ packages/html/src/define/background/skin.ts | 1 + packages/html/src/define/player/video.ts | 17 ------ packages/html/src/define/video/player.ts | 18 ++++++ .../define/{skin/video.ts => video/skin.ts} | 0 .../src/player/tests/create-player.test-d.ts | 6 ++ .../src/player/tests/create-player.test.ts | 24 ++++++++ rfc/player-api/examples.md | 42 +++++++------- rfc/player-api/html.md | 55 ++++++++++--------- rfc/player-api/index.md | 10 ++-- .../installation/HTMLUsageCodeBlock.tsx | 11 +++- 18 files changed, 178 insertions(+), 84 deletions(-) create mode 100644 packages/html/src/define/audio/player.ts create mode 100644 packages/html/src/define/audio/skin.ts create mode 100644 packages/html/src/define/background/player.ts create mode 100644 packages/html/src/define/background/skin.ts delete mode 100644 packages/html/src/define/player/video.ts create mode 100644 packages/html/src/define/video/player.ts rename packages/html/src/define/{skin/video.ts => video/skin.ts} (100%) diff --git a/.claude/plans/player-api-implementation.md b/.claude/plans/player-api-implementation.md index 0ec379f4..d24e5129 100644 --- a/.claude/plans/player-api-implementation.md +++ b/.claude/plans/player-api-implementation.md @@ -1384,12 +1384,12 @@ Items identified during planning but deferred from initial implementation. ### Side-Effect Registration System -The `/ui/*`, `/skin/*`, `/feature/*` exports provide scaffold for future registration patterns: +The `/ui/*`, `/video/*`, `/audio/*`, `/background/*`, `/feature/*` exports provide scaffold for future registration patterns: ```ts // User imports trigger registration import '@videojs/html/ui/play-button'; -import '@videojs/html/skin/modern'; +import '@videojs/html/video/skin'; import '@videojs/html/feature/quality-selection'; ``` diff --git a/.claude/skills/docs/templates/component-page.md b/.claude/skills/docs/templates/component-page.md index e8eec79f..087d2442 100644 --- a/.claude/skills/docs/templates/component-page.md +++ b/.claude/skills/docs/templates/component-page.md @@ -327,7 +327,7 @@ ElementName.define('custom-name'); // With custom mixin import { createStore } from '@videojs/store/lit'; -import { extendConfig } from '@videojs/html/skins/frosted'; +import { extendConfig } from '@videojs/html/video/skin'; const { StoreMixin } = createStore( extendConfig({ features: [customFeature] }) @@ -384,7 +384,7 @@ ElementName.define('custom-name', StoreMixin); ### Extending import { createStore } from '@videojs/store/lit'; -import { extendConfig, FrostedSkinElement } from '@videojs/html/skins/frosted'; +import { extendConfig, FrostedSkinElement } from '@videojs/html/video/skin'; import { chaptersFeature } from './features/chapters'; const { StoreMixin } = createStore( diff --git a/packages/core/src/dom/media/types.ts b/packages/core/src/dom/media/types.ts index c577542e..097804b9 100644 --- a/packages/core/src/dom/media/types.ts +++ b/packages/core/src/dom/media/types.ts @@ -51,6 +51,11 @@ export type AudioFeatures = [ PlayerFeature, ]; +// TODO: Define background video features (e.g., playback, source, buffer) +export type BackgroundFeatures = []; + export type VideoPlayerStore = PlayerStore; export type AudioPlayerStore = PlayerStore; + +export type BackgroundPlayerStore = PlayerStore; diff --git a/packages/core/src/dom/store/features/feature.parts.ts b/packages/core/src/dom/store/features/feature.parts.ts index 9142c770..0fcfa8f4 100644 --- a/packages/core/src/dom/store/features/feature.parts.ts +++ b/packages/core/src/dom/store/features/feature.parts.ts @@ -1,4 +1,4 @@ -import type { AudioFeatures, VideoFeatures } from '../../media/types'; +import type { AudioFeatures, BackgroundFeatures, VideoFeatures } from '../../media/types'; import { bufferFeature } from './buffer'; import { fullscreenFeature } from './fullscreen'; import { pipFeature } from './pip'; @@ -29,3 +29,6 @@ export const video: VideoFeatures = [ ]; export const audio: AudioFeatures = [playbackFeature, volumeFeature, timeFeature, sourceFeature, bufferFeature]; + +// TODO: Add background video features (e.g., playback, source, buffer) +export const background: BackgroundFeatures = []; diff --git a/packages/html/package.json b/packages/html/package.json index f975f93f..308cb956 100644 --- a/packages/html/package.json +++ b/packages/html/package.json @@ -18,21 +18,26 @@ "development": "./dist/dev/index.js", "default": "./dist/default/index.js" }, - "./player/*": { - "types": "./dist/dev/define/player/*.d.ts", - "development": "./dist/dev/define/player/*.js", - "default": "./dist/default/define/player/*.js" + "./video/*": { + "types": "./dist/dev/define/video/*.d.ts", + "development": "./dist/dev/define/video/*.js", + "default": "./dist/default/define/video/*.js" + }, + "./audio/*": { + "types": "./dist/dev/define/audio/*.d.ts", + "development": "./dist/dev/define/audio/*.js", + "default": "./dist/default/define/audio/*.js" + }, + "./background/*": { + "types": "./dist/dev/define/background/*.d.ts", + "development": "./dist/dev/define/background/*.js", + "default": "./dist/default/define/background/*.js" }, "./ui/*": { "types": "./dist/dev/define/ui/*.d.ts", "development": "./dist/dev/define/ui/*.js", "default": "./dist/default/define/ui/*.js" }, - "./skin/*": { - "types": "./dist/dev/define/skin/*.d.ts", - "development": "./dist/dev/define/skin/*.js", - "default": "./dist/default/define/skin/*.js" - }, "./feature/*": { "types": "./dist/dev/define/feature/*.d.ts", "development": "./dist/dev/define/feature/*.js", diff --git a/packages/html/src/define/audio/player.ts b/packages/html/src/define/audio/player.ts new file mode 100644 index 00000000..ccba4e13 --- /dev/null +++ b/packages/html/src/define/audio/player.ts @@ -0,0 +1,18 @@ +import { features } from '@videojs/core/dom'; +import { createPlayer } from '../../player/create-player'; + +const { PlayerElement } = createPlayer({ + features: features.audio, +}); + +export class AudioPlayerElement extends PlayerElement { + static readonly tagName = 'audio-player'; +} + +customElements.define(AudioPlayerElement.tagName, AudioPlayerElement); + +declare global { + interface HTMLElementTagNameMap { + [AudioPlayerElement.tagName]: AudioPlayerElement; + } +} diff --git a/packages/html/src/define/audio/skin.ts b/packages/html/src/define/audio/skin.ts new file mode 100644 index 00000000..2367e2e4 --- /dev/null +++ b/packages/html/src/define/audio/skin.ts @@ -0,0 +1 @@ +// TODO: Implement AudioSkinElement and then register it here diff --git a/packages/html/src/define/background/player.ts b/packages/html/src/define/background/player.ts new file mode 100644 index 00000000..ca87fa4a --- /dev/null +++ b/packages/html/src/define/background/player.ts @@ -0,0 +1,18 @@ +import { features } from '@videojs/core/dom'; +import { createPlayer } from '../../player/create-player'; + +const { PlayerElement } = createPlayer({ + features: features.background, +}); + +export class BackgroundVideoPlayerElement extends PlayerElement { + static readonly tagName = 'background-video-player'; +} + +customElements.define(BackgroundVideoPlayerElement.tagName, BackgroundVideoPlayerElement); + +declare global { + interface HTMLElementTagNameMap { + [BackgroundVideoPlayerElement.tagName]: BackgroundVideoPlayerElement; + } +} diff --git a/packages/html/src/define/background/skin.ts b/packages/html/src/define/background/skin.ts new file mode 100644 index 00000000..64f854b0 --- /dev/null +++ b/packages/html/src/define/background/skin.ts @@ -0,0 +1 @@ +// TODO: Implement BackgroundVideoSkinElement and then register it here diff --git a/packages/html/src/define/player/video.ts b/packages/html/src/define/player/video.ts deleted file mode 100644 index d79a4807..00000000 --- a/packages/html/src/define/player/video.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { features } from '@videojs/core/dom'; -import { MediaElement } from '@/ui/media-element'; -import { createPlayer } from '../../player/create-player'; - -const { PlayerMixin } = createPlayer({ - features: features.video, -}); - -export class VideoPlayer extends PlayerMixin(MediaElement) {} - -customElements.define('video-player', VideoPlayer); - -declare global { - interface HTMLElementTagNameMap { - 'video-player': VideoPlayer; - } -} diff --git a/packages/html/src/define/video/player.ts b/packages/html/src/define/video/player.ts new file mode 100644 index 00000000..20580df5 --- /dev/null +++ b/packages/html/src/define/video/player.ts @@ -0,0 +1,18 @@ +import { features } from '@videojs/core/dom'; +import { createPlayer } from '../../player/create-player'; + +const { PlayerElement } = createPlayer({ + features: features.video, +}); + +export class VideoPlayerElement extends PlayerElement { + static readonly tagName = 'video-player'; +} + +customElements.define(VideoPlayerElement.tagName, VideoPlayerElement); + +declare global { + interface HTMLElementTagNameMap { + [VideoPlayerElement.tagName]: VideoPlayerElement; + } +} diff --git a/packages/html/src/define/skin/video.ts b/packages/html/src/define/video/skin.ts similarity index 100% rename from packages/html/src/define/skin/video.ts rename to packages/html/src/define/video/skin.ts diff --git a/packages/html/src/player/tests/create-player.test-d.ts b/packages/html/src/player/tests/create-player.test-d.ts index 56d66a55..a796be41 100644 --- a/packages/html/src/player/tests/create-player.test-d.ts +++ b/packages/html/src/player/tests/create-player.test-d.ts @@ -62,6 +62,12 @@ describe('createPlayer', () => { assertType(store.events); }); + it('resolves background features to generic PlayerStore', () => { + const result = createPlayer({ features: features.background }); + + assertType>>(result); + }); + it('resolves extended audio features to generic PlayerStore', () => { interface AnalyticsState { events: string[]; diff --git a/packages/html/src/player/tests/create-player.test.ts b/packages/html/src/player/tests/create-player.test.ts index 2396ff8e..374eecdf 100644 --- a/packages/html/src/player/tests/create-player.test.ts +++ b/packages/html/src/player/tests/create-player.test.ts @@ -31,4 +31,28 @@ describe('createPlayer', () => { expect(typeof PlayerElement).toBe('function'); expect(PlayerElement.prototype).toBeDefined(); }); + + it('creates audio player with expected exports', () => { + const result = createPlayer({ features: features.audio }); + + expect(result.context).toBeDefined(); + expect(result.create).toBeInstanceOf(Function); + expect(result.PlayerController).toBeDefined(); + expect(result.PlayerElement).toBeDefined(); + expect(result.PlayerMixin).toBeInstanceOf(Function); + expect(result.ProviderMixin).toBeInstanceOf(Function); + expect(result.ContainerMixin).toBeInstanceOf(Function); + }); + + it('creates background player with expected exports', () => { + const result = createPlayer({ features: features.background }); + + expect(result.context).toBeDefined(); + expect(result.create).toBeInstanceOf(Function); + expect(result.PlayerController).toBeDefined(); + expect(result.PlayerElement).toBeDefined(); + expect(result.PlayerMixin).toBeInstanceOf(Function); + expect(result.ProviderMixin).toBeInstanceOf(Function); + expect(result.ContainerMixin).toBeInstanceOf(Function); + }); }); diff --git a/rfc/player-api/examples.md b/rfc/player-api/examples.md index e5e2d2a3..99c16197 100644 --- a/rfc/player-api/examples.md +++ b/rfc/player-api/examples.md @@ -29,7 +29,7 @@ function App() { ### HTML ```ts -import '@videojs/html/player/video'; +import '@videojs/html/video/player'; ``` ```html @@ -44,11 +44,11 @@ import '@videojs/html/player/video'; ### React ```tsx -import '@videojs/react/skin/video.css'; +import '@videojs/react/video/skin.css'; import { createPlayer } from '@videojs/react'; import { features } from '@videojs/core/dom'; -import { VideoSkin } from '@videojs/react/skin/video'; +import { VideoSkin } from '@videojs/react/video/skin'; const { Provider: VideoProvider } = createPlayer({ features: [...features.video], @@ -68,9 +68,9 @@ function App() { ### HTML ```ts -import '@videojs/html/player/video'; -import '@videojs/html/skin/video.css'; -import '@videojs/html/skin/video'; +import '@videojs/html/video/player'; +import '@videojs/html/video/skin.css'; +import '@videojs/html/video/skin'; ``` ```html @@ -88,11 +88,11 @@ Skin detects chapters feature, shows chapter menu. ### React ```tsx -import '@videojs/react/skin/video.css'; +import '@videojs/react/video/skin.css'; import { createPlayer } from '@videojs/react'; import { features, chaptersSlice } from '@videojs/core/dom'; -import { VideoSkin } from '@videojs/react/skin/video'; +import { VideoSkin } from '@videojs/react/video/skin'; const { Provider: VideoProvider } = createPlayer({ features: [...features.video, chaptersSlice], @@ -112,10 +112,10 @@ function App() { ### HTML ```ts -import '@videojs/html/player/video'; +import '@videojs/html/video/player'; import '@videojs/html/feature/chapters'; -import '@videojs/html/skin/video.css'; -import '@videojs/html/skin/video'; +import '@videojs/html/video/skin.css'; +import '@videojs/html/video/skin'; ``` ```html @@ -133,12 +133,12 @@ Skin detects streaming features, shows quality/tracks menus. ### React ```tsx -import '@videojs/react/skin/video.css'; +import '@videojs/react/video/skin.css'; import { createPlayer } from '@videojs/react'; import { features } from '@videojs/core/dom'; import { HlsVideo } from '@videojs/react/media/hls'; -import { VideoSkin } from '@videojs/react/skin/video'; +import { VideoSkin } from '@videojs/react/video/skin'; const { Provider: VideoProvider } = createPlayer({ features: [...features.video, ...features.streaming], @@ -158,11 +158,11 @@ function App() { ### HTML ```ts -import '@videojs/html/player/video'; +import '@videojs/html/video/player'; import '@videojs/html/feature/streaming'; import '@videojs/html/media/hls-video'; -import '@videojs/html/skin/video.css'; -import '@videojs/html/skin/video'; +import '@videojs/html/video/skin.css'; +import '@videojs/html/video/skin'; ``` ```html @@ -180,12 +180,12 @@ Same skin adapts to show ad UI. ### React ```tsx -import '@videojs/react/skin/video.css'; +import '@videojs/react/video/skin.css'; import { createPlayer } from '@videojs/react'; import { features } from '@videojs/core/dom'; import { HlsVideo } from '@videojs/react/media/hls'; -import { VideoSkin } from '@videojs/react/skin/video'; +import { VideoSkin } from '@videojs/react/video/skin'; const { Provider: VideoProvider } = createPlayer({ features: [...features.video, ...features.streaming, ...features.ads], @@ -205,12 +205,12 @@ function App() { ### HTML ```ts -import '@videojs/html/player/video'; +import '@videojs/html/video/player'; import '@videojs/html/feature/streaming'; import '@videojs/html/feature/ads'; import '@videojs/html/media/hls-video'; -import '@videojs/html/skin/video.css'; -import '@videojs/html/skin/video'; +import '@videojs/html/video/skin.css'; +import '@videojs/html/video/skin'; ``` ## 6. Full Custom (Escape Hatch) diff --git a/rfc/player-api/html.md b/rfc/player-api/html.md index 00561692..a92dfa69 100644 --- a/rfc/player-api/html.md +++ b/rfc/player-api/html.md @@ -21,12 +21,30 @@ Only fundamentally different behaviors get their own player: ## Import Paths -### Player (includes base features) +Grouped by use case, then by concern: + +### Video (default) ```ts -import '@videojs/html/player/video'; // includes features.video -import '@videojs/html/player/audio'; // includes features.audio -import '@videojs/html/player/background-video'; +import '@videojs/html/video/player'; // includes features.video +import '@videojs/html/video/skin'; +import '@videojs/html/video/skin.css'; +``` + +### Audio (default) + +```ts +import '@videojs/html/audio/player'; // includes features.audio +import '@videojs/html/audio/skin'; +import '@videojs/html/audio/skin.css'; +``` + +### Background Video + +```ts +import '@videojs/html/background/player'; // includes features.background +import '@videojs/html/background/skin'; +import '@videojs/html/background/skin.css'; ``` ### Features (additive) @@ -51,21 +69,6 @@ import '@videojs/html/media/hls-audio'; import '@videojs/html/media/dash-video'; ``` -### Skins - -```ts -// Default skin -import '@videojs/html/skin/video.css'; -import '@videojs/html/skin/video'; - -// Named variants -import '@videojs/html/skin/video/minimal.css'; -import '@videojs/html/skin/video/minimal'; - -import '@videojs/html/skin/audio.css'; -import '@videojs/html/skin/audio'; -``` - ### UI Primitives (for custom skins) ```ts @@ -81,8 +84,8 @@ import '@videojs/html/ui/slider'; Elements register globally when imported: ```ts -import '@videojs/html/player/video'; -import '@videojs/html/skin/video'; +import '@videojs/html/video/player'; +import '@videojs/html/video/skin'; ``` ```html @@ -165,9 +168,9 @@ customElements.define('video-player', PlayerElement); Default skin adapts to available features: ```ts -import '@videojs/html/player/video'; +import '@videojs/html/video/player'; import '@videojs/html/feature/streaming'; // skin will show quality menu -import '@videojs/html/skin/video'; +import '@videojs/html/video/skin'; ``` ```html @@ -351,11 +354,11 @@ When media element and container need different DOM locations: ```ts // main.ts -import '@videojs/html/player/video'; +import '@videojs/html/video/player'; import '@videojs/html/feature/streaming'; import '@videojs/html/media/hls-video'; -import '@videojs/html/skin/video.css'; -import '@videojs/html/skin/video'; +import '@videojs/html/video/skin.css'; +import '@videojs/html/video/skin'; ``` ```html diff --git a/rfc/player-api/index.md b/rfc/player-api/index.md index 2e615219..25302176 100644 --- a/rfc/player-api/index.md +++ b/rfc/player-api/index.md @@ -74,11 +74,11 @@ Start simple Add as needed ### React ```tsx -import '@videojs/react/skin/video.css'; +import '@videojs/react/video/skin.css'; import { createPlayer } from '@videojs/react'; import { features } from '@videojs/core/dom'; -import { VideoSkin } from '@videojs/react/skin/video'; +import { VideoSkin } from '@videojs/react/video/skin'; const { Provider: VideoProvider } = createPlayer({ features: [...features.video], @@ -98,9 +98,9 @@ function App() { ### HTML ```ts -import '@videojs/html/player/video'; -import '@videojs/html/skin/video.css'; -import '@videojs/html/skin/video'; +import '@videojs/html/video/player'; +import '@videojs/html/video/skin.css'; +import '@videojs/html/video/skin'; ``` ```html diff --git a/site/src/components/installation/HTMLUsageCodeBlock.tsx b/site/src/components/installation/HTMLUsageCodeBlock.tsx index 184e185e..14d8c5e8 100644 --- a/site/src/components/installation/HTMLUsageCodeBlock.tsx +++ b/site/src/components/installation/HTMLUsageCodeBlock.tsx @@ -79,8 +79,17 @@ function generateHTMLCode(useCase: UseCase, skin: Skin, renderer: Renderer, play `; } +function getSkinImportPath(skin: Skin): string { + const map: Record = { + 'default-video': '@videojs/html/video/skin', + 'default-audio': '@videojs/html/audio/skin', + minimal: '@videojs/html/video/minimal-skin', + }; + return map[skin]; +} + function generateJS(skin: Skin): string { - return `import '@videojs/html/skins/${skin}';`; + return `import '${getSkinImportPath(skin)}';`; } export default function HTMLUsageCodeBlock() {