/** * 音乐播放器 - HTML5 Audio API */ (function() { 'use strict'; const SONGS = window.SITE_DATA ? window.SITE_DATA.songs : []; let playlist = [...SONGS]; let currentIndex = -1; let playMode = localStorage.getItem('playMode') || 'sequence'; let volume = parseFloat(localStorage.getItem('volume')) || 0.8; let muted = false; const audio = document.getElementById('audioElement'); if (!audio) return; // --- 封面路径处理(新增)--- // 根据你的目录结构,封面存放在根目录 covers/ 下 function getCoverUrl(cover) { const DEFAULT_COVER = 'assets/images/default-cover.svg'; if (!cover) return DEFAULT_COVER; if (cover.startsWith('http')) return cover; if (cover.includes('/')) return cover; return 'covers/' + cover; } // --- DOM Elements --- const btnPlay = document.getElementById('btnPlay'); const btnPrev = document.getElementById('btnPrev'); const btnNext = document.getElementById('btnNext'); const btnShuffle = document.getElementById('btnShuffle'); const btnRepeat = document.getElementById('btnRepeat'); const btnMute = document.getElementById('btnMute'); const btnPlaylist = document.getElementById('btnPlaylist'); const playerEl = document.getElementById('player'); const playerTitle = document.getElementById('playerTitle'); const playerArtist = document.getElementById('playerArtist'); const playerCover = document.getElementById('playerCover'); const timeCurrent = document.getElementById('timeCurrent'); const timeTotal = document.getElementById('timeTotal'); const progressBar = document.getElementById('progressBar'); const progressFill = document.getElementById('progressFill'); const progressThumb = document.getElementById('progressThumb'); const volumeBar = document.getElementById('volumeBar'); const volumeFill = document.getElementById('volumeFill'); const playlistDrawer = document.getElementById('playlistDrawer'); const drawerBody = document.getElementById('drawerBody'); const drawerOverlay = document.getElementById('drawerOverlay'); const drawerClose = document.getElementById('drawerClose'); const bgGlow = document.getElementById('bgGlow'); const playerBgGlow = document.getElementById('playerBgGlow'); const heroDisc = document.querySelector('.hero-disc'); // --- Storage Helpers --- const Store = { get(key, def) { try { const v = localStorage.getItem(key); return v !== null ? JSON.parse(v) : def; } catch(e) { return def; } }, set(key, val) { try { localStorage.setItem(key, JSON.stringify(val)); } catch(e) {} } }; let favorites = Store.get('favorites', []); let recent = Store.get('recent', []); // --- Core Functions --- function loadSong(index, autoplay) { if (index < 0 || index >= playlist.length) return; currentIndex = index; const song = playlist[index]; audio.src = song.file; audio.load(); playerTitle.textContent = song.title || '未知歌曲'; playerArtist.textContent = song.artist || '--'; // 【修改】使用 getCoverUrl playerCover.src = getCoverUrl(song.cover); playerEl.classList.add('active'); updatePlayingUI(song); // 【修改】使用 getCoverUrl updateBackground(getCoverUrl(song.cover)); updateDrawerActive(); Store.set('currentSong', { id: song.id, file: song.file }); if (autoplay) play(); // Record recent recent = recent.filter(id => id !== song.id); recent.unshift(song.id); if (recent.length > 50) recent = recent.slice(0, 50); Store.set('recent', recent); // Update play count via API fetch('api/data.php?action=play_count&id=' + song.id, { method: 'GET' }).catch(()=>{}); } function play() { audio.play().catch(()=>{}); } function pause() { audio.pause(); } function togglePlay() { if (audio.paused) { if (!audio.src && playlist.length > 0) { loadSong(0, true); } else { play(); } } else { pause(); } } function next() { if (playMode === 'shuffle') { let idx; do { idx = Math.floor(Math.random() * playlist.length); } while (idx === currentIndex && playlist.length > 1); loadSong(idx, true); } else { let idx = currentIndex + 1; if (idx >= playlist.length) idx = 0; loadSong(idx, true); } } function prev() { if (audio.currentTime > 3) { audio.currentTime = 0; return; } let idx = currentIndex - 1; if (idx < 0) idx = playlist.length - 1; loadSong(idx, true); } function onEnded() { if (playMode === 'repeat') { audio.currentTime = 0; play(); } else { next(); } } // --- UI Updates --- function updatePlayingUI(song) { document.querySelectorAll('.music-card.playing, .song-item.playing, .drawer-song.playing').forEach(el => { el.classList.remove('playing'); }); document.querySelectorAll('[data-song-id="' + song.id + '"]').forEach(el => { el.classList.add('playing'); }); } function updateBackground(coverUrl) { if (!coverUrl || coverUrl.indexOf('default-cover') > -1) return; if (bgGlow) { bgGlow.style.background = 'radial-gradient(ellipse 60% 50% at 70% 30%, rgba(60,60,60,0.3), transparent 70%), url(' + coverUrl + ')'; bgGlow.style.backgroundSize = 'cover, cover'; bgGlow.style.backgroundPosition = 'center, center'; bgGlow.style.filter = 'blur(80px)'; bgGlow.style.opacity = '0.3'; } } function updatePlayButton() { const iconsPlay = document.querySelectorAll('.icon-play'); const iconsPause = document.querySelectorAll('.icon-pause'); if (audio.paused) { iconsPlay.forEach(el => el.style.display = ''); iconsPause.forEach(el => el.style.display = 'none'); if (heroDisc) heroDisc.classList.remove('playing'); } else { iconsPlay.forEach(el => el.style.display = 'none'); iconsPause.forEach(el => el.style.display = ''); if (heroDisc) heroDisc.classList.add('playing'); } } function updateProgress() { const cur = audio.currentTime || 0; const dur = audio.duration || 0; const pct = dur > 0 ? (cur / dur) * 100 : 0; if (progressFill) progressFill.style.width = pct + '%'; if (progressThumb) progressThumb.style.left = pct + '%'; if (timeCurrent) timeCurrent.textContent = fmtTime(cur); if (timeTotal) timeTotal.textContent = fmtTime(dur); const mobileFill = document.getElementById('progressFillMobile'); if (mobileFill) mobileFill.style.width = pct + '%'; if (cur > 0) { Store.set('playPos', cur); } } function fmtTime(sec) { if (!sec || isNaN(sec)) return '0:00'; const m = Math.floor(sec / 60); const s = Math.floor(sec % 60); return m + ':' + (s < 10 ? '0' : '') + s; } function setVolume(v) { volume = Math.max(0, Math.min(1, v)); audio.volume = muted ? 0 : volume; if (volumeFill) volumeFill.style.width = (volume * 100) + '%'; Store.set('volume', volume); updateMuteIcon(); } function updateMuteIcon() { const vol = document.querySelector('.icon-vol'); const mut = document.querySelector('.icon-mute'); if (muted || volume === 0) { if (vol) vol.style.display = 'none'; if (mut) mut.style.display = ''; } else { if (vol) vol.style.display = ''; if (mut) mut.style.display = 'none'; } } function updateModeUI() { if (btnShuffle) btnShuffle.classList.toggle('active', playMode === 'shuffle'); if (btnRepeat) btnRepeat.classList.toggle('active', playMode === 'repeat'); } // --- Progress Bar Drag --- let dragging = false; function seekTo(e) { const rect = progressBar.getBoundingClientRect(); const pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); if (audio.duration) { audio.currentTime = pct * audio.duration; } } if (progressBar) { progressBar.addEventListener('mousedown', function(e) { dragging = true; seekTo(e); }); document.addEventListener('mousemove', function(e) { if (dragging) seekTo(e); }); document.addEventListener('mouseup', function() { dragging = false; }); progressBar.addEventListener('touchstart', function(e) { dragging = true; const t = e.touches[0]; seekTo({ clientX: t.clientX }); }, { passive: true }); document.addEventListener('touchmove', function(e) { if (dragging) { const t = e.touches[0]; seekTo({ clientX: t.clientX }); } }, { passive: true }); document.addEventListener('touchend', function() { dragging = false; }); } // --- Volume Bar Drag --- let volDragging = false; function volSeek(e) { const rect = volumeBar.getBoundingClientRect(); const pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); muted = false; setVolume(pct); } if (volumeBar) { volumeBar.addEventListener('mousedown', function(e) { volDragging = true; volSeek(e); }); document.addEventListener('mousemove', function(e) { if (volDragging) volSeek(e); }); document.addEventListener('mouseup', function() { volDragging = false; }); } // --- Playlist Drawer --- function renderPlaylist() { if (!drawerBody) return; let html = ''; playlist.forEach((song, i) => { const isPlaying = i === currentIndex; // 【修改】使用 getCoverUrl html += '
'; html += ''; html += '
'; html += '
' + escHtml(song.title) + '
'; html += '
' + escHtml(song.artist) + '
'; html += '
'; }); drawerBody.innerHTML = html; drawerBody.querySelectorAll('.drawer-song').forEach(el => { el.addEventListener('click', function() { const idx = parseInt(this.dataset.index); loadSong(idx, true); }); }); } function openDrawer() { if (playlistDrawer) playlistDrawer.classList.add('open'); if (drawerOverlay) drawerOverlay.classList.add('show'); renderPlaylist(); } function closeDrawer() { if (playlistDrawer) playlistDrawer.classList.remove('open'); if (drawerOverlay) drawerOverlay.classList.remove('show'); } function updateDrawerActive() { if (!playlistDrawer || !playlistDrawer.classList.contains('open')) return; renderPlaylist(); } // --- Favorites --- window.toggleFav = function(songId, btn) { songId = parseInt(songId); const idx = favorites.indexOf(songId); if (idx > -1) { favorites.splice(idx, 1); if (btn) btn.classList.remove('fav-active'); } else { favorites.push(songId); if (btn) btn.classList.add('fav-active'); } Store.set('favorites', favorites); }; window.isFav = function(songId) { return favorites.indexOf(parseInt(songId)) > -1; }; // --- Public API --- window.MusicPlayer = { playSongById(id) { const idx = playlist.findIndex(s => s.id == id); if (idx > -1) { loadSong(idx, true); } }, playSongByIndex(idx) { if (idx >= 0 && idx < playlist.length) { loadSong(idx, true); } }, setPlaylist(songs) { playlist = songs; }, getCurrentSong() { return currentIndex >= 0 ? playlist[currentIndex] : null; }, togglePlay, next, prev }; // --- Helper --- function escHtml(str) { const d = document.createElement('div'); d.textContent = str || ''; return d.innerHTML; } // --- Event Listeners --- if (btnPlay) btnPlay.addEventListener('click', togglePlay); if (btnPrev) btnPrev.addEventListener('click', prev); if (btnNext) btnNext.addEventListener('click', next); if (btnShuffle) { btnShuffle.addEventListener('click', function() { playMode = playMode === 'shuffle' ? 'sequence' : 'shuffle'; localStorage.setItem('playMode', playMode); updateModeUI(); }); } if (btnRepeat) { btnRepeat.addEventListener('click', function() { playMode = playMode === 'repeat' ? 'sequence' : 'repeat'; localStorage.setItem('playMode', playMode); updateModeUI(); }); } if (btnMute) { btnMute.addEventListener('click', function() { muted = !muted; audio.volume = muted ? 0 : volume; updateMuteIcon(); }); } if (btnPlaylist) btnPlaylist.addEventListener('click', openDrawer); if (drawerClose) drawerClose.addEventListener('click', closeDrawer); if (drawerOverlay) drawerOverlay.addEventListener('click', closeDrawer); // Mobile controls const btnPlayMobile = document.getElementById('btnPlayMobile'); const btnPrevMobile = document.getElementById('btnPrevMobile'); const btnNextMobile = document.getElementById('btnNextMobile'); const btnPlaylistMobile = document.getElementById('btnPlaylistMobile'); const progressBarMobile = document.getElementById('progressBarMobile'); if (btnPlayMobile) btnPlayMobile.addEventListener('click', togglePlay); if (btnPrevMobile) btnPrevMobile.addEventListener('click', prev); if (btnNextMobile) btnNextMobile.addEventListener('click', next); if (btnPlaylistMobile) btnPlaylistMobile.addEventListener('click', openDrawer); if (progressBarMobile) { progressBarMobile.addEventListener('click', function(e) { const rect = this.getBoundingClientRect(); const pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); if (audio.duration) audio.currentTime = pct * audio.duration; }); } audio.addEventListener('play', updatePlayButton); audio.addEventListener('pause', updatePlayButton); audio.addEventListener('timeupdate', updateProgress); audio.addEventListener('loadedmetadata', updateProgress); audio.addEventListener('ended', onEnded); // --- Init --- setVolume(volume); updateModeUI(); // Restore last session const lastSong = Store.get('currentSong', null); if (lastSong && lastSong.id) { const idx = playlist.findIndex(s => s.id == lastSong.id); if (idx > -1) { currentIndex = idx; const song = playlist[idx]; audio.src = song.file; audio.load(); playerTitle.textContent = song.title || '未知歌曲'; playerArtist.textContent = song.artist || '--'; // 【修改】使用 getCoverUrl playerCover.src = getCoverUrl(song.cover); playerEl.classList.add('active'); updateBackground(getCoverUrl(song.cover)); const pos = Store.get('playPos', 0); if (pos > 0 && pos < (audio.duration || 99999)) { audio.addEventListener('loadedmetadata', function() { audio.currentTime = pos; }, { once: true }); } } } // Auto-scan on load fetch('api/scan.php', { method: 'GET' }).then(r => r.json()).then(data => { if (data && data.new_count > 0) { location.reload(); } }).catch(()=>{}); })();