VideoPlayer
DS-native video via Vidstack — CDN MP4, YouTube, and Vimeo URLs with one player API
Desktop autoplays muted or click-to-play; mobile shows poster still — no video downloaded
YouTube/Vimeo on touch or narrow viewports can use optional `nativeFallbackUrl` (MP4) instead of the embed for reliable tap-to-play
Exposes imperative handle (play/pause/restart/seek) for scroll-driven or external control
import { VideoPlayer } from "@everydayos/ui";Usage
Silent autoplay behind slot content — no controls.
<VideoPlayer
source={{ kind: "cdn", cdnUrl: "/video/bg.mp4", posterUrl: "/poster.jpg" }}
behavior="background"
controls="none"
loop
/>API
behaviorcontrolsambientGlass| Prop | Type | Default |
|---|---|---|
source* | VideoSource | — |
loop | boolean | — |
restartOnPlay | boolean | — |
onInitialPlaybackStart | () => void | — |
className | string | — |
Examples
Click-to-play with poster
Shows poster still + play button; clicking starts from t=0.
<MediaFrame aspect="video" radius="sm">
<VideoPlayer
source={{ kind: "cdn", cdnUrl: "/video/film.mp4", posterUrl: "/poster.jpg" }}
behavior="clickToPlay"
controls="minimal"
restartOnPlay
/>
</MediaFrame>Loop preview + click-to-play
Ambient loop on desktop, poster on mobile; click plays full video.
<MediaFrame aspect="video" radius="sm">
<VideoPlayer
source={{
kind: "cdn",
cdnUrl: "/video/film.mp4",
posterUrl: "/poster.jpg",
loopPreviewUrl: "/video/preview-loop.mp4",
}}
behavior="clickToPlay"
controls="minimal"
/>
</MediaFrame>YouTube (click-to-play)
Studio: YouTube URL + Mobile / touch MP4 strongly recommended; touch uses native playback when `nativeFallbackUrl` is set.
<MediaFrame aspect="video" radius="sm">
<VideoPlayer
source={{
kind: "youtube",
externalUrl: "https://www.youtube.com/watch?v=VIDEO_ID",
nativeFallbackUrl: "/video/same-film.mp4",
posterUrl: "/poster.jpg",
}}
behavior="clickToPlay"
controls="minimal"
/>
</MediaFrame>Externally controlled (scroll expand)
forwardRef handle for imperative play/pause from a scroll wrapper.
const playerRef = useRef<VideoPlayerHandle>(null);
<VideoPlayer
ref={playerRef}
source={{ kind: "cdn", cdnUrl: "/video/hero.mp4" }}
behavior="background"
/>
// From scroll wrapper:
playerRef.current?.restart();
playerRef.current?.pause();Composition
Pairs well with
When to avoid
- You want a background image — use MediaFrame with <Image fill />
- You cannot ship client-side JS — VideoPlayer is a client component with Vidstack
- You want a full-page video hero with text overlay — use v2Cell background:video + backgroundVideo
Patterns
Video in a slot (VideoPlayerShape)
The videoPlayer v2 shape wraps VideoPlayer in a MediaFrame with -mx-content-inset so it hugs cell edges, matching MediaShape behaviour.
<div className="-mx-content-inset">
<MediaFrame aspect="video" radius="sm">
<VideoPlayer source={source} behavior="clickToPlay" controls="minimal" />
</MediaFrame>
</div>Cell backdrop video (backgroundVideo)
VideoPlayer used as full-bleed background on a v2Cell. Slot content (textGroup, etc.) renders above it via Layer. Mode background = silent loop; mode player = play button over slots.
{/* CellRenderer renders this as absolute fill, slots on top */}
<VideoPlayer
source={cell.backgroundVideo.source}
behavior="background"
controls="none"
loop
/>Mobile click-to-play
On small viewports, autoplay background/minimal modes stay poster-only to save data. Click-to-play still mounts <video> with playsInline; the poster (or empty placeholder) stays on top until the user taps play so playback can start in a user gesture.
<VideoPlayer
source={{ kind: "cdn", cdnUrl: "...", posterUrl: "/poster.jpg" }}
behavior="clickToPlay"
/>
{/* On mobile: shows <VideoPlayerPoster> only, no <video> element created */}