fix: 解决 Safari 浏览器音频播放问题

- 创建浏览器检测工具,专门检测 Safari 和音频可视化支持
- 在 Safari 浏览器上禁用 AudioContext 连接,避免播放问题
- 保持其他浏览器的音频可视化功能正常工作

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Astrian Zheng 2025-06-04 22:23:34 +10:00
parent 5be5b4812f
commit 8ee2b928f9
Signed by: Astrian
SSH Key Fingerprint: SHA256:rVnhx3DAKjujCwWE13aDl7uV6+9U1MvydLkNRXJrBiA
3 changed files with 156 additions and 24 deletions

View File

@ -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,8 +263,19 @@ function updateCurrentTime() {
}
}
//
const isAudioVisualizationSupported = supportsWebAudioVisualization()
console.log('[Player] 音频可视化支持状态:', isAudioVisualizationSupported)
//
let barHeights = ref<number[]>([0, 0, 0, 0, 0, 0])
let connectAudio = (_audio: HTMLAudioElement) => {}
let isAnalyzing = ref(false)
let error = ref<string | null>(null)
if (isAudioVisualizationSupported) {
console.log('[Player] 初始化 audioVisualizer')
const { barHeights, connectAudio, isAnalyzing, error } = audioVisualizer({
const visualizer = audioVisualizer({
sensitivity: 1.5,
barCount: 6,
maxDecibels: -10,
@ -274,10 +285,18 @@ const { barHeights, connectAudio, isAnalyzing, error } = audioVisualizer({
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,6 +312,7 @@ watch(
await nextTick()
if (player.value) {
if (isAudioVisualizationSupported) {
console.log('[Player] 连接音频元素到可视化器')
console.log('[Player] 音频元素状态:', {
src: player.value.src?.substring(0, 50) + '...',
@ -300,6 +320,9 @@ watch(
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)
}
},

View File

@ -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()
}
}

View File

@ -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
}