Initial commit
This commit is contained in:
250
lib/background-music.ts
Normal file
250
lib/background-music.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
// 背景音樂管理系統 - 修復重新播放問題
|
||||
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()
|
Reference in New Issue
Block a user