当前位置: 首页 > news >正文

一个简单的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><!-- 引入 Tailwind CSS --><script src="https://cdn.tailwindcss.com"></script><!-- 引入 Font Awesome --><link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"><!-- 配置 Tailwind 自定义颜色和字体 --><script>tailwind.config = {theme: {extend: {colors: {primary: '#6366F1', // 主色调:靛蓝色secondary: '#EC4899', // 辅助色:粉红色dark: '#1E1B4B', // 深色背景light: '#EEF2FF', // 浅色文本},fontFamily: {inter: ['Inter', 'system-ui', 'sans-serif'],},},}}</script><style type="text/tailwindcss">@layer utilities {.content-auto {content-visibility: auto;}.backdrop-blur {backdrop-filter: blur(8px);}.text-shadow {text-shadow: 0 2px 4px rgba(0,0,0,0.1);}.player-gradient {background: linear-gradient(135deg, #1E1B4B 0%, #3B3B98 100%);}.album-rotate {animation: rotate 20s linear infinite;}@keyframes rotate {from { transform: rotate(0deg); }to { transform: rotate(360deg); }}.progress-thumb {transform: scale(0);transition: transform 0.2s ease;}.progress-bar:hover .progress-thumb {transform: scale(1);}.fade-in {animation: fadeIn 0.3s ease-in-out;}@keyframes fadeIn {from { opacity: 0; }to { opacity: 1; }}.pulse {animation: pulse 1.5s infinite;}@keyframes pulse {0% { transform: scale(1); }50% { transform: scale(1.05); }100% { transform: scale(1); }}.mode-badge {transform-origin: center;animation: popIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);}@keyframes popIn {0% { transform: scale(0); }70% { transform: scale(1.2); }100% { transform: scale(1); }}.repeat-highlight {animation: highlight 2s infinite;}@keyframes highlight {0%, 100% { box-shadow: 0 0 0 0 rgba(236, 72, 153, 0.6); }50% { box-shadow: 0 0 0 6px rgba(236, 72, 153, 0.2); }}.single-repeat-indicator {position: relative;}.single-repeat-indicator::after {content: "1";position: absolute;bottom: -2px;right: -2px;width: 14px;height: 14px;background-color: #EC4899;color: white;border-radius: 50%;font-size: 8px;display: flex;align-items: center;justify-content: center;animation: pulse 1.5s infinite;}.track-highlight {background: linear-gradient(90deg, rgba(236, 72, 153, 0.1) 0%, rgba(236, 72, 153, 0) 100%);}.auto-import-notification {position: fixed;top: 20px;right: 20px;z-index: 1000;animation: slideIn 0.3s ease-out;}@keyframes slideIn {from { transform: translateX(100%); opacity: 0; }to { transform: translateX(0); opacity: 1; }}}</style>
</head><body class="font-inter bg-gray-900 text-light min-h-screen flex flex-col"><!-- 顶部导航栏 --><header class="fixed top-0 left-0 right-0 z-50 bg-dark/80 backdrop-blur transition-all duration-300"><div class="container mx-auto px-4 py-3 flex items-center justify-between"><div class="flex items-center space-x-2"><i class="fa fa-music text-primary text-2xl"></i><h1 class="text-xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">旋律</h1></div><div class="hidden md:flex items-center space-x-6"><a href="#" class="hover:text-primary transition-colors">发现</a><a href="#" class="hover:text-primary transition-colors">我的音乐</a><a href="#" class="hover:text-primary transition-colors">电台</a><a href="#" class="hover:text-primary transition-colors">排行榜</a></div><div class="flex items-center space-x-4"><button class="hover:text-primary transition-colors"><i class="fa fa-search text-lg"></i></button><button class="hover:text-primary transition-colors md:hidden"><i class="fa fa-bars text-lg"></i></button><div class="hidden md:block w-8 h-8 rounded-full bg-primary/20 flex items-center justify-center"><i class="fa fa-user text-primary"></i></div></div></div></header><!-- 主内容区 --><main class="flex-grow pt-16 flex flex-col md:flex-row"><!-- 左侧播放列表 --><aside class="w-full md:w-80 bg-dark/50 border-r border-gray-800 p-4 overflow-y-auto max-h-[calc(100vh-160px)] md:max-h-[calc(100vh-64px)]"><div class="flex items-center justify-between mb-4"><h2 class="text-lg font-semibold">播放列表</h2><div class="flex space-x-2"><!-- 导入音乐按钮 --><label for="import-music" class="text-gray-400 hover:text-primary transition-colors cursor-pointer"><i class="fa fa-upload"></i></label><input type="file" id="import-music" accept="audio/*" multiple style="display: none"><button class="text-gray-400 hover:text-primary transition-colors"><i class="fa fa-plus"></i></button></div></div><!-- 播放模式提示 --><div id="mode-indicator" class="mb-4 p-2 bg-primary/10 rounded-lg text-xs text-gray-300 hidden fade-in flex items-center"><i id="mode-icon" class="fa fa-random mr-2 text-primary"></i><span id="mode-text">随机播放已开启</span></div><!-- 导入提示 --><div id="import-hint" class="mb-4 p-3 bg-primary/10 rounded-lg text-sm text-gray-300 hidden fade-in"><i class="fa fa-info-circle mr-2 text-primary"></i><span>支持导入 MP3, WAV, OGG 等音频格式</span></div><!-- 导入成功提示 --><div id="import-success" class="mb-4 p-3 bg-green-500/20 rounded-lg text-sm text-green-300 hidden fade-in"><i class="fa fa-check-circle mr-2 text-green-400"></i><span id="import-success-text">成功导入 2 首音乐</span></div><!-- 单曲循环提示 --><div id="single-repeat-hint" class="mb-4 p-3 bg-secondary/10 rounded-lg text-sm text-secondary hidden fade-in"><i class="fa fa-info-circle mr-2"></i><span>单曲循环已开启,当前歌曲将重复播放</span></div><ul id="playlist" class="space-y-2"><!-- 播放列表项将通过JS动态生成 --></ul></aside><!-- 右侧主播放区 --><section class="flex-grow flex flex-col items-center justify-center p-6 md:p-12"><!-- 专辑封面 --><div class="relative mb-8 group"><div id="album-container" class="w-64 h-64 md:w-80 md:h-80 rounded-full overflow-hidden border-4 border-primary/30 shadow-lg shadow-primary/20"><img id="album-cover" src="https://picsum.photos/id/1025/400/400" alt="专辑封面" class="w-full h-full object-cover album-rotate"></div><!-- 重复模式指示器 (在专辑封面上方) --><div id="repeat-overlay" class="absolute -top-3 -right-3 bg-dark/80 backdrop-blur px-2 py-1 rounded-full text-xs hidden items-center space-x-1 mode-badge"><i id="repeat-overlay-icon" class="fa fa-repeat text-primary"></i><span id="repeat-overlay-text">列表循环</span></div><div class="absolute inset-0 rounded-full bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center"><button id="play-pause-btn" class="w-16 h-16 rounded-full bg-primary flex items-center justify-center shadow-lg transform scale-90 group-hover:scale-100 transition-transform"><i id="play-icon" class="fa fa-pause text-white text-2xl"></i></button></div></div><!-- 歌曲信息 --><div class="text-center mb-8"><h2 id="song-title" class="text-2xl md:text-3xl font-bold mb-2 text-shadow">未播放歌曲</h2><p id="song-artist" class="text-gray-400">艺术家</p><!-- 单曲循环标签 --><div id="single-repeat-label" class="mt-2 inline-block px-3 py-0.5 bg-secondary/20 text-secondary text-xs rounded-full hidden fade-in"><i class="fa fa-repeat mr-1"></i>单曲循环中</div></div><!-- 进度条 --><div class="w-full max-w-2xl mb-2 px-2"><div class="flex justify-between text-xs text-gray-500 mb-1"><span id="current-time">00:00</span><span id="total-time">00:00</span></div><div class="progress-bar relative h-1 bg-gray-700 rounded-full cursor-pointer group"><div id="progress" class="absolute left-0 top-0 h-full bg-gradient-to-r from-primary to-secondary rounded-full w-0"></div><div class="progress-thumb absolute h-3 w-3 bg-white rounded-full -mt-1 group-hover:ring-4 group-hover:ring-primary/50"></div></div></div><!-- 控制按钮 --><div class="flex items-center justify-center space-x-6 md:space-x-10 mt-6"><button id="shuffle-btn" class="text-gray-400 hover:text-primary transition-colors relative"><i class="fa fa-random text-xl"></i><span id="shuffle-indicator" class="absolute -top-2 -right-2 w-4 h-4 bg-primary rounded-full text-[10px] flex items-center justify-center hidden mode-badge"><i class="fa fa-check"></i></span></button><button id="prev-btn" class="text-gray-300 hover:text-primary transition-colors"><i class="fa fa-step-backward text-2xl"></i></button><button id="big-play-pause-btn" class="w-14 h-14 md:w-16 md:h-16 rounded-full bg-gradient-to-r from-primary to-secondary flex items-center justify-center shadow-lg shadow-primary/20 hover:scale-105 transition-transform"><i id="big-play-icon" class="fa fa-pause text-white text-2xl md:text-3xl"></i></button><button id="next-btn" class="text-gray-300 hover:text-primary transition-colors"><i class="fa fa-step-forward text-2xl"></i></button><button id="repeat-btn" class="text-gray-400 hover:text-primary transition-colors relative group"><i id="repeat-btn-icon" class="fa fa-repeat text-xl"></i><!-- 重复模式提示气泡 --><div class="absolute bottom-full right-0 mb-2 bg-dark/90 backdrop-blur rounded px-3 py-1.5 text-xs opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 pointer-events-none whitespace-nowrap"><span id="repeat-tooltip">点击切换重复模式</span></div><span id="repeat-indicator" class="absolute -top-2 -right-2 w-4 h-4 bg-secondary rounded-full text-[10px] flex items-center justify-center hidden mode-badge">1</span></button></div><!-- 音量控制 --><div class="w-full max-w-md mt-8 px-2"><div class="flex items-center space-x-3"><i class="fa fa-volume-up text-gray-400"></i><div class="flex-grow h-1 bg-gray-700 rounded-full relative cursor-pointer group"><div id="volume-level" class="absolute left-0 top-0 h-full bg-gray-400 rounded-full w-3/4"></div><div class="progress-thumb absolute h-3 w-3 bg-white rounded-full -mt-1 group-hover:ring-4 group-hover:ring-gray-400/50" style="left: 75%"></div></div></div></div></section></main><!-- 底部播放控制栏 (仅在移动设备上显示) --><div class="md:hidden fixed bottom-0 left-0 right-0 bg-dark/90 backdrop-blur border-t border-gray-800 p-3"><div class="flex items-center justify-between"><div class="flex items-center space-x-3"><img src="https://picsum.photos/id/1025/60/60" alt="当前播放歌曲封面" class="w-12 h-12 rounded shadow"><div><h4 class="text-sm font-medium truncate w-24">未播放歌曲</h4><p class="text-xs text-gray-400 truncate w-24">艺术家</p></div></div><div class="flex items-center space-x-4"><button class="text-gray-300 hover:text-primary transition-colors relative"><i class="fa fa-random"></i><span class="absolute -top-1 -right-1 w-3 h-3 bg-primary rounded-full text-[8px] flex items-center justify-center hidden mode-badge"><i class="fa fa-check"></i></span></button><button class="text-gray-300 hover:text-primary transition-colors"><i class="fa fa-step-backward"></i></button><button class="w-10 h-10 rounded-full bg-primary flex items-center justify-center"><i class="fa fa-pause text-white"></i></button><button class="text-gray-300 hover:text-primary transition-colors"><i class="fa fa-step-forward"></i></button><button class="text-gray-300 hover:text-primary transition-colors relative"><i class="fa fa-repeat"></i><span class="absolute -top-1 -right-1 w-3 h-3 bg-secondary rounded-full text-[8px] flex items-center justify-center hidden mode-badge">1</span></button></div></div></div><!-- 音频元素 --><audio id="audio-player" preload="metadata">您的浏览器不支持音频播放</audio><!-- 用于读取本地文件系统的隐藏输入 --><input type="file" id="directory-reader" webkitdirectory directory multiple style="display: none"><script>// 音乐数据 - 包含示例音乐和用户导入的音乐let songs = [{id: 1,title: "星光下的海",artist: "梦境乐队",src: "https://example.com/music/song1.mp3", // 示例URLcover: "https://picsum.photos/id/1025/400/400",duration: "3:45",isLocal: false},{id: 2,title: "城市脉动",artist: "节奏工厂",src: "https://example.com/music/song2.mp3",cover: "https://picsum.photos/id/1039/400/400",duration: "4:12",isLocal: false},{id: 3,title: "微风轻拂",artist: "自然之声",src: "https://example.com/music/song3.mp3",cover: "https://picsum.photos/id/1043/400/400",duration: "2:58",isLocal: false},{id: 4,title: "午夜旋律",artist: "蓝调爵士",src: "https://example.com/music/song4.mp3",cover: "https://picsum.photos/id/1050/400/400",duration: "5:20",isLocal: false},{id: 5,title: "晨曦微光",artist: "宁静钢琴",src: "https://example.com/music/song5.mp3",cover: "https://picsum.photos/id/1054/400/400",duration: "3:30",isLocal: false}];// DOM 元素const audioPlayer = document.getElementById('audio-player');const playPauseBtn = document.getElementById('play-pause-btn');const bigPlayPauseBtn = document.getElementById('big-play-pause-btn');const playIcon = document.getElementById('play-icon');const bigPlayIcon = document.getElementById('big-play-icon');const prevBtn = document.getElementById('prev-btn');const nextBtn = document.getElementById('next-btn');const progressBar = document.querySelector('.progress-bar');const progress = document.getElementById('progress');const progressThumb = document.querySelector('.progress-thumb');const currentTimeEl = document.getElementById('current-time');const totalTimeEl = document.getElementById('total-time');const shuffleBtn = document.getElementById('shuffle-btn');const repeatBtn = document.getElementById('repeat-btn');const shuffleIndicator = document.getElementById('shuffle-indicator');const repeatIndicator = document.getElementById('repeat-indicator');const repeatOverlay = document.getElementById('repeat-overlay');const repeatOverlayIcon = document.getElementById('repeat-overlay-icon');const repeatOverlayText = document.getElementById('repeat-overlay-text');const repeatTooltip = document.getElementById('repeat-tooltip');const repeatBtnIcon = document.getElementById('repeat-btn-icon');const volumeControl = document.querySelector('.flex-grow.relative');const volumeLevel = document.getElementById('volume-level');const volumeThumb = volumeControl.querySelector('.progress-thumb');const albumCover = document.getElementById('album-cover');const albumContainer = document.getElementById('album-container');const songTitle = document.getElementById('song-title');const songArtist = document.getElementById('song-artist');const playlist = document.getElementById('playlist');const importMusicInput = document.getElementById('import-music');const directoryReader = document.getElementById('directory-reader');const importHint = document.getElementById('import-hint');const importSuccess = document.getElementById('import-success');const importSuccessText = document.getElementById('import-success-text');const modeIndicator = document.getElementById('mode-indicator');const modeIcon = document.getElementById('mode-icon');const modeText = document.getElementById('mode-text');const singleRepeatHint = document.getElementById('single-repeat-hint');const singleRepeatLabel = document.getElementById('single-repeat-label');// 播放状态let currentSongIndex = 0;let isPlaying = false;let isShuffle = false;let repeatMode = 0; // 0: 不重复, 1: 单曲循环, 2: 列表循环let isDragging = false;let lastImportHintTime = 0;let playHistory = []; // 记录播放历史let historyIndex = -1; // 历史记录索引let modeIndicatorTimeout; // 用于管理模式提示的超时let repeatEndTimeout; // 用于处理歌曲结束时的延迟let repeatNotificationTimeout; // 单曲循环通知超时// 单曲循环特定变量let singleRepeatCount = 0; // 记录单曲循环次数const SINGLE_REPEAT_ANIMATION_THRESHOLD = 2; // 循环多少次后开始动画提示const SINGLE_REPEAT_NOTIFICATION_DELAY = 10000; // 单曲循环提示显示时间(ms)// 初始化播放列表function initPlaylist() {playlist.innerHTML = '';songs.forEach((song, index) => {const li = document.createElement('li');// 单曲循环时高亮当前歌曲const isCurrentAndSingle = index === currentSongIndex && repeatMode === 1;li.className = `p-3 rounded-lg hover:bg-gray-800/50 transition-colors cursor-pointer ${index === currentSongIndex ? 'bg-primary/20' : ''} ${isCurrentAndSingle ? 'border-l-2 border-secondary track-highlight' : ''} fade-in`;li.innerHTML = `<div class="flex items-center justify-between"><div class="flex items-center space-x-3"><div class="w-10 h-10 rounded overflow-hidden ${isCurrentAndSingle ? 'ring-2 ring-secondary' : ''}"><img src="${song.cover}" alt="${song.title}封面" class="w-full h-full object-cover"></div><div><h4 class="text-sm font-medium truncate w-40 ${isCurrentAndSingle ? 'text-secondary' : ''}">${song.title}</h4><p class="text-xs text-gray-400">${song.artist} ${song.isLocal ? '<span class="inline-block ml-1 px-1.5 py-0.5 bg-primary/20 text-primary text-[10px] rounded">本地</span>' : ''}${isCurrentAndSingle ? '<span class="inline-block ml-1 px-1.5 py-0.5 bg-secondary/20 text-secondary text-[10px] rounded">单曲循环中</span>' : ''}</p></div></div><div class="flex items-center space-x-2"><span class="text-xs text-gray-500">${song.duration}</span>${song.isLocal ? `<button class="remove-song text-gray-500 hover:text-red-400 transition-colors" data-index="${index}"><i class="fa fa-times text-xs"></i></button>` : ''}</div></div>`;li.addEventListener('click', (e) => {// 防止点击删除按钮时播放歌曲if (!e.target.closest('.remove-song')) {playSong(index);}});playlist.appendChild(li);});// 添加删除本地歌曲事件监听document.querySelectorAll('.remove-song').forEach(btn => {btn.addEventListener('click', (e) => {e.stopPropagation();const index = parseInt(btn.getAttribute('data-index'));removeSong(index);});});}// 播放指定歌曲function playSong(index) {if (index < 0 || index >= songs.length) return;// 重置单曲循环计数(切换歌曲时)if (index !== currentSongIndex) {singleRepeatCount = 0;albumContainer.classList.remove('repeat-highlight');singleRepeatLabel.classList.add('hidden');// 清除单曲循环通知超时if (repeatNotificationTimeout) {clearTimeout(repeatNotificationTimeout);}}// 更新播放历史if (historyIndex >= 0) {// 如果不是继续播放历史,截断历史记录if (index !== playHistory[historyIndex + 1]) {playHistory = playHistory.slice(0, historyIndex + 1);}}// 添加到历史记录(如果不是同一首歌)if (playHistory.length === 0 || index !== playHistory[playHistory.length - 1]) {playHistory.push(index);historyIndex = playHistory.length - 1;}currentSongIndex = index;const song = songs[currentSongIndex];// 更新UIalbumCover.src = song.cover;songTitle.textContent = song.title;songArtist.textContent = song.artist;totalTimeEl.textContent = song.duration;// 更新单曲循环标签显示updateSingleRepeatDisplay();// 更新播放列表高亮initPlaylist();// 加载并播放歌曲audioPlayer.src = song.src;audioPlayer.load();play();// 更新模式指示器updateModeIndicator();}// 播放function play() {audioPlayer.play();isPlaying = true;playIcon.className = 'fa fa-pause text-white text-2xl';bigPlayIcon.className = 'fa fa-pause text-white text-2xl md:text-3xl';albumCover.classList.add('album-rotate');}// 暂停function pause() {audioPlayer.pause();isPlaying = false;playIcon.className = 'fa fa-play text-white text-2xl';bigPlayIcon.className = 'fa fa-play text-white text-2xl md:text-3xl';albumCover.classList.remove('album-rotate');}// 切换播放/暂停function togglePlayPause() {if (isPlaying) {pause();} else {play();}}// 上一首function prevSong() {if (songs.length <= 1) return;// 重置单曲循环计数(切换歌曲时)singleRepeatCount = 0;albumContainer.classList.remove('repeat-highlight');singleRepeatLabel.classList.add('hidden');// 清除单曲循环通知超时if (repeatNotificationTimeout) {clearTimeout(repeatNotificationTimeout);}let index;// 如果有播放历史且可以后退if (historyIndex > 0) {historyIndex--;index = playHistory[historyIndex];} else {// 根据播放模式确定上一首if (isShuffle) {// 随机播放模式下,从所有歌曲中随机选择(不包括当前歌曲)do {index = Math.floor(Math.random() * songs.length);} while (index === currentSongIndex);} else {// 顺序播放模式index = currentSongIndex - 1;if (index < 0) index = songs.length - 1;}}playSong(index);}// 下一首function nextSong() {if (songs.length <= 1) return;// 重置单曲循环计数(切换歌曲时)singleRepeatCount = 0;albumContainer.classList.remove('repeat-highlight');singleRepeatLabel.classList.add('hidden');// 清除单曲循环通知超时if (repeatNotificationTimeout) {clearTimeout(repeatNotificationTimeout);}let index;// 如果有播放历史且可以前进if (historyIndex < playHistory.length - 1) {historyIndex++;index = playHistory[historyIndex];} else {// 根据播放模式确定下一首if (isShuffle) {// 随机播放逻辑if (songs.length <= 3) {index = Math.floor(Math.random() * songs.length);} else {do {index = Math.floor(Math.random() * songs.length);} while (index === currentSongIndex);}} else {// 顺序播放模式index = currentSongIndex + 1;if (index >= songs.length) index = 0;}}playSong(index);}// 更新进度条function updateProgress() {if (isDragging) return;const { currentTime, duration } = audioPlayer;if (isNaN(duration)) return;const progressPercent = (currentTime / duration) * 100;progress.style.width = `${progressPercent}%`;progressThumb.style.left = `${progressPercent}%`;// 更新当前时间显示currentTimeEl.textContent = formatTime(currentTime);// 更新总时长(对于本地文件)if (songs[currentSongIndex]?.isLocal && duration > 0) {const totalTime = formatTime(duration);totalTimeEl.textContent = totalTime;// 更新歌曲数据中的时长songs[currentSongIndex].duration = totalTime;}// 检测歌曲即将结束(用于单曲循环的平滑过渡)if (duration - currentTime < 1 && currentTime > 0 && duration > 0) {handleSongAlmostEnd();}}// 处理歌曲即将结束function handleSongAlmostEnd() {// 清除之前的超时,防止多次触发if (repeatEndTimeout) {clearTimeout(repeatEndTimeout);}// 稍微延迟,确保歌曲真正结束repeatEndTimeout = setTimeout(handleSongEnd, 500);}// 处理歌曲播放结束function handleSongEnd() {switch (repeatMode) {case 1: // 单曲循环// 增加循环计数singleRepeatCount++;// 循环多次后添加视觉提示if (singleRepeatCount >= SINGLE_REPEAT_ANIMATION_THRESHOLD) {albumContainer.classList.add('repeat-highlight');}// 第一次循环时显示通知if (singleRepeatCount === 1) {showSingleRepeatNotification();}// 重置播放位置并继续播放audioPlayer.currentTime = 0;play();// 更新播放列表,显示单曲循环状态initPlaylist();break;case 0: // 不重复,且是最后一首歌if (currentSongIndex === songs.length - 1) {pause();audioPlayer.currentTime = 0;updateProgress();return;}// 否则继续播放下一首// fall throughcase 2: // 列表循环nextSong();break;}}// 显示单曲循环通知function showSingleRepeatNotification() {singleRepeatHint.classList.remove('hidden');singleRepeatLabel.classList.remove('hidden');// 清除之前的超时if (repeatNotificationTimeout) {clearTimeout(repeatNotificationTimeout);}// 设置超时隐藏通知repeatNotificationTimeout = setTimeout(() => {singleRepeatHint.classList.add('hidden');}, SINGLE_REPEAT_NOTIFICATION_DELAY);}// 更新单曲循环显示function updateSingleRepeatDisplay() {if (repeatMode === 1) {singleRepeatLabel.classList.remove('hidden');if (singleRepeatCount >= SINGLE_REPEAT_ANIMATION_THRESHOLD) {albumContainer.classList.add('repeat-highlight');}} else {singleRepeatLabel.classList.add('hidden');albumContainer.classList.remove('repeat-highlight');}}// 格式化时间(秒 -> mm:ss)function formatTime(seconds) {if (isNaN(seconds)) return "00:00";const minutes = Math.floor(seconds / 60);const secs = Math.floor(seconds % 60);return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;}// 设置进度function setProgress(e) {const rect = progressBar.getBoundingClientRect();const pos = (e.clientX - rect.left) / rect.width;const newTime = pos * audioPlayer.duration;audioPlayer.currentTime = newTime;progress.style.width = `${pos * 100}%`;progressThumb.style.left = `${pos * 100}%`;}// 设置音量function setVolume(e) {const rect = volumeControl.getBoundingClientRect();const pos = (e.clientX - rect.left) / rect.width;const volume = Math.max(0, Math.min(1, pos));audioPlayer.volume = volume;volumeLevel.style.width = `${pos * 100}%`;volumeThumb.style.left = `${pos * 100}%`;}// 切换随机播放function toggleShuffle() {isShuffle = !isShuffle;// 如果开启随机播放,关闭单曲循环if (isShuffle && repeatMode === 1) {repeatMode = 0;updateRepeatDisplay();singleRepeatCount = 0;albumContainer.classList.remove('repeat-highlight');singleRepeatLabel.classList.add('hidden');// 清除单曲循环通知超时if (repeatNotificationTimeout) {clearTimeout(repeatNotificationTimeout);}}// 更新UIshuffleBtn.classList.toggle('text-primary', isShuffle);shuffleIndicator.classList.toggle('hidden', !isShuffle);// 重置播放历史和单曲循环计数if (isShuffle) {playHistory = [currentSongIndex];historyIndex = 0;}// 显示模式提示showModeIndicator();}// 切换重复模式function toggleRepeat() {// 保存旧模式用于比较const oldMode = repeatMode;// 切换模式repeatMode = (repeatMode + 1) % 3;// 重置单曲循环计数(退出单曲循环模式时)if (oldMode === 1 && repeatMode !== 1) {singleRepeatCount = 0;albumContainer.classList.remove('repeat-highlight');singleRepeatLabel.classList.add('hidden');// 清除单曲循环通知超时if (repeatNotificationTimeout) {clearTimeout(repeatNotificationTimeout);}}// 如果开启单曲循环,关闭随机播放if (repeatMode === 1 && isShuffle) {isShuffle = false;shuffleBtn.classList.remove('text-primary');shuffleIndicator.classList.add('hidden');playHistory = [currentSongIndex];historyIndex = 0;}// 更新UIupdateRepeatDisplay();// 第一次进入单曲循环模式时显示通知if (repeatMode === 1 && oldMode !== 1) {showSingleRepeatNotification();}// 显示模式提示showModeIndicator();}// 更新重复模式的显示function updateRepeatDisplay() {// 更新按钮样式if (repeatMode === 1) {repeatBtn.classList.add('text-secondary');repeatBtn.classList.remove('text-primary');repeatBtnIcon.classList.add('single-repeat-indicator');} else {repeatBtn.classList.toggle('text-primary', repeatMode !== 0);repeatBtn.classList.remove('text-secondary');repeatBtnIcon.classList.remove('single-repeat-indicator');}// 更新重复指示器if (repeatMode === 1) {repeatIndicator.textContent = '1';repeatIndicator.classList.remove('hidden');} else {repeatIndicator.classList.add('hidden');}// 更新提示文本和图标switch (repeatMode) {case 0:repeatTooltip.textContent = "点击开启列表循环";repeatOverlayIcon.className = 'fa fa-repeat text-primary';repeatOverlayText.textContent = "不重复";break;case 1:repeatTooltip.textContent = "点击开启不重复模式";repeatOverlayIcon.className = 'fa fa-repeat text-secondary single-repeat-indicator';repeatOverlayText.textContent = "单曲循环";break;case 2:repeatTooltip.textContent = "点击开启单曲循环";repeatOverlayIcon.className = 'fa fa-repeat text-primary';repeatOverlayText.textContent = "列表循环";break;}// 控制专辑封面上方的指示器显示if (repeatMode === 0) {repeatOverlay.classList.add('hidden');} else {repeatOverlay.classList.remove('hidden');// 重新触发动画repeatOverlay.classList.remove('mode-badge');void repeatOverlay.offsetWidth; // 触发重绘repeatOverlay.classList.add('mode-badge');}// 更新单曲循环显示updateSingleRepeatDisplay();// 更新播放列表,反映当前模式initPlaylist();}// 显示播放模式提示function showModeIndicator() {updateModeIndicator();modeIndicator.classList.remove('hidden');// 清除之前的超时if (modeIndicatorTimeout) {clearTimeout(modeIndicatorTimeout);}// 3秒后隐藏modeIndicatorTimeout = setTimeout(() => {modeIndicator.classList.add('hidden');}, 3000);}// 更新模式指示器文本和图标function updateModeIndicator() {if (isShuffle) {modeIcon.className = 'fa fa-random mr-2 text-primary';modeText.textContent = '随机播放已开启';return;}switch (repeatMode) {case 0:modeIcon.className = 'fa fa-repeat mr-2 text-gray-500';modeText.textContent = '顺序播放(不重复)';break;case 1:modeIcon.className = 'fa fa-repeat mr-2 text-secondary';modeText.textContent = '单曲循环已开启';break;case 2:modeIcon.className = 'fa fa-repeat mr-2 text-primary';modeText.textContent = '列表循环已开启';break;}}// 导入本地音乐function importLocalMusic(files) {if (!files || files.length === 0) return 0;let importedCount = 0;// 为每个文件创建一个对象URLArray.from(files).forEach(file => {// 检查文件类型if (!file.type.startsWith('audio/')) {console.log(`跳过非音频文件: ${file.name}`);return;}// 检查是否已导入过此文件const isDuplicate = songs.some(song => song.isLocal && song.title === file.name.replace(/\.[^/.]+$/, ""));if (isDuplicate) {console.log(`已跳过重复文件: ${file.name}`);return;}importedCount++;// 创建对象URLconst objectUrl = URL.createObjectURL(file);// 解析文件名(去除扩展名)const fileName = file.name.replace(/\.[^/.]+$/, "");// 尝试从文件名提取艺术家和标题(简单处理)let title = fileName;let artist = "未知艺术家";const separatorIndex = fileName.indexOf(' - ');if (separatorIndex > 0) {artist = fileName.substring(0, separatorIndex);title = fileName.substring(separatorIndex + 3);}// 生成唯一IDconst id = Date.now() + Math.floor(Math.random() * 1000);// 添加到歌曲列表songs.push({id,title,artist,src: objectUrl,cover: `https://picsum.photos/seed/${id}/400/400`, // 使用ID作为种子生成一致的封面duration: "00:00", // 初始时长,将在播放时更新isLocal: true});});// 更新播放列表initPlaylist();// 显示成功提示if (importedCount > 0) {importSuccessText.textContent = `成功导入 ${importedCount} 首音乐`;importSuccess.classList.remove('hidden');// 3秒后隐藏提示setTimeout(() => {importSuccess.classList.add('hidden');}, 3000);}return importedCount;}// 移除本地歌曲function removeSong(index) {if (index < 0 || index >= songs.length) return;const song = songs[index];// 如果是本地歌曲,释放object URLif (song.isLocal) {URL.revokeObjectURL(song.src);}// 从播放历史中移除const historyPos = playHistory.indexOf(index);if (historyPos !== -1) {playHistory.splice(historyPos, 1);if (historyIndex >= historyPos) {historyIndex--;}// 确保历史索引有效if (historyIndex < 0 && playHistory.length > 0) {historyIndex = 0;}}// 重置单曲循环计数(如果删除的是当前循环的歌曲)if (index === currentSongIndex && repeatMode === 1) {singleRepeatCount = 0;albumContainer.classList.remove('repeat-highlight');singleRepeatLabel.classList.add('hidden');// 清除单曲循环通知超时if (repeatNotificationTimeout) {clearTimeout(repeatNotificationTimeout);}}// 如果删除的是当前播放的歌曲if (index === currentSongIndex) {// 暂停播放pause();// 如果还有其他歌曲,播放下一首if (songs.length > 1) {const newIndex = index < songs.length - 1 ? index : 0;setTimeout(() => {playSong(newIndex);}, 300);} else {// 重置UIsongTitle.textContent = "未播放歌曲";songArtist.textContent = "艺术家";totalTimeEl.textContent = "00:00";currentTimeEl.textContent = "00:00";progress.style.width = "0%";progressThumb.style.left = "0%";}} else if (index < currentSongIndex) {// 如果删除的是当前歌曲前面的歌曲,调整索引currentSongIndex--;}// 从列表中移除songs.splice(index, 1);// 更新播放列表initPlaylist();}// 显示导入提示function showImportHint() {const now = Date.now();// 防止短时间内重复显示if (now - lastImportHintTime > 2000) {importHint.classList.remove('hidden');setTimeout(() => {importHint.classList.add('hidden');}, 2000);lastImportHintTime = now;}}// 事件监听 - 音频结束事件(作为备用检测)audioPlayer.addEventListener('ended', handleSongEnd);// 事件监听playPauseBtn.addEventListener('click', togglePlayPause);bigPlayPauseBtn.addEventListener('click', togglePlayPause);prevBtn.addEventListener('click', prevSong);nextBtn.addEventListener('click', nextSong);audioPlayer.addEventListener('timeupdate', updateProgress);shuffleBtn.addEventListener('click', toggleShuffle);repeatBtn.addEventListener('click', toggleRepeat);// 进度条拖动控制progressBar.addEventListener('click', setProgress);progressBar.addEventListener('mousedown', () => isDragging = true);document.addEventListener('mouseup', (e) => {if (isDragging) {setProgress(e);isDragging = false;}});document.addEventListener('mousemove', (e) => {if (isDragging) {setProgress(e);}});// 音量控制volumeControl.addEventListener('click', setVolume);volumeControl.addEventListener('mousedown', (e) => {isDragging = true;setVolume(e);});// 本地音乐导入importMusicInput.addEventListener('change', (e) => {importLocalMusic(e.target.files);// 重置input值,允许重复选择同一文件e.target.value = '';});// 导入按钮悬停提示document.querySelector('label[for="import-music"]').addEventListener('mouseenter', showImportHint);// 初始化window.addEventListener('DOMContentLoaded', () => {initPlaylist();// 初始化播放历史playHistory = [0];historyIndex = 0;// 加载第一首歌但不自动播放audioPlayer.src = songs[0].src;audioPlayer.load();// 初始化重复模式显示updateRepeatDisplay();// 初始显示模式updateModeIndicator();});// 页面关闭时清理object URLwindow.addEventListener('beforeunload', () => {songs.forEach(song => {if (song.isLocal) {URL.revokeObjectURL(song.src);}});});</script>
</body>
</html>
http://www.dtcms.com/a/349000.html

相关文章:

  • 阿里发布Qoder:颠覆软件开发体验的AI编程平台
  • 前端应用容器化,基于Docker多阶段构建的最佳实践
  • More Effective C++ 条款05: 谨慎定义类型转换函数
  • Java 泛型的“擦除”与“保留”:一次完整的编译与反编译实验
  • Docker中Dify镜像由Windows系统迁移到Linux系统的方法
  • 【计算机408数据结构】第二章:基本数据结构之线性表
  • Leetcode 3660. Jump Game IX
  • 新的 Gmail 网络钓鱼攻击利用 AI 提示注入来逃避检测
  • rust语言 (1.88) egui (0.32.1) 学习笔记(逐行注释)(十四)垂直滚动条
  • 【URP】[投影Projector]解析与应用
  • 【cs336学习笔记】[第6课]内核优化与Triton框架应用
  • 如何在算力时代乘风破浪?
  • 深度学习中的模型量化及实现示例
  • 【RAGFlow代码详解-4】数据存储层
  • MySQL学习记录-基础知识及SQL语句
  • 【零代码】OpenCV C# 快速开发框架演示
  • 在 Docker 容器中查看 Python 版本
  • C语言第十二章自定义类型:结构体
  • LangChain RAG系统开发基础学习之文档切分
  • Python核心技术开发指南(016)——表达式
  • 多线程——认识Thread类和创建线程
  • 【记录】Docker|Docker镜像拉取超时的问题、推荐的解决办法及安全校验
  • FPGA时序分析(四)
  • asio的线程安全
  • 使用Cobra 完成CLI开发 (一)
  • 3.1 存储系统概述 (答案见原书 P149)
  • C++ string自定义类的实现
  • 【论文阅读 | arXiv 2025 | WaveMamba:面向RGB-红外目标检测的小波驱动Mamba融合方法】
  • 上科大解锁城市建模新视角!AerialGo:从航拍视角到地面漫步的3D城市重建
  • 深度剖析Spring AI源码(三):ChatClient详解,优雅的流式API设计