{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "audio-player",
  "title": "Audio Player",
  "description": "Inline and floating audio-player controls.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "avatar",
    "popover",
    "slider",
    "tooltip"
  ],
  "files": [
    {
      "path": "src/components/examples/audio-player.tsx",
      "content": "import { useCallback, useEffect, useRef, useState } from 'react'\nimport { FastForward, ListMusic, Pause, Play, Rewind, SkipBack, SkipForward, Volume2 } from 'lucide-react'\nimport { Button } from '@/components/ui/button'\nimport { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'\nimport { Slider } from '@/components/ui/slider'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'\nimport { cn } from '@/lib/utils'\n\nexport function InlineAudioPlayer() {\n  const [isPlaying, setIsPlaying] = useState(false)\n  const [volume, setVolume] = useState([72])\n  const audioContextRef = useRef<AudioContext | null>(null)\n  const sequenceStepRef = useRef(0)\n  const volumeRef = useRef(volume[0])\n  const duration = 243\n  const elapsedBeforePlayRef = useRef(102)\n  const playbackStartedAtRef = useRef(0)\n  const [currentTime, setCurrentTime] = useState(102)\n  const progress = (currentTime / duration) * 100\n  const formatTime = (seconds: number) => `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`\n  volumeRef.current = volume[0]\n\n  const playStep = useCallback(() => {\n    const context = audioContextRef.current\n    if (!context || context.state !== 'running') return\n    const notes = [261.63, 329.63, 392, 493.88, 440, 392, 329.63, 293.66]\n    const note = notes[sequenceStepRef.current % notes.length]\n    sequenceStepRef.current += 1\n    const oscillator = context.createOscillator()\n    const gain = context.createGain()\n    oscillator.type = 'triangle'\n    oscillator.frequency.setValueAtTime(note, context.currentTime)\n    gain.gain.setValueAtTime((volumeRef.current / 100) * 0.035, context.currentTime)\n    gain.gain.exponentialRampToValueAtTime(0.001, context.currentTime + 0.42)\n    oscillator.connect(gain).connect(context.destination)\n    oscillator.start()\n    oscillator.stop(context.currentTime + 0.43)\n  }, [])\n\n  const togglePlayback = async () => {\n    if (isPlaying) {\n      const context = audioContextRef.current\n      if (context) {\n        const elapsed = Math.min(duration, Math.floor(elapsedBeforePlayRef.current + context.currentTime - playbackStartedAtRef.current))\n        elapsedBeforePlayRef.current = elapsed\n        setCurrentTime(elapsed)\n        await context.suspend()\n      }\n      setIsPlaying(false)\n      return\n    }\n    audioContextRef.current ??= new AudioContext()\n    await audioContextRef.current.resume()\n    playbackStartedAtRef.current = audioContextRef.current.currentTime\n    sequenceStepRef.current = Math.floor(elapsedBeforePlayRef.current / 0.48)\n    setIsPlaying(true)\n  }\n\n  const seek = (percentage: number) => {\n    const nextTime = Math.round((percentage / 100) * duration)\n    elapsedBeforePlayRef.current = nextTime\n    sequenceStepRef.current = Math.floor(nextTime / 0.48)\n    if (audioContextRef.current) playbackStartedAtRef.current = audioContextRef.current.currentTime\n    setCurrentTime(nextTime)\n  }\n\n  useEffect(() => {\n    if (!isPlaying) return\n    playStep()\n    const progressTimer = window.setInterval(() => {\n      const context = audioContextRef.current\n      if (!context) return\n      const elapsed = Math.floor(elapsedBeforePlayRef.current + context.currentTime - playbackStartedAtRef.current)\n      if (elapsed >= duration) {\n        elapsedBeforePlayRef.current = 0\n        playbackStartedAtRef.current = context.currentTime\n        sequenceStepRef.current = 0\n        setCurrentTime(0)\n        return\n      }\n      setCurrentTime(elapsed)\n    }, 100)\n    const audioTimer = window.setInterval(playStep, 480)\n    return () => { window.clearInterval(progressTimer); window.clearInterval(audioTimer) }\n  }, [isPlaying, playStep])\n\n  return <div className=\"flex w-full max-w-2xl items-center gap-3 rounded-full border bg-background p-2 shadow-sm sm:gap-4 sm:px-3\">\n    <Avatar size=\"lg\"><AvatarImage src=\"/images/alpine-cabin.png\" alt=\"Night Drive artwork\" /><AvatarFallback>ND</AvatarFallback></Avatar>\n    <div className=\"min-w-0 flex-1\"><div className=\"flex items-center justify-between gap-3\"><div className=\"min-w-0\"><p className=\"truncate text-sm font-medium text-foreground\">Night Drive</p><p className=\"truncate text-xs text-muted-foreground\">Demo synth loop</p></div><span className=\"hidden text-[0.625rem] tabular-nums text-muted-foreground sm:inline\">{formatTime(currentTime)} / {formatTime(duration)}</span></div><input aria-label=\"Track progress\" className=\"mt-2 block h-1.5 w-full cursor-pointer appearance-none rounded-full bg-muted accent-foreground\" type=\"range\" min=\"0\" max=\"100\" value={progress} onChange={(event) => seek(Number(event.target.value))} /></div>\n    <div className=\"flex shrink-0 items-center gap-0.5\"><Tooltip><TooltipTrigger asChild><Button variant=\"ghost\" size=\"icon-sm\" className=\"hidden sm:inline-flex\" aria-label=\"Previous track\"><SkipBack /></Button></TooltipTrigger><TooltipContent>Previous track</TooltipContent></Tooltip><Tooltip><TooltipTrigger asChild><Button variant=\"default\" size=\"icon\" className=\"rounded-full\" onClick={() => void togglePlayback()} aria-label={isPlaying ? 'Pause' : 'Play'}>{isPlaying ? <Pause className=\"fill-current\" /> : <Play className=\"fill-current\" />}</Button></TooltipTrigger><TooltipContent>{isPlaying ? 'Pause' : 'Play'}</TooltipContent></Tooltip><Tooltip><TooltipTrigger asChild><Button variant=\"ghost\" size=\"icon-sm\" className=\"hidden sm:inline-flex\" aria-label=\"Next track\"><SkipForward /></Button></TooltipTrigger><TooltipContent>Next track</TooltipContent></Tooltip><Popover><Tooltip><TooltipTrigger asChild><PopoverTrigger asChild><Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Adjust volume\"><Volume2 /></Button></PopoverTrigger></TooltipTrigger><TooltipContent>Adjust volume</TooltipContent></Tooltip><PopoverContent side=\"top\" className=\"z-50 w-auto items-center gap-3 p-3\"><span className=\"text-[0.625rem] tabular-nums text-muted-foreground\">{volume[0]}%</span><Slider orientation=\"vertical\" aria-label=\"Volume\" className=\"!h-36 !min-h-0\" value={volume} onValueChange={setVolume} /></PopoverContent></Popover></div>\n  </div>\n}\n\nexport type AudioPlayerProps = {\n  currentTime: number\n  handleSeek: (value: number[]) => void\n  audioDuration: number\n  skipBack: () => void\n  skipForward: () => void\n  togglePlayPause: () => void\n  isPlaying: boolean\n  volume: number\n  setVolume: (value: number) => void\n}\n\nfunction formatTime(seconds: number) {\n  return `${Math.floor(seconds / 60)}:${String(Math.floor(seconds % 60)).padStart(2, '0')}`\n}\n\nexport function FloatingAudioPlayer({ className, fixed = true, currentTime, handleSeek, audioDuration, skipBack, skipForward, togglePlayPause, isPlaying, volume, setVolume }: AudioPlayerProps & { className?: string; fixed?: boolean }) {\n  return <div className={cn(fixed ? 'fixed' : 'absolute', 'bottom-4 left-1/2 z-30 flex w-[calc(100%-2rem)] max-w-xl -translate-x-1/2 items-center gap-1.5 rounded-full border-2 border-primary bg-background p-2 shadow-sm sm:w-auto sm:gap-2', className)}>\n    <Tooltip><TooltipTrigger asChild><Button variant=\"outline\" className=\"rounded-full\" size=\"icon\" onClick={skipBack} aria-label=\"Skip back 10 seconds\"><Rewind /></Button></TooltipTrigger><TooltipContent>Skip back 10 seconds</TooltipContent></Tooltip>\n    <Tooltip><TooltipTrigger asChild><Button variant=\"default\" size=\"icon\" className=\"rounded-full\" onClick={togglePlayPause} aria-label={isPlaying ? 'Pause' : 'Play'}>{isPlaying ? <Pause /> : <Play className=\"fill-current\" />}</Button></TooltipTrigger><TooltipContent>{isPlaying ? 'Pause' : 'Play'}</TooltipContent></Tooltip>\n    <Tooltip><TooltipTrigger asChild><Button variant=\"outline\" className=\"hidden rounded-full sm:inline-flex\" size=\"icon\" onClick={skipForward} aria-label=\"Skip forward 10 seconds\"><FastForward /></Button></TooltipTrigger><TooltipContent>Skip forward 10 seconds</TooltipContent></Tooltip>\n    <Slider value={[currentTime]} max={audioDuration} step={0.1} onValueChange={handleSeek} aria-label=\"Track progress\" className=\"mx-1 w-16 sm:w-32 md:w-48\" />\n    <span className=\"hidden min-w-9 text-right text-[0.625rem] tabular-nums text-muted-foreground sm:inline\">{formatTime(currentTime)}</span>\n    <Tooltip><TooltipTrigger asChild><Button variant=\"ghost\" size=\"icon\" className=\"hidden rounded-full md:inline-flex\" aria-label=\"Live transcription\"><ListMusic /></Button></TooltipTrigger><TooltipContent>Live transcription</TooltipContent></Tooltip>\n    <Popover><Tooltip><TooltipTrigger asChild><PopoverTrigger asChild><Button variant=\"ghost\" size=\"icon\" className=\"rounded-full\" aria-label=\"Adjust volume\"><Volume2 /></Button></PopoverTrigger></TooltipTrigger><TooltipContent>Adjust volume</TooltipContent></Tooltip><PopoverContent side=\"top\" className=\"z-50 w-auto items-center gap-2 p-3\"><span className=\"text-[0.625rem] tabular-nums text-muted-foreground\">{volume}%</span><Slider orientation=\"vertical\" className=\"h-24\" value={[volume]} onValueChange={(newVolume) => setVolume(newVolume[0])} max={100} step={1} aria-label=\"Volume\" /></PopoverContent></Popover>\n  </div>\n}\n\nexport function FloatingAudioPlayerDemo() {\n  const [currentTime, setCurrentTime] = useState(78)\n  const [isPlaying, setIsPlaying] = useState(false)\n  const [volume, setVolume] = useState(72)\n  const audioDuration = 243\n\n  useEffect(() => {\n    if (!isPlaying) return\n    const timer = window.setInterval(() => setCurrentTime((time) => time >= audioDuration ? 0 : time + 1), 1000)\n    return () => window.clearInterval(timer)\n  }, [isPlaying])\n\n  return <div className=\"relative h-36 w-full max-w-3xl\"><FloatingAudioPlayer fixed={false} className=\"bottom-3\" currentTime={currentTime} handleSeek={(value) => setCurrentTime(value[0])} audioDuration={audioDuration} skipBack={() => setCurrentTime((time) => Math.max(0, time - 10))} skipForward={() => setCurrentTime((time) => Math.min(audioDuration, time + 10))} togglePlayPause={() => setIsPlaying((value) => !value)} isPlaying={isPlaying} volume={volume} setVolume={setVolume} /></div>\n}\n",
      "type": "registry:block"
    }
  ],
  "type": "registry:block"
}