网站首页 > 知识剖析 正文
PS:音量可以鼠标点击按住在音量图标边的轮盘上下拖拽滑动音量大小
中心按钮可以更改播放器为白色
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>iPod Classic 音乐播放器</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-color: #333;
--secondary-color: #555;
--text-color: #fff;
--light-text: #ccc;
--highlight-color: #007AFF;
--wheel-color: #1a1a1a;
--screen-color: #000;
--body-gradient-start: #2c3e50;
--body-gradient-end: #1a1a1a;
--ipod-gradient-start: #1a1a1a;
--ipod-gradient-end: #000;
--sidebar-gradient-start: rgba(0,0,0,0.9);
--sidebar-gradient-end: rgba(30,30,30,0.9);
--progress-bg: #555;
}
.light-theme {
--primary-color: #f0f0f0;
--secondary-color: #d0d0d0;
--text-color: #222;
--light-text: #666;
--highlight-color: #0066cc;
--wheel-color: #e0e0e0;
--screen-color: #f5f5f5;
--body-gradient-start: #e0e0e0;
--body-gradient-end: #c0c0c0;
--ipod-gradient-start: #f5f5f5;
--ipod-gradient-end: #e0e0e0;
--sidebar-gradient-start: rgba(245,245,245,0.95);
--sidebar-gradient-end: rgba(230,230,230,0.95);
--progress-bg: #d0d0d0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, rgba(44, 62, 80, 0.1), rgba(26, 26, 26, 0.1));
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
color: var(--text-color);
overflow: hidden;
transition: all 0.5s ease;
padding: 16px;
backdrop-filter: blur(3px);
background-blend-mode: overlay;
}
.ipod-container {
width: 100%;
max-width: 384px;
height: 660px;
max-height: 90vh;
background: linear-gradient(to bottom, var(--ipod-gradient-start), var(--ipod-gradient-end));
border-radius: 48px;
box-shadow: 0 24px 60px rgba(0,0,0,0.5),
inset 0 0 24px rgba(255,255,255,0.05);
position: relative;
overflow: hidden;
transition: all 0.5s ease;
}
.screen {
width: 90%;
height: 40%;
max-height: 288px;
background: var(--screen-color);
margin: 7% auto 5%;
border-radius: 6px;
padding: 16px;
position: relative;
overflow: hidden;
box-shadow: inset 0 0 12px rgba(0,0,0,0.1);
transition: background 0.5s ease;
}
.now-playing {
display: flex;
flex-direction: column;
height: 100%;
cursor: pointer;
position: relative;
}
.album-art {
width: 40%;
max-width: 144px;
aspect-ratio: 1/1;
margin: 0 auto 12px;
background: var(--wheel-color);
border-radius: 6px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
transition: background 0.5s ease;
box-shadow: 0 6px 18px rgba(0,0,0,0.2);
}
.album-art img {
width: 100%;
height: 100%;
object-fit: cover;
}
.song-info {
text-align: center;
margin-bottom: 12px;
}
.song-title {
font-size: clamp(18px, 4vw, 22px);
font-weight: bold;
margin-bottom: 6px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.artist {
font-size: clamp(14px, 3vw, 17px);
color: var(--light-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.progress-container {
width: 100%;
height: 5px;
background: var(--progress-bg);
border-radius: 3px;
margin: 12px 0;
cursor: pointer;
position: relative;
}
.progress-bar {
height: 100%;
background: var(--highlight-color);
border-radius: 3px;
width: 0%;
transition: width 0.1s linear;
}
.progress-handle {
position: absolute;
top: 50%;
width: 12px;
height: 12px;
background: var(--text-color);
border-radius: 50%;
transform: translate(-50%, -50%);
opacity: 0;
transition: opacity 0.2s;
box-shadow: 0 0 4px rgba(0,0,0,0.5);
}
.progress-container:hover .progress-handle {
opacity: 1;
}
.time-display {
display: flex;
justify-content: space-between;
font-size: clamp(12px, 2.5vw, 14px);
color: var(--light-text);
margin-bottom: 16px;
}
.click-wheel {
width: 70%;
max-width: 264px;
aspect-ratio: 1/1;
background: var(--wheel-color);
border-radius: 50%;
margin: 0 auto;
position: relative;
box-shadow: inset 0 0 24px rgba(0,0,0,0.1);
display: flex;
justify-content: center;
align-items: center;
user-select: none;
transition: background 0.5s ease;
}
.wheel-center {
width: 35%;
max-width: 96px;
aspect-ratio: 1/1;
background: var(--screen-color);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
color: var(--text-color);
font-size: clamp(14px, 3vw, 17px);
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 0 12px rgba(0,0,0,0.1);
}
.wheel-center svg {
width: 36px;
height: 36px;
fill: var(--text-color);
}
.wheel-center:hover svg {
fill: var(--highlight-color);
}
.wheel-center:hover {
background: var(--primary-color);
}
.wheel-btn {
position: absolute;
width: 15%;
max-width: 48px;
aspect-ratio: 1/1;
background: none;
border: none;
color: var(--text-color);
cursor: pointer;
transition: all 0.2s;
display: flex;
justify-content: center;
align-items: center;
}
.wheel-btn svg {
width: 28.8px;
height: 28.8px;
fill: var(--text-color);
transition: fill 0.2s;
}
.wheel-btn:hover svg {
fill: var(--highlight-color);
}
.wheel-btn.active svg {
fill: var(--highlight-color);
}
.menu-btn {
top: 10%;
left: 50%;
transform: translateX(-50%);
}
.prev-btn {
left: 10%;
top: 50%;
transform: translateY(-50%);
}
.next-btn {
right: 10%;
top: 50%;
transform: translateY(-50%);
}
.play-btn {
bottom: 10%;
left: 50%;
transform: translateX(-50%);
}
.sidebar {
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(to right, var(--sidebar-gradient-start), var(--sidebar-gradient-end));
border-radius: 48px 0 0 48px;
padding: 36px 24px;
transition: transform 0.3s ease-in-out;
z-index: 15;
box-shadow: 6px 0 18px rgba(0,0,0,0.2);
}
.sidebar.show {
transform: translateX(100%);
}
.sidebar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding-bottom: 12px;
border-bottom: 1.2px solid var(--secondary-color);
}
.sidebar-title {
font-size: clamp(18px, 4vw, 22px);
font-weight: bold;
}
.search-container {
margin: 16px 0;
position: relative;
}
#search-input {
width: 100%;
padding: 10px 15px;
border-radius: 20px;
border: none;
background: rgba(255, 255, 255, 0.1);
color: var(--text-color);
font-size: 14px;
transition: background 0.3s;
}
#search-input:focus {
outline: none;
background: rgba(255, 255, 255, 0.2);
}
.light-theme #search-input {
background: rgba(0, 0, 0, 0.1);
}
.light-theme #search-input:focus {
background: rgba(0, 0, 0, 0.2);
}
.playlist {
height: calc(100% - 160px);
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.playlist::-webkit-scrollbar {
width: 6px;
}
.playlist::-webkit-scrollbar-track {
background: rgba(0,0,0,0.1);
}
.playlist::-webkit-scrollbar-thumb {
background: var(--highlight-color);
border-radius: 6px;
}
.playlist-item {
padding: 12px;
border-radius: 6px;
margin-bottom: 6px;
cursor: pointer;
transition: background 0.2s;
display: flex;
align-items: center;
gap: 12px;
}
.playlist-item:hover {
background: rgba(0,0,0,0.05);
}
.playlist-item.active {
background: rgba(0,122,255,0.2);
}
.playlist-song {
font-size: clamp(14px, 3vw, 17px);
font-weight: bold;
margin-bottom: 3.6px;
}
.playlist-artist {
font-size: clamp(12px, 2.5vw, 14px);
color: var(--light-text);
}
.file-input-container {
margin-top: 18px;
position: relative;
}
.file-input-label {
display: block;
padding: 9.6px 14.4px;
background: rgba(0,122,255,0.2);
border-radius: 6px;
text-align: center;
font-size: clamp(14px, 3vw, 17px);
cursor: pointer;
transition: background 0.2s;
}
.file-input-label:hover {
background: rgba(0,122,255,0.3);
}
#file-input {
display: none;
}
.lyrics-view {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--screen-color);
padding: 18px;
overflow: hidden;
display: none;
transition: background 0.5s ease;
text-align: center;
cursor: pointer;
z-index: 10;
}
.lyrics-view.show {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.lyrics-container {
width: 100%;
height: 100%;
overflow-y: auto;
padding: 20px 0;
scrollbar-width: none;
-ms-overflow-style: none;
-webkit-overflow-scrolling: touch;
}
.lyrics-container::-webkit-scrollbar {
display: none;
}
.lyrics-line {
margin: 15px 0;
font-size: clamp(16px, 3.5vw, 19px);
color: var(--light-text);
transition: all 0.3s;
padding: 8px 0;
min-height: 30px;
}
.lyrics-line.active {
color: var(--highlight-color);
font-size: clamp(18px, 4vw, 22px);
font-weight: bold;
transform: scale(1.05);
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.3);
display: none;
z-index: 10;
backdrop-filter: blur(3px);
}
.sidebar.show ~ .overlay {
display: block;
}
.volume-display {
position: absolute;
bottom: 18px;
left: 50%;
transform: translateX(-50%);
font-size: clamp(12px, 2.5vw, 14px);
background: rgba(0,0,0,0.7);
padding: 8px 16px;
border-radius: 20px;
display: none;
z-index: 20;
backdrop-filter: blur(5px);
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
.light-theme .volume-display {
background: rgba(255,255,255,0.7);
}
.empty-state {
text-align: center;
padding: 40px 20px;
color: var(--light-text);
}
.empty-state i {
font-size: 48px;
margin-bottom: 16px;
color: var(--highlight-color);
}
.empty-state p {
font-size: clamp(14px, 3vw, 16px);
margin-top: 8px;
}
.song-icon {
width: 36px;
height: 36px;
border-radius: 4px;
background: var(--highlight-color);
display: flex;
align-items: center;
justify-content: center;
}
.song-icon i {
color: white;
font-size: 16px;
}
.now-playing-placeholder {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
text-align: center;
color: var(--light-text);
padding: 0 20px;
}
.now-playing-placeholder i {
font-size: 48px;
margin-bottom: 20px;
color: var(--highlight-color);
}
.now-playing-placeholder p {
font-size: clamp(14px, 3vw, 16px);
line-height: 1.5;
}
/* 状态指示器 - 在now-playing内部 */
.status-indicators {
position: absolute;
bottom: 10px;
left: 0;
width: 100%;
display: flex;
justify-content: center;
gap: 15px;
z-index: 5;
}
.status-indicator {
display: flex;
align-items: center;
background: rgba(0,0,0,0.5);
padding: 4px 10px;
border-radius: 15px;
font-size: 12px;
gap: 5px;
opacity: 0;
transform: translateY(10px);
transition: all 0.3s ease;
}
.status-indicator.show {
opacity: 1;
transform: translateY(0);
}
.light-theme .status-indicator {
background: rgba(255,255,255,0.5);
}
.status-indicator i {
font-size: 14px;
}
.status-indicator.active {
background: rgba(0,122,255,0.5);
}
.status-indicator.active i {
color: var(--highlight-color);
}
/* 优化单曲循环图标显示 */
.single-loop-icon {
transform: scale(0.85);
transform-origin: center;
}
/* 旋转动画 */
@keyframes rotateAlbum {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.playing .album-art img {
animation: rotateAlbum 20s linear infinite;
}
/* 移动端优化 */
@media (max-width: 480px) {
.ipod-container {
height: 580px;
max-height: 85vh;
}
.screen {
height: 35%;
padding: 12px;
}
.click-wheel {
width: 65%;
}
.sidebar {
width: 85%;
}
}
@media (max-width: 380px) {
.ipod-container {
height: 500px;
}
.screen {
height: 32%;
margin: 5% auto 4%;
}
.album-art {
width: 35%;
}
.click-wheel {
width: 60%;
}
}
/* Safari 特定优化 */
@media not all and (min-resolution:.001dpcm) {
@supports (-webkit-appearance:none) {
.progress-container {
height: 6px;
}
.progress-handle {
width: 14px;
height: 14px;
}
.click-wheel {
-webkit-backface-visibility: hidden;
-webkit-transform: translate3d(0,0,0);
}
}
}
</style>
</head>
<body>
<div class="ipod-container">
<div class="volume-display" id="volume-display">音量: 100%</div>
<div class="sidebar">
<div class="sidebar-header">
<div class="sidebar-title">播放列表</div>
</div>
<div class="search-container">
<input type="text" id="search-input" placeholder="搜索歌曲...">
</div>
<div class="playlist" id="playlist">
<div class="empty-state">
<i class="fas fa-music"></i>
<h3>播放列表为空</h3>
<p>添加音乐文件开始播放</p>
</div>
</div>
<div class="file-input-container">
<label for="file-input" class="file-input-label">添加音乐文件</label>
<input type="file" id="file-input" accept="audio/*" multiple>
</div>
</div>
<div class="overlay" id="overlay"></div>
<div class="screen">
<div class="now-playing" id="now-playing">
<div class="status-indicators">
<div class="status-indicator" id="loop-indicator">
<i class="fas fa-repeat"></i>
<span>全部循环</span>
</div>
<div class="status-indicator" id="mute-indicator">
<i class="fas fa-volume-up"></i>
<span>音量开启</span>
</div>
</div>
<div class="now-playing-placeholder" id="now-playing-placeholder">
<i class="fas fa-music"></i>
<p>添加音乐文件开始播放</p>
</div>
<div class="album-art">
<img src="" alt="Album Art" id="album-art-img">
</div>
<div class="song-info">
<div class="song-title" id="song-title"></div>
<div class="artist" id="artist"></div>
</div>
<div class="progress-container" id="progress-container">
<div class="progress-bar" id="progress-bar"></div>
<div class="progress-handle" id="progress-handle"></div>
</div>
<div class="time-display">
<span id="current-time">0:00</span>
<span id="duration">0:00</span>
</div>
</div>
<div class="lyrics-view" id="lyrics-view">
<div class="lyrics-container" id="lyrics-container">
<div class="empty-state">
<i class="fas fa-file-alt"></i>
<h3>暂无歌词</h3>
</div>
</div>
</div>
</div>
<div class="click-wheel">
<button class="wheel-btn menu-btn" id="menu-btn">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path d="M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"/>
</svg>
</button>
<button class="wheel-btn prev-btn" id="wheel-prev-btn">
<svg id="loop-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M500.33 0h-47.41a12 12 0 0 0-12 12.57l4 82.76A247.42 247.42 0 0 0 256 8C119.34 8 7.9 119.53 8 256.19 8.1 393.07 119.1 504 256 504a247.1 247.1 0 0 0 166.18-63.91 12 12 0 0 0 .48-17.43l-34-34a12 12 0 0 0-16.38-.55A176 176 0 1 1 402.1 157.8l-101.53-4.87a12 12 0 0 0-12.57 12v47.41a12 12 0 0 0 12 12h200.33a12 12 0 0 0 12-12V12a12 12 0 0 0-12-12z"/>
</svg>
</button>
<button class="wheel-btn next-btn" id="wheel-next-btn">
<svg id="volume-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<path d="M215.03 71.05L126.06 160H24c-13.26 0-24 10.74-24 24v144c0 13.25 10.74 24 24 24h102.06l88.97 88.95c15.03 15.03 40.97 4.47 40.97-16.97V88.02c0-21.46-25.96-31.98-40.97-16.97zm233.32-51.08c-11.17-7.33-26.18-4.24-33.51 6.95-7.34 11.17-4.22 26.18 6.95 33.51 66.27 43.49 105.82 116.6 105.82 195.58 0 78.98-39.55 152.09-105.82 195.58-11.17 7.32-14.29 22.34-6.95 33.5 7.04 10.71 21.93 14.56 33.51 6.95C528.27 439.58 576 351.33 576 256S528.27 72.43 448.35 19.97zM480 256c0-63.53-32.06-121.94-85.77-156.24-11.19-7.14-26.03-3.82-33.12 7.46s-3.78 26.21 7.41 33.36C408.27 165.97 432 209.11 432 256c0 14.38-7.65 27.7-20.92 34.81-11.61 6.41-15.84 21-9.45 32.61 6.43 11.66 21.05 15.8 32.61 9.45 28.23-15.55 45.77-45 45.77-76.88s-17.54-61.32-45.78-76.86z"/>
</svg>
</button>
<button class="wheel-btn play-btn" id="wheel-play-btn">
<svg id="play-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path d="M424.4 214.7L72.4 6.6C43.8-10.3 0 6.1 0 47.9V464c0 37.5 40.7 60.1 72.4 41.3l352-208c31.4-18.5 31.5-64.1 0-82.6z"/>
</svg>
</button>
<div class="wheel-center" id="wheel-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
<path d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z"/>
</svg>
</div>
</div>
<audio id="audio-player"></audio>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// DOM元素
const audioPlayer = document.getElementById('audio-player');
const wheelPlayBtn = document.getElementById('wheel-play-btn');
const wheelPrevBtn = document.getElementById('wheel-prev-btn');
const wheelNextBtn = document.getElementById('wheel-next-btn');
const menuBtn = document.getElementById('menu-btn');
const wheelCenter = document.getElementById('wheel-center');
const progressBar = document.getElementById('progress-bar');
const progressContainer = document.getElementById('progress-container');
const progressHandle = document.getElementById('progress-handle');
const currentTimeEl = document.getElementById('current-time');
const durationEl = document.getElementById('duration');
const songTitle = document.getElementById('song-title');
const artist = document.getElementById('artist');
const sidebar = document.querySelector('.sidebar');
const fileInput = document.getElementById('file-input');
const playlist = document.getElementById('playlist');
const lyricsView = document.getElementById('lyrics-view');
const lyricsContainer = document.getElementById('lyrics-container');
const nowPlaying = document.getElementById('now-playing');
const nowPlayingPlaceholder = document.getElementById('now-playing-placeholder');
const overlay = document.getElementById('overlay');
const body = document.body;
const playIcon = document.getElementById('play-icon');
const volumeIcon = document.getElementById('volume-icon');
const loopIcon = document.getElementById('loop-icon');
const volumeDisplay = document.getElementById('volume-display');
const albumArtImg = document.getElementById('album-art-img');
const loopIndicator = document.getElementById('loop-indicator');
const muteIndicator = document.getElementById('mute-indicator');
const searchInput = document.getElementById('search-input');
// 播放列表数据
let songs = [];
let currentSongIndex = -1;
let isPlaying = false;
let theme = 'dark';
let loopMode = 'all'; // 'all' or 'single'
let isMuted = false;
let lastVolume = 1;
let isDraggingProgress = false;
// 初始化播放器
function initPlayer() {
renderPlaylist(songs);
updateIndicators();
// 事件监听
wheelPlayBtn.addEventListener('click', togglePlay);
wheelPrevBtn.addEventListener('click', function(e) {
toggleLoopMode(e);
showLoopIndicator();
});
wheelNextBtn.addEventListener('click', function(e) {
toggleMute(e);
showMuteIndicator();
});
menuBtn.addEventListener('click', toggleSidebar);
wheelCenter.addEventListener('click', toggleTheme);
progressContainer.addEventListener('click', setProgress);
audioPlayer.addEventListener('timeupdate', updateProgress);
audioPlayer.addEventListener('ended', nextSong);
audioPlayer.addEventListener('loadedmetadata', updateDuration);
fileInput.addEventListener('change', handleFileSelect);
overlay.addEventListener('click', closeSidebar);
nowPlaying.addEventListener('click', toggleLyricsView);
lyricsView.addEventListener('click', toggleLyricsView);
// 进度条拖动事件
progressContainer.addEventListener('mousedown', startDragProgress);
progressContainer.addEventListener('touchstart', startDragProgressTouch);
document.addEventListener('mousemove', dragProgress);
document.addEventListener('touchmove', dragProgressTouch);
document.addEventListener('mouseup', endDragProgress);
document.addEventListener('touchend', endDragProgress);
// 搜索框功能
searchInput.addEventListener('input', filterPlaylist);
// 触摸轮盘事件
setupWheelControls();
}
// 显示循环模式指示器
function showLoopIndicator() {
loopIndicator.classList.add('show');
setTimeout(() => {
loopIndicator.classList.remove('show');
}, 1500);
}
// 显示静音指示器
function showMuteIndicator() {
muteIndicator.classList.add('show');
setTimeout(() => {
muteIndicator.classList.remove('show');
}, 1500);
}
// 更新状态指示器
function updateIndicators() {
// 更新循环模式指示器
if (loopMode === 'all') {
loopIndicator.innerHTML = '<i class="fas fa-repeat"></i><span>全部循环</span>';
loopIndicator.classList.remove('active');
loopIcon.innerHTML = '<path d="M500.33 0h-47.41a12 12 0 0 0-12 12.57l4 82.76A247.42 247.42 0 0 0 256 8C119.34 8 7.9 119.53 8 256.19 8.1 393.07 119.1 504 256 504a247.1 247.1 0 0 0 166.18-63.91 12 12 0 0 0 .48-17.43l-34-34a12 12 0 0 0-16.38-.55A176 176 0 1 1 402.1 157.8l-101.53-4.87a12 12 0 0 0-12.57 12v47.41a12 12 0 0 0 12 12h200.33a12 12 0 0 0 12-12V12a12 12 0 0 0-12-12z"/>';
} else {
loopIndicator.innerHTML = '<i class="fas fa-repeat single-loop-icon"></i><span>单曲循环</span>';
loopIndicator.classList.add('active');
loopIcon.innerHTML = `
<svg t="1750649017434" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1504" width="520" height="520"><path d="M594.066286 937.472c33.645714 35.108571-21.504 88.868571-59.465143 55.003429l-115.785143-103.570286c-16.164571-14.482286-15.725714-42.715429-0.365714-57.344l114.102857-109.714286c36.644571-35.108571 91.721143 17.188571 60.928 53.394286 189.805714-21.942857 258.121143-230.253714 155.062857-379.904-59.684571-86.162286 78.043429-163.108571 136.996571-77.677714 179.712 260.169143 10.24 586.752-291.474285 619.812571z m-436.662857-231.350857c-179.638857-260.169143-10.313143-586.459429 291.254857-619.666286-33.572571-35.035429 21.577143-88.868571 59.465143-55.003428L623.908571 135.021714c16.091429 14.336 15.798857 42.569143 0.438858 57.344L510.171429 301.933714c-36.498286 35.108571-91.574857-17.188571-60.854858-53.394285-189.732571 21.942857-258.048 230.326857-154.843428 379.904 59.538286 86.016-78.116571 163.108571-136.996572 77.677714z" p-id="1505"></path></svg>
`;
}
// 更新静音指示器
if (isMuted) {
muteIndicator.innerHTML = '<i class="fas fa-volume-mute"></i><span>静音中</span>';
muteIndicator.classList.add('active');
volumeIcon.innerHTML = '<path d="M215.03 71.05L126.06 160H24c-13.26 0-24 10.74-24 24v144c0 13.25 10.74 24 24 24h102.06l88.97 88.95c15.03 15.03 40.97 4.47 40.97-16.97V88.02c0-21.46-25.96-31.98-40.97-16.97zm"/>';
} else {
muteIndicator.innerHTML = '<i class="fas fa-volume-up"></i><span>音量开启</span>';
muteIndicator.classList.remove('active');
volumeIcon.innerHTML = '<path d="M215.03 71.05L126.06 160H24c-13.26 0-24 10.74-24 24v144c0 13.25 10.74 24 24 24h102.06l88.97 88.95c15.03 15.03 40.97 4.47 40.97-16.97V88.02c0-21.46-25.96-31.98-40.97-16.97zm233.32-51.08c-11.17-7.33-26.18-4.24-33.51 6.95-7.34 11.17-4.22 26.18 6.95 33.51 66.27 43.49 105.82 116.6 105.82 195.58 0 78.98-39.55 152.09-105.82 195.58-11.17 7.32-14.29 22.34-6.95 33.5 7.04 10.71 21.93 14.56 33.51 6.95C528.27 439.58 576 351.33 576 256S528.27 72.43 448.35 19.97zM480 256c0-63.53-32.06-121.94-85.77-156.24-11.19-7.14-26.03-3.82-33.12 7.46s-3.78 26.21 7.41 33.36C408.27 165.97 432 209.11 432 256c0 14.38-7.65 27.7-20.92 34.81-11.61 6.41-15.84 21-9.45 32.61 6.43 11.66 21.05 15.8 32.61 9.45 28.23-15.55 45.77-45 45.77-76.88s-17.54-61.32-45.78-76.86z"/>';
}
}
// 更新播放器信息
function updatePlayerInfo() {
if (currentSongIndex === -1 || songs.length === 0) {
// 没有歌曲
nowPlayingPlaceholder.style.display = 'flex';
songTitle.textContent = '';
artist.textContent = '';
albumArtImg.src = '';
albumArtImg.style.display = 'none';
progressContainer.style.display = 'none';
return;
}
const song = songs[currentSongIndex];
songTitle.textContent = song.title;
artist.textContent = song.artist;
// 显示专辑封面
albumArtImg.src = song.art || 'https://images.unsplash.com/photo-1470225620780-dba8ba36b745?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=600&q=80';
albumArtImg.style.display = 'block';
nowPlayingPlaceholder.style.display = 'none';
progressContainer.style.display = 'block';
// 更新播放列表高亮
const playlistItems = playlist.querySelectorAll('.playlist-item');
playlistItems.forEach((item, index) => {
if (index === currentSongIndex) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
});
// 更新歌词
updateLyrics();
}
// 播放/暂停切换
function togglePlay() {
if (songs.length === 0 || currentSongIndex === -1) return;
if (isPlaying) {
pauseSong();
} else {
playSong();
}
}
// 播放歌曲
function playSong() {
if (songs.length === 0 || currentSongIndex === -1) return;
audioPlayer.src = songs[currentSongIndex].src;
audioPlayer.play().catch(error => {
console.error('播放错误:', error);
alert('无法播放音频,请确保文件格式受支持');
});
isPlaying = true;
updatePlayIcon();
document.querySelector('.ipod-container').classList.add('playing');
}
// 暂停歌曲
function pauseSong() {
audioPlayer.pause();
isPlaying = false;
updatePlayIcon();
document.querySelector('.ipod-container').classList.remove('playing');
}
// 更新播放图标
function updatePlayIcon() {
if (isPlaying) {
playIcon.innerHTML = '<path d="M144 479H48c-26.5 0-48-21.5-48-48V79c0-26.5 21.5-48 48-48h96c26.5 0 48 21.5 48 48v352c0 26.5-21.5 48-48 48zm304-48V79c0-26.5-21.5-48-48-48h-96c-26.5 0-48 21.5-48 48v352c0 26.5 21.5 48 48 48h96c26.5 0 48-21.5 48-48z"/>';
} else {
playIcon.innerHTML = '<path d="M424.4 214.7L72.4 6.6C43.8-10.3 0 6.1 0 47.9V464c0 37.5 40.7 60.1 72.4 41.3l352-208c31.4-18.5 31.5-64.1 0-82.6z"/>';
}
}
// 切换循环模式
function toggleLoopMode(e) {
e.stopPropagation(); // 防止事件冒泡
if (loopMode === 'all') {
loopMode = 'single';
} else {
loopMode = 'all';
}
updateIndicators();
}
// 切换静音
function toggleMute(e) {
e.stopPropagation(); // 防止事件冒泡
if (isMuted) {
// 取消静音
audioPlayer.muted = false;
audioPlayer.volume = lastVolume;
isMuted = false;
} else {
// 静音
lastVolume = audioPlayer.volume;
audioPlayer.muted = true;
isMuted = true;
}
updateIndicators();
}
// 上一首
function prevSong() {
if (songs.length === 0 || currentSongIndex === -1) return;
if (loopMode === 'single') {
audioPlayer.currentTime = 0;
playSong();
return;
}
currentSongIndex--;
if (currentSongIndex < 0) {
currentSongIndex = songs.length - 1;
}
updatePlayerInfo();
playSong();
}
// 下一首
function nextSong() {
if (songs.length === 0 || currentSongIndex === -1) return;
if (loopMode === 'single') {
audioPlayer.currentTime = 0;
playSong();
return;
}
currentSongIndex++;
if (currentSongIndex >= songs.length) {
currentSongIndex = 0;
}
updatePlayerInfo();
playSong();
}
// 更新进度条
function updateProgress() {
const { currentTime, duration } = audioPlayer;
const progressPercent = (currentTime / duration) * 100;
progressBar.style.width = `${progressPercent}%`;
progressHandle.style.left = `${progressPercent}%`;
// 更新时间显示
currentTimeEl.textContent = formatTime(currentTime);
// 更新歌词高亮
updateLyricsHighlight(currentTime);
}
// 设置进度
function setProgress(e) {
const width = this.clientWidth;
const clickX = e.offsetX;
const duration = audioPlayer.duration;
audioPlayer.currentTime = (clickX / width) * duration;
}
// 开始拖动进度条 (鼠标)
function startDragProgress(e) {
isDraggingProgress = true;
const width = progressContainer.clientWidth;
const clickX = e.offsetX;
const duration = audioPlayer.duration;
audioPlayer.currentTime = (clickX / width) * duration;
progressHandle.style.opacity = 1;
}
// 开始拖动进度条 (触摸)
function startDragProgressTouch(e) {
isDraggingProgress = true;
const rect = progressContainer.getBoundingClientRect();
const touch = e.touches[0];
const clickX = touch.clientX - rect.left;
const width = rect.width;
const duration = audioPlayer.duration;
audioPlayer.currentTime = (clickX / width) * duration;
progressHandle.style.opacity = 1;
}
// 拖动进度条 (鼠标)
function dragProgress(e) {
if (!isDraggingProgress) return;
const rect = progressContainer.getBoundingClientRect();
let clickX = e.clientX - rect.left;
clickX = Math.max(0, Math.min(rect.width, clickX));
const progressPercent = (clickX / rect.width) * 100;
progressBar.style.width = `${progressPercent}%`;
progressHandle.style.left = `${progressPercent}%`;
const duration = audioPlayer.duration;
audioPlayer.currentTime = (clickX / rect.width) * duration;
}
// 拖动进度条 (触摸)
function dragProgressTouch(e) {
if (!isDraggingProgress) return;
e.preventDefault();
const rect = progressContainer.getBoundingClientRect();
const touch = e.touches[0];
let clickX = touch.clientX - rect.left;
clickX = Math.max(0, Math.min(rect.width, clickX));
const progressPercent = (clickX / rect.width) * 100;
progressBar.style.width = `${progressPercent}%`;
progressHandle.style.left = `${progressPercent}%`;
const duration = audioPlayer.duration;
audioPlayer.currentTime = (clickX / rect.width) * duration;
}
// 结束拖动
function endDragProgress() {
isDraggingProgress = false;
progressHandle.style.opacity = '';
}
// 更新总时长
function updateDuration() {
durationEl.textContent = formatTime(audioPlayer.duration);
}
// 格式化时间
function formatTime(seconds) {
if (isNaN(seconds)) return "0:00";
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
}
// 切换侧边栏
function toggleSidebar() {
sidebar.classList.add('show');
overlay.style.display = 'block';
}
// 关闭侧边栏
function closeSidebar() {
sidebar.classList.remove('show');
overlay.style.display = 'none';
}
// 设置覆盖层点击事件
overlay.addEventListener('click', function(e) {
if (e.target === overlay) {
closeSidebar();
}
});
// 处理文件选择
function handleFileSelect(e) {
const files = e.target.files;
// 移除空状态
const emptyState = playlist.querySelector('.empty-state');
if (emptyState) {
emptyState.remove();
}
for (let i = 0; i < files.length; i++) {
const file = files[i];
if (!file.type.match('audio.*')) continue;
const song = {
title: file.name.replace(/\.[^/.]+$/, ""),
artist: "未知艺术家",
src: URL.createObjectURL(file),
art: "https://images.unsplash.com/photo-1470225620780-dba8ba36b745?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=600&q=80",
lyrics: [
{time: 0, text: file.name.replace(/\.[^/.]+$/, "")},
{time: 5, text: "暂无歌词"}
]
};
songs.push(song);
// 如果是第一首歌曲,设置为当前播放
if (songs.length === 1) {
currentSongIndex = 0;
updatePlayerInfo();
}
// 添加到播放列表
const playlistItem = document.createElement('div');
playlistItem.className = 'playlist-item';
playlistItem.innerHTML = `
<div class="song-icon">
<i class="fas fa-music"></i>
</div>
<div>
<div class="playlist-song">${song.title}</div>
<div class="playlist-artist">${song.artist}</div>
</div>
`;
playlistItem.addEventListener('click', () => {
currentSongIndex = songs.indexOf(song);
updatePlayerInfo();
playSong();
closeSidebar();
});
playlist.appendChild(playlistItem);
}
// 重置文件输入,允许重复选择相同文件
fileInput.value = '';
renderPlaylist(songs);
}
// 切换歌词视图
function toggleLyricsView() {
if (songs.length === 0 || currentSongIndex === -1) return;
lyricsView.classList.toggle('show');
nowPlaying.style.display = lyricsView.classList.contains('show') ? 'none' : 'flex';
updateLyricsHighlight(audioPlayer.currentTime);
}
// 更新歌词
function updateLyrics() {
const lyricsContainer = document.getElementById('lyrics-container');
const song = songs[currentSongIndex];
if (song.lyrics && song.lyrics.length > 0) {
lyricsContainer.innerHTML = '';
song.lyrics.forEach(line => {
const lyricLine = document.createElement('div');
lyricLine.className = 'lyrics-line';
lyricLine.textContent = line.text;
lyricLine.dataset.time = line.time;
lyricsContainer.appendChild(lyricLine);
});
} else {
lyricsContainer.innerHTML = '<div class="empty-state"><i class="fas fa-file-alt"></i><h3>暂无歌词</h3></div>';
}
}
// 更新歌词高亮
function updateLyricsHighlight(currentTime) {
const lyricsLines = document.querySelectorAll('.lyrics-line');
const song = songs[currentSongIndex];
if (!song || !song.lyrics || lyricsLines.length === 0) return;
let activeLine = null;
for (let i = 0; i < song.lyrics.length; i++) {
if (currentTime >= song.lyrics[i].time) {
activeLine = i;
} else {
break;
}
}
lyricsLines.forEach((line, index) => {
if (index === activeLine) {
line.classList.add('active');
// 滚动到可视区域
if (lyricsView.classList.contains('show')) {
line.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
} else {
line.classList.remove('active');
}
});
}
// 切换主题
function toggleTheme() {
if (theme === 'dark') {
body.classList.add('light-theme');
theme = 'light';
} else {
body.classList.remove('light-theme');
theme = 'dark';
}
}
// 设置轮盘控制
function setupWheelControls() {
const wheel = document.querySelector('.click-wheel');
let startAngle = 0;
let currentAngle = 0;
let rotation = 0;
let isRotating = false;
let volumeTimeout;
wheel.addEventListener('mousedown', startRotation);
wheel.addEventListener('touchstart', startRotation);
document.addEventListener('mousemove', rotate);
document.addEventListener('touchmove', rotate);
document.addEventListener('mouseup', endRotation);
document.addEventListener('touchend', endRotation);
function startRotation(e) {
e.preventDefault();
const rect = wheel.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
let clientX, clientY;
if (e.type === 'touchstart') {
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
} else {
clientX = e.clientX;
clientY = e.clientY;
}
startAngle = Math.atan2(clientY - centerY, clientX - centerX) * 180 / Math.PI;
isRotating = true;
}
function rotate(e) {
if (!isRotating) return;
e.preventDefault();
const rect = wheel.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
let clientX, clientY;
if (e.type === 'touchmove') {
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
} else {
clientX = e.clientX;
clientY = e.clientY;
}
currentAngle = Math.atan2(clientY - centerY, clientX - centerX) * 180 / Math.PI;
rotation = currentAngle - startAngle;
// 根据旋转角度调整音量
if (Math.abs(rotation) > 5) {
const volumeChange = rotation > 0 ? 0.05 : -0.05;
let newVolume = audioPlayer.volume + volumeChange;
newVolume = Math.max(0, Math.min(1, newVolume));
audioPlayer.volume = newVolume;
// 更新音量显示
volumeDisplay.textContent = `音量: ${Math.round(newVolume * 100)}%`;
volumeDisplay.style.display = 'block';
// 清除之前的超时
if (volumeTimeout) clearTimeout(volumeTimeout);
// 设置超时隐藏音量显示
volumeTimeout = setTimeout(() => {
volumeDisplay.style.display = 'none';
}, 2000);
startAngle = currentAngle;
}
}
function endRotation() {
isRotating = false;
}
}
// 渲染播放列表
function renderPlaylist(list) {
const playlist = document.getElementById('playlist');
// 清空播放列表
playlist.innerHTML = '';
// 如果列表为空,显示空状态
if (list.length === 0) {
playlist.innerHTML = `
<div class="empty-state">
<i class="fas fa-music"></i>
<h3>播放列表为空</h3>
<p>添加音乐文件开始播放</p>
</div>
`;
return;
}
// 添加歌曲到播放列表
list.forEach(song => {
const playlistItem = document.createElement('div');
playlistItem.className = 'playlist-item';
playlistItem.innerHTML = `
<div class="song-icon">
<i class="fas fa-music"></i>
</div>
<div>
<div class="playlist-song">${song.title}</div>
<div class="playlist-artist">${song.artist}</div>
</div>
`;
playlistItem.addEventListener('click', () => {
currentSongIndex = songs.indexOf(song);
updatePlayerInfo();
playSong();
closeSidebar();
});
playlist.appendChild(playlistItem);
});
}
// 搜索
function filterPlaylist() {
const searchTerm = searchInput.value.toLowerCase().trim();
if (!searchTerm) {
renderPlaylist(songs);
return;
}
// 过滤匹配的歌曲
const filteredSongs = songs.filter(song =>
song.title.toLowerCase().includes(searchTerm) ||
song.artist.toLowerCase().includes(searchTerm)
);
// 渲染过滤后的播放列表
renderPlaylist(filteredSongs);
}
// 初始化播放器
initPlayer();
});
</script>
</body>
</html>
猜你喜欢
- 2025-06-28 组合SISTAR公开新专 性感热曲引期待
- 2025-06-28 换新手机最烦传文件,尤其是苹果和安卓机,你该学学这招
- 2025-06-28 原创自研uniapp+vite5+pinia2手机版后台OA系统
- 2025-06-28 「龙腾网」心理探秘:为什么悲伤的歌曲会让我们感觉良好?
- 2025-06-28 小程序echarts和分包使用(微信小程序echarts降低层级)
- 2025-06-28 一块铝合金的非凡「铝生」:认识 Magic Trackpad
- 2025-06-28 SISTAR多顺自曝与名模柯尔摩亲密照
- 2025-06-28 在小程序里实现一个知乎浮动按钮(知乎小程序怎么用)
- 2025-06-28 「按键教程」若干坐标点依次模拟人工滑动
- 2025-06-28 事件——《JS高级程序设计》(js高级程序设计怎么样)
- 最近发表
- 标签列表
-
- xml (46)
- css animation (57)
- array_slice (60)
- htmlspecialchars (54)
- position: absolute (54)
- datediff函数 (47)
- array_pop (49)
- jsmap (52)
- toggleclass (43)
- console.time (63)
- .sql (41)
- ahref (40)
- js json.parse (59)
- html复选框 (60)
- css 透明 (44)
- css 颜色 (47)
- php replace (41)
- css nth-child (48)
- min-height (40)
- xml schema (44)
- css 最后一个元素 (46)
- location.origin (44)
- table border (49)
- html tr (40)
- video controls (49)