xukunCai commited on
Commit
1504438
·
verified ·
1 Parent(s): 1572821

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +212 -441
index.html CHANGED
@@ -757,7 +757,6 @@
757
  </style>
758
  </head>
759
  <body>
760
- <!-- 顶部导航 -->
761
  <nav class="navbar">
762
  <div class="nav-container">
763
  <div class="logo">
@@ -791,10 +790,8 @@
791
  </div>
792
  </nav>
793
 
794
- <!-- 主要内容 -->
795
  <div class="main-container">
796
  <div class="desktop-layout">
797
- <!-- 搜索结果视图 -->
798
  <div class="view-container" id="searchView">
799
  <div class="content-panel">
800
  <h2 class="panel-title">
@@ -810,7 +807,6 @@
810
  </div>
811
  </div>
812
 
813
- <!-- 播放器视图 -->
814
  <div class="view-container" id="playerView">
815
  <div class="content-panel player-panel">
816
  <div class="current-song">
@@ -845,7 +841,6 @@
845
  </div>
846
  </div>
847
 
848
- <!-- 音质选择 -->
849
  <div class="quality-container">
850
  <div class="quality-label">
851
  <i class="fas fa-music"></i>
@@ -865,7 +860,6 @@
865
  <input type="range" class="volume-slider" id="volumeSlider" min="0" max="100" value="80" onchange="setVolume(this.value)">
866
  </div>
867
 
868
- <!-- 下载区域 -->
869
  <div class="download-container">
870
  <button class="download-btn" onclick="downloadCurrentSong()" id="downloadSongBtn" disabled>
871
  <i class="fas fa-download"></i>
@@ -881,7 +875,6 @@
881
  </div>
882
  </div>
883
 
884
- <!-- 歌词视图 -->
885
  <div class="view-container" id="lyricsView">
886
  <div class="content-panel">
887
  <h2 class="panel-title">
@@ -896,7 +889,6 @@
896
  </div>
897
  </div>
898
 
899
- <!-- 移动端底部导航 -->
900
  <div class="mobile-nav">
901
  <div class="mobile-nav-items">
902
  <a href="#searchView" class="mobile-nav-item active" onclick="switchView('searchView', this)">
@@ -915,12 +907,17 @@
915
  </div>
916
 
917
  <script>
 
918
  const API_BASE = 'https://music-api.gdstudio.xyz/api.php';
 
 
 
919
  let currentPlaylist = [];
920
  let currentIndex = -1;
921
  let currentLyrics = [];
922
  let isPlaying = false;
923
 
 
924
  const audioPlayer = document.getElementById('audioPlayer');
925
  const playBtn = document.getElementById('playBtn');
926
  const progressFill = document.getElementById('progressFill');
@@ -928,113 +925,147 @@
928
  const totalTimeSpan = document.getElementById('totalTime');
929
  const lyricsContainer = document.getElementById('lyricsContainer');
930
  const currentCover = document.getElementById('currentCover');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
931
 
932
- // 初始化视图 - PC端显示所有视图,移动端只显示活动视图
933
- if (window.innerWidth >= 1200) {
934
- document.getElementById('searchView').classList.add('active');
935
- document.getElementById('playerView').classList.add('active');
936
- document.getElementById('lyricsView').classList.add('active');
937
- } else {
938
- switchView('searchView', document.querySelectorAll('.mobile-nav-item')[0]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
939
  }
940
 
941
  // 切换视图
942
  function switchView(viewId, navItem) {
943
- // 隐藏所有视图
944
- document.querySelectorAll('.view-container').forEach(view => {
945
- view.classList.remove('active');
946
- });
947
-
948
- // 显示选中视图
949
  document.getElementById(viewId).classList.add('active');
950
-
951
- // 更新导航项状态
952
  if (navItem) {
953
- document.querySelectorAll('.mobile-nav-item').forEach(item => {
954
- item.classList.remove('active');
955
- });
956
  navItem.classList.add('active');
957
  }
958
  }
959
 
960
  // 搜索音乐
961
  async function searchMusic() {
962
- const keyword = document.getElementById('searchInput').value.trim();
963
- const source = document.getElementById('sourceSelect').value;
964
-
965
  if (!keyword) {
966
  showNotification('请输入搜索关键词', 'warning');
967
  return;
968
  }
969
 
970
  const resultsContainer = document.getElementById('searchResults');
971
- resultsContainer.innerHTML = `
972
- <div class="loading">
973
- <i class="fas fa-spinner"></i>
974
- <div>正在搜索音乐...</div>
975
- </div>
976
- `;
977
 
978
  try {
979
  const response = await fetch(`${API_BASE}?types=search&source=${source}&name=${encodeURIComponent(keyword)}&count=30`);
980
  const data = await response.json();
981
-
982
  if (data && data.length > 0) {
983
  currentPlaylist = data;
 
984
  displaySearchResults(data);
 
985
  } else {
986
- resultsContainer.innerHTML = `
987
- <div class="error">
988
- <i class="fas fa-exclamation-triangle"></i>
989
- <div>未找到相关歌曲,请尝试其他关键词</div>
990
- </div>
991
- `;
992
  }
993
  } catch (error) {
994
  console.error('搜索失败:', error);
995
- resultsContainer.innerHTML = `
996
- <div class="error">
997
- <i class="fas fa-wifi"></i>
998
- <div>网络连接失败,请检查网络后重试</div>
999
- </div>
1000
- `;
1001
  }
1002
  }
1003
 
1004
- // 获取专辑图片URL
1005
  async function getAlbumCoverUrl(song, size = 300) {
1006
- if (!song.pic_id) {
1007
- return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTUiIGhlaWdodD0iNTUiIHZpZXdCb3g9IjAgMCA1NSA1NSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjU1IiBoZWlnaHQ9IjU1IiBmaWxsPSIjZmZmZmZmIi8+CjxjaXJjbGUgY3g9IjI3LjUiIGN5PSIyNy41IiByPSIyMCIgZmlsbD0iI2Y4ZjlmYSIvPgo8cGF0aCBkPSJNMjcuNSAxOEwzNSAyNy41SDMwVjM3SDI1VjI3LjUyMEwyNy41IDE4WiIgZmlsbD0iIzRhOTBlMiIvPgo8L3N2Zz4=';
1008
  }
1009
-
1010
  try {
1011
  const response = await fetch(`${API_BASE}?types=pic&source=${song.source}&id=${song.pic_id}&size=${size}`);
1012
  const data = await response.json();
1013
-
1014
- if (data && data.url) {
1015
- return data.url;
1016
- }
1017
  } catch (error) {
1018
  console.error('获取专辑图失败:', error);
 
1019
  }
1020
-
1021
- return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTUiIGhlaWdodD0iNTUiIHZpZXdCb3g9IjAgMCA1NSA1NSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjU1IiBoZWlnaHQ9IjU1IiBmaWxsPSIjZmZmZmZmIi8+CjxjaXJjbGUgY3g9IjI3LjUiIGN5PSIyNy41IiByPSIyMCIgZmlsbD0iI2Y4ZjlmYSIvPgo8cGF0aCBkPSJNMjcuNSAxOEwzNSAyNy41SDMwVjM3SDI1VjI3LjUyMEwyNy41IDE4WiIgZmlsbD0iIzRhOTBlMiIvPgo8L3N2Zz4=';
1022
  }
1023
 
1024
- // 显示搜索结果 - 卡片式布局
1025
- async function displaySearchResults(songs) {
1026
  const resultsContainer = document.getElementById('searchResults');
1027
  resultsContainer.innerHTML = '';
1028
-
1029
- for (let index = 0; index < songs.length; index++) {
1030
- const song = songs[index];
1031
  const songCard = document.createElement('div');
1032
  songCard.className = 'song-card';
1033
- songCard.onclick = () => { playSong(index); };
1034
-
1035
- // 处理过长文本,保留主要信息
1036
  const artistText = Array.isArray(song.artist) ? song.artist.join(' / ') : song.artist;
1037
-
1038
  songCard.innerHTML = `
1039
  <div class="song-index">${(index + 1).toString().padStart(2, '0')}</div>
1040
  <div class="song-info">
@@ -1042,62 +1073,44 @@
1042
  <div class="song-artist">${artistText} ${song.album ? '· ' + song.album : ''}</div>
1043
  </div>
1044
  <div class="song-actions">
1045
- <button class="action-btn" onclick="event.stopPropagation(); downloadSong(${index})" title="下载音乐">
1046
- <i class="fas fa-download"></i>
1047
- </button>
1048
- <button class="action-btn" onclick="event.stopPropagation(); downloadLyric(${index})" title="下载歌词">
1049
- <i class="fas fa-file-text"></i>
1050
- </button>
1051
- </div>
1052
- `;
1053
-
1054
  resultsContainer.appendChild(songCard);
1055
- }
1056
  }
1057
 
1058
- // 播放歌曲
1059
  async function playSong(index) {
1060
  if (index < 0 || index >= currentPlaylist.length) return;
1061
-
1062
  currentIndex = index;
1063
  const song = currentPlaylist[index];
1064
-
1065
- // 更新UI
1066
  await updateCurrentSongInfo(song);
1067
  updateActiveItem();
 
1068
 
1069
  try {
1070
  showNotification('正在加载音乐...', 'info');
1071
-
1072
- // 获取当前选择的音质
1073
- const quality = document.getElementById('qualitySelect').value;
1074
-
1075
- // 获取音乐URL
1076
  const urlResponse = await fetch(`${API_BASE}?types=url&source=${song.source}&id=${song.id}&br=${quality}`);
1077
  const urlData = await urlResponse.json();
1078
 
1079
  if (urlData && urlData.url) {
1080
  audioPlayer.src = urlData.url;
1081
  audioPlayer.load();
1082
-
1083
- // 获取歌词
1084
  loadLyrics(song);
1085
-
1086
- // 启用下载按钮
1087
  document.getElementById('downloadSongBtn').disabled = false;
1088
  document.getElementById('downloadLyricBtn').disabled = false;
1089
 
1090
- // 自动播放
1091
  const playPromise = audioPlayer.play();
1092
  if (playPromise !== undefined) {
1093
  playPromise.then(() => {
1094
  isPlaying = true;
1095
  updatePlayButton();
1096
  currentCover.classList.add('playing');
1097
- showNotification(`开始播放 (${getQualityText(urlData.br || quality)})`, 'success');
1098
  }).catch(error => {
1099
  console.error('播放失败:', error);
1100
- showNotification('播放失败,请尝试其他歌曲', 'error');
1101
  });
1102
  }
1103
  } else {
@@ -1109,41 +1122,26 @@
1109
  }
1110
  }
1111
 
1112
- // 获取音质文本
1113
  function getQualityText(br) {
1114
- const qualityMap = {
1115
- '128': '标准音质',
1116
- '192': '较高音质',
1117
- '320': '高品质',
1118
- '740': '无损音质',
1119
- '999': 'Hi-Res音质'
1120
- };
1121
  return qualityMap[br] || `${br}K`;
1122
  }
1123
-
1124
- // 下载歌曲
1125
  async function downloadSong(index) {
1126
  const song = currentPlaylist[index];
1127
- const quality = document.getElementById('qualitySelect').value;
1128
-
1129
  try {
1130
  showNotification('正在获取下载链接...', 'info');
1131
-
1132
  const response = await fetch(`${API_BASE}?types=url&source=${song.source}&id=${song.id}&br=${quality}`);
1133
  const data = await response.json();
1134
-
1135
  if (data && data.url) {
1136
- // 创建下载链接
1137
  const link = document.createElement('a');
1138
  link.href = data.url;
1139
  link.download = `${song.name} - ${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}.mp3`;
1140
  link.target = '_blank';
1141
-
1142
- // 触发下载
1143
  document.body.appendChild(link);
1144
  link.click();
1145
  document.body.removeChild(link);
1146
-
1147
  showNotification('开始下载音乐文件', 'success');
1148
  } else {
1149
  showNotification('无法获取下载链接', 'error');
@@ -1154,41 +1152,23 @@
1154
  }
1155
  }
1156
 
1157
- // 下载歌词
1158
  async function downloadLyric(index) {
1159
  const song = currentPlaylist[index];
1160
-
1161
  try {
1162
  showNotification('正在获取歌词...', 'info');
1163
-
1164
  const response = await fetch(`${API_BASE}?types=lyric&source=${song.source}&id=${song.lyric_id || song.id}`);
1165
  const data = await response.json();
1166
-
1167
  if (data && data.lyric) {
1168
- // 创建歌词文件内容
1169
- let lyricContent = `歌曲:${song.name}\r\n`;
1170
- lyricContent += `歌手:${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}\r\n`;
1171
- lyricContent += `专辑:${song.album}\r\n`;
1172
- lyricContent += `来源:${song.source}\r\n\r\n`;
1173
- lyricContent += data.lyric;
1174
-
1175
- if (data.tlyric) {
1176
- lyricContent += '\r\n=== 翻译歌词 ===\r\n';
1177
- lyricContent += data.tlyric;
1178
- }
1179
-
1180
- // 创建Blob并下载
1181
  const blob = new Blob([lyricContent], { type: 'text/plain;charset=utf-8' });
1182
  const url = URL.createObjectURL(blob);
1183
-
1184
  const link = document.createElement('a');
1185
  link.href = url;
1186
  link.download = `${song.name} - ${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}.lrc`;
1187
-
1188
  document.body.appendChild(link);
1189
  link.click();
1190
  document.body.removeChild(link);
1191
-
1192
  URL.revokeObjectURL(url);
1193
  showNotification('歌词下载完成', 'success');
1194
  } else {
@@ -1200,446 +1180,237 @@
1200
  }
1201
  }
1202
 
1203
- // 下载当前播放的歌曲
1204
  async function downloadCurrentSong() {
1205
- if (currentIndex === -1) {
1206
- showNotification('请先选择要下载的歌曲', 'warning');
1207
- return;
1208
- }
1209
-
1210
  await downloadSong(currentIndex);
1211
  }
1212
 
1213
- // 下载当前播放的歌词
1214
  async function downloadCurrentLyric() {
1215
- if (currentIndex === -1) {
1216
- showNotification('请先选择要下载歌词的歌曲', 'warning');
1217
- return;
1218
- }
1219
-
1220
  await downloadLyric(currentIndex);
1221
  }
1222
 
1223
- // 更新当前歌曲信息
1224
  async function updateCurrentSongInfo(song) {
1225
  document.getElementById('currentTitle').textContent = song.name;
1226
-
1227
  const artistText = Array.isArray(song.artist) ? song.artist.join(' / ') : song.artist;
1228
- document.getElementById('currentArtist').textContent =
1229
- `${artistText}${song.album ? ' · ' + song.album : ''}`;
1230
-
1231
- // 获取专辑图片URL
1232
- const coverUrl = await getAlbumCoverUrl(song, 500);
1233
- currentCover.src = coverUrl;
1234
  }
1235
 
1236
- // 更新活跃项目
1237
  function updateActiveItem() {
1238
  document.querySelectorAll('.song-card').forEach((item, index) => {
1239
  item.classList.toggle('active', index === currentIndex);
1240
  });
1241
  }
1242
 
1243
- // 更新播放按钮
1244
  function updatePlayButton() {
1245
- const icon = playBtn.querySelector('i');
1246
- if (isPlaying) {
1247
- icon.className = 'fas fa-pause';
1248
- } else {
1249
- icon.className = 'fas fa-play';
1250
- }
1251
  }
1252
 
1253
- // 切换播放/暂停
1254
  function togglePlay() {
1255
- if (audioPlayer.src) {
1256
- if (isPlaying) {
1257
- audioPlayer.pause();
1258
- currentCover.classList.remove('playing');
1259
- } else {
1260
- audioPlayer.play();
1261
- currentCover.classList.add('playing');
1262
- }
1263
- isPlaying = !isPlaying;
1264
- updatePlayButton();
1265
  } else {
1266
- showNotification('请先选择要播放的歌曲', 'warning');
 
1267
  }
 
 
1268
  }
1269
 
1270
- // 设置音量
1271
  function setVolume(value) {
1272
  audioPlayer.volume = value / 100;
1273
  const volumeIcon = document.querySelector('.volume-icon');
1274
-
1275
- if (value == 0) {
1276
- volumeIcon.className = 'fas fa-volume-mute volume-icon';
1277
- } else if (value < 50) {
1278
- volumeIcon.className = 'fas fa-volume-down volume-icon';
1279
- } else {
1280
- volumeIcon.className = 'fas fa-volume-up volume-icon';
1281
- }
1282
  }
1283
 
1284
- // 跳转到指定时间
1285
  function seekTo(event) {
 
1286
  const progressBar = event.currentTarget;
1287
  const rect = progressBar.getBoundingClientRect();
1288
- const pos = (event.clientX - rect.left) / rect.width;
1289
- audioPlayer.currentTime = pos * audioPlayer.duration;
1290
  }
1291
 
1292
- // 加载歌词
1293
  async function loadLyrics(song) {
1294
  try {
1295
  const response = await fetch(`${API_BASE}?types=lyric&source=${song.source}&id=${song.lyric_id || song.id}`);
1296
  const data = await response.json();
1297
-
1298
  if (data && data.lyric) {
1299
  currentLyrics = parseLyrics(data.lyric);
1300
- displayLyrics(currentLyrics);
1301
-
1302
- // 如果有翻译歌词,也显示出来
1303
  if (data.tlyric) {
1304
  const tLyrics = parseLyrics(data.tlyric);
1305
  mergeLyrics(currentLyrics, tLyrics);
1306
- displayLyrics(currentLyrics);
1307
  }
1308
- } else {
1309
- lyricsContainer.innerHTML = '<div class="lyric-line">暂无歌词</div>';
1310
  }
 
1311
  } catch (error) {
1312
  console.error('加载歌词失败:', error);
1313
  lyricsContainer.innerHTML = '<div class="lyric-line">加载歌词失败</div>';
1314
  }
1315
  }
1316
-
1317
- // 解析歌词
1318
  function parseLyrics(lyricText) {
1319
  const lines = lyricText.split('\n');
1320
  const lyrics = [];
1321
-
1322
- const timeRegex = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/g;
1323
-
1324
  for (const line of lines) {
1325
  if (!line.trim()) continue;
1326
-
1327
- const times = [];
1328
- let text = line;
1329
-
1330
- let match;
1331
- while ((match = timeRegex.exec(line)) !== null) {
1332
- const minutes = parseInt(match[1], 10);
1333
- const seconds = parseInt(match[2], 10);
1334
- const milliseconds = match[3].length === 3 ? parseInt(match[3], 10) : parseInt(match[3], 10) * 10;
1335
-
1336
- const timeInSeconds = minutes * 6e4 + seconds * 1e3 + milliseconds;
1337
- times.push(timeInSeconds);
1338
-
1339
- text = text.replace(match[0], '');
1340
- }
1341
-
1342
- if (times.length > 0 && text.trim()) {
1343
- for (const time of times) {
1344
- lyrics.push({
1345
- time: time / 1000,
1346
- text: text.trim()
1347
- });
1348
  }
1349
  }
1350
  }
1351
-
1352
- // 按时间排序
1353
  return lyrics.sort((a, b) => a.time - b.time);
1354
  }
1355
 
1356
- // 合并歌词和翻译
1357
  function mergeLyrics(originalLyrics, translatedLyrics) {
1358
- const timeMap = new Map();
1359
-
1360
- // 先将原歌词放入Map
1361
- for (const lyric of originalLyrics) {
1362
- timeMap.set(Math.round(lyric.time * 10) / 10, {
1363
- original: lyric.text,
1364
- translated: ''
1365
- });
1366
- }
1367
-
1368
- // 再合并翻译歌词
1369
- for (const lyric of translatedLyrics) {
1370
- const key = Math.round(lyric.time * 10) / 10;
1371
- if (timeMap.has(key)) {
1372
- timeMap.get(key).translated = lyric.text;
1373
  }
1374
- }
1375
-
1376
- // 清空原歌词数组
1377
- originalLyrics.length = 0;
1378
-
1379
- // 将合并后的歌词放回原数组
1380
- for (const [time, texts] of timeMap) {
1381
- originalLyrics.push({
1382
- time,
1383
- text: texts.translated ? `${texts.original} (${texts.translated})` : texts.original
1384
- });
1385
- }
1386
-
1387
- // 重新排序
1388
- originalLyrics.sort((a, b) => a.time - b.time);
1389
  }
1390
 
1391
- // 显示歌词
1392
  function displayLyrics(lyrics) {
1393
- lyricsContainer.innerHTML = '';
1394
-
1395
- if (lyrics.length === 0) {
1396
- lyricsContainer.innerHTML = '<div class="lyric-line">暂无歌词</div>';
1397
- return;
1398
- }
1399
-
1400
- for (const lyric of lyrics) {
1401
  const line = document.createElement('div');
1402
  line.className = 'lyric-line';
1403
- line.textContent = lyric.text;
1404
  line.setAttribute('data-time', lyric.time);
1405
-
1406
- line.addEventListener('click', () => {
1407
- audioPlayer.currentTime = lyric.time;
1408
- });
1409
-
1410
  lyricsContainer.appendChild(line);
1411
- }
1412
  }
1413
 
1414
- // 更新进度条和歌词高亮
1415
  function updateProgress() {
1416
  if (audioPlayer.duration) {
1417
- const percent = (audioPlayer.currentTime / audioPlayer.duration) * 100;
1418
- progressFill.style.width = `${percent}%`;
1419
-
1420
- // 更新时间显示
1421
  currentTimeSpan.textContent = formatTime(audioPlayer.currentTime);
1422
- totalTimeSpan.textContent = formatTime(audioPlayer.duration);
1423
-
1424
- // 更新歌词高亮
1425
  highlightCurrentLyric();
1426
  }
1427
  }
1428
 
1429
- // 格式化时间
1430
  function formatTime(seconds) {
1431
- const minutes = Math.floor(seconds / 60);
1432
- const remainingSeconds = Math.floor(seconds % 60);
1433
- return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
1434
  }
1435
 
1436
- // 高亮当前歌词并在容器内滚动
1437
  function highlightCurrentLyric() {
1438
  const currentTime = audioPlayer.currentTime;
1439
  const lyricLines = document.querySelectorAll('.lyric-line');
1440
- const lyricsContainer = document.getElementById('lyricsContainer');
1441
-
1442
- // 检查歌词容器是否可见
1443
- const isLyricsVisible = lyricsContainer.offsetParent !== null;
1444
- if (!isLyricsVisible) return;
1445
-
1446
- let currentIndex = -1;
1447
-
1448
- // 找到当前应该高亮的歌词行
1449
- for (let i = 0; i < lyricLines.length; i++) {
1450
- const lineTime = parseFloat(lyricLines[i].getAttribute('data-time'));
1451
- if (lineTime <= currentTime) {
1452
- currentIndex = i;
1453
- } else {
1454
  break;
1455
  }
1456
  }
1457
 
1458
- // 更新歌词高亮状态
1459
  lyricLines.forEach((line, index) => {
1460
- line.classList.toggle('active', index === currentIndex);
 
 
1461
  });
1462
 
1463
- // 只在有当前歌词且歌词容器可见时才滚动
1464
- if (currentIndex !== -1 && lyricLines[currentIndex]) {
1465
- // 计算歌词容器的中间位置
1466
- const containerHeight = lyricsContainer.clientHeight;
1467
- const lineHeight = lyricLines[currentIndex].offsetHeight;
1468
- const lineTop = lyricLines[currentIndex].offsetTop;
1469
-
1470
- // 计算滚动位置使当前歌词居中
1471
- const scrollPosition = lineTop - containerHeight / 2 + lineHeight / 2;
1472
-
1473
- // 在歌词容器内滚动,不影响整个页面
1474
- lyricsContainer.scrollTop = scrollPosition;
1475
  }
1476
  }
1477
-
1478
- // 显示通知
1479
  function showNotification(message, type = 'info') {
1480
- // 检查是否已有通知,如果有则移除
1481
- const existingNotification = document.querySelector('.custom-notification');
1482
- if (existingNotification) {
1483
- existingNotification.remove();
1484
- }
1485
-
1486
- // 创建通知元素
1487
  const notification = document.createElement('div');
1488
  notification.className = `custom-notification notification-${type}`;
1489
-
1490
- // 设置图标
1491
- let icon = 'info-circle';
1492
- if (type === 'success') icon = 'check-circle';
1493
- if (type === 'error') icon = 'exclamation-circle';
1494
- if (type === 'warning') icon = 'exclamation-triangle';
1495
-
1496
- notification.innerHTML = `
1497
- <i class="fas fa-${icon}"></i>
1498
- <span>${message}</span>
1499
- `;
1500
-
1501
- // 添加到页面
1502
  document.body.appendChild(notification);
1503
-
1504
- // 设置样式
1505
- notification.style.position = 'fixed';
1506
- notification.style.bottom = '20px';
1507
- notification.style.right = '20px';
1508
- notification.style.padding = '12px 20px';
1509
- notification.style.borderRadius = '8px';
1510
- notification.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
1511
- notification.style.display = 'flex';
1512
- notification.style.alignItems = 'center';
1513
- notification.style.gap = '10px';
1514
- notification.style.fontSize = '14px';
1515
- notification.style.zIndex = '9999';
1516
- notification.style.transition = 'all 0.3s ease';
1517
- notification.style.transform = 'translateY(100px)';
1518
- notification.style.opacity = '0';
1519
-
1520
- // 根据类型设置颜色
1521
- if (type === 'success') {
1522
- notification.style.backgroundColor = 'rgba(40, 167, 69, 0.9)';
1523
- notification.style.color = '#fff';
1524
- } else if (type === 'error') {
1525
- notification.style.backgroundColor = 'rgba(220, 53, 69, 0.9)';
1526
- notification.style.color = '#fff';
1527
- } else if (type === 'warning') {
1528
- notification.style.backgroundColor = 'rgba(255, 193, 7, 0.9)';
1529
- notification.style.color = '#212529';
1530
- } else {
1531
- notification.style.backgroundColor = 'rgba(74, 144, 226, 0.9)';
1532
- notification.style.color = '#fff';
1533
- }
1534
-
1535
- // 显示通知
1536
  setTimeout(() => {
1537
  notification.style.transform = 'translateY(0)';
1538
  notification.style.opacity = '1';
1539
- }, 70);
1540
-
1541
- // 3秒后移除通知
1542
  setTimeout(() => {
1543
  notification.style.transform = 'translateY(100px)';
1544
  notification.style.opacity = '0';
1545
-
1546
- setTimeout(() => {
1547
- notification.remove();
1548
- }, 300);
1549
  }, 3000);
1550
  }
1551
 
1552
- // 上一首
1553
  function previousSong() {
1554
  if (currentPlaylist.length === 0) return;
1555
-
1556
  currentIndex = (currentIndex - 1 + currentPlaylist.length) % currentPlaylist.length;
1557
  playSong(currentIndex);
1558
  }
1559
 
1560
- // 下一首
1561
  function nextSong() {
1562
  if (currentPlaylist.length === 0) return;
1563
-
1564
  currentIndex = (currentIndex + 1) % currentPlaylist.length;
1565
  playSong(currentIndex);
1566
  }
1567
 
1568
- // 音频播放结束事件
1569
- audioPlayer.addEventListener('ended', () => {
1570
- isPlaying = false;
1571
- updatePlayButton();
1572
- currentCover.classList.remove('playing');
1573
-
1574
- // 如果有下一首歌,自动播放下一首
1575
- if (currentIndex < currentPlaylist.length - 1) {
1576
- nextSong();
1577
- }
1578
- });
1579
-
1580
- // 音频元数据加载完成事件
1581
  audioPlayer.addEventListener('loadedmetadata', () => {
1582
  totalTimeSpan.textContent = formatTime(audioPlayer.duration);
1583
  });
1584
 
1585
- // 定期更新进度条
1586
- setInterval(updateProgress, 1000);
1587
-
1588
- // 初始化音量图标
1589
- setVolume(document.getElementById('volumeSlider').value);
1590
 
1591
- // 键盘快捷键
1592
- document.addEventListener('keydown', (e) => {
1593
- // 如果焦点在输入框中,则不触发快捷键
1594
- if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
1595
- return;
1596
- }
1597
-
1598
- switch (e.key) {
1599
- case ' ':
1600
- case 'Spacebar':
1601
- e.preventDefault();
1602
- togglePlay();
1603
- break;
1604
- case 'ArrowRight':
1605
- e.preventDefault();
1606
- audioPlayer.currentTime += 5;
1607
- break;
1608
- case 'ArrowLeft':
1609
- e.preventDefault();
1610
- audioPlayer.currentTime -= 5;
1611
- break;
1612
- case 'ArrowUp':
1613
- e.preventDefault();
1614
- const volumeUp = Math.min(parseInt(document.getElementById('volumeSlider').value) + 5, 100);
1615
- document.getElementById('volumeSlider').value = volumeUp;
1616
- setVolume(volumeUp);
1617
- break;
1618
- case 'ArrowDown':
1619
- e.preventDefault();
1620
- const volumeDown = Math.max(parseInt(document.getElementById('volumeSlider').value) - 5, 0);
1621
- document.getElementById('volumeSlider').value = volumeDown;
1622
- setVolume(volumeDown);
1623
- break;
1624
- case 'n':
1625
- case 'N':
1626
- e.preventDefault();
1627
- nextSong();
1628
- break;
1629
- case 'p':
1630
- case 'P':
1631
- e.preventDefault();
1632
- previousSong();
1633
- break;
1634
- }
1635
  });
1636
 
1637
- // 搜索框回车事件
1638
- document.getElementById('searchInput').addEventListener('keypress', (e) => {
1639
- if (e.key === 'Enter') {
1640
- searchMusic();
1641
- }
 
 
 
 
 
 
1642
  });
 
 
1643
  </script>
1644
  </body>
1645
  </html>
 
757
  </style>
758
  </head>
759
  <body>
 
760
  <nav class="navbar">
761
  <div class="nav-container">
762
  <div class="logo">
 
790
  </div>
791
  </nav>
792
 
 
793
  <div class="main-container">
794
  <div class="desktop-layout">
 
795
  <div class="view-container" id="searchView">
796
  <div class="content-panel">
797
  <h2 class="panel-title">
 
807
  </div>
808
  </div>
809
 
 
810
  <div class="view-container" id="playerView">
811
  <div class="content-panel player-panel">
812
  <div class="current-song">
 
841
  </div>
842
  </div>
843
 
 
844
  <div class="quality-container">
845
  <div class="quality-label">
846
  <i class="fas fa-music"></i>
 
860
  <input type="range" class="volume-slider" id="volumeSlider" min="0" max="100" value="80" onchange="setVolume(this.value)">
861
  </div>
862
 
 
863
  <div class="download-container">
864
  <button class="download-btn" onclick="downloadCurrentSong()" id="downloadSongBtn" disabled>
865
  <i class="fas fa-download"></i>
 
875
  </div>
876
  </div>
877
 
 
878
  <div class="view-container" id="lyricsView">
879
  <div class="content-panel">
880
  <h2 class="panel-title">
 
889
  </div>
890
  </div>
891
 
 
892
  <div class="mobile-nav">
893
  <div class="mobile-nav-items">
894
  <a href="#searchView" class="mobile-nav-item active" onclick="switchView('searchView', this)">
 
907
  </div>
908
 
909
  <script>
910
+ // Dr.Kun: 常量定义
911
  const API_BASE = 'https://music-api.gdstudio.xyz/api.php';
912
+ const PLAYER_STATE_KEY = 'cloudMusicPlayerState_v1'; // Dr.Kun: 定义统一的localStorage Key
913
+
914
+ // Dr.Kun: 全局状态变量
915
  let currentPlaylist = [];
916
  let currentIndex = -1;
917
  let currentLyrics = [];
918
  let isPlaying = false;
919
 
920
+ // Dr.Kun: DOM元素缓存
921
  const audioPlayer = document.getElementById('audioPlayer');
922
  const playBtn = document.getElementById('playBtn');
923
  const progressFill = document.getElementById('progressFill');
 
925
  const totalTimeSpan = document.getElementById('totalTime');
926
  const lyricsContainer = document.getElementById('lyricsContainer');
927
  const currentCover = document.getElementById('currentCover');
928
+ const qualitySelect = document.getElementById('qualitySelect');
929
+ const sourceSelect = document.getElementById('sourceSelect');
930
+ const volumeSlider = document.getElementById('volumeSlider');
931
+ const searchInput = document.getElementById('searchInput');
932
+
933
+ // Dr.Kun: 新增 - 保存播放器状态到localStorage
934
+ function savePlayerState() {
935
+ const state = {
936
+ playlist: currentPlaylist,
937
+ index: currentIndex,
938
+ volume: audioPlayer.volume,
939
+ quality: qualitySelect.value,
940
+ source: sourceSelect.value,
941
+ };
942
+ try {
943
+ localStorage.setItem(PLAYER_STATE_KEY, JSON.stringify(state));
944
+ } catch (e) {
945
+ console.error("保存状态失败:", e);
946
+ showNotification("无法保存播放状态,可能是存储已满", "error");
947
+ }
948
+ }
949
 
950
+ // Dr.Kun: 新增 - 从localStorage加载播放器状态
951
+ async function loadPlayerState() {
952
+ try {
953
+ const savedState = localStorage.getItem(PLAYER_STATE_KEY);
954
+ if (savedState) {
955
+ const state = JSON.parse(savedState);
956
+
957
+ // 恢复状态
958
+ currentPlaylist = state.playlist || [];
959
+ currentIndex = state.index || -1;
960
+
961
+ // 恢复UI
962
+ qualitySelect.value = state.quality || '320';
963
+ sourceSelect.value = state.source || 'netease';
964
+
965
+ // 恢复音量 (注意: setVolume接受0-100的值)
966
+ const volumeValue = (state.volume !== undefined ? state.volume : 0.8) * 100;
967
+ volumeSlider.value = volumeValue;
968
+ setVolume(volumeValue);
969
+
970
+ // 如果有播放列表,则渲染
971
+ if (currentPlaylist.length > 0) {
972
+ displaySearchResults(currentPlaylist);
973
+ }
974
+
975
+ // 如果有当前播放的歌曲,则更新播放器信息(但不自动播放)
976
+ if (currentIndex > -1 && currentPlaylist[currentIndex]) {
977
+ await updateCurrentSongInfo(currentPlaylist[currentIndex]);
978
+ updateActiveItem();
979
+ document.getElementById('downloadSongBtn').disabled = false;
980
+ document.getElementById('downloadLyricBtn').disabled = false;
981
+ }
982
+ }
983
+ } catch (e) {
984
+ console.error("加载状态失败, 可能数据已损坏:", e);
985
+ localStorage.removeItem(PLAYER_STATE_KEY); // Dr.Kun: 如果数据损坏,则清除,避免下次继续出错
986
+ }
987
+ }
988
+
989
+ // Dr.Kun: 页面加载时执行的初始化函数
990
+ async function initializeApp() {
991
+ // 视图初始化
992
+ if (window.innerWidth >= 1200) {
993
+ document.getElementById('searchView').classList.add('active');
994
+ document.getElementById('playerView').classList.add('active');
995
+ document.getElementById('lyricsView').classList.add('active');
996
+ } else {
997
+ switchView('searchView', document.querySelectorAll('.mobile-nav-item')[0]);
998
+ }
999
+
1000
+ // Dr.Kun: 关键步骤 - 加载上次保存的状态
1001
+ await loadPlayerState();
1002
+
1003
+ // Dr.Kun: 为需要保存状态的控件添加事件监听
1004
+ qualitySelect.onchange = savePlayerState;
1005
+ sourceSelect.onchange = savePlayerState;
1006
  }
1007
 
1008
  // 切换视图
1009
  function switchView(viewId, navItem) {
1010
+ document.querySelectorAll('.view-container').forEach(view => view.classList.remove('active'));
 
 
 
 
 
1011
  document.getElementById(viewId).classList.add('active');
 
 
1012
  if (navItem) {
1013
+ document.querySelectorAll('.mobile-nav-item').forEach(item => item.classList.remove('active'));
 
 
1014
  navItem.classList.add('active');
1015
  }
1016
  }
1017
 
1018
  // 搜索音乐
1019
  async function searchMusic() {
1020
+ const keyword = searchInput.value.trim();
1021
+ const source = sourceSelect.value;
 
1022
  if (!keyword) {
1023
  showNotification('请输入搜索关键词', 'warning');
1024
  return;
1025
  }
1026
 
1027
  const resultsContainer = document.getElementById('searchResults');
1028
+ resultsContainer.innerHTML = `<div class="loading"><i class="fas fa-spinner"></i><div>正在搜索音乐...</div></div>`;
 
 
 
 
 
1029
 
1030
  try {
1031
  const response = await fetch(`${API_BASE}?types=search&source=${source}&name=${encodeURIComponent(keyword)}&count=30`);
1032
  const data = await response.json();
 
1033
  if (data && data.length > 0) {
1034
  currentPlaylist = data;
1035
+ currentIndex = -1; // Dr.Kun: 新的搜索重置当前播放索引
1036
  displaySearchResults(data);
1037
+ savePlayerState(); // Dr.Kun: 搜索成功后保存播放列表状态
1038
  } else {
1039
+ resultsContainer.innerHTML = `<div class="error"><i class="fas fa-exclamation-triangle"></i><div>未找到相关歌曲</div></div>`;
 
 
 
 
 
1040
  }
1041
  } catch (error) {
1042
  console.error('搜索失败:', error);
1043
+ resultsContainer.innerHTML = `<div class="error"><i class="fas fa-wifi"></i><div>网络连接失败,请重试</div></div>`;
 
 
 
 
 
1044
  }
1045
  }
1046
 
 
1047
  async function getAlbumCoverUrl(song, size = 300) {
1048
+ if (!song || !song.pic_id) {
1049
+ return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwIiBoZWlnaHQ9IjIyMCIgdmlld0JveD0iMCAwIDIyMCAyMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMjAiIGhlaWdodD0iMjIwIiBmaWxsPSIjZmZmZmZmIi8+CjxjaXJjbGUgY3g9IjExMCIgY3k9IjExMCIgcj0iODAiIGZpbGw9IiNmOGY5ZmEiLz4KPHBhdGggZD0iTTEwNSAxNzVWODVIMTE1VjE3NVoiIGZpbGw9IiM0YTkwZTIiLz4KPC9zdmc+';
1050
  }
 
1051
  try {
1052
  const response = await fetch(`${API_BASE}?types=pic&source=${song.source}&id=${song.pic_id}&size=${size}`);
1053
  const data = await response.json();
1054
+ return (data && data.url) ? data.url : 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwIiBoZWlnaHQ9IjIyMCIgdmlld0JveD0iMCAwIDIyMCAyMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMjAiIGhlaWdodD0iMjIwIiBmaWxsPSIjZmZmZmZmIi8+CjxjaXJjbGUgY3g9IjExMCIgY3k9IjExMCIgcj0iODAiIGZpbGw9IiNmOGY5ZmEiLz4KPHBhdGggZD0iTTEwNSAxNzVWODVIMTE1VjE3NVoiIGZpbGw9IiM0YTkwZTIiLz4KPC9zdmc+';
 
 
 
1055
  } catch (error) {
1056
  console.error('获取专辑图失败:', error);
1057
+ return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwIiBoZWlnaHQ9IjIyMCIgdmlld0JveD0iMCAwIDIyMCAyMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMjAiIGhlaWdodD0iMjIwIiBmaWxsPSIjZmZmZmZmIi8+CjxjaXJjbGUgY3g9IjExMCIgY3k9IjExMCIgcj0iODAiIGZpbGw9IiNmOGY5ZmEiLz4KPHBhdGggZD0iTTEwNSAxNzVWODVIMTE1VjE3NVoiIGZpbGw9IiM0YTkwZTIiLz4KPC9zdmc+';
1058
  }
 
 
1059
  }
1060
 
1061
+ function displaySearchResults(songs) {
 
1062
  const resultsContainer = document.getElementById('searchResults');
1063
  resultsContainer.innerHTML = '';
1064
+ songs.forEach((song, index) => {
 
 
1065
  const songCard = document.createElement('div');
1066
  songCard.className = 'song-card';
1067
+ songCard.onclick = () => playSong(index);
 
 
1068
  const artistText = Array.isArray(song.artist) ? song.artist.join(' / ') : song.artist;
 
1069
  songCard.innerHTML = `
1070
  <div class="song-index">${(index + 1).toString().padStart(2, '0')}</div>
1071
  <div class="song-info">
 
1073
  <div class="song-artist">${artistText} ${song.album ? '· ' + song.album : ''}</div>
1074
  </div>
1075
  <div class="song-actions">
1076
+ <button class="action-btn" onclick="event.stopPropagation(); downloadSong(${index})" title="下载音乐"><i class="fas fa-download"></i></button>
1077
+ <button class="action-btn" onclick="event.stopPropagation(); downloadLyric(${index})" title="下载歌词"><i class="fas fa-file-text"></i></button>
1078
+ </div>`;
 
 
 
 
 
 
1079
  resultsContainer.appendChild(songCard);
1080
+ });
1081
  }
1082
 
 
1083
  async function playSong(index) {
1084
  if (index < 0 || index >= currentPlaylist.length) return;
 
1085
  currentIndex = index;
1086
  const song = currentPlaylist[index];
 
 
1087
  await updateCurrentSongInfo(song);
1088
  updateActiveItem();
1089
+ savePlayerState(); // Dr.Kun: 切换歌曲时保存状态
1090
 
1091
  try {
1092
  showNotification('正在加载音乐...', 'info');
1093
+ const quality = qualitySelect.value;
 
 
 
 
1094
  const urlResponse = await fetch(`${API_BASE}?types=url&source=${song.source}&id=${song.id}&br=${quality}`);
1095
  const urlData = await urlResponse.json();
1096
 
1097
  if (urlData && urlData.url) {
1098
  audioPlayer.src = urlData.url;
1099
  audioPlayer.load();
 
 
1100
  loadLyrics(song);
 
 
1101
  document.getElementById('downloadSongBtn').disabled = false;
1102
  document.getElementById('downloadLyricBtn').disabled = false;
1103
 
 
1104
  const playPromise = audioPlayer.play();
1105
  if (playPromise !== undefined) {
1106
  playPromise.then(() => {
1107
  isPlaying = true;
1108
  updatePlayButton();
1109
  currentCover.classList.add('playing');
1110
+ showNotification(`开始播放: ${song.name}`, 'success');
1111
  }).catch(error => {
1112
  console.error('播放失败:', error);
1113
+ showNotification('自动播放失败,请手动点击播放', 'warning');
1114
  });
1115
  }
1116
  } else {
 
1122
  }
1123
  }
1124
 
 
1125
  function getQualityText(br) {
1126
+ const qualityMap = { '128': '标准', '192': '较高', '320': '高品质', '740': '无损FLAC', '999': 'Hi-Res' };
 
 
 
 
 
 
1127
  return qualityMap[br] || `${br}K`;
1128
  }
1129
+
 
1130
  async function downloadSong(index) {
1131
  const song = currentPlaylist[index];
1132
+ const quality = qualitySelect.value;
 
1133
  try {
1134
  showNotification('正在获取下载链接...', 'info');
 
1135
  const response = await fetch(`${API_BASE}?types=url&source=${song.source}&id=${song.id}&br=${quality}`);
1136
  const data = await response.json();
 
1137
  if (data && data.url) {
 
1138
  const link = document.createElement('a');
1139
  link.href = data.url;
1140
  link.download = `${song.name} - ${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}.mp3`;
1141
  link.target = '_blank';
 
 
1142
  document.body.appendChild(link);
1143
  link.click();
1144
  document.body.removeChild(link);
 
1145
  showNotification('开始下载音乐文件', 'success');
1146
  } else {
1147
  showNotification('无法获取下载链接', 'error');
 
1152
  }
1153
  }
1154
 
 
1155
  async function downloadLyric(index) {
1156
  const song = currentPlaylist[index];
 
1157
  try {
1158
  showNotification('正在获取歌词...', 'info');
 
1159
  const response = await fetch(`${API_BASE}?types=lyric&source=${song.source}&id=${song.lyric_id || song.id}`);
1160
  const data = await response.json();
 
1161
  if (data && data.lyric) {
1162
+ 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}`;
1163
+ if (data.tlyric) lyricContent += `\r\n\r\n=== 翻译 ===\r\n${data.tlyric}`;
 
 
 
 
 
 
 
 
 
 
 
1164
  const blob = new Blob([lyricContent], { type: 'text/plain;charset=utf-8' });
1165
  const url = URL.createObjectURL(blob);
 
1166
  const link = document.createElement('a');
1167
  link.href = url;
1168
  link.download = `${song.name} - ${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}.lrc`;
 
1169
  document.body.appendChild(link);
1170
  link.click();
1171
  document.body.removeChild(link);
 
1172
  URL.revokeObjectURL(url);
1173
  showNotification('歌词下载完成', 'success');
1174
  } else {
 
1180
  }
1181
  }
1182
 
 
1183
  async function downloadCurrentSong() {
1184
+ if (currentIndex === -1) { showNotification('请先选择歌曲', 'warning'); return; }
 
 
 
 
1185
  await downloadSong(currentIndex);
1186
  }
1187
 
 
1188
  async function downloadCurrentLyric() {
1189
+ if (currentIndex === -1) { showNotification('请先选择歌曲', 'warning'); return; }
 
 
 
 
1190
  await downloadLyric(currentIndex);
1191
  }
1192
 
 
1193
  async function updateCurrentSongInfo(song) {
1194
  document.getElementById('currentTitle').textContent = song.name;
 
1195
  const artistText = Array.isArray(song.artist) ? song.artist.join(' / ') : song.artist;
1196
+ document.getElementById('currentArtist').textContent = `${artistText}${song.album ? ' · ' + song.album : ''}`;
1197
+ currentCover.src = await getAlbumCoverUrl(song, 500);
 
 
 
 
1198
  }
1199
 
 
1200
  function updateActiveItem() {
1201
  document.querySelectorAll('.song-card').forEach((item, index) => {
1202
  item.classList.toggle('active', index === currentIndex);
1203
  });
1204
  }
1205
 
 
1206
  function updatePlayButton() {
1207
+ playBtn.querySelector('i').className = isPlaying ? 'fas fa-pause' : 'fas fa-play';
 
 
 
 
 
1208
  }
1209
 
 
1210
  function togglePlay() {
1211
+ if (!audioPlayer.src) {
1212
+ showNotification('请先选择一首歌曲', 'warning');
1213
+ return;
1214
+ }
1215
+ if (isPlaying) {
1216
+ audioPlayer.pause();
1217
+ currentCover.classList.remove('playing');
 
 
 
1218
  } else {
1219
+ audioPlayer.play().catch(e => console.error("播放命令被拒绝", e));
1220
+ currentCover.classList.add('playing');
1221
  }
1222
+ isPlaying = !isPlaying;
1223
+ updatePlayButton();
1224
  }
1225
 
 
1226
  function setVolume(value) {
1227
  audioPlayer.volume = value / 100;
1228
  const volumeIcon = document.querySelector('.volume-icon');
1229
+ if (value == 0) volumeIcon.className = 'fas fa-volume-mute volume-icon';
1230
+ else if (value < 50) volumeIcon.className = 'fas fa-volume-down volume-icon';
1231
+ else volumeIcon.className = 'fas fa-volume-up volume-icon';
1232
+ savePlayerState(); // Dr.Kun: 调整音量后保存状态
 
 
 
 
1233
  }
1234
 
 
1235
  function seekTo(event) {
1236
+ if(!audioPlayer.duration) return;
1237
  const progressBar = event.currentTarget;
1238
  const rect = progressBar.getBoundingClientRect();
1239
+ audioPlayer.currentTime = ((event.clientX - rect.left) / rect.width) * audioPlayer.duration;
 
1240
  }
1241
 
 
1242
  async function loadLyrics(song) {
1243
  try {
1244
  const response = await fetch(`${API_BASE}?types=lyric&source=${song.source}&id=${song.lyric_id || song.id}`);
1245
  const data = await response.json();
1246
+ currentLyrics = []; // 清空旧歌词
1247
  if (data && data.lyric) {
1248
  currentLyrics = parseLyrics(data.lyric);
 
 
 
1249
  if (data.tlyric) {
1250
  const tLyrics = parseLyrics(data.tlyric);
1251
  mergeLyrics(currentLyrics, tLyrics);
 
1252
  }
 
 
1253
  }
1254
+ displayLyrics(currentLyrics);
1255
  } catch (error) {
1256
  console.error('加载歌词失败:', error);
1257
  lyricsContainer.innerHTML = '<div class="lyric-line">加载歌词失败</div>';
1258
  }
1259
  }
1260
+
 
1261
  function parseLyrics(lyricText) {
1262
  const lines = lyricText.split('\n');
1263
  const lyrics = [];
1264
+ const timeRegex = /\[(\d{2}):(\d{2})[.:](\d{2,3})\]/g;
 
 
1265
  for (const line of lines) {
1266
  if (!line.trim()) continue;
1267
+ let text = line.replace(timeRegex, '').trim();
1268
+ if (text) {
1269
+ let match;
1270
+ timeRegex.lastIndex = 0; // 重置正则索引
1271
+ while ((match = timeRegex.exec(line)) !== null) {
1272
+ const timeInSeconds = parseInt(match[1]) * 60 + parseInt(match[2]) + parseInt(match[3]) / (match[3].length === 3 ? 1000 : 100);
1273
+ lyrics.push({ time: timeInSeconds, text: text });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1274
  }
1275
  }
1276
  }
 
 
1277
  return lyrics.sort((a, b) => a.time - b.time);
1278
  }
1279
 
 
1280
  function mergeLyrics(originalLyrics, translatedLyrics) {
1281
+ const translatedMap = new Map(translatedLyrics.map(l => [Math.round(l.time * 10), l.text]));
1282
+ originalLyrics.forEach(l => {
1283
+ const key = Math.round(l.time * 10);
1284
+ const translatedText = translatedMap.get(key);
1285
+ if (translatedText) {
1286
+ l.text += `<br><span style="color: #adb5bd; font-size: 0.9em;">${translatedText}</span>`;
 
 
 
 
 
 
 
 
 
1287
  }
1288
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1289
  }
1290
 
 
1291
  function displayLyrics(lyrics) {
1292
+ lyricsContainer.innerHTML = lyrics.length === 0 ? '<div class="lyric-line">暂无歌词</div>' : '';
1293
+ lyrics.forEach(lyric => {
 
 
 
 
 
 
1294
  const line = document.createElement('div');
1295
  line.className = 'lyric-line';
1296
+ line.innerHTML = lyric.text; // 使用innerHTML以渲染br标签
1297
  line.setAttribute('data-time', lyric.time);
1298
+ line.onclick = () => { if(audioPlayer.duration) audioPlayer.currentTime = lyric.time; };
 
 
 
 
1299
  lyricsContainer.appendChild(line);
1300
+ });
1301
  }
1302
 
 
1303
  function updateProgress() {
1304
  if (audioPlayer.duration) {
1305
+ progressFill.style.width = `${(audioPlayer.currentTime / audioPlayer.duration) * 100}%`;
 
 
 
1306
  currentTimeSpan.textContent = formatTime(audioPlayer.currentTime);
 
 
 
1307
  highlightCurrentLyric();
1308
  }
1309
  }
1310
 
 
1311
  function formatTime(seconds) {
1312
+ const min = Math.floor(seconds / 60);
1313
+ const sec = Math.floor(seconds % 60);
1314
+ return `${min}:${sec.toString().padStart(2, '0')}`;
1315
  }
1316
 
 
1317
  function highlightCurrentLyric() {
1318
  const currentTime = audioPlayer.currentTime;
1319
  const lyricLines = document.querySelectorAll('.lyric-line');
1320
+ if (lyricLines.length === 0 || lyricsContainer.offsetParent === null) return;
1321
+
1322
+ let activeIndex = -1;
1323
+ for (let i = lyricLines.length - 1; i >= 0; i--) {
1324
+ if (parseFloat(lyricLines[i].getAttribute('data-time')) <= currentTime + 0.2) { // 增加0.2秒的容差
1325
+ activeIndex = i;
 
 
 
 
 
 
 
 
1326
  break;
1327
  }
1328
  }
1329
 
 
1330
  lyricLines.forEach((line, index) => {
1331
+ if (line.classList.contains('active') !== (index === activeIndex)) {
1332
+ line.classList.toggle('active', index === activeIndex);
1333
+ }
1334
  });
1335
 
1336
+ if (activeIndex > -1) {
1337
+ const activeLine = lyricLines[activeIndex];
1338
+ const scrollPosition = activeLine.offsetTop - lyricsContainer.clientHeight / 2 + activeLine.clientHeight / 2;
1339
+ lyricsContainer.scrollTo({ top: scrollPosition, behavior: 'smooth' });
 
 
 
 
 
 
 
 
1340
  }
1341
  }
1342
+
 
1343
  function showNotification(message, type = 'info') {
1344
+ const existing = document.querySelector('.custom-notification');
1345
+ if (existing) existing.remove();
 
 
 
 
 
1346
  const notification = document.createElement('div');
1347
  notification.className = `custom-notification notification-${type}`;
1348
+ const icons = { info: 'info-circle', success: 'check-circle', error: 'exclamation-circle', warning: 'exclamation-triangle' };
1349
+ notification.innerHTML = `<i class="fas fa-${icons[type]}"></i><span>${message}</span>`;
 
 
 
 
 
 
 
 
 
 
 
1350
  document.body.appendChild(notification);
1351
+ Object.assign(notification.style, {
1352
+ position: 'fixed', bottom: '20px', right: '20px', padding: '12px 20px', borderRadius: '8px',
1353
+ boxShadow: '0 4px 12px rgba(0,0,0,0.15)', display: 'flex', alignItems: 'center', gap: '10px',
1354
+ fontSize: '14px', zIndex: '9999', transition: 'all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)', transform: 'translateY(100px)', opacity: '0'
1355
+ });
1356
+ const colors = {
1357
+ success: { bg: 'rgba(40, 167, 69, 0.9)', color: '#fff' },
1358
+ error: { bg: 'rgba(220, 53, 69, 0.9)', color: '#fff' },
1359
+ warning: { bg: 'rgba(255, 193, 7, 0.9)', color: '#212529' },
1360
+ info: { bg: 'rgba(74, 144, 226, 0.9)', color: '#fff' }
1361
+ };
1362
+ notification.style.backgroundColor = colors[type].bg;
1363
+ notification.style.color = colors[type].color;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1364
  setTimeout(() => {
1365
  notification.style.transform = 'translateY(0)';
1366
  notification.style.opacity = '1';
1367
+ }, 50);
 
 
1368
  setTimeout(() => {
1369
  notification.style.transform = 'translateY(100px)';
1370
  notification.style.opacity = '0';
1371
+ setTimeout(() => notification.remove(), 400);
 
 
 
1372
  }, 3000);
1373
  }
1374
 
 
1375
  function previousSong() {
1376
  if (currentPlaylist.length === 0) return;
 
1377
  currentIndex = (currentIndex - 1 + currentPlaylist.length) % currentPlaylist.length;
1378
  playSong(currentIndex);
1379
  }
1380
 
 
1381
  function nextSong() {
1382
  if (currentPlaylist.length === 0) return;
 
1383
  currentIndex = (currentIndex + 1) % currentPlaylist.length;
1384
  playSong(currentIndex);
1385
  }
1386
 
1387
+ audioPlayer.addEventListener('play', () => { isPlaying = true; updatePlayButton(); currentCover.classList.add('playing'); });
1388
+ audioPlayer.addEventListener('pause', () => { isPlaying = false; updatePlayButton(); currentCover.classList.remove('playing'); });
1389
+ audioPlayer.addEventListener('ended', nextSong);
 
 
 
 
 
 
 
 
 
 
1390
  audioPlayer.addEventListener('loadedmetadata', () => {
1391
  totalTimeSpan.textContent = formatTime(audioPlayer.duration);
1392
  });
1393
 
1394
+ setInterval(updateProgress, 500);
 
 
 
 
1395
 
1396
+ searchInput.addEventListener('keypress', (e) => {
1397
+ if (e.key === 'Enter') searchMusic();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1398
  });
1399
 
1400
+ document.addEventListener('keydown', (e) => {
1401
+ if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) return;
1402
+ const keyMap = {
1403
+ ' ': togglePlay,
1404
+ 'ArrowRight': () => { if(audioPlayer.duration) audioPlayer.currentTime = Math.min(audioPlayer.currentTime + 5, audioPlayer.duration); },
1405
+ 'ArrowLeft': () => { if(audioPlayer.duration) audioPlayer.currentTime = Math.max(audioPlayer.currentTime - 5, 0); },
1406
+ 'ArrowUp': () => { const v = Math.min(parseInt(volumeSlider.value) + 5, 100); volumeSlider.value = v; setVolume(v); },
1407
+ 'ArrowDown': () => { const v = Math.max(parseInt(volumeSlider.value) - 5, 0); volumeSlider.value = v; setVolume(v); },
1408
+ 'n': nextSong, 'N': nextSong, 'p': previousSong, 'P': previousSong
1409
+ };
1410
+ if (keyMap[e.key]) { e.preventDefault(); keyMap[e.key](); }
1411
  });
1412
+
1413
+ initializeApp();
1414
  </script>
1415
  </body>
1416
  </html>