feat: video player

This commit is contained in:
math-pixel
2026-05-14 11:35:03 +02:00
parent 3222d2ed3d
commit 3ece1d76de
3 changed files with 94 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
import { useEffect, useRef } from "react";
import { useGameStore } from "@/managers/stores/useGameStore";
export function VideoPlayer(): null {
const currentVideo = useGameStore((state) => state.missionFlow.currentVideo);
const clearVideo = useGameStore((state) => state.clearVideo);
const setCinematicPlaying = useGameStore(
(state) => state.setCinematicPlaying,
);
const videoRef = useRef<HTMLVideoElement>(null);
useEffect(() => {
if (currentVideo) {
setCinematicPlaying(true);
}
}, [currentVideo, setCinematicPlaying]);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape" && currentVideo) {
closeVideo();
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [currentVideo]);
const closeVideo = () => {
if (videoRef.current) {
videoRef.current.pause();
}
clearVideo();
setCinematicPlaying(false);
};
useEffect(() => {
const video = videoRef.current;
if (!video) return;
const handleEnded = () => {
closeVideo();
};
video.addEventListener("ended", handleEnded);
return () => video.removeEventListener("ended", handleEnded);
}, []);
if (!currentVideo) return null;
return (
<div
style={{
position: "fixed",
top: 0,
left: 0,
width: "100vw",
height: "100vh",
backgroundColor: "rgba(0, 0, 0, 0.95)",
display: "flex",
alignItems: "center",
justifyContent: "center",
zIndex: 9999,
}}
>
<video
ref={videoRef}
src={currentVideo}
autoPlay
playsInline
style={{
maxWidth: "100%",
maxHeight: "100%",
cursor: "pointer",
}}
onClick={closeVideo}
/>
</div>
);
}
+12
View File
@@ -29,6 +29,7 @@ interface MissionFlowState {
canMove: boolean; canMove: boolean;
dialogMessage: string | null; dialogMessage: string | null;
playerName: string; playerName: string;
currentVideo: string | null;
} }
interface GameState { interface GameState {
@@ -78,6 +79,8 @@ interface GameActions {
rewindGameState: () => void; rewindGameState: () => void;
resetGame: () => void; resetGame: () => void;
showDialog: (dialogMessage: string) => void; showDialog: (dialogMessage: string) => void;
playVideo: (videoSrc: string) => void;
clearVideo: () => void;
} }
type GameStore = GameState & GameActions; type GameStore = GameState & GameActions;
@@ -233,6 +236,7 @@ function createInitialGameState(): GameState {
canMove: false, canMove: false,
dialogMessage: null, dialogMessage: null,
playerName: "", playerName: "",
currentVideo: null,
}, },
intro: { intro: {
currentStep: "intro", currentStep: "intro",
@@ -342,4 +346,12 @@ export const useGameStore = create<GameStore>()((set) => ({
set((state) => ({ set((state) => ({
missionFlow: { ...state.missionFlow, dialogMessage }, missionFlow: { ...state.missionFlow, dialogMessage },
})), })),
playVideo: (videoSrc) =>
set((state) => ({
missionFlow: { ...state.missionFlow, currentVideo: videoSrc },
})),
clearVideo: () =>
set((state) => ({
missionFlow: { ...state.missionFlow, currentVideo: null },
})),
})); }));
+2
View File
@@ -29,6 +29,7 @@ import { GameStageContent } from "@/world/GameStageContent";
import { Player } from "@/world/player/Player"; import { Player } from "@/world/player/Player";
import { TestMap } from "@/world/debug/TestMap"; import { TestMap } from "@/world/debug/TestMap";
import { NetTest } from "@/components/three/NetTest"; import { NetTest } from "@/components/three/NetTest";
import { VideoPlayer } from "@/components/ui/VideoPlayer";
import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading"; import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading";
interface WorldProps { interface WorldProps {
@@ -62,6 +63,7 @@ export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element {
return ( return (
<> <>
<Environment /> <Environment />
<VideoPlayer />
<Lighting /> <Lighting />
<DebugHelpers /> <DebugHelpers />
{showHandTrackingGloves ? ( {showHandTrackingGloves ? (