{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "audio-player",
  "title": "Audio Player",
  "author": "Rohan Gupta (@winoffrg)",
  "description": "Compact audio player with playlist support",
  "dependencies": [
    "@phosphor-icons/react",
    "@radix-ui/react-slot",
    "motion",
    "zustand",
    "shaka-player@^5.2.7"
  ],
  "registryDependencies": [
    "https://bazza.dev/r/hit-area",
    "https://limeplay.winoffrg.dev/r/media-provider.json",
    "https://limeplay.winoffrg.dev/r/media.json",
    "https://limeplay.winoffrg.dev/r/root-container.json",
    "https://limeplay.winoffrg.dev/r/timeline-control.json",
    "https://limeplay.winoffrg.dev/r/timeline-labels.json",
    "https://limeplay.winoffrg.dev/r/mute-control.json",
    "https://limeplay.winoffrg.dev/r/volume-control.json",
    "https://limeplay.winoffrg.dev/r/playback-control.json",
    "dropdown-menu",
    "toggle-group",
    "https://limeplay.winoffrg.dev/r/use-player.json",
    "https://limeplay.winoffrg.dev/r/use-playback.json",
    "https://limeplay.winoffrg.dev/r/use-volume.json",
    "https://limeplay.winoffrg.dev/r/use-timeline.json",
    "https://limeplay.winoffrg.dev/r/use-captions.json",
    "https://limeplay.winoffrg.dev/r/use-playlist.json",
    "https://limeplay.winoffrg.dev/r/use-asset.json",
    "https://limeplay.winoffrg.dev/r/use-media.json",
    "https://limeplay.winoffrg.dev/r/use-playback-source.json",
    "https://limeplay.winoffrg.dev/r/use-media-session.json",
    "https://limeplay.winoffrg.dev/r/limeplay-logo.json",
    "https://limeplay.winoffrg.dev/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/default/blocks/audio-player/player.tsx",
      "content": "\"use client\"\n\nimport React from \"react\"\n\nimport type { TAsset } from \"@/registry/default/hooks/use-asset\"\nimport type { PlaybackSourceControllerProps } from \"@/registry/default/hooks/use-playback-source\"\n\nimport { cn } from \"@/lib/utils\"\nimport { AudioSourceProvider } from \"@/registry/default/blocks/audio-player/components/audio-source\"\nimport { PlayerControls } from \"@/registry/default/blocks/audio-player/components/controls\"\nimport { TimelineControl } from \"@/registry/default/blocks/audio-player/components/fixed-timeline-control\"\nimport { AudioMediaSessionController } from \"@/registry/default/blocks/audio-player/components/media-session-controller\"\nimport { MediaProvider } from \"@/registry/default/blocks/audio-player/lib/media-kit\"\nimport { Media } from \"@/registry/default/ui/media\"\nimport { RootContainer } from \"@/registry/default/ui/root-container\"\n\nimport \"./styles.css\"\n\nexport interface AudioPlayerAsset extends TAsset {\n  albumName?: string\n  artistName?: string\n  artwork?: {\n    templateUrl?: string\n    url?: string\n  }\n  description?: string\n  duration?: number\n  features?: string[]\n  genre?: string\n  group?: string\n  images?: {\n    backdrop?: string\n    poster?: string\n  }\n  name?: string\n  playbackUrls?: PlaybackUrls\n  poster?: string\n  releaseYear?: number | string\n  subtitle?: string\n  title?: string\n  year?: number | string\n}\n\nexport interface AudioPlayerProps extends PlaybackSourceControllerProps<AudioPlayerAsset> {\n  children?: React.ReactNode\n  className?: string\n  debug?: boolean\n  /**\n   * Props to pass to the underlying audio element.\n   */\n  mediaProps?: Omit<React.AudioHTMLAttributes<HTMLAudioElement>, \"as\" | \"src\">\n  /**\n   * @default dark\n   */\n  theme?: \"dark\" | \"light\"\n}\n\nexport interface PlaybackUrls {\n  primary: string\n  secondary?: string\n}\n\nexport const AudioPlayer = React.forwardRef<HTMLDivElement, AudioPlayerProps>(\n  function AudioPlayer(\n    {\n      autoLoad,\n      children,\n      className,\n      debug,\n      initialIndex,\n      loading,\n      mediaProps,\n      source,\n      sourceKey,\n      theme = \"dark\",\n    },\n    ref\n  ) {\n    const { className: mediaClassName, ...safeMediaProps } = mediaProps ?? {}\n\n    return (\n      <MediaProvider debug={debug}>\n        <AudioSourceProvider\n          autoLoad={autoLoad}\n          initialIndex={initialIndex}\n          loading={loading}\n          source={source}\n          sourceKey={sourceKey}\n        >\n          <AudioMediaSessionController />\n          <RootContainer\n            aria-label=\"Audio player\"\n            aspectRatio={false}\n            className={cn(\n              \"limeplay relative z-50 h-18 w-full border-t border-border bg-background\",\n              theme === \"dark\" && \"dark\",\n              theme === \"light\" && \"light\",\n              className\n            )}\n            ref={ref}\n          >\n            <Media\n              {...(safeMediaProps as React.ComponentPropsWithoutRef<\n                typeof Media\n              >)}\n              as=\"audio\"\n              className={mediaClassName}\n            />\n            <TimelineControl />\n            <PlayerControls />\n          </RootContainer>\n          {children}\n        </AudioSourceProvider>\n      </MediaProvider>\n    )\n  }\n)\n\nAudioPlayer.displayName = \"AudioPlayer\"\n",
      "type": "registry:component",
      "target": "components/audio-player/player.tsx"
    },
    {
      "path": "registry/default/blocks/audio-player/lib/media-kit.ts",
      "content": "\"use client\"\n\nimport { assetFeature } from \"@/registry/default/hooks/use-asset\"\nimport { captionsFeature } from \"@/registry/default/hooks/use-captions\"\nimport { mediaFeature } from \"@/registry/default/hooks/use-media\"\nimport { playbackFeature } from \"@/registry/default/hooks/use-playback\"\nimport { playerFeature } from \"@/registry/default/hooks/use-player\"\nimport { playlistFeature } from \"@/registry/default/hooks/use-playlist\"\nimport { timelineFeature } from \"@/registry/default/hooks/use-timeline\"\nimport { volumeFeature } from \"@/registry/default/hooks/use-volume\"\nimport { createMediaKit } from \"@/registry/default/ui/media-provider\"\n\nexport const media = createMediaKit({\n  features: [\n    mediaFeature(),\n    playerFeature(),\n    playbackFeature(),\n    playlistFeature(),\n    volumeFeature(),\n    timelineFeature(),\n    captionsFeature(),\n    assetFeature(),\n  ] as const,\n})\n\nexport const MediaProvider = media.MediaProvider\nexport const useMediaApi = media.useMediaApi\n",
      "type": "registry:lib",
      "target": "components/audio-player/lib/media-kit.ts"
    },
    {
      "path": "registry/default/blocks/audio-player/components/audio-source.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport type { AudioPlayerAsset } from \"@/registry/default/blocks/audio-player/player\"\nimport type {\n  PlayerSource,\n  UseAssetOptions,\n} from \"@/registry/default/hooks/use-asset\"\n\nimport { AssetRecoveryAction } from \"@/registry/default/hooks/use-asset\"\nimport { PlaybackSourceController } from \"@/registry/default/hooks/use-playback-source\"\n\nexport interface AudioAssetDisplayMetadata {\n  poster?: string\n  subtitle?: string\n  title: string\n}\n\nexport interface AudioSourceProviderProps {\n  autoLoad?: boolean\n  children?: React.ReactNode\n  initialIndex?: number\n  loading?: UseAssetOptions<AudioPlayerAsset>\n  source?: PlayerSource<AudioPlayerAsset>\n  sourceKey?: string\n}\n\ninterface RawPlaybackResponse {\n  expires_at: string\n  url: string\n}\n\nexport function AudioSourceProvider({\n  autoLoad = true,\n  children,\n  initialIndex,\n  loading,\n  source,\n  sourceKey,\n}: AudioSourceProviderProps) {\n  const resolvedLoading = React.useMemo<UseAssetOptions<AudioPlayerAsset>>(\n    () => ({\n      ...loading,\n      getAssetId: (asset, context) =>\n        loading?.getAssetId?.(asset, context) ??\n        asset.id ??\n        asset.src ??\n        asset.playbackUrls?.primary,\n      recover: {\n        loadError: (_asset: AudioPlayerAsset, _error: unknown, { hasNext }) => {\n          return hasNext ? AssetRecoveryAction.Skip : AssetRecoveryAction.Stop\n        },\n        playbackError: async (\n          _asset: AudioPlayerAsset,\n          error: Error,\n          { currentTime }: { currentTime: number }\n        ) => {\n          if (isNetworkError(error)) {\n            return {\n              action: AssetRecoveryAction.Reload,\n              startTime: currentTime,\n            }\n          }\n\n          return { action: AssetRecoveryAction.Skip }\n        },\n        ...loading?.recover,\n      },\n      resolveSource: async (context) => {\n        if (loading?.resolveSource) return loading.resolveSource(context)\n\n        const { asset, signal } = context\n        if (asset.src) {\n          return {\n            config: asset.config,\n            src: asset.src,\n          }\n        }\n\n        if (!asset.playbackUrls?.primary) {\n          throw new Error(\n            \"AudioPlayerAsset requires src or playbackUrls.primary\"\n          )\n        }\n\n        const src = await fetchPlaybackUrl(asset.playbackUrls.primary, signal)\n        if (signal.aborted) {\n          throw new DOMException(\n            \"Playback source request aborted\",\n            \"AbortError\"\n          )\n        }\n\n        return {\n          config: asset.config,\n          src,\n        }\n      },\n    }),\n    [loading]\n  )\n\n  return (\n    <>\n      <PlaybackSourceController\n        autoLoad={autoLoad}\n        initialIndex={initialIndex}\n        loading={resolvedLoading}\n        source={source}\n        sourceKey={sourceKey}\n      />\n      {children}\n    </>\n  )\n}\n\nexport function getAudioAssetMetadata(\n  asset: AudioPlayerAsset | null | undefined,\n  fallbackTitle = \"Unknown Title\"\n): AudioAssetDisplayMetadata {\n  if (!asset) {\n    return {\n      title: fallbackTitle,\n    }\n  }\n\n  const releaseYear = asset.releaseYear ?? asset.year\n  const artistAlbum = joinDisplayParts([asset.artistName, asset.albumName])\n  const genreYear = joinDisplayParts([asset.genre, releaseYear])\n  const streamLabel = getStreamLabel(asset)\n\n  return {\n    poster:\n      firstNonEmpty(\n        asset.poster,\n        getArtworkUrl(asset.artwork),\n        asset.images?.poster,\n        asset.images?.backdrop\n      ) ?? undefined,\n    subtitle:\n      firstNonEmpty(\n        artistAlbum,\n        genreYear,\n        asset.subtitle,\n        streamLabel,\n        asset.description\n      ) ?? undefined,\n    title:\n      firstNonEmpty(\n        asset.title,\n        asset.name,\n        asset.albumName,\n        asset.description\n      ) ?? fallbackTitle,\n  }\n}\n\nasync function fetchPlaybackUrl(\n  playbackUrl: string,\n  signal?: AbortSignal\n): Promise<string> {\n  const url = new URL(playbackUrl)\n  url.searchParams.set(\"raw\", \"true\")\n\n  const response = await fetch(url.toString(), { signal })\n  if (!response.ok) {\n    throw new Error(`Failed to fetch playback URL: ${response.statusText}`)\n  }\n\n  const data: RawPlaybackResponse = await response.json()\n  return data.url\n}\n\nfunction firstNonEmpty(\n  ...values: (null | number | string | undefined)[]\n): string | undefined {\n  for (const value of values) {\n    if (typeof value === \"number\" && Number.isFinite(value)) {\n      return String(value)\n    }\n\n    if (typeof value !== \"string\") continue\n\n    const trimmed = value.trim()\n    if (trimmed) return trimmed\n  }\n\n  return undefined\n}\n\nfunction getArtworkUrl(\n  artwork: AudioPlayerAsset[\"artwork\"]\n): string | undefined {\n  const url = firstNonEmpty(artwork?.url, artwork?.templateUrl)\n  if (!url) return undefined\n\n  return url\n    .replaceAll(\"{w}\", \"80\")\n    .replaceAll(\"{h}\", \"80\")\n    .replaceAll(\"{f}\", \"jpg\")\n}\n\nfunction getStreamLabel(asset: AudioPlayerAsset): string | undefined {\n  return joinDisplayParts([asset.group, ...(asset.features ?? [])])\n}\n\nfunction isNetworkError(error: unknown) {\n  return (\n    error &&\n    typeof error === \"object\" &&\n    \"category\" in error &&\n    (error as { category: number }).category === 1\n  )\n}\n\nfunction joinDisplayParts(\n  values: (null | number | string | undefined)[]\n): string | undefined {\n  const parts = values\n    .map((value) => firstNonEmpty(value))\n    .filter((value): value is string => Boolean(value))\n\n  return parts.length > 0 ? parts.join(\" • \") : undefined\n}\n",
      "type": "registry:component",
      "target": "components/audio-player/components/audio-source.tsx"
    },
    {
      "path": "registry/default/blocks/audio-player/components/controls.tsx",
      "content": "\"use client\"\n\nimport { ActionControls } from \"@/registry/default/blocks/audio-player/components/action-controls\"\nimport { TimeLabels } from \"@/registry/default/blocks/audio-player/components/fixed-timeline-control\"\nimport { PlaybackControls } from \"@/registry/default/blocks/audio-player/components/playback-controls\"\nimport {\n  RepeatControl,\n  ShuffleControl,\n} from \"@/registry/default/blocks/audio-player/components/playback-mode-controls\"\nimport { Playlist } from \"@/registry/default/blocks/audio-player/components/playlist\"\nimport { TrackInfo } from \"@/registry/default/blocks/audio-player/components/track-info\"\nimport { VolumeControl } from \"@/registry/default/blocks/audio-player/components/volume-group-control\"\nimport {\n  AssetSourceType,\n  useAssetStore,\n} from \"@/registry/default/hooks/use-asset\"\nimport { usePlaylistStore } from \"@/registry/default/hooks/use-playlist\"\n\nexport function PlayerControls() {\n  const sourceType = useAssetStore((state) => state.sourceType)\n  const queueLength = usePlaylistStore((state) => state.queue.length)\n  const isPlaylistMode =\n    sourceType === AssetSourceType.Playlist || queueLength > 1\n\n  return (\n    <div className=\"grid h-full grid-cols-3 items-center px-4\">\n      <div className=\"flex items-center justify-start gap-4\">\n        <PlaybackControls showNavigation={isPlaylistMode} />\n        <TimeLabels />\n      </div>\n      <div className=\"flex items-center justify-center gap-2\">\n        <TrackInfo />\n        <ActionControls />\n      </div>\n      <div className=\"flex items-center justify-end gap-2\">\n        <VolumeControl />\n        <RepeatControl variant={isPlaylistMode ? \"playlist\" : \"asset\"} />\n        {isPlaylistMode ? (\n          <>\n            <ShuffleControl />\n            <Playlist />\n          </>\n        ) : null}\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/audio-player/components/controls.tsx"
    },
    {
      "path": "registry/default/blocks/audio-player/components/playback-controls.tsx",
      "content": "\"use client\"\n\nimport { CircleNotchIcon } from \"@phosphor-icons/react\"\nimport { AnimatePresence, motion } from \"motion/react\"\nimport { useEffect, useRef, useState } from \"react\"\n\nimport { Button } from \"@/registry/default/blocks/audio-player/components/button\"\nimport {\n  NextIcon,\n  PauseIcon,\n  PlayIcon,\n  PreviousIcon,\n} from \"@/registry/default/blocks/audio-player/components/icons\"\nimport { usePlaybackStore } from \"@/registry/default/hooks/use-playback\"\nimport { RECOMMENDED_PLAYER_BUFFERING_THROTTLE_MS } from \"@/registry/default/hooks/use-player\"\nimport { usePlaylist } from \"@/registry/default/hooks/use-playlist\"\nimport { PlaybackControl } from \"@/registry/default/ui/playback-control\"\n\nexport function PlaybackControls({\n  showNavigation = true,\n}: {\n  showNavigation?: boolean\n}) {\n  const status = usePlaybackStore((state) => state.status)\n  const { hasNext, hasPrevious, next, previous } = usePlaylist()\n\n  const rawBuffering = status === \"buffering\" || status === \"loading\"\n  const { isBuffering, isPlaying } = useStablePlaybackState(\n    rawBuffering,\n    status,\n    RECOMMENDED_PLAYER_BUFFERING_THROTTLE_MS\n  )\n\n  return (\n    <div className=\"flex items-center gap-1\">\n      {showNavigation ? (\n        <Button\n          aria-label=\"Previous track\"\n          disabled={!hasPrevious}\n          onClick={previous}\n        >\n          <PreviousIcon aria-hidden=\"true\" />\n        </Button>\n      ) : null}\n\n      <PlaybackControl asChild>\n        <Button aria-label={isPlaying ? \"Pause\" : \"Play\"} size=\"xl\">\n          <AnimatedIcon\n            id={isBuffering ? \"loading\" : isPlaying ? \"pause\" : \"play\"}\n          >\n            {isBuffering ? (\n              <CircleNotchIcon\n                aria-hidden=\"true\"\n                className=\"animate-spin\"\n                size={6}\n              />\n            ) : isPlaying ? (\n              <PauseIcon aria-hidden=\"true\" />\n            ) : (\n              <PlayIcon aria-hidden=\"true\" className=\"relative left-px\" />\n            )}\n          </AnimatedIcon>\n        </Button>\n      </PlaybackControl>\n\n      {showNavigation ? (\n        <Button aria-label=\"Next track\" disabled={!hasNext} onClick={next}>\n          <NextIcon aria-hidden=\"true\" />\n        </Button>\n      ) : null}\n    </div>\n  )\n}\n\nfunction AnimatedIcon({\n  children,\n  id,\n}: {\n  children: React.ReactNode\n  id: string\n}) {\n  return (\n    <AnimatePresence initial={false} mode=\"popLayout\">\n      <motion.span\n        animate={{ filter: \"blur(0px)\", opacity: 1, scale: 1 }}\n        exit={{ filter: \"blur(4px)\", opacity: 0, scale: 0.25 }}\n        initial={{ filter: \"blur(4px)\", opacity: 0, scale: 0.25 }}\n        key={id}\n        transition={{ bounce: 0, duration: 0.3, type: \"spring\" }}\n      >\n        {children}\n      </motion.span>\n    </AnimatePresence>\n  )\n}\n\nfunction useStablePlaybackState(\n  isBuffering: boolean,\n  status: string,\n  delayMs: number\n) {\n  const [showSpinner, setShowSpinner] = useState(false)\n  const timeoutRef = useRef<null | ReturnType<typeof setTimeout>>(null)\n  const lastIntentRef = useRef<string>(\"paused\")\n\n  useEffect(() => {\n    if (status === \"playing\" || status === \"paused\") {\n      lastIntentRef.current = status\n    }\n  }, [status])\n\n  useEffect(() => {\n    if (isBuffering && lastIntentRef.current === \"playing\") {\n      timeoutRef.current = setTimeout(() => setShowSpinner(true), delayMs)\n    } else {\n      if (timeoutRef.current) {\n        clearTimeout(timeoutRef.current)\n        timeoutRef.current = null\n      }\n      setShowSpinner(false)\n    }\n\n    return () => {\n      if (timeoutRef.current) {\n        clearTimeout(timeoutRef.current)\n        timeoutRef.current = null\n      }\n    }\n    // DEV: The spinner from lastIntentRef.current, but this effect never reruns when status changes while isBuffering stays true. A pause during buffering can still show the delayed loading spinner because the old timeout is left in place.\n  }, [isBuffering, delayMs, status])\n\n  // During transient buffering, hold the last intent as the visual state\n  const stableIsPlaying = isBuffering\n    ? lastIntentRef.current === \"playing\"\n    : status === \"playing\"\n\n  return { isBuffering: showSpinner, isPlaying: stableIsPlaying }\n}\n",
      "type": "registry:component",
      "target": "components/audio-player/components/playback-controls.tsx"
    },
    {
      "path": "registry/default/blocks/audio-player/components/action-controls.tsx",
      "content": "\"use client\"\n\nimport { DotsThreeVerticalIcon } from \"@phosphor-icons/react\"\nimport { AnimatePresence, motion } from \"motion/react\"\nimport * as React from \"react\"\n\nimport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\"\nimport { Button } from \"@/registry/default/blocks/audio-player/components/button\"\nimport {\n  DislikeIcon,\n  DislikeSelectedIcon,\n  LikeIcon,\n  LikeSelectedIcon,\n} from \"@/registry/default/blocks/audio-player/components/icons\"\n\nexport function ActionControls() {\n  const [value, setValue] = React.useState<string>(\"\")\n  const toggleGroupProps = {\n    onValueChange: (next: string | string[]) => {\n      setValue(Array.isArray(next) ? (next[0] ?? \"\") : next)\n    },\n    spacing: 2,\n    type: \"single\" as const,\n    value,\n  }\n\n  return (\n    <div className=\"flex items-center gap-1\">\n      <ToggleGroup\n        {...(toggleGroupProps as unknown as React.ComponentProps<\n          typeof ToggleGroup\n        >)}\n      >\n        <ToggleGroupItem\n          aria-label=\"Dislike\"\n          className={`\n            cursor-pointer rounded-full p-1.5\n            hover:bg-muted\n            data-[state=on]:bg-transparent\n          `}\n          value=\"dislike\"\n        >\n          <AnimatePresence initial={false} mode=\"popLayout\">\n            <motion.span\n              animate={{ filter: \"blur(0px)\", opacity: 1, scale: 1 }}\n              exit={{ filter: \"blur(4px)\", opacity: 0, scale: 0.25 }}\n              initial={{ filter: \"blur(4px)\", opacity: 0, scale: 0.25 }}\n              key={value === \"dislike\" ? \"dislike-on\" : \"dislike-off\"}\n              transition={{ bounce: 0, duration: 0.3, type: \"spring\" }}\n            >\n              <Button asChild size=\"sm\">\n                {value === \"dislike\" ? (\n                  <DislikeSelectedIcon aria-hidden=\"true\" />\n                ) : (\n                  <DislikeIcon aria-hidden=\"true\" />\n                )}\n              </Button>\n            </motion.span>\n          </AnimatePresence>\n        </ToggleGroupItem>\n\n        <ToggleGroupItem\n          aria-label=\"Like\"\n          className={`\n            cursor-pointer rounded-full p-1.5\n            hover:bg-muted\n            data-[state=on]:bg-transparent\n          `}\n          value=\"like\"\n        >\n          <AnimatePresence initial={false} mode=\"popLayout\">\n            <motion.span\n              animate={{ filter: \"blur(0px)\", opacity: 1, scale: 1 }}\n              exit={{ filter: \"blur(4px)\", opacity: 0, scale: 0.25 }}\n              initial={{ filter: \"blur(4px)\", opacity: 0, scale: 0.25 }}\n              key={value === \"like\" ? \"like-on\" : \"like-off\"}\n              transition={{ bounce: 0, duration: 0.3, type: \"spring\" }}\n            >\n              <Button asChild size=\"sm\">\n                {value === \"like\" ? (\n                  <LikeSelectedIcon aria-hidden=\"true\" />\n                ) : (\n                  <LikeIcon aria-hidden=\"true\" />\n                )}\n              </Button>\n            </motion.span>\n          </AnimatePresence>\n        </ToggleGroupItem>\n      </ToggleGroup>\n\n      <Button aria-label=\"More actions\">\n        <DotsThreeVerticalIcon aria-hidden=\"true\" weight=\"bold\" />\n      </Button>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/audio-player/components/action-controls.tsx"
    },
    {
      "path": "registry/default/blocks/audio-player/components/playback-mode-controls.tsx",
      "content": "\"use client\"\n\nimport { AnimatePresence, motion } from \"motion/react\"\nimport { useEffect, useMemo } from \"react\"\n\nimport type { RepeatMode } from \"@/registry/default/hooks/use-playlist\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/blocks/audio-player/components/button\"\nimport {\n  RepeatAllIcon,\n  RepeatIcon,\n  RepeatOneIcon,\n  ShuffleOffIcon,\n  ShuffleOneIcon,\n} from \"@/registry/default/blocks/audio-player/components/icons\"\nimport {\n  usePlaylist,\n  usePlaylistStore,\n} from \"@/registry/default/hooks/use-playlist\"\n\ntype RepeatControlVariant = \"asset\" | \"playlist\"\n\nconst REPEAT_MODES: Record<RepeatControlVariant, RepeatMode[]> = {\n  asset: [\"off\", \"one\"],\n  playlist: [\"off\", \"all\", \"one\"],\n}\n\nexport function RepeatControl({\n  variant = \"playlist\",\n}: {\n  variant?: RepeatControlVariant\n}) {\n  const setRepeatMode = usePlaylistStore((state) => state.setRepeatMode)\n  const repeatMode = usePlaylistStore((state) => state.repeatMode)\n  const modes = REPEAT_MODES[variant]\n  const normalizedRepeatMode = useMemo(\n    () => normalizeRepeatMode(repeatMode, modes),\n    [modes, repeatMode]\n  )\n  const isActive = normalizedRepeatMode !== \"off\"\n\n  useEffect(() => {\n    if (repeatMode === normalizedRepeatMode) return\n    setRepeatMode(normalizedRepeatMode)\n  }, [normalizedRepeatMode, repeatMode, setRepeatMode])\n\n  const handleCycleRepeatMode = () => {\n    const modeIndex = modes.indexOf(normalizedRepeatMode)\n    const nextMode = modes[(modeIndex + 1) % modes.length] ?? \"off\"\n    setRepeatMode(nextMode)\n  }\n\n  return (\n    <Button\n      aria-label={`Repeat: ${normalizedRepeatMode}`}\n      aria-pressed={isActive}\n      className={cn({\n        \"text-muted-foreground\": !isActive,\n      })}\n      onClick={handleCycleRepeatMode}\n    >\n      <AnimatePresence initial={false} mode=\"popLayout\">\n        <motion.span\n          animate={{ filter: \"blur(0px)\", opacity: 1, scale: 1 }}\n          className=\"flex items-center justify-center\"\n          exit={{ filter: \"blur(4px)\", opacity: 0, scale: 0.25 }}\n          initial={{ filter: \"blur(4px)\", opacity: 0, scale: 0.25 }}\n          key={normalizedRepeatMode}\n          transition={{ bounce: 0, duration: 0.3, type: \"spring\" }}\n        >\n          {normalizedRepeatMode === \"one\" ? (\n            <RepeatOneIcon aria-hidden=\"true\" />\n          ) : normalizedRepeatMode === \"off\" ? (\n            <RepeatIcon aria-hidden=\"true\" />\n          ) : (\n            <RepeatAllIcon aria-hidden=\"true\" />\n          )}\n        </motion.span>\n      </AnimatePresence>\n    </Button>\n  )\n}\n\nexport function ShuffleControl() {\n  const { toggleShuffle } = usePlaylist()\n  const shuffle = usePlaylistStore((state) => state.shuffle)\n\n  const handleToggleShuffle = () => {\n    toggleShuffle()\n  }\n\n  return (\n    <Button\n      aria-label={shuffle ? \"Shuffle on\" : \"Shuffle off\"}\n      aria-pressed={shuffle}\n      className={cn({\n        \"text-muted-foreground\": !shuffle,\n      })}\n      onClick={handleToggleShuffle}\n    >\n      <AnimatePresence initial={false} mode=\"popLayout\">\n        <motion.span\n          animate={{ filter: \"blur(0px)\", opacity: 1, scale: 1 }}\n          className=\"flex items-center justify-center\"\n          exit={{ filter: \"blur(4px)\", opacity: 0, scale: 0.25 }}\n          initial={{ filter: \"blur(4px)\", opacity: 0, scale: 0.25 }}\n          key={shuffle ? \"on\" : \"off\"}\n          transition={{ bounce: 0, duration: 0.3, type: \"spring\" }}\n        >\n          {shuffle ? (\n            <ShuffleOneIcon aria-hidden=\"true\" />\n          ) : (\n            <ShuffleOffIcon aria-hidden=\"true\" />\n          )}\n        </motion.span>\n      </AnimatePresence>\n    </Button>\n  )\n}\n\nfunction normalizeRepeatMode(\n  mode: RepeatMode,\n  modes: RepeatMode[]\n): RepeatMode {\n  if (modes.includes(mode)) return mode\n  if (mode === \"all\" && modes.includes(\"one\")) return \"one\"\n  return modes[0] ?? \"off\"\n}\n",
      "type": "registry:component",
      "target": "components/audio-player/components/playback-mode-controls.tsx"
    },
    {
      "path": "registry/default/blocks/audio-player/components/volume-group-control.tsx",
      "content": "\"use client\"\n\nimport { AnimatePresence, motion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/blocks/audio-player/components/button\"\nimport {\n  VolumeFullIcon,\n  VolumeMutedIcon,\n} from \"@/registry/default/blocks/audio-player/components/icons\"\nimport { useVolumeStore } from \"@/registry/default/hooks/use-volume\"\nimport { MuteControl } from \"@/registry/default/ui/mute-control\"\nimport * as VolumeSlider from \"@/registry/default/ui/volume-control\"\n\nexport function VolumeControl() {\n  const muted = useVolumeStore((state) => state.muted)\n  const volume = useVolumeStore((state) => state.level)\n\n  const isMuted = muted || volume === 0\n\n  return (\n    <div className=\"group flex items-center gap-3\">\n      <VolumeSlider.Root\n        className={cn(\n          \"relative flex h-3 w-0 cursor-pointer touch-none items-center select-none\",\n          `\n            transition-[width] duration-200 ease-out\n            group-hover:w-20\n            focus-within:w-20\n          `\n        )}\n      >\n        <VolumeSlider.Track className=\"relative h-0.75 w-full overflow-hidden rounded-sm bg-secondary\">\n          <VolumeSlider.Progress className=\"absolute h-full bg-foreground\" />\n        </VolumeSlider.Track>\n        <VolumeSlider.Thumb\n          className={cn(\n            \"absolute size-3 rounded-full bg-foreground opacity-0 transition-opacity\",\n            `\n              group-hover:opacity-100\n              focus-within:opacity-100\n            `\n          )}\n        />\n      </VolumeSlider.Root>\n      <MuteControl asChild>\n        <Button\n          aria-label={muted ? \"Unmute\" : \"Mute\"}\n          className=\"relative text-muted-foreground\"\n          title={muted ? \"Unmute\" : \"Mute\"}\n        >\n          <AnimatePresence initial={false} mode=\"popLayout\">\n            <motion.span\n              animate={{ filter: \"blur(0px)\", opacity: 1, scale: 1 }}\n              className=\"flex items-center justify-center\"\n              exit={{ filter: \"blur(4px)\", opacity: 0, scale: 0.25 }}\n              initial={{ filter: \"blur(4px)\", opacity: 0, scale: 0.25 }}\n              key={isMuted ? \"muted\" : \"unmuted\"}\n              transition={{ bounce: 0, duration: 0.3, type: \"spring\" }}\n            >\n              {isMuted ? (\n                <VolumeMutedIcon aria-hidden=\"true\" />\n              ) : (\n                <VolumeFullIcon aria-hidden=\"true\" />\n              )}\n            </motion.span>\n          </AnimatePresence>\n        </Button>\n      </MuteControl>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/audio-player/components/volume-group-control.tsx"
    },
    {
      "path": "registry/default/blocks/audio-player/components/fixed-timeline-control.tsx",
      "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport * as TimelineSlider from \"@/registry/default/ui/timeline-control\"\nimport {\n  Duration,\n  Elapsed,\n  HoverTime,\n} from \"@/registry/default/ui/timeline-labels\"\n\nexport function TimeLabels() {\n  return (\n    <div className=\"flex items-center gap-1 text-xs text-muted-foreground\">\n      <Elapsed />\n      <span>/</span>\n      <Duration />\n    </div>\n  )\n}\n\nexport function TimelineControl() {\n  return (\n    <div className=\"group/timeline absolute -top-2 flex h-4 w-full items-center pr-2\">\n      <TimelineSlider.Root\n        className={cn(\n          `\n            flex w-full cursor-pointer touch-none items-center transition-[height] duration-150 select-none\n            group-hover/timeline:data-[orientation=horizontal]:h-1\n          `,\n          \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\",\n          \"hit-area-x-0 hit-area-y-[12px] hit-area\"\n        )}\n      >\n        <TimelineSlider.Track className={`overflow-hidden bg-[#383838]`}>\n          <TimelineSlider.Buffered\n            className=\"absolute h-full bg-[#4C4C4C]\"\n            variant=\"combined\"\n          />\n          <TimelineSlider.Progress className=\"absolute h-full bg-linear-to-r from-rose-600 from-80% to-pink-500\" />\n        </TimelineSlider.Track>\n\n        <TimelineSlider.Thumb\n          className={cn(\n            \"absolute top-1/2 size-0 -translate-y-1/2 rounded-full bg-rose-600!\",\n            \"transition-[height,width] duration-150 ease-in-out\",\n            \"group-hover/timeline:size-4\"\n          )}\n        />\n\n        <TimelineSlider.Thumb\n          className={`\n            pointer-events-none top-auto! bottom-2 flex h-auto w-fit rounded-sm bg-background/90! px-2 py-1 text-xs whitespace-nowrap text-foreground\n            opacity-0 transition-opacity duration-200\n            group-hover/timeline:opacity-100\n          `}\n          showWithHover\n          style={\n            {\n              \"--lp-timeline-thumb-left\":\n                \"clamp(calc((8ch + 1rem) / 2), var(--lp-timeline-thumb-position), calc(100% - calc((8ch + 1rem) / 2)))\",\n            } as unknown as React.CSSProperties\n          }\n        >\n          <HoverTime />\n        </TimelineSlider.Thumb>\n      </TimelineSlider.Root>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/audio-player/components/fixed-timeline-control.tsx"
    },
    {
      "path": "registry/default/blocks/audio-player/components/media-session-controller.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport type { AudioPlayerAsset } from \"@/registry/default/blocks/audio-player/player\"\n\nimport { getAudioAssetMetadata } from \"@/registry/default/blocks/audio-player/components/audio-source\"\nimport { useMediaApi } from \"@/registry/default/blocks/audio-player/lib/media-kit\"\nimport { useAsset, useAssetStore } from \"@/registry/default/hooks/use-asset\"\nimport {\n  canMoveToNextMediaSessionTrack,\n  canMoveToPreviousMediaSessionTrack,\n  canStartMediaSessionPlayback,\n  getMediaSessionCurrentTime,\n  getMediaSessionMetadataValue,\n  getMediaSessionPlaybackState,\n  getMediaSessionPositionState,\n  isMediaSessionPlaylistSource,\n  useMediaSessionActionHandlers,\n  useMediaSessionSync,\n} from \"@/registry/default/hooks/use-media-session\"\nimport { usePlaybackStore } from \"@/registry/default/hooks/use-playback\"\nimport { useTimelineStore } from \"@/registry/default/hooks/use-timeline\"\n\nexport function AudioMediaSessionController() {\n  const mediaApi = useMediaApi()\n  const { currentItem, hasNext, hasPrevious } = useAsset<AudioPlayerAsset>()\n  const currentTime = useTimelineStore((state) => state.currentTime)\n  const duration = useTimelineStore((state) => state.duration)\n  const sourceType = useAssetStore((state) => state.sourceType)\n  const status = usePlaybackStore((state) => state.status)\n  const asset = currentItem?.properties ?? null\n  const active = asset !== null\n  const playlistSource = isMediaSessionPlaylistSource(sourceType)\n\n  const metadata = React.useMemo(\n    () => (asset ? getAudioMediaSessionMetadata(asset) : null),\n    [asset]\n  )\n  const position = React.useMemo(\n    () =>\n      status === \"loading\"\n        ? null\n        : getMediaSessionPositionState({\n            active,\n            currentTime,\n            duration,\n          }),\n    [active, currentTime, duration, status]\n  )\n  const actions = useMediaSessionActionHandlers({\n    canGoNext: playlistSource && hasNext,\n    canGoPrevious: playlistSource && hasPrevious,\n    getCurrentTime: () => {\n      const state = mediaApi.getState()\n\n      return getMediaSessionCurrentTime({\n        currentTime: state.timeline.currentTime,\n        isLive: state.timeline.isLive,\n        mediaElement: state.media.mediaElement,\n      })\n    },\n    getSeekRange: () => {\n      const state = mediaApi.getState()\n\n      return state.timeline.isLive ? state.player.instance?.seekRange() : null\n    },\n    onNextTrack: () => {\n      const state = mediaApi.getState()\n      if (\n        canMoveToNextMediaSessionTrack(state.asset.sourceType, state.playlist)\n      ) {\n        state.playlist.next()\n      }\n    },\n    onPause: () => {\n      mediaApi.getState().playback.pause()\n    },\n    onPlay: () => {\n      const state = mediaApi.getState()\n      if (!canStartMediaSessionPlayback(state.playback.status)) return\n\n      return state.playback.play()\n    },\n    onPreviousTrack: () => {\n      const state = mediaApi.getState()\n      if (\n        canMoveToPreviousMediaSessionTrack(\n          state.asset.sourceType,\n          state.playlist\n        )\n      ) {\n        state.playlist.previous()\n      }\n    },\n    onSeek: (time) => {\n      mediaApi.getState().timeline.seek(time)\n    },\n  })\n\n  useMediaSessionSync({\n    actions,\n    active,\n    claim: status === \"playing\",\n    metadata,\n    playbackState: getMediaSessionPlaybackState({ active, status }),\n    position,\n    positionKey: currentItem?.id ?? null,\n  })\n\n  return null\n}\n\nfunction getAudioMediaSessionArtwork(\n  asset: AudioPlayerAsset,\n  fallbackPoster: string | undefined\n): MediaImage[] {\n  const templateUrl = getMediaSessionMetadataValue(asset.artwork?.templateUrl)\n  if (templateUrl) {\n    return [\n      {\n        sizes: \"512x512\",\n        src: templateUrl\n          .replaceAll(\"{w}\", \"512\")\n          .replaceAll(\"{h}\", \"512\")\n          .replaceAll(\"{f}\", \"jpg\"),\n      },\n    ]\n  }\n\n  const poster = getMediaSessionMetadataValue(\n    asset.poster,\n    asset.artwork?.url,\n    asset.images?.poster,\n    asset.images?.backdrop,\n    fallbackPoster\n  )\n\n  return poster ? [{ src: poster }] : []\n}\n\nfunction getAudioMediaSessionMetadata(\n  asset: AudioPlayerAsset\n): MediaMetadataInit {\n  const metadata = getAudioAssetMetadata(asset)\n  const artwork = getAudioMediaSessionArtwork(asset, metadata.poster)\n\n  return {\n    album: getMediaSessionMetadataValue(asset.albumName),\n    artist: getMediaSessionMetadataValue(asset.artistName),\n    artwork,\n    title: metadata.title,\n  }\n}\n",
      "type": "registry:component",
      "target": "components/audio-player/components/media-session-controller.tsx"
    },
    {
      "path": "registry/default/blocks/audio-player/components/track-info.tsx",
      "content": "\"use client\"\n\nimport type { AudioPlayerAsset } from \"@/registry/default/blocks/audio-player/player\"\n\nimport { getAudioAssetMetadata } from \"@/registry/default/blocks/audio-player/components/audio-source\"\nimport { useAsset } from \"@/registry/default/hooks/use-asset\"\nimport { LimeplayLogo } from \"@/registry/default/ui/limeplay-logo\"\n\nexport function TrackInfo() {\n  const { currentItem } = useAsset<AudioPlayerAsset>()\n\n  const asset = currentItem?.properties\n  const metadata = getAudioAssetMetadata(asset)\n\n  return (\n    <div className=\"flex max-w-sm min-w-0 items-center gap-3\">\n      {metadata.poster ? (\n        <div className=\"relative size-10 shrink-0 overflow-hidden rounded-sm bg-secondary outline-1 -outline-offset-1 outline-white/10\">\n          <img\n            alt={`Album art for ${metadata.title}`}\n            className=\"size-full object-cover\"\n            height={40}\n            src={metadata.poster}\n            width={40}\n          />\n        </div>\n      ) : (\n        <LimeplayLogo className=\"size-10\" />\n      )}\n      <div className=\"min-w-0 flex-1\">\n        <div className=\"truncate text-sm/snug font-medium text-secondary-foreground\">\n          {metadata.title}\n        </div>\n        {metadata.subtitle && (\n          <div className=\"truncate text-xs/snug text-secondary-foreground/80\">\n            {metadata.subtitle}\n          </div>\n        )}\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/audio-player/components/track-info.tsx"
    },
    {
      "path": "registry/default/blocks/audio-player/components/playlist.tsx",
      "content": "\"use client\"\n\nimport { CardsThreeIcon, PlayIcon } from \"@phosphor-icons/react\"\nimport { XIcon } from \"lucide-react\"\nimport { type ReactNode, useCallback, useMemo, useState } from \"react\"\n\nimport type { AudioPlayerAsset } from \"@/registry/default/blocks/audio-player/player\"\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\nimport { cn } from \"@/lib/utils\"\nimport { getAudioAssetMetadata } from \"@/registry/default/blocks/audio-player/components/audio-source\"\nimport { Button } from \"@/registry/default/blocks/audio-player/components/button\"\nimport { usePlayerStore } from \"@/registry/default/hooks/use-player\"\nimport { usePlaylistStore } from \"@/registry/default/hooks/use-playlist\"\nimport { LimeplayLogo } from \"@/registry/default/ui/limeplay-logo\"\n\nconst PLAYLIST_SIDE_OFFSET = 24\n\nexport function Playlist() {\n  const [open, setOpen] = useState(false)\n  const currentItem = usePlaylistStore(\n    (state) =>\n      state.currentItem as null | { id: string; properties: AudioPlayerAsset }\n  )\n  const preloadManagers = usePlayerStore((state) => state.preloadManagers)\n  const queue = usePlaylistStore(\n    (state) => state.queue as { id: string; properties: AudioPlayerAsset }[]\n  )\n  const repeatMode = usePlaylistStore((state) => state.repeatMode)\n  const shuffle = usePlaylistStore((state) => state.shuffle)\n  const shuffleOrder = usePlaylistStore((state) => state.shuffleOrder)\n  const skipToId = usePlaylistStore((state) => state.skipToId)\n\n  const orderedItems = useMemo(() => {\n    if (!shuffle || shuffleOrder.length === 0) {\n      return queue.map((item, queueIndex) => ({ item, queueIndex }))\n    }\n\n    return shuffleOrder\n      .map((queueIndex) => ({\n        item: queue[queueIndex],\n        queueIndex,\n      }))\n      .filter(\n        (\n          entry\n        ): entry is {\n          item: { id: string; properties: AudioPlayerAsset }\n          queueIndex: number\n        } => Boolean(entry.item)\n      )\n  }, [queue, shuffle, shuffleOrder])\n\n  const displayAssets = useMemo(\n    () =>\n      orderedItems.map(({ item, queueIndex }) => ({\n        asset: item.properties,\n        id: item.id,\n        queueIndex,\n      })),\n    [orderedItems]\n  )\n\n  const currentDisplayAsset = useMemo(() => {\n    if (!currentItem) return null\n\n    return (\n      displayAssets.find(({ id }) => id === currentItem.id) ?? {\n        asset: currentItem.properties,\n        id: currentItem.id,\n        queueIndex: queue.findIndex((item) => item.id === currentItem.id),\n      }\n    )\n  }, [currentItem, displayAssets, queue])\n\n  const currentDisplayIndex = useMemo(() => {\n    if (!currentItem) return -1\n    return displayAssets.findIndex(({ id }) => id === currentItem.id)\n  }, [currentItem, displayAssets])\n\n  const nextDisplayAssets = useMemo(() => {\n    if (!currentItem) return displayAssets\n    if (currentDisplayIndex === -1) return displayAssets\n\n    if (repeatMode === \"all\" && displayAssets.length > 1) {\n      return [\n        ...displayAssets.slice(currentDisplayIndex + 1),\n        ...displayAssets.slice(0, currentDisplayIndex),\n      ]\n    }\n\n    return displayAssets.slice(currentDisplayIndex + 1)\n  }, [currentDisplayIndex, currentItem, displayAssets, repeatMode])\n\n  const playlistName = useMemo(\n    () => getPlaylistName(currentItem?.properties, queue),\n    [currentItem, queue]\n  )\n\n  const handleAssetSelect = useCallback(\n    (assetId: string) => {\n      skipToId(assetId)\n    },\n    [skipToId]\n  )\n\n  return (\n    <DropdownMenu onOpenChange={setOpen} open={open}>\n      <Button aria-label=\"Open Playlist\" asChild>\n        <DropdownMenuTrigger>\n          <CardsThreeIcon weight=\"fill\" />\n        </DropdownMenuTrigger>\n      </Button>\n\n      <DropdownMenuContent\n        align=\"end\"\n        alignOffset={-8}\n        className=\"dark w-92 rounded-xl border border-border bg-popover p-0 shadow-xl ring-0 shadow-black/25\"\n        side=\"top\"\n        sideOffset={PLAYLIST_SIDE_OFFSET}\n      >\n        <div className=\"flex items-center justify-between px-4 pt-4 pb-5\">\n          <h2 className=\"text-lg leading-none font-semibold tracking-normal text-foreground\">\n            Queue\n          </h2>\n          <Button\n            aria-label=\"Close playlist\"\n            className=\"\n              size-8 text-muted-foreground\n              hover:bg-muted hover:text-foreground\n              focus-visible:outline-offset-0\n              [&_svg]:size-5\n            \"\n            onClick={() => setOpen(false)}\n          >\n            <XIcon strokeWidth={2} />\n          </Button>\n        </div>\n\n        <div\n          className={`relative max-h-100 scrollbar-none overflow-y-auto overscroll-contain px-4 pb-4`}\n        >\n          {displayAssets.length === 0 && (\n            <div className=\"py-14 text-center text-[13px] text-muted-foreground\">\n              Queue is empty\n            </div>\n          )}\n\n          {displayAssets.length > 0 && (\n            <div className=\"space-y-4\">\n              {currentDisplayAsset && (\n                <section aria-labelledby=\"audio-playlist-now-playing\">\n                  <SectionHeading id=\"audio-playlist-now-playing\">\n                    Now playing\n                  </SectionHeading>\n                  <TrackRow\n                    asset={currentDisplayAsset.asset}\n                    index={0}\n                    isActive\n                    key={currentDisplayAsset.id}\n                    onSelect={() => handleAssetSelect(currentDisplayAsset.id)}\n                    preloaded={preloadManagers.has(currentDisplayAsset.id)}\n                    setSize={1}\n                    showArtworkOverlay={false}\n                  />\n                </section>\n              )}\n\n              <section aria-labelledby=\"audio-playlist-next-from\">\n                <SectionHeading id=\"audio-playlist-next-from\">\n                  Next From: {playlistName}\n                </SectionHeading>\n                {nextDisplayAssets.length === 0 ? (\n                  <div className=\"rounded-lg px-3 py-6 text-center text-[13px] text-muted-foreground\">\n                    Nothing queued next\n                  </div>\n                ) : (\n                  <div className=\"space-y-1\">\n                    {nextDisplayAssets.map(({ asset, id }, index) => (\n                      <TrackRow\n                        asset={asset}\n                        index={index}\n                        isActive={currentItem?.id === id}\n                        key={id}\n                        onSelect={() => handleAssetSelect(id)}\n                        preloaded={preloadManagers.has(id)}\n                        setSize={nextDisplayAssets.length}\n                      />\n                    ))}\n                  </div>\n                )}\n              </section>\n            </div>\n          )}\n        </div>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n\nfunction firstNonEmpty(\n  ...values: (null | number | string | undefined)[]\n): string | undefined {\n  for (const value of values) {\n    if (typeof value === \"number\" && Number.isFinite(value)) {\n      return String(value)\n    }\n\n    if (typeof value !== \"string\") continue\n\n    const trimmed = value.trim()\n    if (trimmed) return trimmed\n  }\n\n  return undefined\n}\n\nfunction formatDuration(ms?: number) {\n  if (typeof ms !== \"number\" || !Number.isFinite(ms)) return \"--:--\"\n\n  const secs = Math.floor(ms / 1000)\n  const m = Math.floor(secs / 60)\n  const s = secs % 60\n  return `${m}:${String(s).padStart(2, \"0\")}`\n}\n\nfunction getAssetPlaylistName(\n  asset: AudioPlayerAsset | null | undefined\n): string | undefined {\n  if (!asset) return undefined\n\n  const assetWithPlaylist = asset as AudioPlayerAsset & {\n    collectionName?: string\n    playlistName?: string\n    playlistTitle?: string\n    sourceName?: string\n  }\n\n  return firstNonEmpty(\n    assetWithPlaylist.playlistName,\n    assetWithPlaylist.playlistTitle,\n    assetWithPlaylist.collectionName,\n    assetWithPlaylist.sourceName,\n    asset.group\n  )\n}\n\nfunction getPlaylistName(\n  currentAsset: AudioPlayerAsset | null | undefined,\n  queue: { id: string; properties: AudioPlayerAsset }[]\n) {\n  return (\n    getAssetPlaylistName(currentAsset) ??\n    queue.map((item) => getAssetPlaylistName(item.properties)).find(Boolean) ??\n    \"Queue\"\n  )\n}\n\nfunction SectionHeading({ children, id }: { children: ReactNode; id: string }) {\n  return (\n    <h3\n      className=\"px-0 pb-2 text-[15px] leading-none font-semibold tracking-normal text-foreground\"\n      id={id}\n    >\n      {children}\n    </h3>\n  )\n}\n\nfunction TrackRow({\n  asset,\n  index,\n  isActive,\n  onSelect,\n  preloaded,\n  setSize,\n  showArtworkOverlay = true,\n}: {\n  asset: AudioPlayerAsset\n  index: number\n  isActive: boolean\n  onSelect: () => void\n  preloaded: boolean\n  setSize: number\n  showArtworkOverlay?: boolean\n}) {\n  const metadata = getAudioAssetMetadata(asset, \"Untitled track\")\n\n  return (\n    <Button\n      aria-posinset={index + 1}\n      aria-setsize={setSize}\n      className={cn(\n        `\n          group/track h-auto w-full cursor-pointer justify-start gap-3 rounded-lg bg-transparent px-3 py-2.5 text-left transition-[background-color]\n          duration-150 ease-out\n          hover:bg-muted\n          focus-visible:outline-offset-0\n        `,\n        isActive\n          ? `\n            bg-accent/70\n            hover:bg-accent/70\n          `\n          : \"\"\n      )}\n      data-active={isActive ? \"true\" : undefined}\n      onClick={onSelect}\n    >\n      <div className=\"relative size-10 shrink-0 overflow-hidden rounded-md bg-muted outline-1 -outline-offset-1 outline-white/10\">\n        {metadata.poster && (\n          <img\n            alt=\"\"\n            className=\"absolute inset-0 size-full object-cover\"\n            src={metadata.poster}\n          />\n        )}\n        {!metadata.poster && (\n          <div\n            aria-hidden=\"true\"\n            className=\"absolute inset-0 flex items-center justify-center text-muted-foreground\"\n          >\n            <LimeplayLogo className=\"size-5\" />\n          </div>\n        )}\n        {showArtworkOverlay && (\n          <div\n            className={cn(\n              \"absolute inset-0 flex items-center justify-center transition-[background-color] duration-150 ease-out\",\n              isActive\n                ? \"bg-black/50\"\n                : `\n                  bg-black/0\n                  group-hover/track:bg-black/45\n                `\n            )}\n          >\n            <PlayIcon\n              className={cn(\n                \"size-4 text-white drop-shadow-sm transition-[opacity,transform] duration-150 ease-out\",\n                isActive\n                  ? \"scale-100 opacity-100\"\n                  : `\n                    scale-50 opacity-0\n                    group-hover/track:scale-100 group-hover/track:opacity-100\n                  `\n              )}\n              weight=\"fill\"\n            />\n          </div>\n        )}\n      </div>\n\n      <div className=\"min-w-0 flex-1\">\n        <div className=\"flex items-center gap-2\">\n          <p\n            className={cn(\n              \"truncate text-[13px] leading-snug transition-[color] duration-150 ease-out\",\n              isActive\n                ? \"font-medium text-foreground\"\n                : `\n                  text-foreground/75\n                  group-hover/track:text-foreground\n                `\n            )}\n          >\n            {metadata.title}\n          </p>\n          {preloaded && !isActive && (\n            <span\n              aria-label=\"Preloaded\"\n              className=\"size-1.5 shrink-0 rounded-full bg-foreground/35\"\n            />\n          )}\n        </div>\n        {metadata.subtitle && (\n          <p className=\"mt-0.5 truncate text-[11px] text-muted-foreground\">\n            {metadata.subtitle}\n          </p>\n        )}\n      </div>\n\n      <span className=\"ml-auto shrink-0 pl-2 text-[11px] text-muted-foreground tabular-nums\">\n        {formatDuration(asset.duration)}\n      </span>\n    </Button>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/audio-player/components/playlist.tsx"
    },
    {
      "path": "registry/default/blocks/audio-player/components/icons.tsx",
      "content": "import type { SVGProps } from \"react\"\n\nexport function DislikeIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"currentColor\"\n      focusable=\"false\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      {...props}\n    >\n      <path d=\"m11.31 2 .392.007c1.824.06 3.61.534 5.223 1.388l.343.189.27.154c.264.152.56.24.863.26l.13.004H20.5a1.5 1.5 0 011.5 1.5V11.5a1.5 1.5 0 01-1.5 1.5h-1.79l-.158.013a1 1 0 00-.723.512l-.064.145-2.987 8.535a1 1 0 01-1.109.656l-1.04-.174a4 4 0 01-3.251-4.783L10 15H5.938a3.664 3.664 0 01-3.576-2.868A3.682 3.682 0 013 9.15l-.02-.088A3.816 3.816 0 014 5.5v-.043l.008-.227a2.86 2.86 0 01.136-.664l.107-.28A3.754 3.754 0 017.705 2h3.605ZM7.705 4c-.755 0-1.425.483-1.663 1.2l-.032.126a.818.818 0 00-.01.131v.872l-.587.586a1.816 1.816 0 00-.524 1.465l.038.23.02.087.21.9-.55.744a1.686 1.686 0 00-.321 1.18l.029.177c.17.76.844 1.302 1.623 1.302H10a2.002 2.002 0 011.956 2.419l-.623 2.904-.034.208a2.002 2.002 0 001.454 2.139l.206.045.21.035 2.708-7.741A3.001 3.001 0 0118.71 11H20V6.002h-1.47c-.696 0-1.38-.183-1.985-.528l-.27-.155-.285-.157A10.002 10.002 0 0011.31 4H7.705Z\" />\n    </svg>\n  )\n}\n\nexport function DislikeSelectedIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"currentColor\"\n      focusable=\"false\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      {...props}\n    >\n      <path d=\"M11.313 2.002c2.088 0 4.14.546 5.953 1.583l.273.156a2 2 0 00.993.264H21a1 1 0 011 1V11a1 1 0 01-1.002 1l-2.787-.005a1 1 0 00-.946.67l-3.02 8.628a.815.815 0 01-.966.522 3.262 3.262 0 01-2.35-4.062l.707-2.477a1 1 0 00-.961-1.274h-5.29a2.24 2.24 0 01-2.004-1.238l-.18-.359a1.784 1.784 0 01.601-2.278.446.446 0 00.198-.37v-.07a.578.578 0 00-.116-.347 2.374 2.374 0 01.412-3.278l.498-.399a.379.379 0 00.123-.415l-.07-.207a2.1 2.1 0 01.313-1.923A2.798 2.798 0 017.4 2l3.913.002Z\" />\n    </svg>\n  )\n}\n\nexport function LikeIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"currentColor\"\n      focusable=\"false\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      {...props}\n    >\n      <path d=\"M9.221 1.795a1 1 0 011.109-.656l1.04.173a4 4 0 013.252 4.784L14 9h4.061a3.664 3.664 0 013.576 2.868A3.68 3.68 0 0121 14.85l.02.087A3.815 3.815 0 0120 18.5v.043l-.01.227a2.82 2.82 0 01-.135.663l-.106.282A3.754 3.754 0 0116.295 22h-3.606l-.392-.007a12.002 12.002 0 01-5.223-1.388l-.343-.189-.27-.154a2.005 2.005 0 00-.863-.26l-.13-.004H3.5a1.5 1.5 0 01-1.5-1.5V12.5A1.5 1.5 0 013.5 11h1.79l.157-.013a1 1 0 00.724-.512l.063-.145 2.987-8.535Zm-1.1 9.196A3 3 0 015.29 13H4v4.998h1.468a4 4 0 011.986.528l.27.155.285.157A10 10 0 0012.69 20h3.606c.754 0 1.424-.483 1.663-1.2l.03-.126a.819.819 0 00.012-.131v-.872l.587-.586c.388-.388.577-.927.523-1.465l-.038-.23-.02-.087-.21-.9.55-.744A1.663 1.663 0 0018.061 11H14a2.002 2.002 0 01-1.956-2.418l.623-2.904a2 2 0 00-1.626-2.392l-.21-.035-2.71 7.741Z\"></path>\n    </svg>\n  )\n}\n\nexport function LikeSelectedIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"currentColor\"\n      focusable=\"false\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      {...props}\n    >\n      <path d=\"M10.72 2.18a3.263 3.263 0 012.352 4.063l-.708 2.476a1 1 0 00.962 1.275h5.29c.848 0 1.624.48 2.003 1.238l.179.359a1.785 1.785 0 01-.6 2.279.446.446 0 00-.198.37v.07c0 .124.041.246.116.346a2.375 2.375 0 01-.41 3.278l-.5.399a.38.38 0 00-.123.416l.07.206c.217.653.1 1.372-.313 1.923a2.8 2.8 0 01-2.24 1.12l-3.914-.002a12 12 0 01-5.952-1.584l-.272-.155a2.002 2.002 0 00-.993-.265H3a1 1 0 01-1-1v-5.996a1 1 0 011.002-1L5.789 12a1 1 0 00.945-.67l3.02-8.628a.816.816 0 01.967-.523Z\" />\n    </svg>\n  )\n}\n\nexport function NextIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"currentColor\"\n      focusable=\"false\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      {...props}\n    >\n      <path d=\"M20 20a1 1 0 001-1V5a1 1 0 00-2 0v14a1 1 0 001 1Zm-14.955-.226L18 12 5.045 4.228A1.35 1.35 0 003 5.386v13.23a1.35 1.35 0 002.045 1.158Z\" />\n    </svg>\n  )\n}\n\nexport function PauseIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"currentColor\"\n      focusable=\"false\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      {...props}\n    >\n      <path d=\"M6.5 3A1.5 1.5 0 005 4.5v15A1.5 1.5 0 006.5 21h2a1.5 1.5 0 001.5-1.5v-15A1.5 1.5 0 008.5 3h-2Zm9 0A1.5 1.5 0 0014 4.5v15a1.5 1.5 0 001.5 1.5h2a1.5 1.5 0 001.5-1.5v-15A1.5 1.5 0 0017.5 3h-2Z\"></path>\n    </svg>\n  )\n}\n\nexport function PlayIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"currentColor\"\n      focusable=\"false\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      {...props}\n    >\n      <path d=\"M5 4.623V19.38a1.5 1.5 0 002.26 1.29L22 12 7.26 3.33A1.5 1.5 0 005 4.623Z\" />\n    </svg>\n  )\n}\n\nexport function PreviousIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"currentColor\"\n      focusable=\"false\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      {...props}\n    >\n      <path d=\"M4 4a1 1 0 00-1 1v14a1 1 0 102 0V5a1 1 0 00-1-1Zm14.955.23L6 12.003l12.955 7.772A1.35 1.35 0 0021 18.617V5.387a1.35 1.35 0 00-2.045-1.157Z\" />\n    </svg>\n  )\n}\n\nexport function RepeatAllIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"currentColor\"\n      focusable=\"false\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      {...props}\n    >\n      <path d=\"M21 10a1 1 0 011 1v4a5 5 0 01-5 5H5.414l1.293 1.293a1 1 0 11-1.414 1.414L1.586 19l3.707-3.707a1 1 0 111.414 1.414L5.414 18H17a3 3 0 003-3v-4a1 1 0 011-1Zm-3.707-8.707a1 1 0 011.414 0L22.414 5l-3.707 3.707a1 1 0 11-1.414-1.414L18.586 6H7a3 3 0 00-3 3v4a1 1 0 01-2 0V9a5 5 0 015-5h11.586l-1.293-1.293a1 1 0 010-1.414ZM12 10a2 2 0 110 4 2 2 0 010-4Z\" />\n    </svg>\n  )\n}\n\nexport function RepeatIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"currentColor\"\n      focusable=\"false\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      {...props}\n    >\n      <path d=\"M17.293 1.293a1 1 0 000 1.415L18.586 4H7a5 5 0 00-5 5v4a1 1 0 102 0V9a3 3 0 013-3h11.586l-1.293 1.293a1 1 0 001.414 1.415L22.414 5l-3.707-3.707a1 1 0 00-1.414 0ZM21 10a1 1 0 00-1 1v4a3 3 0 01-3 3H5.414l1.293-1.292a1.001 1.001 0 00-1.414-1.415L1.586 19l3.707 3.707a1 1 0 101.414-1.413L5.414 20H17a5 5 0 005-5v-4a1 1 0 00-1-1Z\"></path>\n    </svg>\n  )\n}\n\nexport function RepeatOneIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"currentColor\"\n      focusable=\"false\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      {...props}\n    >\n      <path d=\"M17.293 1.293a1 1 0 000 1.415L18.586 4H7a5 5 0 00-5 5v4a1 1 0 102 0V9a3 3 0 013-3h11.586l-1.293 1.293a1 1 0 001.414 1.415L22.414 5l-3.707-3.707a1 1 0 00-1.414 0ZM13 15V8h-2.5a1 1 0 000 2h.5v5a1 1 0 002 0Zm8-5a1 1 0 00-1 1v4a3 3 0 01-3 3H5.414l1.293-1.292a1.001 1.001 0 00-1.414-1.415L1.586 19l3.707 3.707a1 1 0 101.414-1.413L5.414 20H17a5 5 0 005-5v-4a1 1 0 00-1-1Z\" />\n    </svg>\n  )\n}\n\nexport function ShuffleOffIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"currentColor\"\n      focusable=\"false\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      {...props}\n    >\n      <path d=\"M16.293 1.293a1 1 0 00-.001 1.415L18.585 5H17.21a7 7 0 00-5.823 3.118L6.95 14.774A5 5 0 012.79 17H2a1 1 0 000 2h.79a7 7 0 005.822-3.117l4.438-6.656A5 5 0 0117.21 7h1.376l-2.293 2.293a1 1 0 001.414 1.414L22.414 6l-4.707-4.707a1 1 0 00-1.414 0ZM2.789 5H2a1 1 0 000 2h.79a5 5 0 014.159 2.227l.647.97 1.202-1.802-.185-.277A7 7 0 002.789 5Zm13.504 8.293a1 1 0 00-.001 1.414L18.585 17H17.21a5 5 0 01-4.16-2.226l-.648-.972-1.202 1.803.186.278A7 7 0 0017.21 19h1.376l-2.293 2.294-.068.076a1 1 0 001.406 1.406l.076-.07L22.414 18l-4.707-4.707a1 1 0 00-1.414 0Z\" />\n    </svg>\n  )\n}\n\nexport function ShuffleOneIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"currentColor\"\n      focusable=\"false\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      {...props}\n    >\n      <path d=\"M16.293 13.293a1 1 0 011.414 0L22.414 18l-4.707 4.707a1 1 0 01-1.414-1.413L18.586 19H17.21a7.001 7.001 0 01-5.824-3.117l-.186-.278 1.202-1.803.648.972A5.001 5.001 0 0017.21 17h1.375l-2.293-2.293a1 1 0 010-1.414Zm0-12a1 1 0 011.414 0L22.414 6l-4.707 4.707a1 1 0 01-1.414-1.414L18.586 7H17.21a5 5 0 00-4.16 2.227l-4.438 6.656A7 7 0 012.79 19H2a1 1 0 010-2h.79a5 5 0 004.16-2.226l4.437-6.656A7 7 0 0117.21 5h1.375l-2.293-2.292a1 1 0 010-1.415ZM3 10.001a2 2 0 110 4 2 2 0 010-4Zm-.21-5a7 7 0 015.823 3.117l.185.277-1.202 1.803-.647-.971A5 5 0 002.79 7H2a1 1 0 010-2h.79Z\" />\n    </svg>\n  )\n}\n\nexport function VolumeFullIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"currentColor\"\n      focusable=\"false\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      {...props}\n    >\n      <path d=\"M11.485 2.143 3.913 6.687A6 6 0 001 11.832v.338a6 6 0 002.913 5.144l7.572 4.543A1 1 0 0013 21V3a1.001 1.001 0 00-1.515-.857Zm6.88 2.079a1 1 0 00-.001 1.414 9 9 0 010 12.728 1 1 0 001.414 1.414 11 11 0 000-15.556 1 1 0 00-1.413 0ZM4.941 8.402l.001-.002L11 4.767v14.466l-6.058-3.635A4 4 0 013 12.168v-.337a4 4 0 011.941-3.429ZM15.535 7.05a1 1 0 000 1.415 5 5 0 010 7.07 1 1 0 001.415 1.415 6.999 6.999 0 000-9.9 1 1 0 00-1.415 0Z\"></path>\n    </svg>\n  )\n}\n\nexport function VolumeMutedIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"currentColor\"\n      focusable=\"false\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      {...props}\n    >\n      <path d=\"M11.485 2.143 3.913 6.687A6 6 0 001 11.832v.338a6 6 0 002.913 5.144l7.572 4.543A1 1 0 0013 21V3a1.001 1.001 0 00-1.515-.857ZM4.942 8.4 11 4.767v14.466l-6.058-3.634A4 4 0 013 12.169v-.338A4 4 0 014.942 8.4Zm16.351-.108L19 10.586l-2.293-2.293a1 1 0 10-1.414 1.414L17.586 12l-2.293 2.293a1 1 0 101.414 1.414L19 13.414l2.293 2.293a1 1 0 101.414-1.414L20.414 12l2.293-2.294a1 1 0 10-1.414-1.414Z\" />\n    </svg>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/audio-player/components/icons.tsx"
    },
    {
      "path": "registry/default/blocks/audio-player/hooks/use-playlist-asset.ts",
      "content": "\"use client\"\n\nimport type {\n  AudioPlayerAsset,\n  PlaybackUrls,\n} from \"@/registry/default/blocks/audio-player/player\"\nimport type { UseAssetReturn } from \"@/registry/default/hooks/use-asset\"\n\nimport { useAsset } from \"@/registry/default/hooks/use-asset\"\n\nexport type { PlaybackUrls }\n\nexport type PlaylistAsset = AudioPlayerAsset\n\nexport interface UsePlaylistAssetReturn extends UseAssetReturn<PlaylistAsset> {\n  items: PlaylistAsset[]\n}\n\nexport function usePlaylistAsset(): UsePlaylistAssetReturn {\n  const asset = useAsset<PlaylistAsset>()\n  const items = asset.orderedItems.map((item) => item.properties)\n\n  return {\n    ...asset,\n    items,\n  }\n}\n",
      "type": "registry:hook",
      "target": "components/audio-player/hooks/use-playlist-asset.ts"
    },
    {
      "path": "registry/default/blocks/audio-player/components/button.tsx",
      "content": "import { Slot } from \"@radix-ui/react-slot\"\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  asChild?: boolean\n  render?: React.ReactElement\n  size?: \"default\" | \"large\" | \"sm\" | \"xl\"\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ asChild = false, className, render, size = \"large\", ...props }, ref) => {\n    const Comp = render ? Slot : asChild ? Slot : \"button\"\n    const buttonProps =\n      Comp === \"button\" ? { type: props.type ?? \"button\", ...props } : props\n\n    return (\n      <Comp\n        className={cn(\n          `\n            inline-flex shrink-0 cursor-pointer items-center justify-center rounded-full bg-transparent text-foreground\n            transition-[color,background-color,scale] duration-150 ease-out\n            hover:bg-muted\n            focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\n            active:scale-[0.96]\n            disabled:cursor-not-allowed disabled:opacity-50\n          `,\n          size === \"default\" &&\n            `\n              size-8\n              [&_svg]:size-5\n            `,\n          size === \"large\" &&\n            `\n              size-10\n              [&_svg]:size-6\n            `,\n          size === \"xl\" &&\n            `\n              size-12\n              [&_svg]:size-10\n            `,\n          size === \"sm\" &&\n            `\n              size-6\n              [&_svg]:size-4\n            `,\n          className\n        )}\n        ref={ref}\n        {...buttonProps}\n      >\n        {render\n          ? React.cloneElement(render, undefined, props.children)\n          : props.children}\n      </Comp>\n    )\n  }\n)\n\nButton.displayName = \"AudioPlayerButton\"\n\nexport { Button }\n",
      "type": "registry:component",
      "target": "components/audio-player/components/button.tsx"
    },
    {
      "path": "registry/default/blocks/audio-player/styles.css",
      "content": ".limeplay {\n  --background: oklch(0.985 0 0);\n  --foreground: oklch(0.205 0 0);\n  --card: oklch(1 0 0);\n  --card-foreground: oklch(0.205 0 0);\n  --popover: oklch(1 0 0);\n  --popover-foreground: oklch(0.205 0 0);\n  --primary: oklch(0.577 0.245 27.325);\n  --primary-foreground: oklch(0.985 0 0);\n  --secondary: oklch(0.94 0 0);\n  --secondary-foreground: oklch(0.205 0 0);\n  --muted: oklch(0.94 0 0);\n  --muted-foreground: oklch(0.439 0 0);\n  --accent: oklch(0.94 0 0);\n  --accent-foreground: oklch(0.205 0 0);\n  --destructive: oklch(0.577 0.245 27.325);\n  --border: oklch(0 0 0 / 12%);\n  --input: oklch(0 0 0 / 14%);\n  --ring: oklch(0.577 0.245 27.325);\n  --chart-1: oklch(0.577 0.245 27.325);\n  --chart-2: oklch(0.704 0.191 22.216);\n  --chart-3: oklch(0.623 0.214 259.815);\n  --chart-4: oklch(0.809 0.105 251.813);\n  --chart-5: oklch(0.488 0.243 264.376);\n  --sidebar: oklch(0.985 0 0);\n  --sidebar-foreground: oklch(0.205 0 0);\n  --sidebar-primary: oklch(0.577 0.245 27.325);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.94 0 0);\n  --sidebar-accent-foreground: oklch(0.205 0 0);\n  --sidebar-border: oklch(0 0 0 / 12%);\n  --sidebar-ring: oklch(0.577 0.245 27.325);\n\n  --lp-timeline-track-height: 2px;\n}\n\n.dark .limeplay:not(.light),\n.limeplay.dark {\n  --background: oklch(0.247 0 129.63);\n  --foreground: oklch(0.985 0 0);\n  --card: oklch(0.205 0 0);\n  --card-foreground: oklch(0.985 0 0);\n  --popover: oklch(0.205 0 0);\n  --popover-foreground: oklch(0.985 0 0);\n  --primary: oklch(0.87 0 0);\n  --primary-foreground: oklch(0.205 0 0);\n  --secondary: oklch(0.269 0 0);\n  --secondary-foreground: oklch(0.985 0 0);\n  --muted: oklch(0.269 0 0);\n  --muted-foreground: oklch(0.708 0 0);\n  --accent: oklch(0.371 0 0);\n  --accent-foreground: oklch(0.985 0 0);\n  --destructive: oklch(0.704 0.191 22.216);\n  --border: oklch(1 0 0 / 10%);\n  --input: oklch(1 0 0 / 15%);\n  --ring: oklch(0.556 0 0);\n  --chart-1: oklch(0.809 0.105 251.813);\n  --chart-2: oklch(0.623 0.214 259.815);\n  --chart-3: oklch(0.546 0.245 262.881);\n  --chart-4: oklch(0.488 0.243 264.376);\n  --chart-5: oklch(0.424 0.199 265.638);\n  --sidebar: oklch(0.205 0 0);\n  --sidebar-foreground: oklch(0.985 0 0);\n  --sidebar-primary: oklch(0.488 0.243 264.376);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.269 0 0);\n  --sidebar-accent-foreground: oklch(0.985 0 0);\n  --sidebar-border: oklch(1 0 0 / 10%);\n  --sidebar-ring: oklch(0.556 0 0);\n\n  --lp-timeline-track-height: 2px;\n}\n",
      "type": "registry:style",
      "target": "components/audio-player/styles.css"
    }
  ],
  "meta": {
    "iframeHeight": "100px"
  },
  "type": "registry:block"
}