{
  "name": "media-player",
  "type": "registry:ui",
  "dependencies": [
    "cn",
    "radix-ui",
    "media-chrome"
  ],
  "registryDependencies": [
    "badge",
    "button",
    "select",
    "slider",
    "tooltip",
    "dropdown-menu",
    "@diceui/use-lazy-ref"
  ],
  "files": [
    {
      "path": "ui/media-player.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { cn } from \"cn\";\nimport {\n  MediaActionTypes,\n  MediaProvider,\n  timeUtils,\n  useMediaDispatch,\n  useMediaFullscreenRef,\n  useMediaRef,\n  useMediaSelector,\n} from \"media-chrome/react/media-store\";\nimport {\n  Direction as DirectionPrimitive,\n  Slider as SliderPrimitive,\n  Slot as SlotPrimitive,\n} from \"radix-ui\";\nimport * as React from \"react\";\nimport * as ReactDOM from \"react-dom\";\n\nimport { useComposedRefs } from \"@/lib/compose-refs\";\nimport { useLazyRef } from \"@/registry/bases/radix/hooks/use-lazy-ref\";\nimport { Badge } from \"@/registry/bases/radix/ui/badge\";\nimport { Button } from \"@/registry/bases/radix/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuTrigger,\n} from \"@/registry/bases/radix/ui/dropdown-menu\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/registry/bases/radix/ui/tooltip\";\nimport { IconPlaceholder } from \"@/registry/icons/icon-placeholder\";\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: (\n    key: keyof StoreState,\n    value: StoreState[keyof StoreState],\n  ) => 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 = DirectionPrimitive.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 ? SlotPrimitive.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          \"dark relative isolate flex size-full flex-col overflow-hidden rounded-lg bg-background outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 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, ...videoProps } = props;\n\n  const context = useMediaPlayerContext(\"MediaPlayerVideo\");\n  const dispatch = useMediaDispatch();\n  const mediaRefCallback =\n    useMediaRef() as React.RefCallback<HTMLVideoElement | null>;\n  const composedRef = useComposedRefs(ref, context.mediaRef, mediaRefCallback);\n\n  const onPlayToggle = React.useCallback(\n    (event: React.MouseEvent<HTMLVideoElement>) => {\n      props.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, props.onClick],\n  );\n\n  const VideoPrimitive = asChild ? SlotPrimitive.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 =\n    useMediaRef() as React.RefCallback<HTMLAudioElement | null>;\n  const composedRef = useComposedRefs(ref, context.mediaRef, mediaRefCallback);\n\n  const AudioPrimitive = asChild ? SlotPrimitive.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 ? SlotPrimitive.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        \"dark 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 {\n    delayMs = 500,\n    asChild,\n    className,\n    children,\n    ...loadingProps\n  } = 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 ? SlotPrimitive.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        \"pointer-events-none absolute inset-0 z-50 flex animate-in items-center justify-center duration-200 fade-in-0 zoom-in-95\",\n        className,\n      )}\n    >\n      {children ?? (\n        <IconPlaceholder\n          lucide=\"Loader2Icon\"\n          tabler=\"IconLoader2\"\n          hugeicons=\"Loading03Icon\"\n          phosphor=\"SpinnerIcon\"\n          remixicon=\"RiLoader4Line\"\n          className=\"size-20 animate-spin stroke-[.0938rem] text-primary\"\n        />\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 ? SlotPrimitive.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          <IconPlaceholder\n            lucide=\"AlertTriangleIcon\"\n            tabler=\"IconAlertTriangle\"\n            hugeicons=\"Alert02Icon\"\n            phosphor=\"WarningIcon\"\n            remixicon=\"RiAlertLine\"\n            className=\"size-12 text-destructive\"\n          />\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-sm leading-relaxed text-balance text-muted-foreground\">\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                <IconPlaceholder\n                  lucide=\"Loader2Icon\"\n                  tabler=\"IconLoader2\"\n                  hugeicons=\"Loading03Icon\"\n                  phosphor=\"SpinnerIcon\"\n                  remixicon=\"RiLoader4Line\"\n                  className=\"animate-spin\"\n                />\n              ) : (\n                <IconPlaceholder\n                  lucide=\"RefreshCcwIcon\"\n                  tabler=\"IconRefresh\"\n                  hugeicons=\"RefreshIcon\"\n                  phosphor=\"ArrowsClockwiseIcon\"\n                  remixicon=\"RiRefreshLine\"\n                />\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                <IconPlaceholder\n                  lucide=\"Loader2Icon\"\n                  tabler=\"IconLoader2\"\n                  hugeicons=\"Loading03Icon\"\n                  phosphor=\"SpinnerIcon\"\n                  remixicon=\"RiLoader4Line\"\n                  className=\"animate-spin\"\n                />\n              ) : (\n                <IconPlaceholder\n                  lucide=\"RotateCcwIcon\"\n                  tabler=\"IconRotate2\"\n                  hugeicons=\"Rotate01Icon\"\n                  phosphor=\"ArrowCounterClockwiseIcon\"\n                  remixicon=\"RiResetLeftLine\"\n                />\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 ? SlotPrimitive.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=\"flex animate-in flex-col items-center gap-3 rounded-lg bg-black/30 px-6 py-4 text-white backdrop-blur-xs duration-200 fade-in-0 zoom-in-95\">\n        <div className=\"flex items-center gap-2\">\n          {mediaVolumeLevel === \"off\" || mediaMuted ? (\n            <IconPlaceholder\n              lucide=\"VolumeXIcon\"\n              tabler=\"IconVolumeOff\"\n              hugeicons=\"VolumeOffIcon\"\n              phosphor=\"SpeakerSlashIcon\"\n              remixicon=\"RiVolumeMuteLine\"\n              className=\"size-6\"\n            />\n          ) : mediaVolumeLevel === \"high\" ? (\n            <IconPlaceholder\n              lucide=\"Volume2Icon\"\n              tabler=\"IconVolume\"\n              hugeicons=\"VolumeHighIcon\"\n              phosphor=\"SpeakerHighIcon\"\n              remixicon=\"RiVolumeUpLine\"\n              className=\"size-6\"\n            />\n          ) : (\n            <IconPlaceholder\n              lucide=\"Volume1Icon\"\n              tabler=\"IconVolume2\"\n              hugeicons=\"VolumeLowIcon\"\n              phosphor=\"SpeakerLowIcon\"\n              remixicon=\"RiVolumeDownLine\"\n              className=\"size-6\"\n            />\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 ? SlotPrimitive.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, ...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      props.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, props.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 ??\n          (mediaPaused ? (\n            <IconPlaceholder\n              lucide=\"PlayIcon\"\n              tabler=\"IconPlayerPlay\"\n              hugeicons=\"PlayIcon\"\n              phosphor=\"PlayIcon\"\n              remixicon=\"RiPlayLine\"\n            />\n          ) : (\n            <IconPlaceholder\n              lucide=\"PauseIcon\"\n              tabler=\"IconPlayerPause\"\n              hugeicons=\"PauseIcon\"\n              phosphor=\"PauseIcon\"\n              remixicon=\"RiPauseLine\"\n            />\n          ))}\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    ...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      props.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, props.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 ?? (\n          <IconPlaceholder\n            lucide=\"RewindIcon\"\n            tabler=\"IconRewindBackward10\"\n            hugeicons=\"ReplayIcon\"\n            phosphor=\"RewindIcon\"\n            remixicon=\"RiReplay10Line\"\n          />\n        )}\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    ...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      props.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, props.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 ?? (\n          <IconPlaceholder\n            lucide=\"FastForwardIcon\"\n            tabler=\"IconPlayerSkipForward\"\n            hugeicons=\"FastForwardIcon\"\n            phosphor=\"FastForwardIcon\"\n            remixicon=\"RiSpeedLine\"\n          />\n        )}\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(\n    (state) => state.mediaChaptersCues ?? [],\n  );\n  const mediaPreviewTime = useMediaSelector((state) => state.mediaPreviewTime);\n  const mediaPreviewImage = useMediaSelector(\n    (state) => state.mediaPreviewImage,\n  );\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 =\n      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=\"relative h-1 w-full grow overflow-hidden rounded-full bg-primary/40\">\n          <div\n            data-slot=\"media-player-seek-buffered\"\n            className=\"absolute h-full bg-primary/70 will-change-[width]\"\n            style={{\n              width: `${bufferedProgress * 100}%`,\n            }}\n          />\n          <SliderPrimitive.Range className=\"absolute h-full bg-primary will-change-[width]\" />\n          {seekState.isHovering && seekableEnd > 0 && (\n            <div\n              data-slot=\"media-player-seek-hover-range\"\n              className=\"absolute h-full bg-primary/70 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=\"relative z-10 block size-2.5 shrink-0 rounded-full bg-primary shadow-sm ring-ring/50 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                  \"flex flex-col items-center gap-1.5 rounded-md border bg-background text-foreground 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                      <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            <IconPlaceholder\n              lucide=\"VolumeXIcon\"\n              tabler=\"IconVolumeOff\"\n              hugeicons=\"VolumeOffIcon\"\n              phosphor=\"SpeakerSlashIcon\"\n              remixicon=\"RiVolumeMuteLine\"\n            />\n          ) : mediaVolumeLevel === \"high\" ? (\n            <IconPlaceholder\n              lucide=\"Volume2Icon\"\n              tabler=\"IconVolume\"\n              hugeicons=\"VolumeHighIcon\"\n              phosphor=\"SpeakerHighIcon\"\n              remixicon=\"RiVolumeUpLine\"\n            />\n          ) : (\n            <IconPlaceholder\n              lucide=\"Volume1Icon\"\n              tabler=\"IconVolume2\"\n              hugeicons=\"VolumeLowIcon\"\n              phosphor=\"SpeakerLowIcon\"\n              remixicon=\"RiVolumeDownLine\"\n            />\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=\"absolute h-full bg-primary will-change-[width]\" />\n        </SliderPrimitive.Track>\n        <SliderPrimitive.Thumb className=\"block size-2.5 shrink-0 rounded-full bg-primary shadow-sm ring-ring/50 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 ? SlotPrimitive.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-sm text-foreground/80 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        \"flex items-center gap-1 text-sm text-foreground/80\",\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    Omit<React.ComponentProps<typeof DropdownMenu>, \"dir\">,\n    Pick<React.ComponentProps<typeof DropdownMenuContent>, \"sideOffset\">,\n    React.ComponentProps<typeof DropdownMenuTrigger>,\n    React.ComponentProps<typeof Button> {\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(\"h-8 w-16 aria-expanded:bg-accent/50\", 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\n            {mediaPlaybackRate === speed && (\n              <IconPlaceholder\n                lucide=\"CheckIcon\"\n                tabler=\"IconCheck\"\n                hugeicons=\"Tick02Icon\"\n                phosphor=\"CheckIcon\"\n                remixicon=\"RiCheckLine\"\n              />\n            )}\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, ...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      props.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, props.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            <IconPlaceholder\n              lucide=\"RepeatIcon\"\n              tabler=\"IconRepeat\"\n              hugeicons=\"RepeatIcon\"\n              phosphor=\"RepeatIcon\"\n              remixicon=\"RiRepeatLine\"\n              className=\"text-muted-foreground\"\n            />\n          ) : (\n            <IconPlaceholder\n              lucide=\"RepeatIcon\"\n              tabler=\"IconRepeat\"\n              hugeicons=\"RepeatIcon\"\n              phosphor=\"RepeatIcon\"\n              remixicon=\"RiRepeatLine\"\n            />\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, ...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      props.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, props.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 ??\n          (isFullscreen ? (\n            <IconPlaceholder\n              lucide=\"Minimize2Icon\"\n              tabler=\"IconMinimize\"\n              hugeicons=\"MinimizeIcon\"\n              phosphor=\"ArrowsInIcon\"\n              remixicon=\"RiFullscreenExitLine\"\n            />\n          ) : (\n            <IconPlaceholder\n              lucide=\"Maximize2Icon\"\n              tabler=\"IconMaximize\"\n              hugeicons=\"MaximizeIcon\"\n              phosphor=\"ArrowsOutIcon\"\n              remixicon=\"RiFullscreenLine\"\n            />\n          ))}\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, ...pipButtonProps } =\n    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      props.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, props.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              <IconPlaceholder\n                lucide=\"PictureInPicture2Icon\"\n                tabler=\"IconPictureInPictureOn\"\n                hugeicons=\"PictureInPictureOnIcon\"\n                phosphor=\"PictureInPictureIcon\"\n                remixicon=\"RiPictureInPicture2Line\"\n              />\n            ) : (\n              <IconPlaceholder\n                lucide=\"PictureInPictureIcon\"\n                tabler=\"IconPictureInPicture\"\n                hugeicons=\"PictureInPictureOnIcon\"\n                phosphor=\"PictureInPictureIcon\"\n                remixicon=\"RiPictureInPictureLine\"\n              />\n            )))}\n      </Button>\n    </MediaPlayerTooltip>\n  );\n}\n\nfunction MediaPlayerCaptions(props: React.ComponentProps<typeof Button>) {\n  const { children, className, disabled, ...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      props.onClick?.(event);\n\n      if (event.defaultPrevented) return;\n\n      dispatch({\n        type: MediaActionTypes.MEDIA_TOGGLE_SUBTITLES_REQUEST,\n      });\n    },\n    [dispatch, props.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 ? (\n            <IconPlaceholder\n              lucide=\"SubtitlesIcon\"\n              tabler=\"IconCaptions\"\n              hugeicons=\"ClosedCaptionIcon\"\n              phosphor=\"ClosedCaptioningIcon\"\n              remixicon=\"RiClosedCaptioningLine\"\n            />\n          ) : (\n            <IconPlaceholder\n              lucide=\"CaptionsOffIcon\"\n              tabler=\"IconCaptionsOff\"\n              hugeicons=\"ClosedCaptionIcon\"\n              phosphor=\"ClosedCaptioningIcon\"\n              remixicon=\"RiClosedCaptioningOffLine\"\n            />\n          ))}\n      </Button>\n    </MediaPlayerTooltip>\n  );\n}\n\nfunction MediaPlayerDownload(props: React.ComponentProps<typeof Button>) {\n  const { children, className, disabled, ...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      props.onClick?.(event);\n\n      if (event.defaultPrevented) return;\n\n      const mediaElement = context.mediaRef.current;\n\n      if (!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, props.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 ?? (\n          <IconPlaceholder\n            lucide=\"DownloadIcon\"\n            tabler=\"IconDownload\"\n            hugeicons=\"Download01Icon\"\n            phosphor=\"DownloadSimpleIcon\"\n            remixicon=\"RiDownloadLine\"\n          />\n        )}\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(\"size-8 aria-expanded:bg-accent/50\", className)}\n          >\n            <IconPlaceholder\n              lucide=\"SettingsIcon\"\n              tabler=\"IconSettings\"\n              hugeicons=\"Settings01Icon\"\n              phosphor=\"GearIcon\"\n              remixicon=\"RiSettings3Line\"\n            />\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\n                {mediaPlaybackRate === speed && (\n                  <IconPlaceholder\n                    lucide=\"CheckIcon\"\n                    tabler=\"IconCheck\"\n                    hugeicons=\"Tick02Icon\"\n                    phosphor=\"CheckIcon\"\n                    remixicon=\"RiCheckLine\"\n                  />\n                )}\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 && (\n                  <IconPlaceholder\n                    lucide=\"CheckIcon\"\n                    tabler=\"IconCheck\"\n                    hugeicons=\"Tick02Icon\"\n                    phosphor=\"CheckIcon\"\n                    remixicon=\"RiCheckLine\"\n                  />\n                )}\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 && (\n                        <IconPlaceholder\n                          lucide=\"CheckIcon\"\n                          tabler=\"IconCheck\"\n                          hugeicons=\"Tick02Icon\"\n                          phosphor=\"CheckIcon\"\n                          remixicon=\"RiCheckLine\"\n                        />\n                      )}\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 && (\n                <IconPlaceholder\n                  lucide=\"CheckIcon\"\n                  tabler=\"IconCheck\"\n                  hugeicons=\"Tick02Icon\"\n                  phosphor=\"CheckIcon\"\n                  remixicon=\"RiCheckLine\"\n                />\n              )}\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 && (\n                    <IconPlaceholder\n                      lucide=\"CheckIcon\"\n                      tabler=\"IconCheck\"\n                      hugeicons=\"Tick02Icon\"\n                      phosphor=\"CheckIcon\"\n                      remixicon=\"RiCheckLine\"\n                    />\n                  )}\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=\"flex items-center gap-2 border bg-accent px-2 py-1 font-medium text-foreground 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=\"rounded border bg-secondary px-1.5 py-0.5 font-mono text-[11.2px] text-foreground 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=\"rounded border bg-secondary px-1.5 py-px font-mono text-[11.2px] text-foreground 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  MediaPlayerAudio,\n  MediaPlayerCaptions,\n  MediaPlayerControls,\n  MediaPlayerControlsOverlay,\n  MediaPlayerDownload,\n  MediaPlayerError,\n  MediaPlayerFullscreen,\n  MediaPlayerLoading,\n  MediaPlayerLoop,\n  MediaPlayerPiP,\n  MediaPlayerPlay,\n  MediaPlayerPlaybackSpeed,\n  MediaPlayerPortal,\n  type MediaPlayerProps,\n  MediaPlayerSeek,\n  MediaPlayerSeekBackward,\n  MediaPlayerSeekForward,\n  MediaPlayerSettings,\n  MediaPlayerTime,\n  MediaPlayerTooltip,\n  MediaPlayerVideo,\n  MediaPlayerVolume,\n  MediaPlayerVolumeIndicator,\n  useMediaSelector as useMediaPlayer,\n  useStore as useMediaPlayerStore,\n};\n",
      "target": ""
    },
    {
      "path": "lib/compose-refs.ts",
      "type": "registry:lib",
      "content": "/**\n * @see https://github.com/radix-ui/primitives/blob/main/packages/react/compose-refs/src/compose-refs.tsx\n */\n\nimport * 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  // oxlint-disable-next-line react/exhaustive-deps -- we want to memoize by all values\n  return React.useCallback(composeRefs(...refs), refs);\n}\n\nexport { composeRefs, useComposedRefs };\n",
      "target": ""
    }
  ]
}