mirror of
https://github.com/xmanrui/OpenClaw-bot-review.git
synced 2026-07-27 22:25:52 +00:00
feat(pixel-office): add toggleable background music with gesture retry
This commit is contained in:
@@ -19,7 +19,14 @@ import { TileType, EditTool } from '@/lib/pixel-office/types'
|
||||
import type { TileType as TileTypeVal, FloorColor, OfficeLayout } from '@/lib/pixel-office/types'
|
||||
import { getCatalogEntry, isRotatable } from '@/lib/pixel-office/layout/furnitureCatalog'
|
||||
import { createDefaultLayout, migrateLayoutColors, serializeLayout } from '@/lib/pixel-office/layout/layoutSerializer'
|
||||
import { playDoneSound, unlockAudio, setSoundEnabled, isSoundEnabled } from '@/lib/pixel-office/notificationSound'
|
||||
import {
|
||||
playDoneSound,
|
||||
playBackgroundMusic,
|
||||
stopBackgroundMusic,
|
||||
unlockAudio,
|
||||
setSoundEnabled,
|
||||
isSoundEnabled,
|
||||
} from '@/lib/pixel-office/notificationSound'
|
||||
import { loadCharacterPNGs, loadWallPNG } from '@/lib/pixel-office/sprites/pngLoader'
|
||||
import { useI18n } from '@/lib/i18n'
|
||||
import { EditorToolbar } from './components/EditorToolbar'
|
||||
@@ -424,6 +431,7 @@ export default function PixelOfficePage() {
|
||||
}
|
||||
|
||||
return () => {
|
||||
stopBackgroundMusic()
|
||||
cachedOfficeState = officeRef.current
|
||||
cachedEditorState = editorRef.current
|
||||
cachedSavedLayout = savedLayoutRef.current
|
||||
@@ -435,6 +443,14 @@ export default function PixelOfficePage() {
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (soundOn) {
|
||||
void playBackgroundMusic()
|
||||
} else {
|
||||
stopBackgroundMusic()
|
||||
}
|
||||
}, [soundOn])
|
||||
|
||||
useEffect(() => {
|
||||
cachedAgents = agents
|
||||
}, [agents])
|
||||
@@ -1090,6 +1106,7 @@ export default function PixelOfficePage() {
|
||||
const PHOTO_COUNT = 13
|
||||
const handleMouseDown = (e: React.MouseEvent) => {
|
||||
unlockAudio()
|
||||
if (soundOn) void playBackgroundMusic()
|
||||
if (!canvasRef.current || !officeRef.current) return
|
||||
const office = officeRef.current
|
||||
const editor = editorRef.current
|
||||
@@ -1531,6 +1548,11 @@ export default function PixelOfficePage() {
|
||||
setSoundEnabled(newVal)
|
||||
setSoundOn(newVal)
|
||||
localStorage.setItem('pixel-office-sound', String(newVal))
|
||||
if (newVal) {
|
||||
void playBackgroundMusic()
|
||||
} else {
|
||||
stopBackgroundMusic()
|
||||
}
|
||||
}, [])
|
||||
|
||||
const resetView = useCallback(() => {
|
||||
|
||||
@@ -9,9 +9,15 @@ import {
|
||||
|
||||
let soundEnabled = true
|
||||
let audioCtx: AudioContext | null = null
|
||||
let bgmAudio: HTMLAudioElement | null = null
|
||||
let bgmGestureRetryBound = false
|
||||
|
||||
const BGM_SRC = '/assets/pixel-office/pixel-adventure.mp3'
|
||||
const BGM_VOLUME = 0.28
|
||||
|
||||
export function setSoundEnabled(enabled: boolean): void {
|
||||
soundEnabled = enabled
|
||||
if (!enabled) stopBackgroundMusic()
|
||||
}
|
||||
|
||||
export function isSoundEnabled(): boolean {
|
||||
@@ -36,6 +42,51 @@ function playNote(ctx: AudioContext, freq: number, startOffset: number): void {
|
||||
osc.stop(t + NOTIFICATION_NOTE_DURATION_SEC)
|
||||
}
|
||||
|
||||
function getBgmAudio(): HTMLAudioElement | null {
|
||||
if (typeof window === 'undefined') return null
|
||||
if (!bgmAudio) {
|
||||
bgmAudio = new Audio(BGM_SRC)
|
||||
bgmAudio.loop = true
|
||||
bgmAudio.preload = 'auto'
|
||||
bgmAudio.volume = BGM_VOLUME
|
||||
}
|
||||
return bgmAudio
|
||||
}
|
||||
|
||||
function bindBgmGestureRetry(): void {
|
||||
if (typeof window === 'undefined' || bgmGestureRetryBound) return
|
||||
bgmGestureRetryBound = true
|
||||
|
||||
const cleanup = () => {
|
||||
if (typeof window === 'undefined' || !bgmGestureRetryBound) return
|
||||
bgmGestureRetryBound = false
|
||||
window.removeEventListener('pointerdown', resumeOnGesture)
|
||||
window.removeEventListener('touchstart', resumeOnGesture)
|
||||
window.removeEventListener('keydown', resumeOnGesture)
|
||||
}
|
||||
|
||||
const resumeOnGesture = () => {
|
||||
if (!soundEnabled) {
|
||||
cleanup()
|
||||
return
|
||||
}
|
||||
const audio = getBgmAudio()
|
||||
if (!audio) {
|
||||
cleanup()
|
||||
return
|
||||
}
|
||||
audio.play().then(() => {
|
||||
cleanup()
|
||||
}).catch(() => {
|
||||
// Keep listeners for next user gesture attempt.
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener('pointerdown', resumeOnGesture, { passive: true })
|
||||
window.addEventListener('touchstart', resumeOnGesture, { passive: true })
|
||||
window.addEventListener('keydown', resumeOnGesture)
|
||||
}
|
||||
|
||||
export async function playDoneSound(): Promise<void> {
|
||||
if (!soundEnabled) return
|
||||
try {
|
||||
@@ -64,3 +115,24 @@ export function unlockAudio(): void {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
export async function playBackgroundMusic(): Promise<void> {
|
||||
if (!soundEnabled) return
|
||||
try {
|
||||
const audio = getBgmAudio()
|
||||
if (!audio) return
|
||||
audio.muted = false
|
||||
audio.loop = true
|
||||
audio.volume = BGM_VOLUME
|
||||
await audio.play()
|
||||
} catch {
|
||||
// Browser autoplay may block playback until a user gesture.
|
||||
bindBgmGestureRetry()
|
||||
}
|
||||
}
|
||||
|
||||
export function stopBackgroundMusic(): void {
|
||||
if (!bgmAudio) return
|
||||
bgmAudio.pause()
|
||||
bgmAudio.currentTime = 0
|
||||
}
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user