<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>多媒体播放器</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> :root { --primary-color: #2196F3; --bg-alpha: 0.9; --control-height: 80px; } * { margin: 0; padding: 0; box-sizing: border-box; } body { height: 100vh; display: flex; justify-content: center; align-items: center; background: #1a1a1a; font-family: 'Microsoft Yahei', sans-serif; color: white; } .player-container { width: 1650px; height: 1110px; background: linear-gradient(135deg, rgba(44,62,80,var(--bg-alpha)), rgba(52,152,219,var(--bg-alpha))); border-radius: 15px; position: relative; overflow: hidden; transition: all 0.3s; } .current-track { flex-grow: 1; text-align: center; padding: 0 20px; line-height: 40px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .top-bar { position: absolute; top: 0; width: 100%; display: flex; justify-content: space-between; padding: 15px; background: rgba(0,0,0,0.5); opacity: 0; transition: opacity 0.3s; z-index: 100; } .sidebar { position: absolute; left: -300px; top: 60px; width: 300px; height: calc(100% - 120px); background: rgba(0,0,0,0.7); transition: all 0.3s; overflow-y: auto; padding: 15px; } .file-list { list-style: none; margin-top: 15px; } .folder-item { margin-bottom: 15px; } .folder-header { padding: 8px; background: rgba(255,255,255,0.05); border-radius: 4px; margin-bottom: 5px; cursor: pointer; } .file-sublist { padding-left: 20px; list-style: none; display: none; } .file-item { padding: 8px; cursor: pointer; border-radius: 4px; margin-bottom: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .file-item:hover { background: rgba(255,255,255,0.1); } .vinyl-container { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 500px; height: 500px; display: none; } .vinyl { width: 100%; height: 100%; border-radius: 50%; background: radial-gradient(circle, #f70505 50%, #060606 50%); animation: rotate 20s linear infinite; animation-play-state: paused; box-shadow: 0 0 30px rgb(0 0 0 / 50%); } @keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .video-player { width: 100%; height: calc(100% - var(--control-height)); object-fit: contain; display: none; } .bottom-controls { position: absolute; bottom: 0; width: 100%; /* height: var(--control-height); */ padding: 15px 30px; background: rgba(0,0,0,0.6); opacity: 0; transition: opacity 0.3s; display: flex; flex-direction: column; gap: 10px; } .progress-container { width: 100%; height: 5px; background: rgba(255,255,255,0.2); cursor: pointer; margin-bottom: 10px; } .progress-bar { height: 100%; background: var(--primary-color); width: 0%; transition: width 0.1s linear; } .controls-row { display: flex; justify-content: center; align-items: center; gap: 25px; } .icon-btn { background: none; border: none; color: white; font-size: 20px; cursor: pointer; padding: 8px; border-radius: 50%; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; transition: all 0.3s; } .icon-btn:hover { background: rgba(255,255,255,0.1); } .fa-folder { margin-right: 8px; color: #FFD700; } .tonearm { position: absolute; right: -120px; top: 50%; width: 150px; height: 4px; background: #666; transform-origin: right center; transform: rotate(-30deg); transition: all 0.5s ease; z-index: 10; } .tonearm::after { content: ''; position: absolute; right: -10px; top: -8px; width: 20px; height: 20px; background: #444; border-radius: 50%; } .playing .tonearm { transform: rotate(0deg); } /* 歌词容器样式 */ .lyrics-container { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 50%; height: 70%; overflow-y: auto; padding: 20px; display: none; } .lyrics-available { width: 50% !important; left: 25% !important; } .lyrics-line { margin: 10px 0; padding: 8px; transition: all 0.3s; opacity: 0.5; } .lyrics-line.active { opacity: 1; transform: scale(1.1); color: var(--primary-color); } </style> </head> <body> <div id="player"> <div id="topBar"> <div> <button title="列表" id="toggleSidebar"> <i class="fas fa-bars"></i> </button> </div> <div id="currentTrack">暂无播放</div> <div> <button title="全屏" id="toggleFullscreen"> <i class="fas fa-expand"></i> </button> </div> </div> <div id="sidebar"> <input type="file" id="fileInput" multiple webkitdirectory style="display: none;"> <button onclick="document.getElementById('fileInput').click()"> <i class="fas fa-folder-open"></i> </button> <ul id="fileList"></ul> </div> <div> <div id="vinylContainer"> <div id="vinyl"></div> <div id="tonearm"></div> </div> <div id="lyricsContainer"></div> <video id="videoPlayer"></video> </div> <div id="bottomControls" style="opacity: 0;"> <div id="progressContainer"> <div id="progressBar"></div> </div> <div> <button title="上一首" id="prevBtn"> <i class="fas fa-step-backward"></i> </button> <button title="播放/暂停" id="playPauseBtn"> <i class="fas fa-play"></i> </button> <button title="下一首" id="nextBtn"> <i class="fas fa-step-forward"></i> </button> <button title="列表循环" id="loopBtn"><i class="fas fa-retweet"></i></button> <input type="range" id="volumeSlider" min="0" max="1" step="0.1" value="1" title="音量"> <button title="播放速度" id="speedBtn" style="display: none;"> 1x </button> </div> </div> </div> <script> class MediaPlayer { constructor() { this.mediaElements = { audio: new Audio(), video: document.getElementById('videoPlayer') } this.playlist = []; this.currentIndex = 0; this.isPlaying = false; this.hideTimeout = null; this.sidebarVisible = false; this.loopMode = 'none'; // none, all, random this.folderStructure = new Map(); this.lyricsData = []; this.currentLyricIndex = 0; this.hasLyrics = false; this.initEventListeners(); this.initMediaListeners(); this.updatePlayButtonState(); } initEventListeners() { // 鼠标活动检测 document.addEventListener('mousemove', () => this.showControls()); // 文件选择 document.getElementById('fileInput').addEventListener('change', e => { this.handleFiles([...e.target.files]); }); // 播放控制 document.getElementById('playPauseBtn').addEventListener('click', () => this.togglePlay()); document.getElementById('prevBtn').addEventListener('click', () => this.prevTrack()); document.getElementById('nextBtn').addEventListener('click', () => this.nextTrack()); document.getElementById('loopBtn').addEventListener('click', () => this.toggleLoopMode()); // 侧边栏控制 document.getElementById('toggleSidebar').addEventListener('click', () => this.toggleSidebar()); // 点击外部隐藏侧边栏 document.addEventListener('click', (e) => { if (!e.target.closest('.sidebar') && !e.target.closest('#toggleSidebar')) { this.hideSidebar(); } }); // 音量控制 document.getElementById('volumeSlider').addEventListener('input', (e) => { this.setVolume(e.target.value); }); // 文件夹展开/收起 document.querySelectorAll('.folder-header').forEach(header => { header.addEventListener('click', (e) => { const sublist = e.currentTarget.nextElementSibling; sublist.style.display = sublist.style.display === 'none' ? 'block' : 'none'; }); }); // 全屏切换 document.getElementById('toggleFullscreen').addEventListener('click', () => { if (!document.fullscreenElement) { document.documentElement.requestFullscreen(); } else { document.exitFullscreen(); } }); window.addEventListener('online', () => this.checkLyrics()); window.addEventListener('offline', () => this.hasLyrics = false); // 歌词同步 document.getElementById('progressContainer').addEventListener('click', (e) => { const rect = e.currentTarget.getBoundingClientRect(); const progress = (e.clientX - rect.left) / rect.width; this.currentMedia.currentTime = this.currentMedia.duration * progress; }); } async checkLyrics() { if (!this.hasLyrics) { await this.loadLyrics(this.currentMedia.name); this.adjustLayout(); } } initMediaListeners() { // 时间更新 this.mediaElements.audio.addEventListener('timeupdate', () => this.updateProgress()); this.mediaElements.video.addEventListener('timeupdate', () => this.updateProgress()); // 播放结束处理 this.mediaElements.audio.addEventListener('ended', () => this.handleEnded()); this.mediaElements.video.addEventListener('ended', () => this.handleEnded()); } handleFiles(files) { const supportedFormats = ['mp3','wav','ogg','mp4','mkv','avi']; this.playlist = files.filter(file => { const ext = file.name.split('.').pop().toLowerCase(); return supportedFormats.includes(ext); }); this.buildFolderStructure(files); this.renderFileList(); this.updatePlayButtonState(); if(this.playlist.length > 0) this.loadMedia(this.playlist[0]); } buildFolderStructure(files) { this.folderStructure.clear(); files.forEach(file => { const path = file.webkitRelativePath.split('/'); const folder = path.slice(0, -1).join('/'); if (!this.folderStructure.has(folder)) { this.folderStructure.set(folder, []); } this.folderStructure.get(folder).push(file); }); } renderFileList() { const list = document.getElementById('fileList'); list.innerHTML = ''; this.folderStructure.forEach((files, folder) => { const folderItem = document.createElement('li'); folderItem.className = 'folder-item'; folderItem.innerHTML = ` <div> <i class="fas fa-folder"></i> ${folder || '根目录'} </div> <ul style="display:none"> ${files.map(file => ` <li data-path="${file.webkitRelativePath}"> ${file.name} </li> `).join('')} </ul> `; list.appendChild(folderItem); }); // 绑定文件点击事件 document.querySelectorAll('.file-item').forEach(item => { item.addEventListener('click', (e) => { e.stopPropagation(); const path = item.dataset.path; const file = [...this.folderStructure.values()] .flat() .find(f => f.webkitRelativePath === path); this.loadMedia(file); this.hideSidebar(); }); }); // 绑定文件夹点击事件 document.querySelectorAll('.folder-header').forEach(header => { header.addEventListener('click', (e) => { const sublist = e.currentTarget.nextElementSibling; sublist.style.display = sublist.style.display === 'none' ? 'block' : 'none'; }); }); } async loadMedia(file) { // 解析文件名信息 let fileName = file.name.replace(/\.[^/.]+$/, ""); let trackInfo = fileName.split(' - '); let displayText = trackInfo.length > 1 ? `${trackInfo[0]} - ${trackInfo.slice(1).join(' ')}` : fileName; document.getElementById('currentTrack').textContent = displayText; // 加载歌词 await this.loadLyrics(file.name); this.adjustLayout(); this.stop(); this.currentIndex = this.playlist.findIndex(f => f === file); if(file.type.startsWith('audio')) { this.mediaElements.video.style.display = 'none'; this.mediaElements.audio.src = URL.createObjectURL(file); document.getElementById('vinylContainer').style.display = 'block'; } else { this.mediaElements.audio.pause(); this.mediaElements.video.src = URL.createObjectURL(file); this.mediaElements.video.style.display = 'block'; document.getElementById('vinylContainer').style.display = 'none'; } this.updatePlayButtonState(); } async loadLyrics(filename) { this.lyricsData = []; this.currentLyricIndex = 0; const container = document.getElementById('lyricsContainer'); container.innerHTML = ''; if (!navigator.onLine) { this.hasLyrics = false; return; } try { const songName = filename.replace(/\.[^/.]+$/, ""); const response = await fetch(`https://api.example.com/lyrics?q=${encodeURIComponent(songName)}`); const lyrics = await response.json(); this.parseLyrics(lyrics); this.hasLyrics = lyrics.length > 0; if (this.hasLyrics) { container.innerHTML = lyrics.map(line => `<div data-time="${line.time}">${line.text}</div>` ).join(''); } } catch (error) { this.hasLyrics = false; console.error('歌词加载失败:', error); } } parseLyrics(rawLyrics) { // 示例歌词格式:[00:12.34]歌词内容 const lines = rawLyrics.split('\n'); this.lyricsData = lines.map(line => { const match = line.match(/\[(\d+):(\d+\.\d+)\](.*)/); if (match) { const minutes = parseInt(match[1]); const seconds = parseFloat(match[2]); return { time: minutes * 60 + seconds, text: match[3].trim() }; } return null; }).filter(Boolean); } // 新增布局调整方法 adjustLayout() { const vinyl = document.getElementById('vinylContainer'); const lyrics = document.getElementById('lyricsContainer'); if (this.hasLyrics) { vinyl.classList.add('lyrics-available'); lyrics.style.display = 'block'; } else { vinyl.classList.remove('lyrics-available'); lyrics.style.display = 'none'; } } togglePlay() { if(this.playlist.length === 0) return; this.isPlaying = !this.isPlaying; const icon = this.isPlaying ? 'fa-pause' : 'fa-play'; document.getElementById('playPauseBtn').innerHTML = `<i class="fas ${icon}"></i>`; if(this.isPlaying) { this.currentMedia.play(); document.getElementById('vinyl').style.animationPlayState = 'running'; } else { this.currentMedia.pause(); document.getElementById('vinyl').style.animationPlayState = 'paused'; } // 控制唱针动画 const vinylContainer = document.getElementById('vinylContainer'); if (this.isPlaying) { vinylContainer.classList.add('playing'); } else { vinylContainer.classList.remove('playing'); } } toggleLoopMode() { const modes = ['all', 'random', 'none']; this.loopMode = modes[(modes.indexOf(this.loopMode) + 1) % modes.length]; const titles = ['列表循环', '随机播放', '不循环']; const icons = ['fa-retweet', 'fa-random', 'fa-retweet']; document.getElementById('loopBtn').innerHTML = `<i class="fas ${icons[modes.indexOf(this.loopMode)]}"></i>`; document.getElementById('loopBtn').title = titles[modes.indexOf(this.loopMode)]; } updateProgress() { const media = this.currentMedia; const progress = (media.currentTime / media.duration) * 100 || 0; document.getElementById('progressBar').style.width = `${progress}%`; // 歌词同步 if (this.hasLyrics) { const currentTime = this.currentMedia.currentTime; const lines = document.querySelectorAll('.lyrics-line'); // 找到当前应显示的歌词 let activeIndex = this.lyricsData.findIndex( (line, index) => currentTime >= line.time && (index === this.lyricsData.length -1 || currentTime < this.lyricsData[index+1].time) ); if (activeIndex !== -1 && activeIndex !== this.currentLyricIndex) { lines[this.currentLyricIndex]?.classList.remove('active'); lines[activeIndex]?.classList.add('active'); this.currentLyricIndex = activeIndex; // 自动滚动 const container = document.getElementById('lyricsContainer'); const activeLine = lines[activeIndex]; container.scrollTo({ top: activeLine.offsetTop - container.offsetHeight/2, behavior: 'smooth' }); } } } setVolume(value) { this.mediaElements.audio.volume = value; this.mediaElements.video.volume = value; } nextTrack() { if (this.playlist.length === 0) return; const wasPlaying = this.isPlaying; if (this.loopMode === 'random') { let newIndex; do { newIndex = Math.floor(Math.random() * this.playlist.length); } while (newIndex === this.currentIndex && this.playlist.length > 1); this.currentIndex = newIndex; } else { // 列表循环时正常递增(到末尾自动回0) this.currentIndex = (this.currentIndex + 1) % this.playlist.length; } this.loadMedia(this.playlist[this.currentIndex]); // 循环模式下强制保持播放 if (this.loopMode !== 'none' || wasPlaying) { this.currentMedia.play().catch(() => {}); this.isPlaying = true; document.getElementById('playPauseBtn').innerHTML = '<i class="fas fa-pause"></i>'; document.getElementById('vinyl').style.animationPlayState = 'running'; } } prevTrack() { this.currentIndex = (this.currentIndex - 1 + this.playlist.length) % this.playlist.length; this.loadMedia(this.playlist[this.currentIndex]); if(this.isPlaying) this.currentMedia.play(); } handleEnded() { if (this.loopMode === 'none') return; // 强制继续播放状态 this.isPlaying = true; this.nextTrack(); } updatePlayButtonState() { const playBtn = document.getElementById('playPauseBtn'); playBtn.disabled = this.playlist.length === 0; if(this.playlist.length === 0) { playBtn.innerHTML = '<i class="fas fa-pause"></i>'; } } stop() { this.currentMedia.pause(); this.currentMedia.currentTime = 0; this.isPlaying = false; document.getElementById('playPauseBtn').innerHTML = '<i class="fas fa-play"></i>'; } toggleSidebar() { this.sidebarVisible = !this.sidebarVisible; document.getElementById('sidebar').style.left = this.sidebarVisible ? '0' : '-300px'; this.showControls(); } hideSidebar() { this.sidebarVisible = false; document.getElementById('sidebar').style.left = '-300px'; } showControls() { clearTimeout(this.hideTimeout); document.getElementById('topBar').style.opacity = 1; document.getElementById('bottomControls').style.opacity = 1; this.hideTimeout = setTimeout(() => { document.getElementById('topBar').style.opacity = 0; document.getElementById('bottomControls').style.opacity = 0; this.hideSidebar(); }, 10000); } get currentMedia() { return this.playlist[this.currentIndex]?.type.startsWith('audio') ? this.mediaElements.audio : this.mediaElements.video; } } const player = new MediaPlayer(); </script> </body> </html>
本文最后更新时间 2025-05-02
文章链接地址:https://xzlo.blog/index.php/2025/05/01/player.html
本站文章除注明[转载|引用|原文]出处外,均为本站原生内容,转载前请注明出处