<!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/archives/43/
本站文章除注明[转载|引用|原文]出处外,均为本站原生内容,转载前请注明出处
还可以继续优化一下