xukunCai
commited on
Update index.html
Browse files- index.html +216 -50
index.html
CHANGED
|
@@ -6,7 +6,6 @@
|
|
| 6 |
<title>云音乐 - 在线音乐播放器</title>
|
| 7 |
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
| 8 |
<style>
|
| 9 |
-
/* CSS样式部分保持不变,此处省略以保持简洁 */
|
| 10 |
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 11 |
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; background: #f8f9fa; color: #333; overflow-x: hidden; line-height: 1.6; }
|
| 12 |
.navbar { background: #ffffff; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); padding: 15px 0; position: sticky; top: 0; z-index: 100; }
|
|
@@ -101,7 +100,6 @@
|
|
| 101 |
</style>
|
| 102 |
</head>
|
| 103 |
<body>
|
| 104 |
-
<!-- HTML结构部分保持不变 -->
|
| 105 |
<nav class="navbar">
|
| 106 |
<div class="nav-container">
|
| 107 |
<div class="logo"><i class="fas fa-music"></i><span>云音乐</span></div>
|
|
@@ -193,11 +191,11 @@
|
|
| 193 |
<script>
|
| 194 |
// Dr.Kun: 常量和全局变量
|
| 195 |
const API_BASE = 'https://music-api.gdstudio.xyz/api.php';
|
| 196 |
-
const PLAYER_STATE_KEY = '
|
| 197 |
let currentPlaylist = [];
|
| 198 |
let currentIndex = -1;
|
| 199 |
let isPlaying = false;
|
| 200 |
-
let timeToRestore = 0;
|
| 201 |
|
| 202 |
// Dr.Kun: DOM元素缓存
|
| 203 |
const audioPlayer = document.getElementById('audioPlayer');
|
|
@@ -212,24 +210,21 @@
|
|
| 212 |
const volumeSlider = document.getElementById('volumeSlider');
|
| 213 |
const searchInput = document.getElementById('searchInput');
|
| 214 |
|
| 215 |
-
// Dr.Kun: 核心升级 - 保存播放器状态(现在包含播放进度)
|
| 216 |
function savePlayerState() {
|
|
|
|
| 217 |
const state = {
|
| 218 |
playlist: currentPlaylist,
|
| 219 |
index: currentIndex,
|
| 220 |
volume: audioPlayer.volume,
|
| 221 |
quality: qualitySelect.value,
|
| 222 |
source: sourceSelect.value,
|
| 223 |
-
currentTime: audioPlayer.currentTime > 1 ? audioPlayer.currentTime : 0
|
| 224 |
};
|
| 225 |
try {
|
| 226 |
localStorage.setItem(PLAYER_STATE_KEY, JSON.stringify(state));
|
| 227 |
-
} catch (e) {
|
| 228 |
-
console.error("保存状态失败:", e);
|
| 229 |
-
}
|
| 230 |
}
|
| 231 |
|
| 232 |
-
// Dr.Kun: 核心升级 - 从localStorage加载播放器状态
|
| 233 |
async function loadPlayerState() {
|
| 234 |
try {
|
| 235 |
const savedState = localStorage.getItem(PLAYER_STATE_KEY);
|
|
@@ -238,15 +233,15 @@
|
|
| 238 |
const state = JSON.parse(savedState);
|
| 239 |
|
| 240 |
currentPlaylist = state.playlist || [];
|
| 241 |
-
currentIndex = state.index
|
| 242 |
-
timeToRestore = state.currentTime || 0;
|
| 243 |
|
| 244 |
qualitySelect.value = state.quality || '320';
|
| 245 |
sourceSelect.value = state.source || 'netease';
|
| 246 |
|
| 247 |
const volumeValue = (state.volume !== undefined ? state.volume : 0.8) * 100;
|
| 248 |
volumeSlider.value = volumeValue;
|
| 249 |
-
setVolume(volumeValue, false);
|
| 250 |
|
| 251 |
if (currentPlaylist.length > 0) {
|
| 252 |
displaySearchResults(currentPlaylist);
|
|
@@ -254,10 +249,12 @@
|
|
| 254 |
|
| 255 |
if (currentIndex > -1 && currentPlaylist[currentIndex]) {
|
| 256 |
const song = currentPlaylist[currentIndex];
|
| 257 |
-
// Dr.Kun: 只更新UI,不获取URL,等待用户操作或后续逻辑
|
| 258 |
await updateCurrentSongInfo(song);
|
| 259 |
updateActiveItem();
|
| 260 |
-
|
|
|
|
|
|
|
|
|
|
| 261 |
const quality = qualitySelect.value;
|
| 262 |
const urlResponse = await fetch(`${API_BASE}?types=url&source=${song.source}&id=${song.id}&br=${quality}`);
|
| 263 |
const urlData = await urlResponse.json();
|
|
@@ -274,7 +271,6 @@
|
|
| 274 |
}
|
| 275 |
}
|
| 276 |
|
| 277 |
-
// Dr.Kun: 页面加载时执行的初始化函数
|
| 278 |
async function initializeApp() {
|
| 279 |
if (window.innerWidth >= 1200) {
|
| 280 |
document.getElementById('searchView').classList.add('active');
|
|
@@ -286,19 +282,15 @@
|
|
| 286 |
|
| 287 |
await loadPlayerState();
|
| 288 |
|
| 289 |
-
// Dr.Kun: 监听器
|
| 290 |
qualitySelect.onchange = savePlayerState;
|
| 291 |
sourceSelect.onchange = savePlayerState;
|
| 292 |
-
|
| 293 |
-
// Dr.Kun: 核心升级 - 在页面卸载前做最后一次保存
|
| 294 |
window.addEventListener('beforeunload', savePlayerState);
|
| 295 |
-
|
| 296 |
-
|
|
|
|
| 297 |
}
|
| 298 |
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
function setVolume(value, shouldSave = true) { // Dr.Kun: 增加一个参数判断是否需要保存
|
| 302 |
audioPlayer.volume = value / 100;
|
| 303 |
const volumeIcon = document.querySelector('.volume-icon');
|
| 304 |
if (value == 0) volumeIcon.className = 'fas fa-volume-mute volume-icon';
|
|
@@ -312,13 +304,16 @@
|
|
| 312 |
async function playSong(index) {
|
| 313 |
if (index < 0 || index >= currentPlaylist.length) return;
|
| 314 |
currentIndex = index;
|
| 315 |
-
timeToRestore = 0;
|
| 316 |
const song = currentPlaylist[index];
|
| 317 |
await updateCurrentSongInfo(song);
|
| 318 |
updateActiveItem();
|
| 319 |
-
|
| 320 |
try {
|
| 321 |
showNotification('正在加载音乐...', 'info');
|
|
|
|
|
|
|
|
|
|
| 322 |
const quality = qualitySelect.value;
|
| 323 |
const urlResponse = await fetch(`${API_BASE}?types=url&source=${song.source}&id=${song.id}&br=${quality}`);
|
| 324 |
const urlData = await urlResponse.json();
|
|
@@ -326,15 +321,12 @@
|
|
| 326 |
if (urlData && urlData.url) {
|
| 327 |
audioPlayer.src = urlData.url;
|
| 328 |
audioPlayer.load();
|
| 329 |
-
loadLyrics(song);
|
| 330 |
document.getElementById('downloadSongBtn').disabled = false;
|
| 331 |
document.getElementById('downloadLyricBtn').disabled = false;
|
| 332 |
|
| 333 |
const playPromise = audioPlayer.play();
|
| 334 |
if (playPromise !== undefined) {
|
| 335 |
-
playPromise.
|
| 336 |
-
showNotification(`开始播放: ${song.name}`, 'success');
|
| 337 |
-
}).catch(error => {
|
| 338 |
showNotification('自动播放失败,请手动点击播放', 'warning');
|
| 339 |
});
|
| 340 |
}
|
|
@@ -347,28 +339,80 @@
|
|
| 347 |
}
|
| 348 |
}
|
| 349 |
|
| 350 |
-
// Dr.Kun: 核心升级 - 在元数据加载后恢复播放进度
|
| 351 |
audioPlayer.addEventListener('loadedmetadata', () => {
|
| 352 |
totalTimeSpan.textContent = formatTime(audioPlayer.duration);
|
| 353 |
if (timeToRestore > 0 && timeToRestore < audioPlayer.duration) {
|
| 354 |
audioPlayer.currentTime = timeToRestore;
|
| 355 |
-
timeToRestore = 0;
|
| 356 |
-
updateProgress();
|
| 357 |
showNotification('已恢复上次播放进度', 'info');
|
| 358 |
}
|
| 359 |
});
|
| 360 |
|
| 361 |
-
// Dr.Kun: 保持其他事件监听和函数不变
|
| 362 |
audioPlayer.addEventListener('play', () => { isPlaying = true; updatePlayButton(); currentCover.classList.add('playing'); });
|
| 363 |
-
audioPlayer.addEventListener('pause', () => { isPlaying = false; updatePlayButton(); currentCover.classList.remove('playing'); savePlayerState(); });
|
| 364 |
audioPlayer.addEventListener('ended', nextSong);
|
| 365 |
setInterval(updateProgress, 500);
|
| 366 |
searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') searchMusic(); });
|
| 367 |
document.addEventListener('keydown', (e) => { if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) return; const keyMap = { ' ': togglePlay, 'ArrowRight': () => { if(audioPlayer.duration) audioPlayer.currentTime = Math.min(audioPlayer.currentTime + 5, audioPlayer.duration); }, 'ArrowLeft': () => { if(audioPlayer.duration) audioPlayer.currentTime = Math.max(audioPlayer.currentTime - 5, 0); }, 'ArrowUp': () => { const v = Math.min(parseInt(volumeSlider.value) + 5, 100); volumeSlider.value = v; setVolume(v); }, 'ArrowDown': () => { const v = Math.max(parseInt(volumeSlider.value) - 5, 0); volumeSlider.value = v; setVolume(v); }, 'n': nextSong, 'N': nextSong, 'p': previousSong, 'P': previousSong }; if (keyMap[e.key]) { e.preventDefault(); keyMap[e.key](); } });
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
async function downloadSong(index) { const song = currentPlaylist[index]; const quality = qualitySelect.value; try { showNotification('正在获取下载链接...', 'info'); const response = await fetch(`${API_BASE}?types=url&source=${song.source}&id=${song.id}&br=${quality}`); const data = await response.json(); if (data && data.url) { const link = document.createElement('a'); link.href = data.url; link.download = `${song.name} - ${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}.mp3`; link.target = '_blank'; document.body.appendChild(link); link.click(); document.body.removeChild(link); showNotification('开始下载音乐文件', 'success'); } else { showNotification('无法获取下载链接', 'error'); } } catch (error) { console.error('下载失败:', error); showNotification('下载失败,请稍后重试', 'error'); } }
|
| 373 |
async function downloadLyric(index) { const song = currentPlaylist[index]; try { showNotification('正在获取歌词...', 'info'); const response = await fetch(`${API_BASE}?types=lyric&source=${song.source}&id=${song.lyric_id || song.id}`); const data = await response.json(); if (data && data.lyric) { let lyricContent = `歌曲:${song.name}\r\n歌手:${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}\r\n专辑:${song.album}\r\n\r\n${data.lyric}`; if (data.tlyric) lyricContent += `\r\n\r\n=== 翻译 ===\r\n${data.tlyric}`; const blob = new Blob([lyricContent], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `${song.name} - ${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}.lrc`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); showNotification('歌词下载完成', 'success'); } else { showNotification('该歌曲暂无歌词', 'warning'); } } catch (error) { console.error('下载歌词失败:', error); showNotification('下载歌词失败,请稍后重试', 'error'); } }
|
| 374 |
async function downloadCurrentSong() { if (currentIndex === -1) { showNotification('请先选择歌曲', 'warning'); return; } await downloadSong(currentIndex); }
|
|
@@ -378,18 +422,140 @@
|
|
| 378 |
function updatePlayButton() { playBtn.querySelector('i').className = isPlaying ? 'fas fa-pause' : 'fas fa-play'; }
|
| 379 |
function togglePlay() { if (!audioPlayer.src) { showNotification('请先选择一首歌曲', 'warning'); return; } if (isPlaying) { audioPlayer.pause(); } else { audioPlayer.play().catch(e => console.error("播放命令被拒绝", e)); } }
|
| 380 |
function seekTo(event) { if(!audioPlayer.duration) return; const progressBar = event.currentTarget; const rect = progressBar.getBoundingClientRect(); audioPlayer.currentTime = ((event.clientX - rect.left) / rect.width) * audioPlayer.duration; }
|
| 381 |
-
async function loadLyrics(song) { try { const response = await fetch(`${API_BASE}?types=lyric&source=${song.source}&id=${song.lyric_id || song.id}`); const data = await response.json(); let lyrics = []; if (data && data.lyric) { lyrics = parseLyrics(data.lyric); if (data.tlyric) { const tLyrics = parseLyrics(data.tlyric); mergeLyrics(lyrics, tLyrics); } } displayLyrics(lyrics); } catch (error) { console.error('加载歌词失败:', error); lyricsContainer.innerHTML = '<div class="lyric-line">加载歌词失败</div>'; } }
|
| 382 |
-
function parseLyrics(lyricText) { const lines = lyricText.split('\n'); const lyrics = []; const timeRegex = /\[(\d{2}):(\d{2})[.:](\d{2,3})\]/g; for (const line of lines) { if (!line.trim()) continue; let text = line.replace(timeRegex, '').trim(); if (text) { let match; timeRegex.lastIndex = 0; while ((match = timeRegex.exec(line)) !== null) { const timeInSeconds = parseInt(match[1]) * 60 + parseInt(match[2]) + parseInt(match[3]) / (match[3].length === 3 ? 1000 : 100); lyrics.push({ time: timeInSeconds, text: text }); } } } return lyrics.sort((a, b) => a.time - b.time); }
|
| 383 |
-
function mergeLyrics(originalLyrics, translatedLyrics) { const translatedMap = new Map(translatedLyrics.map(l => [Math.round(l.time * 10), l.text])); originalLyrics.forEach(l => { const key = Math.round(l.time * 10); const translatedText = translatedMap.get(key); if (translatedText) { l.text += `<br><span style="color: #adb5bd; font-size: 0.9em;">${translatedText}</span>`; } }); }
|
| 384 |
-
function displayLyrics(lyrics) { lyricsContainer.innerHTML = lyrics.length === 0 ? '<div class="lyric-line">暂无歌词</div>' : ''; lyrics.forEach(lyric => { const line = document.createElement('div'); line.className = 'lyric-line'; line.innerHTML = lyric.text; line.setAttribute('data-time', lyric.time); line.onclick = () => { if(audioPlayer.duration) audioPlayer.currentTime = lyric.time; }; lyricsContainer.appendChild(line); }); }
|
| 385 |
-
function updateProgress() { if (audioPlayer.duration) { progressFill.style.width = `${(audioPlayer.currentTime / audioPlayer.duration) * 100}%`; currentTimeSpan.textContent = formatTime(audioPlayer.currentTime); highlightCurrentLyric(); } }
|
| 386 |
-
function formatTime(seconds) { const min = Math.floor(seconds / 60); const sec = Math.floor(seconds % 60); return `${min}:${sec.toString().padStart(2, '0')}`; }
|
| 387 |
-
function highlightCurrentLyric() { const currentTime = audioPlayer.currentTime; const lyricLines = document.querySelectorAll('.lyric-line'); if (lyricLines.length === 0 || lyricsContainer.offsetParent === null) return; let activeIndex = -1; for (let i = lyricLines.length - 1; i >= 0; i--) { if (parseFloat(lyricLines[i].getAttribute('data-time')) <= currentTime + 0.2) { activeIndex = i; break; } } lyricLines.forEach((line, index) => { if (line.classList.contains('active') !== (index === activeIndex)) { line.classList.toggle('active', index === activeIndex); } }); if (activeIndex > -1) { const activeLine = lyricLines[activeIndex]; const scrollPosition = activeLine.offsetTop - lyricsContainer.clientHeight / 2 + activeLine.clientHeight / 2; lyricsContainer.scrollTo({ top: scrollPosition, behavior: 'smooth' }); } }
|
| 388 |
-
function showNotification(message, type = 'info') { const existing = document.querySelector('.custom-notification'); if (existing) existing.remove(); const notification = document.createElement('div'); notification.className = `custom-notification notification-${type}`; const icons = { info: 'info-circle', success: 'check-circle', error: 'exclamation-circle', warning: 'exclamation-triangle' }; notification.innerHTML = `<i class="fas fa-${icons[type]}"></i><span>${message}</span>`; document.body.appendChild(notification); Object.assign(notification.style, { position: 'fixed', bottom: '20px', right: '20px', padding: '12px 20px', borderRadius: '8px', boxShadow: '0 4px 12px rgba(0,0,0,0.15)', display: 'flex', alignItems: 'center', gap: '10px', fontSize: '14px', zIndex: '9999', transition: 'all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)', transform: 'translateY(100px)', opacity: '0' }); const colors = { success: { bg: 'rgba(40, 167, 69, 0.9)', color: '#fff' }, error: { bg: 'rgba(220, 53, 69, 0.9)', color: '#fff' }, warning: { bg: 'rgba(255, 193, 7, 0.9)', color: '#212529' }, info: { bg: 'rgba(74, 144, 226, 0.9)', color: '#fff' } }; notification.style.backgroundColor = colors[type].bg; notification.style.color = colors[type].color; setTimeout(() => { notification.style.transform = 'translateY(0)'; notification.style.opacity = '1'; }, 50); setTimeout(() => { notification.style.transform = 'translateY(100px)'; notification.style.opacity = '0'; setTimeout(() => notification.remove(), 400); }, 3000); }
|
| 389 |
-
function previousSong() { if (currentPlaylist.length === 0) return; currentIndex = (currentIndex - 1 + currentPlaylist.length) % currentPlaylist.length; playSong(currentIndex); }
|
| 390 |
-
function nextSong() { if (currentPlaylist.length === 0) return; currentIndex = (currentIndex + 1) % currentPlaylist.length; playSong(currentIndex); }
|
| 391 |
|
| 392 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 393 |
initializeApp();
|
| 394 |
</script>
|
| 395 |
</body>
|
|
|
|
| 6 |
<title>云音乐 - 在线音乐播放器</title>
|
| 7 |
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
| 8 |
<style>
|
|
|
|
| 9 |
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 10 |
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; background: #f8f9fa; color: #333; overflow-x: hidden; line-height: 1.6; }
|
| 11 |
.navbar { background: #ffffff; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); padding: 15px 0; position: sticky; top: 0; z-index: 100; }
|
|
|
|
| 100 |
</style>
|
| 101 |
</head>
|
| 102 |
<body>
|
|
|
|
| 103 |
<nav class="navbar">
|
| 104 |
<div class="nav-container">
|
| 105 |
<div class="logo"><i class="fas fa-music"></i><span>云音乐</span></div>
|
|
|
|
| 191 |
<script>
|
| 192 |
// Dr.Kun: 常量和全局变量
|
| 193 |
const API_BASE = 'https://music-api.gdstudio.xyz/api.php';
|
| 194 |
+
const PLAYER_STATE_KEY = 'cloudMusicPlayerState_v3';
|
| 195 |
let currentPlaylist = [];
|
| 196 |
let currentIndex = -1;
|
| 197 |
let isPlaying = false;
|
| 198 |
+
let timeToRestore = 0;
|
| 199 |
|
| 200 |
// Dr.Kun: DOM元素缓存
|
| 201 |
const audioPlayer = document.getElementById('audioPlayer');
|
|
|
|
| 210 |
const volumeSlider = document.getElementById('volumeSlider');
|
| 211 |
const searchInput = document.getElementById('searchInput');
|
| 212 |
|
|
|
|
| 213 |
function savePlayerState() {
|
| 214 |
+
if (currentIndex === -1) return; // 如果没有歌曲,则不保存
|
| 215 |
const state = {
|
| 216 |
playlist: currentPlaylist,
|
| 217 |
index: currentIndex,
|
| 218 |
volume: audioPlayer.volume,
|
| 219 |
quality: qualitySelect.value,
|
| 220 |
source: sourceSelect.value,
|
| 221 |
+
currentTime: audioPlayer.currentTime > 1 ? audioPlayer.currentTime : 0
|
| 222 |
};
|
| 223 |
try {
|
| 224 |
localStorage.setItem(PLAYER_STATE_KEY, JSON.stringify(state));
|
| 225 |
+
} catch (e) { console.error("保存状态失败:", e); }
|
|
|
|
|
|
|
| 226 |
}
|
| 227 |
|
|
|
|
| 228 |
async function loadPlayerState() {
|
| 229 |
try {
|
| 230 |
const savedState = localStorage.getItem(PLAYER_STATE_KEY);
|
|
|
|
| 233 |
const state = JSON.parse(savedState);
|
| 234 |
|
| 235 |
currentPlaylist = state.playlist || [];
|
| 236 |
+
currentIndex = state.index !== undefined ? state.index : -1;
|
| 237 |
+
timeToRestore = state.currentTime || 0;
|
| 238 |
|
| 239 |
qualitySelect.value = state.quality || '320';
|
| 240 |
sourceSelect.value = state.source || 'netease';
|
| 241 |
|
| 242 |
const volumeValue = (state.volume !== undefined ? state.volume : 0.8) * 100;
|
| 243 |
volumeSlider.value = volumeValue;
|
| 244 |
+
setVolume(volumeValue, false);
|
| 245 |
|
| 246 |
if (currentPlaylist.length > 0) {
|
| 247 |
displaySearchResults(currentPlaylist);
|
|
|
|
| 249 |
|
| 250 |
if (currentIndex > -1 && currentPlaylist[currentIndex]) {
|
| 251 |
const song = currentPlaylist[currentIndex];
|
|
|
|
| 252 |
await updateCurrentSongInfo(song);
|
| 253 |
updateActiveItem();
|
| 254 |
+
|
| 255 |
+
lyricsContainer.innerHTML = `<div class="loading"><i class="fas fa-spinner"></i><div>正在加载歌词...</div></div>`;
|
| 256 |
+
await loadLyrics(song);
|
| 257 |
+
|
| 258 |
const quality = qualitySelect.value;
|
| 259 |
const urlResponse = await fetch(`${API_BASE}?types=url&source=${song.source}&id=${song.id}&br=${quality}`);
|
| 260 |
const urlData = await urlResponse.json();
|
|
|
|
| 271 |
}
|
| 272 |
}
|
| 273 |
|
|
|
|
| 274 |
async function initializeApp() {
|
| 275 |
if (window.innerWidth >= 1200) {
|
| 276 |
document.getElementById('searchView').classList.add('active');
|
|
|
|
| 282 |
|
| 283 |
await loadPlayerState();
|
| 284 |
|
|
|
|
| 285 |
qualitySelect.onchange = savePlayerState;
|
| 286 |
sourceSelect.onchange = savePlayerState;
|
|
|
|
|
|
|
| 287 |
window.addEventListener('beforeunload', savePlayerState);
|
| 288 |
+
setInterval(() => {
|
| 289 |
+
if (isPlaying) savePlayerState();
|
| 290 |
+
}, 5000);
|
| 291 |
}
|
| 292 |
|
| 293 |
+
function setVolume(value, shouldSave = true) {
|
|
|
|
|
|
|
| 294 |
audioPlayer.volume = value / 100;
|
| 295 |
const volumeIcon = document.querySelector('.volume-icon');
|
| 296 |
if (value == 0) volumeIcon.className = 'fas fa-volume-mute volume-icon';
|
|
|
|
| 304 |
async function playSong(index) {
|
| 305 |
if (index < 0 || index >= currentPlaylist.length) return;
|
| 306 |
currentIndex = index;
|
| 307 |
+
timeToRestore = 0;
|
| 308 |
const song = currentPlaylist[index];
|
| 309 |
await updateCurrentSongInfo(song);
|
| 310 |
updateActiveItem();
|
| 311 |
+
|
| 312 |
try {
|
| 313 |
showNotification('正在加载音乐...', 'info');
|
| 314 |
+
lyricsContainer.innerHTML = `<div class="loading"><i class="fas fa-spinner"></i><div>正在加载歌词...</div></div>`;
|
| 315 |
+
await loadLyrics(song);
|
| 316 |
+
|
| 317 |
const quality = qualitySelect.value;
|
| 318 |
const urlResponse = await fetch(`${API_BASE}?types=url&source=${song.source}&id=${song.id}&br=${quality}`);
|
| 319 |
const urlData = await urlResponse.json();
|
|
|
|
| 321 |
if (urlData && urlData.url) {
|
| 322 |
audioPlayer.src = urlData.url;
|
| 323 |
audioPlayer.load();
|
|
|
|
| 324 |
document.getElementById('downloadSongBtn').disabled = false;
|
| 325 |
document.getElementById('downloadLyricBtn').disabled = false;
|
| 326 |
|
| 327 |
const playPromise = audioPlayer.play();
|
| 328 |
if (playPromise !== undefined) {
|
| 329 |
+
playPromise.catch(error => {
|
|
|
|
|
|
|
| 330 |
showNotification('自动播放失败,请手动点击播放', 'warning');
|
| 331 |
});
|
| 332 |
}
|
|
|
|
| 339 |
}
|
| 340 |
}
|
| 341 |
|
|
|
|
| 342 |
audioPlayer.addEventListener('loadedmetadata', () => {
|
| 343 |
totalTimeSpan.textContent = formatTime(audioPlayer.duration);
|
| 344 |
if (timeToRestore > 0 && timeToRestore < audioPlayer.duration) {
|
| 345 |
audioPlayer.currentTime = timeToRestore;
|
| 346 |
+
timeToRestore = 0;
|
| 347 |
+
updateProgress();
|
| 348 |
showNotification('已恢复上次播放进度', 'info');
|
| 349 |
}
|
| 350 |
});
|
| 351 |
|
|
|
|
| 352 |
audioPlayer.addEventListener('play', () => { isPlaying = true; updatePlayButton(); currentCover.classList.add('playing'); });
|
| 353 |
+
audioPlayer.addEventListener('pause', () => { isPlaying = false; updatePlayButton(); currentCover.classList.remove('playing'); savePlayerState(); });
|
| 354 |
audioPlayer.addEventListener('ended', nextSong);
|
| 355 |
setInterval(updateProgress, 500);
|
| 356 |
searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') searchMusic(); });
|
| 357 |
document.addEventListener('keydown', (e) => { if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) return; const keyMap = { ' ': togglePlay, 'ArrowRight': () => { if(audioPlayer.duration) audioPlayer.currentTime = Math.min(audioPlayer.currentTime + 5, audioPlayer.duration); }, 'ArrowLeft': () => { if(audioPlayer.duration) audioPlayer.currentTime = Math.max(audioPlayer.currentTime - 5, 0); }, 'ArrowUp': () => { const v = Math.min(parseInt(volumeSlider.value) + 5, 100); volumeSlider.value = v; setVolume(v); }, 'ArrowDown': () => { const v = Math.max(parseInt(volumeSlider.value) - 5, 0); volumeSlider.value = v; setVolume(v); }, 'n': nextSong, 'N': nextSong, 'p': previousSong, 'P': previousSong }; if (keyMap[e.key]) { e.preventDefault(); keyMap[e.key](); } });
|
| 358 |
+
|
| 359 |
+
function switchView(viewId, navItem) {
|
| 360 |
+
document.querySelectorAll('.view-container').forEach(view => view.classList.remove('active'));
|
| 361 |
+
document.getElementById(viewId).classList.add('active');
|
| 362 |
+
if (navItem) {
|
| 363 |
+
document.querySelectorAll('.mobile-nav-item').forEach(item => item.classList.remove('active'));
|
| 364 |
+
navItem.classList.add('active');
|
| 365 |
+
}
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
async function searchMusic() {
|
| 369 |
+
const keyword = searchInput.value.trim();
|
| 370 |
+
const source = sourceSelect.value;
|
| 371 |
+
if (!keyword) { showNotification('请输入搜索关键词', 'warning'); return; }
|
| 372 |
+
const resultsContainer = document.getElementById('searchResults');
|
| 373 |
+
resultsContainer.innerHTML = `<div class="loading"><i class="fas fa-spinner"></i><div>正在搜索音乐...</div></div>`;
|
| 374 |
+
try {
|
| 375 |
+
const response = await fetch(`${API_BASE}?types=search&source=${source}&name=${encodeURIComponent(keyword)}&count=30`);
|
| 376 |
+
const data = await response.json();
|
| 377 |
+
if (data && data.length > 0) {
|
| 378 |
+
currentPlaylist = data;
|
| 379 |
+
currentIndex = -1;
|
| 380 |
+
displaySearchResults(data);
|
| 381 |
+
savePlayerState();
|
| 382 |
+
} else {
|
| 383 |
+
resultsContainer.innerHTML = `<div class="error"><i class="fas fa-exclamation-triangle"></i><div>未找到相关歌曲</div></div>`;
|
| 384 |
+
}
|
| 385 |
+
} catch (error) {
|
| 386 |
+
console.error('搜索失败:', error);
|
| 387 |
+
resultsContainer.innerHTML = `<div class="error"><i class="fas fa-wifi"></i><div>网络连接失败</div></div>`;
|
| 388 |
+
}
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
async function getAlbumCoverUrl(song, size = 300) {
|
| 392 |
+
if (!song || !song.pic_id) { return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwIiBoZWlnaHQ9IjIyMCIgdmlld0JveD0iMCAwIDIyMCAyMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMjAiIGhlaWdodD0iMjIwIiBmaWxsPSIjZmZmZmZmIi8+CjxjaXJjbGUgY3g9IjExMCIgY3k9IjExMCIgcj0iODAiIGZpbGw9IiNmOGY5ZmEiLz4KPHBhdGggZD0iTTEwNSAxNzVWODVIMTE1VjE3NVoiIGZpbGw9IiM0YTkwZTIiLz4KPC9zdmc+'; }
|
| 393 |
+
try {
|
| 394 |
+
const response = await fetch(`${API_BASE}?types=pic&source=${song.source}&id=${song.pic_id}&size=${size}`);
|
| 395 |
+
const data = await response.json();
|
| 396 |
+
return (data && data.url) ? data.url : 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwIiBoZWlnaHQ9IjIyMCIgdmlld0JveD0iMCAwIDIyMCAyMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMjAiIGhlaWdodD0iMjIwIiBmaWxsPSIjZmZmZmZmIi8+CjxjaXJjbGUgY3g9IjExMCIgY3k9IjExMCIgcj0iODAiIGZpbGw9IiNmOGY5ZmEiLz4KPHBhdGggZD0iTTEwNSAxNzVWODVIMTE1VjE3NVoiIGZpbGw9IiM0YTkwZTIiLz4KPC9zdmc+';
|
| 397 |
+
} catch (error) {
|
| 398 |
+
console.error('获取专辑图失败:', error);
|
| 399 |
+
return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwIiBoZWlnaHQ9IjIyMCIgdmlld0JveD0iMCAwIDIyMCAyMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMjAiIGhlaWdodD0iMjIwIiBmaWxsPSIjZmZmZmZmIi8+CjxjaXJjbGUgY3g9IjExMCIgY3k9IjExMCIgcj0iODAiIGZpbGw9IiNmOGY5ZmEiLz4KPHBhdGggZD0iTTEwNSAxNzVWODVIMTE1VjE3NVoiIGZpbGw9IiM0YTkwZTIiLz4KPC9zdmc+';
|
| 400 |
+
}
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
function displaySearchResults(songs) {
|
| 404 |
+
const resultsContainer = document.getElementById('searchResults');
|
| 405 |
+
resultsContainer.innerHTML = '';
|
| 406 |
+
songs.forEach((song, index) => {
|
| 407 |
+
const songCard = document.createElement('div');
|
| 408 |
+
songCard.className = 'song-card';
|
| 409 |
+
songCard.onclick = () => playSong(index);
|
| 410 |
+
const artistText = Array.isArray(song.artist) ? song.artist.join(' / ') : song.artist;
|
| 411 |
+
songCard.innerHTML = ` <div class="song-index">${(index + 1).toString().padStart(2, '0')}</div> <div class="song-info"> <div class="song-name">${song.name}</div> <div class="song-artist">${artistText} ${song.album ? '· ' + song.album : ''}</div> </div> <div class="song-actions"> <button class="action-btn" onclick="event.stopPropagation(); downloadSong(${index})" title="下载音乐"><i class="fas fa-download"></i></button> <button class="action-btn" onclick="event.stopPropagation(); downloadLyric(${index})" title="下载歌词"><i class="fas fa-file-text"></i></button> </div>`;
|
| 412 |
+
resultsContainer.appendChild(songCard);
|
| 413 |
+
});
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
async function downloadSong(index) { const song = currentPlaylist[index]; const quality = qualitySelect.value; try { showNotification('正在获取下载链接...', 'info'); const response = await fetch(`${API_BASE}?types=url&source=${song.source}&id=${song.id}&br=${quality}`); const data = await response.json(); if (data && data.url) { const link = document.createElement('a'); link.href = data.url; link.download = `${song.name} - ${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}.mp3`; link.target = '_blank'; document.body.appendChild(link); link.click(); document.body.removeChild(link); showNotification('开始下载音乐文件', 'success'); } else { showNotification('无法获取下载链接', 'error'); } } catch (error) { console.error('下载失败:', error); showNotification('下载失败,请稍后重试', 'error'); } }
|
| 417 |
async function downloadLyric(index) { const song = currentPlaylist[index]; try { showNotification('正在获取歌词...', 'info'); const response = await fetch(`${API_BASE}?types=lyric&source=${song.source}&id=${song.lyric_id || song.id}`); const data = await response.json(); if (data && data.lyric) { let lyricContent = `歌曲:${song.name}\r\n歌手:${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}\r\n专辑:${song.album}\r\n\r\n${data.lyric}`; if (data.tlyric) lyricContent += `\r\n\r\n=== 翻译 ===\r\n${data.tlyric}`; const blob = new Blob([lyricContent], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `${song.name} - ${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}.lrc`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); showNotification('歌词下载完成', 'success'); } else { showNotification('该歌曲暂无歌词', 'warning'); } } catch (error) { console.error('下载歌词失败:', error); showNotification('下载歌词失败,请稍后重试', 'error'); } }
|
| 418 |
async function downloadCurrentSong() { if (currentIndex === -1) { showNotification('请先选择歌曲', 'warning'); return; } await downloadSong(currentIndex); }
|
|
|
|
| 422 |
function updatePlayButton() { playBtn.querySelector('i').className = isPlaying ? 'fas fa-pause' : 'fas fa-play'; }
|
| 423 |
function togglePlay() { if (!audioPlayer.src) { showNotification('请先选择一首歌曲', 'warning'); return; } if (isPlaying) { audioPlayer.pause(); } else { audioPlayer.play().catch(e => console.error("播放命令被拒绝", e)); } }
|
| 424 |
function seekTo(event) { if(!audioPlayer.duration) return; const progressBar = event.currentTarget; const rect = progressBar.getBoundingClientRect(); audioPlayer.currentTime = ((event.clientX - rect.left) / rect.width) * audioPlayer.duration; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
|
| 426 |
+
async function loadLyrics(song) {
|
| 427 |
+
try {
|
| 428 |
+
const response = await fetch(`${API_BASE}?types=lyric&source=${song.source}&id=${song.lyric_id || song.id}`);
|
| 429 |
+
const data = await response.json();
|
| 430 |
+
let lyrics = [];
|
| 431 |
+
if (data && data.lyric) {
|
| 432 |
+
lyrics = parseLyrics(data.lyric);
|
| 433 |
+
if (data.tlyric) {
|
| 434 |
+
const tLyrics = parseLyrics(data.tlyric);
|
| 435 |
+
mergeLyrics(lyrics, tLyrics);
|
| 436 |
+
}
|
| 437 |
+
}
|
| 438 |
+
displayLyrics(lyrics);
|
| 439 |
+
} catch (error) {
|
| 440 |
+
console.error('加载歌词失败:', error);
|
| 441 |
+
lyricsContainer.innerHTML = '<div class="lyric-line">加载歌词失败</div>';
|
| 442 |
+
}
|
| 443 |
+
}
|
| 444 |
+
|
| 445 |
+
function parseLyrics(lyricText) {
|
| 446 |
+
const lines = lyricText.split('\n');
|
| 447 |
+
const lyrics = [];
|
| 448 |
+
const timeRegex = /\[(\d{2}):(\d{2})[.:](\d{2,3})\]/g;
|
| 449 |
+
for (const line of lines) {
|
| 450 |
+
if (!line.trim()) continue;
|
| 451 |
+
let text = line.replace(timeRegex, '').trim();
|
| 452 |
+
if (text) {
|
| 453 |
+
let match;
|
| 454 |
+
timeRegex.lastIndex = 0;
|
| 455 |
+
while ((match = timeRegex.exec(line)) !== null) {
|
| 456 |
+
const timeInSeconds = parseInt(match[1]) * 60 + parseInt(match[2]) + parseInt(match[3]) / (match[3].length === 3 ? 1000 : 100);
|
| 457 |
+
lyrics.push({ time: timeInSeconds, text: text });
|
| 458 |
+
}
|
| 459 |
+
}
|
| 460 |
+
}
|
| 461 |
+
return lyrics.sort((a, b) => a.time - b.time);
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
function mergeLyrics(originalLyrics, translatedLyrics) {
|
| 465 |
+
const translatedMap = new Map(translatedLyrics.map(l => [Math.round(l.time * 10), l.text]));
|
| 466 |
+
originalLyrics.forEach(l => {
|
| 467 |
+
const key = Math.round(l.time * 10);
|
| 468 |
+
const translatedText = translatedMap.get(key);
|
| 469 |
+
if (translatedText) {
|
| 470 |
+
l.text += `<br><span style="color: #adb5bd; font-size: 0.9em;">${translatedText}</span>`;
|
| 471 |
+
}
|
| 472 |
+
});
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
function displayLyrics(lyrics) {
|
| 476 |
+
lyricsContainer.innerHTML = lyrics.length === 0 ? '<div class="lyric-line">暂无歌词</div>' : '';
|
| 477 |
+
lyrics.forEach(lyric => {
|
| 478 |
+
const line = document.createElement('div');
|
| 479 |
+
line.className = 'lyric-line';
|
| 480 |
+
line.innerHTML = lyric.text;
|
| 481 |
+
line.setAttribute('data-time', lyric.time);
|
| 482 |
+
line.onclick = () => { if(audioPlayer.duration) audioPlayer.currentTime = lyric.time; };
|
| 483 |
+
lyricsContainer.appendChild(line);
|
| 484 |
+
});
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
function updateProgress() {
|
| 488 |
+
if (audioPlayer.duration) {
|
| 489 |
+
progressFill.style.width = `${(audioPlayer.currentTime / audioPlayer.duration) * 100}%`;
|
| 490 |
+
currentTimeSpan.textContent = formatTime(audioPlayer.currentTime);
|
| 491 |
+
highlightCurrentLyric();
|
| 492 |
+
}
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
function formatTime(seconds) {
|
| 496 |
+
const min = Math.floor(seconds / 60);
|
| 497 |
+
const sec = Math.floor(seconds % 60);
|
| 498 |
+
return `${min}:${sec.toString().padStart(2, '0')}`;
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
function highlightCurrentLyric() {
|
| 502 |
+
const currentTime = audioPlayer.currentTime;
|
| 503 |
+
const lyricLines = document.querySelectorAll('.lyric-line');
|
| 504 |
+
if (lyricLines.length === 0 || lyricsContainer.offsetParent === null) return;
|
| 505 |
+
let activeIndex = -1;
|
| 506 |
+
for (let i = lyricLines.length - 1; i >= 0; i--) {
|
| 507 |
+
if (parseFloat(lyricLines[i].getAttribute('data-time')) <= currentTime + 0.2) {
|
| 508 |
+
activeIndex = i;
|
| 509 |
+
break;
|
| 510 |
+
}
|
| 511 |
+
}
|
| 512 |
+
lyricLines.forEach((line, index) => {
|
| 513 |
+
if (line.classList.contains('active') !== (index === activeIndex)) {
|
| 514 |
+
line.classList.toggle('active', index === activeIndex);
|
| 515 |
+
}
|
| 516 |
+
});
|
| 517 |
+
if (activeIndex > -1) {
|
| 518 |
+
const activeLine = lyricLines[activeIndex];
|
| 519 |
+
const scrollPosition = activeLine.offsetTop - lyricsContainer.clientHeight / 2 + activeLine.clientHeight / 2;
|
| 520 |
+
lyricsContainer.scrollTo({ top: scrollPosition, behavior: 'smooth' });
|
| 521 |
+
}
|
| 522 |
+
}
|
| 523 |
+
|
| 524 |
+
function showNotification(message, type = 'info') {
|
| 525 |
+
const existing = document.querySelector('.custom-notification');
|
| 526 |
+
if (existing) existing.remove();
|
| 527 |
+
const notification = document.createElement('div');
|
| 528 |
+
notification.className = `custom-notification notification-${type}`;
|
| 529 |
+
const icons = { info: 'info-circle', success: 'check-circle', error: 'exclamation-circle', warning: 'exclamation-triangle' };
|
| 530 |
+
notification.innerHTML = `<i class="fas fa-${icons[type]}"></i><span>${message}</span>`;
|
| 531 |
+
document.body.appendChild(notification);
|
| 532 |
+
Object.assign(notification.style, { position: 'fixed', bottom: '20px', right: '20px', padding: '12px 20px', borderRadius: '8px', boxShadow: '0 4px 12px rgba(0,0,0,0.15)', display: 'flex', alignItems: 'center', gap: '10px', fontSize: '14px', zIndex: '9999', transition: 'all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)', transform: 'translateY(100px)', opacity: '0' });
|
| 533 |
+
const colors = { success: { bg: 'rgba(40, 167, 69, 0.9)', color: '#fff' }, error: { bg: 'rgba(220, 53, 69, 0.9)', color: '#fff' }, warning: { bg: 'rgba(255, 193, 7, 0.9)', color: '#212529' }, info: { bg: 'rgba(74, 144, 226, 0.9)', color: '#fff' } };
|
| 534 |
+
notification.style.backgroundColor = colors[type].bg;
|
| 535 |
+
notification.style.color = colors[type].color;
|
| 536 |
+
setTimeout(() => {
|
| 537 |
+
notification.style.transform = 'translateY(0)';
|
| 538 |
+
notification.style.opacity = '1';
|
| 539 |
+
}, 50);
|
| 540 |
+
setTimeout(() => {
|
| 541 |
+
notification.style.transform = 'translateY(100px)';
|
| 542 |
+
notification.style.opacity = '0';
|
| 543 |
+
setTimeout(() => notification.remove(), 400);
|
| 544 |
+
}, 3000);
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
function previousSong() {
|
| 548 |
+
if (currentPlaylist.length === 0) return;
|
| 549 |
+
currentIndex = (currentIndex - 1 + currentPlaylist.length) % currentPlaylist.length;
|
| 550 |
+
playSong(currentIndex);
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
function nextSong() {
|
| 554 |
+
if (currentPlaylist.length === 0) return;
|
| 555 |
+
currentIndex = (currentIndex + 1) % currentPlaylist.length;
|
| 556 |
+
playSong(currentIndex);
|
| 557 |
+
}
|
| 558 |
+
|
| 559 |
initializeApp();
|
| 560 |
</script>
|
| 561 |
</body>
|