Files
wish-pool/lib/background-music.ts
2025-07-18 13:07:28 +08:00

251 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 背景音樂管理系統 - 修復重新播放問題
class BackgroundMusicManager {
private audio: HTMLAudioElement | null = null
private isPlaying = false
private enabled = false
private volume = 0.3
private fadeInterval: NodeJS.Timeout | null = null
constructor() {
this.initAudio()
this.loadState()
}
private loadState() {
try {
const savedState = localStorage.getItem("backgroundMusicState")
if (savedState) {
const state = JSON.parse(savedState)
this.volume = state.volume || 0.3
this.enabled = state.enabled || false
// 不要自動恢復播放狀態,讓用戶手動控制
this.isPlaying = false
}
} catch (error) {
// 忽略載入錯誤
}
}
private saveState() {
try {
const state = {
volume: this.volume,
enabled: this.enabled,
isPlaying: this.isPlaying,
}
localStorage.setItem("backgroundMusicState", JSON.stringify(state))
} catch (error) {
// 忽略保存錯誤
}
}
private initAudio() {
try {
this.audio = new Audio("https://hebbkx1anhila5yf.public.blob.vercel-storage.com/just-relax-11157-iAgp15dV2YGybAezUJFtKmKZPbteXd.mp3")
this.audio.loop = true
this.audio.volume = this.volume
this.audio.preload = "metadata"
// 明確禁用自動播放相關屬性
this.audio.autoplay = false
this.audio.muted = false
// 靜默處理載入完成
this.audio.addEventListener("canplaythrough", () => {
// 音樂載入完成
})
// 靜默錯誤處理
this.audio.addEventListener("error", (e) => {
// 重新初始化音頻對象
this.reinitAudio()
})
// 播放結束處理
this.audio.addEventListener("ended", () => {
if (this.enabled && this.isPlaying) {
// 重新播放
this.audio?.play().catch(() => {
// 如果播放失敗,重新初始化
this.reinitAudio()
})
}
})
} catch (error) {
// 靜默處理初始化錯誤
}
}
// 重新初始化音頻對象
private reinitAudio() {
try {
if (this.audio) {
this.audio.pause()
this.audio.src = ""
this.audio = null
}
// 重新創建音頻對象
setTimeout(() => {
this.initAudio()
}, 100)
} catch (error) {
// 靜默處理錯誤
}
}
// 淡入效果
private fadeIn(duration = 2000) {
if (!this.audio) return
this.audio.volume = 0
const targetVolume = this.volume
const steps = 50
const stepTime = duration / steps
const volumeStep = targetVolume / steps
let currentStep = 0
this.fadeInterval = setInterval(() => {
if (currentStep >= steps || !this.audio) {
if (this.fadeInterval) {
clearInterval(this.fadeInterval)
this.fadeInterval = null
}
if (this.audio) {
this.audio.volume = targetVolume
}
return
}
this.audio.volume = Math.min(volumeStep * currentStep, targetVolume)
currentStep++
}, stepTime)
}
// 淡出效果
private fadeOut(duration = 1000) {
if (!this.audio) return
const startVolume = this.audio.volume
const steps = 50
const stepTime = duration / steps
const volumeStep = startVolume / steps
let currentStep = 0
this.fadeInterval = setInterval(() => {
if (currentStep >= steps || !this.audio) {
if (this.fadeInterval) {
clearInterval(this.fadeInterval)
this.fadeInterval = null
}
if (this.audio) {
this.audio.pause()
this.audio.currentTime = 0 // 重置播放位置
this.audio.volume = this.volume // 恢復原始音量
}
return
}
this.audio.volume = Math.max(startVolume - volumeStep * currentStep, 0)
currentStep++
}, stepTime)
}
async start() {
// 清除任何進行中的淡出效果
if (this.fadeInterval) {
clearInterval(this.fadeInterval)
this.fadeInterval = null
}
// 如果音頻對象不存在,重新初始化
if (!this.audio) {
this.initAudio()
// 等待一下讓音頻對象初始化完成
await new Promise((resolve) => setTimeout(resolve, 100))
}
if (!this.audio) return
try {
this.enabled = true
this.isPlaying = true
this.saveState()
// 確保音頻對象處於正確狀態
this.audio.currentTime = 0
this.audio.volume = 0 // 從0開始準備淡入
// 開始播放並淡入
await this.audio.play()
this.fadeIn(2000)
} catch (error) {
// 播放失敗,重新初始化並重試
this.reinitAudio()
this.isPlaying = false
this.enabled = false
this.saveState()
}
}
stop() {
if (!this.audio) return
this.enabled = false
this.isPlaying = false
this.saveState()
// 清除淡入效果
if (this.fadeInterval) {
clearInterval(this.fadeInterval)
this.fadeInterval = null
}
// 淡出並停止
this.fadeOut(1000)
}
setVolume(volume: number) {
this.volume = Math.max(0, Math.min(1, volume))
if (this.audio && this.isPlaying) {
this.audio.volume = this.volume
}
this.saveState()
}
getVolume() {
return this.volume
}
isEnabled() {
return this.enabled
}
getIsPlaying() {
return this.isPlaying && this.audio && !this.audio.paused
}
// 獲取當前狀態
getState() {
return {
isPlaying: this.getIsPlaying(),
enabled: this.enabled,
volume: this.volume,
}
}
// 獲取音樂資訊
getMusicInfo() {
if (!this.audio) return null
return {
duration: this.audio.duration || 0,
currentTime: this.audio.currentTime || 0,
loaded: this.audio.readyState >= 3,
}
}
}
// 全局背景音樂管理器
export const backgroundMusicManager = new BackgroundMusicManager()