这次升级得相对比较完美了,可以本地添加歌曲还有网易云api拉取歌曲及歌词功能,自己用完全够了,就是要自己搭建网易云音乐api, 将代码中的****号替换为自己搭建的api地址就行了。 api搭建项目:https://gitlab.com/Binaryify/NeteaseCloudMusicApi 我是用vercel搭建的api(原谅无法公开分享),有流量限制100G每个月,自己用是完全够了,给个代码给喜欢折腾的佬友。目前不爽的是网易云好多歌曲没有版权无法播放,拉取下来不能播放的会自动跳过,只播放有版本的,希望哪位佬能指点一下,有个UnblockNeteaseMusic项目来解决这个问题,但目前我是没折腾出来方法。有佬友能解决的可以发个教程。
使用方法:将代码保存在 记事本 文件里面,把后缀改为html就可以了。兄弟们点个赞啊
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>音乐播放器 (桌面/移动端优化)</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
<style>
:root {
--font-main: 'Noto Sans SC', 'Segoe UI', Arial, sans-serif;
--bg-gradient: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
--container-bg: rgba(255, 255, 255, 0.6);
--container-shadow: rgba(0, 0, 0, 0.1);
--backdrop-blur: 12px;
--text-color: #2c3e50;
--text-secondary-color: #7f8c8d;
--primary-color: #3498db;
--primary-color-dark: #2980b9;
--warning-color: #e74c3c;
--item-hover-bg: rgba(52, 152, 219, 0.1);
--item-current-bg: rgba(52, 152, 219, 0.2);
--item-current-text: var(--primary-color);
--border-color: rgba(0, 0, 0, 0.1);
--lyrics-highlight-bg: rgba(46, 204, 113, 0.15);
--lyrics-highlight-text: #27ae60;
--lyrics-highlight-shadow: rgba(46, 204, 113, 0.2);
--component-bg: rgba(255, 255, 255, 0.5);
--scrollbar-thumb-bg: rgba(0, 0, 0, 0.2);
--scrollbar-track-bg: transparent;
}
.dark-mode {
--bg-gradient: linear-gradient(135deg, #0f2027 0%, #203a43 50%, #2c5364 100%);
--container-bg: rgba(30, 30, 30, 0.6);
--container-shadow: rgba(0, 0, 0, 0.3);
--text-color: #ecf0f1;
--text-secondary-color: #95a5a6;
--primary-color: #5dade2;
--primary-color-dark: #3498db;
--item-hover-bg: rgba(93, 173, 226, 0.1);
--item-current-bg: rgba(93, 173, 226, 0.2);
--border-color: rgba(255, 255, 255, 0.15);
--lyrics-highlight-bg: rgba(26, 188, 156, 0.15);
--lyrics-highlight-text: #1abc9c;
--component-bg: rgba(44, 44, 44, 0.5);
--scrollbar-thumb-bg: rgba(255, 255, 255, 0.2);
}
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: var(--scrollbar-track-bg); }
::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb-bg); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--primary-color); }
body {
font-family: var(--font-main);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
box-sizing: border-box;
background-image: var(--bg-gradient);
background-attachment: fixed;
color: var(--text-color);
transition: background 0.5s, color 0.5s;
}
.container {
background: var(--container-bg);
backdrop-filter: blur(var(--backdrop-blur));
-webkit-backdrop-filter: blur(var(--backdrop-blur));
padding: 35px;
border-radius: 24px;
box-shadow: 0 15px 30px var(--container-shadow);
border: 1px solid var(--border-color);
width: 100%;
max-width: 850px;
display: grid;
/* 桌面端布局 */
grid-template-rows: auto auto 1fr auto;
gap: 20px;
transition: background 0.5s, box-shadow 0.5s;
height: 90vh;
max-height: 700px;
}
.header { text-align: center; position: relative; }
.header h1 { margin: 0; font-size: 2.2em; font-weight: 700; color: var(--primary-color); letter-spacing: 1px; }
.header .warning { color: var(--text-secondary-color); font-size: 0.9em; margin-top: 10px; font-style: italic; }
.file-input-area {
background: var(--component-bg);
border: 2px dashed var(--border-color);
border-radius: 16px;
padding: 20px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.file-input-area:hover, .file-input-area.dragover { border-color: var(--primary-color); background: var(--item-hover-bg); }
.file-input-area .file-input-label { display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--text-secondary-color); }
.file-input-label i { font-size: 2em; margin-bottom: 10px; color: var(--primary-color); }
.view-toggle { display: none; }
.main-content { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; overflow: hidden; }
.playlist, .lyrics { background: var(--component-bg); border-radius: 16px; padding: 20px; border: 1px solid var(--border-color); height: 100%; overflow-y: auto; }
.playlist div { padding: 12px 15px; border-radius: 10px; transition: all 0.2s ease-in-out; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.playlist div:hover { background-color: var(--item-hover-bg); color: var(--item-current-text); transform: translateX(5px); }
.playlist .current { color: var(--item-current-text); font-weight: 500; background-color: var(--item-current-bg); }
.lyrics { text-align: center; font-size: 1.1em; line-height: 2.2; }
.lyrics div { white-space: pre-wrap; }
.lyrics .current { color: var(--lyrics-highlight-text); font-weight: 700; background-color: var(--lyrics-highlight-bg); transform: scale(1.05); box-shadow: 0 4px 15px var(--lyrics-highlight-shadow); }
.controls { display: flex; justify-content: center; align-items: center; gap: 20px; flex-wrap: wrap; }
.controls button { background: var(--primary-color); color: white; border: none; border-radius: 50%; cursor: pointer; font-size: 1.2em; width: 50px; height: 50px; display: flex; justify-content: center; align-items: center; transition: all 0.2s ease; box-shadow: 0 4px 10px rgba(0,0,0,0.1); }
.controls button:hover { background: var(--primary-color-dark); transform: translateY(-2px); box-shadow: 0 6px 15px rgba(0,0,0,0.15); }
.controls button:active { transform: translateY(0); box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
.controls button#loadOnlineBtn { width: auto; border-radius: 25px; padding: 0 25px; gap: 10px; }
.controls button:disabled { background: var(--text-secondary-color); cursor: not-allowed; transform: none; box-shadow: none; }
.controls audio { flex-grow: 1; min-width: 300px; }
.loader { width: 20px; height: 20px; border: 3px solid var(--primary-color); border-bottom-color: transparent; border-radius: 50%; display: inline-block; box-sizing: border-box; animation: rotation 1s linear infinite; }
@keyframes rotation { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
/* [优化] 更小、更精致的主题切换按钮 */
.theme-switch-wrapper { position: absolute; top: 0px; right: 0px; }
.theme-switch { display: inline-block; height: 24px; position: relative; width: 48px; }
.theme-switch input { display:none; }
.slider { background-color: #ccc; bottom: 0; cursor: pointer; left: 0; position: absolute; right: 0; top: 0; transition: .4s; border-radius: 24px; }
.slider:before { background-color: #fff; bottom: 3px; content: ""; height: 18px; left: 3px; position: absolute; transition: .4s; width: 18px; border-radius: 50%; }
.slider .fa-sun, .slider .fa-moon { position: absolute; top: 50%; transform: translateY(-50%); color: #fff; font-size: 12px; opacity: 0; transition: opacity 0.4s; }
.slider .fa-sun { left: 6px; opacity: 1; }
.slider .fa-moon { right: 6px; }
input:checked + .slider { background-color: var(--primary-color); }
input:checked + .slider:before { transform: translateX(24px); }
input:checked + .slider .fa-sun { opacity: 0; }
input:checked + .slider .fa-moon { opacity: 1; }
/* --- [手机版优化] 媒体查询 --- */
@media (max-width: 768px) {
body { padding: 0; }
.container {
padding: 15px;
gap: 15px;
grid-template-rows: auto auto auto 1fr auto;
border-radius: 0;
border: none;
height: 100vh;
max-height: none;
}
.header h1 { font-size: 1.8em; }
.file-input-area { padding: 15px; }
.file-input-label i { font-size: 1.8em; }
.view-toggle {
display: flex;
gap: 10px;
}
.view-toggle button {
flex-grow: 1;
background: var(--component-bg);
color: var(--text-secondary-color);
border: 1px solid var(--border-color);
padding: 10px;
border-radius: 10px;
font-size: 0.9em;
font-family: inherit;
cursor: pointer;
transition: all 0.3s ease;
}
.view-toggle button.active {
background: var(--primary-color);
color: white;
font-weight: 500;
border-color: var(--primary-color);
}
/* [修复-1] 修复列表撑开布局问题:确保主内容区高度受控,内部元素滚动 */
.main-content {
display: flex; /* 改为flex以约束子元素 */
grid-template-columns: none; /* 取消桌面端的分栏 */
}
.playlist, .lyrics {
width: 100%;
flex-shrink: 0; /* 防止被压缩 */
}
/* [修复-2] 移动端默认隐藏歌词 */
.mobile-hidden {
display: none !important;
}
/* [优化-3] 优化手机歌词行高,显示更多内容 */
.lyrics {
line-height: 1.8;
font-size: 1em;
}
/* [优化-5] 重新设计移动端控制器布局 */
.controls {
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.controls audio {
order: -1; /* 进度条置顶 */
width: 100%;
min-width: unset;
}
.controls button {
font-size: 1.1em;
width: 48px;
height: 48px;
}
.controls button#loadOnlineBtn {
flex-grow: 1;
width: auto;
max-width: 180px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="theme-switch-wrapper">
<label class="theme-switch" for="checkbox"><input type="checkbox" id="checkbox" /><div class="slider"><i class="fas fa-sun"></i><i class="fas fa-moon"></i></div></label>
</div>
<h1>音乐播放器</h1>
<div class="warning">支持本地音乐与在线雷达探索</div>
</div>
<div class="file-input-area" id="fileDropArea">
<input type="file" id="fileInput" multiple accept="audio/*,.lrc,.txt" style="display: none;">
<label for="fileInput" class="file-input-label"><i class="fas fa-folder-plus"></i><span>点击选择 或 拖放本地音乐文件至此</span></label>
</div>
<div class="view-toggle">
<button id="showPlaylistBtn" class="active">播放列表</button>
<button id="showLyricsBtn">歌词</button>
</div>
<div class="main-content">
<div class="playlist" id="playlist">请选择本地音乐或探索在线雷达</div>
<div class="lyrics mobile-hidden" id="lyrics">歌词将在此处同步显示</div>
</div>
<div class="controls">
<button onclick="playPrevious()" title="上一曲"><i class="fas fa-backward-step"></i></button>
<audio id="audioPlayer" controls></audio>
<button onclick="playNext()" title="下一曲"><i class="fas fa-forward-step"></i></button>
<button id="loadOnlineBtn" title="聚合所有雷达,探索新音乐">
<span class="btn-text"><i class="fas fa-satellite-dish"></i> 探索雷达</span>
<span class="loader" style="display: none;"></span>
</button>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsmediatags/3.9.7/jsmediatags.min.js"></script>
<script>
const dom = {
playlist: document.getElementById('playlist'),
lyrics: document.getElementById('lyrics'),
audioPlayer: document.getElementById('audioPlayer'),
themeToggle: document.getElementById('checkbox'),
loadOnlineBtn: document.getElementById('loadOnlineBtn'),
fileInput: document.getElementById('fileInput'),
fileDropArea: document.getElementById('fileDropArea'),
showPlaylistBtn: document.getElementById('showPlaylistBtn'),
showLyricsBtn: document.getElementById('showLyricsBtn'),
};
const state = {
isOnlineMode: false,
audioFiles: [],
lyricsFiles: {},
onlineSongs: [],
currentTrackIndex: -1,
currentAudioUrl: null,
lyricsData: [],
currentLyricLine: -1,
};
const API_BASE_URL = 'https://*****.****.xyz';
const RADAR_IDS = ['3136952023', '5300458264', '5320167908', '5362359247'];
window.addEventListener('load', setupInteractions);
dom.audioPlayer.addEventListener('ended', autoPlayNext);
dom.audioPlayer.addEventListener('timeupdate', syncLyrics);
function setupInteractions() {
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.body.classList.toggle('dark-mode', savedTheme === 'dark');
dom.themeToggle.checked = savedTheme === 'dark';
}
dom.themeToggle.addEventListener('change', (e) => {
const isDark = e.target.checked;
document.body.classList.toggle('dark-mode', isDark);
localStorage.setItem('theme', isDark ? 'dark' : 'light');
});
dom.loadOnlineBtn.addEventListener('click', handleOnlineMode);
dom.fileInput.addEventListener('change', (e) => processFiles(e.target.files));
dom.fileDropArea.addEventListener('dragover', (e) => { e.preventDefault(); e.currentTarget.classList.add('dragover'); });
dom.fileDropArea.addEventListener('dragleave', (e) => e.currentTarget.classList.remove('dragover'));
dom.fileDropArea.addEventListener('drop', (e) => {
e.preventDefault();
e.currentTarget.classList.remove('dragover');
processFiles(e.dataTransfer.files);
});
dom.showPlaylistBtn.addEventListener('click', () => switchMobileView('playlist'));
dom.showLyricsBtn.addEventListener('click', () => switchMobileView('lyrics'));
}
function switchMobileView(view) {
if (window.innerWidth > 768) return;
if (view === 'playlist') {
dom.showPlaylistBtn.classList.add('active');
dom.showLyricsBtn.classList.remove('active');
dom.playlist.classList.remove('mobile-hidden');
dom.lyrics.classList.add('mobile-hidden');
} else {
dom.showPlaylistBtn.classList.remove('active');
dom.showLyricsBtn.classList.add('active');
dom.playlist.classList.add('mobile-hidden');
dom.lyrics.classList.remove('mobile-hidden');
}
}
function playPrevious() {
if (state.currentTrackIndex <= 0) return;
const newIndex = state.currentTrackIndex - 1;
state.isOnlineMode ? findAndPlayOnlineSong(newIndex) : playAudio(newIndex);
}
function playNext() {
const limit = state.isOnlineMode ? state.onlineSongs.length : state.audioFiles.length;
if (state.currentTrackIndex >= limit - 1) {
dom.lyrics.innerHTML = '<div>已是列表最后一首。</div>';
return;
}
const newIndex = state.currentTrackIndex + 1;
state.isOnlineMode ? findAndPlayOnlineSong(newIndex) : playAudio(newIndex);
}
function autoPlayNext() { playNext(); }
function processFiles(files) {
if (files.length === 0) return;
state.isOnlineMode = false;
if (window.innerWidth <= 768) switchMobileView('playlist');
let newFilesAdded = false;
for (const file of files) {
const ext = file.name.split('.').pop().toLowerCase();
const baseName = file.name.split('.').slice(0, -1).join('.');
if (['mp3', 'wav', 'ogg', 'm4a', 'flac', 'aac', 'wma'].includes(ext)) {
if (!state.audioFiles.some(f => f.name === file.name)) {
state.audioFiles.push(file);
newFilesAdded = true;
}
} else if (['lrc', 'txt'].includes(ext)) {
state.lyricsFiles[baseName] = file;
}
}
if (newFilesAdded) renderLocalPlaylist();
if (state.audioFiles.length > 0 && dom.audioPlayer.paused) playAudio(0);
}
function playAudio(index) {
if (index < 0 || index >= state.audioFiles.length) return;
if (state.currentAudioUrl) URL.revokeObjectURL(state.currentAudioUrl);
state.isOnlineMode = false;
state.currentTrackIndex = index;
updatePlaylistHighlight();
const audioFile = state.audioFiles[index];
state.currentAudioUrl = URL.createObjectURL(audioFile);
dom.audioPlayer.src = state.currentAudioUrl;
dom.audioPlayer.load();
dom.audioPlayer.play();
resetLyrics();
loadLyricsForFile(audioFile);
}
async function handleOnlineMode() {
state.isOnlineMode = true;
if (window.innerWidth <= 768) switchMobileView('playlist');
const btnText = dom.loadOnlineBtn.querySelector('.btn-text');
const loader = dom.loadOnlineBtn.querySelector('.loader');
dom.loadOnlineBtn.disabled = true;
btnText.style.display = 'none';
loader.style.display = 'inline-block';
try {
await fetchAndProcessRadars();
if (state.onlineSongs.length > 0) findAndPlayOnlineSong(0);
} catch (error) {
console.error("加载在线歌曲时发生最终错误:", error);
dom.playlist.innerHTML = "<div>加载失败,请稍后再试。</div>";
} finally {
dom.loadOnlineBtn.disabled = false;
btnText.style.display = 'inline-block';
loader.style.display = 'none';
}
}
async function fetchAndProcessRadars() {
dom.playlist.innerHTML = `<div>正在聚合四大雷达歌单...</div>`;
const promises = RADAR_IDS.map(id =>
fetch(`${API_BASE_URL}/playlist/detail?id=${id}`)
.then(res => res.ok ? res.json() : Promise.reject(`歌单${id}加载失败`))
);
const results = await Promise.allSettled(promises);
let allTrackIds = [];
results.forEach(result => {
if (result.status === 'fulfilled' && result.value.playlist?.trackIds) {
allTrackIds.push(...result.value.playlist.trackIds);
}
});
const uniqueSongsMap = new Map();
const batchSize = 200;
for (let i = 0; i < allTrackIds.length; i += batchSize) {
const idsString = allTrackIds.slice(i, i + batchSize).map(item => item.id).join(',');
try {
const songsRes = await fetch(`${API_BASE_URL}/song/detail?ids=${idsString}`);
if (songsRes.ok) {
const songsData = await songsRes.json();
songsData.songs?.forEach(song => uniqueSongsMap.set(song.id, song));
}
} catch (e) {
console.warn("分批获取歌曲详情失败", e);
}
}
if (uniqueSongsMap.size === 0) throw new Error("未能获取任何歌曲详情");
let finalSongs = Array.from(uniqueSongsMap.values());
shuffleArray(finalSongs);
state.onlineSongs = finalSongs;
renderOnlinePlaylist();
}
async function findAndPlayOnlineSong(startIndex) {
for (let i = startIndex; i < state.onlineSongs.length; i++) {
try {
await playOnlineSong(i);
return; // 成功播放后退出循环
} catch (error) {
console.log(`第 ${i + 1} 首 (${state.onlineSongs[i].name}) 尝试失败,继续...`);
}
}
dom.lyrics.innerHTML = '<div>抱歉,当前列表中的歌曲均无法播放。</div>';
}
async function playOnlineSong(index) {
state.isOnlineMode = true;
state.currentTrackIndex = index;
updatePlaylistHighlight();
resetLyrics();
const song = state.onlineSongs[index];
dom.lyrics.innerHTML = `<div>正在加载: ${song.name}</div>`;
const urlRes = await fetch(`${API_BASE_URL}/song/url?id=${song.id}&br=320000`);
if (!urlRes.ok) throw new Error(`获取播放地址失败`);
const urlData = await urlRes.json();
if (!urlData.data?.[0]?.url) throw new Error('无效的播放地址');
dom.audioPlayer.src = urlData.data[0].url.replace(/^http:/, 'https:');
dom.audioPlayer.load();
dom.audioPlayer.play();
const lyricRes = await fetch(`${API_BASE_URL}/lyric?id=${song.id}`);
if (lyricRes.ok) {
const lyricData = await lyricRes.json();
if (lyricData.lrc?.lyric) {
displayLyrics(lyricData.lrc.lyric);
} else {
dom.lyrics.innerHTML = '<div>未找到在线歌词。</div>';
}
} else {
dom.lyrics.innerHTML = '<div>歌词加载失败。</div>';
}
}
function renderLocalPlaylist() {
dom.playlist.innerHTML = state.audioFiles.map((f, i) => `<div id="track-item-${i}" onclick="playAudio(${i})">${f.name}</div>`).join('') || "<div>请选择本地音乐</div>";
}
function renderOnlinePlaylist() {
dom.playlist.innerHTML = state.onlineSongs.map((s, i) => `<div id="track-item-${i}" onclick="findAndPlayOnlineSong(${i})">${s.name} - ${s.ar.map(a => a.name).join('/')}</div>`).join('') || "<div>列表为空</div>";
}
function updatePlaylistHighlight() {
const items = dom.playlist.querySelectorAll('div');
let currentItem = null;
items.forEach((item, index) => {
const isCurrent = index === state.currentTrackIndex;
item.classList.toggle('current', isCurrent);
if (isCurrent) currentItem = item;
});
if (currentItem) {
currentItem.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
}
}
function resetLyrics() {
state.lyricsData = [];
state.currentLyricLine = -1;
dom.lyrics.innerHTML = '<div>...</div>';
}
async function loadLyricsForFile(audioFile) {
const baseName = audioFile.name.split('.').slice(0, -1).join('.');
const lyricsFile = state.lyricsFiles[baseName];
if (lyricsFile) {
const reader = new FileReader();
reader.onload = (e) => displayLyrics(e.target.result);
reader.readAsText(lyricsFile);
} else {
try {
const searchRes = await fetch(`${API_BASE_URL}/search?keywords=${encodeURIComponent(baseName)}`);
const searchData = await searchRes.json();
const songId = searchData.result?.songs?.[0]?.id;
if (songId) {
const lyricRes = await fetch(`${API_BASE_URL}/lyric?id=${songId}`);
const lyricData = await lyricRes.json();
if (lyricData.lrc?.lyric) {
displayLyrics(lyricData.lrc.lyric);
return;
}
}
readEmbeddedLyrics(audioFile); // Fallback
} catch (error) {
readEmbeddedLyrics(audioFile); // Fallback on error
}
}
}
function readEmbeddedLyrics(file) {
jsmediatags.read(file, {
onSuccess: (tag) => {
const lyricsText = tag.tags.USLT?.data ?? tag.tags.SYLT?.data ?? tag.tags.lyrics;
if (lyricsText) {
displayLyrics(lyricsText);
} else {
dom.lyrics.innerHTML = '<div>未找到任何歌词。</div>';
}
},
onError: () => {
dom.lyrics.innerHTML = '<div>读取内嵌歌词失败。</div>';
}
});
}
function parseLrc(text) {
if (!text) return [];
const lines = text.split('\n');
const result = [];
// 改进后的正则,兼容更多格式
const timeRegex = /\[(\d{2}):(\d{2})[.:](\d{2,3})\]/g;
for (const line of lines) {
const textContent = line.replace(timeRegex, '').trim();
if (textContent) {
let match;
// 重置正则索引,确保可以匹配一行内的多个时间标签
timeRegex.lastIndex = 0;
while ((match = timeRegex.exec(line)) !== null) {
const minutes = parseInt(match[1], 10);
const seconds = parseInt(match[2], 10);
const milliseconds = parseInt(match[3].padEnd(3, '0'), 10);
result.push({
time: minutes * 60 + seconds + milliseconds / 1000,
text: textContent
});
}
}
}
return result.sort((a, b) => a.time - b.time);
}
function displayLyrics(lrcText) {
state.lyricsData = parseLrc(lrcText);
if (state.lyricsData.length > 0) {
dom.lyrics.innerHTML = state.lyricsData.map((item, index) => `<div id="lyric-line-${index}">${item.text}</div>`).join('');
} else {
// 如果解析后没内容,则原文显示
dom.lyrics.innerHTML = lrcText.split('\n').map(line => `<div>${line || ' '}</div>`).join('');
}
state.currentLyricLine = -1;
syncLyrics();
}
function syncLyrics() {
if (state.lyricsData.length === 0 || dom.audioPlayer.paused) return;
const currentTime = dom.audioPlayer.currentTime;
let newIndex = state.lyricsData.findIndex((line, i) => {
const nextLine = state.lyricsData[i + 1];
return currentTime >= line.time && (nextLine ? currentTime < nextLine.time : true);
});
if (newIndex !== -1 && newIndex !== state.currentLyricLine) {
// [优化-4] 移除强制切换视图的逻辑
// if (window.innerWidth <= 768 && dom.lyrics.classList.contains('mobile-hidden')) {
// switchMobileView('lyrics');
// }
const prevLine = document.getElementById(`lyric-line-${state.currentLyricLine}`);
if (prevLine) prevLine.classList.remove('current');
const currentLine = document.getElementById(`lyric-line-${newIndex}`);
if (currentLine) {
currentLine.classList.add('current');
// 只有当歌词视图可见时才滚动
if (dom.lyrics.offsetParent !== null) {
currentLine.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
state.currentLyricLine = newIndex;
}
}
function shuffleArray(array) {
let currentIndex = array.length, randomIndex;
while (currentIndex != 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
}
return array;
}
</script>
</body>
</html>