fix(web): implement browser autoplay policy compliance with click-to-start

Add WaitingForInteraction game stage and Suspended audio state for Emscripten builds.
Audio unlocks and game starts after user clicks or presses any key, satisfying browser
autoplay restrictions while maintaining immediate playback on desktop.
This commit is contained in:
2025-12-29 00:41:31 -06:00
parent 191fe49c64
commit fc349c45c5
7 changed files with 207 additions and 12 deletions
+1
View File
@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "pacman-web",
+44 -2
View File
@@ -1,7 +1,15 @@
import { useEffect } from "react";
import { useCallback, useEffect, useState } from "react";
export default function Page() {
const [gameReady, setGameReady] = useState(false);
const [gameStarted, setGameStarted] = useState(false);
useEffect(() => {
// Set up callback for when WASM signals it's ready
(window as any).pacmanReady = () => {
setGameReady(true);
};
if (!(window as any).Module) {
const canvas = document.getElementById("canvas");
@@ -20,20 +28,54 @@ export default function Page() {
return () => {
script.remove();
delete (window as any).pacmanReady;
};
}
}, []);
const handleInteraction = useCallback(() => {
if (gameReady && !gameStarted) {
// Call the exported Rust function to start the game
const module = (window as any).Module;
if (module && module._start_game) {
module._start_game();
setGameStarted(true);
}
}
}, [gameReady, gameStarted]);
// Handle keyboard interaction
useEffect(() => {
if (!gameReady || gameStarted) return;
const handleKeyDown = (e: KeyboardEvent) => {
handleInteraction();
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [gameReady, gameStarted, handleInteraction]);
return (
<div className="mt-4 flex justify-center h-[calc(100vh-120px)]">
<div
className="block border-1 border-yellow-400/50 aspect-[5/6] h-[min(calc(100vh-120px),_calc(95vw_*_6/5))] w-auto"
className="relative block border-1 border-yellow-400/50 aspect-[5/6] h-[min(calc(100vh-120px),_calc(95vw_*_6/5))] w-auto"
style={{
boxShadow:
"0 0 12px rgba(250, 204, 21, 0.35), 0 0 2px rgba(255, 255, 255, 0.25)",
}}
onClick={handleInteraction}
>
<canvas id="canvas" className="w-full h-full" />
{/* Click to Start overlay */}
{gameReady && !gameStarted && (
<div className="absolute inset-0 flex items-center justify-center bg-black/60 cursor-pointer">
<span className="text-yellow-400 text-5xl font-bold">
Click to Start
</span>
</div>
)}
</div>
</div>
);