{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "media-player",
  "type": "registry:ui",
  "title": "Media Player",
  "description": "A composable media player with controls, settings, captions, and PiP support.",
  "dependencies": [
    "media-chrome",
    "@radix-ui/react-direction",
    "@radix-ui/react-slider",
    "@radix-ui/react-slot"
  ],
  "registryDependencies": [
    "@joyco/badge",
    "@joyco/button",
    "@joyco/dropdown-menu",
    "@joyco/tooltip",
    "@joyco/utils"
  ],
  "files": [
    {
      "path": "components/ui/media-player.tsx",
      "content": "'use client'\n\nimport { useDirection } from '@radix-ui/react-direction'\nimport * as SliderPrimitive from '@radix-ui/react-slider'\nimport { Slot } from '@radix-ui/react-slot'\nimport {\n  AlertTriangleIcon,\n  CaptionsOffIcon,\n  CheckIcon,\n  DownloadIcon,\n  FastForwardIcon,\n  Loader2Icon,\n  Maximize2Icon,\n  Minimize2Icon,\n  PauseIcon,\n  PictureInPicture2Icon,\n  PictureInPictureIcon,\n  PlayIcon,\n  RefreshCcwIcon,\n  RepeatIcon,\n  RewindIcon,\n  RotateCcwIcon,\n  SettingsIcon,\n  SubtitlesIcon,\n  Volume1Icon,\n  Volume2Icon,\n  VolumeXIcon,\n} from 'lucide-react'\nimport {\n  MediaActionTypes,\n  MediaProvider,\n  timeUtils,\n  useMediaDispatch,\n  useMediaFullscreenRef,\n  useMediaRef,\n  useMediaSelector,\n} from 'media-chrome/react/media-store'\nimport * as React from 'react'\nimport * as ReactDOM from 'react-dom'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from '@/components/ui/tooltip'\nimport { useComposedRefs } from '@/registry/lib/compose-refs'\nimport { cn } from '@/lib/utils'\nimport { useLazyRef } from '@/hooks/use-lazy-ref'\n\nconst ROOT_NAME = 'MediaPlayer'\nconst SEEK_NAME = 'MediaPlayerSeek'\nconst SETTINGS_NAME = 'MediaPlayerSettings'\nconst VOLUME_NAME = 'MediaPlayerVolume'\nconst PLAYBACK_SPEED_NAME = 'MediaPlayerPlaybackSpeed'\n\nconst FLOATING_MENU_SIDE_OFFSET = 10\nconst SPEEDS = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]\n\nconst SEEK_STEP_SHORT = 5\nconst SEEK_STEP_LONG = 10\nconst SEEK_COLLISION_PADDING = 10\nconst SEEK_TOOLTIP_WIDTH_FALLBACK = 240\n\nconst SEEK_HOVER_PERCENT = '--seek-hover-percent'\nconst SEEK_TOOLTIP_X = '--seek-tooltip-x'\nconst SEEK_TOOLTIP_Y = '--seek-tooltip-y'\n\nconst SPRITE_CONTAINER_WIDTH = 224\nconst SPRITE_CONTAINER_HEIGHT = 128\n\ninterface DivProps extends React.ComponentProps<'div'> {\n  asChild?: boolean\n}\n\ntype RootElement = React.ComponentRef<typeof MediaPlayer>\n\ntype Direction = 'ltr' | 'rtl'\n\ninterface StoreState {\n  controlsVisible: boolean\n  dragging: boolean\n  menuOpen: boolean\n  volumeIndicatorVisible: boolean\n}\n\ninterface Store {\n  subscribe: (cb: () => void) => () => void\n  getState: () => StoreState\n  setState: (key: keyof StoreState, value: StoreState[keyof StoreState]) => void\n  notify: () => void\n}\n\nconst StoreContext = React.createContext<Store | null>(null)\n\nfunction useStoreContext(consumerName: string) {\n  const context = React.useContext(StoreContext)\n  if (!context) {\n    throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``)\n  }\n  return context\n}\n\nfunction useStore<T>(selector: (state: StoreState) => T): T {\n  const store = useStoreContext('useStore')\n\n  const getSnapshot = React.useCallback(\n    () => selector(store.getState()),\n    [store, selector]\n  )\n\n  return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot)\n}\n\ninterface MediaPlayerContextValue {\n  mediaId: string\n  labelId: string\n  descriptionId: string\n  dir: Direction\n  rootRef: React.RefObject<RootElement | null>\n  mediaRef: React.RefObject<HTMLVideoElement | HTMLAudioElement | null>\n  portalContainer: Element | DocumentFragment | null\n  tooltipDelayDuration: number\n  tooltipSideOffset: number\n  disabled: boolean\n  isVideo: boolean\n  withoutTooltip: boolean\n}\n\nconst MediaPlayerContext = React.createContext<MediaPlayerContextValue | null>(\n  null\n)\n\nfunction useMediaPlayerContext(consumerName: string) {\n  const context = React.useContext(MediaPlayerContext)\n  if (!context) {\n    throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``)\n  }\n  return context\n}\n\ninterface MediaPlayerProps extends Omit<\n  DivProps,\n  'onTimeUpdate' | 'onVolumeChange'\n> {\n  onPlay?: () => void\n  onPause?: () => void\n  onEnded?: () => void\n  onTimeUpdate?: (time: number) => void\n  onVolumeChange?: (volume: number) => void\n  onMuted?: (muted: boolean) => void\n  onMediaError?: (error: MediaError | null) => void\n  onPipError?: (error: unknown, state: 'enter' | 'exit') => void\n  onFullscreenChange?: (fullscreen: boolean) => void\n  dir?: Direction\n  label?: string\n  tooltipDelayDuration?: number\n  tooltipSideOffset?: number\n  autoHide?: boolean\n  disabled?: boolean\n  withoutTooltip?: boolean\n}\n\nfunction MediaPlayer(props: MediaPlayerProps) {\n  const listenersRef = useLazyRef(() => new Set<() => void>())\n  const stateRef = useLazyRef<StoreState>(() => ({\n    controlsVisible: true,\n    dragging: false,\n    menuOpen: false,\n    volumeIndicatorVisible: false,\n  }))\n\n  const store: Store = React.useMemo(() => {\n    return {\n      subscribe: (cb) => {\n        listenersRef.current.add(cb)\n        return () => listenersRef.current.delete(cb)\n      },\n      getState: () => stateRef.current,\n      setState: (key, value) => {\n        if (Object.is(stateRef.current[key], value)) return\n        stateRef.current[key] = value\n        store.notify()\n      },\n      notify: () => {\n        for (const cb of listenersRef.current) {\n          cb()\n        }\n      },\n    }\n  }, [listenersRef, stateRef])\n\n  return (\n    <MediaProvider>\n      <StoreContext.Provider value={store}>\n        <MediaPlayerImpl {...props} />\n      </StoreContext.Provider>\n    </MediaProvider>\n  )\n}\n\nfunction MediaPlayerImpl(props: MediaPlayerProps) {\n  const {\n    onPlay,\n    onPause,\n    onEnded,\n    onTimeUpdate,\n    onFullscreenChange,\n    onVolumeChange,\n    onMuted,\n    onMediaError,\n    onPipError,\n    dir: dirProp,\n    label,\n    tooltipDelayDuration = 600,\n    tooltipSideOffset = FLOATING_MENU_SIDE_OFFSET,\n    asChild,\n    autoHide = false,\n    disabled = false,\n    withoutTooltip = false,\n    children,\n    className,\n    ref,\n    ...rootImplProps\n  } = props\n\n  const mediaId = React.useId()\n  const labelId = React.useId()\n  const descriptionId = React.useId()\n\n  const rootRef = React.useRef<RootElement | null>(null)\n  const fullscreenRef = useMediaFullscreenRef()\n  const composedRef = useComposedRefs(ref, rootRef, fullscreenRef)\n\n  const dir = useDirection(dirProp)\n  const dispatch = useMediaDispatch()\n  const mediaRef = React.useRef<HTMLVideoElement | HTMLAudioElement | null>(\n    null\n  )\n\n  const store = useStoreContext(ROOT_NAME)\n\n  const controlsVisible = useStore((state) => state.controlsVisible)\n  const dragging = useStore((state) => state.dragging)\n  const menuOpen = useStore((state) => state.menuOpen)\n\n  const hideControlsTimeoutRef = React.useRef<NodeJS.Timeout | null>(null)\n  const lastMouseMoveRef = React.useRef<number>(Date.now())\n  const volumeIndicatorTimeoutRef = React.useRef<NodeJS.Timeout | null>(null)\n\n  const mediaPaused = useMediaSelector((state) => state.mediaPaused ?? true)\n  const isFullscreen = useMediaSelector(\n    (state) => state.mediaIsFullscreen ?? false\n  )\n\n  const [mounted, setMounted] = React.useState(false)\n  React.useLayoutEffect(() => {\n    setMounted(true)\n  }, [])\n\n  const portalContainer = mounted\n    ? isFullscreen\n      ? rootRef.current\n      : globalThis.document.body\n    : null\n\n  const isVideo =\n    (typeof HTMLVideoElement !== 'undefined' &&\n      mediaRef.current instanceof HTMLVideoElement) ||\n    mediaRef.current?.tagName?.toLowerCase() === 'mux-player'\n\n  const onControlsShow = React.useCallback(() => {\n    store.setState('controlsVisible', true)\n    lastMouseMoveRef.current = Date.now()\n\n    if (hideControlsTimeoutRef.current) {\n      clearTimeout(hideControlsTimeoutRef.current)\n    }\n\n    if (autoHide && !mediaPaused && !menuOpen && !dragging) {\n      hideControlsTimeoutRef.current = setTimeout(() => {\n        store.setState('controlsVisible', false)\n      }, 3000)\n    }\n  }, [store.setState, autoHide, mediaPaused, menuOpen, dragging])\n\n  const onVolumeIndicatorTrigger = React.useCallback(() => {\n    if (menuOpen) return\n\n    store.setState('volumeIndicatorVisible', true)\n\n    if (volumeIndicatorTimeoutRef.current) {\n      clearTimeout(volumeIndicatorTimeoutRef.current)\n    }\n\n    volumeIndicatorTimeoutRef.current = setTimeout(() => {\n      store.setState('volumeIndicatorVisible', false)\n    }, 2000)\n\n    if (autoHide) {\n      onControlsShow()\n    }\n  }, [store.setState, menuOpen, autoHide, onControlsShow])\n\n  const onMouseLeave = React.useCallback(\n    (event: React.MouseEvent<RootElement>) => {\n      rootImplProps.onMouseLeave?.(event)\n\n      if (event.defaultPrevented) return\n\n      if (autoHide && !mediaPaused && !menuOpen && !dragging) {\n        store.setState('controlsVisible', false)\n      }\n    },\n    [\n      store.setState,\n      rootImplProps.onMouseLeave,\n      autoHide,\n      mediaPaused,\n      menuOpen,\n      dragging,\n    ]\n  )\n\n  const onMouseMove = React.useCallback(\n    (event: React.MouseEvent<RootElement>) => {\n      rootImplProps.onMouseMove?.(event)\n\n      if (event.defaultPrevented) return\n\n      if (autoHide) {\n        onControlsShow()\n      }\n    },\n    [autoHide, rootImplProps.onMouseMove, onControlsShow]\n  )\n\n  React.useEffect(() => {\n    if (mediaPaused || menuOpen || dragging) {\n      store.setState('controlsVisible', true)\n      if (hideControlsTimeoutRef.current) {\n        clearTimeout(hideControlsTimeoutRef.current)\n      }\n      return\n    }\n\n    if (autoHide) {\n      onControlsShow()\n    }\n  }, [\n    store.setState,\n    onControlsShow,\n    autoHide,\n    menuOpen,\n    mediaPaused,\n    dragging,\n  ])\n\n  const onKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<RootElement>) => {\n      if (disabled) return\n\n      rootImplProps.onKeyDown?.(event)\n\n      if (event.defaultPrevented) return\n\n      const mediaElement = mediaRef.current\n      if (!mediaElement) return\n\n      const isMediaFocused = document.activeElement === mediaElement\n      const isPlayerFocused =\n        document.activeElement?.closest('[data-slot=\"media-player\"]') !== null\n\n      if (!isMediaFocused && !isPlayerFocused) return\n\n      if (autoHide) onControlsShow()\n\n      switch (event.key.toLowerCase()) {\n        case ' ':\n        case 'k':\n          event.preventDefault()\n          dispatch({\n            type: mediaElement.paused\n              ? MediaActionTypes.MEDIA_PLAY_REQUEST\n              : MediaActionTypes.MEDIA_PAUSE_REQUEST,\n          })\n          break\n\n        case 'f':\n          event.preventDefault()\n          dispatch({\n            type: document.fullscreenElement\n              ? MediaActionTypes.MEDIA_EXIT_FULLSCREEN_REQUEST\n              : MediaActionTypes.MEDIA_ENTER_FULLSCREEN_REQUEST,\n          })\n          break\n\n        case 'm': {\n          event.preventDefault()\n          if (isVideo) {\n            onVolumeIndicatorTrigger()\n          }\n          dispatch({\n            type: mediaElement.muted\n              ? MediaActionTypes.MEDIA_UNMUTE_REQUEST\n              : MediaActionTypes.MEDIA_MUTE_REQUEST,\n          })\n          break\n        }\n\n        case 'arrowright':\n          event.preventDefault()\n          if (\n            isVideo ||\n            (mediaElement instanceof HTMLAudioElement && event.shiftKey)\n          ) {\n            dispatch({\n              type: MediaActionTypes.MEDIA_SEEK_REQUEST,\n              detail: Math.min(\n                mediaElement.duration,\n                mediaElement.currentTime + SEEK_STEP_SHORT\n              ),\n            })\n          }\n          break\n\n        case 'arrowleft':\n          event.preventDefault()\n          if (\n            isVideo ||\n            (mediaElement instanceof HTMLAudioElement && event.shiftKey)\n          ) {\n            dispatch({\n              type: MediaActionTypes.MEDIA_SEEK_REQUEST,\n              detail: Math.max(0, mediaElement.currentTime - SEEK_STEP_SHORT),\n            })\n          }\n          break\n\n        case 'arrowup':\n          event.preventDefault()\n          if (isVideo) {\n            onVolumeIndicatorTrigger()\n            dispatch({\n              type: MediaActionTypes.MEDIA_VOLUME_REQUEST,\n              detail: Math.min(1, mediaElement.volume + 0.1),\n            })\n          }\n          break\n\n        case 'arrowdown':\n          event.preventDefault()\n          if (isVideo) {\n            onVolumeIndicatorTrigger()\n            dispatch({\n              type: MediaActionTypes.MEDIA_VOLUME_REQUEST,\n              detail: Math.max(0, mediaElement.volume - 0.1),\n            })\n          }\n          break\n\n        case '<': {\n          event.preventDefault()\n          const currentRate = mediaElement.playbackRate\n          const currentIndex = SPEEDS.indexOf(currentRate)\n          const newIndex = Math.max(0, currentIndex - 1)\n          const newRate = SPEEDS[newIndex] ?? 1\n          dispatch({\n            type: MediaActionTypes.MEDIA_PLAYBACK_RATE_REQUEST,\n            detail: newRate,\n          })\n          break\n        }\n\n        case '>': {\n          event.preventDefault()\n          const currentRate = mediaElement.playbackRate\n          const currentIndex = SPEEDS.indexOf(currentRate)\n          const newIndex = Math.min(SPEEDS.length - 1, currentIndex + 1)\n          const newRate = SPEEDS[newIndex] ?? 1\n          dispatch({\n            type: MediaActionTypes.MEDIA_PLAYBACK_RATE_REQUEST,\n            detail: newRate,\n          })\n          break\n        }\n\n        case 'c':\n          event.preventDefault()\n          if (isVideo && mediaElement.textTracks.length > 0) {\n            dispatch({\n              type: MediaActionTypes.MEDIA_TOGGLE_SUBTITLES_REQUEST,\n            })\n          }\n          break\n\n        case 'd': {\n          const hasDownload = mediaElement.querySelector(\n            '[data-slot=\"media-player-download\"]'\n          )\n\n          if (!hasDownload) break\n\n          event.preventDefault()\n          if (mediaElement.currentSrc) {\n            const link = document.createElement('a')\n            link.href = mediaElement.currentSrc\n            link.download = ''\n            document.body.appendChild(link)\n            link.click()\n            document.body.removeChild(link)\n          }\n          break\n        }\n\n        case 'p': {\n          event.preventDefault()\n          if (isVideo && 'requestPictureInPicture' in mediaElement) {\n            const isPip = document.pictureInPictureElement === mediaElement\n            dispatch({\n              type: isPip\n                ? MediaActionTypes.MEDIA_EXIT_PIP_REQUEST\n                : MediaActionTypes.MEDIA_ENTER_PIP_REQUEST,\n            })\n            if (isPip) {\n              document.exitPictureInPicture().catch((error) => {\n                onPipError?.(error, 'exit')\n              })\n            } else {\n              mediaElement.requestPictureInPicture().catch((error) => {\n                onPipError?.(error, 'enter')\n              })\n            }\n          }\n          break\n        }\n\n        case 'r': {\n          event.preventDefault()\n          mediaElement.loop = !mediaElement.loop\n          break\n        }\n\n        case 'j': {\n          event.preventDefault()\n          dispatch({\n            type: MediaActionTypes.MEDIA_SEEK_REQUEST,\n            detail: Math.max(0, mediaElement.currentTime - SEEK_STEP_LONG),\n          })\n          break\n        }\n\n        case 'l': {\n          event.preventDefault()\n          dispatch({\n            type: MediaActionTypes.MEDIA_SEEK_REQUEST,\n            detail: Math.min(\n              mediaElement.duration,\n              mediaElement.currentTime + SEEK_STEP_LONG\n            ),\n          })\n          break\n        }\n\n        case '0':\n        case '1':\n        case '2':\n        case '3':\n        case '4':\n        case '5':\n        case '6':\n        case '7':\n        case '8':\n        case '9': {\n          event.preventDefault()\n          const percent = Number.parseInt(event.key, 10) / 10\n          const seekTime = mediaElement.duration * percent\n          dispatch({\n            type: MediaActionTypes.MEDIA_SEEK_REQUEST,\n            detail: seekTime,\n          })\n          break\n        }\n\n        case 'home': {\n          event.preventDefault()\n          dispatch({\n            type: MediaActionTypes.MEDIA_SEEK_REQUEST,\n            detail: 0,\n          })\n          break\n        }\n\n        case 'end': {\n          event.preventDefault()\n          dispatch({\n            type: MediaActionTypes.MEDIA_SEEK_REQUEST,\n            detail: mediaElement.duration,\n          })\n          break\n        }\n      }\n    },\n    [\n      dispatch,\n      rootImplProps.onKeyDown,\n      onVolumeIndicatorTrigger,\n      onPipError,\n      disabled,\n      isVideo,\n      onControlsShow,\n      autoHide,\n    ]\n  )\n\n  const onKeyUp = React.useCallback(\n    (event: React.KeyboardEvent<RootElement>) => {\n      rootImplProps.onKeyUp?.(event)\n\n      const key = event.key.toLowerCase()\n      if (key === 'arrowup' || key === 'arrowdown' || key === 'm') {\n        onVolumeIndicatorTrigger()\n      }\n    },\n    [rootImplProps.onKeyUp, onVolumeIndicatorTrigger]\n  )\n\n  React.useEffect(() => {\n    const mediaElement = mediaRef.current\n    if (!mediaElement) return\n\n    if (onPlay) mediaElement.addEventListener('play', onPlay)\n    if (onPause) mediaElement.addEventListener('pause', onPause)\n    if (onEnded) mediaElement.addEventListener('ended', onEnded)\n    if (onTimeUpdate)\n      mediaElement.addEventListener('timeupdate', () =>\n        onTimeUpdate?.(mediaElement.currentTime)\n      )\n    if (onVolumeChange)\n      mediaElement.addEventListener('volumechange', () => {\n        onVolumeChange?.(mediaElement.volume)\n        onMuted?.(mediaElement.muted)\n      })\n    if (onMediaError)\n      mediaElement.addEventListener('error', () =>\n        onMediaError?.(mediaElement.error)\n      )\n    if (onFullscreenChange) {\n      document.addEventListener('fullscreenchange', () =>\n        onFullscreenChange?.(!!document.fullscreenElement)\n      )\n    }\n\n    return () => {\n      if (onPlay) mediaElement.removeEventListener('play', onPlay)\n      if (onPause) mediaElement.removeEventListener('pause', onPause)\n      if (onEnded) mediaElement.removeEventListener('ended', onEnded)\n      if (onTimeUpdate)\n        mediaElement.removeEventListener('timeupdate', () =>\n          onTimeUpdate?.(mediaElement.currentTime)\n        )\n      if (onVolumeChange)\n        mediaElement.removeEventListener('volumechange', () => {\n          onVolumeChange?.(mediaElement.volume)\n          onMuted?.(mediaElement.muted)\n        })\n      if (onMediaError)\n        mediaElement.removeEventListener('error', () =>\n          onMediaError?.(mediaElement.error)\n        )\n      if (onFullscreenChange) {\n        document.removeEventListener('fullscreenchange', () =>\n          onFullscreenChange?.(!!document.fullscreenElement)\n        )\n      }\n      if (volumeIndicatorTimeoutRef.current) {\n        clearTimeout(volumeIndicatorTimeoutRef.current)\n      }\n      if (hideControlsTimeoutRef.current) {\n        clearTimeout(hideControlsTimeoutRef.current)\n      }\n    }\n  }, [\n    onPlay,\n    onPause,\n    onEnded,\n    onTimeUpdate,\n    onVolumeChange,\n    onMuted,\n    onMediaError,\n    onFullscreenChange,\n  ])\n\n  const contextValue = React.useMemo<MediaPlayerContextValue>(\n    () => ({\n      mediaId,\n      labelId,\n      descriptionId,\n      dir,\n      rootRef,\n      mediaRef,\n      portalContainer,\n      tooltipDelayDuration,\n      tooltipSideOffset,\n      disabled,\n      isVideo,\n      withoutTooltip,\n    }),\n    [\n      mediaId,\n      labelId,\n      descriptionId,\n      dir,\n      portalContainer,\n      tooltipDelayDuration,\n      tooltipSideOffset,\n      disabled,\n      isVideo,\n      withoutTooltip,\n    ]\n  )\n\n  const RootPrimitive = asChild ? Slot : 'div'\n\n  return (\n    <MediaPlayerContext.Provider value={contextValue}>\n      <RootPrimitive\n        aria-labelledby={labelId}\n        aria-describedby={descriptionId}\n        aria-disabled={disabled}\n        data-disabled={disabled ? '' : undefined}\n        data-controls-visible={controlsVisible ? '' : undefined}\n        data-slot=\"media-player\"\n        data-state={isFullscreen ? 'fullscreen' : 'windowed'}\n        dir={dir}\n        tabIndex={disabled ? undefined : 0}\n        {...rootImplProps}\n        ref={composedRef}\n        onMouseLeave={onMouseLeave}\n        onMouseMove={onMouseMove}\n        onKeyDown={onKeyDown}\n        onKeyUp={onKeyUp}\n        className={cn(\n          'bg-background focus-visible:border-ring focus-visible:ring-ring/50 relative isolate flex flex-col overflow-hidden rounded-lg outline-none focus-visible:ring-[3px] data-disabled:pointer-events-none data-disabled:opacity-50 [&_video]:relative [&_video]:object-contain',\n          'in-[:fullscreen]:flex in-[:fullscreen]:h-full in-[:fullscreen]:max-h-screen in-[:fullscreen]:flex-col in-[:fullscreen]:justify-between data-[state=fullscreen]:[&_video]:size-full',\n          \"**:data-slider:relative [&_[data-slider]::before]:absolute [&_[data-slider]::before]:inset-x-0 [&_[data-slider]::before]:-top-4 [&_[data-slider]::before]:-bottom-2 [&_[data-slider]::before]:z-10 [&_[data-slider]::before]:h-8 [&_[data-slider]::before]:cursor-pointer [&_[data-slider]::before]:content-[''] [&_[data-slot='media-player-seek']:not([data-hovering])::before]:cursor-default\",\n          '[&_video::-webkit-media-text-track-display]:top-auto! [&_video::-webkit-media-text-track-display]:bottom-[4%]! [&_video::-webkit-media-text-track-display]:mb-0! data-controls-visible:[&_video::-webkit-media-text-track-display]:bottom-[13%]! data-[state=fullscreen]:[&_video::-webkit-media-text-track-display]:bottom-[7%]! data-[state=fullscreen]:data-controls-visible:[&_video::-webkit-media-text-track-display]:bottom-[9%]!',\n          className\n        )}\n      >\n        <span id={labelId} className=\"sr-only\">\n          {label ?? 'Media player'}\n        </span>\n        <span id={descriptionId} className=\"sr-only\">\n          {isVideo\n            ? 'Video player with custom controls for playback, volume, seeking, and more. Use space bar to play/pause, arrow keys (←/→) to seek, and arrow keys (↑/↓) to adjust volume.'\n            : 'Audio player with custom controls for playback, volume, seeking, and more. Use space bar to play/pause, Shift + arrow keys (←/→) to seek, and arrow keys (↑/↓) to adjust volume.'}\n        </span>\n        {children}\n        <MediaPlayerVolumeIndicator />\n      </RootPrimitive>\n    </MediaPlayerContext.Provider>\n  )\n}\n\ninterface MediaPlayerVideoProps extends React.ComponentProps<'video'> {\n  asChild?: boolean\n}\n\nfunction MediaPlayerVideo(props: MediaPlayerVideoProps) {\n  const { asChild, ref, onClick, ...videoProps } = props\n\n  const context = useMediaPlayerContext('MediaPlayerVideo')\n  const dispatch = useMediaDispatch()\n  const mediaRefCallback = useMediaRef()\n  const composedRef = useComposedRefs(ref, context.mediaRef, mediaRefCallback)\n\n  const onPlayToggle = React.useCallback(\n    (event: React.MouseEvent<HTMLVideoElement>) => {\n      onClick?.(event)\n\n      if (event.defaultPrevented) return\n\n      const mediaElement = event.currentTarget\n      if (!mediaElement) return\n\n      dispatch({\n        type: mediaElement.paused\n          ? MediaActionTypes.MEDIA_PLAY_REQUEST\n          : MediaActionTypes.MEDIA_PAUSE_REQUEST,\n      })\n    },\n    [dispatch, onClick]\n  )\n\n  const VideoPrimitive = asChild ? Slot : 'video'\n\n  return (\n    <VideoPrimitive\n      aria-describedby={context.descriptionId}\n      aria-labelledby={context.labelId}\n      data-slot=\"media-player-video\"\n      {...videoProps}\n      id={context.mediaId}\n      ref={composedRef}\n      onClick={onPlayToggle}\n    />\n  )\n}\n\ninterface MediaPlayerAudioProps extends React.ComponentProps<'audio'> {\n  asChild?: boolean\n}\n\nfunction MediaPlayerAudio(props: MediaPlayerAudioProps) {\n  const { asChild, ref, ...audioProps } = props\n\n  const context = useMediaPlayerContext('MediaPlayerAudio')\n  const mediaRefCallback = useMediaRef()\n  const composedRef = useComposedRefs(ref, context.mediaRef, mediaRefCallback)\n\n  const AudioPrimitive = asChild ? Slot : 'audio'\n\n  return (\n    <AudioPrimitive\n      aria-describedby={context.descriptionId}\n      aria-labelledby={context.labelId}\n      data-slot=\"media-player-audio\"\n      {...audioProps}\n      id={context.mediaId}\n      ref={composedRef}\n    />\n  )\n}\n\nfunction MediaPlayerControls(props: DivProps) {\n  const { asChild, className, ...controlsProps } = props\n\n  const context = useMediaPlayerContext('MediaPlayerControls')\n  const isFullscreen = useMediaSelector(\n    (state) => state.mediaIsFullscreen ?? false\n  )\n  const controlsVisible = useStore((state) => state.controlsVisible)\n\n  const ControlsPrimitive = asChild ? Slot : 'div'\n\n  return (\n    <ControlsPrimitive\n      data-disabled={context.disabled ? '' : undefined}\n      data-slot=\"media-player-controls\"\n      data-state={isFullscreen ? 'fullscreen' : 'windowed'}\n      data-visible={controlsVisible ? '' : undefined}\n      dir={context.dir}\n      className={cn(\n        'pointer-events-none absolute right-0 bottom-0 left-0 z-50 flex items-center gap-2 px-4 py-3 opacity-0 transition-opacity duration-200 in-[:fullscreen]:px-6 in-[:fullscreen]:py-4 data-visible:pointer-events-auto data-visible:opacity-100',\n        className\n      )}\n      {...controlsProps}\n    />\n  )\n}\n\ninterface MediaPlayerLoadingProps extends DivProps {\n  delayMs?: number\n}\n\nfunction MediaPlayerLoading(props: MediaPlayerLoadingProps) {\n  const { delayMs = 500, asChild, className, children, ...loadingProps } = props\n\n  const isLoading = useMediaSelector((state) => state.mediaLoading ?? false)\n  const isPaused = useMediaSelector((state) => state.mediaPaused ?? true)\n  const hasPlayed = useMediaSelector((state) => state.mediaHasPlayed ?? false)\n\n  const shouldShowLoading = isLoading && !isPaused\n  const shouldUseDelay = hasPlayed && shouldShowLoading\n  const loadingDelayMs = shouldUseDelay ? delayMs : 0\n\n  const [shouldRender, setShouldRender] = React.useState(false)\n  const timeoutRef = React.useRef<NodeJS.Timeout | null>(null)\n\n  React.useEffect(() => {\n    if (timeoutRef.current) {\n      clearTimeout(timeoutRef.current)\n      timeoutRef.current = null\n    }\n\n    if (shouldShowLoading) {\n      if (loadingDelayMs > 0) {\n        timeoutRef.current = setTimeout(() => {\n          setShouldRender(true)\n          timeoutRef.current = null\n        }, loadingDelayMs)\n      } else {\n        setShouldRender(true)\n      }\n    } else {\n      setShouldRender(false)\n    }\n\n    return () => {\n      if (timeoutRef.current) {\n        clearTimeout(timeoutRef.current)\n        timeoutRef.current = null\n      }\n    }\n  }, [shouldShowLoading, loadingDelayMs])\n\n  if (!shouldRender) return null\n\n  const LoadingPrimitive = asChild ? Slot : 'div'\n\n  return (\n    <LoadingPrimitive\n      role=\"status\"\n      aria-live=\"polite\"\n      data-slot=\"media-player-loading\"\n      {...loadingProps}\n      className={cn(\n        'fade-in-0 zoom-in-95 animate-in pointer-events-none absolute inset-0 z-50 flex items-center justify-center duration-200',\n        className\n      )}\n    >\n      {children ?? (\n        <Loader2Icon className=\"text-primary size-20 animate-spin stroke-[.0938rem]\" />\n      )}\n    </LoadingPrimitive>\n  )\n}\n\ninterface MediaPlayerErrorProps extends DivProps {\n  error?: MediaError | null\n  label?: string\n  description?: string\n  onRetry?: () => void\n  onReload?: () => void\n  asChild?: boolean\n}\n\nfunction MediaPlayerError(props: MediaPlayerErrorProps) {\n  const {\n    error: errorProp,\n    label,\n    description,\n    onRetry: onRetryProp,\n    onReload: onReloadProp,\n    asChild,\n    className,\n    children,\n    ...errorProps\n  } = props\n\n  const context = useMediaPlayerContext('MediaPlayerError')\n  const isFullscreen = useMediaSelector(\n    (state) => state.mediaIsFullscreen ?? false\n  )\n  const mediaError = useMediaSelector((state) => state.mediaError)\n\n  const error = errorProp ?? mediaError\n\n  const labelId = React.useId()\n  const descriptionId = React.useId()\n\n  const [actionState, setActionState] = React.useState<{\n    retryPending: boolean\n    reloadPending: boolean\n  }>({\n    retryPending: false,\n    reloadPending: false,\n  })\n\n  const onRetry = React.useCallback(() => {\n    setActionState((prev) => ({ ...prev, retryPending: true }))\n\n    requestAnimationFrame(() => {\n      const mediaElement = context.mediaRef.current\n      if (!mediaElement) {\n        setActionState((prev) => ({ ...prev, retryPending: false }))\n        return\n      }\n\n      if (onRetryProp) {\n        onRetryProp()\n      } else {\n        const currentSrc = mediaElement.currentSrc ?? mediaElement.src\n        if (currentSrc) {\n          mediaElement.load()\n        }\n      }\n\n      setActionState((prev) => ({ ...prev, retryPending: false }))\n    })\n  }, [context.mediaRef, onRetryProp])\n\n  const onReload = React.useCallback(() => {\n    setActionState((prev) => ({ ...prev, reloadPending: true }))\n\n    requestAnimationFrame(() => {\n      if (onReloadProp) {\n        onReloadProp()\n      } else {\n        window.location.reload()\n      }\n    })\n  }, [onReloadProp])\n\n  const errorLabel = React.useMemo(() => {\n    if (label) return label\n\n    if (!error) return 'Playback Error'\n\n    const labelMap: Record<number, string> = {\n      [MediaError.MEDIA_ERR_ABORTED]: 'Playback Interrupted',\n      [MediaError.MEDIA_ERR_NETWORK]: 'Connection Problem',\n      [MediaError.MEDIA_ERR_DECODE]: 'Media Error',\n      [MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED]: 'Unsupported Format',\n    }\n\n    return labelMap[error.code] ?? 'Playback Error'\n  }, [label, error])\n\n  const errorDescription = React.useMemo(() => {\n    if (description) return description\n\n    if (!error) return 'An unknown error occurred'\n\n    const descriptionMap: Record<number, string> = {\n      [MediaError.MEDIA_ERR_ABORTED]: 'Media playback was aborted',\n      [MediaError.MEDIA_ERR_NETWORK]:\n        'A network error occurred while loading the media',\n      [MediaError.MEDIA_ERR_DECODE]:\n        'An error occurred while decoding the media',\n      [MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED]:\n        'The media format is not supported',\n    }\n\n    return descriptionMap[error.code] ?? 'An unknown error occurred'\n  }, [description, error])\n\n  if (!error) return null\n\n  const ErrorPrimitive = asChild ? Slot : 'div'\n\n  return (\n    <ErrorPrimitive\n      role=\"alert\"\n      aria-describedby={descriptionId}\n      aria-labelledby={labelId}\n      aria-live=\"assertive\"\n      data-slot=\"media-player-error\"\n      data-state={isFullscreen ? 'fullscreen' : 'windowed'}\n      {...errorProps}\n      className={cn(\n        'pointer-events-auto absolute inset-0 z-50 flex flex-col items-center justify-center bg-black/80 text-white backdrop-blur-sm',\n        className\n      )}\n    >\n      {children ?? (\n        <div className=\"flex max-w-md flex-col items-center gap-4 px-6 py-8 text-center\">\n          <AlertTriangleIcon className=\"text-destructive size-12\" />\n          <div className=\"flex flex-col gap-px text-center\">\n            <h3 className=\"text-xl font-semibold tracking-tight\">\n              {errorLabel}\n            </h3>\n            <p className=\"text-muted-foreground text-sm leading-relaxed text-balance\">\n              {errorDescription}\n            </p>\n          </div>\n          <div className=\"flex items-center gap-2\">\n            <Button\n              variant=\"secondary\"\n              size=\"sm\"\n              onClick={onRetry}\n              disabled={actionState.retryPending}\n            >\n              {actionState.retryPending ? (\n                <Loader2Icon className=\"animate-spin\" />\n              ) : (\n                <RefreshCcwIcon />\n              )}\n              Try again\n            </Button>\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={onReload}\n              disabled={actionState.reloadPending}\n            >\n              {actionState.reloadPending ? (\n                <Loader2Icon className=\"animate-spin\" />\n              ) : (\n                <RotateCcwIcon />\n              )}\n              Reload page\n            </Button>\n          </div>\n        </div>\n      )}\n    </ErrorPrimitive>\n  )\n}\n\nfunction MediaPlayerVolumeIndicator(props: DivProps) {\n  const { asChild, className, ...indicatorProps } = props\n\n  const mediaVolume = useMediaSelector((state) => state.mediaVolume ?? 1)\n  const mediaMuted = useMediaSelector((state) => state.mediaMuted ?? false)\n  const mediaVolumeLevel = useMediaSelector(\n    (state) => state.mediaVolumeLevel ?? 'high'\n  )\n  const volumeIndicatorVisible = useStore(\n    (state) => state.volumeIndicatorVisible\n  )\n\n  if (!volumeIndicatorVisible) return null\n\n  const effectiveVolume = mediaMuted ? 0 : mediaVolume\n  const volumePercentage = Math.round(effectiveVolume * 100)\n  const barCount = 10\n  const activeBarCount = Math.ceil(effectiveVolume * barCount)\n\n  const VolumeIndicatorPrimitive = asChild ? Slot : 'div'\n\n  return (\n    <VolumeIndicatorPrimitive\n      role=\"status\"\n      aria-live=\"polite\"\n      aria-label={`Volume ${mediaMuted ? 'muted' : `${volumePercentage}%`}`}\n      data-slot=\"media-player-volume-indicator\"\n      {...indicatorProps}\n      className={cn(\n        'pointer-events-none absolute inset-0 z-50 flex items-center justify-center',\n        className\n      )}\n    >\n      <div className=\"fade-in-0 zoom-in-95 animate-in flex flex-col items-center gap-3 rounded-lg bg-black/30 px-6 py-4 text-white backdrop-blur-xs duration-200\">\n        <div className=\"flex items-center gap-2\">\n          {mediaVolumeLevel === 'off' || mediaMuted ? (\n            <VolumeXIcon className=\"size-6\" />\n          ) : mediaVolumeLevel === 'high' ? (\n            <Volume2Icon className=\"size-6\" />\n          ) : (\n            <Volume1Icon className=\"size-6\" />\n          )}\n          <span className=\"text-sm font-medium tabular-nums\">\n            {mediaMuted ? 'Muted' : `${volumePercentage}%`}\n          </span>\n        </div>\n        <div className=\"flex items-center gap-1\">\n          {Array.from({ length: barCount }, (_, index) => (\n            <div\n              key={index}\n              className={cn(\n                'w-1.5 rounded-full transition-all duration-150',\n                index < activeBarCount && !mediaMuted\n                  ? 'scale-100 bg-white'\n                  : 'scale-90 bg-white/30'\n              )}\n              style={{\n                height: `${12 + index * 2}px`,\n                animationDelay: `${index * 50}ms`,\n              }}\n            />\n          ))}\n        </div>\n      </div>\n    </VolumeIndicatorPrimitive>\n  )\n}\n\nfunction MediaPlayerControlsOverlay(props: DivProps) {\n  const { asChild, className, ...overlayProps } = props\n\n  const isFullscreen = useMediaSelector(\n    (state) => state.mediaIsFullscreen ?? false\n  )\n  const controlsVisible = useStore((state) => state.controlsVisible)\n\n  const OverlayPrimitive = asChild ? Slot : 'div'\n\n  return (\n    <OverlayPrimitive\n      data-slot=\"media-player-controls-overlay\"\n      data-state={isFullscreen ? 'fullscreen' : 'windowed'}\n      data-visible={controlsVisible ? '' : undefined}\n      {...overlayProps}\n      className={cn(\n        'pointer-events-none absolute inset-0 -z-10 bg-linear-to-t from-black/80 to-transparent opacity-0 transition-opacity duration-200 data-visible:opacity-100',\n        className\n      )}\n    />\n  )\n}\n\nfunction MediaPlayerPlay(props: React.ComponentProps<typeof Button>) {\n  const { children, className, disabled, onClick, ...playButtonProps } = props\n\n  const context = useMediaPlayerContext('MediaPlayerPlay')\n  const dispatch = useMediaDispatch()\n  const mediaPaused = useMediaSelector((state) => state.mediaPaused ?? true)\n\n  const isDisabled = disabled || context.disabled\n\n  const onPlayToggle = React.useCallback(\n    (event: React.MouseEvent<HTMLButtonElement>) => {\n      onClick?.(event)\n\n      if (event.defaultPrevented) return\n\n      dispatch({\n        type: mediaPaused\n          ? MediaActionTypes.MEDIA_PLAY_REQUEST\n          : MediaActionTypes.MEDIA_PAUSE_REQUEST,\n      })\n    },\n    [dispatch, onClick, mediaPaused]\n  )\n\n  return (\n    <MediaPlayerTooltip\n      tooltip={mediaPaused ? 'Play' : 'Pause'}\n      shortcut=\"Space\"\n    >\n      <Button\n        type=\"button\"\n        aria-controls={context.mediaId}\n        aria-label={mediaPaused ? 'Play' : 'Pause'}\n        aria-pressed={!mediaPaused}\n        data-disabled={isDisabled ? '' : undefined}\n        data-slot=\"media-player-play-button\"\n        data-state={mediaPaused ? 'off' : 'on'}\n        disabled={isDisabled}\n        {...playButtonProps}\n        variant=\"ghost\"\n        size=\"icon\"\n        className={cn(\n          \"size-8 [&_svg:not([class*='fill-'])]:fill-current\",\n          className\n        )}\n        onClick={onPlayToggle}\n      >\n        {children ?? (mediaPaused ? <PlayIcon /> : <PauseIcon />)}\n      </Button>\n    </MediaPlayerTooltip>\n  )\n}\n\ninterface MediaPlayerSeekBackwardProps extends React.ComponentProps<\n  typeof Button\n> {\n  seconds?: number\n}\n\nfunction MediaPlayerSeekBackward(props: MediaPlayerSeekBackwardProps) {\n  const {\n    seconds = SEEK_STEP_SHORT,\n    children,\n    className,\n    disabled,\n    onClick,\n    ...seekBackwardProps\n  } = props\n\n  const context = useMediaPlayerContext('MediaPlayerSeekBackward')\n  const dispatch = useMediaDispatch()\n  const mediaCurrentTime = useMediaSelector(\n    (state) => state.mediaCurrentTime ?? 0\n  )\n\n  const isDisabled = disabled || context.disabled\n\n  const onSeekBackward = React.useCallback(\n    (event: React.MouseEvent<HTMLButtonElement>) => {\n      onClick?.(event)\n\n      if (event.defaultPrevented) return\n\n      dispatch({\n        type: MediaActionTypes.MEDIA_SEEK_REQUEST,\n        detail: Math.max(0, mediaCurrentTime - seconds),\n      })\n    },\n    [dispatch, onClick, mediaCurrentTime, seconds]\n  )\n\n  return (\n    <MediaPlayerTooltip\n      tooltip={`Back ${seconds}s`}\n      shortcut={context.isVideo ? ['←'] : ['Shift ←']}\n    >\n      <Button\n        type=\"button\"\n        aria-controls={context.mediaId}\n        aria-label={`Back ${seconds} seconds`}\n        data-disabled={isDisabled ? '' : undefined}\n        data-slot=\"media-player-seek-backward\"\n        disabled={isDisabled}\n        {...seekBackwardProps}\n        variant=\"ghost\"\n        size=\"icon\"\n        className={cn('size-8', className)}\n        onClick={onSeekBackward}\n      >\n        {children ?? <RewindIcon />}\n      </Button>\n    </MediaPlayerTooltip>\n  )\n}\n\ninterface MediaPlayerSeekForwardProps extends React.ComponentProps<\n  typeof Button\n> {\n  seconds?: number\n}\n\nfunction MediaPlayerSeekForward(props: MediaPlayerSeekForwardProps) {\n  const {\n    seconds = SEEK_STEP_LONG,\n    children,\n    className,\n    disabled,\n    onClick,\n    ...seekForwardProps\n  } = props\n\n  const context = useMediaPlayerContext('MediaPlayerSeekForward')\n  const dispatch = useMediaDispatch()\n  const mediaCurrentTime = useMediaSelector(\n    (state) => state.mediaCurrentTime ?? 0\n  )\n  const [, seekableEnd] = useMediaSelector(\n    (state) => state.mediaSeekable ?? [0, 0]\n  )\n  const isDisabled = disabled || context.disabled\n\n  const onSeekForward = React.useCallback(\n    (event: React.MouseEvent<HTMLButtonElement>) => {\n      onClick?.(event)\n\n      if (event.defaultPrevented) return\n\n      dispatch({\n        type: MediaActionTypes.MEDIA_SEEK_REQUEST,\n        detail: Math.min(\n          seekableEnd ?? Number.POSITIVE_INFINITY,\n          mediaCurrentTime + seconds\n        ),\n      })\n    },\n    [dispatch, onClick, mediaCurrentTime, seekableEnd, seconds]\n  )\n\n  return (\n    <MediaPlayerTooltip\n      tooltip={`Forward ${seconds}s`}\n      shortcut={context.isVideo ? ['→'] : ['Shift →']}\n    >\n      <Button\n        type=\"button\"\n        aria-controls={context.mediaId}\n        aria-label={`Forward ${seconds} seconds`}\n        data-disabled={isDisabled ? '' : undefined}\n        data-slot=\"media-player-seek-forward\"\n        disabled={isDisabled}\n        {...seekForwardProps}\n        variant=\"ghost\"\n        size=\"icon\"\n        className={cn('size-8', className)}\n        onClick={onSeekForward}\n      >\n        {children ?? <FastForwardIcon />}\n      </Button>\n    </MediaPlayerTooltip>\n  )\n}\n\ninterface SeekState {\n  isHovering: boolean\n  pendingSeekTime: number | null\n  hasInitialPosition: boolean\n}\n\ninterface MediaPlayerSeekProps extends React.ComponentProps<\n  typeof SliderPrimitive.Root\n> {\n  withTime?: boolean\n  withoutChapter?: boolean\n  withoutTooltip?: boolean\n  tooltipThumbnailSrc?: string | ((time: number) => string)\n  tooltipTimeVariant?: 'current' | 'progress'\n  tooltipSideOffset?: number\n  tooltipCollisionBoundary?: Element | Element[]\n  tooltipCollisionPadding?:\n    | number\n    | Partial<Record<'top' | 'right' | 'bottom' | 'left', number>>\n}\n\nfunction MediaPlayerSeek(props: MediaPlayerSeekProps) {\n  const {\n    withTime = false,\n    withoutChapter = false,\n    withoutTooltip = false,\n    tooltipTimeVariant = 'current',\n    tooltipThumbnailSrc,\n    tooltipSideOffset,\n    tooltipCollisionPadding = SEEK_COLLISION_PADDING,\n    tooltipCollisionBoundary,\n    className,\n    disabled,\n    ...seekProps\n  } = props\n\n  const context = useMediaPlayerContext(SEEK_NAME)\n  const store = useStoreContext(SEEK_NAME)\n  const dispatch = useMediaDispatch()\n  const mediaCurrentTime = useMediaSelector(\n    (state) => state.mediaCurrentTime ?? 0\n  )\n  const [seekableStart = 0, seekableEnd = 0] = useMediaSelector(\n    (state) => state.mediaSeekable ?? [0, 0]\n  )\n  const mediaBuffered = useMediaSelector((state) => state.mediaBuffered ?? [])\n  const mediaEnded = useMediaSelector((state) => state.mediaEnded ?? false)\n\n  const chapterCues = useMediaSelector((state) => state.mediaChaptersCues ?? [])\n  const mediaPreviewTime = useMediaSelector((state) => state.mediaPreviewTime)\n  const mediaPreviewImage = useMediaSelector((state) => state.mediaPreviewImage)\n  const mediaPreviewCoords = useMediaSelector(\n    (state) => state.mediaPreviewCoords\n  )\n\n  const seekRef = React.useRef<HTMLDivElement>(null)\n  const tooltipRef = React.useRef<HTMLDivElement>(null)\n  const justCommittedRef = React.useRef<boolean>(false)\n\n  const hoverTimeRef = React.useRef(0)\n  const tooltipXRef = React.useRef(0)\n  const tooltipYRef = React.useRef(0)\n  const seekRectRef = React.useRef<DOMRect | null>(null)\n  const collisionDataRef = React.useRef<{\n    padding: { top: number; right: number; bottom: number; left: number }\n    boundaries: Element[]\n  } | null>(null)\n\n  const [seekState, setSeekState] = React.useState<SeekState>({\n    isHovering: false,\n    pendingSeekTime: null,\n    hasInitialPosition: false,\n  })\n\n  const rafIdRef = React.useRef<number | null>(null)\n  const seekThrottleRef = React.useRef<number | null>(null)\n  const hoverTimeoutRef = React.useRef<number | null>(null)\n  const lastPointerXRef = React.useRef<number>(0)\n  const lastPointerYRef = React.useRef<number>(0)\n  const previewDebounceRef = React.useRef<number | null>(null)\n  const pointerEnterTimeRef = React.useRef<number>(0)\n  const horizontalMovementRef = React.useRef<number>(0)\n  const verticalMovementRef = React.useRef<number>(0)\n  const lastSeekCommitTimeRef = React.useRef<number>(0)\n\n  const timeCache = React.useRef<Map<number, string>>(new Map())\n\n  const displayValue = seekState.pendingSeekTime ?? mediaCurrentTime\n\n  const isDisabled = disabled || context.disabled\n  const tooltipDisabled =\n    withoutTooltip || context.withoutTooltip || store.getState().menuOpen\n\n  const currentTooltipSideOffset =\n    tooltipSideOffset ?? context.tooltipSideOffset\n\n  const getCachedTime = React.useCallback((time: number, duration: number) => {\n    const roundedTime = Math.floor(time)\n    const key = roundedTime + duration * 10000\n\n    if (timeCache.current.has(key)) {\n      return timeCache.current.get(key) as string\n    }\n\n    const formatted = timeUtils.formatTime(time, duration)\n    timeCache.current.set(key, formatted)\n\n    if (timeCache.current.size > 100) {\n      timeCache.current.clear()\n    }\n\n    return formatted\n  }, [])\n\n  const currentTime = getCachedTime(displayValue, seekableEnd)\n  const duration = getCachedTime(seekableEnd, seekableEnd)\n  const remainingTime = getCachedTime(seekableEnd - displayValue, seekableEnd)\n\n  const onCollisionDataUpdate = React.useCallback(() => {\n    if (collisionDataRef.current) return collisionDataRef.current\n\n    const padding =\n      typeof tooltipCollisionPadding === 'number'\n        ? {\n            top: tooltipCollisionPadding,\n            right: tooltipCollisionPadding,\n            bottom: tooltipCollisionPadding,\n            left: tooltipCollisionPadding,\n          }\n        : { top: 0, right: 0, bottom: 0, left: 0, ...tooltipCollisionPadding }\n\n    const boundaries = tooltipCollisionBoundary\n      ? Array.isArray(tooltipCollisionBoundary)\n        ? tooltipCollisionBoundary\n        : [tooltipCollisionBoundary]\n      : ([context.rootRef.current].filter(Boolean) as Element[])\n\n    collisionDataRef.current = { padding, boundaries }\n    return collisionDataRef.current\n  }, [tooltipCollisionPadding, tooltipCollisionBoundary, context.rootRef])\n\n  const getCurrentChapterCue = React.useCallback(\n    (time: number) => {\n      if (withoutChapter || chapterCues.length === 0) return null\n      return chapterCues.find((c) => time >= c.startTime && time < c.endTime)\n    },\n    [chapterCues, withoutChapter]\n  )\n\n  const getThumbnail = React.useCallback(\n    (time: number) => {\n      if (tooltipDisabled) return null\n\n      if (tooltipThumbnailSrc) {\n        const src =\n          typeof tooltipThumbnailSrc === 'function'\n            ? tooltipThumbnailSrc(time)\n            : tooltipThumbnailSrc\n        return { src, coords: null }\n      }\n\n      if (\n        mediaPreviewTime !== undefined &&\n        Math.abs(time - mediaPreviewTime) < 0.1 &&\n        mediaPreviewImage\n      ) {\n        return {\n          src: mediaPreviewImage,\n          coords: mediaPreviewCoords ?? null,\n        }\n      }\n\n      return null\n    },\n    [\n      tooltipThumbnailSrc,\n      mediaPreviewTime,\n      mediaPreviewImage,\n      mediaPreviewCoords,\n      tooltipDisabled,\n    ]\n  )\n\n  const onPreviewUpdate = React.useCallback(\n    (time: number) => {\n      if (tooltipDisabled) return\n\n      if (previewDebounceRef.current) {\n        cancelAnimationFrame(previewDebounceRef.current)\n      }\n\n      previewDebounceRef.current = requestAnimationFrame(() => {\n        dispatch({\n          type: MediaActionTypes.MEDIA_PREVIEW_REQUEST,\n          detail: time,\n        })\n        previewDebounceRef.current = null\n      })\n    },\n    [dispatch, tooltipDisabled]\n  )\n\n  const onTooltipPositionUpdate = React.useCallback(\n    (clientX: number) => {\n      if (!seekRef.current) return\n\n      const tooltipWidth =\n        tooltipRef.current?.offsetWidth ?? SEEK_TOOLTIP_WIDTH_FALLBACK\n\n      let x = clientX\n      const y = seekRectRef.current?.top ?? 0\n\n      const collisionData = onCollisionDataUpdate()\n      const halfTooltipWidth = tooltipWidth / 2\n\n      let minLeft = 0\n      let maxRight = window.innerWidth\n\n      for (const boundary of collisionData.boundaries) {\n        const boundaryRect = boundary.getBoundingClientRect()\n        minLeft = Math.max(\n          minLeft,\n          boundaryRect.left + collisionData.padding.left\n        )\n        maxRight = Math.min(\n          maxRight,\n          boundaryRect.right - collisionData.padding.right\n        )\n      }\n\n      if (x - halfTooltipWidth < minLeft) {\n        x = minLeft + halfTooltipWidth\n      } else if (x + halfTooltipWidth > maxRight) {\n        x = maxRight - halfTooltipWidth\n      }\n\n      const viewportPadding = SEEK_COLLISION_PADDING\n      if (x - halfTooltipWidth < viewportPadding) {\n        x = viewportPadding + halfTooltipWidth\n      } else if (x + halfTooltipWidth > window.innerWidth - viewportPadding) {\n        x = window.innerWidth - viewportPadding - halfTooltipWidth\n      }\n\n      tooltipXRef.current = x\n      tooltipYRef.current = y\n\n      if (tooltipRef.current) {\n        tooltipRef.current.style.setProperty(SEEK_TOOLTIP_X, `${x}px`)\n        tooltipRef.current.style.setProperty(SEEK_TOOLTIP_Y, `${y}px`)\n      }\n\n      if (!seekState.hasInitialPosition) {\n        setSeekState((prev) => ({ ...prev, hasInitialPosition: true }))\n      }\n    },\n    [onCollisionDataUpdate, seekState.hasInitialPosition]\n  )\n\n  const onHoverProgressUpdate = React.useCallback(() => {\n    if (!seekRef.current || seekableEnd <= 0) return\n\n    const hoverPercent = Math.min(\n      100,\n      (hoverTimeRef.current / seekableEnd) * 100\n    )\n    seekRef.current.style.setProperty(\n      SEEK_HOVER_PERCENT,\n      `${hoverPercent.toFixed(4)}%`\n    )\n  }, [seekableEnd])\n\n  React.useEffect(() => {\n    if (seekState.pendingSeekTime !== null) {\n      const diff = Math.abs(mediaCurrentTime - seekState.pendingSeekTime)\n      if (diff < 0.5) {\n        setSeekState((prev) => ({ ...prev, pendingSeekTime: null }))\n      }\n    }\n  }, [mediaCurrentTime, seekState.pendingSeekTime])\n\n  React.useEffect(() => {\n    if (!seekState.isHovering || tooltipDisabled) return\n\n    function onScroll() {\n      setSeekState((prev) => ({\n        ...prev,\n        isHovering: false,\n        hasInitialPosition: false,\n      }))\n      dispatch({\n        type: MediaActionTypes.MEDIA_PREVIEW_REQUEST,\n        detail: undefined,\n      })\n    }\n\n    document.addEventListener('scroll', onScroll, { passive: true })\n    return () => {\n      document.removeEventListener('scroll', onScroll)\n    }\n  }, [dispatch, seekState.isHovering, tooltipDisabled])\n\n  const bufferedProgress = React.useMemo(() => {\n    if (mediaBuffered.length === 0 || seekableEnd <= 0) return 0\n\n    if (mediaEnded) return 1\n\n    const containingRange = mediaBuffered.find(\n      ([start, end]) => start <= mediaCurrentTime && mediaCurrentTime <= end\n    )\n\n    if (containingRange) {\n      return Math.min(1, containingRange[1] / seekableEnd)\n    }\n\n    return Math.min(1, seekableStart / seekableEnd)\n  }, [mediaBuffered, mediaCurrentTime, seekableEnd, mediaEnded, seekableStart])\n\n  const onPointerEnter = React.useCallback(() => {\n    if (seekRef.current) {\n      seekRectRef.current = seekRef.current.getBoundingClientRect()\n    }\n\n    collisionDataRef.current = null\n    pointerEnterTimeRef.current = Date.now()\n    horizontalMovementRef.current = 0\n    verticalMovementRef.current = 0\n\n    if (seekableEnd > 0) {\n      if (hoverTimeoutRef.current) {\n        clearTimeout(hoverTimeoutRef.current)\n      }\n\n      if (!tooltipDisabled) {\n        if (lastPointerXRef.current && seekRectRef.current) {\n          const clientX = Math.max(\n            seekRectRef.current.left,\n            Math.min(lastPointerXRef.current, seekRectRef.current.right)\n          )\n          onTooltipPositionUpdate(clientX)\n        }\n      }\n    }\n  }, [seekableEnd, onTooltipPositionUpdate, tooltipDisabled])\n\n  const onPointerLeave = React.useCallback(() => {\n    if (hoverTimeoutRef.current) {\n      clearTimeout(hoverTimeoutRef.current)\n      hoverTimeoutRef.current = null\n    }\n    if (rafIdRef.current) {\n      cancelAnimationFrame(rafIdRef.current)\n      rafIdRef.current = null\n    }\n    if (previewDebounceRef.current) {\n      cancelAnimationFrame(previewDebounceRef.current)\n      previewDebounceRef.current = null\n    }\n\n    setSeekState((prev) => ({\n      ...prev,\n      isHovering: false,\n      hasInitialPosition: false,\n    }))\n\n    justCommittedRef.current = false\n    seekRectRef.current = null\n    collisionDataRef.current = null\n\n    pointerEnterTimeRef.current = 0\n    horizontalMovementRef.current = 0\n    verticalMovementRef.current = 0\n    lastPointerXRef.current = 0\n    lastPointerYRef.current = 0\n    lastSeekCommitTimeRef.current = 0\n\n    if (!tooltipDisabled) {\n      dispatch({\n        type: MediaActionTypes.MEDIA_PREVIEW_REQUEST,\n        detail: undefined,\n      })\n    }\n  }, [dispatch, tooltipDisabled])\n\n  const onPointerMove = React.useCallback(\n    (event: React.PointerEvent<HTMLDivElement>) => {\n      if (seekableEnd <= 0) return\n\n      if (!seekRectRef.current && seekRef.current) {\n        seekRectRef.current = seekRef.current.getBoundingClientRect()\n      }\n\n      if (!seekRectRef.current) return\n\n      const currentX = event.clientX\n      const currentY = event.clientY\n\n      if (lastPointerXRef.current !== 0 && lastPointerYRef.current !== 0) {\n        const deltaX = Math.abs(currentX - lastPointerXRef.current)\n        const deltaY = Math.abs(currentY - lastPointerYRef.current)\n\n        horizontalMovementRef.current += deltaX\n        verticalMovementRef.current += deltaY\n      }\n\n      lastPointerXRef.current = currentX\n      lastPointerYRef.current = currentY\n\n      if (rafIdRef.current) {\n        cancelAnimationFrame(rafIdRef.current)\n      }\n\n      rafIdRef.current = requestAnimationFrame(() => {\n        const wasJustCommitted = justCommittedRef.current\n        if (wasJustCommitted) {\n          justCommittedRef.current = false\n        }\n\n        const seekRect = seekRectRef.current\n        if (!seekRect) {\n          rafIdRef.current = null\n          return\n        }\n\n        const clientX = lastPointerXRef.current\n        const offsetXOnSeekBar = Math.max(\n          0,\n          Math.min(clientX - seekRect.left, seekRect.width)\n        )\n        const relativeX = offsetXOnSeekBar / seekRect.width\n        const calculatedHoverTime = relativeX * seekableEnd\n\n        hoverTimeRef.current = calculatedHoverTime\n\n        onHoverProgressUpdate()\n\n        const wasHovering = seekState.isHovering\n        const isCurrentlyHovering =\n          clientX >= seekRect.left && clientX <= seekRect.right\n\n        const timeHovering = Date.now() - pointerEnterTimeRef.current\n        const totalMovement =\n          horizontalMovementRef.current + verticalMovementRef.current\n        const horizontalRatio =\n          totalMovement > 0 ? horizontalMovementRef.current / totalMovement : 0\n\n        const timeSinceSeekCommit = Date.now() - lastSeekCommitTimeRef.current\n        const isInSeekCooldown = timeSinceSeekCommit < 300\n\n        const shouldShowTooltip =\n          !wasJustCommitted &&\n          !isInSeekCooldown &&\n          (timeHovering > 150 ||\n            horizontalRatio > 0.6 ||\n            (totalMovement < 10 && timeHovering > 50))\n\n        if (\n          !wasHovering &&\n          isCurrentlyHovering &&\n          shouldShowTooltip &&\n          !tooltipDisabled\n        ) {\n          setSeekState((prev) => ({ ...prev, isHovering: true }))\n        }\n\n        if (!tooltipDisabled) {\n          onPreviewUpdate(calculatedHoverTime)\n\n          if (isCurrentlyHovering && (wasHovering || shouldShowTooltip)) {\n            onTooltipPositionUpdate(clientX)\n          }\n        }\n\n        rafIdRef.current = null\n      })\n    },\n    [\n      onPreviewUpdate,\n      onTooltipPositionUpdate,\n      onHoverProgressUpdate,\n      seekableEnd,\n      seekState.isHovering,\n      tooltipDisabled,\n    ]\n  )\n\n  const onSeek = React.useCallback(\n    (value: number[]) => {\n      const time = value[0] ?? 0\n\n      setSeekState((prev) => ({ ...prev, pendingSeekTime: time }))\n\n      if (!store.getState().dragging) {\n        store.setState('dragging', true)\n      }\n\n      if (seekThrottleRef.current) {\n        cancelAnimationFrame(seekThrottleRef.current)\n      }\n\n      seekThrottleRef.current = requestAnimationFrame(() => {\n        dispatch({\n          type: MediaActionTypes.MEDIA_SEEK_REQUEST,\n          detail: time,\n        })\n        seekThrottleRef.current = null\n      })\n    },\n    [dispatch, store.getState, store.setState]\n  )\n\n  const onSeekCommit = React.useCallback(\n    (value: number[]) => {\n      const time = value[0] ?? 0\n\n      if (seekThrottleRef.current) {\n        cancelAnimationFrame(seekThrottleRef.current)\n        seekThrottleRef.current = null\n      }\n\n      if (hoverTimeoutRef.current) {\n        clearTimeout(hoverTimeoutRef.current)\n        hoverTimeoutRef.current = null\n      }\n      if (rafIdRef.current) {\n        cancelAnimationFrame(rafIdRef.current)\n        rafIdRef.current = null\n      }\n      if (previewDebounceRef.current) {\n        cancelAnimationFrame(previewDebounceRef.current)\n        previewDebounceRef.current = null\n      }\n\n      setSeekState((prev) => ({\n        ...prev,\n        pendingSeekTime: time,\n        isHovering: false,\n        hasInitialPosition: false,\n      }))\n\n      justCommittedRef.current = true\n      collisionDataRef.current = null\n      lastSeekCommitTimeRef.current = Date.now()\n\n      // Reset movement tracking after seek commit\n      pointerEnterTimeRef.current = Date.now()\n      horizontalMovementRef.current = 0\n      verticalMovementRef.current = 0\n\n      if (store.getState().dragging) {\n        store.setState('dragging', false)\n      }\n\n      dispatch({\n        type: MediaActionTypes.MEDIA_SEEK_REQUEST,\n        detail: time,\n      })\n\n      dispatch({\n        type: MediaActionTypes.MEDIA_PREVIEW_REQUEST,\n        detail: undefined,\n      })\n    },\n    [dispatch, store.getState, store.setState]\n  )\n\n  React.useEffect(() => {\n    return () => {\n      if (seekThrottleRef.current) {\n        cancelAnimationFrame(seekThrottleRef.current)\n      }\n      if (hoverTimeoutRef.current) {\n        clearTimeout(hoverTimeoutRef.current)\n      }\n      if (rafIdRef.current) {\n        cancelAnimationFrame(rafIdRef.current)\n      }\n      if (previewDebounceRef.current) {\n        cancelAnimationFrame(previewDebounceRef.current)\n      }\n    }\n  }, [])\n\n  const currentChapterCue = getCurrentChapterCue(hoverTimeRef.current)\n  const thumbnail = getThumbnail(hoverTimeRef.current)\n  const hoverTime = getCachedTime(hoverTimeRef.current, seekableEnd)\n\n  const chapterSeparators = React.useMemo(() => {\n    if (withoutChapter || chapterCues.length <= 1 || seekableEnd <= 0) {\n      return null\n    }\n\n    return chapterCues.slice(1).map((chapterCue, index) => {\n      const position = (chapterCue.startTime / seekableEnd) * 100\n\n      return (\n        <div\n          key={`chapter-${index}-${chapterCue.startTime}`}\n          role=\"presentation\"\n          aria-hidden=\"true\"\n          data-slot=\"media-player-seek-chapter-separator\"\n          className=\"absolute top-0 h-full bg-zinc-50 dark:bg-zinc-950\"\n          style={{\n            width: '.1563rem',\n            left: `${position}%`,\n            transform: 'translateX(-50%)',\n          }}\n        />\n      )\n    })\n  }, [chapterCues, seekableEnd, withoutChapter])\n\n  const spriteStyle = React.useMemo<React.CSSProperties>(() => {\n    if (!thumbnail?.coords || !thumbnail?.src) {\n      return {}\n    }\n\n    const coordX = thumbnail.coords[0]\n    const coordY = thumbnail.coords[1]\n\n    const spriteWidth = Number.parseFloat(thumbnail.coords[2] ?? '0')\n    const spriteHeight = Number.parseFloat(thumbnail.coords[3] ?? '0')\n\n    const scaleX = spriteWidth > 0 ? SPRITE_CONTAINER_WIDTH / spriteWidth : 1\n    const scaleY = spriteHeight > 0 ? SPRITE_CONTAINER_HEIGHT / spriteHeight : 1\n    const scale = Math.min(scaleX, scaleY)\n\n    return {\n      width: `${spriteWidth}px`,\n      height: `${spriteHeight}px`,\n      backgroundImage: `url(${thumbnail.src})`,\n      backgroundPosition: `-${coordX}px -${coordY}px`,\n      backgroundRepeat: 'no-repeat',\n      transform: `scale(${scale})`,\n      transformOrigin: 'top left',\n    }\n  }, [thumbnail?.coords, thumbnail?.src])\n\n  const SeekSlider = (\n    <div data-slot=\"media-player-seek-container\" className=\"relative w-full\">\n      <SliderPrimitive.Root\n        aria-controls={context.mediaId}\n        aria-valuetext={`${currentTime} of ${duration}`}\n        data-hovering={seekState.isHovering ? '' : undefined}\n        data-slider=\"\"\n        data-slot=\"media-player-seek\"\n        disabled={isDisabled}\n        {...seekProps}\n        ref={seekRef}\n        min={seekableStart}\n        max={seekableEnd}\n        step={0.01}\n        className={cn(\n          'relative flex w-full touch-none items-center select-none data-disabled:pointer-events-none data-disabled:opacity-50',\n          className\n        )}\n        value={[displayValue]}\n        onValueChange={onSeek}\n        onValueCommit={onSeekCommit}\n        onPointerEnter={onPointerEnter}\n        onPointerLeave={onPointerLeave}\n        onPointerMove={onPointerMove}\n      >\n        <SliderPrimitive.Track className=\"bg-primary/40 relative h-1 w-full grow overflow-hidden rounded-full\">\n          <div\n            data-slot=\"media-player-seek-buffered\"\n            className=\"bg-primary/70 absolute h-full will-change-[width]\"\n            style={{\n              width: `${bufferedProgress * 100}%`,\n            }}\n          />\n          <SliderPrimitive.Range className=\"bg-primary absolute h-full will-change-[width]\" />\n          {seekState.isHovering && seekableEnd > 0 && (\n            <div\n              data-slot=\"media-player-seek-hover-range\"\n              className=\"bg-primary/70 absolute h-full will-change-[width,opacity]\"\n              style={{\n                width: `var(${SEEK_HOVER_PERCENT}, 0%)`,\n                transition: 'opacity 150ms ease-out',\n              }}\n            />\n          )}\n          {chapterSeparators}\n        </SliderPrimitive.Track>\n        <SliderPrimitive.Thumb className=\"bg-primary ring-ring/50 relative z-10 block size-2.5 shrink-0 rounded-full shadow-sm transition-[color,box-shadow] will-change-transform hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50\" />\n      </SliderPrimitive.Root>\n      {!withoutTooltip &&\n        !context.withoutTooltip &&\n        seekState.isHovering &&\n        seekableEnd > 0 && (\n          <MediaPlayerPortal>\n            <div\n              ref={tooltipRef}\n              className=\"pointer-events-none z-50 contain-[layout_style] [transition:opacity_150ms_ease-in-out] backface-hidden\"\n              style={{\n                position: 'fixed' as const,\n                left: `var(${SEEK_TOOLTIP_X}, 0rem)`,\n                top: `var(${SEEK_TOOLTIP_Y}, 0rem)`,\n                transform: `translateX(-50%) translateY(calc(-100% - ${currentTooltipSideOffset}px))`,\n                visibility: seekState.hasInitialPosition ? 'visible' : 'hidden',\n                opacity: seekState.hasInitialPosition ? 1 : 0,\n              }}\n            >\n              <div\n                className={cn(\n                  'bg-background text-foreground flex flex-col items-center gap-1.5 rounded-md border shadow-sm dark:bg-zinc-900',\n                  thumbnail && 'min-h-10',\n                  !thumbnail && currentChapterCue && 'px-3 py-1.5'\n                )}\n              >\n                {thumbnail?.src && (\n                  <div\n                    data-slot=\"media-player-seek-thumbnail\"\n                    className=\"overflow-hidden rounded-md rounded-b-none\"\n                    style={{\n                      width: `${SPRITE_CONTAINER_WIDTH}px`,\n                      height: `${SPRITE_CONTAINER_HEIGHT}px`,\n                    }}\n                  >\n                    {thumbnail.coords ? (\n                      <div style={spriteStyle} />\n                    ) : (\n                      // biome-ignore lint/performance/noImgElement: dynamic thumbnail URLs from media don't work well with Next.js Image optimization\n                      <img\n                        src={thumbnail.src}\n                        alt={`Preview at ${hoverTime}`}\n                        className=\"size-full object-cover\"\n                      />\n                    )}\n                  </div>\n                )}\n                {currentChapterCue && (\n                  <div\n                    data-slot=\"media-player-seek-chapter-title\"\n                    className=\"line-clamp-2 max-w-48 text-center text-xs text-balance\"\n                  >\n                    {currentChapterCue.text}\n                  </div>\n                )}\n                <div\n                  data-slot=\"media-player-seek-time\"\n                  className={cn(\n                    'text-center text-xs whitespace-nowrap tabular-nums',\n                    thumbnail && 'pb-1.5',\n                    !(thumbnail || currentChapterCue) && 'px-2.5 py-1'\n                  )}\n                >\n                  {tooltipTimeVariant === 'progress'\n                    ? `${hoverTime} / ${duration}`\n                    : hoverTime}\n                </div>\n              </div>\n            </div>\n          </MediaPlayerPortal>\n        )}\n    </div>\n  )\n\n  if (withTime) {\n    return (\n      <div className=\"flex w-full items-center gap-2\">\n        <span className=\"text-sm tabular-nums\">{currentTime}</span>\n        {SeekSlider}\n        <span className=\"text-sm tabular-nums\">{remainingTime}</span>\n      </div>\n    )\n  }\n\n  return SeekSlider\n}\n\ninterface MediaPlayerVolumeProps extends React.ComponentProps<\n  typeof SliderPrimitive.Root\n> {\n  asChild?: boolean\n  expandable?: boolean\n}\n\nfunction MediaPlayerVolume(props: MediaPlayerVolumeProps) {\n  const { expandable = false, className, disabled, ...volumeProps } = props\n\n  const context = useMediaPlayerContext(VOLUME_NAME)\n  const store = useStoreContext(VOLUME_NAME)\n  const dispatch = useMediaDispatch()\n  const mediaVolume = useMediaSelector((state) => state.mediaVolume ?? 1)\n  const mediaMuted = useMediaSelector((state) => state.mediaMuted ?? false)\n  const mediaVolumeLevel = useMediaSelector(\n    (state) => state.mediaVolumeLevel ?? 'high'\n  )\n\n  const sliderId = React.useId()\n  const volumeTriggerId = React.useId()\n\n  const isDisabled = disabled || context.disabled\n\n  const onMute = React.useCallback(() => {\n    dispatch({\n      type: mediaMuted\n        ? MediaActionTypes.MEDIA_UNMUTE_REQUEST\n        : MediaActionTypes.MEDIA_MUTE_REQUEST,\n    })\n  }, [dispatch, mediaMuted])\n\n  const onVolumeChange = React.useCallback(\n    (value: number[]) => {\n      const volume = value[0] ?? 0\n\n      if (!store.getState().dragging) {\n        store.setState('dragging', true)\n      }\n\n      dispatch({\n        type: MediaActionTypes.MEDIA_VOLUME_REQUEST,\n        detail: volume,\n      })\n    },\n    [dispatch, store.getState, store.setState]\n  )\n\n  const onVolumeCommit = React.useCallback(\n    (value: number[]) => {\n      const volume = value[0] ?? 0\n\n      if (store.getState().dragging) {\n        store.setState('dragging', false)\n      }\n\n      dispatch({\n        type: MediaActionTypes.MEDIA_VOLUME_REQUEST,\n        detail: volume,\n      })\n    },\n    [dispatch, store]\n  )\n\n  const effectiveVolume = mediaMuted ? 0 : mediaVolume\n\n  return (\n    <div\n      data-disabled={isDisabled ? '' : undefined}\n      data-slot=\"media-player-volume-container\"\n      className={cn(\n        'group flex items-center',\n        expandable\n          ? 'gap-0 group-focus-within:gap-2 group-hover:gap-1.5'\n          : 'gap-1.5',\n        className\n      )}\n    >\n      <MediaPlayerTooltip tooltip=\"Volume\" shortcut=\"M\">\n        <Button\n          id={volumeTriggerId}\n          type=\"button\"\n          aria-controls={`${context.mediaId} ${sliderId}`}\n          aria-label={mediaMuted ? 'Unmute' : 'Mute'}\n          aria-pressed={mediaMuted}\n          data-slot=\"media-player-volume-trigger\"\n          data-state={mediaMuted ? 'on' : 'off'}\n          variant=\"ghost\"\n          size=\"icon\"\n          className=\"size-8\"\n          disabled={isDisabled}\n          onClick={onMute}\n        >\n          {mediaVolumeLevel === 'off' || mediaMuted ? (\n            <VolumeXIcon />\n          ) : mediaVolumeLevel === 'high' ? (\n            <Volume2Icon />\n          ) : (\n            <Volume1Icon />\n          )}\n        </Button>\n      </MediaPlayerTooltip>\n      <SliderPrimitive.Root\n        id={sliderId}\n        aria-controls={context.mediaId}\n        aria-valuetext={`${Math.round(effectiveVolume * 100)}% volume`}\n        data-slider=\"\"\n        data-slot=\"media-player-volume\"\n        {...volumeProps}\n        min={0}\n        max={1}\n        step={0.1}\n        className={cn(\n          'relative flex touch-none items-center select-none',\n          expandable\n            ? 'w-0 opacity-0 transition-[width,opacity] duration-200 ease-in-out group-focus-within:w-16 group-focus-within:opacity-100 group-hover:w-16 group-hover:opacity-100'\n            : 'w-16',\n          className\n        )}\n        disabled={isDisabled}\n        value={[effectiveVolume]}\n        onValueChange={onVolumeChange}\n        onValueCommit={onVolumeCommit}\n      >\n        <SliderPrimitive.Track className=\"relative h-1 w-full grow overflow-hidden rounded-full bg-zinc-500\">\n          <SliderPrimitive.Range className=\"bg-primary absolute h-full will-change-[width]\" />\n        </SliderPrimitive.Track>\n        <SliderPrimitive.Thumb className=\"bg-primary ring-ring/50 block size-2.5 shrink-0 rounded-full shadow-sm transition-[color,box-shadow] will-change-transform hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50\" />\n      </SliderPrimitive.Root>\n    </div>\n  )\n}\n\ninterface MediaPlayerTimeProps extends React.ComponentProps<'div'> {\n  variant?: 'progress' | 'remaining' | 'duration'\n  asChild?: boolean\n}\n\nfunction MediaPlayerTime(props: MediaPlayerTimeProps) {\n  const { variant = 'progress', asChild, className, ...timeProps } = props\n\n  const context = useMediaPlayerContext('MediaPlayerTime')\n  const mediaCurrentTime = useMediaSelector(\n    (state) => state.mediaCurrentTime ?? 0\n  )\n  const [, seekableEnd = 0] = useMediaSelector(\n    (state) => state.mediaSeekable ?? [0, 0]\n  )\n\n  const times = React.useMemo(() => {\n    if (variant === 'remaining') {\n      return {\n        remaining: timeUtils.formatTime(\n          seekableEnd - mediaCurrentTime,\n          seekableEnd\n        ),\n      }\n    }\n\n    if (variant === 'duration') {\n      return {\n        duration: timeUtils.formatTime(seekableEnd, seekableEnd),\n      }\n    }\n\n    return {\n      current: timeUtils.formatTime(mediaCurrentTime, seekableEnd),\n      duration: timeUtils.formatTime(seekableEnd, seekableEnd),\n    }\n  }, [variant, mediaCurrentTime, seekableEnd])\n\n  const TimePrimitive = asChild ? Slot : 'div'\n\n  if (variant === 'remaining' || variant === 'duration') {\n    return (\n      <TimePrimitive\n        data-slot=\"media-player-time\"\n        data-variant={variant}\n        dir={context.dir}\n        {...timeProps}\n        className={cn('text-foreground/80 text-sm tabular-nums', className)}\n      >\n        {times[variant]}\n      </TimePrimitive>\n    )\n  }\n\n  return (\n    <TimePrimitive\n      data-slot=\"media-player-time\"\n      data-variant={variant}\n      dir={context.dir}\n      {...timeProps}\n      className={cn(\n        'text-foreground/80 flex items-center gap-1 text-sm',\n        className\n      )}\n    >\n      <span className=\"tabular-nums\">{times.current}</span>\n      <span role=\"separator\" aria-hidden=\"true\" aria-valuenow={0} tabIndex={-1}>\n        /\n      </span>\n      <span className=\"tabular-nums\">{times.duration}</span>\n    </TimePrimitive>\n  )\n}\n\ninterface MediaPlayerPlaybackSpeedProps\n  extends\n    React.ComponentProps<typeof DropdownMenuTrigger>,\n    React.ComponentProps<typeof Button>,\n    Omit<React.ComponentProps<typeof DropdownMenu>, 'dir'>,\n    Pick<React.ComponentProps<typeof DropdownMenuContent>, 'sideOffset'> {\n  speeds?: number[]\n}\n\nfunction MediaPlayerPlaybackSpeed(props: MediaPlayerPlaybackSpeedProps) {\n  const {\n    open,\n    defaultOpen,\n    onOpenChange: onOpenChangeProp,\n    sideOffset = FLOATING_MENU_SIDE_OFFSET,\n    speeds = SPEEDS,\n    modal = false,\n    className,\n    disabled,\n    ...playbackSpeedProps\n  } = props\n\n  const context = useMediaPlayerContext(PLAYBACK_SPEED_NAME)\n  const store = useStoreContext(PLAYBACK_SPEED_NAME)\n  const dispatch = useMediaDispatch()\n  const mediaPlaybackRate = useMediaSelector(\n    (state) => state.mediaPlaybackRate ?? 1\n  )\n\n  const isDisabled = disabled || context.disabled\n\n  const onPlaybackRateChange = React.useCallback(\n    (rate: number) => {\n      dispatch({\n        type: MediaActionTypes.MEDIA_PLAYBACK_RATE_REQUEST,\n        detail: rate,\n      })\n    },\n    [dispatch]\n  )\n\n  const onOpenChange = React.useCallback(\n    (open: boolean) => {\n      store.setState('menuOpen', open)\n      onOpenChangeProp?.(open)\n    },\n    [store.setState, onOpenChangeProp]\n  )\n\n  return (\n    <DropdownMenu\n      modal={modal}\n      open={open}\n      defaultOpen={defaultOpen}\n      onOpenChange={onOpenChange}\n    >\n      <MediaPlayerTooltip tooltip=\"Playback speed\" shortcut={['<', '>']}>\n        <DropdownMenuTrigger asChild>\n          <Button\n            type=\"button\"\n            aria-controls={context.mediaId}\n            disabled={isDisabled}\n            {...playbackSpeedProps}\n            variant=\"ghost\"\n            size=\"icon\"\n            className={cn('aria-expanded:bg-accent/50 h-8 w-16', className)}\n          >\n            {mediaPlaybackRate}x\n          </Button>\n        </DropdownMenuTrigger>\n      </MediaPlayerTooltip>\n      <DropdownMenuContent\n        container={context.portalContainer}\n        sideOffset={sideOffset}\n        align=\"center\"\n        className=\"min-w-(--radix-dropdown-menu-trigger-width) data-[side=top]:mb-3.5\"\n      >\n        {speeds.map((speed) => (\n          <DropdownMenuItem\n            key={speed}\n            className=\"justify-between\"\n            onSelect={() => onPlaybackRateChange(speed)}\n          >\n            {speed}x{mediaPlaybackRate === speed && <CheckIcon />}\n          </DropdownMenuItem>\n        ))}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n\ninterface MediaPlayerLoopProps extends React.ComponentProps<typeof Button> {}\n\nfunction MediaPlayerLoop(props: MediaPlayerLoopProps) {\n  const { children, className, disabled, onClick, ...loopProps } = props\n\n  const context = useMediaPlayerContext('MediaPlayerLoop')\n  const isDisabled = disabled || context.disabled\n\n  const [isLooping, setIsLooping] = React.useState(() => {\n    const mediaElement = context.mediaRef.current\n    return mediaElement?.loop ?? false\n  })\n\n  React.useEffect(() => {\n    const mediaElement = context.mediaRef.current\n    if (!mediaElement) return\n\n    setIsLooping(mediaElement.loop)\n\n    const checkLoop = () => setIsLooping(mediaElement.loop)\n    const observer = new MutationObserver(checkLoop)\n    observer.observe(mediaElement, {\n      attributes: true,\n      attributeFilter: ['loop'],\n    })\n\n    return () => observer.disconnect()\n  }, [context.mediaRef])\n\n  const onLoopToggle = React.useCallback(\n    (event: React.MouseEvent<HTMLButtonElement>) => {\n      onClick?.(event)\n      if (event.defaultPrevented) return\n\n      const mediaElement = context.mediaRef.current\n      if (mediaElement) {\n        const newLoopState = !mediaElement.loop\n        mediaElement.loop = newLoopState\n        setIsLooping(newLoopState)\n      }\n    },\n    [context.mediaRef, onClick]\n  )\n\n  return (\n    <MediaPlayerTooltip\n      tooltip={isLooping ? 'Disable loop' : 'Enable loop'}\n      shortcut=\"R\"\n    >\n      <Button\n        type=\"button\"\n        aria-controls={context.mediaId}\n        aria-label={isLooping ? 'Disable loop' : 'Enable loop'}\n        aria-pressed={isLooping}\n        data-disabled={isDisabled ? '' : undefined}\n        data-slot=\"media-player-loop\"\n        data-state={isLooping ? 'on' : 'off'}\n        disabled={isDisabled}\n        {...loopProps}\n        variant=\"ghost\"\n        size=\"icon\"\n        className={cn('size-8', className)}\n        onClick={onLoopToggle}\n      >\n        {children ??\n          (isLooping ? (\n            <RepeatIcon className=\"text-muted-foreground\" />\n          ) : (\n            <RepeatIcon />\n          ))}\n      </Button>\n    </MediaPlayerTooltip>\n  )\n}\n\ninterface MediaPlayerFullscreenProps extends React.ComponentProps<\n  typeof Button\n> {}\n\nfunction MediaPlayerFullscreen(props: MediaPlayerFullscreenProps) {\n  const { children, className, disabled, onClick, ...fullscreenProps } = props\n\n  const context = useMediaPlayerContext('MediaPlayerFullscreen')\n  const dispatch = useMediaDispatch()\n  const isFullscreen = useMediaSelector(\n    (state) => state.mediaIsFullscreen ?? false\n  )\n\n  const isDisabled = disabled || context.disabled\n\n  const onFullscreen = React.useCallback(\n    (event: React.MouseEvent<HTMLButtonElement>) => {\n      onClick?.(event)\n\n      if (event.defaultPrevented) return\n\n      dispatch({\n        type: isFullscreen\n          ? MediaActionTypes.MEDIA_EXIT_FULLSCREEN_REQUEST\n          : MediaActionTypes.MEDIA_ENTER_FULLSCREEN_REQUEST,\n      })\n    },\n    [dispatch, onClick, isFullscreen]\n  )\n\n  return (\n    <MediaPlayerTooltip tooltip=\"Fullscreen\" shortcut=\"F\">\n      <Button\n        type=\"button\"\n        aria-label={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}\n        data-disabled={isDisabled ? '' : undefined}\n        data-slot=\"media-player-fullscreen\"\n        data-state={isFullscreen ? 'on' : 'off'}\n        disabled={isDisabled}\n        {...fullscreenProps}\n        variant=\"ghost\"\n        size=\"icon\"\n        className={cn('size-8', className)}\n        onClick={onFullscreen}\n      >\n        {children ?? (isFullscreen ? <Minimize2Icon /> : <Maximize2Icon />)}\n      </Button>\n    </MediaPlayerTooltip>\n  )\n}\n\ninterface MediaPlayerPiPProps extends Omit<\n  React.ComponentProps<typeof Button>,\n  'children'\n> {\n  children?:\n    | React.ReactNode\n    | ((isPictureInPicture: boolean) => React.ReactNode)\n  onPipError?: (error: unknown, state: 'enter' | 'exit') => void\n}\n\nfunction MediaPlayerPiP(props: MediaPlayerPiPProps) {\n  const { children, className, onPipError, disabled, onClick, ...pipButtonProps } = props\n\n  const context = useMediaPlayerContext('MediaPlayerPiP')\n  const dispatch = useMediaDispatch()\n  const isPictureInPicture = useMediaSelector(\n    (state) => state.mediaIsPip ?? false\n  )\n\n  const isDisabled = disabled || context.disabled\n\n  const onPictureInPicture = React.useCallback(\n    (event: React.MouseEvent<HTMLButtonElement>) => {\n      onClick?.(event)\n\n      if (event.defaultPrevented) return\n\n      dispatch({\n        type: isPictureInPicture\n          ? MediaActionTypes.MEDIA_EXIT_PIP_REQUEST\n          : MediaActionTypes.MEDIA_ENTER_PIP_REQUEST,\n      })\n\n      const mediaElement = context.mediaRef.current\n\n      if (mediaElement instanceof HTMLVideoElement) {\n        if (isPictureInPicture) {\n          document.exitPictureInPicture().catch((error) => {\n            onPipError?.(error, 'exit')\n          })\n        } else {\n          mediaElement.requestPictureInPicture().catch((error) => {\n            onPipError?.(error, 'enter')\n          })\n        }\n      }\n    },\n    [dispatch, onClick, isPictureInPicture, onPipError, context.mediaRef]\n  )\n\n  return (\n    <MediaPlayerTooltip tooltip=\"Picture in picture\" shortcut=\"P\">\n      <Button\n        type=\"button\"\n        aria-controls={context.mediaId}\n        aria-label={isPictureInPicture ? 'Exit pip' : 'Enter pip'}\n        data-disabled={isDisabled ? '' : undefined}\n        data-slot=\"media-player-pip\"\n        data-state={isPictureInPicture ? 'on' : 'off'}\n        disabled={isDisabled}\n        {...pipButtonProps}\n        variant=\"ghost\"\n        size=\"icon\"\n        className={cn('size-8', className)}\n        onClick={onPictureInPicture}\n      >\n        {typeof children === 'function'\n          ? children(isPictureInPicture)\n          : (children ??\n            (isPictureInPicture ? (\n              <PictureInPicture2Icon />\n            ) : (\n              <PictureInPictureIcon />\n            )))}\n      </Button>\n    </MediaPlayerTooltip>\n  )\n}\n\nfunction MediaPlayerCaptions(props: React.ComponentProps<typeof Button>) {\n  const { children, className, disabled, onClick, ...captionsProps } = props\n\n  const context = useMediaPlayerContext('MediaPlayerCaptions')\n  const dispatch = useMediaDispatch()\n  const isSubtitlesActive = useMediaSelector(\n    (state) => (state.mediaSubtitlesShowing ?? []).length > 0\n  )\n\n  const isDisabled = disabled || context.disabled\n  const onCaptionsToggle = React.useCallback(\n    (event: React.MouseEvent<HTMLButtonElement>) => {\n      onClick?.(event)\n\n      if (event.defaultPrevented) return\n\n      dispatch({\n        type: MediaActionTypes.MEDIA_TOGGLE_SUBTITLES_REQUEST,\n      })\n    },\n    [dispatch, onClick]\n  )\n\n  return (\n    <MediaPlayerTooltip tooltip=\"Captions\" shortcut=\"C\">\n      <Button\n        type=\"button\"\n        aria-controls={context.mediaId}\n        aria-label={isSubtitlesActive ? 'Disable captions' : 'Enable captions'}\n        aria-pressed={isSubtitlesActive}\n        data-disabled={isDisabled ? '' : undefined}\n        data-slot=\"media-player-captions\"\n        data-state={isSubtitlesActive ? 'on' : 'off'}\n        disabled={isDisabled}\n        {...captionsProps}\n        variant=\"ghost\"\n        size=\"icon\"\n        className={cn('size-8', className)}\n        onClick={onCaptionsToggle}\n      >\n        {children ??\n          (isSubtitlesActive ? <SubtitlesIcon /> : <CaptionsOffIcon />)}\n      </Button>\n    </MediaPlayerTooltip>\n  )\n}\n\nfunction MediaPlayerDownload(props: React.ComponentProps<typeof Button>) {\n  const { children, className, disabled, onClick, ...downloadProps } = props\n\n  const context = useMediaPlayerContext('MediaPlayerDownload')\n\n  const isDisabled = disabled || context.disabled\n\n  const onDownload = React.useCallback(\n    (event: React.MouseEvent<HTMLButtonElement>) => {\n      onClick?.(event)\n\n      if (event.defaultPrevented) return\n\n      const mediaElement = context.mediaRef.current\n\n      if (!mediaElement || !mediaElement.currentSrc) return\n\n      const link = document.createElement('a')\n      link.href = mediaElement.currentSrc\n      link.download = ''\n      document.body.appendChild(link)\n      link.click()\n      document.body.removeChild(link)\n    },\n    [context.mediaRef, onClick]\n  )\n\n  return (\n    <MediaPlayerTooltip tooltip=\"Download\" shortcut=\"D\">\n      <Button\n        type=\"button\"\n        aria-controls={context.mediaId}\n        aria-label=\"Download\"\n        data-disabled={isDisabled ? '' : undefined}\n        data-slot=\"media-player-download\"\n        disabled={isDisabled}\n        {...downloadProps}\n        variant=\"ghost\"\n        size=\"icon\"\n        className={cn('size-8', className)}\n        onClick={onDownload}\n      >\n        {children ?? <DownloadIcon />}\n      </Button>\n    </MediaPlayerTooltip>\n  )\n}\n\ninterface MediaPlayerSettingsProps extends MediaPlayerPlaybackSpeedProps {}\n\nfunction MediaPlayerSettings(props: MediaPlayerSettingsProps) {\n  const {\n    open,\n    defaultOpen,\n    onOpenChange: onOpenChangeProp,\n    sideOffset = FLOATING_MENU_SIDE_OFFSET,\n    speeds = SPEEDS,\n    modal = false,\n    className,\n    disabled,\n    ...settingsProps\n  } = props\n\n  const context = useMediaPlayerContext(SETTINGS_NAME)\n  const store = useStoreContext(SETTINGS_NAME)\n  const dispatch = useMediaDispatch()\n\n  const mediaPlaybackRate = useMediaSelector(\n    (state) => state.mediaPlaybackRate ?? 1\n  )\n  const mediaSubtitlesList = useMediaSelector(\n    (state) => state.mediaSubtitlesList ?? []\n  )\n  const mediaSubtitlesShowing = useMediaSelector(\n    (state) => state.mediaSubtitlesShowing ?? []\n  )\n  const mediaRenditionList = useMediaSelector(\n    (state) => state.mediaRenditionList ?? []\n  )\n  const selectedRenditionId = useMediaSelector(\n    (state) => state.mediaRenditionSelected\n  )\n\n  const isDisabled = disabled || context.disabled\n  const isSubtitlesActive = mediaSubtitlesShowing.length > 0\n\n  const onPlaybackRateChange = React.useCallback(\n    (rate: number) => {\n      dispatch({\n        type: MediaActionTypes.MEDIA_PLAYBACK_RATE_REQUEST,\n        detail: rate,\n      })\n    },\n    [dispatch]\n  )\n\n  const onRenditionChange = React.useCallback(\n    (renditionId: string) => {\n      dispatch({\n        type: MediaActionTypes.MEDIA_RENDITION_REQUEST,\n        detail: renditionId === 'auto' ? undefined : renditionId,\n      })\n    },\n    [dispatch]\n  )\n\n  const onSubtitlesToggle = React.useCallback(() => {\n    dispatch({\n      type: MediaActionTypes.MEDIA_TOGGLE_SUBTITLES_REQUEST,\n      detail: false,\n    })\n  }, [dispatch])\n\n  const onShowSubtitleTrack = React.useCallback(\n    (subtitleTrack: (typeof mediaSubtitlesList)[number]) => {\n      dispatch({\n        type: MediaActionTypes.MEDIA_TOGGLE_SUBTITLES_REQUEST,\n        detail: false,\n      })\n      dispatch({\n        type: MediaActionTypes.MEDIA_SHOW_SUBTITLES_REQUEST,\n        detail: subtitleTrack,\n      })\n    },\n    [dispatch]\n  )\n\n  const selectedSubtitleLabel = React.useMemo(() => {\n    if (!isSubtitlesActive) return 'Off'\n    if (mediaSubtitlesShowing.length > 0) {\n      return mediaSubtitlesShowing[0]?.label ?? 'On'\n    }\n    return 'Off'\n  }, [isSubtitlesActive, mediaSubtitlesShowing])\n\n  const selectedRenditionLabel = React.useMemo(() => {\n    if (!selectedRenditionId) return 'Auto'\n\n    const currentRendition = mediaRenditionList?.find(\n      (rendition) => rendition.id === selectedRenditionId\n    )\n    if (!currentRendition) return 'Auto'\n\n    if (currentRendition.height) return `${currentRendition.height}p`\n    if (currentRendition.width) return `${currentRendition.width}p`\n    return currentRendition.id ?? 'Auto'\n  }, [selectedRenditionId, mediaRenditionList])\n\n  const onOpenChange = React.useCallback(\n    (open: boolean) => {\n      store.setState('menuOpen', open)\n      onOpenChangeProp?.(open)\n    },\n    [store.setState, onOpenChangeProp]\n  )\n\n  return (\n    <DropdownMenu\n      modal={modal}\n      open={open}\n      defaultOpen={defaultOpen}\n      onOpenChange={onOpenChange}\n    >\n      <MediaPlayerTooltip tooltip=\"Settings\">\n        <DropdownMenuTrigger asChild>\n          <Button\n            type=\"button\"\n            aria-controls={context.mediaId}\n            aria-label=\"Settings\"\n            data-disabled={isDisabled ? '' : undefined}\n            data-slot=\"media-player-settings\"\n            disabled={isDisabled}\n            {...settingsProps}\n            variant=\"ghost\"\n            size=\"icon\"\n            className={cn('aria-expanded:bg-accent/50 size-8', className)}\n          >\n            <SettingsIcon />\n          </Button>\n        </DropdownMenuTrigger>\n      </MediaPlayerTooltip>\n      <DropdownMenuContent\n        align=\"end\"\n        side=\"top\"\n        sideOffset={sideOffset}\n        container={context.portalContainer}\n        className=\"w-56 data-[side=top]:mb-3.5\"\n      >\n        <DropdownMenuLabel className=\"sr-only\">Settings</DropdownMenuLabel>\n        <DropdownMenuSub>\n          <DropdownMenuSubTrigger>\n            <span className=\"flex-1\">Speed</span>\n            <Badge variant=\"outline\" className=\"rounded-sm\">\n              {mediaPlaybackRate}x\n            </Badge>\n          </DropdownMenuSubTrigger>\n          <DropdownMenuSubContent>\n            {speeds.map((speed) => (\n              <DropdownMenuItem\n                key={speed}\n                className=\"justify-between\"\n                onSelect={() => onPlaybackRateChange(speed)}\n              >\n                {speed}x{mediaPlaybackRate === speed && <CheckIcon />}\n              </DropdownMenuItem>\n            ))}\n          </DropdownMenuSubContent>\n        </DropdownMenuSub>\n        {context.isVideo && mediaRenditionList.length > 0 && (\n          <DropdownMenuSub>\n            <DropdownMenuSubTrigger>\n              <span className=\"flex-1\">Quality</span>\n              <Badge variant=\"outline\" className=\"rounded-sm\">\n                {selectedRenditionLabel}\n              </Badge>\n            </DropdownMenuSubTrigger>\n            <DropdownMenuSubContent>\n              <DropdownMenuItem\n                className=\"justify-between\"\n                onSelect={() => onRenditionChange('auto')}\n              >\n                Auto\n                {!selectedRenditionId && <CheckIcon />}\n              </DropdownMenuItem>\n              {mediaRenditionList\n                .slice()\n                .sort((a, b) => {\n                  const aHeight = a.height ?? 0\n                  const bHeight = b.height ?? 0\n                  return bHeight - aHeight\n                })\n                .map((rendition) => {\n                  const label = rendition.height\n                    ? `${rendition.height}p`\n                    : rendition.width\n                      ? `${rendition.width}p`\n                      : (rendition.id ?? 'Unknown')\n\n                  const selected = rendition.id === selectedRenditionId\n\n                  return (\n                    <DropdownMenuItem\n                      key={rendition.id}\n                      className=\"justify-between\"\n                      onSelect={() => onRenditionChange(rendition.id ?? '')}\n                    >\n                      {label}\n                      {selected && <CheckIcon />}\n                    </DropdownMenuItem>\n                  )\n                })}\n            </DropdownMenuSubContent>\n          </DropdownMenuSub>\n        )}\n        <DropdownMenuSub>\n          <DropdownMenuSubTrigger>\n            <span className=\"flex-1\">Captions</span>\n            <Badge variant=\"outline\" className=\"rounded-sm\">\n              {selectedSubtitleLabel}\n            </Badge>\n          </DropdownMenuSubTrigger>\n          <DropdownMenuSubContent>\n            <DropdownMenuItem\n              className=\"justify-between\"\n              onSelect={onSubtitlesToggle}\n            >\n              Off\n              {!isSubtitlesActive && <CheckIcon />}\n            </DropdownMenuItem>\n            {mediaSubtitlesList.map((subtitleTrack) => {\n              const isSelected = mediaSubtitlesShowing.some(\n                (showingSubtitle) =>\n                  showingSubtitle.label === subtitleTrack.label\n              )\n              return (\n                <DropdownMenuItem\n                  key={`${subtitleTrack.kind}-${subtitleTrack.label}-${subtitleTrack.language}`}\n                  className=\"justify-between\"\n                  onSelect={() => onShowSubtitleTrack(subtitleTrack)}\n                >\n                  {subtitleTrack.label}\n                  {isSelected && <CheckIcon />}\n                </DropdownMenuItem>\n              )\n            })}\n            {mediaSubtitlesList.length === 0 && (\n              <DropdownMenuItem disabled>\n                No captions available\n              </DropdownMenuItem>\n            )}\n          </DropdownMenuSubContent>\n        </DropdownMenuSub>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n\ninterface MediaPlayerPortalProps {\n  container?: Element | DocumentFragment | null\n  children?: React.ReactNode\n}\n\nfunction MediaPlayerPortal(props: MediaPlayerPortalProps) {\n  const { container: containerProp, children } = props\n\n  const context = useMediaPlayerContext('MediaPlayerPortal')\n  const container = containerProp ?? context.portalContainer\n\n  if (!container) return null\n\n  return ReactDOM.createPortal(children, container)\n}\n\ninterface MediaPlayerTooltipProps\n  extends\n    React.ComponentProps<typeof Tooltip>,\n    Pick<React.ComponentProps<typeof TooltipContent>, 'sideOffset'> {\n  tooltip?: string\n  shortcut?: string | string[]\n}\n\nfunction MediaPlayerTooltip(props: MediaPlayerTooltipProps) {\n  const {\n    tooltip,\n    shortcut,\n    delayDuration,\n    sideOffset,\n    children,\n    ...tooltipProps\n  } = props\n\n  const context = useMediaPlayerContext('MediaPlayerTooltip')\n  const tooltipDelayDuration = delayDuration ?? context.tooltipDelayDuration\n  const tooltipSideOffset = sideOffset ?? context.tooltipSideOffset\n\n  if ((!tooltip && !shortcut) || context.withoutTooltip) return <>{children}</>\n\n  return (\n    <Tooltip {...tooltipProps} delayDuration={tooltipDelayDuration}>\n      <TooltipTrigger\n        className=\"text-foreground focus-visible:ring-ring/50\"\n        asChild\n      >\n        {children}\n      </TooltipTrigger>\n      <TooltipContent\n        container={context.portalContainer}\n        sideOffset={tooltipSideOffset}\n        className=\"bg-accent text-foreground flex items-center gap-2 border px-2 py-1 font-medium data-[side=top]:mb-3.5 dark:bg-zinc-900 [&>span]:hidden\"\n      >\n        <p>{tooltip}</p>\n        {Array.isArray(shortcut) ? (\n          <div className=\"flex items-center gap-1\">\n            {shortcut.map((shortcutKey) => (\n              <kbd\n                key={shortcutKey}\n                className=\"bg-secondary text-foreground rounded border px-1.5 py-0.5 font-mono text-[11.2px] shadow-xs select-none\"\n              >\n                <abbr title={shortcutKey} className=\"no-underline\">\n                  {shortcutKey}\n                </abbr>\n              </kbd>\n            ))}\n          </div>\n        ) : (\n          shortcut && (\n            <kbd\n              key={shortcut}\n              className=\"bg-secondary text-foreground rounded border px-1.5 py-px font-mono text-[11.2px] shadow-xs select-none\"\n            >\n              <abbr title={shortcut} className=\"no-underline\">\n                {shortcut}\n              </abbr>\n            </kbd>\n          )\n        )}\n      </TooltipContent>\n    </Tooltip>\n  )\n}\n\nexport {\n  MediaPlayer,\n  MediaPlayerVideo,\n  MediaPlayerAudio,\n  MediaPlayerControls,\n  MediaPlayerControlsOverlay,\n  MediaPlayerLoading,\n  MediaPlayerError,\n  MediaPlayerVolumeIndicator,\n  MediaPlayerPlay,\n  MediaPlayerSeekBackward,\n  MediaPlayerSeekForward,\n  MediaPlayerSeek,\n  MediaPlayerVolume,\n  MediaPlayerTime,\n  MediaPlayerPlaybackSpeed,\n  MediaPlayerLoop,\n  MediaPlayerFullscreen,\n  MediaPlayerPiP,\n  MediaPlayerCaptions,\n  MediaPlayerDownload,\n  MediaPlayerSettings,\n  MediaPlayerPortal,\n  MediaPlayerTooltip,\n  //\n  useMediaSelector as useMediaPlayer,\n  useStore as useMediaPlayerStore,\n  //\n  type MediaPlayerProps,\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/lib/compose-refs.ts",
      "content": "import * as React from \"react\";\n\ntype PossibleRef<T> = React.Ref<T> | undefined;\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n  if (typeof ref === \"function\") {\n    return ref(value);\n  }\n\n  if (ref !== null && ref !== undefined) {\n    ref.current = value;\n  }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nfunction composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n  return (node) => {\n    let hasCleanup = false;\n    const cleanups = refs.map((ref) => {\n      const cleanup = setRef(ref, node);\n      if (!hasCleanup && typeof cleanup === \"function\") {\n        hasCleanup = true;\n      }\n      return cleanup;\n    });\n\n    // React <19 will log an error to the console if a callback ref returns a\n    // value. We don't use ref cleanups internally so this will only happen if a\n    // user's ref callback returns a value, which we only expect if they are\n    // using the cleanup functionality added in React 19.\n    if (hasCleanup) {\n      return () => {\n        for (let i = 0; i < cleanups.length; i++) {\n          const cleanup = cleanups[i];\n          if (typeof cleanup === \"function\") {\n            cleanup();\n          } else {\n            setRef(refs[i], null);\n          }\n        }\n      };\n    }\n  };\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nfunction useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n  // biome-ignore lint/correctness/useExhaustiveDependencies: we want to memoize by all values\n  // eslint-disable-next-line react-hooks/use-memo\n  return React.useCallback(composeRefs(...refs), refs);\n}\n\nexport { composeRefs, useComposedRefs };\n",
      "type": "registry:lib"
    },
    {
      "path": "hooks/use-lazy-ref.ts",
      "content": "import * as React from \"react\";\n\nfunction useLazyRef<T>(fn: () => T) {\n  const ref = React.useRef<T | null>(null);\n\n  if (ref.current === null) {\n    ref.current = fn();\n  }\n\n  return ref as React.RefObject<T>;\n}\n\nexport { useLazyRef };\n",
      "type": "registry:hook"
    }
  ]
}