Custom players
Put Video.js, Plyr, Mux or your own player in a slide.
render.slide can draw anything in a slide, including a full video player. The lightbox stays out of its way:
- Slideshow. Set
type: "video"on the slide. The slideshow plays and pauses the first<video>in the current slide, waits on it, moves on when it firesendedand stops when the viewer pauses it. Any player that renders a native<video>works without extra wiring. - Gestures. Drags that start on a
video,iframe, button, link, form field,role="slider"or anything inside[data-eg-no-gestures]are left to that element, so scrubbing or changing the volume never swipes. - Keyboard. Keys your content already handled with
event.preventDefault()are ignored, so a player's own shortcuts win while it has focus. - Leaving the slide. Native videos in other slides are paused. Unmounting does the rest, since only
preloadslides are mounted.
A tiny player
The demo below replaces the built-in video with a <video> and one play button. The slideshow still waits for the video to end.
Live demo. Click a thumbnail to open it.
function Player({ slide, width, height }: RenderSlideProps) {
const video = useRef<HTMLVideoElement>(null);
const [playing, setPlaying] = useState(false);
return (
<div style={{ position: "relative", width, height }} data-eg-no-gestures>
<video
ref={video}
src={slide.src}
poster={slide.poster}
width={width}
height={height}
playsInline
aria-label={slide.alt}
onPlay={() => setPlaying(true)}
onPause={() => setPlaying(false)}
>
{slide.tracks?.map((track) => (
<track
key={track.src}
kind="captions"
src={track.src}
srcLang={track.srcLang}
label={track.label}
/>
))}
</video>
<button
type="button"
className="eg-button"
aria-label={playing ? "Pause video" : "Play video"}
onClick={() => (video.current?.paused ? video.current.play() : video.current?.pause())}
>
{playing ? "Pause" : "Play"}
</button>
</div>
);
}
<Lightbox
slides={slides}
slideshow
render={{
slide: (props) => (props.slide.type === "video" ? <Player {...props} /> : undefined),
}}
{...props}
/>;render.slide is called like a function, not rendered as a component, so put hooks in a component such as Player and return it. Returning undefined keeps the built-in rendering for that slide.
Video.js 10
Video.js 10 (@videojs/react, 10.2.0 at the time of writing) renders a native <video> inside a skin you can fully customise, so it slots straight into render.slide:
import "@videojs/react/video/skin.css";
import { Video, VideoPlayer, VideoSkin } from "@videojs/react/video";
import { Lightbox, type Slide } from "liftbox";
const slides: Slide[] = [
{
src: "/media/talk.mp4",
type: "video",
poster: "/media/talk.jpg",
width: 1920,
height: 1080,
alt: "Talk",
},
{ src: "/photos/stage.jpg", width: 1600, height: 1067, alt: "Stage" },
];
<Lightbox
slides={slides}
slideshow
render={{
slide: ({ slide, width, height }) =>
slide.type === "video" ? (
<VideoPlayer>
<VideoSkin style={{ width, height }}>
<Video src={slide.src} poster={slide.poster} playsInline preload="metadata">
<track
kind="captions"
src="/media/talk.en.vtt"
srcLang="en"
label="English"
default
/>
</Video>
</VideoSkin>
</VideoPlayer>
) : undefined,
}}
{...props}
/>;- Theme. The packaged skin reads
--media-accent-color,--media-accent-text-color,--media-border-color,--media-border-radius,--media-font-family,--media-object-fit,--media-object-positionand--media-scale-unit, so it can share values with your--eg-*variables. - Full control.
npx shadcn@latest add @videojs/videocopies the skin's source into your project, where you can add, remove or restyle controls, or build your own skin from the Video.js UI components. - Content Security Policy. Video.js documents
style-src 'unsafe-inline'for some player UI, plusmedia-src blob:,worker-src blob:andconnect-srcfor HLS or DASH. The lightbox itself needs none of these.
The example follows the Video.js 10 docs: installation, customise skins, Video and CSP. Video forwards native attributes, so poster reaches the <video> element.
Players without a native video
Players that draw into a canvas or an iframe, such as embedded Mux or Wistia players, still render fine and get their own gestures. The slideshow cannot see when they end, so leave type unset and it treats the slide like an image.