From 8ee2b928f92c33eed0dea1f38d03d08d86c0ceb9 Mon Sep 17 00:00:00 2001 From: Astrian Zheng Date: Wed, 4 Jun 2025 22:23:34 +1000 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=A7=A3=E5=86=B3=20Safari=20=E6=B5=8F?= =?UTF-8?q?=E8=A7=88=E5=99=A8=E9=9F=B3=E9=A2=91=E6=92=AD=E6=94=BE=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建浏览器检测工具,专门检测 Safari 和音频可视化支持 - 在 Safari 浏览器上禁用 AudioContext 连接,避免播放问题 - 保持其他浏览器的音频可视化功能正常工作 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/components/Player.vue | 69 ++++++++++++++++-------- src/utils/browserDetection.ts | 98 +++++++++++++++++++++++++++++++++++ src/utils/index.ts | 13 ++++- 3 files changed, 156 insertions(+), 24 deletions(-) create mode 100644 src/utils/browserDetection.ts diff --git a/src/components/Player.vue b/src/components/Player.vue index 605fd04..55bf6cc 100644 --- a/src/components/Player.vue +++ b/src/components/Player.vue @@ -7,7 +7,7 @@ import { usePlayQueueStore } from '../stores/usePlayQueueStore' import LoadingIndicator from '../assets/icons/loadingindicator.vue' import PlayIcon from '../assets/icons/play.vue' -import { audioVisualizer, checkAndRefreshSongResource } from '../utils' +import { audioVisualizer, checkAndRefreshSongResource, supportsWebAudioVisualization } from '../utils' const playQueueStore = usePlayQueueStore() const favourites = useFavourites() @@ -263,21 +263,40 @@ function updateCurrentTime() { } } -console.log('[Player] 初始化 audioVisualizer') -const { barHeights, connectAudio, isAnalyzing, error } = audioVisualizer({ - sensitivity: 1.5, - barCount: 6, - maxDecibels: -10, - bassBoost: 0.8, - midBoost: 1.2, - trebleBoost: 1.4, - threshold: 0, -}) +// 检查浏览器是否支持音频可视化 +const isAudioVisualizationSupported = supportsWebAudioVisualization() +console.log('[Player] 音频可视化支持状态:', isAudioVisualizationSupported) -console.log('[Player] audioVisualizer 返回值:', { - barHeights: barHeights.value, - isAnalyzing: isAnalyzing.value, -}) +// 只在支持的浏览器上初始化音频可视化 +let barHeights = ref([0, 0, 0, 0, 0, 0]) +let connectAudio = (_audio: HTMLAudioElement) => {} +let isAnalyzing = ref(false) +let error = ref(null) + +if (isAudioVisualizationSupported) { + console.log('[Player] 初始化 audioVisualizer') + const visualizer = audioVisualizer({ + sensitivity: 1.5, + barCount: 6, + maxDecibels: -10, + bassBoost: 0.8, + midBoost: 1.2, + trebleBoost: 1.4, + threshold: 0, + }) + + barHeights = visualizer.barHeights + connectAudio = visualizer.connectAudio + isAnalyzing = visualizer.isAnalyzing + error = visualizer.error + + console.log('[Player] audioVisualizer 返回值:', { + barHeights: barHeights.value, + isAnalyzing: isAnalyzing.value, + }) +} else { + console.log('[Player] 音频可视化被禁用(Safari 或不支持的浏览器)') +} // 监听播放列表变化 watch( @@ -293,13 +312,17 @@ watch( await nextTick() if (player.value) { - console.log('[Player] 连接音频元素到可视化器') - console.log('[Player] 音频元素状态:', { - src: player.value.src?.substring(0, 50) + '...', - readyState: player.value.readyState, - paused: player.value.paused, - }) - connectAudio(player.value) + if (isAudioVisualizationSupported) { + console.log('[Player] 连接音频元素到可视化器') + console.log('[Player] 音频元素状态:', { + src: player.value.src?.substring(0, 50) + '...', + readyState: player.value.readyState, + paused: player.value.paused, + }) + connectAudio(player.value) + } else { + console.log('[Player] 跳过音频可视化连接(不支持的浏览器)') + } } else { console.log('[Player] ❌ 音频元素不存在') } @@ -322,7 +345,7 @@ watch( watch( () => player.value, (audioElement) => { - if (audioElement && playQueueStore.list.length > 0) { + if (audioElement && playQueueStore.list.length > 0 && isAudioVisualizationSupported) { connectAudio(audioElement) } }, diff --git a/src/utils/browserDetection.ts b/src/utils/browserDetection.ts new file mode 100644 index 0000000..28ba2e9 --- /dev/null +++ b/src/utils/browserDetection.ts @@ -0,0 +1,98 @@ +/** + * 浏览器检测工具 + */ + +/** + * 检测是否为 Safari 浏览器 + * @returns {boolean} 如果是 Safari 返回 true,否则返回 false + */ +export function isSafari(): boolean { + const ua = navigator.userAgent.toLowerCase() + + // 检测 Safari 浏览器(包括 iOS 和 macOS) + // Safari 的 User Agent 包含 'safari' 但不包含 'chrome' 或 'chromium' + const isSafariBrowser = ua.includes('safari') && + !ua.includes('chrome') && + !ua.includes('chromium') && + !ua.includes('android') + + // 额外检查:使用 Safari 特有的 API + const isSafariByFeature = 'safari' in window || + /^((?!chrome|android).)*safari/i.test(navigator.userAgent) + + return isSafariBrowser || isSafariByFeature +} + +/** + * 检测是否为移动版 Safari + * @returns {boolean} 如果是移动版 Safari 返回 true,否则返回 false + */ +export function isMobileSafari(): boolean { + return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream +} + +/** + * 检测是否支持 Web Audio API 的完整功能 + * @returns {boolean} 如果支持返回 true,否则返回 false + */ +export function supportsWebAudioVisualization(): boolean { + // Safari 在某些情况下对 AudioContext 的支持有限制 + // 特别是在处理跨域音频资源时 + if (isSafari()) { + console.log('[BrowserDetection] Safari detected, audio visualization disabled') + return false + } + + // 检查基本的 Web Audio API 支持 + const hasAudioContext = 'AudioContext' in window || 'webkitAudioContext' in window + const hasAnalyserNode = hasAudioContext && ( + 'AnalyserNode' in window || + (window.AudioContext && 'createAnalyser' in AudioContext.prototype) + ) + + return hasAudioContext && hasAnalyserNode +} + +/** + * 获取浏览器信息 + * @returns {object} 包含浏览器类型和版本信息的对象 + */ +export function getBrowserInfo() { + const ua = navigator.userAgent + let browserName = 'Unknown' + let browserVersion = 'Unknown' + + if (isSafari()) { + browserName = 'Safari' + const versionMatch = ua.match(/Version\/(\d+\.\d+)/) + if (versionMatch) { + browserVersion = versionMatch[1] + } + } else if (ua.includes('Chrome')) { + browserName = 'Chrome' + const versionMatch = ua.match(/Chrome\/(\d+\.\d+)/) + if (versionMatch) { + browserVersion = versionMatch[1] + } + } else if (ua.includes('Firefox')) { + browserName = 'Firefox' + const versionMatch = ua.match(/Firefox\/(\d+\.\d+)/) + if (versionMatch) { + browserVersion = versionMatch[1] + } + } else if (ua.includes('Edge')) { + browserName = 'Edge' + const versionMatch = ua.match(/Edge\/(\d+\.\d+)/) + if (versionMatch) { + browserVersion = versionMatch[1] + } + } + + return { + name: browserName, + version: browserVersion, + isSafari: isSafari(), + isMobileSafari: isMobileSafari(), + supportsAudioVisualization: supportsWebAudioVisualization() + } +} \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts index 5f850e4..67aee1a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -2,5 +2,16 @@ import artistsOrganize from "./artistsOrganize" import { audioVisualizer } from "./audioVisualizer" import cicdInfo from "./cicdInfo" import { checkAndRefreshSongResource, checkAndRefreshMultipleSongs } from "./songResourceChecker" +import { isSafari, isMobileSafari, supportsWebAudioVisualization, getBrowserInfo } from "./browserDetection" -export { artistsOrganize, audioVisualizer, cicdInfo, checkAndRefreshSongResource, checkAndRefreshMultipleSongs } +export { + artistsOrganize, + audioVisualizer, + cicdInfo, + checkAndRefreshSongResource, + checkAndRefreshMultipleSongs, + isSafari, + isMobileSafari, + supportsWebAudioVisualization, + getBrowserInfo +}