添加网络音频播放功能

This commit is contained in:
陈乾 2026-02-02 18:03:29 +08:00
parent 38b1c613b5
commit 3ec43d5240
4 changed files with 804 additions and 22 deletions

View File

@ -12,12 +12,18 @@
<h1>音乐播放器</h1> <h1>音乐播放器</h1>
<!-- 文件选择 --> <!-- 文件选择 -->
<div class="file-selector"> <div class="file-selector" id="fileSelector">
<input type="file" id="audioFile" accept="audio/*" /> <input type="file" id="audioFile" accept="audio/*" />
<label for="audioFile" class="file-label">选择音乐文件</label> <label for="audioFile" class="file-label">选择音乐文件</label>
<input type="file" id="audioFolder" webkitdirectory directory multiple accept="audio/*" style="display: none;" /> <input type="file" id="audioFolder" webkitdirectory directory multiple accept="audio/*" style="display: none;" />
<label for="audioFolder" class="file-label folder-label">选择音乐文件夹</label> <label for="audioFolder" class="file-label folder-label">选择音乐文件夹</label>
</div> </div>
<!-- 网络音乐信息(默认隐藏) -->
<div class="network-music-info" id="networkMusicInfo" style="display: none;">
<p>🌐 正在播放网络音乐</p>
<p id="networkMusicName">-</p>
</div>
<!-- 当前播放信息 --> <!-- 当前播放信息 -->
<div class="track-info"> <div class="track-info">

View File

@ -20,6 +20,11 @@ class AudioPlayer {
this.isPlayingFolder = false; this.isPlayingFolder = false;
this.playbackMode = 'loop'; // 默认列表循环模式:'single', 'loop', 'random' this.playbackMode = 'loop'; // 默认列表循环模式:'single', 'loop', 'random'
// 网络音乐相关
this.isNetworkMusic = false;
this.networkMusicUrl = null;
this.networkMusicName = null;
// Web Audio API警告标志 // Web Audio API警告标志
this.webAudioApiWarningShown = false; this.webAudioApiWarningShown = false;
@ -31,6 +36,7 @@ class AudioPlayer {
this.setupCanvas(); this.setupCanvas();
this.setupKeyboardShortcuts(); this.setupKeyboardShortcuts();
this.initializePlayerState(); this.initializePlayerState();
this.checkUrlParams(); // 检查URL参数
} }
initializeElements() { initializeElements() {
@ -164,6 +170,12 @@ class AudioPlayer {
} }
handleFileSelect(event) { handleFileSelect(event) {
// 如果正在播放网络音乐,不允许选择本地文件
if (this.isNetworkMusic) {
alert('正在播放网络音乐,请先停止当前播放再选择本地文件');
return;
}
const file = event.target.files[0]; const file = event.target.files[0];
if (file) { if (file) {
// 单个文件播放模式 // 单个文件播放模式
@ -172,7 +184,7 @@ class AudioPlayer {
this.currentTrackIndex = 0; this.currentTrackIndex = 0;
// 清理旧的URL // 清理旧的URL
if (this.audio.src) { if (this.audio.src && !this.isNetworkMusic) {
URL.revokeObjectURL(this.audio.src); URL.revokeObjectURL(this.audio.src);
} }
@ -186,10 +198,20 @@ class AudioPlayer {
// 设置音频上下文(如果尚未初始化) // 设置音频上下文(如果尚未初始化)
this.setupAudioContext(); this.setupAudioContext();
// 关键:本地音乐也需要连接音频源
console.log('本地音乐文件已选择,准备连接音频源');
this.connectAudioSource();
} }
} }
handleFolderSelect(event) { handleFolderSelect(event) {
// 如果正在播放网络音乐,不允许选择本地文件夹
if (this.isNetworkMusic) {
alert('正在播放网络音乐,请先停止当前播放再选择本地文件夹');
return;
}
const files = Array.from(event.target.files); const files = Array.from(event.target.files);
if (files.length > 0) { if (files.length > 0) {
// 筛选音频文件 - 支持更多格式 // 筛选音频文件 - 支持更多格式
@ -206,6 +228,7 @@ class AudioPlayer {
this.playlist = audioFiles; this.playlist = audioFiles;
this.currentTrackIndex = 0; this.currentTrackIndex = 0;
this.isPlayingFolder = true; this.isPlayingFolder = true;
this.isNetworkMusic = false; // 切换到本地模式
// 确保播放模式显示正确(默认列表循环) // 确保播放模式显示正确(默认列表循环)
this.playbackMode = 'loop'; this.playbackMode = 'loop';
@ -271,6 +294,10 @@ class AudioPlayer {
// 设置自动播放状态 // 设置自动播放状态
this.shouldAutoPlay = shouldAutoPlay; this.shouldAutoPlay = shouldAutoPlay;
// 关键:文件夹中的本地音乐也需要连接音频源
console.log('文件夹音乐已加载,准备连接音频源');
this.connectAudioSource();
// 等待音频数据加载完成后播放 // 等待音频数据加载完成后播放
const tryAutoPlay = () => { const tryAutoPlay = () => {
if (this.audio.readyState >= 2 && (!this.audio.paused || this.shouldAutoPlay)) { if (this.audio.readyState >= 2 && (!this.audio.paused || this.shouldAutoPlay)) {
@ -335,14 +362,12 @@ class AudioPlayer {
this.waveformVisualizer = new WaveformVisualizer(this.canvas, this.audioContext, this.analyser); this.waveformVisualizer = new WaveformVisualizer(this.canvas, this.audioContext, this.analyser);
console.log('音频上下文和可视化器初始化成功'); console.log('音频上下文和可视化器初始化成功');
} }
// 连接音频源(只在首次或需要重新连接时) // 确保音频上下文处于运行状态
if (this.audioContext && this.analyser && !this.source) { if (this.audioContext.state === 'suspended') {
this.source = this.audioContext.createMediaElementSource(this.audio); this.audioContext.resume();
this.source.connect(this.analyser);
this.analyser.connect(this.audioContext.destination);
console.log('音频源连接成功');
} }
} catch (error) { } catch (error) {
console.error('音频上下文初始化失败:', error); console.error('音频上下文初始化失败:', error);
if (!this.webAudioApiWarningShown) { if (!this.webAudioApiWarningShown) {
@ -351,25 +376,387 @@ class AudioPlayer {
} }
} }
} }
// 为网络音乐创建一个智能模拟可视化系统绕过CORS限制
startNetworkMusicVisualization() {
console.log('🔄 启动网络音乐智能模拟可视化...');
// 检查是否有音频上下文和画布
if (!this.audioContext || !this.canvas) {
console.error('无法启动模拟可视化:缺少必要组件');
return;
}
// 创建一个音频活动和节奏检测器
const rhythmDetectorState = {
lastTime: 0,
timeDelta: 0,
energy: 0,
energyHistory: []
};
// 检测音频活动
const detectActivity = () => {
return !this.audio.paused && this.audio.currentTime > 0;
};
// 计算节奏能量(基于播放速度变化)
const calculateRhythmEnergy = () => {
if (!detectActivity()) return 0;
const currentTime = this.audio.currentTime;
rhythmDetectorState.timeDelta = currentTime - rhythmDetectorState.lastTime;
rhythmDetectorState.lastTime = currentTime;
// 计算能量(模拟音频波形的振幅)
const baseEnergy = Math.sin(currentTime * 3) * 0.5 + 0.5;
const beatEnergy = Math.sin(currentTime * 1.5) > 0.8 ? 1 : 0;
const energy = baseEnergy + beatEnergy * 0.5;
// 平滑能量值
rhythmDetectorState.energyHistory.push(energy);
if (rhythmDetectorState.energyHistory.length > 10) rhythmDetectorState.energyHistory.shift();
rhythmDetectorState.energy = rhythmDetectorState.energyHistory.reduce((sum, val) => sum + val, 0) / rhythmDetectorState.energyHistory.length;
return rhythmDetectorState.energy;
};
// 智能可视化绘制函数
const drawSmartVisualization = () => {
if (!detectActivity()) {
// 音频未播放,清空画布
this.ctx.fillStyle = 'rgb(0, 0, 0)';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
return;
}
const time = this.audio.currentTime;
const energy = calculateRhythmEnergy();
// 清除画布
this.ctx.fillStyle = 'rgb(0, 0, 0)';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
// 绘制动态条形图(根据能量变化)
const barWidth = 8;
const barSpacing = 4;
const numBars = Math.floor(this.canvas.width / (barWidth + barSpacing));
for (let i = 0; i < numBars; i++) {
// 使用多种波形组合生成更自然的高度变化
const wave1 = Math.sin(time * 2 + i * 0.1) * 0.5 + 0.5;
const wave2 = Math.cos(time * 1.5 + i * 0.2) * 0.3 + 0.3;
const wave3 = Math.sin(time * 0.5 + i * 0.05) * 0.2 + 0.2;
// 结合能量值调整高度
const heightFactor = wave1 + wave2 + wave3;
const height = energy * heightFactor * (this.canvas.height * 0.7) + 5;
// 颜色随能量和位置动态变化
const r = Math.floor(200 + energy * 55 * Math.sin(i * 0.1 + time));
const g = Math.floor(100 + energy * 155 * Math.cos(i * 0.1 + time));
const b = Math.floor(150 + energy * 105 * Math.sin(i * 0.2 + time));
this.ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
// 绘制带圆角的条形
this.ctx.fillRect(
i * (barWidth + barSpacing),
this.canvas.height - height,
barWidth,
height
);
// 添加顶部高光
this.ctx.fillStyle = `rgba(255, 255, 255, ${energy * 0.3})`;
this.ctx.fillRect(
i * (barWidth + barSpacing),
this.canvas.height - height,
barWidth,
3
);
}
// 绘制中央波形线(响应能量变化)
this.ctx.beginPath();
this.ctx.strokeStyle = `rgba(0, ${Math.floor(255 * energy)}, 255, ${0.8 + energy * 0.2})`;
this.ctx.lineWidth = 3;
// 绘制更复杂的波形
for (let x = 0; x < this.canvas.width; x += 2) {
// 基础波形
const baseWave = Math.sin(x * 0.015 + time * 5) * 30;
// 能量影响的波形
const energyWave = Math.cos(x * 0.02 + time * 3) * 40 * energy;
// 高频细节
const detailWave = Math.sin(x * 0.05 + time * 10) * 10;
const y = this.canvas.height / 2 + baseWave + energyWave + detailWave;
if (x === 0) {
this.ctx.moveTo(x, y);
} else {
this.ctx.lineTo(x, y);
}
}
this.ctx.stroke();
// 添加脉冲效果(当检测到强节拍时)
if (rhythmDetectorState.timeDelta < 0.01 && energy > 0.8) {
this.ctx.beginPath();
this.ctx.strokeStyle = `rgba(255, 255, 255, ${energy * 0.6})`;
this.ctx.lineWidth = 2;
this.ctx.arc(
this.canvas.width / 2,
this.canvas.height / 2,
Math.random() * 50 + 100,
0,
Math.PI * 2
);
this.ctx.stroke();
}
};
// 定期更新可视化60fps
const animate = () => {
this.animationId = requestAnimationFrame(animate);
drawSmartVisualization();
};
animate();
console.log('✅ 网络音乐智能模拟可视化已启动');
}
connectAudioSource() {
try {
console.log('🔄 创建新的音频源连接...');
// 检查音频元素状态
if (this.audio.readyState < 2) {
console.log('音频元素尚未准备好,延迟连接');
return false;
}
// 确保没有现有的连接
if (this.source) {
console.log('清理现有音频源连接');
try {
this.source.disconnect();
} catch (e) {
console.log('断开旧连接时出错:', e);
}
this.source = null;
}
// 关键重新创建MediaElementSource
console.log('创建新的MediaElementSource...');
this.source = this.audioContext.createMediaElementSource(this.audio);
console.log('MediaElementSource创建成功');
// 连接到分析器
console.log('连接到分析器...');
this.source.connect(this.analyser);
console.log('分析器连接成功');
console.log('连接到音频目标...');
this.analyser.connect(this.audioContext.destination);
console.log('音频目标连接成功');
console.log('✅ 音频源连接完成');
console.log('连接链路: 音频元素 → MediaElementSource → 分析器' + (this.isNetworkMusic ? '' : ' → 目标'));
return true;
} catch (error) {
console.error('❌ 音频源连接失败:', error);
console.error('错误类型:', error.name);
console.error('错误消息:', error.message);
// 特殊处理:音频元素已连接的错误
if (error.name === 'InvalidStateError' && error.message.includes('already connected')) {
console.log('💡 检测到音频元素已连接,尝试重新初始化...');
return this.reinitializeAudioConnection();
}
// 特殊处理CORS错误
if (error.name === 'NotSupportedError' || error.message.includes('CORS')) {
console.log('💡 检测到CORS错误网络音乐将完全独立播放');
return true; // 即使连接失败,也返回成功,让网络音乐独立播放
}
return false;
}
}
tryAlternativeConnection() {
console.log('🔄 尝试备用音频连接方式...');
try {
// 清理现有连接
if (this.source) {
this.source.disconnect();
this.source = null;
}
// 创建新的音频源连接但不连接到destination
this.source = this.audioContext.createMediaElementSource(this.audio);
this.source.connect(this.analyser);
console.log('✅ 备用连接方式成功');
console.log('连接链路: 音频元素 → MediaElementSource → 分析器跳过destination');
return true;
} catch (error) {
console.error('❌ 备用连接方式失败:', error);
return false;
}
}
reinitializeAudioConnection() {
console.log('🔄 重新初始化音频连接...');
try {
// 1. 完全重置音频元素
const currentSrc = this.audio.src;
const currentTime = this.audio.currentTime;
const wasPlaying = !this.audio.paused;
console.log('保存当前播放状态:', { currentTime, wasPlaying });
// 2. 暂停并清理
if (wasPlaying) {
this.audio.pause();
}
// 3. 完全断开所有连接
if (this.source) {
try {
this.source.disconnect();
this.source = null;
} catch (e) {
console.log('清理旧连接失败:', e);
}
}
// 4. 重新设置音频源(强制重置)
this.audio.src = '';
this.audio.removeAttribute('src');
// 5. 重新设置跨域属性和音频源
this.audio.crossOrigin = 'anonymous';
this.audio.src = currentSrc;
this.audio.load();
// 6. 等待音频准备好
return new Promise((resolve) => {
const onCanPlay = () => {
console.log('音频重新加载完成');
this.audio.removeEventListener('canplay', onCanPlay);
// 恢复播放位置
this.audio.currentTime = currentTime;
// 重新连接音频源
try {
this.source = this.audioContext.createMediaElementSource(this.audio);
this.source.connect(this.analyser);
this.analyser.connect(this.audioContext.destination);
console.log('✅ 重新初始化音频连接成功');
resolve(true);
} catch (e) {
console.error('重新连接失败:', e);
resolve(false);
}
};
this.audio.addEventListener('canplay', onCanPlay, { once: true });
});
} catch (error) {
console.error('重新初始化失败:', error);
return false;
}
}
play() { play() {
if (this.audio.src) { if (this.audio.src) {
console.log('开始播放,音频源:', this.audio.src);
console.log('网络音乐模式:', this.isNetworkMusic);
// 确保音频上下文处于运行状态 // 确保音频上下文处于运行状态
if (this.audioContext && this.audioContext.state === 'suspended') { if (this.audioContext && this.audioContext.state === 'suspended') {
this.audioContext.resume(); this.audioContext.resume();
} }
this.audio.play().then(() => { // 关键:确保音频源已连接,无论是网络音乐还是本地音乐
this.startVisualization(); if (!this.source) {
this.updatePlayPauseButton(true); console.log('音频源未连接,尝试重新连接');
document.querySelector('.audio-player').classList.add('playing'); this.setupAudioContext();
}).catch(error => {
console.error('播放失败:', error); // 延迟播放,等待音频源连接
alert('播放失败,请检查音频文件'); setTimeout(() => {
}); if (this.source) {
console.log('音频源已连接,继续播放');
this.performPlay();
} else {
console.error('音频源连接失败,尝试直接播放');
this.performPlay();
}
}, 500);
return;
}
// 确保音频元素已准备好
if (this.audio.readyState < 2) {
console.log('音频尚未准备好等待canplay事件');
this.audio.addEventListener('canplay', () => {
console.log('音频已准备好,开始播放');
this.performPlay();
}, { once: true });
return;
}
this.performPlay();
} }
} }
performPlay() {
console.log('执行播放操作');
// 确保音量未被静音且设置正确
console.log('当前音量设置:', this.audio.volume);
console.log('静音状态:', this.isMuted);
// 如果是静音状态,临时取消静音用于测试
if (this.isMuted) {
this.audio.volume = 0.5;
this.isMuted = false;
this.updateVolumeIcon(50);
console.log('已临时取消静音');
}
this.audio.play().then(() => {
console.log('播放成功,启动可视化');
console.log('实际播放音量:', this.audio.volume);
console.log('播放状态:', this.audio.paused ? '暂停' : '播放中');
// 可视化
this.startVisualization();
this.updatePlayPauseButton(true);
document.querySelector('.audio-player').classList.add('playing');
}).catch(error => {
if (error.name === 'NotAllowedError') {
console.error('播放被阻止,请点击播放按钮手动开始播放');
} else {
console.error('播放失败: ' + error.message);
}
});
}
pause() { pause() {
this.audio.pause(); this.audio.pause();
this.stopVisualization(); this.stopVisualization();
@ -487,23 +874,75 @@ class AudioPlayer {
} }
startVisualization() { startVisualization() {
if (!this.waveformVisualizer) { if (!this.waveformVisualizer) {
console.warn('可视化器未初始化,使用备用方案'); console.warn('可视化器未初始化,使用备用方案');
this.startBasicVisualization(); this.startBasicVisualization();
return; return;
} }
if (!this.analyser) {
console.error('分析器未创建,无法开始可视化');
return;
}
// 关键:确保音频源连接,无论是网络音乐还是本地音乐
if (!this.source) {
console.warn('音频源未连接,尝试重新连接');
// 强制重新设置音频上下文
this.setupAudioContext();
// 尝试连接音频源
const connected = this.connectAudioSource();
if (!connected) {
// 尝试备用连接方式
console.warn('常规连接失败,尝试备用连接方式...');
const altConnected = this.tryAlternativeConnection();
if (!altConnected) {
// 延迟开始可视化,等待连接完成
setTimeout(() => {
console.log('延迟后开始可视化检查');
if (this.source) {
console.log('✅ 音频源已连接,开始可视化');
this.startVisualization();
} else {
console.error('❌ 音频源连接失败,使用基础可视化');
this.startBasicVisualization();
}
}, 1500);
return;
}
}
}
const draw = () => { const draw = () => {
this.animationId = requestAnimationFrame(draw); this.animationId = requestAnimationFrame(draw);
// 测试频率数据
if (this.analyser && this.dataArray) {
this.analyser.getByteFrequencyData(this.dataArray);
}
this.waveformVisualizer.draw(); this.waveformVisualizer.draw();
}; };
draw(); draw();
console.log('可视化已启动');
} }
startBasicVisualization() { startBasicVisualization() {
// 备用基础可视化方案 // 备用基础可视化方案
if (!this.analyser) return; if (!this.analyser) return;
console.log('启动基础可视化方案');
if (!this.analyser || !this.dataArray) {
console.error('基础可视化失败:分析器或数据数组未准备好');
return;
}
const draw = () => { const draw = () => {
this.animationId = requestAnimationFrame(draw); this.animationId = requestAnimationFrame(draw);
@ -634,12 +1073,12 @@ class AudioPlayer {
} }
break; break;
case 'n': case 'n':
if (this.isPlayingFolder) { if (this.isPlayingFolder && !this.isNetworkMusic) {
this.nextTrack(); this.nextTrack();
} }
break; break;
case 'p': case 'p':
if (this.isPlayingFolder) { if (this.isPlayingFolder && !this.isNetworkMusic) {
this.previousTrack(); this.previousTrack();
} }
break; break;
@ -660,7 +1099,204 @@ class AudioPlayer {
this.updateNavigationButtons(); this.updateNavigationButtons();
} }
checkUrlParams() {
// 获取URL参数
const urlParams = new URLSearchParams(window.location.search);
let filePath = urlParams.get('filePath');
let fileName = urlParams.get('fileName') || '网络音乐';
try {
filePath = decodeURIComponent(decodeURIComponent(filePath));
} catch (e) {
}
try {
fileName = decodeURIComponent(decodeURIComponent(fileName));
} catch (e) {
}
if (filePath) {
this.loadNetworkMusic(filePath, fileName);
}
}
loadNetworkMusic(filePath, fileName) {
try {
console.log('开始加载网络音乐:', filePath, fileName);
// 设置网络音乐状态
this.isNetworkMusic = true;
this.networkMusicUrl = filePath;
this.networkMusicName = fileName;
// 隐藏文件选择按钮
const fileSelector = document.getElementById('fileSelector');
const networkMusicInfo = document.getElementById('networkMusicInfo');
//const networkMusicNameEl = document.getElementById('networkMusicName');
if (fileSelector) {
fileSelector.style.display = 'none';
}
// if (networkMusicInfo) {
// networkMusicInfo.style.display = 'block';
// networkMusicNameEl.textContent = fileName;
// }
// 清理旧的音频源
if (this.audio.src && !this.isNetworkMusic) {
URL.revokeObjectURL(this.audio.src);
}
// 设置音频源 - 关键:先停止当前播放
this.stop();
// 关键:重置音频元素,确保正确的设置顺序
console.log('设置网络音频源URL:', filePath);
// 1. 设置crossOrigin避免CORS错误
this.audio.crossOrigin = 'anonymous';
// 2. 设置音频源
this.audio.src = filePath;
console.log('音频源已设置:', this.audio.src);
// 3. 强制重新加载
this.audio.load();
console.log('音频加载已触发');
// 关键确保音频元素独立播放不依赖Web Audio API连接
console.log('设置网络音乐独立播放模式');
// 检查并重置音量设置
const currentVolume = this.volumeSlider.value / 100;
this.audio.volume = currentVolume;
this.isMuted = false;
console.log('网络音乐音量设置为:', currentVolume);
this.trackName.textContent = fileName;
// 显示加载提示
this.showLoadingFeedback('正在加载网络音乐...');
// 监听加载错误 - 更详细的错误处理
this.audio.addEventListener('error', (e) => {
console.error('网络音乐加载失败:', e);
console.error('错误代码:', this.audio.error?.code);
console.error('错误信息:', this.audio.error?.message);
let errorMsg = '网络音乐加载失败';
if (this.audio.error) {
switch (this.audio.error.code) {
case 1: errorMsg = '音频下载被中止'; break;
case 2: errorMsg = '网络错误'; break;
case 3: errorMsg = '音频解码错误'; break;
case 4: errorMsg = '音频格式不支持'; break;
default: errorMsg = '未知错误请检查URL是否有效且支持跨域访问';
}
}
this.showErrorFeedback(errorMsg);
}, { once: true });
// 监听加载成功 - 使用canplaythrough事件确保音频可以完整播放
this.audio.addEventListener('canplaythrough', () => {
console.log('网络音乐可以完整播放了');
this.showSuccessFeedback('网络音乐加载成功!');
// 检查音频格式和准备状态
console.log('音频就绪状态:', this.audio.readyState);
console.log('音频格式:', this.audio.canPlayType(this.audio.src));
// 尝试自动播放
this.play();
}, { once: true });
// 监听播放事件
this.audio.addEventListener('play', () => {
console.log('网络音乐开始播放');
console.log('播放状态:', this.audio.paused ? '暂停' : '播放中');
console.log('当前音量:', this.audio.volume);
console.log('静音状态:', this.isMuted);
}, { once: true });
// 监听加载进度
this.audio.addEventListener('loadstart', () => {
console.log('开始加载音频...');
});
this.audio.addEventListener('progress', () => {
if (this.audio.buffered.length > 0) {
const buffered = this.audio.buffered.end(0);
const duration = this.audio.duration;
console.log(`加载进度: ${buffered}/${duration}`);
}
});
} catch (error) {
console.error('加载网络音乐失败:', error);
this.showErrorFeedback('加载网络音乐失败: ' + error.message);
}
}
showLoadingFeedback(message) {
const feedback = document.createElement('div');
feedback.textContent = message;
feedback.className = 'visualization-feedback';
feedback.style.background = 'rgba(102, 126, 234, 0.9)';
document.body.appendChild(feedback);
setTimeout(() => {
if (feedback.parentNode) {
feedback.parentNode.removeChild(feedback);
}
}, 2000);
}
showSuccessFeedback(message) {
const feedback = document.createElement('div');
feedback.textContent = message;
feedback.className = 'visualization-feedback';
feedback.style.background = 'rgba(76, 175, 80, 0.9)';
document.body.appendChild(feedback);
setTimeout(() => {
if (feedback.parentNode) {
feedback.parentNode.removeChild(feedback);
}
}, 2000);
}
showErrorFeedback(message) {
const feedback = document.createElement('div');
feedback.textContent = message;
feedback.className = 'visualization-feedback';
feedback.style.background = 'rgba(244, 67, 54, 0.9)';
document.body.appendChild(feedback);
setTimeout(() => {
if (feedback.parentNode) {
feedback.parentNode.removeChild(feedback);
}
}, 3000);
}
updateNavigationButtons() { updateNavigationButtons() {
// 网络音乐模式下禁用所有导航按钮
if (this.isNetworkMusic) {
this.prevBtn.disabled = true;
this.nextBtn.disabled = true;
this.playbackModeBtn.disabled = true;
this.prevBtn.style.opacity = '0.3';
this.nextBtn.style.opacity = '0.3';
this.playbackModeBtn.style.opacity = '0.3';
return;
}
// 文件夹模式下的正常处理
const hasPlaylist = this.isPlayingFolder && this.playlist.length > 0; const hasPlaylist = this.isPlayingFolder && this.playlist.length > 0;
this.prevBtn.disabled = !hasPlaylist; this.prevBtn.disabled = !hasPlaylist;
this.nextBtn.disabled = !hasPlaylist; this.nextBtn.disabled = !hasPlaylist;
@ -677,6 +1313,89 @@ class AudioPlayer {
} }
} }
startAudioDiagnostics() {
// 启动音频连接状态监控
if (this.diagnosticsInterval) {
clearInterval(this.diagnosticsInterval);
}
let diagnosticCount = 0;
this.diagnosticsInterval = setInterval(() => {
diagnosticCount++;
if (diagnosticCount > 10) { // 最多监控10次
clearInterval(this.diagnosticsInterval);
return;
}
console.log(`🔍 音频诊断 #${diagnosticCount}:`);
console.log(` 播放状态: ${this.audio.paused ? '暂停' : '播放中'}`);
console.log(` 当前时间: ${this.audio.currentTime.toFixed(2)}s`);
console.log(` 音频时长: ${this.audio.duration ? this.audio.duration.toFixed(2) + 's' : '未知'}`);
console.log(` 音量: ${(this.audio.volume * 100).toFixed(0)}%`);
console.log(` 音频源连接: ${this.source ? '已连接' : '未连接'}`);
console.log(` 音频上下文状态: ${this.audioContext ? this.audioContext.state : '未创建'}`);
// 检查音频是否正在播放但没有声音
if (!this.audio.paused && this.audio.currentTime > 0 && !this.source) {
console.warn('⚠️ 警告:音频正在播放但没有音频源连接!');
console.log('💡 尝试重新连接音频源...');
this.setupAudioContext();
}
// 检查音频上下文是否被暂停
if (this.audioContext && this.audioContext.state === 'suspended') {
console.log('🔧 音频上下文被暂停,尝试恢复...');
this.audioContext.resume();
}
// 检查可视化数据
if (this.analyser && this.dataArray && !this.audio.paused) {
this.analyser.getByteFrequencyData(this.dataArray);
const hasData = this.dataArray.some(value => value > 0);
if (!hasData && diagnosticCount > 3) {
console.warn('⚠️ 无可视化数据,尝试强制重连...');
this.forceReconnectAudio();
}
}
}, 2000); // 每2秒检查一次
}
forceReconnectAudio() {
console.log('🔄 执行强制音频重连...');
try {
// 1. 重新创建音频上下文(如果需要)
if (!this.audioContext || this.audioContext.state === 'closed') {
console.log('重新创建音频上下文');
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
this.analyser = this.audioContext.createAnalyser();
this.analyser.fftSize = 2048;
const bufferLength = this.analyser.frequencyBinCount;
this.dataArray = new Uint8Array(bufferLength);
// 重新初始化可视化器
if (this.waveformVisualizer) {
this.waveformVisualizer = new WaveformVisualizer(this.canvas, this.audioContext, this.analyser);
}
}
// 2. 确保音频上下文运行
if (this.audioContext.state === 'suspended') {
this.audioContext.resume();
}
// 3. 使用新的连接方法
console.log('使用新的连接方法');
this.connectAudioSource();
console.log('✅ 强制重连完成');
} catch (error) {
console.error('强制重连失败:', error);
}
}
togglePlayPause() { togglePlayPause() {
if (this.audio.paused) { if (this.audio.paused) {
this.play(); this.play();

View File

@ -18,8 +18,8 @@ body {
.audio-player { .audio-player {
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
border-radius: 20px; border-radius: 10px;
padding: 30px; padding: 10px;
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37); box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
border: 1px solid rgba(255, 255, 255, 0.18); border: 1px solid rgba(255, 255, 255, 0.18);
width: 100%; width: 100%;
@ -49,6 +49,46 @@ body {
background: linear-gradient(45deg, #667eea, #764ba2); background: linear-gradient(45deg, #667eea, #764ba2);
} }
/* 网络音乐信息 */
.network-music-info {
margin: 15px 0;
padding: 15px;
background: linear-gradient(45deg, #4facfe, #00f2fe);
border-radius: 15px;
text-align: center;
box-shadow: 0 4px 15px rgba(79, 172, 254, 0.4);
border: 1px solid rgba(255, 255, 255, 0.3);
animation: fadeIn 0.5s ease-out;
}
.network-music-info p {
margin: 5px 0;
font-weight: 500;
color: white;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
}
.network-music-info p:first-child {
font-size: 18px;
margin-bottom: 8px;
}
.network-music-info p:last-child {
font-size: 16px;
opacity: 0.9;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.file-selector input[type="file"] { .file-selector input[type="file"] {
display: none; display: none;
} }

View File

@ -4,7 +4,24 @@ export default defineConfig({
// Vite配置选项 // Vite配置选项
server: { server: {
port: 3000, port: 3000,
open: true open: false,
host: false,
cors: true,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization, Range',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Expose-Headers': 'Content-Length, Content-Range'
},
proxy: {
'/DocServer': {
target: 'http://192.168.1.201:8080',
changeOrigin: true,
secure: false,
rewrite: (path) => path.replace(/^\/DocServer/, '/DocServer')
}
}
}, },
build: { build: {
outDir: 'dist', outDir: 'dist',