From 34c9b58d8f64c623b0c1389d1b731b3e8c689693 Mon Sep 17 00:00:00 2001 From: Lanskikh Date: Thu, 16 Oct 2025 16:04:59 +0500 Subject: [PATCH] Update API URL in .env; refactor routing in main.tsx to switch between NewSessionPage and TestPage; enhance PixelStreamingWrapper with video initialization callback; improve UI components for better interaction and responsiveness in ActionsSidebarWrapper, UserCamera, UserDevicesControls, and ControlsPopover; add session data fetching and error handling in NewSessionPage. --- client/.env | 2 +- .../src/components/ActionsSidebarWrapper.tsx | 2 +- .../src/components/PixelStreamingWrapper.tsx | 6 + client/src/components/ui/ControlsPopover.tsx | 5 +- client/src/components/ui/UserCamera.tsx | 2 +- .../src/components/ui/UserDevicesControls.tsx | 3 +- client/src/main.tsx | 9 +- client/src/pages/NewSessionPage.tsx | 131 ++++++++++++++++-- client/src/pages/SessionPage.tsx | 4 +- client/src/types/Session.ts | 39 ++++++ 10 files changed, 178 insertions(+), 25 deletions(-) create mode 100644 client/src/types/Session.ts diff --git a/client/.env b/client/.env index cd41370..b8dedb4 100644 --- a/client/.env +++ b/client/.env @@ -1 +1 @@ -VITE_API_URL=http://localhost:3000 \ No newline at end of file +VITE_API_URL=http://192.168.1.23:3000 \ No newline at end of file diff --git a/client/src/components/ActionsSidebarWrapper.tsx b/client/src/components/ActionsSidebarWrapper.tsx index eed1306..a7123bc 100644 --- a/client/src/components/ActionsSidebarWrapper.tsx +++ b/client/src/components/ActionsSidebarWrapper.tsx @@ -20,7 +20,7 @@ function ActionsSidebarWrapper({ animate={{ opacity: 1 }} exit={{ opacity: 0 }} className={clsx( - "flex 2xl:flex 2xl:gap-[0.556vw] 2xl:flex-col gap-2 max-2xl:p-2 max-2xl:rounded-[32px] absolute 2xl:top-1/2 2xl:-translate-y-1/2 2xl:right-[1.111vw] max-2xl:left-1/2 max-2xl:-translate-x-1/2 max-2xl:bottom-2 max-2xl:landscape:bg-[#00000026] max-2xl:landscape:backdrop-blur", + "flex 2xl:flex 2xl:gap-[0.556vw] 2xl:flex-col gap-2 max-2xl:p-2 max-2xl:rounded-[32px] absolute 2xl:top-1/2 2xl:-translate-y-1/2 2xl:right-[1.111vw] max-2xl:left-1/2 max-2xl:-translate-x-1/2 max-2xl:bottom-2 max-2xl:bg-[#00000026] max-2xl:backdrop-blur", className )} > diff --git a/client/src/components/PixelStreamingWrapper.tsx b/client/src/components/PixelStreamingWrapper.tsx index 2cfe4d9..91a8cc1 100644 --- a/client/src/components/PixelStreamingWrapper.tsx +++ b/client/src/components/PixelStreamingWrapper.tsx @@ -10,10 +10,12 @@ import type { AllSettings } from "@epicgames-ps/lib-pixelstreamingfrontend-ue5.7 export interface PixelStreamingWrapperProps { initialSettings?: Partial; + onVideoInitialized?: () => void; } export const PixelStreamingWrapper = ({ initialSettings, + onVideoInitialized, }: PixelStreamingWrapperProps) => { // A reference to parent div element that the Pixel Streaming library attaches into: const videoParent = useRef(null); @@ -38,6 +40,10 @@ export const PixelStreamingWrapper = ({ setClickToPlayVisible(true); }); + streaming.addEventListener("videoInitialized", () => { + onVideoInitialized?.(); + }); + // Save the library instance into component state so that it can be accessed later: setPixelStreaming(streaming); diff --git a/client/src/components/ui/ControlsPopover.tsx b/client/src/components/ui/ControlsPopover.tsx index d99830f..6c56c1a 100644 --- a/client/src/components/ui/ControlsPopover.tsx +++ b/client/src/components/ui/ControlsPopover.tsx @@ -13,6 +13,7 @@ import ChatPopup from "../popups/ChatPopup"; import ParticipantsPopup from "../popups/ParticipantsPopup"; import SharePopup from "../popups/SharePopup"; import SettingsModal from "../modals/SettingsModal"; +import clsx from "clsx"; function ControlsPopover() { const [isOpened, setIsOpened] = useState(false); @@ -41,10 +42,10 @@ function ControlsPopover() { const { setModal } = useModalStore(); return ( -
+
setIsOpened(!isOpened)} >
diff --git a/client/src/components/ui/UserCamera.tsx b/client/src/components/ui/UserCamera.tsx index 557423a..a0d5cc7 100644 --- a/client/src/components/ui/UserCamera.tsx +++ b/client/src/components/ui/UserCamera.tsx @@ -53,7 +53,7 @@ export default function UserCamera({ : "0.139vw solid #FFFFFF4D", }} className={clsx( - "aspect-square rounded-[1.667vw] bg-yellow-500 relative flex-shrink-0", + "aspect-square rounded-[1.667vw] bg-yellow-500 relative flex-shrink-0 pointer-events-auto", isAdmin && "order-last" )} > diff --git a/client/src/components/ui/UserDevicesControls.tsx b/client/src/components/ui/UserDevicesControls.tsx index 5068667..fc4824a 100644 --- a/client/src/components/ui/UserDevicesControls.tsx +++ b/client/src/components/ui/UserDevicesControls.tsx @@ -8,6 +8,7 @@ import SettingsModal from "../modals/SettingsModal"; export default function UserDevicesControls() { const { setModal } = useModalStore(); + function ToggleAudioDevice() { console.log("Mute device"); } @@ -22,7 +23,7 @@ export default function UserDevicesControls() { } return ( -
+
} diff --git a/client/src/main.tsx b/client/src/main.tsx index 953fd6f..16220df 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -2,16 +2,17 @@ import { createRoot } from "react-dom/client"; import "./index.css"; import HomePage from "./pages/HomePage"; import { createBrowserRouter, RouterProvider } from "react-router"; -import SessionPage from "./pages/SessionPage"; +// import SessionPage from "./pages/SessionPage"; import LoginPage from "./pages/LoginPage"; import RegisterPage from "./pages/RegisterPage"; -// import TestPage from "./pages/TestPage"; import { QueryClientProvider } from "@tanstack/react-query"; import { queryClient } from "./lib/queryClient"; import ProtectedRoute from "./components/ProtectedRoute"; import PublicRoute from "./components/PublicRoute"; import ModalContainer from "./components/ModalContainer"; import PopupContainer from "./components/PopupContainer"; +// import NewSessionPage from "./pages/NewSessionPage"; +import TestPage from "./pages/TestPage"; import NewSessionPage from "./pages/NewSessionPage"; const router = createBrowserRouter([ @@ -41,11 +42,11 @@ const router = createBrowserRouter([ }, { path: "/test", - element: , + element: , }, { path: "/sessions/:id", - element: , + element: , }, ]); diff --git a/client/src/pages/NewSessionPage.tsx b/client/src/pages/NewSessionPage.tsx index 74d24f1..dcacb1a 100644 --- a/client/src/pages/NewSessionPage.tsx +++ b/client/src/pages/NewSessionPage.tsx @@ -1,6 +1,5 @@ import ActionsSidebarWrapper from "../components/ActionsSidebarWrapper"; import ChatFilledIcon from "../components/icons/ChatFilledIcon"; -import CogFilledIcon from "../components/icons/CogFilledIcon"; import ExitFilledIcon from "../components/icons/ExitFilledIcon"; import FullscreenExitIcon from "../components/icons/FullscreenExitIcon"; import FullscreenIcon from "../components/icons/FullscreenIcon"; @@ -14,13 +13,19 @@ import usePopupStore from "../store/popupStore"; import ControlsPopover from "../components/ui/ControlsPopover"; import ChatPopup from "../components/popups/ChatPopup"; import SharePopup from "../components/popups/SharePopup"; -import SettingsModal from "../components/modals/SettingsModal"; -import useModalStore from "../store/modalStore"; import { useEffect, useState } from "react"; +import { useNavigate, useParams } from "react-router"; +import { useQuery } from "@tanstack/react-query"; +import { api } from "../lib/api"; +import type { Session } from "../types/Session"; +import { PixelStreamingWrapper } from "../components/PixelStreamingWrapper"; +import WarningIcon from "../components/icons/WarningIcon"; +import Button from "../components/ui/Button"; +import LoaderIcon from "../components/icons/LoaderIcon"; +import SessionUsersPanel from "../components/SessionUsersPanel"; function NewSessionPage() { const { setPopup } = usePopupStore(); - const { setModal } = useModalStore(); const [isFullscreen, setIsFullscreen] = useState(false); @@ -39,8 +44,109 @@ function NewSessionPage() { ); }, []); + const { id } = useParams(); + const navigate = useNavigate(); + + const { + data: sessionData, + isLoading, + error, + // refetch, + } = useQuery({ + queryKey: ["session", id], + queryFn: async () => { + const response = await api.get(`sessions/${id}`).json<{ + session: Session; + }>(); + return response; + }, + refetchInterval: (query) => { + // Автоматически обновляем каждые 2 секунды, если сессия в процессе запуска + const data = query.state.data; + if ( + data?.session.status === "starting" || + data?.session.status === "ending" + ) { + return 2000; + } + return false; + }, + }); + + const session = sessionData?.session; + + // Перенаправление на тестовую страницу при завершении сессии + useEffect(() => { + if (session?.status === "ended") { + const timer = setTimeout(() => { + navigate("/test"); + }, 5000); + return () => clearTimeout(timer); + } + }, [session?.status, navigate]); + + if (isLoading) { + return ( +
+
+
+ +
+

+ Загрузка информации о сессии... +

+
+
+ ); + } + + if (error || !session) { + return ( +
+
+
+
+ +
+
+

Сессия не найдена

+

+ {error instanceof Error + ? error.message + : "Не удалось загрузить информацию о сессии"} +

+ +
+
+
+
+ ); + } + return ( -
+
+ {session.status === "started" && + session.mode === "stream" && + session.server?.localIp && + session.playerPort && ( +
+ { + console.log("Video initialized"); + }} + /> +
+ )} - setPopup() + setPopup( + + ) } >
- setModal()} - > -
- -
-
@@ -101,6 +201,9 @@ function NewSessionPage() { +
+ +
); } diff --git a/client/src/pages/SessionPage.tsx b/client/src/pages/SessionPage.tsx index 915ada9..1c4fd6d 100644 --- a/client/src/pages/SessionPage.tsx +++ b/client/src/pages/SessionPage.tsx @@ -164,7 +164,9 @@ function SessionPage() { HoveringMouse: true, WaitForStreamer: true, }} - // onVideoInitialized={() => setIsVideoInitialized(true)} + onVideoInitialized={() => { + console.log("Video initialized"); + }} />
)} diff --git a/client/src/types/Session.ts b/client/src/types/Session.ts new file mode 100644 index 0000000..8c99b2b --- /dev/null +++ b/client/src/types/Session.ts @@ -0,0 +1,39 @@ +export interface Session { + id: string; + appId: string; + userId: string | null; + mode: "stream" | "local"; + status: "starting" | "started" | "ending" | "ended"; + tier: "demo" | "prod" | null; + serverId: string | null; + appPid: number | null; + cirrusPid: number | null; + streamerPort: number | null; + playerPort: number | null; + sfuPort: number | null; + startAt: string; + endAt: string | null; + createdAt: string; + updatedAt: string; + app?: { + id: string; + name: string; + title: string; + gpuLimitMb: number | null; + psVersion: number | null; + }; + server?: { + id: string; + localIp: string; + hostname: string; + type: "stream" | "local"; + tier: "demo" | "prod" | null; + location: "ru1" | "uae1" | null; + } | null; + user?: { + id: string; + email: string; + role: string; + displayName: string; + } | null; +}