{
  "name": "media-player-playlist-demo",
  "type": "registry:example",
  "dependencies": [
    "cn",
    "media-chrome"
  ],
  "registryDependencies": [
    "button",
    "media-player",
    "scroll-area"
  ],
  "files": [
    {
      "path": "examples/media-player-playlist-demo.tsx",
      "type": "registry:example",
      "content": "\"use client\";\n\nimport { cn } from \"cn\";\nimport {\n  ListMusicIcon,\n  Loader2Icon,\n  PauseCircleIcon,\n  PlayCircleIcon,\n  SkipBackIcon,\n  SkipForwardIcon,\n} from \"lucide-react\";\nimport * as React from \"react\";\nimport { toast } from \"sonner\";\n\nimport { Button } from \"@/registry/bases/radix/ui/button\";\nimport {\n  MediaPlayer,\n  MediaPlayerAudio,\n  MediaPlayerControls,\n  MediaPlayerPlay,\n  MediaPlayerSeek,\n  MediaPlayerTime,\n  MediaPlayerTooltip,\n  MediaPlayerVolume,\n} from \"@/registry/bases/radix/ui/media-player\";\nimport { ScrollArea } from \"@/registry/bases/radix/ui/scroll-area\";\n\ninterface Track {\n  id: string;\n  title: string;\n  artist: string;\n  src: string;\n  cover: string;\n}\n\nconst tracks: Track[] = [\n  {\n    id: \"1\",\n    title: \"Medieval: Battle\",\n    artist: \"RandomMind\",\n    src: \"https://opengameart.org/sites/default/files/battle.mp3\",\n    cover: \"https://picsum.photos/seed/battle/200/200\",\n  },\n  {\n    id: \"2\",\n    title: \"City Lights\",\n    artist: \"The Lemming Shepherds\",\n    src: \"https://www.dropbox.com/s/mvvwaw1msplnteq/City%20Lights%20-%20The%20Lemming%20Shepherds.mp3?raw=1\",\n    cover: \"https://picsum.photos/seed/citylights/200/200\",\n  },\n];\n\nexport default function MediaPlayerPlaylistDemo() {\n  const [currentTrackIndex, setCurrentTrackIndex] = React.useState(0);\n  const [isPlaying, setIsPlaying] = React.useState(false);\n  const [isLoading, setIsLoading] = React.useState(false);\n  const audioRef = React.useRef<HTMLAudioElement | null>(null);\n  const shouldPlayAfterLoad = React.useRef(false);\n\n  const onPlay = React.useCallback(() => {\n    setIsPlaying(true);\n  }, []);\n\n  const onPause = React.useCallback(() => {\n    setIsPlaying(false);\n  }, []);\n\n  const onLoadAndPlayTrack = React.useCallback(\n    async (index: number, shouldPlay = true) => {\n      const trackToPlay = tracks[index];\n      if (!trackToPlay) {\n        toast.error(\"Track not found\");\n        return;\n      }\n\n      if (!audioRef.current) return;\n\n      if (!audioRef.current.paused) {\n        audioRef.current.pause();\n      }\n\n      setCurrentTrackIndex(index);\n      setIsLoading(true);\n      shouldPlayAfterLoad.current = shouldPlay;\n\n      audioRef.current.src = trackToPlay.src;\n      audioRef.current.load();\n    },\n    [],\n  );\n\n  const onPlayTrack = React.useCallback(\n    (index: number) => {\n      onLoadAndPlayTrack(index, true);\n    },\n    [onLoadAndPlayTrack],\n  );\n\n  const onPreviousTrack = React.useCallback(() => {\n    const prevIndex = (currentTrackIndex - 1 + tracks.length) % tracks.length;\n    onPlayTrack(prevIndex);\n  }, [currentTrackIndex, onPlayTrack]);\n\n  const onNextTrack = React.useCallback(() => {\n    const nextIndex = (currentTrackIndex + 1) % tracks.length;\n    onPlayTrack(nextIndex);\n  }, [currentTrackIndex, onPlayTrack]);\n\n  const onEnded = React.useCallback(() => {\n    onNextTrack();\n  }, [onNextTrack]);\n\n  const onAudioPlay = React.useCallback(async () => {\n    if (!audioRef.current) return;\n\n    try {\n      await audioRef.current.play();\n      setIsPlaying(true);\n    } catch (error) {\n      toast.error(\n        error instanceof Error ? error.message : \"Failed to play track\",\n      );\n      setIsPlaying(false);\n    }\n  }, []);\n\n  const onTogglePlayPauseTrack = React.useCallback(\n    (index: number) => {\n      if (index === currentTrackIndex) {\n        if (isPlaying) {\n          audioRef.current?.pause();\n        } else {\n          onAudioPlay();\n        }\n      } else {\n        onPlayTrack(index);\n      }\n    },\n    [currentTrackIndex, isPlaying, onAudioPlay, onPlayTrack],\n  );\n\n  const currentTrack = React.useMemo(\n    () => tracks[currentTrackIndex],\n    [currentTrackIndex],\n  );\n\n  React.useEffect(() => {\n    const audioElement = audioRef.current;\n    if (!audioElement) return;\n\n    const onCanPlay = () => {\n      setIsLoading(false);\n      if (shouldPlayAfterLoad.current) {\n        onAudioPlay();\n        shouldPlayAfterLoad.current = false;\n      }\n    };\n\n    const onLoadStart = () => {\n      setIsLoading(true);\n    };\n\n    const onError = () => {\n      setIsLoading(false);\n      setIsPlaying(false);\n      toast.error(\"Failed to load track\");\n    };\n\n    audioElement.addEventListener(\"canplay\", onCanPlay);\n    audioElement.addEventListener(\"loadstart\", onLoadStart);\n    audioElement.addEventListener(\"error\", onError);\n\n    return () => {\n      audioElement.removeEventListener(\"canplay\", onCanPlay);\n      audioElement.removeEventListener(\"loadstart\", onLoadStart);\n      audioElement.removeEventListener(\"error\", onError);\n    };\n  }, [onAudioPlay]);\n\n  React.useEffect(() => {\n    if (\n      audioRef.current &&\n      currentTrack &&\n      audioRef.current.src !== currentTrack.src\n    ) {\n      onLoadAndPlayTrack(currentTrackIndex, false);\n    }\n  }, [currentTrack, currentTrackIndex, onLoadAndPlayTrack]);\n\n  const onKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      const isMediaFocused = event.currentTarget.contains(\n        document.activeElement,\n      );\n\n      if (!isMediaFocused) return;\n\n      switch (event.key.toLowerCase()) {\n        case \"n\":\n          event.preventDefault();\n          onNextTrack();\n          break;\n\n        case \"p\":\n          event.preventDefault();\n          onPreviousTrack();\n          break;\n      }\n    },\n    [onNextTrack, onPreviousTrack],\n  );\n\n  if (!currentTrack) return null;\n\n  return (\n    <MediaPlayer\n      onPlay={onPlay}\n      onPause={onPause}\n      onEnded={onEnded}\n      onKeyDown={onKeyDown}\n      className=\"w-full max-w-2xl overflow-hidden rounded-lg border bg-background shadow-lg\"\n    >\n      <MediaPlayerAudio\n        ref={audioRef}\n        src={currentTrack.src}\n        className=\"sr-only\"\n      />\n      <div className=\"flex w-full flex-col items-center gap-4 md:items-start\">\n        <div className=\"relative w-full overflow-hidden rounded-md rounded-b-none border-b\">\n          <img\n            src={currentTrack.cover}\n            alt={currentTrack.title}\n            className=\"h-40 w-full object-cover\"\n          />\n          <div className=\"absolute inset-0 bg-linear-to-t from-black/60 via-black/20 to-transparent\" />\n          <div className=\"absolute right-0 bottom-0 left-0 p-4\">\n            <h2 className=\"text-2xl font-semibold tracking-tight text-white drop-shadow-lg\">\n              {currentTrack.title}\n            </h2>\n            <p className=\"text-sm text-white/90 drop-shadow-md\">\n              {currentTrack.artist}\n            </p>\n          </div>\n        </div>\n        <div className=\"w-full\">\n          <div className=\"flex items-center border-b border-border px-4 pb-4\">\n            <div className=\"flex flex-1 items-center gap-2\">\n              <h3 className=\"text-lg font-medium tracking-tight\">Playlist</h3>\n              <ListMusicIcon className=\"size-4\" />\n            </div>\n            <span className=\"text-sm text-muted-foreground\">{`${currentTrackIndex + 1} / ${tracks.length}`}</span>\n          </div>\n          <ScrollArea className=\"max-h-[200px]\">\n            {tracks.map((track, index) => (\n              <Button\n                key={track.id}\n                variant=\"ghost\"\n                className={cn(\n                  \"h-auto w-full rounded-none px-4 py-3 text-left\",\n                  index === currentTrackIndex && \"bg-accent\",\n                )}\n                onClick={() => onTogglePlayPauseTrack(index)}\n                disabled={isLoading}\n              >\n                <img\n                  src={track.cover}\n                  alt={track.title}\n                  className=\"aspect-square size-9 rounded object-cover\"\n                />\n                <div className=\"flex flex-1 flex-col\">\n                  <span\n                    className={cn(\n                      \"leading-tight font-medium\",\n                      index === currentTrackIndex && \"text-primary\",\n                    )}\n                  >\n                    {track.title}\n                  </span>\n                  <span className=\"text-sm text-muted-foreground\">\n                    {track.artist}\n                  </span>\n                </div>\n                {index === currentTrackIndex && isLoading ? (\n                  <Loader2Icon className=\"size-6 animate-spin text-primary\" />\n                ) : index === currentTrackIndex && isPlaying ? (\n                  <PauseCircleIcon className=\"size-6 text-primary\" />\n                ) : index === currentTrackIndex && !isPlaying ? (\n                  <PlayCircleIcon className=\"size-6 text-muted-foreground\" />\n                ) : (\n                  <PlayCircleIcon className=\"size-6 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100\" />\n                )}\n              </Button>\n            ))}\n          </ScrollArea>\n        </div>\n        <MediaPlayerControls className=\"relative flex w-full flex-col gap-2.5\">\n          <MediaPlayerSeek />\n          <div className=\"flex w-full items-center justify-center gap-2\">\n            <MediaPlayerTooltip tooltip=\"Previous track\" shortcut=\"B\">\n              <Button\n                aria-label=\"Previous track\"\n                variant=\"ghost\"\n                size=\"icon\"\n                className=\"size-8\"\n                onClick={onPreviousTrack}\n                disabled={isLoading}\n              >\n                <SkipBackIcon />\n              </Button>\n            </MediaPlayerTooltip>\n            <MediaPlayerPlay />\n            <MediaPlayerTooltip tooltip=\"Next track\" shortcut=\"N\">\n              <Button\n                aria-label=\"Next track\"\n                variant=\"ghost\"\n                size=\"icon\"\n                className=\"size-8\"\n                onClick={onNextTrack}\n                disabled={isLoading}\n              >\n                <SkipForwardIcon />\n              </Button>\n            </MediaPlayerTooltip>\n            <MediaPlayerTime variant=\"progress\" />\n            <MediaPlayerVolume className=\"ml-auto\" />\n          </div>\n        </MediaPlayerControls>\n      </div>\n    </MediaPlayer>\n  );\n}\n",
      "target": ""
    }
  ]
}