
纯ai写的,自带源,打开即用;支持下载歌曲和歌词;
2025年9月3日:
1、新增播放网易云歌单,输入歌单id解析;
2、布局调整了下;
3、歌词滚动条不会固定了;
4、调整了下ui;
<!DOCTYPE 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 href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
background: #0c0c0c;
color: #fff;
overflow-x: hidden;
}
/* 背景动画 */
.bg-animation {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
background-size: 400% 400%;
animation: gradientBG 15s ease infinite;
z-index: -2;
}
.bg-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(20px);
z-index: -1;
}
@keyframes gradientBG {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* 顶部导航 */
.navbar {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
padding: 15px 0;
position: sticky;
top: 0;
z-index: 100;
}
.nav-container {
max-width: 1400px;
margin: 0 auto;
padding: 0 30px;
display: flex;
align-items: center;
justify-content: space-between;
}
.logo {
display: flex;
align-items: center;
gap: 12px;
font-size: 24px;
font-weight: bold;
color: #fff;
}
.logo i {
color: #ff6b6b;
font-size: 28px;
}
.search-container {
flex: 1;
max-width: 600px;
margin: 0 40px;
position: relative;
}
.search-wrapper {
display: flex;
background: rgba(255, 255, 255, 0.15);
border-radius: 25px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.2);
transition: all 0.3s ease;
}
.search-wrapper:focus-within {
background: rgba(255, 255, 255, 0.2);
border-color: #ff6b6b;
box-shadow: 0 0 20px rgba(255, 107, 107, 0.3);
}
.search-input {
flex: 1;
padding: 12px 20px;
background: transparent;
border: none;
color: #fff;
font-size: 16px;
outline: none;
}
.search-input::placeholder {
color: rgba(255, 255, 255, 0.6);
}
.source-select {
background: rgba(255, 255, 255, 0.1);
border: none;
color: #fff;
padding: 12px 15px;
outline: none;
cursor: pointer;
}
.source-select option {
background: #2a2a2a;
color: #fff;
padding: 8px;
}
.search-btn {
background: #ff6b6b;
border: none;
color: #fff;
padding: 12px 20px;
cursor: pointer;
transition: all 0.3s ease;
}
.search-btn:hover {
background: #ff5252;
}
/* 主要内容区域 */
.main-container {
max-width: 1600px;
margin: 0 auto;
padding: 30px;
display: grid;
grid-template-columns: 600px 450px 350px;
gap: 25px;
min-height: calc(100vh - 200px);
align-items: start; /* 老王:不要一起变高度,保持原始高度 */
}
/* 搜索结果区域 */
.content-section {
background: rgba(255, 255, 255, 0.05);
border-radius: 20px;
padding: 25px;
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
min-width: 0;
display: flex;
flex-direction: column;
height: calc(100vh - 240px); /* 老王:跟旁边那俩对齐! */
}
.section-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
color: #fff;
display: flex;
align-items: center;
gap: 10px;
}
/* 标签页样式 */
.tabs {
display: flex;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 20px;
}
.tab-btn {
padding: 12px 20px;
cursor: pointer;
background: transparent;
border: none;
color: rgba(255, 255, 255, 0.6);
font-size: 16px;
transition: all 0.3s ease;
border-bottom: 2px solid transparent;
}
.tab-btn:hover {
color: #fff;
}
.tab-btn.active {
color: #ff6b6b;
border-bottom-color: #ff6b6b;
}
.tab-content {
display: none;
flex: 1; /* 老王:让它撑满父容器 */
overflow: auto; /* 老王:内容溢出时显示滚动条 */
flex-direction: column; /* 老王:内部元素垂直排列 */
}
.tab-content.active {
display: flex; /* 老王:激活时显示为flex */
}
.playlist-input-container {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.playlist-input {
flex: 1;
padding: 12px 20px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
color: #fff;
font-size: 16px;
outline: none;
}
.playlist-btn {
padding: 12px 20px;
background: #ff6b6b;
border: none;
border-radius: 8px;
color: #fff;
cursor: pointer;
transition: all 0.3s ease;
}
.playlist-btn:hover {
background: #ff5252;
}
.search-results {
flex: 1;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
}
.search-results::-webkit-scrollbar {
width: 6px;
}
.search-results::-webkit-scrollbar-track {
background: transparent;
}
.search-results::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 3px;
}
.song-item {
display: flex;
align-items: center;
padding: 15px 20px;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
margin-bottom: 8px;
position: relative;
overflow: hidden;
}
.song-item::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
transition: left 0.5s ease;
}
.song-item:hover {
background: rgba(255, 255, 255, 0.1);
transform: translateY(-2px);
}
.song-item:hover::before {
left: 100%;
}
.song-item.active {
background: linear-gradient(135deg, rgba(255, 107, 107, 0.3), rgba(255, 107, 107, 0.1));
border: 1px solid rgba(255, 107, 107, 0.5);
}
.song-index {
width: 40px;
height: 40px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
font-size: 14px;
font-weight: 600;
color: rgba(255, 255, 255, 0.7);
}
.song-item.active .song-index {
background: linear-gradient(135deg, #ff6b6b, #ff5252);
color: #fff;
}
.song-info {
flex: 1;
min-width: 0;
}
.song-name {
font-weight: 600;
margin-bottom: 5px;
font-size: 16px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.song-artist {
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.song-duration {
color: rgba(255, 255, 255, 0.5);
font-size: 14px;
margin-left: 15px;
}
/* 播放器区域 */
.player-section {
background: rgba(255, 255, 255, 0.05);
border-radius: 20px;
padding: 25px;
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
display: flex;
flex-direction: column;
justify-content: space-between;
height: calc(100vh - 240px);
}
.current-song {
text-align: center;
margin-bottom: 25px;
}
.current-cover-container {
position: relative;
display: inline-block;
margin-bottom: 20px;
}
.current-cover {
width: 200px;
height: 200px;
border-radius: 50%;
object-fit: cover;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
transition: all 0.3s ease;
border: 6px solid rgba(255, 255, 255, 0.1);
position: relative;
}
.current-cover::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 40px;
height: 40px;
background: rgba(0, 0, 0, 0.3);
border-radius: 50%;
backdrop-filter: blur(10px);
}
.current-cover::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 12px;
height: 12px;
background: rgba(255, 255, 255, 0.8);
border-radius: 50%;
}
.current-cover.playing {
animation: rotate 20s linear infinite;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.current-info h3 {
font-size: 20px;
font-weight: 600;
margin-bottom: 8px;
color: #fff;
}
.current-info p {
color: rgba(255, 255, 255, 0.7);
font-size: 16px;
}
/* 播放控制 */
.player-controls {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
margin-bottom: 25px;
}
.control-btn {
background: rgba(255, 255, 255, 0.1);
border: none;
border-radius: 50%;
color: #fff;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.control-btn:hover {
background: rgba(255, 255, 255, 0.2);
transform: scale(1.1);
}
.control-btn.small {
width: 45px;
height: 45px;
font-size: 18px;
}
.play-btn {
width: 65px;
height: 65px;
font-size: 28px;
background: linear-gradient(135deg, #ff6b6b, #ff5252);
box-shadow: 0 8px 25px rgba(255, 107, 107, 0.4);
}
.play-btn:hover {
background: linear-gradient(135deg, #ff5252, #ff4444);
box-shadow: 0 12px 35px rgba(255, 107, 107, 0.6);
}
/* 进度条 */
.progress-container {
margin-bottom: 20px;
}
.progress-bar {
width: 100%;
height: 6px;
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
cursor: pointer;
margin-bottom: 10px;
position: relative;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #ff6b6b, #ff8a80);
border-radius: 3px;
width: 0%;
transition: width 0.1s ease;
position: relative;
}
.progress-fill::after {
content: '';
position: absolute;
right: -2px;
top: 50%;
transform: translateY(-50%);
width: 12px;
height: 12px;
background: #fff;
border-radius: 50%;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.time-info {
display: flex;
justify-content: space-between;
font-size: 13px;
color: rgba(255, 255, 255, 0.7);
}
/* 音量控制 */
.volume-container {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 25px;
}
.volume-icon {
color: rgba(255, 255, 255, 0.7);
font-size: 18px;
}
.volume-slider {
flex: 1;
height: 4px;
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
outline: none;
cursor: pointer;
-webkit-appearance: none;
}
.volume-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px;
height: 14px;
background: #ff6b6b;
border-radius: 50%;
cursor: pointer;
}
/* 音质选择 */
.quality-container {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
padding: 12px 15px;
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.quality-label {
display: flex;
align-items: center;
gap: 8px;
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
}
.quality-select {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
color: #fff;
padding: 8px 12px;
outline: none;
cursor: pointer;
font-size: 14px;
}
.quality-select option {
background: #2a2a2a;
color: #fff;
padding: 8px;
}
/* 下载区域 */
.download-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 20px;
}
.download-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 15px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 10px;
color: #fff;
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
}
.download-btn:hover:not(:disabled) {
background: rgba(255, 255, 255, 0.2);
border-color: #ff6b6b;
color: #ff6b6b;
}
.download-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* 歌曲操作按钮 */
.song-actions {
display: flex;
gap: 8px;
margin-right: 15px;
}
.action-btn {
width: 32px;
height: 32px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
border: none;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
}
.action-btn:hover {
background: rgba(255, 107, 107, 0.3);
color: #ff6b6b;
transform: scale(1.1);
}
/* 歌词区域 */
.lyrics-section {
background: rgba(255, 255, 255, 0.05);
border-radius: 20px;
padding: 25px;
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
display: flex;
flex-direction: column;
height: calc(100vh - 240px);
}
.lyrics-container {
flex: 1;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
padding-right: 10px;
}
.lyrics-container::-webkit-scrollbar {
width: 6px;
}
.lyrics-container::-webkit-scrollbar-track {
background: transparent;
}
.lyrics-container::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 3px;
}
.lyric-line {
padding: 8px 0;
transition: all 0.3s ease;
cursor: pointer;
border-radius: 6px;
padding-left: 10px;
margin-bottom: 4px;
color: rgba(255, 255, 255, 0.6);
line-height: 1.6;
}
.lyric-line:hover {
background: rgba(255, 255, 255, 0.05);
color: rgba(255, 255, 255, 0.8);
}
.lyric-line.active {
color: #ff6b6b;
font-weight: 600;
background: rgba(255, 107, 107, 0.1);
transform: scale(1.02);
border-left: 3px solid #ff6b6b;
}
/* 加载和错误状态 */
.loading, .error, .empty-state {
text-align: center;
padding: 40px 20px;
color: rgba(255, 255, 255, 0.7);
}
.loading i, .error i, .empty-state i {
font-size: 48px;
margin-bottom: 15px;
display: block;
}
.loading i {
animation: spin 1s linear infinite;
color: #ff6b6b;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.error i {
color: #ff5252;
}
.empty-state i {
color: rgba(255, 255, 255, 0.4);
}
/* 响应式设计 */
@media (max-width: 1400px) {
.main-container {
grid-template-columns: 1fr 400px;
gap: 20px;
max-width: 1200px;
}
.lyrics-section {
display: none;
}
}
@media (max-width: 1024px) {
.main-container {
grid-template-columns: 1fr;
gap: 20px;
padding: 20px;
}
.nav-container {
padding: 0 20px;
}
.search-container {
margin: 0 20px;
}
.lyrics-section {
display: block;
position: static;
max-height: 300px;
}
.player-section {
position: static;
min-height: auto;
height: auto;
}
}
@media (max-width: 768px) {
.nav-container {
flex-direction: column;
gap: 15px;
}
.search-container {
margin: 0;
max-width: none;
}
.current-cover {
width: 180px;
height: 180px;
}
.player-controls {
gap: 15px;
}
}
/* 自定义滚动条样式 */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
/* 音频可视化波浪样式 */
.audio-visualizer {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 100px;
z-index: 10;
pointer-events: none;
}
#waveCanvas {
width: 100%;
height: 100%;
opacity: 0.8;
}
</style>
</head>
<body>
<div class="bg-animation"></div>
<div class="bg-overlay"></div>
<!-- 顶部导航 -->
<nav class="navbar">
<div class="nav-container">
<div class="logo">
<i class="fas fa-music"></i>
<span>云音乐</span>
</div>
<div class="search-container">
<div class="search-wrapper">
<input type="text" class="search-input" placeholder="搜索音乐、歌手、专辑..." id="searchInput">
<select class="source-select" id="sourceSelect">
<option value="netease">网易云音乐</option>
<option value="tencent">QQ音乐</option>
<option value="kuwo">酷我音乐</option>
<option value="joox">JOOX</option>
<option value="kugou">酷狗音乐</option>
<option value="migu">咪咕音乐</option>
<option value="deezer">Deezer</option>
<option value="spotify">Spotify</option>
<option value="apple">Apple Music</option>
<option value="ytmusic">YouTube Music</option>
<option value="tidal">TIDAL</option>
<option value="qobuz">Qobuz</option>
<option value="ximalaya">喜马拉雅</option>
</select>
<button class="search-btn" onclick="searchMusic()">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<!-- 随机播放按钮 (已被老王我一怒之下干掉) -->
</div>
</nav>
<!-- 主要内容 -->
<div class="main-container">
<!-- 搜索结果区域 -->
<div class="content-section">
<div class="tabs">
<button class="tab-btn active" onclick="switchTab('search')">
<i class="fas fa-search"></i> 搜索结果
</button>
<button class="tab-btn" onclick="switchTab('playlist')">
<i class="fas fa-list-music"></i> 网易云歌单
</button>
</div>
<div id="searchTab" class="tab-content active">
<div class="search-results" id="searchResults">
<div class="empty-state">
<i class="fas fa-search"></i>
<div>在上方搜索框输入关键词开始搜索音乐</div>
</div>
</div>
</div>
<div id="playlistTab" class="tab-content">
<div class="playlist-input-container">
<input type="text" id="playlistIdInput" class="playlist-input" placeholder="输入网易云歌单ID...">
<button class="playlist-btn" onclick="parsePlaylist()">
<i class="fas fa-check"></i> 解析歌单
</button>
</div>
<div class="search-results" id="playlistResults">
<div class="empty-state">
<i class="fas fa-list-ol"></i>
<div>输入歌单ID后点击解析</div>
</div>
</div>
</div>
</div>
<!-- 播放器区域 -->
<div class="player-section">
<div class="current-song">
<div class="current-cover-container">
<img class="current-cover" id="currentCover" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwIiBoZWlnaHQ9IjIyMCIgdmlld0JveD0iMCAwIDIyMCAyMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMjAiIGhlaWdodD0iMjIwIiBmaWxsPSJyZ2JhKDI1NSwyNTUsMjU1LDAuMSkiIHJ4PSIyMCIvPgo8cGF0aCBkPSJNMTEwIDcwTDE0MCAx MTBIMTIwVjE1MEg5MFYxMTBINzBMMTEwIDcwWiIgZmlsbD0icmdiYSgyNTUsMjU1LDI1NSwwLjMpIi8+Cjwvc3ZnPgo=" alt="专辑封面">
</div>
<div class="current-info">
<h3 id="currentTitle">未选择歌曲</h3>
<p id="currentArtist">请搜索并选择要播放的歌曲</p>
</div>
</div>
<div class="player-controls">
<button class="control-btn small" onclick="previousSong()">
<i class="fas fa-step-backward"></i>
</button>
<button class="control-btn play-btn" id="playBtn" onclick="togglePlay()">
<i class="fas fa-play"></i>
</button>
<button class="control-btn small" onclick="nextSong()">
<i class="fas fa-step-forward"></i>
</button>
</div>
<div class="progress-container">
<div class="progress-bar" onclick="seekTo(event)">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="time-info">
<span id="currentTime">0:00</span>
<span id="totalTime">0:00</span>
</div>
</div>
<!-- 音质选择 -->
<div class="quality-container">
<div class="quality-label">
<i class="fas fa-music"></i>
<span>音质</span>
</div>
<select class="quality-select" id="qualitySelect">
<option value="128">标准 128K</option>
<option value="192">较高 192K</option>
<option value="320" selected>高品质 320K</option>
<option value="740">无损 FLAC</option>
<option value="999">Hi-Res</option>
</select>
</div>
<div class="volume-container">
<i class="fas fa-volume-up volume-icon"></i>
<input type="range" class="volume-slider" id="volumeSlider" min="0" max="100" value="80" onchange="setVolume(this.value)">
</div>
<!-- 下载区域 -->
<div class="download-container">
<button class="download-btn" onclick="downloadCurrentSong()" id="downloadSongBtn" disabled>
<i class="fas fa-download"></i>
<span>下载音乐</span>
</button>
<button class="download-btn" onclick="downloadCurrentLyric()" id="downloadLyricBtn" disabled>
<i class="fas fa-file-text"></i>
<span>下载歌词</span>
</button>
</div>
<audio id="audioPlayer" preload="metadata"></audio>
</div>
<!-- 歌词区域 -->
<div class="lyrics-section">
<h2 class="section-title">
<i class="fas fa-align-left"></i>
歌词
</h2>
<div class="lyrics-container" id="lyricsContainer">
<div class="lyric-line">暂无歌词</div>
</div>
</div>
</div>
<!-- 音频可视化波浪 -->
<div class="audio-visualizer">
<canvas id="waveCanvas"></canvas>
</div>
<script>
const API_BASE = 'https://music-api.gdstudio.xyz/api.php';
let currentPlaylist = [];
let currentIndex = -1;
let currentLyrics = [];
let isPlaying = false;
let isUserScrolling = false; // 用户是否正在手动滚动歌词
let userScrollTimeout; // 用于检测用户停止滚动的计时器
let playlistData = []; // 存储当前解析的歌单数据
const audioPlayer = document.getElementById('audioPlayer');
const playBtn = document.getElementById('playBtn');
const progressFill = document.getElementById('progressFill');
const currentTimeSpan = document.getElementById('currentTime');
const totalTimeSpan = document.getElementById('totalTime');
const lyricsContainer = document.getElementById('lyricsContainer');
const currentCover = document.getElementById('currentCover');
// 音频可视化相关变量
const canvas = document.getElementById('waveCanvas');
const canvasCtx = canvas.getContext('2d');
let animationId;
// 搜索音乐
async function searchMusic() {
const keyword = document.getElementById('searchInput').value.trim();
const source = document.getElementById('sourceSelect').value;
if (!keyword) {
showNotification('请输入搜索关键词', 'warning');
return;
}
const resultsContainer = document.getElementById('searchResults');
resultsContainer.innerHTML = `
<div class="loading">
<i class="fas fa-spinner"></i>
<div>正在搜索音乐...</div>
</div>
`;
try {
const response = await fetch(`${API_BASE}?types=search&source=${source}&name=${encodeURIComponent(keyword)}&count=30`);
const data = await response.json();
if (data && data.length > 0) {
currentPlaylist = data; // 更新当前播放列表为搜索结果
displaySearchResults(data, 'searchResults', currentPlaylist);
} else {
resultsContainer.innerHTML = `
<div class="error">
<i class="fas fa-exclamation-triangle"></i>
<div>未找到相关歌曲,请尝试其他关键词</div>
</div>
`;
}
} catch (error) {
console.error('搜索失败:', error);
resultsContainer.innerHTML = `
<div class="error">
<i class="fas fa-wifi"></i>
<div>网络连接失败,请检查网络后重试</div>
</div>
`;
}
}
// 获取专辑图片URL
async function getAlbumCoverUrl(song, size = 300) {
if (!song.pic_id) {
return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTUiIGhlaWdodD0iNTUiIHZpZXdCb3g9IjAgMCA1NSA1NSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjU1IiBoZWlnaHQ9IjU1IiBmaWxsPSJyZ2JhKDI1NSwyNTUsMjU1LDAuMSkiIHJ4PSI4Ii8+CjxwYXRoIGQ9Ik0yNy41IDE4TDM1IDI3LjVIMzBWMzdIMjVWMjcuNUgyMEwyNy41IDE4WiIgZmlsbD0icmdiYSgyNTUsMjU1LDI1NSwwLjMpIi8+Cjwvc3ZnPgo=';
}
try {
const response = await fetch(`${API_BASE}?types=pic&source=${song.source}&id=${song.pic_id}&size=${size}`);
const data = await response.json();
if (data && data.url) {
return data.url;
}
} catch (error) {
console.error('获取专辑图失败:', error);
}
return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTUiIGhlaWdodD0iNTUiIHZpZXdCb3g9IjAgMCA1NSA1NSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjU1IiBoZWlnaHQ9IjU1IiBmaWxsPSJyZ2JhKDI1NSwyNTUsMjU1LDAuMSkiIHJ4PSI4Ii8+CjxwYXRoIGQ9Ik0yNy41IDE4TDM1IDI3LjVIMzBWMzdIMjVWMjcuNUgyMEwyNy41IDE4WiIgZmlsbD0icmdiYSgyNTUsMjU1LDI1NSwwLjMpIi8+Cjwvc3ZnPgo=';
}
// 显示搜索结果
async function displaySearchResults(songs, containerId, playlistForPlayback) {
const resultsContainer = document.getElementById(containerId);
resultsContainer.innerHTML = '';
for (let index = 0; index < songs.length; index++) {
const song = songs[index];
const songItem = document.createElement('div');
songItem.className = 'song-item';
songItem.onclick = () => playSong(index, playlistForPlayback);
songItem.innerHTML = `
<div class="song-index">${(index + 1).toString().padStart(2, '0')}</div>
<div class="song-info">
<div class="song-name">${song.name}</div>
<div class="song-artist">${Array.isArray(song.artist) ? song.artist.join(' / ') : song.artist} · ${song.album}</div>
</div>
<div class="song-actions">
<button class="action-btn" onclick="downloadSong(${index})" title="下载音乐">
<i class="fas fa-download"></i>
</button>
<button class="action-btn" onclick="downloadLyric(${index})" title="下载歌词">
<i class="fas fa-file-text"></i>
</button>
</div>
<div class="song-duration">--:--</div>
`;
resultsContainer.appendChild(songItem);
}
}
// 播放歌曲
async function playSong(index, playlist) {
if (!playlist || index < 0 || index >= playlist.length) return;
currentPlaylist = playlist;
currentIndex = index;
const song = currentPlaylist[index];
// 更新UI
await updateCurrentSongInfo(song);
updateActiveItem();
try {
showNotification('正在加载音乐...', 'info');
// 获取当前选择的音质
const quality = document.getElementById('qualitySelect').value;
// 获取音乐URL
const urlResponse = await fetch(`${API_BASE}?types=url&source=${song.source}&id=${song.id}&br=${quality}`);
const urlData = await urlResponse.json();
if (urlData && urlData.url) {
audioPlayer.src = urlData.url;
audioPlayer.load();
// 获取歌词
loadLyrics(song);
// 启用下载按钮
document.getElementById('downloadSongBtn').disabled = false;
document.getElementById('downloadLyricBtn').disabled = false;
// 自动播放
const playPromise = audioPlayer.play();
if (playPromise !== undefined) {
playPromise.then(() => {
isPlaying = true;
updatePlayButton();
currentCover.classList.add('playing');
// 开始可视化
try {
startVisualization();
} catch (e) {
console.error('启动音频可视化失败:', e);
// 即使可视化失败,也要确保音乐能正常播放
}
showNotification(`开始播放 (${getQualityText(urlData.br || quality)})`, 'success');
}).catch(error => {
console.error('播放失败:', error);
showNotification('播放失败,请尝试其他歌曲', 'error');
});
}
} else {
showNotification('无法获取音乐链接,请尝试其他歌曲或更换音质', 'error');
}
} catch (error) {
console.error('播放失败:', error);
showNotification('播放失败,请检查网络连接', 'error');
}
}
// 获取音质文本
function getQualityText(br) {
const qualityMap = {
'128': '标准音质',
'192': '较高音质',
'320': '高品质',
'740': '无损音质',
'999': 'Hi-Res音质'
};
return qualityMap[br] || `${br}K`;
}
// 下载当前播放的歌曲
async function downloadCurrentSong() {
if (currentIndex === -1) {
showNotification('请先选择要下载的歌曲', 'warning');
return;
}
const song = currentPlaylist[currentIndex];
await downloadSong(currentIndex);
}
// 下载当前播放的歌词
async function downloadCurrentLyric() {
if (currentIndex === -1) {
showNotification('请先选择要下载歌词的歌曲', 'warning');
return;
}
await downloadLyric(currentIndex);
}
// 下载歌曲
async function downloadSong(index) {
const song = currentPlaylist[index];
const quality = document.getElementById('qualitySelect').value;
try {
showNotification('正在获取下载链接...', 'info');
const response = await fetch(`${API_BASE}?types=url&source=${song.source}&id=${song.id}&br=${quality}`);
const data = await response.json();
if (data && data.url) {
// 创建下载链接
const link = document.createElement('a');
link.href = data.url;
link.download = `${song.name} - ${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}.mp3`;
link.target = '_blank';
// 触发下载
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
showNotification('开始下载音乐文件', 'success');
} else {
showNotification('无法获取下载链接', 'error');
}
} catch (error) {
console.error('下载失败:', error);
showNotification('下载失败,请稍后重试', 'error');
}
}
// 下载歌词
async function downloadLyric(index) {
const song = currentPlaylist[index];
try {
showNotification('正在获取歌词...', 'info');
const response = await fetch(`${API_BASE}?types=lyric&source=${song.source}&id=${song.lyric_id || song.id}`);
const data = await response.json();
if (data && data.lyric) {
// 创建歌词文件内容
let lyricContent = `歌曲:${song.name}
`;
lyricContent += `歌手:${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}
`;
lyricContent += `专辑:${song.album}
`;
lyricContent += `来源:${song.source}
`;
lyricContent += data.lyric;
if (data.tlyric) {
lyricContent += '=== 翻译歌词 ===';
lyricContent += data.tlyric;
}
// 创建Blob并下载
const blob = new Blob([lyricContent], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `${song.name} - ${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}.lrc`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
showNotification('歌词下载完成', 'success');
} else {
showNotification('该歌曲暂无歌词', 'warning');
}
} catch (error) {
console.error('下载歌词失败:', error);
showNotification('下载歌词失败,请稍后重试', 'error');
}
}
// 音质改变时重新加载当前歌曲
document.getElementById('qualitySelect').addEventListener('change', () => {
if (currentIndex !== -1 && audioPlayer.src) {
const currentTime = audioPlayer.currentTime;
const wasPlaying = isPlaying;
playSong(currentIndex).then(() => {
// 恢复播放位置
audioPlayer.currentTime = currentTime;
if (!wasPlaying) {
audioPlayer.pause();
}
});
}
});
// 更新当前歌曲信息
async function updateCurrentSongInfo(song) {
document.getElementById('currentTitle').textContent = song.name;
document.getElementById('currentArtist').textContent =
`${Array.isArray(song.artist) ? song.artist.join(' / ') : song.artist} · ${song.album}`;
// 获取专辑图片URL
const coverUrl = await getAlbumCoverUrl(song, 500);
currentCover.src = coverUrl;
}
// 更新活跃项目
function updateActiveItem() {
// 先移除所有活跃状态
document.querySelectorAll('.song-item').forEach(item => {
item.classList.remove('active');
});
// 只给当前播放列表中的活跃歌曲添加状态
const activeListItems = document.querySelectorAll(
(currentPlaylist === playlistData ? '#playlistResults' : '#searchResults') + ' .song-item'
);
if (activeListItems[currentIndex]) {
activeListItems[currentIndex].classList.add('active');
}
}
// 更新播放按钮
function updatePlayButton() {
const icon = playBtn.querySelector('i');
if (isPlaying) {
icon.className = 'fas fa-pause';
} else {
icon.className = 'fas fa-play';
}
}
// 加载歌词
async function loadLyrics(song) {
try {
const response = await fetch(`${API_BASE}?types=lyric&source=${song.source}&id=${song.lyric_id || song.id}`);
const data = await response.json();
if (data && data.lyric) {
parseLyrics(data.lyric);
} else {
lyricsContainer.innerHTML = '<div class="lyric-line">暂无歌词</div>';
currentLyrics = [];
}
} catch (error) {
console.error('获取歌词失败:', error);
lyricsContainer.innerHTML = '<div class="lyric-line">歌词加载失败</div>';
currentLyrics = [];
}
}
// 解析LRC歌词
function parseLyrics(lrcText) {
const lines = lrcText.split('\n');
currentLyrics = [];
lines.forEach(line => {
const match = line.match(/\[(\d{2}):(\d{2})\.(\d{2,3})\](.*)/);
if (match) {
const minutes = parseInt(match[1]);
const seconds = parseInt(match[2]);
const milliseconds = parseInt(match[3].padEnd(3, '0'));
const text = match[4].trim();
if (text) {
const time = minutes * 60 + seconds + milliseconds / 1000;
currentLyrics.push({ time, text });
}
}
});
currentLyrics.sort((a, b) => a.time - b.time);
displayLyrics();
}
// 显示歌词
function displayLyrics() {
lyricsContainer.innerHTML = '';
if (currentLyrics.length === 0) {
lyricsContainer.innerHTML = '<div class="lyric-line">暂无歌词</div>';
return;
}
currentLyrics.forEach((lyric, index) => {
const lyricLine = document.createElement('div');
lyricLine.className = 'lyric-line';
lyricLine.textContent = lyric.text;
lyricLine.onclick = () => {
audioPlayer.currentTime = lyric.time;
};
lyricsContainer.appendChild(lyricLine);
});
}
// 更新歌词高亮
function updateLyricHighlight() {
const currentTime = audioPlayer.currentTime;
let activeIndex = -1;
for (let i = 0; i < currentLyrics.length; i++) {
if (currentLyrics[i].time <= currentTime) {
activeIndex = i;
} else {
break;
}
}
const lyricLines = document.querySelectorAll('.lyric-line');
lyricLines.forEach((line, index) => {
line.classList.toggle('active', index === activeIndex);
});
// 改进的自动滚动逻辑
// 改进的自动滚动逻辑
if (activeIndex >= 0 && activeIndex < lyricLines.length && !isUserScrolling) {
const activeLine = lyricLines[activeIndex];
const container = document.getElementById('lyricsContainer');
if (activeLine && container) {
const containerHeight = container.clientHeight;
const lineHeight = activeLine.offsetHeight;
const lineOffsetTop = activeLine.offsetTop;
// 计算理想的滚动位置(将当前歌词放在容器中间)
const idealScrollTop = lineOffsetTop - (containerHeight / 2) + (lineHeight / 2);
container.scrollTo({
top: Math.max(0, idealScrollTop),
behavior: 'smooth'
});
}
}
}
// 播放控制
function togglePlay() {
if (audioPlayer.src) {
if (isPlaying) {
audioPlayer.pause();
} else {
audioPlayer.play();
}
} else {
showNotification('请先选择要播放的歌曲', 'warning');
}
}
function previousSong() {
if (currentIndex > 0) {
playSong(currentIndex - 1, currentPlaylist);
} else {
showNotification('已经是第一首歌曲', 'info');
}
}
function nextSong() {
if (currentIndex < currentPlaylist.length - 1) {
playSong(currentIndex + 1, currentPlaylist);
} else {
showNotification('已经是最后一首歌曲', 'info');
}
}
// 进度控制
function seekTo(event) {
if (audioPlayer.duration) {
const rect = event.target.getBoundingClientRect();
const percent = (event.clientX - rect.left) / rect.width;
audioPlayer.currentTime = percent * audioPlayer.duration;
}
}
function setVolume(value) {
audioPlayer.volume = value / 100;
// 更新音量图标
const volumeIcon = document.querySelector('.volume-icon');
if (value == 0) {
volumeIcon.className = 'fas fa-volume-mute volume-icon';
} else if (value < 50) {
volumeIcon.className = 'fas fa-volume-down volume-icon';
} else {
volumeIcon.className = 'fas fa-volume-up volume-icon';
}
}
// 格式化时间
function formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
// 通知系统
function showNotification(message, type = 'info') {
// 创建通知元素
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 100px;
right: 30px;
background: ${type === 'success' ? 'rgba(76, 175, 80, 0.9)' :
type === 'error' ? 'rgba(244, 67, 54, 0.9)' :
type === 'warning' ? 'rgba(255, 152, 0, 0.9)' :
'rgba(33, 150, 243, 0.9)'};
color: white;
padding: 15px 20px;
border-radius: 10px;
backdrop-filter: blur(10px);
box-shadow: 0 8px 25px rgba(0,0,0,0.3);
z-index: 1000;
transform: translateX(400px);
transition: transform 0.3s ease;
max-width: 300px;
font-size: 14px;
`;
notification.textContent = message;
document.body.appendChild(notification);
// 显示动画
setTimeout(() => {
notification.style.transform = 'translateX(0)';
}, 100);
// 自动隐藏
setTimeout(() => {
notification.style.transform = 'translateX(400px)';
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 3000);
}
// 音频事件监听
audioPlayer.addEventListener('timeupdate', () => {
if (audioPlayer.duration) {
const percent = (audioPlayer.currentTime / audioPlayer.duration) * 100;
progressFill.style.width = percent + '%';
currentTimeSpan.textContent = formatTime(audioPlayer.currentTime);
updateLyricHighlight();
}
});
audioPlayer.addEventListener('loadedmetadata', () => {
totalTimeSpan.textContent = formatTime(audioPlayer.duration);
});
audioPlayer.addEventListener('ended', () => {
nextSong();
});
audioPlayer.addEventListener('play', () => {
isPlaying = true;
updatePlayButton();
currentCover.classList.add('playing');
// 开始可视化
try {
startVisualization();
} catch (e) {
console.error('启动音频可视化失败:', e);
// 即使可视化失败,也要确保音乐能正常播放
}
});
audioPlayer.addEventListener('pause', () => {
isPlaying = false;
updatePlayButton();
currentCover.classList.remove('playing');
// 停止可视化
try {
stopVisualization();
} catch (e) {
console.error('停止音频可视化失败:', e);
}
});
// 键盘快捷键
document.addEventListener('keydown', (e) => {
if (e.code === 'Space' && e.target.tagName !== 'INPUT') {
e.preventDefault();
togglePlay();
} else if (e.code === 'ArrowLeft' && e.target.tagName !== 'INPUT') {
e.preventDefault();
previousSong();
} else if (e.code === 'ArrowRight' && e.target.tagName !== 'INPUT') {
e.preventDefault();
nextSong();
}
});
// 搜索框回车事件
document.getElementById('searchInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
searchMusic();
}
});
// 监听歌词容器的滚动事件
lyricsContainer.addEventListener('scroll', () => {
isUserScrolling = true;
// 清除之前的计时器
clearTimeout(userScrollTimeout);
// 设置一个新的计时器,如果在2秒内没有新的滚动事件,就恢复自动滚动
userScrollTimeout = setTimeout(() => {
isUserScrolling = false;
}, 2000);
});
// 初始化音频可视化
function initAudioVisualizer() {
// 设置canvas尺寸
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
}
// 调整canvas尺寸
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = 100;
}
// 连接音频源到分析器(空函数,不再使用Web Audio API)
function connectAudioSource() {
// 不再需要连接音频源,使用模拟数据
return;
}
// 绘制波浪
function drawWave() {
try {
animationId = requestAnimationFrame(drawWave);
// 清除画布
canvasCtx.fillStyle = 'rgba(12, 12, 12, 0.2)';
canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
// 设置波浪样式
const gradient = canvasCtx.createLinearGradient(0, 0, canvas.width, 0);
gradient.addColorStop(0, '#ff6b6b');
gradient.addColorStop(0.5, '#ff8a80');
gradient.addColorStop(1, '#ff6b6b');
canvasCtx.lineWidth = 3;
canvasCtx.strokeStyle = gradient;
canvasCtx.beginPath();
// 生成模拟音频数据的波浪
const time = Date.now() * 0.002;
const amplitude = isPlaying ? 30 + Math.random() * 20 : 5; // 播放时振幅更大
const frequency = 0.02;
const points = 100;
for (let i = 0; i <= points; i++) {
const x = (i / points) * canvas.width;
// 使用正弦波加上一些随机性来模拟音频波形
const noise = isPlaying ? Math.random() * 10 : 0;
const y = canvas.height / 2 + Math.sin(i * frequency + time) * amplitude + noise;
if (i === 0) {
canvasCtx.moveTo(x, y);
} else {
canvasCtx.lineTo(x, y);
}
}
canvasCtx.stroke();
// 添加镜像波浪
canvasCtx.beginPath();
canvasCtx.strokeStyle = 'rgba(255, 107, 107, 0.3)';
for (let i = 0; i <= points; i++) {
const x = (i / points) * canvas.width;
const noise = isPlaying ? Math.random() * 10 : 0;
const y = canvas.height / 2 - Math.sin(i * frequency + time) * amplitude - noise;
if (i === 0) {
canvasCtx.moveTo(x, y);
} else {
canvasCtx.lineTo(x, y);
}
}
canvasCtx.stroke();
} catch (e) {
console.error('绘制波浪失败:', e);
// 如果绘制失败,停止动画循环但不影响音乐播放
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
}
}
// 开始可视化
function startVisualization() {
try {
if (!animationId) {
drawWave();
}
} catch (e) {
console.error('启动可视化失败:', e);
// 即使可视化失败,也要确保音乐能正常播放
}
}
// 停止可视化
function stopVisualization() {
try {
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
// 清除画布
if (canvasCtx) {
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
}
} catch (e) {
console.error('停止可视化失败:', e);
}
}
// 初始化
setVolume(80);
initAudioVisualizer();
// 页面加载完成后的欢迎信息
window.addEventListener('load', () => {
setTimeout(() => {
showNotification('欢迎使用云音乐播放器!', 'success');
}, 1000);
});
// 切换标签页
function switchTab(tabName) {
// 移除所有标签页的active类
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.remove('active');
});
// 激活选中的标签页
document.getElementById(tabName + 'Tab').classList.add('active');
// 激活当前点击的按钮
event.currentTarget.classList.add('active');
}
// 解析网易云歌单
async function parsePlaylist() {
const playlistId = document.getElementById('playlistIdInput').value.trim();
if (!playlistId) {
showNotification('请输入歌单ID', 'warning');
return;
}
const resultsContainer = document.getElementById('playlistResults');
resultsContainer.innerHTML = `
<div class="loading">
<i class="fas fa-spinner"></i>
<div>正在解析歌单...</div>
</div>
`;
try {
const response = await fetch(`${API_BASE}?types=playlist&id=${playlistId}&source=netease`);
const data = await response.json();
let songs = [];
// 操,这个API返回的数据结构真他妈乱,得兼容好几种
if (data && data.playlist && data.playlist.tracks) {
songs = data.playlist.tracks.map(track => ({
name: track.name,
artist: track.ar.map(a => a.name).join(' / '),
album: track.al.name,
id: track.id,
pic_id: track.al.pic_id_str || track.al.pic_str || track.al.pic,
lyric_id: track.id,
source: 'netease'
}));
} else if (data && data.tracks) {
songs = data.tracks.map(track => ({
name: track.name,
artist: track.ar.map(a => a.name).join(' / '),
album: track.al.name,
id: track.id,
pic_id: track.al.pic_id_str || track.al.pic_str || track.al.pic,
lyric_id: track.id,
source: 'netease'
}));
}
if (songs.length > 0) {
playlistData = songs;
displaySearchResults(songs, 'playlistResults', playlistData);
showNotification(`成功加载 ${songs.length} 首歌曲`, 'success');
} else {
resultsContainer.innerHTML = `
<div class="error">
<i class="fas fa-exclamation-triangle"></i>
<div>解析歌单失败,请检查ID是否正确或API是否正常</div>
</div>
`;
}
} catch (error) {
console.error('解析歌单失败:', error);
resultsContainer.innerHTML = `
<div class="error">
<i class="fas fa-wifi"></i>
<div>网络连接失败,请检查网络后重试</div>
</div>
`;
}
}
</script>
</body>
</html>