{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "hover-video-player", "type": "registry:ui", "description": "Video player component that plays on hover with smooth animations", "dependencies": [ "motion" ], "files": [ { "path": "registry/default/ui/hover-video-player.tsx", "content": "\"use client\"\n\n/**\n * HoverVideoPlayer Component\n *\n * A React component that plays video on hover/touch with advanced features like:\n * - Lazy loading and intersection observer support\n * - Mobile touch support\n * - Picture-in-Picture\n * - Custom overlay support\n * - Thumbnail support\n * - Playback controls\n *\n * @example\n * ```tsx\n * }\n * loadingOverlay={}\n * enableControls\n * />\n * ```\n */\nimport React, {\n createContext,\n ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\"\nimport Image from \"next/image\"\nimport { Maximize, Minimize, Pause, Play, Volume2, VolumeX } from \"lucide-react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Slider } from \"@/components/ui/slider\"\n\nconst hoverTransition = {\n duration: 0.18,\n ease: [0.2, 0, 0, 1],\n} as const\n\nconst hoverSpring = {\n type: \"spring\",\n duration: 0.3,\n bounce: 0,\n} as const\n\n// Types\ninterface VideoPlayerState {\n isHovering: boolean\n isPlaying: boolean\n isLoading: boolean\n progress: number\n muted: boolean\n volume: number\n isPiP: boolean\n isMobile: boolean\n controlsVisible: boolean\n showThumbnail: boolean\n isInView: boolean\n}\n\ninterface HoverVideoPlayerProps {\n videoSrc: string\n thumbnailSrc?: string\n hoverOverlay?: React.ReactNode\n pausedOverlay?: React.ReactNode\n loadingOverlay?: React.ReactNode\n playbackStartDelay?: number\n restartOnPaused?: boolean\n unloadVideoOnPaused?: boolean\n playbackRangeStart?: number\n playbackRangeEnd?: number\n muted?: boolean\n loop?: boolean\n preload?: \"auto\" | \"metadata\" | \"none\"\n className?: string\n style?: React.CSSProperties\n onHoverStart?: () => void\n onHoverEnd?: () => void\n enableControls?: boolean\n cropTop?: number\n cropBottom?: number\n isVimeo?: boolean\n}\n\ninterface HoverVideoPlayerContextType {\n isPlaying: boolean\n isHovering: boolean\n isLoading: boolean\n progress: number\n volume: number\n muted: boolean\n isPiP: boolean\n isMobile: boolean\n controlsVisible: boolean\n videoRef: React.RefObject\n togglePlay: () => void\n toggleMute: () => void\n togglePiP: () => void\n setVolume: (value: number) => void\n setProgress: (value: number) => void\n setVideoLoading: (isLoading: boolean) => void\n setVideoPlaying: (isPlaying: boolean) => void\n cropTop: number\n cropBottom: number\n thumbnailSrc?: string\n}\n\ninterface VimeoPlayer {\n destroy: () => void\n ready: () => Promise\n setVolume: (volume: number) => Promise\n play: () => Promise\n pause: () => Promise\n on: (event: string, callback: (...args: any[]) => void) => void\n off: (event: string, callback: (...args: any[]) => void) => void\n}\n\ninterface VimeoConstructor {\n Player: {\n new (element: HTMLElement, options: any): VimeoPlayer\n }\n}\n\n// Context\nconst HoverVideoPlayerContext =\n createContext(null)\n\n/**\n * Custom hook to access HoverVideoPlayer context\n * @throws {Error} If used outside of HoverVideoPlayerContext\n */\nconst useHoverVideoPlayer = () => {\n const context = useContext(HoverVideoPlayerContext)\n if (!context) {\n throw new Error(\n \"useHoverVideoPlayer must be used within a HoverVideoPlayer\"\n )\n }\n return context\n}\n\n// Add this helper function before the HoverVideoPlayer component\nfunction debounce any>(\n func: T,\n wait: number\n): {\n (...args: Parameters): void\n cancel: () => void\n} {\n let timeoutId: NodeJS.Timeout | null = null\n\n const debouncedFn = (...args: Parameters) => {\n if (timeoutId) clearTimeout(timeoutId)\n timeoutId = setTimeout(() => func(...args), wait)\n }\n\n debouncedFn.cancel = () => {\n if (timeoutId) {\n clearTimeout(timeoutId)\n timeoutId = null\n }\n }\n\n return debouncedFn\n}\n\n// Add this helper function\nfunction isVimeoUrl(url: string): boolean {\n return url.includes(\"player.vimeo.com/video/\") || url.includes(\"vimeo.com/\")\n}\n\n// Add this helper function\nfunction loadVimeoSDK(): Promise {\n return new Promise((resolve, reject) => {\n if ((window as any).Vimeo) {\n resolve((window as any).Vimeo)\n return\n }\n\n const script = document.createElement(\"script\")\n script.src = \"https://player.vimeo.com/api/player.js\"\n script.async = true\n script.onload = () => resolve((window as any).Vimeo)\n script.onerror = reject\n document.body.appendChild(script)\n })\n}\n\n// Main Component\nconst HoverVideoPlayer: React.FC = ({\n videoSrc,\n thumbnailSrc,\n hoverOverlay,\n pausedOverlay,\n loadingOverlay,\n playbackStartDelay = 0,\n restartOnPaused = false,\n unloadVideoOnPaused = false,\n playbackRangeStart,\n playbackRangeEnd,\n muted: initialMuted = false,\n loop = true,\n preload = \"metadata\",\n className,\n style,\n onHoverStart,\n onHoverEnd,\n enableControls = false,\n cropTop = 0,\n cropBottom = 0,\n isVimeo = false,\n}) => {\n const isVimeoVideo = isVimeo || isVimeoUrl(videoSrc)\n\n // Refs for DOM elements and timing\n const containerRef = useRef(null)\n const videoRef = useRef(null)\n const playbackTimeoutRef = useRef(null)\n const lastPlayAttemptRef = useRef(0)\n\n // Consolidated state management\n const [state, setState] = useState({\n isHovering: false,\n isPlaying: false,\n isLoading: false,\n progress: 0,\n muted: initialMuted,\n volume: 1,\n isPiP: false,\n isMobile: false,\n controlsVisible: false,\n showThumbnail: true,\n isInView: false,\n })\n\n // Mobile detection\n const checkMobile = useMemo(\n () =>\n debounce(() => {\n setState((prev) => ({ ...prev, isMobile: window.innerWidth <= 768 }))\n }, 200),\n []\n )\n\n // Event Handlers\n const handleTouchStart = useCallback(() => {\n if (state.isMobile) {\n setState((prev) => ({ ...prev, controlsVisible: !prev.controlsVisible }))\n }\n }, [state.isMobile])\n\n const handleHoverStart = useCallback(() => {\n if (!state.isMobile) {\n console.log(\"Hover start\")\n setState((prev) => ({ ...prev, isHovering: true }))\n onHoverStart?.()\n }\n }, [state.isMobile, onHoverStart])\n\n const handleHoverEnd = useCallback(() => {\n if (!state.isMobile) {\n console.log(\"Hover end\")\n setState((prev) => ({ ...prev, isHovering: false }))\n onHoverEnd?.()\n }\n }, [state.isMobile, onHoverEnd])\n\n // Video Playback Controls\n const playVideo = useCallback(() => {\n if (!videoRef.current || !state.isInView) {\n console.log(\"PlayVideo blocked:\", {\n hasVideo: !!videoRef.current,\n isInView: state.isInView,\n })\n return\n }\n\n const video = videoRef.current\n console.log(\"Attempting to play video:\", {\n readyState: video.readyState,\n paused: video.paused,\n currentSrc: video.currentSrc,\n })\n\n // Reset loading state when attempting to play\n setState((prev) => ({ ...prev, isLoading: true }))\n\n // Ensure video is ready to play\n const attemptPlay = () => {\n video\n .play()\n .then(() => {\n console.log(\"Video play success\")\n setState((prev) => ({\n ...prev,\n isLoading: false,\n isPlaying: true,\n showThumbnail: false,\n }))\n })\n .catch((error) => {\n console.log(\"Video play error:\", error.name)\n if (error.name === \"NotAllowedError\") {\n // User interaction required - show thumbnail\n setState((prev) => ({\n ...prev,\n isLoading: false,\n showThumbnail: true,\n }))\n } else if (error.name !== \"AbortError\") {\n console.error(\"HoverVideoPlayer: Playback error:\", error)\n setState((prev) => ({\n ...prev,\n isLoading: false,\n showThumbnail: true,\n }))\n }\n })\n }\n\n if (video.readyState >= 3) {\n console.log(\"Video ready, attempting immediate play\")\n attemptPlay()\n } else {\n console.log(\"Video not ready, waiting for canplay event\")\n const handleCanPlay = () => {\n console.log(\"Canplay event fired\")\n video.removeEventListener(\"canplay\", handleCanPlay)\n attemptPlay()\n }\n video.addEventListener(\"canplay\", handleCanPlay)\n }\n }, [state.isInView])\n\n const pauseVideo = useCallback(() => {\n if (!videoRef.current) return\n\n videoRef.current.pause()\n setState((prev) => ({\n ...prev,\n isPlaying: false,\n showThumbnail: true,\n }))\n\n if (restartOnPaused) {\n videoRef.current.currentTime = playbackRangeStart || 0\n }\n }, [restartOnPaused, playbackRangeStart])\n\n // Effects\n\n // Mobile detection effect\n useEffect(() => {\n checkMobile()\n window.addEventListener(\"resize\", checkMobile)\n return () => {\n window.removeEventListener(\"resize\", checkMobile)\n checkMobile.cancel()\n }\n }, [checkMobile])\n\n // Intersection Observer effect\n useEffect(() => {\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n const isNowInView = entry.isIntersecting\n setState((prev) => ({ ...prev, isInView: isNowInView }))\n\n if (!isNowInView) {\n pauseVideo()\n }\n })\n },\n {\n root: null,\n rootMargin: \"50px\",\n threshold: 0.1,\n }\n )\n\n if (containerRef.current) {\n observer.observe(containerRef.current)\n }\n\n return () => observer.disconnect()\n }, [pauseVideo])\n\n // Video event handlers effect\n useEffect(() => {\n const video = videoRef.current\n if (!video || !state.isInView) return\n\n const handlers = {\n loadstart: () => {\n console.log(\"Video loadstart\")\n setState((prev) => ({ ...prev, isLoading: true }))\n },\n loadeddata: () => {\n console.log(\"Video loadeddata\", {\n readyState: video.readyState,\n duration: video.duration,\n paused: video.paused,\n isHovering: state.isHovering,\n controlsVisible: state.controlsVisible,\n })\n setState((prev) => ({ ...prev, isLoading: false }))\n // Only attempt to play if we're hovering or controls are visible\n if (state.isHovering || state.controlsVisible) {\n console.log(\"Video loaded and hover/controls active, attempting play\")\n playVideo()\n }\n },\n canplay: () => {\n console.log(\"Video canplay event\")\n },\n playing: () => {\n console.log(\"Video playing\")\n setState((prev) => ({\n ...prev,\n isLoading: false,\n isPlaying: true,\n showThumbnail: false,\n }))\n },\n pause: () => {\n console.log(\"Video paused\")\n setState((prev) => ({\n ...prev,\n isPlaying: false,\n showThumbnail: true,\n }))\n },\n error: (e: Event) => {\n const videoError = (e.target as HTMLVideoElement).error\n console.error(\"Video error:\", {\n code: videoError?.code,\n message: videoError?.message,\n currentSrc: video.currentSrc,\n })\n setState((prev) => ({\n ...prev,\n isLoading: false,\n showThumbnail: true,\n }))\n },\n }\n\n // Add event listeners\n Object.entries(handlers).forEach(([event, handler]) => {\n video.addEventListener(event, handler)\n })\n\n // Set initial source and load the video\n if (!video.src) {\n console.log(\"Setting video source:\", videoSrc)\n video.src = videoSrc\n video.load()\n }\n\n return () => {\n Object.entries(handlers).forEach(([event, handler]) => {\n video.removeEventListener(event, handler)\n })\n }\n }, [\n state.isInView,\n state.isHovering,\n state.controlsVisible,\n playVideo,\n videoSrc,\n ])\n\n // Playback control effect\n useEffect(() => {\n console.log(\"Playback control effect:\", {\n isInView: state.isInView,\n isMobile: state.isMobile,\n controlsVisible: state.controlsVisible,\n isHovering: state.isHovering,\n })\n\n if (!state.isInView) {\n console.log(\"Not in view, skipping playback\")\n return\n }\n\n if (isVimeoVideo) {\n return\n }\n\n let playbackTimeout: NodeJS.Timeout | undefined\n\n if (state.isMobile) {\n if (state.controlsVisible) {\n console.log(\"Mobile controls visible, playing\")\n playVideo()\n } else {\n console.log(\"Mobile controls hidden, pausing\")\n pauseVideo()\n }\n } else if (state.isHovering) {\n console.log(\n \"Hovering, scheduling playback with delay:\",\n playbackStartDelay\n )\n playbackTimeout = setTimeout(() => {\n console.log(\"Playback delay completed, attempting to play\")\n playVideo()\n }, playbackStartDelay)\n } else {\n console.log(\"Not hovering, pausing\")\n pauseVideo()\n }\n\n return () => {\n if (playbackTimeout) {\n console.log(\"Clearing playback timeout\")\n clearTimeout(playbackTimeout)\n }\n }\n }, [\n state.isInView,\n state.isMobile,\n state.controlsVisible,\n state.isHovering,\n playVideo,\n pauseVideo,\n playbackStartDelay,\n isVimeoVideo,\n ])\n\n // Volume effect\n useEffect(() => {\n const video = videoRef.current\n if (!video) return\n\n video.muted = state.muted\n video.volume = state.volume\n }, [state.muted, state.volume])\n\n // Control handlers\n const togglePlayPause = useCallback(() => {\n if (state.isPlaying) {\n pauseVideo()\n } else {\n playVideo()\n }\n }, [state.isPlaying, pauseVideo, playVideo])\n\n const toggleMute = useCallback(() => {\n setState((prev) => ({ ...prev, muted: !prev.muted }))\n }, [])\n\n const togglePiP = useCallback(async () => {\n if (!document.pictureInPictureEnabled) return\n\n try {\n if (document.pictureInPictureElement) {\n await document.exitPictureInPicture()\n setState((prev) => ({ ...prev, isPiP: false }))\n } else if (videoRef.current) {\n await videoRef.current.requestPictureInPicture()\n setState((prev) => ({ ...prev, isPiP: true }))\n }\n } catch (error) {\n console.error(\"PiP error:\", error)\n }\n }, [])\n\n const setVideoLoading = useCallback((isLoading: boolean) => {\n setState((prev) => ({ ...prev, isLoading }))\n }, [])\n\n const setVideoPlaying = useCallback((isPlaying: boolean) => {\n setState((prev) => ({\n ...prev,\n isPlaying,\n showThumbnail: !isPlaying,\n }))\n }, [])\n\n // Context value\n const contextValue = useMemo(\n () => ({\n isPlaying: state.isPlaying,\n isHovering: state.isHovering,\n isLoading: state.isLoading,\n progress: state.progress,\n volume: state.volume,\n muted: state.muted,\n isPiP: state.isPiP,\n isMobile: state.isMobile,\n controlsVisible: state.controlsVisible,\n videoRef,\n togglePlay: togglePlayPause,\n toggleMute,\n togglePiP,\n setVolume: (value) => setState((prev) => ({ ...prev, volume: value })),\n setProgress: (value) =>\n setState((prev) => ({ ...prev, progress: value })),\n setVideoLoading,\n setVideoPlaying,\n cropTop,\n cropBottom,\n thumbnailSrc,\n }),\n [\n state,\n togglePlayPause,\n toggleMute,\n togglePiP,\n setVideoLoading,\n setVideoPlaying,\n cropTop,\n cropBottom,\n thumbnailSrc,\n ]\n )\n\n // Render\n return (\n \n \n {/* Video Element */}\n {state.isInView && (\n \n )}\n\n {/* Thumbnail */}\n {thumbnailSrc && }\n\n {/* Overlays */}\n {pausedOverlay && (\n \n {pausedOverlay}\n \n )}\n {loadingOverlay && (\n \n {loadingOverlay}\n \n )}\n {hoverOverlay && !state.isMobile && (\n \n {hoverOverlay}\n \n )}\n\n {/* Controls */}\n {enableControls && (\n \n
\n \n \n \n
\n \n
\n )}\n \n
\n )\n}\n\n/**\n * Video component that handles the actual video element\n */\nconst HoverVideoPlayerVideo: React.FC<{\n src: string\n unloadVideoOnPaused: boolean\n loop: boolean\n preload: string\n}> = ({ src, loop, preload }) => {\n const {\n videoRef,\n muted,\n cropTop,\n cropBottom,\n isHovering,\n isMobile,\n controlsVisible,\n setVideoLoading,\n setVideoPlaying,\n } = useHoverVideoPlayer()\n const isVimeoVideo = isVimeoUrl(src)\n const containerRef = useRef(null)\n const playerRef = useRef(null)\n const shouldPlayVimeo = isMobile ? controlsVisible : isHovering\n const shouldPlayVimeoRef = useRef(shouldPlayVimeo)\n\n useEffect(() => {\n shouldPlayVimeoRef.current = shouldPlayVimeo\n }, [shouldPlayVimeo])\n\n useEffect(() => {\n if (!isVimeoVideo || !containerRef.current) return\n\n const videoId = src.split(\"/\").pop() || \"\"\n let player: VimeoPlayer | null = null\n setVideoLoading(true)\n\n loadVimeoSDK()\n .then((Vimeo) => {\n if (!containerRef.current) return\n\n player = new Vimeo.Player(containerRef.current, {\n id: videoId,\n autopause: false,\n muted,\n loop,\n responsive: true,\n controls: false,\n autoplay: false,\n volume: 1,\n })\n\n playerRef.current = player\n\n player.ready().then(() => {\n console.log(\"Vimeo video loaded\")\n setVideoLoading(false)\n\n if (shouldPlayVimeoRef.current) {\n player\n ?.play()\n .then(() => setVideoPlaying(true))\n .catch((error) => {\n setVideoPlaying(false)\n console.error(\"Vimeo play error:\", error)\n })\n }\n })\n\n player.on(\"play\", () => {\n console.log(\"Vimeo video playing\")\n setVideoLoading(false)\n setVideoPlaying(true)\n })\n player.on(\"pause\", () => {\n console.log(\"Vimeo video paused\")\n setVideoPlaying(false)\n })\n player.on(\"error\", (err) => {\n setVideoLoading(false)\n setVideoPlaying(false)\n console.error(\"Vimeo player error:\", err)\n })\n })\n .catch((error) => {\n setVideoLoading(false)\n console.error(\"Failed to load Vimeo SDK:\", error)\n })\n\n return () => {\n if (player) {\n player.destroy()\n playerRef.current = null\n }\n }\n }, [isVimeoVideo, src, muted, loop, setVideoLoading, setVideoPlaying])\n\n useEffect(() => {\n if (!isVimeoVideo) return\n\n const player = playerRef.current\n if (!player) return\n\n if (shouldPlayVimeo) {\n setVideoLoading(true)\n player\n .play()\n .then(() => {\n setVideoLoading(false)\n setVideoPlaying(true)\n })\n .catch((error) => {\n setVideoLoading(false)\n setVideoPlaying(false)\n console.error(\"Vimeo play error:\", error)\n })\n } else {\n player\n .pause()\n .then(() => setVideoPlaying(false))\n .catch((error) => {\n console.error(\"Vimeo pause error:\", error)\n })\n }\n }, [isVimeoVideo, shouldPlayVimeo, setVideoLoading, setVideoPlaying])\n\n if (isVimeoVideo) {\n return
\n }\n\n return (\n \n )\n}\n\n/**\n * Thumbnail component shown when video is not playing\n */\nconst HoverVideoPlayerThumbnail: React.FC<{ src: string }> = ({ src }) => {\n const { cropTop, cropBottom, isLoading, isPlaying } = useHoverVideoPlayer()\n const shouldReduceMotion = useReducedMotion()\n const shouldShowThumbnail = !isPlaying || isLoading\n\n return (\n \n \n \n )\n}\n\n/**\n * Controls overlay with play/pause, volume, and progress bar\n */\nconst HoverVideoPlayerControls: React.FC<{ children: React.ReactNode }> = ({\n children,\n}) => {\n const { isHovering, isMobile, controlsVisible } = useHoverVideoPlayer()\n const shouldReduceMotion = useReducedMotion()\n\n const shouldShowControls = isMobile ? controlsVisible : isHovering\n\n return (\n \n {shouldShowControls && (\n \n {children}\n \n )}\n \n )\n}\n\n/**\n * Overlay shown when hovering over the video\n */\nconst HoverVideoPlayerHoverOverlay: React.FC<{ children: React.ReactNode }> = ({\n children,\n}) => {\n const { isHovering, isLoading } = useHoverVideoPlayer()\n const shouldReduceMotion = useReducedMotion()\n\n return (\n \n {isHovering && !isLoading && (\n \n {children}\n \n )}\n \n )\n}\n\n/**\n * Overlay shown when video is paused\n */\nconst HoverVideoPlayerPausedOverlay: React.FC<{\n children: React.ReactNode\n}> = ({ children }) => {\n const { isPlaying, isLoading, isHovering } = useHoverVideoPlayer()\n const shouldReduceMotion = useReducedMotion()\n\n return (\n \n {!isPlaying && !isLoading && !isHovering && (\n \n {children}\n \n )}\n \n )\n}\n\n/**\n * Overlay shown while video is loading\n */\nconst HoverVideoPlayerLoadingOverlay: React.FC<{\n children: React.ReactNode\n}> = ({ children }) => {\n const { isLoading } = useHoverVideoPlayer()\n const shouldReduceMotion = useReducedMotion()\n\n return (\n \n {isLoading && (\n \n {children}\n \n )}\n \n )\n}\n\n/**\n * Play/Pause button component\n */\nconst HoverVideoPlayerPlayPauseButton: React.FC = () => {\n const { isPlaying, togglePlay } = useHoverVideoPlayer()\n\n return (\n \n {isPlaying ? : }\n \n )\n}\n\n/**\n * Volume control component with mute toggle and volume slider\n */\nconst HoverVideoPlayerVolumeControl: React.FC = () => {\n const { muted, toggleMute, volume, setVolume } = useHoverVideoPlayer()\n\n return (\n
\n \n {muted ? (\n \n ) : (\n \n )}\n \n setVolume(value[0] / 100)}\n aria-label=\"Volume\"\n />\n
\n )\n}\n\n/**\n * Progress bar component for video timeline\n */\nconst HoverVideoPlayerProgressBar: React.FC = () => {\n const { progress, setProgress, videoRef } = useHoverVideoPlayer()\n\n return (\n {\n if (videoRef.current) {\n const newTime = (value[0] / 100) * videoRef.current.duration\n videoRef.current.currentTime = newTime\n setProgress(value[0])\n }\n }}\n aria-label=\"Video progress\"\n />\n )\n}\n\n/**\n * Picture-in-Picture toggle button\n */\nconst HoverVideoPlayerPiPButton: React.FC = () => {\n const { isPiP, togglePiP } = useHoverVideoPlayer()\n\n return (\n \n {isPiP ? (\n \n ) : (\n \n )}\n \n )\n}\n\n/**\n * Wrapper component for maintaining aspect ratio with padding\n */\nconst HoverVideoPaddingWrapper: React.FC<{\n width: number\n height: number\n className?: string\n children?: ReactNode\n}> = ({ width, height, className, children }) => {\n return (\n \n
\n {children}\n
\n )\n}\n\n// Add display names for better debugging\nHoverVideoPlayerControls.displayName = \"HoverVideoPlayerControls\"\nHoverVideoPaddingWrapper.displayName = \"HoverVideoPaddingWrapper\"\nHoverVideoPlayerHoverOverlay.displayName = \"HoverVideoPlayerHoverOverlay\"\nHoverVideoPlayerPausedOverlay.displayName = \"HoverVideoPlayerPausedOverlay\"\nHoverVideoPlayerLoadingOverlay.displayName = \"HoverVideoPlayerLoadingOverlay\"\nHoverVideoPlayerPlayPauseButton.displayName = \"HoverVideoPlayerPlayPauseButton\"\nHoverVideoPlayerVolumeControl.displayName = \"HoverVideoPlayerVolumeControl\"\nHoverVideoPlayerProgressBar.displayName = \"HoverVideoPlayerProgressBar\"\nHoverVideoPlayerPiPButton.displayName = \"HoverVideoPlayerPiPButton\"\n\nexport {\n HoverVideoPlayer,\n HoverVideoPlayerControls,\n HoverVideoPlayerHoverOverlay,\n HoverVideoPlayerPausedOverlay,\n HoverVideoPlayerLoadingOverlay,\n HoverVideoPlayerPlayPauseButton,\n HoverVideoPlayerVolumeControl,\n HoverVideoPlayerProgressBar,\n HoverVideoPlayerPiPButton,\n HoverVideoPaddingWrapper,\n}\n\nexport default HoverVideoPlayer\n", "type": "registry:ui" } ] }