/* eslint-disable @typescript-eslint/no-explicit-any */ 'use client'; import { useRouter } from 'next/navigation'; import { useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import AddToPlaylistModal from '@/components/AddToPlaylistModal'; import Toast, { ToastProps } from '@/components/Toast'; import LyricsPiPWindow from '@/components/LyricsPiPWindow'; const SPECTRUM_BIN_COUNT = 96; const SPECTRUM_IDLE_LEVEL = 0.02; const SPECTRUM_EDGE_TRIM = 8; type MusicSource = 'wy' | 'tx' | 'kw' | 'kg' | 'mg'; interface Song { id: string; name: string; artist: string; album?: string; pic?: string; platform: MusicSource; duration?: number; durationText?: string; songmid?: string; } interface PlayRecord { platform: MusicSource; id: string; playTime: number; // 播放时间(秒) duration: number; // 总时长(秒) timestamp: number; // 添加时间戳 } interface LyricLine { time: number; text: string; translation?: string; } interface Playlist { id: string; name: string; pic?: string; source?: MusicSource; updateFrequency?: string; } interface DbRecord { source: MusicSource; songId: string; id: string; playProgressSec: number; durationSec: number; createdAt: number; lastPlayedAt: number; name: string; artist: string; album?: string; cover?: string; durationText?: string; songmid?: string; } function MusicLoadingIndicator({ text, size = 'md', className = '', }: { text?: string; size?: 'sm' | 'md'; className?: string; }) { const iconSize = size === 'sm' ? 'w-4 h-4' : 'w-5 h-5'; const textSize = size === 'sm' ? 'text-xs' : 'text-sm'; return (
{[0, 1, 2].map((index) => ( ))}
{text ? {text} : null}
); } function AudioSpectrumCanvas({ bars, compact = false, }: { bars: number[]; compact?: boolean; }) { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const draw = () => { const rect = canvas.getBoundingClientRect(); if (!rect.width || !rect.height) return; const dpr = window.devicePixelRatio || 1; const width = Math.round(rect.width * dpr); const height = Math.round(rect.height * dpr); if (canvas.width !== width || canvas.height !== height) { canvas.width = width; canvas.height = height; } const ctx = canvas.getContext('2d'); if (!ctx) return; ctx.clearRect(0, 0, width, height); const targetPitch = compact ? 4.2 : 4.6; const gap = 1; const count = Math.max(1, Math.floor(rect.width / targetPitch)); const barWidth = Math.max(2, Math.floor((width - gap * (count - 1)) / count)); const cubeHeight = compact ? Math.max(2, Math.round(2 * dpr)) : Math.max(2, Math.round(2.5 * dpr)); const cubeGap = 1; const scaleBase = compact ? height * 1.55 : height * 1.42; const themeColor = '#10b981'; const sampleBar = (index: number) => { const usableLength = Math.max(1, bars.length - SPECTRUM_EDGE_TRIM * 2); const mappedStart = SPECTRUM_EDGE_TRIM + Math.floor((index / count) * usableLength); const start = Math.min(bars.length - 1, mappedStart); const mappedEnd = SPECTRUM_EDGE_TRIM + Math.max(mappedStart + 1, Math.floor(((index + 1) / count) * usableLength)); const end = Math.min(bars.length, Math.max(start + 1, mappedEnd)); let total = 0; for (let i = start; i < end; i++) total += bars[i] ?? 0; return total / Math.max(1, end - start); }; ctx.fillStyle = themeColor; ctx.strokeStyle = themeColor; for (let i = 0; i < count; i++) { const q = Math.max(SPECTRUM_IDLE_LEVEL, sampleBar(i)) * scaleBase; const cubeCount = Math.max(1, Math.ceil(q / Math.max(1, barWidth * 0.9))); const x = i * (barWidth + gap); for (let segment = 0; segment < cubeCount; segment++) { const y = height - segment * (cubeHeight + cubeGap); ctx.beginPath(); ctx.roundRect(x, y - cubeHeight, barWidth, cubeHeight, Math.min(2 * dpr, cubeHeight / 2)); ctx.fill(); } } }; draw(); const observer = new ResizeObserver(draw); observer.observe(canvas); return () => observer.disconnect(); }, [bars, compact]); return ( ); } // 扩展 Window 接口以支持 Document PiP API declare global { interface Window { documentPictureInPicture?: { requestWindow: (options: { width: number; height: number }) => Promise; window: Window | null; }; } } export default function MusicPage() { const router = useRouter(); const [currentSource, setCurrentSource] = useState('wy'); const [playlists, setPlaylists] = useState([]); const [songs, setSongs] = useState([]); const [currentView, setCurrentView] = useState<'playlists' | 'songs' | 'myPlaylists'>('playlists'); const [currentPlaylistTitle, setCurrentPlaylistTitle] = useState(''); const [searchKeyword, setSearchKeyword] = useState(''); const [currentSong, setCurrentSong] = useState(null); const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); const [volume, setVolume] = useState(100); const [quality, setQuality] = useState<'128k' | '320k' | 'flac' | 'flac24bit'>('320k'); const [playMode, setPlayMode] = useState<'loop' | 'single' | 'random'>('loop'); const [currentSongIndex, setCurrentSongIndex] = useState(-1); const [showPlayer, setShowPlayer] = useState(false); const [loading, setLoading] = useState(false); const [showLyrics, setShowLyrics] = useState(false); const [musicProxyEnabled, setMusicProxyEnabled] = useState(() => { if (typeof window === 'undefined') return true; return (window as any).RUNTIME_CONFIG?.MUSIC_PROXY_ENABLED !== false; }); const [lyrics, setLyrics] = useState([]); const [currentLyricIndex, setCurrentLyricIndex] = useState(-1); const [currentSongUrl, setCurrentSongUrl] = useState(''); const [playRecords, setPlayRecords] = useState([]); // 播放记录(只存平台和ID) const [playlist, setPlaylist] = useState([]); // 完整歌曲信息(用于显示) const [showPlaylist, setShowPlaylist] = useState(false); const [playlistIndex, setPlaylistIndex] = useState(-1); // 当前在播放列表中的索引 const [showQualityMenu, setShowQualityMenu] = useState(false); // 音质选择菜单 const [showSourceMenu, setShowSourceMenu] = useState(false); // 移动端音源菜单 const [showVolumeSlider, setShowVolumeSlider] = useState(false); // 音量滑块显示状态 const [pendingSongToPlay, setPendingSongToPlay] = useState<{ platform: string; id: string } | null>(null); // 待播放的歌曲信息 const [resolvingCount, setResolvingCount] = useState(0); // 当前解析中的歌曲数量 const [showAddToPlaylistModal, setShowAddToPlaylistModal] = useState(false); // 添加到歌单弹窗 const [songToAddToPlaylist, setSongToAddToPlaylist] = useState(null); // 要添加到歌单的歌曲 // 我的歌单相关状态 const [userPlaylists, setUserPlaylists] = useState([]); const [selectedUserPlaylist, setSelectedUserPlaylist] = useState(null); const [userPlaylistSongs, setUserPlaylistSongs] = useState([]); const [loadingUserPlaylists, setLoadingUserPlaylists] = useState(false); const [loadingUserPlaylistSongs, setLoadingUserPlaylistSongs] = useState(false); const [loadingPlayAll, setLoadingPlayAll] = useState(false); // 播放全部加载状态 const [loadingCurrentPlayAll, setLoadingCurrentPlayAll] = useState(false); // 当前排行榜/详情页播放全部加载状态 const [deletingPlaylistId, setDeletingPlaylistId] = useState(null); // 正在删除的歌单ID useEffect(() => { if (typeof window !== 'undefined' && !(window as any).RUNTIME_CONFIG?.MUSIC_ENABLED) { router.replace('/'); } }, [router]); // Toast 和 Confirm Modal 状态 const [toast, setToast] = useState(null); const [confirmModal, setConfirmModal] = useState<{ isOpen: boolean; title: string; message: string; onConfirm: () => void; onCancel: () => void; }>({ isOpen: false, title: '', message: '', onConfirm: () => {}, onCancel: () => {}, }); // PiP 相关状态 const [showPiPLyrics, setShowPiPLyrics] = useState(false); const [pipOpacity, setPipOpacity] = useState(0.9); const [pipMinimized, setPipMinimized] = useState(false); const [showSpectrum, setShowSpectrum] = useState(() => { if (typeof window === 'undefined') return true; return localStorage.getItem('musicShowSpectrum') !== '0'; }); const [spectrumBars, setSpectrumBars] = useState( () => Array.from({ length: SPECTRUM_BIN_COUNT }, () => SPECTRUM_IDLE_LEVEL) ); const audioRef = useRef(null); const lyricsContainerRef = useRef(null); const lastSaveTimeRef = useRef(0); const restoredTimeRef = useRef(0); const songStartTimeRef = useRef(0); // 歌曲开始播放的时间戳 const audioContextRef = useRef(null); const analyserRef = useRef(null); const mediaSourceRef = useRef(null); const spectrumDataRef = useRef(null); const spectrumFrameRef = useRef(null); const currentTimeRef = useRef(0); const spectrumSeedRef = useRef(Math.random() * Math.PI * 2); const mapSong = (song: any): Song => ({ id: song.songId || song.id, name: song.name, artist: song.artist, album: song.album, pic: song.cover || song.pic, platform: normalizeSource(song.source || song.platform), duration: song.durationSec || song.duration, durationText: song.durationText || song.interval, songmid: song.songmid, }); const normalizeSource = (source: string | undefined): MusicSource => { switch (source) { case 'netease': return 'wy'; case 'qq': return 'tx'; case 'kuwo': return 'kw'; case 'wy': case 'tx': case 'kw': case 'kg': case 'mg': return source; default: return 'wy'; } }; const musicSources: Array<{ key: MusicSource; label: string }> = [ { key: 'wy', label: '网易云' }, { key: 'tx', label: 'QQ' }, { key: 'kw', label: '酷我' }, { key: 'kg', label: '酷狗' }, { key: 'mg', label: '咪咕' }, ]; const buildStreamUrl = (song: Song, source: MusicSource, songQuality: '128k' | '320k' | 'flac' | 'flac24bit') => { const params = new URLSearchParams({ songId: song.id, source, quality: songQuality, songmid: song.songmid || song.id.split('_').slice(1).join('_'), name: song.name, artist: song.artist, }); if (song.durationText) params.set('durationText', song.durationText); return `/api/music/v2/stream?${params.toString()}`; }; const getMusicProxyEnabled = () => { if (typeof window === 'undefined') return true; return (window as any).RUNTIME_CONFIG?.MUSIC_PROXY_ENABLED !== false; }; const fetchPlayData = async ( song: Song, source: MusicSource, songQuality: '128k' | '320k' | 'flac' | 'flac24bit', includeUrl = false ) => { const response = await fetch('/api/music/v2/play', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ includeUrl, song: { songId: song.id, source, songmid: song.songmid, name: song.name, artist: song.artist, album: song.album, cover: song.pic, durationSec: song.duration, durationText: song.durationText, }, quality: songQuality, }), }); return response.json(); }; const beginResolving = () => { setResolvingCount((prev) => prev + 1); }; const endResolving = () => { setResolvingCount((prev) => Math.max(0, prev - 1)); }; const saveHistoryRecord = async ( record: PlayRecord, song: Song, playTime: number, totalDuration: number, lastPlayedAt = Date.now() ) => { await fetch('/api/music/v2/history', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ song: { songId: record.id, source: record.platform, songmid: song.songmid, name: song.name, artist: song.artist, album: song.album, cover: song.pic, durationSec: totalDuration || song.duration || 0, durationText: song.durationText, }, playProgressSec: playTime, lastPlayedAt, lastQuality: quality, }), }); }; const saveHistoryRecordSafely = ( record: PlayRecord, song: Song, playTime = 0, totalDuration = 0, lastPlayedAt?: number ) => { saveHistoryRecord(record, song, playTime, totalDuration, lastPlayedAt).catch(err => { console.error('保存播放记录到数据库失败:', err); }); }; // 保存播放状态到 localStorage const savePlayState = () => { if (!currentSong) return; const playState = { currentSong, currentSongIndex, songs, currentPlaylistTitle, currentSource, currentView, quality, playMode, volume, currentTime: audioRef.current?.currentTime || 0, currentSongUrl, lyrics, playRecords, // 只保存播放记录(平台+ID+播放信息) playlist, // 保存完整歌曲信息(用于显示) playlistIndex, }; localStorage.setItem('musicPlayState', JSON.stringify(playState)); }; // 清空当前播放状态,并在需要时停止正在播放的音频 const clearCurrentPlaybackState = () => { const audio = audioRef.current; if (audio) { audio.pause(); audio.removeAttribute('src'); audio.load(); } setIsPlaying(false); setCurrentSong(null); setCurrentSongIndex(-1); setCurrentSongUrl(''); setCurrentTime(0); setDuration(0); setLyrics([]); setCurrentLyricIndex(-1); setShowPlayer(false); setShowLyrics(false); setShowPiPLyrics(false); setPendingSongToPlay(null); restoredTimeRef.current = 0; lastSaveTimeRef.current = 0; currentTimeRef.current = 0; localStorage.removeItem('musicPlayState'); }; // 从 localStorage 恢复播放状态(已废弃,现在统一使用数据库) const restorePlayState = async () => { // 此函数已不再使用,所有状态恢复都在 initializePlayState 中完成 }; useEffect(() => { setMusicProxyEnabled(getMusicProxyEnabled()); }, []); // 页面加载时恢复播放状态和数据库记录 useEffect(() => { const initializePlayState = async () => { try { const response = await fetch('/api/music/v2/history'); const history = await response.json(); const dbRecords = (history.data?.records || []) as DbRecord[]; const queueRecords = dbRecords; const sortedRecords: PlayRecord[] = queueRecords.map((record) => ({ platform: record.source, id: record.songId, playTime: record.playProgressSec, duration: record.durationSec || 0, timestamp: record.createdAt || record.lastPlayedAt || 0, })); const sortedSongs: Song[] = queueRecords.map((record) => ({ id: record.songId, name: record.name, artist: record.artist, album: record.album, pic: record.cover, platform: record.source, duration: record.durationSec, durationText: record.durationText, songmid: record.songmid, })); // 2. 更新播放列表 if (sortedRecords.length > 0) { setPlayRecords(sortedRecords); setPlaylist(sortedSongs); } // 3. 获取 localStorage 配置(只获取配置,不获取歌曲信息) const savedPlayState = localStorage.getItem('musicPlayState'); const playState = savedPlayState ? JSON.parse(savedPlayState) : {}; // 恢复配置状态(不包括歌曲) setSongs(playState.songs || []); setCurrentPlaylistTitle(playState.currentPlaylistTitle || ''); setCurrentSource(normalizeSource(playState.currentSource)); setCurrentView(playState.currentView || 'playlists'); setQuality(playState.quality || '320k'); setPlayMode(playState.playMode || 'loop'); setVolume(playState.volume || 100); // 4. 使用数据库的最新记录(歌曲和进度都从数据库获取) if (sortedRecords.length > 0) { const proxyEnabled = getMusicProxyEnabled(); setMusicProxyEnabled(proxyEnabled); const latestIndex = queueRecords.reduce((bestIndex, record, index) => { if (bestIndex < 0) return index; return (record.lastPlayedAt || 0) > (queueRecords[bestIndex].lastPlayedAt || 0) ? index : bestIndex; }, -1); const activeIndex = latestIndex >= 0 ? latestIndex : 0; const latestDbRecord = sortedRecords[activeIndex]; const latestDbSong = sortedSongs[activeIndex]; // 使用数据库的歌曲信息 setCurrentSong(latestDbSong); setPlaylistIndex(activeIndex); setShowPlayer(true); // 从数据库恢复播放进度 const dbPlayTime = latestDbRecord.playTime || 0; songStartTimeRef.current = Date.now(); const platform = latestDbSong.platform || 'kw'; const selectedQuality = playState.quality || '320k'; const restoreTime = () => { if (audioRef.current && dbPlayTime > 0) { audioRef.current.currentTime = dbPlayTime; } }; if (proxyEnabled) { const streamUrl = buildStreamUrl(latestDbSong, platform, selectedQuality); setCurrentSongUrl(streamUrl); if (audioRef.current) { audioRef.current.src = streamUrl; audioRef.current.addEventListener('loadedmetadata', restoreTime, { once: true }); audioRef.current.load(); } fetchPlayData(latestDbSong, platform, selectedQuality, false) .then((data) => { if (data.success && data.data?.lyric?.lyric) { const parsedLyrics = parseLyric(data.data.lyric.lyric, data.data.lyric.tlyric); setLyrics(parsedLyrics); } }) .catch((error) => { console.error('加载歌词失败:', error); }); } else { const data = await fetchPlayData(latestDbSong, platform, selectedQuality, true); if (data.success && data.data?.play?.directUrl && audioRef.current) { setCurrentSongUrl(data.data.play.directUrl); audioRef.current.src = data.data.play.directUrl; audioRef.current.addEventListener('loadedmetadata', restoreTime, { once: true }); audioRef.current.load(); if (data.data.lyric?.lyric) { const parsedLyrics = parseLyric(data.data.lyric.lyric, data.data.lyric.tlyric); setLyrics(parsedLyrics); } } } } } catch (error) { console.error('加载播放记录失败:', error); } }; initializePlayState(); }, []); // 恢复 PiP 偏好设置 useEffect(() => { const savedOpacity = localStorage.getItem('lyricsPiPOpacity'); const savedMinimized = localStorage.getItem('lyricsPiPMinimized'); if (savedOpacity) setPipOpacity(parseFloat(savedOpacity)); if (savedMinimized) setPipMinimized(savedMinimized === 'true'); }, []); // 监听来自 PiP 窗口的消息 useEffect(() => { const handleMessage = (event: MessageEvent) => { switch (event.data.type) { case 'PIP_OPACITY_CHANGE': setPipOpacity(event.data.opacity); localStorage.setItem('lyricsPiPOpacity', event.data.opacity.toString()); break; case 'PIP_MINIMIZED_CHANGE': setPipMinimized(event.data.minimized); localStorage.setItem('lyricsPiPMinimized', event.data.minimized.toString()); break; case 'PIP_CLOSE': setShowPiPLyrics(false); break; } }; window.addEventListener('message', handleMessage); return () => window.removeEventListener('message', handleMessage); }, []); // 监听播放状态变化,自动保存 useEffect(() => { if (currentSong) { savePlayState(); } }, [currentSong, currentSongIndex, songs, currentPlaylistTitle, currentSource, currentView, quality, playMode, volume, currentSongUrl, lyrics, playRecords, playlistIndex]); // 监听 playRecords 变化,更新 playlistIndex useEffect(() => { if (pendingSongToPlay) { const index = playRecords.findIndex( r => r.platform === pendingSongToPlay.platform && r.id === pendingSongToPlay.id ); setPlaylistIndex(index); setPendingSongToPlay(null); } }, [playRecords, pendingSongToPlay]); // 同步音量状态到 audio 元素 useEffect(() => { if (audioRef.current) { audioRef.current.volume = volume / 100; } }, [volume]); // 加载排行榜列表 const loadPlaylists = async (source: string) => { setLoading(true); try { const boardsResponse = await fetch(`/api/music/v2/discovery/boards?source=${source}`); const boardsData = await boardsResponse.json(); if (boardsResponse.ok && boardsData.success) { setPlaylists((boardsData.data?.list || []).map((item: any) => ({ id: item.id, name: item.name, source: normalizeSource(item.source || boardsData.data?.source || source), updateFrequency: item.updateFrequency || item.description || '', }))); } else { console.error('加载排行榜失败:', boardsData); setPlaylists([]); } } catch (error) { console.error('加载排行榜失败:', error); setPlaylists([]); } finally { setLoading(false); } }; // 加载歌单详情 const loadPlaylist = async (playlistId: string, playlistName: string, playlistSource?: MusicSource) => { setLoading(true); try { const source = playlistSource || currentSource; const response = await fetch( `/api/music/v2/discovery/board-songs?source=${source}&boardId=${playlistId}` ); const data = await response.json(); setSongs((data.data?.list || []).map(mapSong)); setCurrentPlaylistTitle(playlistName); setCurrentView('songs'); } catch (error) { console.error('加载歌单失败:', error); setSongs([]); } finally { setLoading(false); } }; // 当前排行榜歌单:播放全部 const handlePlayAllCurrentSongs = async () => { setLoadingCurrentPlayAll(true); try { if (songs.length === 0) { setToast({ message: '当前歌单为空', type: 'error', onClose: () => setToast(null), }); return; } await fetch('/api/music/v2/history', { method: 'DELETE' }); const baseTime = Date.now(); const recordsToAdd = songs.map((song, i) => ({ song: { songId: song.id, source: song.platform, songmid: song.songmid, name: song.name, artist: song.artist, album: song.album, cover: song.pic, durationSec: song.duration || 0, durationText: song.durationText, }, playProgressSec: 0, lastPlayedAt: baseTime + i, playCount: 1, lastQuality: quality, })); await fetch('/api/music/v2/history', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ records: recordsToAdd }), }); const newRecords: PlayRecord[] = songs.map((song, i) => ({ platform: song.platform, id: song.id, playTime: 0, duration: song.duration || 0, timestamp: baseTime + i, })); setPlayRecords(newRecords); setPlaylist(songs); setPlaylistIndex(0); await playSong(songs[0], 0); setToast({ message: `已开始播放 ${currentPlaylistTitle || '当前歌单'}`, type: 'success', onClose: () => setToast(null), }); } catch (error) { console.error('排行榜播放全部失败:', error); setToast({ message: '播放全部失败', type: 'error', onClose: () => setToast(null), }); } finally { setLoadingCurrentPlayAll(false); } }; // 搜索歌曲 const searchSongs = async () => { if (!searchKeyword.trim()) return; setLoading(true); try { const response = await fetch( `/api/music/v2/search?source=${currentSource}&q=${encodeURIComponent(searchKeyword)}&page=1&limit=20` ); const data = await response.json(); setSongs((data.data?.list || []).map(mapSong)); setCurrentPlaylistTitle(`搜索: ${searchKeyword}`); setCurrentView('songs'); } catch (error) { console.error('搜索失败:', error); setSongs([]); } finally { setLoading(false); } }; // 打开添加到歌单弹窗 const handleAddToPlaylist = (song: Song, e: React.MouseEvent) => { e.stopPropagation(); // 阻止事件冒泡,避免触发播放 setSongToAddToPlaylist(song); setShowAddToPlaylistModal(true); }; // 稍后播放:追加到当前播放列表末尾,不立即播放 const handlePlayLater = (song: Song, e: React.MouseEvent) => { e.stopPropagation(); if (!currentSong && playlist.length === 0 && playRecords.length === 0) { playSong(song, -1); return; } const platform = song.platform || currentSource; const exists = playlist.some((item) => item.id === song.id && item.platform === platform); if (exists) { setToast({ message: '歌曲已在播放列表中', type: 'info', onClose: () => setToast(null), }); return; } const record: PlayRecord = { platform, id: song.id, playTime: 0, duration: song.duration || 0, timestamp: Date.now(), }; setPlayRecords((prev) => [...prev, record]); setPlaylist((prev) => [...prev, { ...song, platform }]); saveHistoryRecordSafely(record, { ...song, platform }, 0, song.duration || 0, 0); setToast({ message: '已加入稍后播放', type: 'success', onClose: () => setToast(null), }); }; // 加载用户歌单列表 const loadUserPlaylists = async () => { try { setLoadingUserPlaylists(true); const response = await fetch('/api/music/v2/playlists'); if (response.ok) { const data = await response.json(); setUserPlaylists(data.data?.playlists || []); } } catch (error) { console.error('加载歌单失败:', error); } finally { setLoadingUserPlaylists(false); } }; // 加载歌单中的歌曲 const loadUserPlaylistSongs = async (playlistId: string) => { try { setLoadingUserPlaylistSongs(true); const response = await fetch(`/api/music/v2/playlists/${playlistId}/songs`); if (response.ok) { const data = await response.json(); setUserPlaylistSongs((data.data?.songs || []).map((song: any) => ({ ...song, id: song.songId, platform: song.source, pic: song.cover, duration: song.durationSec, }))); } } catch (error) { console.error('加载歌单歌曲失败:', error); } finally { setLoadingUserPlaylistSongs(false); } }; // 选择歌单 const handleSelectUserPlaylist = (playlist: any) => { setSelectedUserPlaylist(playlist); loadUserPlaylistSongs(playlist.id); }; // 播放全部歌单歌曲 const handlePlayAllPlaylist = async () => { if (!selectedUserPlaylist || userPlaylistSongs.length === 0) { setToast({ message: '歌单为空', type: 'error', onClose: () => setToast(null), }); return; } setLoadingPlayAll(true); try { // 1. 清空所有播放历史 await fetch('/api/music/v2/history', { method: 'DELETE' }); // 2. 清空本地状态 setPlayRecords([]); setPlaylist([]); // 3. 批量添加歌单中的所有歌曲到播放历史 const baseTime = Date.now(); const recordsToAdd = userPlaylistSongs.map((song, i) => ({ song: { songId: song.id, source: song.platform, songmid: song.songmid, name: song.name, artist: song.artist, album: song.album, cover: song.pic, durationSec: song.duration || 0, durationText: song.durationText, }, playProgressSec: 0, lastPlayedAt: baseTime + i, playCount: 1, lastQuality: quality, })); // 一次性批量添加所有歌曲 const response = await fetch('/api/music/v2/history', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ records: recordsToAdd, }), }); if (!response.ok) { throw new Error('批量添加歌曲失败'); } // 4. 立即更新本地状态 const newRecords: PlayRecord[] = userPlaylistSongs.map((song, i) => ({ platform: song.platform, id: song.id, playTime: 0, duration: song.duration || 0, timestamp: baseTime + i, })); const newPlaylist: Song[] = userPlaylistSongs.map((song) => ({ id: song.id, name: song.name, artist: song.artist, album: song.album, pic: song.pic, platform: song.platform, duration: song.duration, durationText: song.durationText, songmid: song.songmid, })); setPlayRecords(newRecords); setPlaylist(newPlaylist); // 5. 直接播放第一首歌 if (userPlaylistSongs.length > 0) { setPlaylistIndex(0); await playSong(userPlaylistSongs[0], 0); } setToast({ message: `已将 ${userPlaylistSongs.length} 首歌曲添加到播放列表`, type: 'success', onClose: () => setToast(null), }); } catch (error) { console.error('播放全部失败:', error); setToast({ message: '播放全部失败', type: 'error', onClose: () => setToast(null), }); } finally { setLoadingPlayAll(false); } }; // 删除歌单 const handleDeleteUserPlaylist = async (playlistId: string) => { setConfirmModal({ isOpen: true, title: '确认删除', message: '确定要删除这个歌单吗?', onConfirm: async () => { // 先关闭确认框 setConfirmModal({ isOpen: false, title: '', message: '', onConfirm: () => {}, onCancel: () => {}, }); // 然后执行删除 setDeletingPlaylistId(playlistId); try { const response = await fetch(`/api/music/v2/playlists/${playlistId}`, { method: 'DELETE' }); if (response.ok) { setToast({ message: '删除成功', type: 'success', onClose: () => setToast(null), }); if (selectedUserPlaylist?.id === playlistId) { setSelectedUserPlaylist(null); setUserPlaylistSongs([]); } loadUserPlaylists(); } else { const data = await response.json(); setToast({ message: data.error || '删除失败', type: 'error', onClose: () => setToast(null), }); } } catch (error) { console.error('删除歌单失败:', error); setToast({ message: '删除歌单失败', type: 'error', onClose: () => setToast(null), }); } finally { setDeletingPlaylistId(null); } }, onCancel: () => { setConfirmModal({ isOpen: false, title: '', message: '', onConfirm: () => {}, onCancel: () => {}, }); }, }); }; // 从歌单中移除歌曲 const handleRemoveSongFromUserPlaylist = async (song: any) => { if (!selectedUserPlaylist) return; setConfirmModal({ isOpen: true, title: '确认移除', message: `确定要从歌单中移除 "${song.name}" 吗?`, onConfirm: async () => { try { const response = await fetch( `/api/music/v2/playlists/${selectedUserPlaylist.id}/songs?songId=${encodeURIComponent(song.id)}`, { method: 'DELETE' } ); if (response.ok) { setToast({ message: '移除成功', type: 'success', onClose: () => setToast(null), }); loadUserPlaylistSongs(selectedUserPlaylist.id); } else { const data = await response.json(); setToast({ message: data.error || '移除失败', type: 'error', onClose: () => setToast(null), }); } } catch (error) { console.error('移除歌曲失败:', error); setToast({ message: '移除歌曲失败', type: 'error', onClose: () => setToast(null), }); } setConfirmModal({ isOpen: false, title: '', message: '', onConfirm: () => {}, onCancel: () => {}, }); }, onCancel: () => { setConfirmModal({ isOpen: false, title: '', message: '', onConfirm: () => {}, onCancel: () => {}, }); }, }); }; // 播放歌曲 const playSong = async (song: Song, index: number) => { beginResolving(); try { // 使用歌曲自己的平台信息,如果没有则使用当前选择的平台 const platform = song.platform || currentSource; const proxyEnabled = getMusicProxyEnabled(); setMusicProxyEnabled(proxyEnabled); // 记录歌曲开始播放的时间 songStartTimeRef.current = Date.now(); // 先设置当前歌曲和显示播放器 setCurrentSong(song); setCurrentSongIndex(index); setShowPlayer(true); setLyrics([]); // 清空旧歌词 // 添加到播放记录和播放列表 const record: PlayRecord = { platform: platform, id: song.id, playTime: 0, // 初始播放时间 duration: song.duration || 0, // 将在音频加载后更新 timestamp: Date.now(), }; // 设置待播放歌曲信息,用于在 playRecords 更新后找到索引 setPendingSongToPlay({ platform, id: song.id }); setPlayRecords(prev => { const existingIndex = prev.findIndex(r => r.platform === record.platform && r.id === record.id); if (existingIndex >= 0) { // 记录已存在,更新时间戳但不重置播放时间 const updated = [...prev]; updated[existingIndex] = { ...updated[existingIndex], timestamp: Date.now(), }; return updated; } else { // 新记录,添加到列表末尾 return [...prev, record]; } }); setPlaylist(prev => { const existingIndex = prev.findIndex(s => s.id === song.id && s.platform === platform); if (existingIndex >= 0) { return prev; } else { return [...prev, { ...song, platform }]; } }); saveHistoryRecordSafely(record, { ...song, platform }, 0, song.duration || 0); if (proxyEnabled) { const streamUrl = buildStreamUrl(song, platform, quality); setCurrentSongUrl(streamUrl); if (audioRef.current) { audioRef.current.src = streamUrl; audioRef.current.load(); audioRef.current.play().catch(err => { console.error('播放失败:', err); }); setIsPlaying(true); } fetchPlayData(song, platform, quality, false) .then((data) => { if (data.success) { if (data.data.song?.cover) { setCurrentSong({ ...song, pic: data.data.song.cover, platform, }); } if (data.data.lyric?.lyric) { const parsedLyrics = parseLyric(data.data.lyric.lyric, data.data.lyric.tlyric); setLyrics(parsedLyrics); } } else { console.error('播放信息获取失败:', data); } }) .catch((error) => { console.error('加载歌词失败:', error); }); } else { const data = await fetchPlayData(song, platform, quality, true); if (data.success && data.data?.play?.directUrl) { if (data.data.song?.cover) { setCurrentSong({ ...song, pic: data.data.song.cover, platform, }); } if (data.data.lyric?.lyric) { const parsedLyrics = parseLyric(data.data.lyric.lyric, data.data.lyric.tlyric); setLyrics(parsedLyrics); } setCurrentSongUrl(data.data.play.directUrl); if (audioRef.current) { audioRef.current.src = data.data.play.directUrl; audioRef.current.load(); audioRef.current.play().catch(err => { console.error('播放失败:', err); }); setIsPlaying(true); } } else { console.error('播放信息获取失败:', data); } } } catch (error) { console.error('播放失败:', error); } finally { endResolving(); } }; // 解析歌词文本 const parseLyric = (lyricText: string, tlyricText?: string): LyricLine[] => { if (!lyricText && !tlyricText) return []; // 匹配 [mm:ss.xx] 或 [mm:ss] 格式 const timeRegex = /\[(\d{2}):(\d{2})(?:\.(\d{2,3}))?\]/g; const parseLyricText = (text: string) => { const parsed = new Map(); const lines = text.split('\n'); lines.forEach(line => { const matches = Array.from(line.matchAll(timeRegex)); if (matches.length > 0) { const content = line.replace(timeRegex, '').trim(); if (content) { matches.forEach(match => { const minutes = parseInt(match[1]); const seconds = parseInt(match[2]); const milliseconds = match[3] ? parseInt(match[3].padEnd(3, '0')) : 0; const time = minutes * 60 + seconds + milliseconds / 1000; parsed.set(time, content); }); } } }); return parsed; }; const mainMap = parseLyricText(lyricText || ''); const transMap = parseLyricText(tlyricText || ''); const times = Array.from(new Set([ ...Array.from(mainMap.keys()), ...Array.from(transMap.keys()), ])).sort((a, b) => a - b); return times .map(time => ({ time, text: mainMap.get(time) || '', translation: transMap.get(time) || undefined, })) .filter(line => line.text || line.translation); }; // 切换播放/暂停 const togglePlay = () => { if (audioRef.current) { if (isPlaying) { audioRef.current.pause(); setIsPlaying(false); // 暂停时保存状态到 localStorage 和数据库 savePlayState(); // 前5秒不保存(避免加载时的跳转触发保存) if (Date.now() - songStartTimeRef.current < 5000) { return; } // 保存到数据库 if (currentSong && playlistIndex >= 0 && playRecords[playlistIndex]) { const record = playRecords[playlistIndex]; saveHistoryRecord(record, currentSong, audioRef.current.currentTime, audioRef.current.duration || 0).catch(err => { console.error('暂停时保存播放记录失败:', err); }); } } else { audioRef.current.play().catch(err => { console.error('播放失败:', err); }); setIsPlaying(true); } } }; // 上一曲 const playPrev = () => { // 优先从播放列表切换 if (playlist.length > 0) { // 如果已经是第一首,循环到最后一首 const prevIndex = playlistIndex > 0 ? playlistIndex - 1 : playlist.length - 1; setPlaylistIndex(prevIndex); playSong(playlist[prevIndex], -1); } else if (currentSongIndex > 0) { playSong(songs[currentSongIndex - 1], currentSongIndex - 1); } }; // 下一曲 const playNext = () => { // 优先从播放列表切换 if (playlist.length > 0) { // 如果已经是最后一首,循环到第一首 const nextIndex = playlistIndex < playlist.length - 1 ? playlistIndex + 1 : 0; setPlaylistIndex(nextIndex); playSong(playlist[nextIndex], -1); } else if (currentSongIndex < songs.length - 1) { playSong(songs[currentSongIndex + 1], currentSongIndex + 1); } }; // 切换音质 const cycleQuality = () => { const qualities: Array<'128k' | '320k' | 'flac' | 'flac24bit'> = ['128k', '320k', 'flac', 'flac24bit']; const currentIndex = qualities.indexOf(quality); const nextIndex = (currentIndex + 1) % qualities.length; setQuality(qualities[nextIndex]); }; // 清空播放记录 const handleClearPlayRecords = () => { setConfirmModal({ isOpen: true, title: '确认清空', message: '确定要清空全部播放记录吗?', onConfirm: async () => { try { await fetch('/api/music/v2/history', { method: 'DELETE' }); clearCurrentPlaybackState(); setPlaylist([]); setPlayRecords([]); setPlaylistIndex(-1); setToast({ message: '播放记录已清空', type: 'success', onClose: () => setToast(null), }); } catch (error) { console.error('清空播放记录失败:', error); setToast({ message: '清空播放记录失败', type: 'error', onClose: () => setToast(null), }); } finally { setConfirmModal({ isOpen: false, title: '', message: '', onConfirm: () => {}, onCancel: () => {}, }); } }, onCancel: () => { setConfirmModal({ isOpen: false, title: '', message: '', onConfirm: () => {}, onCancel: () => {}, }); }, }); }; // 切换播放模式 const toggleMode = () => { const modes: Array<'loop' | 'single' | 'random'> = ['loop', 'single', 'random']; const currentIndex = modes.indexOf(playMode); const nextIndex = (currentIndex + 1) % modes.length; setPlayMode(modes[nextIndex]); }; // 返回 const goBack = () => { if (currentView === 'songs') { setCurrentView('playlists'); setSongs([]); } else if (currentView === 'myPlaylists') { setCurrentView('playlists'); setSelectedUserPlaylist(null); setUserPlaylistSongs([]); } else { router.back(); } }; // 下载歌曲 const downloadSong = () => { if (!currentSongUrl || !currentSong) return; // 创建一个临时的 a 标签来触发下载 const link = document.createElement('a'); link.href = currentSongUrl; link.download = `${currentSong.name} - ${currentSong.artist}.mp3`; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; // 切换平台 const switchSource = (source: MusicSource) => { setCurrentSource(source); setCurrentView('playlists'); setSongs([]); setSearchKeyword(''); }; // 音频事件监听 useEffect(() => { const audio = audioRef.current; if (!audio) return; const handleTimeUpdate = () => { setCurrentTime(audio.currentTime); // 更新当前歌词索引 if (lyrics.length > 0) { let index = -1; for (let i = 0; i < lyrics.length; i++) { if (lyrics[i].time <= audio.currentTime) { index = i; } else { break; } } setCurrentLyricIndex(index); } // 每20秒保存一次播放进度和播放时间 const now = Date.now(); if (now - lastSaveTimeRef.current > 20000) { lastSaveTimeRef.current = now; // 前5秒不保存(避免加载时的跳转触发保存) if (Date.now() - songStartTimeRef.current < 5000) { return; } // 更新当前播放记录的播放时间 if (currentSong && playlistIndex >= 0) { setPlayRecords(prev => { const updated = [...prev]; if (updated[playlistIndex]) { updated[playlistIndex] = { ...updated[playlistIndex], playTime: audio.currentTime, }; // 保存到数据库 const record = updated[playlistIndex]; saveHistoryRecord(record, currentSong, audio.currentTime, audio.duration || 0).catch(err => { console.error('保存播放记录到数据库失败:', err); }); } return updated; }); } savePlayState(); } }; const handleLoadedMetadata = () => { // 恢复播放进度 if (restoredTimeRef.current > 0) { audio.currentTime = restoredTimeRef.current; restoredTimeRef.current = 0; // 清除标记 } }; const handleDurationChange = () => { setDuration(audio.duration); // 前5秒不保存(避免加载时的跳转触发保存) if (Date.now() - songStartTimeRef.current < 5000) { return; } // 更新当前播放记录的总时长 if (currentSong && playlistIndex >= 0) { setPlayRecords(prev => { const updated = [...prev]; if (updated[playlistIndex]) { updated[playlistIndex] = { ...updated[playlistIndex], duration: audio.duration, }; // 保存到数据库(包含时长信息) const record = updated[playlistIndex]; saveHistoryRecord(record, currentSong, record.playTime, audio.duration).catch(err => { console.error('保存播放记录到数据库失败:', err); }); } return updated; }); } }; const handleEnded = () => { if (playMode === 'single') { audio.currentTime = 0; audio.play(); } else if (playMode === 'random') { // 优先从播放列表中随机选择 if (playlist.length > 0) { const randomIndex = Math.floor(Math.random() * playlist.length); setPlaylistIndex(randomIndex); playSong(playlist[randomIndex], -1); } else if (songs.length > 0) { const randomIndex = Math.floor(Math.random() * songs.length); playSong(songs[randomIndex], randomIndex); } } else { playNext(); } }; audio.addEventListener('timeupdate', handleTimeUpdate); audio.addEventListener('loadedmetadata', handleLoadedMetadata); audio.addEventListener('durationchange', handleDurationChange); audio.addEventListener('ended', handleEnded); return () => { audio.removeEventListener('timeupdate', handleTimeUpdate); audio.removeEventListener('loadedmetadata', handleLoadedMetadata); audio.removeEventListener('durationchange', handleDurationChange); audio.removeEventListener('ended', handleEnded); }; }, [playMode, songs, currentSongIndex, lyrics, currentSong, playlistIndex, playRecords]); // 初始加载 useEffect(() => { loadPlaylists(currentSource); }, [currentSource]); // 当切换到我的歌单视图时加载歌单列表 useEffect(() => { if (currentView === 'myPlaylists') { loadUserPlaylists(); } }, [currentView]); // 歌词自动滚动 useEffect(() => { if (lyricsContainerRef.current && currentLyricIndex >= 0) { const container = lyricsContainerRef.current; const activeLine = container.querySelector(`[data-index="${currentLyricIndex}"]`); if (activeLine) { activeLine.scrollIntoView({ behavior: 'smooth', block: 'center', }); } } }, [currentLyricIndex]); // 搜索框回车 const handleSearchKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { searchSongs(); } }; // 进度条拖动 const handleProgressChange = (e: React.ChangeEvent) => { const newTime = (parseFloat(e.target.value) / 100) * duration; if (audioRef.current) { audioRef.current.currentTime = newTime; } }; // 音量调节 const handleVolumeChange = (e: React.ChangeEvent) => { const newVolume = parseInt(e.target.value); setVolume(newVolume); if (audioRef.current) { audioRef.current.volume = newVolume / 100; } }; // 触摸/鼠标滑动音量调节(移动端兼容) const handleVolumeSliderInteraction = (e: React.MouseEvent | React.TouchEvent) => { e.preventDefault(); e.stopPropagation(); const slider = e.currentTarget; const rect = slider.getBoundingClientRect(); const updateVolume = (clientY: number) => { // 计算相对于滑块顶部的位置 const y = clientY - rect.top; // 限制在滑块范围内 const clampedY = Math.max(0, Math.min(rect.height, y)); // 从上到下:0% -> 100%,从下到上:100% -> 0% const percentage = 100 - (clampedY / rect.height) * 100; const newVolume = Math.round(percentage); setVolume(newVolume); if (audioRef.current) { audioRef.current.volume = newVolume / 100; } }; // 获取初始触摸/点击位置 const clientY = 'touches' in e ? e.touches[0]?.clientY || 0 : e.clientY; updateVolume(clientY); const handleMove = (moveEvent: MouseEvent | TouchEvent) => { moveEvent.preventDefault(); const moveClientY = 'touches' in moveEvent ? moveEvent.touches[0]?.clientY || 0 : moveEvent.clientY; updateVolume(moveClientY); }; const handleEnd = () => { document.removeEventListener('mousemove', handleMove); document.removeEventListener('mouseup', handleEnd); document.removeEventListener('touchmove', handleMove); document.removeEventListener('touchend', handleEnd); }; document.addEventListener('mousemove', handleMove); document.addEventListener('mouseup', handleEnd); document.addEventListener('touchmove', handleMove, { passive: false }); document.addEventListener('touchend', handleEnd); }; // PiP 窗口管理 const togglePiPLyrics = () => { if (!('documentPictureInPicture' in window)) { setToast({ message: '您的浏览器不支持画中画功能,请使用 Chrome 116+ 版本', type: 'error', }); // 降级方案:打开全屏歌词 setShowLyrics(true); return; } if (!currentSong) { setToast({ message: '请先播放歌曲', type: 'info', }); return; } setShowPiPLyrics(!showPiPLyrics); }; const progress = duration > 0 ? (currentTime / duration) * 100 : 0; const toggleSpectrum = () => { setShowSpectrum(prev => !prev); }; useEffect(() => { if (typeof window === 'undefined') return; localStorage.setItem('musicShowSpectrum', showSpectrum ? '1' : '0'); }, [showSpectrum]); const getQualityLabel = () => { switch (quality) { case '128k': return '标准'; case '320k': return 'HQ'; case 'flac': return 'SQ'; case 'flac24bit': return 'HR'; } }; const getSourceLabel = () => { switch (currentSource) { case 'wy': return '网易云'; case 'tx': return 'QQ音乐'; case 'kw': return '酷我'; case 'kg': return '酷狗'; case 'mg': return '咪咕'; } }; const formatTime = (seconds: number) => { if (isNaN(seconds) || seconds === 0) return '0:00'; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs.toString().padStart(2, '0')}`; }; useEffect(() => { currentTimeRef.current = currentTime; }, [currentTime]); useEffect(() => { const audio = audioRef.current; if (!audio || typeof window === 'undefined') return; let cancelled = false; const ensureAnalyser = async () => { try { const AudioContextClass = window.AudioContext || (window as typeof window & { webkitAudioContext?: typeof AudioContext; }).webkitAudioContext; if (!AudioContextClass) return; if (!audioContextRef.current) { audioContextRef.current = new AudioContextClass(); } if (!mediaSourceRef.current) { mediaSourceRef.current = audioContextRef.current.createMediaElementSource(audio); } if (!analyserRef.current) { const analyser = audioContextRef.current.createAnalyser(); analyser.fftSize = 512; analyser.smoothingTimeConstant = 0.8; mediaSourceRef.current.connect(analyser); analyser.connect(audioContextRef.current.destination); analyserRef.current = analyser; spectrumDataRef.current = new Uint8Array(analyser.frequencyBinCount); } if (audioContextRef.current.state === 'suspended') { await audioContextRef.current.resume(); } } catch (error) { console.warn('初始化频谱分析器失败,将使用模拟动画:', error); } }; const tick = () => { if (cancelled) return; const analyser = analyserRef.current; const data = spectrumDataRef.current; const isActive = !audio.paused && !audio.ended; let nextBars = Array.from({ length: SPECTRUM_BIN_COUNT }, () => SPECTRUM_IDLE_LEVEL); if (isActive && analyser && data) { analyser.getByteFrequencyData(data); const usableBins = Math.max(1, Math.floor(data.length * 0.88)); nextBars = Array.from({ length: SPECTRUM_BIN_COUNT }, (_, index) => { const start = Math.floor((index / SPECTRUM_BIN_COUNT) * usableBins); const end = Math.max(start + 1, Math.floor(((index + 1) / SPECTRUM_BIN_COUNT) * usableBins)); let total = 0; for (let i = start; i < end; i++) { total += data[i] ?? 0; } const average = total / Math.max(1, end - start); const rightBias = index / Math.max(1, SPECTRUM_BIN_COUNT - 1); const highFreqCompensation = 1 + rightBias * 0.85; const floorLift = rightBias * 0.035; return Math.max( SPECTRUM_IDLE_LEVEL, Math.min(1, (average / 255) * highFreqCompensation + floorLift) ); }); } else if (isActive) { nextBars = Array.from({ length: SPECTRUM_BIN_COUNT }, (_, index) => { const wave = Math.sin(currentTimeRef.current * 5.2 + index * 0.28 + spectrumSeedRef.current) * 0.12 + Math.sin(currentTimeRef.current * 2.6 + index * 0.16) * 0.08 + 0.22; return Math.max(SPECTRUM_IDLE_LEVEL, Math.min(0.65, wave)); }); } setSpectrumBars(prev => nextBars.map((value, index) => { const previous = prev[index] ?? SPECTRUM_IDLE_LEVEL; return previous + (value - previous) * (isActive ? 0.34 : 0.12); }) ); spectrumFrameRef.current = window.requestAnimationFrame(tick); }; void ensureAnalyser(); spectrumFrameRef.current = window.requestAnimationFrame(tick); return () => { cancelled = true; if (spectrumFrameRef.current) { window.cancelAnimationFrame(spectrumFrameRef.current); spectrumFrameRef.current = null; } }; }, [currentSong]); useEffect(() => { return () => { if (typeof window !== 'undefined' && spectrumFrameRef.current) { window.cancelAnimationFrame(spectrumFrameRef.current); } analyserRef.current?.disconnect(); mediaSourceRef.current?.disconnect(); audioContextRef.current?.close().catch(() => undefined); }; }, []); return (
<> {resolvingCount > 0 && (
解析中
{resolvingCount}
)} {/* Header */}
音乐
{musicSources.map((source) => ( ))}
{(currentView === 'songs' || currentView === 'myPlaylists') && ( )}
setSearchKeyword(e.target.value)} onKeyDown={handleSearchKeyDown} className="w-full h-full appearance-none border-0 bg-transparent pl-9 pr-4 text-sm text-white outline-none focus:outline-none focus:ring-0 font-mono placeholder:text-zinc-500" placeholder="搜索歌曲或艺术家..." />
{/* Main Content */}
{loading && ( )} {/* Playlists View */} {currentView === 'playlists' && !loading && (

排行榜

{getSourceLabel()}
{playlists.length > 0 ? (
{playlists.map((playlist, index) => ( ))}
) : (
当前音源暂无排行榜
你可以切换其它音源,或使用上方搜索继续找歌。
)}
)} {/* Songs View */} {currentView === 'songs' && !loading && (

{currentPlaylistTitle}

{songs.length} 首歌曲
{songs.map((song, index) => (
playSong(song, index)} > {index + 1}
playSong(song, index)} >
{song.name}
{song.artist}
playSong(song, index)} > {song.artist}
playSong(song, index)} > {getSourceLabel()}
))}
)} {/* My Playlists View */} {currentView === 'myPlaylists' && (
{/* Playlists List */}

歌单列表

{loadingUserPlaylists ? ( ) : userPlaylists.length === 0 ? (
还没有歌单
) : (
{userPlaylists.map((playlist) => (
handleSelectUserPlaylist(playlist)} >
{playlist.cover ? ( {playlist.name} ) : (
)}
{playlist.name}
{playlist.description && (
{playlist.description}
)}
))}
)}
{/* Playlist Songs */}
{selectedUserPlaylist ? (

{selectedUserPlaylist.name}

{selectedUserPlaylist.description && (

{selectedUserPlaylist.description}

)}
{loadingUserPlaylistSongs ? ( ) : userPlaylistSongs.length === 0 ? (
歌单为空
) : (
{userPlaylistSongs.map((song, index) => (
{index + 1}
{song.pic && ( {song.name} )}
{song.name}
{song.artist}
))}
)}
) : (

选择一个歌单查看详情

)}
)}
{/* Player */} {showPlayer && currentSong && (
{/* Progress Bar */}
{/* Song Info */}
setShowLyrics(true)} > {currentSong.pic ? ( {currentSong.name} { // 图片加载失败时显示默认图标 e.currentTarget.style.display = 'none'; }} /> ) : ( )}
{currentSong.name}
{currentSong.artist}
{/* Controls */}
{/* Right Controls */}
)} {/* Audio Element */}
); }