|
import { |
|
createContext, |
|
useContext, |
|
useState, |
|
useCallback, |
|
useEffect, |
|
} from "react"; |
|
import { useSound } from "use-sound"; |
|
import { storyApi } from "../utils/api"; |
|
|
|
const SOUND_ENABLED_KEY = "sound_enabled"; |
|
|
|
|
|
const SOUNDS = { |
|
page: { |
|
files: Array.from( |
|
{ length: 7 }, |
|
(_, i) => `/sounds/page-flip-${i + 1}.mp3` |
|
), |
|
volume: 0.5, |
|
}, |
|
writing: { |
|
files: Array.from({ length: 5 }, (_, i) => `/sounds/drawing-${i + 1}.mp3`), |
|
volume: 0.3, |
|
}, |
|
transition: { |
|
files: Array.from( |
|
{ length: 3 }, |
|
(_, i) => `/sounds/transitional-swipe-${i + 1}.mp3` |
|
), |
|
volume: 0.1, |
|
}, |
|
talkySarah: { |
|
on: "/sounds/talky-walky-on.mp3", |
|
off: "/sounds/talky-walky-off.mp3", |
|
volume: 0.5, |
|
}, |
|
tick: { |
|
files: Array.from({ length: 4 }, (_, i) => `/sounds/tick-${i + 1}.mp3`), |
|
volume: { |
|
normal: 0.05, |
|
final: 0.4, |
|
}, |
|
}, |
|
lock: { |
|
files: ["/sounds/lock-1.mp3"], |
|
volume: 0.025, |
|
}, |
|
}; |
|
|
|
const SoundContext = createContext(null); |
|
|
|
export function SoundProvider({ children }) { |
|
const [isSoundEnabled, setIsSoundEnabled] = useState(() => { |
|
const stored = localStorage.getItem(SOUND_ENABLED_KEY); |
|
return stored === null ? true : stored === "true"; |
|
}); |
|
|
|
|
|
useEffect(() => { |
|
const handleInteraction = () => { |
|
storyApi.handleUserInteraction(); |
|
|
|
window.removeEventListener("click", handleInteraction); |
|
window.removeEventListener("touchstart", handleInteraction); |
|
window.removeEventListener("keydown", handleInteraction); |
|
}; |
|
|
|
window.addEventListener("click", handleInteraction); |
|
window.addEventListener("touchstart", handleInteraction); |
|
window.addEventListener("keydown", handleInteraction); |
|
|
|
return () => { |
|
window.removeEventListener("click", handleInteraction); |
|
window.removeEventListener("touchstart", handleInteraction); |
|
window.removeEventListener("keydown", handleInteraction); |
|
}; |
|
}, []); |
|
|
|
|
|
const soundInstances = {}; |
|
Object.entries(SOUNDS).forEach(([category, config]) => { |
|
if (category === "tick") { |
|
|
|
soundInstances[category] = { |
|
normal: config.files.map((file) => { |
|
const [play] = useSound(file, { volume: config.volume.normal }); |
|
return play; |
|
}), |
|
final: config.files.map((file) => { |
|
const [play] = useSound(file, { volume: config.volume.final }); |
|
return play; |
|
}), |
|
}; |
|
} else if (Array.isArray(config.files)) { |
|
|
|
soundInstances[category] = config.files.map((file) => { |
|
const [play] = useSound(file, { volume: config.volume }); |
|
return play; |
|
}); |
|
} else if (typeof config.files === "string") { |
|
|
|
const [play] = useSound(config.files, { volume: config.volume }); |
|
soundInstances[category] = play; |
|
} else { |
|
|
|
soundInstances[category] = {}; |
|
Object.entries(config).forEach(([key, value]) => { |
|
if (key !== "volume" && typeof value === "string") { |
|
const [play] = useSound(value, { volume: config.volume }); |
|
soundInstances[category][key] = play; |
|
} |
|
}); |
|
} |
|
}); |
|
|
|
|
|
useEffect(() => { |
|
localStorage.setItem(SOUND_ENABLED_KEY, isSoundEnabled); |
|
}, [isSoundEnabled]); |
|
|
|
|
|
const playSound = useCallback( |
|
(category, subCategory = null) => { |
|
if (!isSoundEnabled) return; |
|
|
|
try { |
|
if (category === "tick") { |
|
|
|
const type = subCategory || "normal"; |
|
const sounds = soundInstances[category][type]; |
|
const randomIndex = Math.floor(Math.random() * sounds.length); |
|
sounds[randomIndex]?.(); |
|
} else if (subCategory) { |
|
|
|
soundInstances[category][subCategory]?.(); |
|
} else if (Array.isArray(soundInstances[category])) { |
|
|
|
const randomIndex = Math.floor( |
|
Math.random() * soundInstances[category].length |
|
); |
|
soundInstances[category][randomIndex]?.(); |
|
} else { |
|
|
|
soundInstances[category]?.(); |
|
} |
|
} catch (error) { |
|
console.warn(`Error playing sound ${category}:`, error); |
|
} |
|
}, |
|
[isSoundEnabled, soundInstances] |
|
); |
|
|
|
const value = { |
|
isSoundEnabled, |
|
setIsSoundEnabled, |
|
playSound, |
|
}; |
|
|
|
return ( |
|
<SoundContext.Provider value={value}>{children}</SoundContext.Provider> |
|
); |
|
} |
|
|
|
export const useSoundSystem = () => { |
|
const context = useContext(SoundContext); |
|
if (!context) { |
|
throw new Error("useSoundSystem must be used within a SoundProvider"); |
|
} |
|
return context; |
|
}; |
|
|