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 (
) }