251 lines
5.9 KiB
TypeScript
251 lines
5.9 KiB
TypeScript
// 背景音樂管理系統 - 修復重新播放問題
|
||
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()
|