xukunCai
commited on
Update index.html
Browse files- 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 |
-
//
|
| 933 |
-
|
| 934 |
-
|
| 935 |
-
|
| 936 |
-
|
| 937 |
-
|
| 938 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 963 |
-
const source =
|
| 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,
|
| 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 = () =>
|
| 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 |
-
|
| 1047 |
-
|
| 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(
|
| 1098 |
}).catch(error => {
|
| 1099 |
console.error('播放失败:', error);
|
| 1100 |
-
showNotification('
|
| 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 =
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 1257 |
-
|
| 1258 |
-
|
| 1259 |
-
|
| 1260 |
-
|
| 1261 |
-
|
| 1262 |
-
}
|
| 1263 |
-
isPlaying = !isPlaying;
|
| 1264 |
-
updatePlayButton();
|
| 1265 |
} else {
|
| 1266 |
-
|
|
|
|
| 1267 |
}
|
|
|
|
|
|
|
| 1268 |
}
|
| 1269 |
|
| 1270 |
-
// 设置音量
|
| 1271 |
function setVolume(value) {
|
| 1272 |
audioPlayer.volume = value / 100;
|
| 1273 |
const volumeIcon = document.querySelector('.volume-icon');
|
| 1274 |
-
|
| 1275 |
-
if (value
|
| 1276 |
-
|
| 1277 |
-
|
| 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 |
-
|
| 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 |
-
|
| 1328 |
-
|
| 1329 |
-
|
| 1330 |
-
|
| 1331 |
-
|
| 1332 |
-
|
| 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
|
| 1359 |
-
|
| 1360 |
-
|
| 1361 |
-
|
| 1362 |
-
|
| 1363 |
-
|
| 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.
|
| 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 |
-
|
| 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
|
| 1432 |
-
const
|
| 1433 |
-
return `${
|
| 1434 |
}
|
| 1435 |
|
| 1436 |
-
// 高亮当前歌词并在容器内滚动
|
| 1437 |
function highlightCurrentLyric() {
|
| 1438 |
const currentTime = audioPlayer.currentTime;
|
| 1439 |
const lyricLines = document.querySelectorAll('.lyric-line');
|
| 1440 |
-
|
| 1441 |
-
|
| 1442 |
-
|
| 1443 |
-
|
| 1444 |
-
|
| 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.
|
|
|
|
|
|
|
| 1461 |
});
|
| 1462 |
|
| 1463 |
-
|
| 1464 |
-
|
| 1465 |
-
|
| 1466 |
-
|
| 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 |
-
|
| 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 |
-
|
| 1506 |
-
|
| 1507 |
-
|
| 1508 |
-
|
| 1509 |
-
|
| 1510 |
-
|
| 1511 |
-
|
| 1512 |
-
|
| 1513 |
-
|
| 1514 |
-
notification.style.
|
| 1515 |
-
notification.style.
|
| 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 |
-
},
|
| 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('
|
| 1570 |
-
|
| 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 |
-
|
| 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 |
-
|
| 1639 |
-
|
| 1640 |
-
|
| 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>
|