From 59bbf6c20924ec04e559fe23cbc1a0ad8c8ca080 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Wed, 18 Mar 2026 17:57:10 +1100 Subject: [PATCH] feat(packages): add poster component to video skins (#994) --- packages/html/src/define/base.css | 2 - .../src/define/video/minimal-skin.tailwind.ts | 8 ++- .../html/src/define/video/minimal-skin.ts | 5 ++ .../html/src/define/video/skin.tailwind.ts | 6 ++ packages/html/src/define/video/skin.ts | 5 ++ packages/react/src/presets/types.ts | 6 ++ .../presets/video/minimal-skin.tailwind.tsx | 14 +++- .../react/src/presets/video/minimal-skin.tsx | 13 +++- .../react/src/presets/video/skin.tailwind.tsx | 14 +++- packages/react/src/presets/video/skin.tsx | 13 +++- packages/react/src/utils/use-render.tsx | 5 ++ .../sandbox/app/shared/html/mux-storyboard.ts | 9 --- .../sandbox/app/shared/html/storyboard.ts | 3 + packages/sandbox/app/shared/mux.ts | 5 -- .../sandbox/app/shared/react/mux-poster.tsx | 14 ---- .../app/shared/react/mux-storyboard.tsx | 12 ---- .../sandbox/app/shared/react/storyboard.tsx | 8 +++ .../sandbox/app/shared/react/use-poster.ts | 8 +++ .../app/shared/react/use-storyboard.ts | 8 +++ packages/sandbox/app/shared/sources.ts | 14 +++- packages/sandbox/scripts/reset.ts | 44 ++++++++++-- packages/sandbox/scripts/setup.ts | 31 +++----- packages/sandbox/scripts/shared.ts | 70 ++++++++++++++++++- .../sandbox/templates/html-dash-video/main.ts | 11 ++- .../sandbox/templates/html-hls-video/main.ts | 10 ++- .../templates/html-simple-hls-video/main.ts | 10 ++- packages/sandbox/templates/html-video/main.ts | 12 ++-- .../templates/react-hls-video/main.tsx | 17 +++-- .../templates/react-simple-hls-video/main.tsx | 17 +++-- .../sandbox/templates/react-video/main.tsx | 17 +++-- packages/sandbox/vite.config.ts | 32 ++++----- .../src/default/css/components/media.css | 24 ------- .../src/default/css/components/poster.css | 34 +++++++++ packages/skins/src/default/css/video.css | 2 + .../src/default/tailwind/components/poster.ts | 21 ++++++ .../src/default/tailwind/video.tailwind.ts | 6 +- .../src/minimal/css/components/poster.css | 34 +++++++++ packages/skins/src/minimal/css/video.css | 2 + .../tailwind/components/playback-rate.ts | 3 + .../src/minimal/tailwind/components/poster.ts | 21 ++++++ .../src/minimal/tailwind/video.tailwind.ts | 7 +- site/src/components/home/Demo/Base.tsx | 5 +- site/src/components/home/HeroVideo.tsx | 4 +- .../react/FrostedSkin/FrostedSkinDemo.tsx | 5 +- .../react/MinimalSkin/MinimalSkinDemo.tsx | 5 +- 45 files changed, 445 insertions(+), 171 deletions(-) delete mode 100644 packages/sandbox/app/shared/html/mux-storyboard.ts create mode 100644 packages/sandbox/app/shared/html/storyboard.ts delete mode 100644 packages/sandbox/app/shared/react/mux-poster.tsx delete mode 100644 packages/sandbox/app/shared/react/mux-storyboard.tsx create mode 100644 packages/sandbox/app/shared/react/storyboard.tsx create mode 100644 packages/sandbox/app/shared/react/use-poster.ts create mode 100644 packages/sandbox/app/shared/react/use-storyboard.ts create mode 100644 packages/skins/src/default/css/components/poster.css create mode 100644 packages/skins/src/default/tailwind/components/poster.ts create mode 100644 packages/skins/src/minimal/css/components/poster.css create mode 100644 packages/skins/src/minimal/tailwind/components/playback-rate.ts create mode 100644 packages/skins/src/minimal/tailwind/components/poster.ts diff --git a/packages/html/src/define/base.css b/packages/html/src/define/base.css index 80ac3605..4d1ac6c2 100644 --- a/packages/html/src/define/base.css +++ b/packages/html/src/define/base.css @@ -10,8 +10,6 @@ video-player video { display: block; width: 100%; height: 100%; - object-fit: var(--media-object-fit, contain); - object-position: var(--media-object-position, center); } video-player video::-webkit-media-text-track-container { diff --git a/packages/html/src/define/video/minimal-skin.tailwind.ts b/packages/html/src/define/video/minimal-skin.tailwind.ts index abe9d1c8..cbc3e199 100644 --- a/packages/html/src/define/video/minimal-skin.tailwind.ts +++ b/packages/html/src/define/video/minimal-skin.tailwind.ts @@ -10,7 +10,9 @@ import { iconFlipped, iconState, overlay, + playbackRate, popup, + poster, preview, root, seek, @@ -32,13 +34,13 @@ import '../ui/pip-button'; import '../ui/play-button'; import '../ui/playback-rate-button'; import '../ui/popover'; +import '../ui/poster'; import '../ui/seek-button'; import '../ui/time'; import '../ui/time-slider'; import '../ui/tooltip'; import '../ui/tooltip-group'; import '../ui/volume-slider'; -import { playbackRate } from '@videojs/skins/default/tailwind/video.tailwind'; const SEEK_TIME = 10; @@ -47,6 +49,10 @@ function getTemplateHTML() { + + + + ${renderIcon('spinner')} diff --git a/packages/html/src/define/video/minimal-skin.ts b/packages/html/src/define/video/minimal-skin.ts index 41a06f6f..a0a66cf5 100644 --- a/packages/html/src/define/video/minimal-skin.ts +++ b/packages/html/src/define/video/minimal-skin.ts @@ -13,6 +13,7 @@ import '../ui/pip-button'; import '../ui/play-button'; import '../ui/playback-rate-button'; import '../ui/popover'; +import '../ui/poster'; import '../ui/seek-button'; import '../ui/time'; import '../ui/time-slider'; @@ -27,6 +28,10 @@ function getTemplateHTML() { + + + + ${renderIcon('spinner', { class: 'media-icon' })} diff --git a/packages/html/src/define/video/skin.tailwind.ts b/packages/html/src/define/video/skin.tailwind.ts index 1f235235..d61ccea4 100644 --- a/packages/html/src/define/video/skin.tailwind.ts +++ b/packages/html/src/define/video/skin.tailwind.ts @@ -11,6 +11,7 @@ import { overlay, playbackRate, popup, + poster, preview, root, seek, @@ -32,6 +33,7 @@ import '../ui/pip-button'; import '../ui/play-button'; import '../ui/playback-rate-button'; import '../ui/popover'; +import '../ui/poster'; import '../ui/seek-button'; import '../ui/time'; import '../ui/time-slider'; @@ -46,6 +48,10 @@ function getTemplateHTML() { + + + +
${renderIcon('spinner')} diff --git a/packages/html/src/define/video/skin.ts b/packages/html/src/define/video/skin.ts index 3aeef13f..823c8c01 100644 --- a/packages/html/src/define/video/skin.ts +++ b/packages/html/src/define/video/skin.ts @@ -14,6 +14,7 @@ import '../ui/pip-button'; import '../ui/play-button'; import '../ui/playback-rate-button'; import '../ui/popover'; +import '../ui/poster'; import '../ui/seek-button'; import '../ui/time'; import '../ui/time-slider'; @@ -28,6 +29,10 @@ function getTemplateHTML() { + + + +
${renderIcon('spinner', { class: 'media-icon' })} diff --git a/packages/react/src/presets/types.ts b/packages/react/src/presets/types.ts index 69f0d47a..fa612f06 100644 --- a/packages/react/src/presets/types.ts +++ b/packages/react/src/presets/types.ts @@ -1,4 +1,6 @@ import type { CSSProperties, PropsWithChildren } from 'react'; +import type { Poster } from '@/ui/poster'; +import type { RenderProp } from '@/utils/types'; export type BaseSkinProps = PropsWithChildren< T & { @@ -6,3 +8,7 @@ export type BaseSkinProps = PropsWithChildren< className?: string; } >; + +export type BaseVideoSkinProps = BaseSkinProps & { + poster?: string | RenderProp | undefined; +}; diff --git a/packages/react/src/presets/video/minimal-skin.tailwind.tsx b/packages/react/src/presets/video/minimal-skin.tailwind.tsx index b06a2307..4675e438 100644 --- a/packages/react/src/presets/video/minimal-skin.tailwind.tsx +++ b/packages/react/src/presets/video/minimal-skin.tailwind.tsx @@ -26,12 +26,14 @@ import { iconState, overlay, popup, + poster, preview, root, seek, slider, time, } from '@videojs/skins/minimal/tailwind/video.tailwind'; +import { isString } from '@videojs/utils/predicate'; import { cn } from '@videojs/utils/style'; import { type ComponentProps, forwardRef, type ReactNode } from 'react'; import { Container, usePlayer } from '@/player/context'; @@ -44,12 +46,14 @@ import { PiPButton } from '@/ui/pip-button'; import { PlayButton } from '@/ui/play-button'; import { PlaybackRateButton } from '@/ui/playback-rate-button'; import { Popover } from '@/ui/popover'; +import { Poster } from '@/ui/poster'; import { SeekButton } from '@/ui/seek-button'; import { Slider } from '@/ui/slider'; import { Time } from '@/ui/time'; import { TimeSlider } from '@/ui/time-slider'; import { Tooltip } from '@/ui/tooltip'; import { VolumeSlider } from '@/ui/volume-slider'; +import { isRenderProp } from '@/utils/use-render'; import { ErrorDialog } from './error-dialog'; import type { MinimalVideoSkinProps } from './minimal-skin'; @@ -142,12 +146,20 @@ function FullscreenLabel(): ReactNode { /* ------------------------------------------ Skin ------------------------------------------- */ export function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNode { - const { children, className, ...rest } = props; + const { children, className, poster: posterProp, ...rest } = props; return ( {children} + {posterProp && ( + + )} + (
diff --git a/packages/react/src/presets/video/minimal-skin.tsx b/packages/react/src/presets/video/minimal-skin.tsx index ccbe4f00..e9b6f74d 100644 --- a/packages/react/src/presets/video/minimal-skin.tsx +++ b/packages/react/src/presets/video/minimal-skin.tsx @@ -13,6 +13,7 @@ import { VolumeLowIcon, VolumeOffIcon, } from '@videojs/icons/react/minimal'; +import { isString } from '@videojs/utils/predicate'; import { cn } from '@videojs/utils/style'; import { type ComponentProps, forwardRef, type ReactNode } from 'react'; import { Container, usePlayer } from '@/player/context'; @@ -25,18 +26,20 @@ import { PiPButton } from '@/ui/pip-button'; import { PlayButton } from '@/ui/play-button'; import { PlaybackRateButton } from '@/ui/playback-rate-button'; import { Popover } from '@/ui/popover'; +import { Poster } from '@/ui/poster'; import { SeekButton } from '@/ui/seek-button'; import { Slider } from '@/ui/slider'; import { Time } from '@/ui/time'; import { TimeSlider } from '@/ui/time-slider'; import { Tooltip } from '@/ui/tooltip'; import { VolumeSlider } from '@/ui/volume-slider'; -import type { BaseSkinProps } from '../types'; +import { isRenderProp } from '@/utils/use-render'; +import type { BaseVideoSkinProps } from '../types'; import { ErrorDialog } from './error-dialog'; const SEEK_TIME = 10; -export type MinimalVideoSkinProps = BaseSkinProps; +export type MinimalVideoSkinProps = BaseVideoSkinProps; const Button = forwardRef>(function Button({ className, ...props }, ref) { return