import { Button } from '@heroui/button'; import { Card, CardBody, CardHeader } from '@heroui/card'; import { Image } from '@heroui/image'; import { Popover, PopoverContent, PopoverTrigger } from '@heroui/popover'; import { Slider } from '@heroui/slider'; import { Tooltip } from '@heroui/tooltip'; import { useLocalStorage } from '@uidotdev/usehooks'; import clsx from 'clsx'; import { useEffect, useRef, useState } from 'react'; import { BiSolidSkipNextCircle, BiSolidSkipPreviousCircle, } from 'react-icons/bi'; import { FaPause, FaPlay, FaRegHandPointRight, FaRepeat, FaShuffle, } from 'react-icons/fa6'; import { TbRepeatOnce } from 'react-icons/tb'; import { useMediaQuery } from 'react-responsive'; import { PlayMode } from '@/const/enum'; import key from '@/const/key'; import { VolumeHighIcon, VolumeLowIcon } from './icons'; export interface AudioPlayerProps extends React.AudioHTMLAttributes { src: string title?: string artist?: string cover?: string pressNext?: () => void pressPrevious?: () => void onPlayEnd?: () => void onChangeMode?: (mode: PlayMode) => void mode?: PlayMode } export default function AudioPlayer (props: AudioPlayerProps) { const { src, pressNext, pressPrevious, cover = 'https://nextui.org/images/album-cover.png', title = '未知', artist = '未知', onTimeUpdate, onLoadedData, onPlay, onPause, onPlayEnd, onChangeMode, autoPlay, mode = PlayMode.Loop, ...rest } = props; const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); const [isPlaying, setIsPlaying] = useState(false); const [volume, setVolume] = useState(100); const [isCollapsed, setIsCollapsed] = useLocalStorage( key.isCollapsedMusicPlayer, false ); const audioRef = useRef(null); const cardRef = useRef(null); const startY = useRef(0); const startX = useRef(0); const [translateY, setTranslateY] = useState(0); const [translateX, setTranslateX] = useState(0); const isSmallScreen = useMediaQuery({ maxWidth: 767 }); const isMediumUp = useMediaQuery({ minWidth: 768 }); const shouldAdd = useRef(false); const currentProgress = (currentTime / duration) * 100; const [storageAutoPlay, setStorageAutoPlay] = useLocalStorage( key.autoPlay, true ); const handleTimeUpdate = (event: React.SyntheticEvent) => { const audio = event.target as HTMLAudioElement; setCurrentTime(audio.currentTime); onTimeUpdate?.(event); }; const handleLoadedData = (event: React.SyntheticEvent) => { const audio = event.target as HTMLAudioElement; setDuration(audio.duration); onLoadedData?.(event); }; const handlePlay = (e: React.SyntheticEvent) => { setIsPlaying(true); setStorageAutoPlay(true); onPlay?.(e); }; const handlePause = (e: React.SyntheticEvent) => { setIsPlaying(false); onPause?.(e); }; const changeMode = () => { const modes = [PlayMode.Loop, PlayMode.Random, PlayMode.Single]; const currentIndex = modes.findIndex((_mode) => _mode === mode); const nextIndex = currentIndex + 1; const nextMode = modes[nextIndex] || modes[0]; onChangeMode?.(nextMode); }; const volumeChange = (value: number) => { setVolume(value); }; useEffect(() => { const audio = audioRef.current; if (audio) { audio.volume = volume / 100; } }, [volume]); const handleTouchStart = (e: React.TouchEvent) => { startY.current = e.touches[0].clientY; startX.current = e.touches[0].clientX; }; const handleTouchMove = (e: React.TouchEvent) => { const deltaY = e.touches[0].clientY - startY.current; const deltaX = e.touches[0].clientX - startX.current; const container = cardRef.current; const header = cardRef.current?.querySelector('[data-header]'); const headerHeight = header?.clientHeight || 20; const addHeight = (container?.clientHeight || headerHeight) - headerHeight; const _shouldAdd = isCollapsed && deltaY < 0; if (isSmallScreen) { shouldAdd.current = _shouldAdd; setTranslateY(_shouldAdd ? deltaY + addHeight : deltaY); } else { setTranslateX(deltaX); } }; const handleTouchEnd = () => { if (isSmallScreen) { const container = cardRef.current; const header = cardRef.current?.querySelector('[data-header]'); const headerHeight = header?.clientHeight || 20; const addHeight = (container?.clientHeight || headerHeight) - headerHeight; const _translateY = translateY - (shouldAdd.current ? addHeight : 0); if (_translateY > 100) { setIsCollapsed(true); } else if (_translateY < -100) { setIsCollapsed(false); } setTranslateY(0); } else { if (translateX > 100) { setIsCollapsed(true); } else if (translateX < -100) { setIsCollapsed(false); } setTranslateX(0); } }; const dragTranslate = isSmallScreen ? translateY ? `translateY(${translateY}px)` : '' : translateX ? `translateX(${translateX}px)` : ''; const collapsedTranslate = isCollapsed ? isSmallScreen ? 'translateY(90%)' : 'translateX(96%)' : ''; const translateStyle = dragTranslate || collapsedTranslate; if (!src) return null; return (