feat(game): 添加游戏大厅音频控制和用户协议功能
- 实现音频资源配置和音频商店状态管理 - 添加用户协议和游戏规则的多语言支持 - 集成音频播放解锁机制和声音开关功能 - 更新API客户端以支持根路径候选 - 优化游戏历史记录组件的滚动加载逻辑 - 添加桌面端控制按钮的动画效果和交互反馈 - 实现语言切换和音效控制的UI组件 - 增加下注相关的状态管理和错误提示 - 完善应用偏好设置的存储和持久化逻辑
This commit is contained in:
19
src/features/game/audio/audio-config.ts
Normal file
19
src/features/game/audio/audio-config.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import hallMusic from '@/assets/music/hall-music.mp3'
|
||||
|
||||
export type AudioAssetId = 'hall-bgm'
|
||||
|
||||
export type AudioAssetDefinition = {
|
||||
id: AudioAssetId
|
||||
loop?: boolean
|
||||
src: string
|
||||
volume?: number
|
||||
}
|
||||
|
||||
export const AUDIO_ASSET_DEFINITIONS: AudioAssetDefinition[] = [
|
||||
{
|
||||
id: 'hall-bgm',
|
||||
src: hallMusic,
|
||||
loop: true,
|
||||
volume: 1,
|
||||
},
|
||||
]
|
||||
103
src/features/game/audio/global-audio-controller.tsx
Normal file
103
src/features/game/audio/global-audio-controller.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import {
|
||||
AUDIO_ASSET_DEFINITIONS,
|
||||
type AudioAssetDefinition,
|
||||
} from '@/features/game/audio/audio-config'
|
||||
import { useAudioStore } from '@/store'
|
||||
|
||||
function createAudioInstance(definition: AudioAssetDefinition) {
|
||||
const audio = new Audio(definition.src)
|
||||
audio.preload = 'auto'
|
||||
audio.loop = definition.loop ?? false
|
||||
audio.volume = definition.volume ?? 1
|
||||
|
||||
return audio
|
||||
}
|
||||
|
||||
export function GlobalAudioController() {
|
||||
const hasUnlockedSoundPlayback = useAudioStore(
|
||||
(state) => state.hasUnlockedSoundPlayback,
|
||||
)
|
||||
const isSoundEnabled = useAudioStore((state) => state.isSoundEnabled)
|
||||
|
||||
useEffect(() => {
|
||||
const audioEntries = AUDIO_ASSET_DEFINITIONS.map((definition) => ({
|
||||
audio: createAudioInstance(definition),
|
||||
definition,
|
||||
}))
|
||||
|
||||
let isDisposed = false
|
||||
let detachResumeListeners: (() => void) | null = null
|
||||
|
||||
const stopAllAudio = () => {
|
||||
audioEntries.forEach(({ audio }) => {
|
||||
audio.pause()
|
||||
audio.currentTime = 0
|
||||
})
|
||||
}
|
||||
|
||||
const playEnabledAudio = async () => {
|
||||
const audioState = useAudioStore.getState()
|
||||
|
||||
if (
|
||||
isDisposed ||
|
||||
!audioState.hasUnlockedSoundPlayback ||
|
||||
!audioState.isSoundEnabled
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const playResults = await Promise.allSettled(
|
||||
audioEntries.map(async ({ audio }) => {
|
||||
audio.currentTime = 0
|
||||
await audio.play()
|
||||
}),
|
||||
)
|
||||
|
||||
const hasBlockedAudio = playResults.some(
|
||||
(result) => result.status === 'rejected',
|
||||
)
|
||||
|
||||
if (!hasBlockedAudio || detachResumeListeners) {
|
||||
return
|
||||
}
|
||||
|
||||
const resumePlayback = () => {
|
||||
detachResumeListeners?.()
|
||||
detachResumeListeners = null
|
||||
void playEnabledAudio()
|
||||
}
|
||||
|
||||
const events: Array<keyof WindowEventMap> = [
|
||||
'pointerdown',
|
||||
'keydown',
|
||||
'touchstart',
|
||||
]
|
||||
|
||||
events.forEach((eventName) => {
|
||||
window.addEventListener(eventName, resumePlayback, { once: true })
|
||||
})
|
||||
|
||||
detachResumeListeners = () => {
|
||||
events.forEach((eventName) => {
|
||||
window.removeEventListener(eventName, resumePlayback)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (hasUnlockedSoundPlayback && isSoundEnabled) {
|
||||
void playEnabledAudio()
|
||||
} else {
|
||||
stopAllAudio()
|
||||
}
|
||||
|
||||
return () => {
|
||||
isDisposed = true
|
||||
detachResumeListeners?.()
|
||||
stopAllAudio()
|
||||
}
|
||||
}, [hasUnlockedSoundPlayback, isSoundEnabled])
|
||||
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user