@ -20,6 +20,11 @@ class AudioPlayer {
this . isPlayingFolder = false ;
this . playbackMode = 'loop' ; // 默认列表循环模式:'single', 'loop', 'random'
// 网络音乐相关
this . isNetworkMusic = false ;
this . networkMusicUrl = null ;
this . networkMusicName = null ;
// Web Audio API警告标志
this . webAudioApiWarningShown = false ;
@ -31,6 +36,7 @@ class AudioPlayer {
this . setupCanvas ( ) ;
this . setupKeyboardShortcuts ( ) ;
this . initializePlayerState ( ) ;
this . checkUrlParams ( ) ; // 检查URL参数
}
initializeElements ( ) {
@ -164,6 +170,12 @@ class AudioPlayer {
}
handleFileSelect ( event ) {
// 如果正在播放网络音乐,不允许选择本地文件
if ( this . isNetworkMusic ) {
alert ( '正在播放网络音乐,请先停止当前播放再选择本地文件' ) ;
return ;
}
const file = event . target . files [ 0 ] ;
if ( file ) {
// 单个文件播放模式
@ -172,7 +184,7 @@ class AudioPlayer {
this . currentTrackIndex = 0 ;
// 清理旧的URL
if ( this . audio . src ) {
if ( this . audio . src && ! this . isNetworkMusic ) {
URL . revokeObjectURL ( this . audio . src ) ;
}
@ -186,10 +198,20 @@ class AudioPlayer {
// 设置音频上下文(如果尚未初始化)
this . setupAudioContext ( ) ;
// 关键:本地音乐也需要连接音频源
console . log ( '本地音乐文件已选择,准备连接音频源' ) ;
this . connectAudioSource ( ) ;
}
}
handleFolderSelect ( event ) {
// 如果正在播放网络音乐,不允许选择本地文件夹
if ( this . isNetworkMusic ) {
alert ( '正在播放网络音乐,请先停止当前播放再选择本地文件夹' ) ;
return ;
}
const files = Array . from ( event . target . files ) ;
if ( files . length > 0 ) {
// 筛选音频文件 - 支持更多格式
@ -206,6 +228,7 @@ class AudioPlayer {
this . playlist = audioFiles ;
this . currentTrackIndex = 0 ;
this . isPlayingFolder = true ;
this . isNetworkMusic = false ; // 切换到本地模式
// 确保播放模式显示正确(默认列表循环)
this . playbackMode = 'loop' ;
@ -271,8 +294,13 @@ class AudioPlayer {
// 设置自动播放状态
this . shouldAutoPlay = shouldAutoPlay ;
// 关键:文件夹中的本地音乐也需要连接音频源
console . log ( '文件夹音乐已加载,准备连接音频源' ) ;
this . connectAudioSource ( ) ;
// 等待音频数据加载完成后播放
const tryAutoPlay = ( ) => {
const tryAutoPlay = ( e ) => {
console . log ( '尝试自动播放' , e ) ;
if ( this . audio . readyState >= 2 && ( ! this . audio . paused || this . shouldAutoPlay ) ) {
this . play ( ) ;
}
@ -336,13 +364,11 @@ class AudioPlayer {
console . log ( '音频上下文和可视化器初始化成功' ) ;
}
// 连接音频源(只在首次或需要重新连接时)
if ( this . audioContext && this . analyser && ! this . source ) {
this . source = this . audioContext . createMediaElementSource ( this . audio ) ;
this . source . connect ( this . analyser ) ;
this . analyser . connect ( this . audioContext . destination ) ;
console . log ( '音频源连接成功' ) ;
// 确保音频上下文处于运行状态
if ( this . audioContext . state === 'suspended' ) {
this . audioContext . resume ( ) ;
}
} catch ( error ) {
console . error ( '音频上下文初始化失败:' , error ) ;
if ( ! this . webAudioApiWarningShown ) {
@ -352,24 +378,386 @@ 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 ( ) {
if ( this . audio . src ) {
console . log ( '开始播放,音频源:' , this . audio . src ) ;
console . log ( '网络音乐模式:' , this . isNetworkMusic ) ;
// 确保音频上下文处于运行状态
if ( this . audioContext && this . audioContext . state === 'suspended' ) {
this . audioContext . resume ( ) ;
}
this . audio . play ( ) . then ( ( ) => {
this . startVisualization ( ) ;
this . updatePlayPauseButton ( true ) ;
document . querySelector ( '.audio-player' ) . classList . add ( 'playing' ) ;
} ) . catch ( error => {
console . error ( '播放失败:' , error ) ;
alert ( '播放失败,请检查音频文件' ) ;
} ) ;
// 关键:确保音频源已连接,无论是网络音乐还是本地音乐
if ( ! this . source ) {
console . log ( '音频源未连接,尝试重新连接' ) ;
this . setupAudioContext ( ) ;
// 延迟播放,等待音频源连接
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 ( ) {
this . audio . pause ( ) ;
this . stopVisualization ( ) ;
@ -487,24 +875,76 @@ class AudioPlayer {
}
startVisualization ( ) {
if ( ! this . waveformVisualizer ) {
console . warn ( '可视化器未初始化,使用备用方案' ) ;
this . startBasicVisualization ( ) ;
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 = ( ) => {
this . animationId = requestAnimationFrame ( draw ) ;
// 测试频率数据
if ( this . analyser && this . dataArray ) {
this . analyser . getByteFrequencyData ( this . dataArray ) ;
}
this . waveformVisualizer . draw ( ) ;
} ;
draw ( ) ;
console . log ( '可视化已启动' ) ;
}
startBasicVisualization ( ) {
// 备用基础可视化方案
if ( ! this . analyser ) return ;
console . log ( '启动基础可视化方案' ) ;
if ( ! this . analyser || ! this . dataArray ) {
console . error ( '基础可视化失败:分析器或数据数组未准备好' ) ;
return ;
}
const draw = ( ) => {
this . animationId = requestAnimationFrame ( draw ) ;
@ -634,12 +1074,12 @@ class AudioPlayer {
}
break ;
case 'n' :
if ( this . isPlayingFolder ) {
if ( this . isPlayingFolder && ! this . isNetworkMusic ) {
this . nextTrack ( ) ;
}
break ;
case 'p' :
if ( this . isPlayingFolder ) {
if ( this . isPlayingFolder && ! this . isNetworkMusic ) {
this . previousTrack ( ) ;
}
break ;
@ -660,7 +1100,204 @@ class AudioPlayer {
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 && 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 ( ) {
// 网络音乐模式下禁用所有导航按钮
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 ;
this . prevBtn . disabled = ! hasPlaylist ;
this . nextBtn . disabled = ! hasPlaylist ;
@ -677,6 +1314,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 ( ) {
if ( this . audio . paused ) {
this . play ( ) ;