feat: video player
This commit is contained in:
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 },
|
||||||
|
})),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -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 ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user