{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "wave-video",
  "title": "Wave Video",
  "description": "Waveform player synced to a video element via the wavesurfer.js media prop.",
  "dependencies": [
    "wavesurfer.js",
    "lucide-react"
  ],
  "registryDependencies": [
    "@waves-cn/wave-cn",
    "button"
  ],
  "files": [
    {
      "path": "src/registry/components/wave-video.tsx",
      "content": "\"use client\";\n\nimport {\n  useState,\n  useCallback,\n  type CSSProperties,\n  type VideoHTMLAttributes,\n} from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Play, Pause } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport WavesurferPlayer, { useWavePlayer } from \"@/lib/wave-cn\";\n\n/**\n * Props for the WaveVideo component\n */\nexport type WaveVideoProps = {\n  /** Video source URL */\n  src?: string;\n  /** @deprecated use `src` */\n  url?: string;\n  /** Optional title shown above the video */\n  title?: string;\n  /** Wave bar color. Accepts any CSS value including var(--*) tokens @default \"var(--muted-foreground)\" */\n  waveColor?: string;\n  /** Progress bar color. Accepts any CSS value including var(--*) tokens @default \"var(--primary)\" */\n  progressColor?: string;\n  /** Wave canvas height in px @default 64 */\n  waveHeight?: number;\n  /** Bar width in px @default 3 */\n  barWidth?: number;\n  /** Gap between bars in px @default 2 */\n  barGap?: number;\n  /** Bar border radius in px @default 2 */\n  barRadius?: number;\n  /** Show the native video element @default true */\n  showVideo?: boolean;\n  /** Called when playback starts */\n  onPlay?: () => void;\n  /** Called when playback pauses */\n  onPause?: () => void;\n  /** Called when playback finishes */\n  onFinish?: () => void;\n  /** Called with current time on every audio process tick */\n  onTimeUpdate?: (currentTime: number, duration: number) => void;\n  /** Root element class */\n  className?: string;\n  /** Root element inline style */\n  style?: CSSProperties;\n  /** Class applied to the <video> element */\n  videoClassName?: string;\n  /** Inline style applied to the <video> element */\n  videoStyle?: CSSProperties;\n  /** Class applied to the waveform + controls container */\n  waveformClassName?: string;\n  /** Class applied to the WavesurferPlayer canvas wrapper */\n  waveClassName?: string;\n  /** Extra props forwarded to the <video> element (e.g. poster, loop, muted) */\n  videoProps?: VideoHTMLAttributes<HTMLVideoElement>;\n};\n\n/**\n * Waveform synced to a video element — wavesurfer.js reads the video\n * as its media source via the `media` prop so playback stays in sync.\n */\nexport function WaveVideo({\n  src,\n  url,\n  title,\n  waveColor,\n  progressColor,\n  waveHeight,\n  barWidth,\n  barGap,\n  barRadius,\n  showVideo = true,\n  onPlay,\n  onPause,\n  onFinish,\n  onTimeUpdate,\n  className,\n  style,\n  videoClassName,\n  videoStyle,\n  waveformClassName,\n  waveClassName,\n  videoProps,\n}: WaveVideoProps) {\n  const source = src ?? url;\n\n  const player = useWavePlayer({ onPlay, onPause, onFinish, onTimeUpdate });\n\n  // Callback ref — triggers a re-render the moment the <video> mounts\n  // so WavesurferPlayer receives the actual HTMLVideoElement, not null.\n  const [videoEl, setVideoEl] = useState<HTMLVideoElement | null>(null);\n  const videoCallbackRef = useCallback((el: HTMLVideoElement | null) => {\n    setVideoEl(el);\n  }, []);\n\n  return (\n    <div className={cn(\"w-full space-y-2 max-w-2xl\", className)} style={style}>\n      {title && (\n        <p className=\"text-sm font-medium text-foreground truncate\">{title}</p>\n      )}\n\n      {/* Video element — wavesurfer uses it as media source via the `media` prop */}\n      {showVideo && (\n        <video\n          ref={videoCallbackRef}\n          src={source}\n          controls={false}\n          playsInline\n          className={cn(\"w-full mx-auto bg-muted\", videoClassName)}\n          style={videoStyle}\n          {...videoProps}\n        />\n      )}\n\n      {/* Waveform + controls — only mounts once the video element is available */}\n      {videoEl && (\n        <div\n          className={cn(\n            \"w-full flex items-center gap-2 rounded-md overflow-hidden bg-muted/40 px-2\",\n            waveformClassName,\n          )}\n        >\n          <Button\n            size=\"icon\"\n            onClick={player.togglePlay}\n            disabled={!player.isReady}\n            aria-label={player.isPlaying ? \"Pause\" : \"Play\"}\n          >\n            {player.isPlaying ? (\n              <Pause className=\"size-4\" />\n            ) : (\n              <Play className=\"size-4\" />\n            )}\n          </Button>\n\n          <WavesurferPlayer\n            media={videoEl}\n            waveColor={waveColor}\n            progressColor={progressColor}\n            height={waveHeight}\n            barWidth={barWidth}\n            barGap={barGap}\n            barRadius={barRadius}\n            dragToSeek\n            className={cn(\"w-full\", waveClassName)}\n            {...player.handlers}\n          />\n        </div>\n      )}\n    </div>\n  );\n}\n\nexport default WaveVideo;\n",
      "type": "registry:component",
      "target": "components/waves-cn/wave-video.tsx"
    }
  ],
  "type": "registry:component"
}