upd
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.3": "^1.0.1",
|
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.3": "^1.0.1",
|
||||||
|
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.5": "^0.0.12",
|
||||||
"@livekit/components-react": "^2.0.3",
|
"@livekit/components-react": "^2.0.3",
|
||||||
"@livekit/components-styles": "^1.0.10",
|
"@livekit/components-styles": "^1.0.10",
|
||||||
"@uidotdev/usehooks": "^2.4.1",
|
"@uidotdev/usehooks": "^2.4.1",
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"i18next-browser-languagedetector": "^7.2.0",
|
"i18next-browser-languagedetector": "^7.2.0",
|
||||||
"ky": "^1.1.3",
|
"ky": "^1.1.3",
|
||||||
"livekit-client": "^1.13.2",
|
"livekit-client": "^1.13.2",
|
||||||
|
"peerjs": "^1.5.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-calendar": "^4.3.0",
|
"react-calendar": "^4.3.0",
|
||||||
"react-countdown": "^2.3.5",
|
"react-countdown": "^2.3.5",
|
||||||
@@ -38,12 +40,14 @@
|
|||||||
"socket.io-client": "^4.7.4",
|
"socket.io-client": "^4.7.4",
|
||||||
"ua-parser-js": "^1.0.35",
|
"ua-parser-js": "^1.0.35",
|
||||||
"use-clipboard-copy": "^0.2.0",
|
"use-clipboard-copy": "^0.2.0",
|
||||||
|
"use-is-audio-active": "^1.0.0",
|
||||||
"usehooks-ts": "^3.0.1",
|
"usehooks-ts": "^3.0.1",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"zustand": "^4.3.9"
|
"zustand": "^4.3.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.11.17",
|
"@types/node": "^20.11.17",
|
||||||
|
"@types/peerjs": "^1.1.0",
|
||||||
"@types/react": "^18.0.28",
|
"@types/react": "^18.0.28",
|
||||||
"@types/react-dom": "^18.0.11",
|
"@types/react-dom": "^18.0.11",
|
||||||
"@types/react-input-mask": "^3.0.2",
|
"@types/react-input-mask": "^3.0.2",
|
||||||
|
|||||||
+10
-5
@@ -31,11 +31,12 @@ function App() {
|
|||||||
state.setIsOpen,
|
state.setIsOpen,
|
||||||
]);
|
]);
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [countdownTimer, setCountdownTimer] = useState(15);
|
const [countdownTimer, setCountdownTimer] = useState(10);
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const build = searchParams.get("build") || null;
|
const build = searchParams.get("build") || null;
|
||||||
const type = searchParams.get("type") || "demo";
|
const type = searchParams.get("type") || "demo";
|
||||||
const endAt = searchParams.get("endAt");
|
const endAt = searchParams.get("endAt");
|
||||||
|
const [streamUrl, setStreamUrl] = useState<string>();
|
||||||
|
|
||||||
function toastError(text: string) {
|
function toastError(text: string) {
|
||||||
toast.error(text, {
|
toast.error(text, {
|
||||||
@@ -95,13 +96,11 @@ function App() {
|
|||||||
.json();
|
.json();
|
||||||
|
|
||||||
if (response.stream) {
|
if (response.stream) {
|
||||||
|
setStreamUrl(`/stream/${response.stream}`);
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
setCountdownTimer((prev) => prev - 1);
|
setCountdownTimer((prev) => prev - 1);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
navigate(`/stream/${response.stream}`);
|
|
||||||
}, 15000);
|
|
||||||
} else if (response.error) {
|
} else if (response.error) {
|
||||||
toastError(response.error);
|
toastError(response.error);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -117,6 +116,12 @@ function App() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (countdownTimer > 0 || !streamUrl) return;
|
||||||
|
|
||||||
|
navigate(streamUrl);
|
||||||
|
}, [countdownTimer]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getLang();
|
getLang();
|
||||||
|
|
||||||
|
|||||||
+1
-3
@@ -28,7 +28,6 @@ import "react-toastify/dist/ReactToastify.css";
|
|||||||
import AlertIcon from "./components/icons/AlertIcon";
|
import AlertIcon from "./components/icons/AlertIcon";
|
||||||
import useSocketStore from "./stores/useSocketStore";
|
import useSocketStore from "./stores/useSocketStore";
|
||||||
import { LiveKitRoom, RoomAudioRenderer } from "@livekit/components-react";
|
import { LiveKitRoom, RoomAudioRenderer } from "@livekit/components-react";
|
||||||
import ToggleMic from "./components/ToggleMic";
|
|
||||||
import Chat from "./components/Chat";
|
import Chat from "./components/Chat";
|
||||||
// import AFKTimerModal from "./components/modals/AFKTimerModal";
|
// import AFKTimerModal from "./components/modals/AFKTimerModal";
|
||||||
import { differenceInMilliseconds, format, parseISO } from "date-fns";
|
import { differenceInMilliseconds, format, parseISO } from "date-fns";
|
||||||
@@ -380,13 +379,12 @@ function StreamPage() {
|
|||||||
</button> */}
|
</button> */}
|
||||||
|
|
||||||
<LiveKitRoom
|
<LiveKitRoom
|
||||||
video={false}
|
video={true}
|
||||||
audio={true}
|
audio={true}
|
||||||
token={token}
|
token={token}
|
||||||
serverUrl={livekitServerUrl}
|
serverUrl={livekitServerUrl}
|
||||||
>
|
>
|
||||||
<RoomAudioRenderer />
|
<RoomAudioRenderer />
|
||||||
<ToggleMic socket={socket} handleUpdate={update} />
|
|
||||||
</LiveKitRoom>
|
</LiveKitRoom>
|
||||||
|
|
||||||
<div className="relative group outline-none w-10 h-10 bg-[#131313] rounded-full shadow-lg shadow-[#131313] p-2 opacity-90 flex justify-center items-center">
|
<div className="relative group outline-none w-10 h-10 bg-[#131313] rounded-full shadow-lg shadow-[#131313] p-2 opacity-90 flex justify-center items-center">
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
/* eslint-disable no-empty */
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import {
|
||||||
|
Config,
|
||||||
|
AllSettings,
|
||||||
|
PixelStreaming,
|
||||||
|
} from "@epicgames-ps/lib-pixelstreamingfrontend-ue5.5";
|
||||||
|
|
||||||
|
export interface PixelStreamingWrapperProps {
|
||||||
|
initialSettings?: Partial<AllSettings>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PixelStreamingWrapper2 = ({
|
||||||
|
initialSettings,
|
||||||
|
}: PixelStreamingWrapperProps) => {
|
||||||
|
// A reference to parent div element that the Pixel Streaming library attaches into:
|
||||||
|
const videoParent = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Pixel streaming library instance is stored into this state variable after initialization:
|
||||||
|
const [pixelStreaming, setPixelStreaming] = useState<PixelStreaming>();
|
||||||
|
|
||||||
|
// A boolean state variable that determines if the Click to play overlay is shown:
|
||||||
|
const [clickToPlayVisible, setClickToPlayVisible] = useState(false);
|
||||||
|
|
||||||
|
// Run on component mount:
|
||||||
|
useEffect(() => {
|
||||||
|
if (videoParent.current) {
|
||||||
|
// Attach Pixel Streaming library to videoParent element:
|
||||||
|
const config = new Config({ initialSettings });
|
||||||
|
const streaming = new PixelStreaming(config, {
|
||||||
|
videoElementParent: videoParent.current,
|
||||||
|
});
|
||||||
|
|
||||||
|
// register a playStreamRejected handler to show Click to play overlay if needed:
|
||||||
|
streaming.addEventListener("playStreamRejected", () => {
|
||||||
|
setClickToPlayVisible(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save the library instance into component state so that it can be accessed later:
|
||||||
|
setPixelStreaming(streaming);
|
||||||
|
|
||||||
|
// Clean up on component unmount:
|
||||||
|
return () => {
|
||||||
|
try {
|
||||||
|
streaming.disconnect();
|
||||||
|
} catch {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
|
ref={videoParent}
|
||||||
|
/>
|
||||||
|
{clickToPlayVisible && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
pixelStreaming?.play();
|
||||||
|
setClickToPlayVisible(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>Click to play</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -10,7 +10,6 @@ import { Trans } from "react-i18next";
|
|||||||
import i18n from "../i18n";
|
import i18n from "../i18n";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import LoaderIcon from "./icons/LoaderIcon";
|
import LoaderIcon from "./icons/LoaderIcon";
|
||||||
import api from "../utils/api";
|
|
||||||
|
|
||||||
function SidebarTab4() {
|
function SidebarTab4() {
|
||||||
const {
|
const {
|
||||||
@@ -28,17 +27,6 @@ function SidebarTab4() {
|
|||||||
|
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
async function sendInvite(email: string, link: string) {
|
|
||||||
try {
|
|
||||||
const reuslt: any = await api
|
|
||||||
.post("sendInvite", { json: { email, link } })
|
|
||||||
.json();
|
|
||||||
console.log("reuslt", reuslt);
|
|
||||||
} catch (error) {
|
|
||||||
console.log({ error: (error as Error).message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleClickSignUp() {
|
async function handleClickSignUp() {
|
||||||
if (!selectedTime || !selectedDay) {
|
if (!selectedTime || !selectedDay) {
|
||||||
return;
|
return;
|
||||||
@@ -63,7 +51,6 @@ function SidebarTab4() {
|
|||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
|
|
||||||
sendInvite(email, result.url);
|
|
||||||
setUrl(result.url);
|
setUrl(result.url);
|
||||||
setCurrentTab(currentTab + 1);
|
setCurrentTab(currentTab + 1);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
function ToastContainer() {
|
|
||||||
return <div></div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ToastContainer;
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
import { useRoomContext } from "@livekit/components-react";
|
|
||||||
import { Track } from "livekit-client";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import MicroOnIcon from "./icons/MicroOnIcon";
|
|
||||||
import MicroOffIcon from "./icons/MicroOffIcon";
|
|
||||||
|
|
||||||
function ToggleMic({ socket, handleUpdate }: any) {
|
|
||||||
const room = useRoomContext();
|
|
||||||
const [muted, setMuted] = useState(true);
|
|
||||||
|
|
||||||
function toggleMic(value: boolean) {
|
|
||||||
const audioTrack = room.localParticipant.getTrack(Track.Source.Microphone);
|
|
||||||
|
|
||||||
if (!audioTrack) return;
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
audioTrack.mute();
|
|
||||||
} else {
|
|
||||||
audioTrack.unmute();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMuted(!audioTrack.isMuted);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!socket) return;
|
|
||||||
|
|
||||||
socket.on("update", (socketId: string, data: { [key: string]: any }) => {
|
|
||||||
if (
|
|
||||||
socket.id === socketId &&
|
|
||||||
Object.prototype.hasOwnProperty.call(data, "muted")
|
|
||||||
) {
|
|
||||||
toggleMic(data.muted);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [socket]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// room.on(RoomEvent.TrackMuted, (_, participant) => {
|
|
||||||
// console.log(participant);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// room.on(RoomEvent.TrackUnmuted, (_, participant) => {
|
|
||||||
// console.log(participant);
|
|
||||||
// });
|
|
||||||
|
|
||||||
room.on("localTrackPublished", () => {
|
|
||||||
room.localParticipant.getTrack(Track.Source.Microphone)?.mute();
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={() =>
|
|
||||||
handleUpdate(room.localParticipant.identity, {
|
|
||||||
muted: !room.localParticipant.getTrack(Track.Source.Microphone)
|
|
||||||
?.isMuted,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="relative group outline-none bg-[#131313] rounded-full shadow-lg shadow-[#131313] p-2 opacity-90"
|
|
||||||
>
|
|
||||||
{!muted ? <MicroOnIcon /> : <MicroOffIcon />}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ToggleMic;
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
import useIsAudioActive from "use-is-audio-active";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
mediaStream: MediaStream | null;
|
||||||
|
muted: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Video({ mediaStream, muted }: Props) {
|
||||||
|
const remoteVideoRef = useRef<HTMLVideoElement>(null);
|
||||||
|
const isSpeaking = useIsAudioActive({ source: mediaStream });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!remoteVideoRef.current) return;
|
||||||
|
|
||||||
|
remoteVideoRef.current.srcObject = mediaStream;
|
||||||
|
remoteVideoRef.current.onloadedmetadata = () => {
|
||||||
|
remoteVideoRef.current?.play();
|
||||||
|
};
|
||||||
|
}, [mediaStream]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<video
|
||||||
|
ref={remoteVideoRef}
|
||||||
|
className={`aspect-video bg-black lg:w-[216px] lg:h-[162px] w-[112px] h-[84px] rounded-lg object-cover ring-2 ${
|
||||||
|
isSpeaking ? "ring-green-500" : "ring-transparent"
|
||||||
|
}`}
|
||||||
|
playsInline
|
||||||
|
autoPlay
|
||||||
|
muted={muted}
|
||||||
|
></video>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Video;
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
function CameraOffIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M12 18.75H4.5a2.25 2.25 0 0 1-2.25-2.25V9m12.841 9.091L16.5 19.5m-1.409-1.409c.407-.407.659-.97.659-1.591v-9a2.25 2.25 0 0 0-2.25-2.25h-9c-.621 0-1.184.252-1.591.659m12.182 12.182L2.909 5.909M1.5 4.5l1.409 1.409"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CameraOffIcon;
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
function CameraOnIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CameraOnIcon;
|
||||||
+1
-1
@@ -8,7 +8,7 @@ import App from "./App";
|
|||||||
import HistoryPage from "./HistoryPage";
|
import HistoryPage from "./HistoryPage";
|
||||||
import ScheduledPage from "./ScheduledPage";
|
import ScheduledPage from "./ScheduledPage";
|
||||||
import StreamPage2 from "./pages/StreamPage2";
|
import StreamPage2 from "./pages/StreamPage2";
|
||||||
// import StreamPage from "./StreamPage";
|
// import StreamPage3 from "./pages/StreamPage3";
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
|
|||||||
+197
-164
@@ -11,7 +11,7 @@ import { Transition } from "react-transition-group";
|
|||||||
import Button from "../components/ui/Button";
|
import Button from "../components/ui/Button";
|
||||||
// import CloseIcon from "../components/icons/CloseIcon";
|
// import CloseIcon from "../components/icons/CloseIcon";
|
||||||
import HandOffIcon from "../components/icons/HandOffIcon";
|
import HandOffIcon from "../components/icons/HandOffIcon";
|
||||||
import MicroOffIcon from "../components/icons/MicroOffIcon";
|
// import MicroOffIcon from "../components/icons/MicroOffIcon";
|
||||||
import PersonsIcon from "../components/icons/PersonsIcon";
|
import PersonsIcon from "../components/icons/PersonsIcon";
|
||||||
import MicroOnIcon from "../components/icons/MicroOnIcon";
|
import MicroOnIcon from "../components/icons/MicroOnIcon";
|
||||||
// import MoreIcon from "../components/icons/MoreIcon";
|
// import MoreIcon from "../components/icons/MoreIcon";
|
||||||
@@ -41,15 +41,6 @@ import DesktopIcon from "../components/icons/DesktopIcon";
|
|||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import IUser from "../types/IUser";
|
import IUser from "../types/IUser";
|
||||||
import IMessage from "../types/IMessage";
|
import IMessage from "../types/IMessage";
|
||||||
import ky from "ky";
|
|
||||||
import {
|
|
||||||
RemoteParticipant,
|
|
||||||
RemoteTrack,
|
|
||||||
RemoteTrackPublication,
|
|
||||||
Room,
|
|
||||||
RoomEvent,
|
|
||||||
Track,
|
|
||||||
} from "livekit-client";
|
|
||||||
import InfoIcon from "../components/icons/InfoIcon";
|
import InfoIcon from "../components/icons/InfoIcon";
|
||||||
import Rotate64Icon from "../components/icons/Rotate64Icon";
|
import Rotate64Icon from "../components/icons/Rotate64Icon";
|
||||||
import { useClipboard } from "use-clipboard-copy";
|
import { useClipboard } from "use-clipboard-copy";
|
||||||
@@ -57,6 +48,13 @@ import QRCode from "react-qr-code";
|
|||||||
import Star12Icon from "../components/icons/Star12Icon";
|
import Star12Icon from "../components/icons/Star12Icon";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import Countdown from "react-countdown";
|
import Countdown from "react-countdown";
|
||||||
|
import useStreamStore from "../stores/useStreamStore";
|
||||||
|
import Peer from "peerjs";
|
||||||
|
import Video from "../components/Video";
|
||||||
|
import MicroOffIcon from "../components/icons/MicroOffIcon";
|
||||||
|
import useIsAudioActive from "use-is-audio-active";
|
||||||
|
import CameraOffIcon from "../components/icons/CameraOffIcon";
|
||||||
|
import CameraOnIcon from "../components/icons/CameraOnIcon";
|
||||||
|
|
||||||
const renderer = ({ minutes, seconds }: any) => {
|
const renderer = ({ minutes, seconds }: any) => {
|
||||||
return (
|
return (
|
||||||
@@ -74,7 +72,7 @@ function StreamPage2() {
|
|||||||
const [socket, setSocket] = useState<Socket>();
|
const [socket, setSocket] = useState<Socket>();
|
||||||
const [wsUrl, setWsUrl] = useState<string>();
|
const [wsUrl, setWsUrl] = useState<string>();
|
||||||
const [isEnded, setIsEnded] = useState<boolean>(false);
|
const [isEnded, setIsEnded] = useState<boolean>(false);
|
||||||
const [name, setName] = useState<string>("");
|
const { name, setName } = useStreamStore();
|
||||||
const [userId] = useState(uuidv4());
|
const [userId] = useState(uuidv4());
|
||||||
const [step, setStep] = useState<number>(1);
|
const [step, setStep] = useState<number>(1);
|
||||||
const nameRef = useRef<HTMLInputElement>(null!);
|
const nameRef = useRef<HTMLInputElement>(null!);
|
||||||
@@ -86,8 +84,6 @@ function StreamPage2() {
|
|||||||
const [isShowChat, setIsShowChat] = useState<boolean>(false);
|
const [isShowChat, setIsShowChat] = useState<boolean>(false);
|
||||||
const [isShowUsers, setIsShowUsers] = useState<boolean>(false);
|
const [isShowUsers, setIsShowUsers] = useState<boolean>(false);
|
||||||
const [isShowInviteModal, setIsShowInviteModal] = useState(false);
|
const [isShowInviteModal, setIsShowInviteModal] = useState(false);
|
||||||
const [liveKitRoom, setLiveKitRoom] = useState<Room>();
|
|
||||||
const [isMicEnabled, setIsMicEnabled] = useState<boolean>(false);
|
|
||||||
const [isVideoInitialized, setIsVideoInitialized] = useState<boolean>(false);
|
const [isVideoInitialized, setIsVideoInitialized] = useState<boolean>(false);
|
||||||
const [messageText, setMessageText] = useState("");
|
const [messageText, setMessageText] = useState("");
|
||||||
const [messages, setMessages] = useState<IMessage[]>([]);
|
const [messages, setMessages] = useState<IMessage[]>([]);
|
||||||
@@ -98,6 +94,21 @@ function StreamPage2() {
|
|||||||
const link = window.location.origin + window.location.pathname;
|
const link = window.location.origin + window.location.pathname;
|
||||||
const [anyNewMessages, setAnyNewMessages] = useState(false);
|
const [anyNewMessages, setAnyNewMessages] = useState(false);
|
||||||
const [endAt, setEndAt] = useState();
|
const [endAt, setEndAt] = useState();
|
||||||
|
const [peerId, setPeerId] = useState<string>("");
|
||||||
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
|
// const [users, setUsers] = useState<IUser[]>([]);
|
||||||
|
const peerInstance = useRef<Peer>();
|
||||||
|
const mediaStreamInstance = useRef<MediaStream | null>(null);
|
||||||
|
// const [isEnabledVideo, setIsEnabledVideo] = useState(true);
|
||||||
|
// const [isEnabledAudio, setIsEnabledAudio] = useState(true);
|
||||||
|
const [permission, setPermission] = useState<boolean>();
|
||||||
|
const [remoteStreams, setRemoteStreams] = useState<any[]>([]);
|
||||||
|
// const [errorMessage, setErrorMessage] = useState<string>("");
|
||||||
|
const [isVideoEnabled, setIsVideoEnabled] = useState<boolean>(false);
|
||||||
|
const [isAudioEnabled, setIsAudioEnabled] = useState<boolean>(false);
|
||||||
|
const isSpeaking = useIsAudioActive({
|
||||||
|
source: mediaStreamInstance.current,
|
||||||
|
});
|
||||||
|
|
||||||
async function getLang() {
|
async function getLang() {
|
||||||
const { countryCode, error }: { countryCode: string; error: string } =
|
const { countryCode, error }: { countryCode: string; error: string } =
|
||||||
@@ -125,17 +136,6 @@ function StreamPage2() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTrackSubscribed(
|
|
||||||
track: RemoteTrack,
|
|
||||||
publication: RemoteTrackPublication,
|
|
||||||
participant: RemoteParticipant
|
|
||||||
) {
|
|
||||||
console.log(track, publication, participant);
|
|
||||||
|
|
||||||
const element = track.attach();
|
|
||||||
parentElementRef.current?.append(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendMessage(e: FormEvent) {
|
async function sendMessage(e: FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@@ -204,6 +204,7 @@ function StreamPage2() {
|
|||||||
name: name,
|
name: name,
|
||||||
device: isMobile ? "mobile" : "desktop",
|
device: isMobile ? "mobile" : "desktop",
|
||||||
isAdmin: searchParams.has("admin", true),
|
isAdmin: searchParams.has("admin", true),
|
||||||
|
peerId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -224,6 +225,7 @@ function StreamPage2() {
|
|||||||
name: i18n.language === "ru" ? "Гость" : "Guest",
|
name: i18n.language === "ru" ? "Гость" : "Guest",
|
||||||
device: isMobile ? "mobile" : "desktop",
|
device: isMobile ? "mobile" : "desktop",
|
||||||
isAdmin: searchParams.has("admin", true),
|
isAdmin: searchParams.has("admin", true),
|
||||||
|
peerId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -232,78 +234,73 @@ function StreamPage2() {
|
|||||||
setStep(2);
|
setStep(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mute() {
|
async function getPermission() {
|
||||||
const result = await liveKitRoom?.localParticipant
|
|
||||||
?.getTrack(Track.Source.Microphone)
|
|
||||||
?.mute();
|
|
||||||
|
|
||||||
if (!result) return;
|
|
||||||
|
|
||||||
setIsMicEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function unmute() {
|
|
||||||
const result = await liveKitRoom?.localParticipant
|
|
||||||
?.getTrack(Track.Source.Microphone)
|
|
||||||
?.unmute();
|
|
||||||
|
|
||||||
if (!result) return;
|
|
||||||
|
|
||||||
setIsMicEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function allowMic() {
|
|
||||||
if (!liveKitRoom) return;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await liveKitRoom.localParticipant.setMicrophoneEnabled(
|
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
||||||
true
|
video: true,
|
||||||
);
|
audio: true,
|
||||||
console.log("result", result);
|
});
|
||||||
|
|
||||||
setIsMicEnabled(
|
mediaStream.getAudioTracks().forEach((track) => {
|
||||||
!liveKitRoom?.localParticipant?.getTrack(Track.Source.Microphone)
|
track.enabled = !track.enabled;
|
||||||
?.isMuted
|
});
|
||||||
);
|
|
||||||
setStep(3);
|
videoRef.current!.srcObject = mediaStream;
|
||||||
|
videoRef.current!.onloadedmetadata = async () => {
|
||||||
|
try {
|
||||||
|
await videoRef.current?.play();
|
||||||
|
} catch (error) {
|
||||||
|
// toast.error((error as Error).message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaStreamInstance.current = mediaStream;
|
||||||
|
|
||||||
|
setIsVideoEnabled(true);
|
||||||
|
setPermission(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
const mediaStream = new MediaStream();
|
||||||
if (error.message === "Requested device not found") {
|
|
||||||
toast.warn(
|
|
||||||
"Устройство ввода не найдено. Подключите микрофон или нажмите кнопку «Пропустить»",
|
|
||||||
{
|
|
||||||
icon: <InfoIcon className="text-[#F2994A]" />,
|
|
||||||
position: "top-center",
|
|
||||||
autoClose: 5000,
|
|
||||||
hideProgressBar: false,
|
|
||||||
closeOnClick: true,
|
|
||||||
pauseOnHover: true,
|
|
||||||
draggable: true,
|
|
||||||
progress: undefined,
|
|
||||||
theme: "light",
|
|
||||||
transition: Bounce,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error.message === "Permission denied") {
|
mediaStreamInstance.current = mediaStream;
|
||||||
toast.warn(
|
|
||||||
"Вы заблокировали доступ к микрофону в настройках. Разрешите доступ в настройках вашего браузера и попробуйте снова.",
|
videoRef.current!.srcObject = mediaStream;
|
||||||
{
|
videoRef.current!.onloadedmetadata = async () => {
|
||||||
icon: <InfoIcon className="text-[#F2994A]" />,
|
try {
|
||||||
position: "top-center",
|
await videoRef.current?.play();
|
||||||
autoClose: 5000,
|
} catch (error) {
|
||||||
hideProgressBar: false,
|
// toast.error((error as Error).message);
|
||||||
closeOnClick: true,
|
|
||||||
pauseOnHover: true,
|
|
||||||
draggable: true,
|
|
||||||
progress: undefined,
|
|
||||||
theme: "light",
|
|
||||||
transition: Bounce,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const errorMessage = (error as Error).message;
|
||||||
|
console.log("errorMessage", errorMessage);
|
||||||
|
|
||||||
|
setPermission(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
setStep(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startCalls() {
|
||||||
|
const options = {
|
||||||
|
constraints: {
|
||||||
|
offerToReceiveVideo: true,
|
||||||
|
offerToReceiveAudio: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const user of users) {
|
||||||
|
if (user.peerId === peerId) continue;
|
||||||
|
|
||||||
|
const call = peerInstance.current!.call(
|
||||||
|
user.peerId,
|
||||||
|
mediaStreamInstance.current!,
|
||||||
|
options as any
|
||||||
|
);
|
||||||
|
|
||||||
|
call.on("stream", (remoteStream) => {
|
||||||
|
setRemoteStreams((prev) => [...prev, { peerId, remoteStream }]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,6 +308,24 @@ function StreamPage2() {
|
|||||||
setStep(3);
|
setStep(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleVideo() {
|
||||||
|
mediaStreamInstance.current!.getVideoTracks().forEach((track) => {
|
||||||
|
track.enabled = !track.enabled;
|
||||||
|
|
||||||
|
if (!permission) return;
|
||||||
|
setIsVideoEnabled(track.enabled);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAudio() {
|
||||||
|
mediaStreamInstance.current!.getAudioTracks().forEach((track) => {
|
||||||
|
track.enabled = !track.enabled;
|
||||||
|
|
||||||
|
if (!permission) return;
|
||||||
|
setIsAudioEnabled(track.enabled);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function transferControl(userId: string) {
|
function transferControl(userId: string) {
|
||||||
socket?.emit("transferControl", userId);
|
socket?.emit("transferControl", userId);
|
||||||
}
|
}
|
||||||
@@ -336,32 +351,46 @@ function StreamPage2() {
|
|||||||
socket?.emit("kickUser", userId);
|
socket?.emit("kickUser", userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLiveKitToken() {
|
|
||||||
const { token }: any = await ky
|
|
||||||
.get(
|
|
||||||
`https://coord.graff.tech/getToken?roomName=${params.id}&participantName=${userId}`
|
|
||||||
)
|
|
||||||
.json();
|
|
||||||
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function liveKitConnect() {
|
|
||||||
const liveKitWsUrl = "wss://livekit.stream.graff.tech";
|
|
||||||
const liveKitToken = await getLiveKitToken();
|
|
||||||
|
|
||||||
const room = new Room();
|
|
||||||
room.on(RoomEvent.TrackSubscribed, handleTrackSubscribed);
|
|
||||||
await room.connect(liveKitWsUrl, liveKitToken);
|
|
||||||
|
|
||||||
setLiveKitRoom(room);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const peer = new Peer();
|
||||||
|
|
||||||
|
// Как только соединение с сервером PeerJS установлено, запускается событие "open"
|
||||||
|
peer.on("open", (id) => {
|
||||||
|
setPeerId(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработка входящего вызова
|
||||||
|
peer.on("call", (call) => {
|
||||||
|
call.answer(mediaStreamInstance.current!);
|
||||||
|
call.on("stream", function (remoteStream) {
|
||||||
|
setRemoteStreams((prev) => [...prev, { peerId, remoteStream }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
peerInstance.current = peer;
|
||||||
|
|
||||||
getLang();
|
getLang();
|
||||||
getWsUrl();
|
getWsUrl();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!remoteStreams.length) return;
|
||||||
|
|
||||||
|
setRemoteStreams((prev) =>
|
||||||
|
prev.filter(
|
||||||
|
(obj, idx, arr) => idx === arr.findIndex((t) => t.peerId === obj.peerId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("remoteStreams", remoteStreams);
|
||||||
|
}, [remoteStreams.length]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (permission === undefined) return;
|
||||||
|
|
||||||
|
startCalls();
|
||||||
|
}, [permission]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = t("title");
|
document.title = t("title");
|
||||||
}, [i18n.language]);
|
}, [i18n.language]);
|
||||||
@@ -369,7 +398,7 @@ function StreamPage2() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!name) return;
|
if (!name) return;
|
||||||
|
|
||||||
setName(() => name.trim());
|
setName(name.trim());
|
||||||
}, [name]);
|
}, [name]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -397,6 +426,11 @@ function StreamPage2() {
|
|||||||
|
|
||||||
socket.on("update", (roomUsers: IUser[]) => {
|
socket.on("update", (roomUsers: IUser[]) => {
|
||||||
setUsers(roomUsers);
|
setUsers(roomUsers);
|
||||||
|
// for (const user of roomUsers) {
|
||||||
|
// setRemoteStreams(
|
||||||
|
// remoteStreams.filter(({ peerId }) => peerId === user.peerId)
|
||||||
|
// );
|
||||||
|
// }
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("message", (message: IMessage) => {
|
socket.on("message", (message: IMessage) => {
|
||||||
@@ -444,24 +478,8 @@ function StreamPage2() {
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
liveKitConnect();
|
|
||||||
}, [socket]);
|
}, [socket]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log(liveKitRoom);
|
|
||||||
|
|
||||||
if (!liveKitRoom) return;
|
|
||||||
|
|
||||||
liveKitRoom.on(RoomEvent.TrackMuted, (_, participant) => {
|
|
||||||
console.log(participant);
|
|
||||||
});
|
|
||||||
|
|
||||||
liveKitRoom.on(RoomEvent.TrackUnmuted, (_, participant) => {
|
|
||||||
console.log(participant);
|
|
||||||
});
|
|
||||||
}, [liveKitRoom]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isMobile) return;
|
if (!isMobile) return;
|
||||||
if (isShowUsers) {
|
if (isShowUsers) {
|
||||||
@@ -535,12 +553,17 @@ function StreamPage2() {
|
|||||||
<Tooltip text="Запросить управление" />
|
<Tooltip text="Запросить управление" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
icon={isMicEnabled ? <MicroOnIcon /> : <MicroOffIcon />}
|
icon={isVideoEnabled ? <CameraOnIcon /> : <CameraOffIcon />}
|
||||||
onlyIcon
|
onlyIcon
|
||||||
onClick={() => (isMicEnabled ? mute() : unmute())}
|
onClick={toggleVideo}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
icon={isAudioEnabled ? <MicroOnIcon /> : <MicroOffIcon />}
|
||||||
|
onlyIcon
|
||||||
|
onClick={toggleAudio}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-px h-4 bg-[#DAE0E5]"></div>
|
<div className="w-px h-4 bg-[#DAE0E5]"></div>
|
||||||
@@ -678,9 +701,9 @@ function StreamPage2() {
|
|||||||
<hr className="bg-[#DAE0E5] w-4" />
|
<hr className="bg-[#DAE0E5] w-4" />
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
icon={isMicEnabled ? <MicroOnIcon /> : <MicroOffIcon />}
|
icon={isAudioEnabled ? <MicroOnIcon /> : <MicroOffIcon />}
|
||||||
onlyIcon
|
onlyIcon
|
||||||
onClick={() => (isMicEnabled ? mute() : unmute())}
|
onClick={toggleAudio}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="">
|
<div className="">
|
||||||
@@ -746,6 +769,31 @@ function StreamPage2() {
|
|||||||
}
|
}
|
||||||
></div>
|
></div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className="absolute top-2 lg:left-2 lg:right-auto right-2 flex flex-col gap-2">
|
||||||
|
<video
|
||||||
|
ref={videoRef}
|
||||||
|
className={`aspect-video bg-black rounded-lg -scale-x-100 object-cover ring-2 ${
|
||||||
|
isAudioEnabled && isSpeaking
|
||||||
|
? "ring-green-500"
|
||||||
|
: "ring-transparent"
|
||||||
|
} ${
|
||||||
|
permission
|
||||||
|
? "lg:w-[216px] lg:h-[162px] w-[112px] h-[84px]"
|
||||||
|
: "w-0 h-0"
|
||||||
|
}`}
|
||||||
|
playsInline
|
||||||
|
autoPlay
|
||||||
|
muted
|
||||||
|
/>
|
||||||
|
{remoteStreams.map(({ remoteStream }, index) => (
|
||||||
|
<Video
|
||||||
|
key={index}
|
||||||
|
mediaStream={remoteStream}
|
||||||
|
muted={!permission}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(isShowUsers || isShowChat) && (
|
{(isShowUsers || isShowChat) && (
|
||||||
@@ -1074,46 +1122,31 @@ function StreamPage2() {
|
|||||||
</div>
|
</div>
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
<Trans i18nKey={"allowMicrophoneUse"}>
|
<Trans i18nKey={"allowMicrophoneUse"}>
|
||||||
Разрешите использование микрофона
|
Разрешите использование камеры и микрофона
|
||||||
</Trans>
|
</Trans>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-10">
|
<div className="mb-10">
|
||||||
<p className="text-[#77828C] text-xs">
|
<p className="text-[#77828C] text-xs">
|
||||||
<Trans i18nKey={"turnOffMicrophone"}>
|
<Trans i18nKey={"turnOffMicrophone"}>
|
||||||
Выключить микрофон можно в любой момент
|
Выключить камеру и микрофон можно в любой момент
|
||||||
</Trans>
|
</Trans>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{liveKitRoom ? (
|
<div className="flex gap-2">
|
||||||
<div className="flex gap-2">
|
<Button
|
||||||
<Button
|
variant="secondary"
|
||||||
variant="secondary"
|
fullWidth
|
||||||
fullWidth
|
large
|
||||||
large
|
onClick={disallowMic}
|
||||||
onClick={disallowMic}
|
>
|
||||||
>
|
<Trans i18nKey={"skip"}>Пропустить</Trans>
|
||||||
<Trans i18nKey={"skip"}>Пропустить</Trans>
|
</Button>
|
||||||
</Button>
|
<Button fullWidth large onClick={getPermission}>
|
||||||
<Button fullWidth large onClick={allowMic}>
|
<Trans i18nKey={"allow"}>Разрешить</Trans>
|
||||||
<Trans i18nKey={"allow"}>Разрешить</Trans>
|
</Button>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<img
|
|
||||||
src="/icons/LoaderPrimary.png"
|
|
||||||
alt=""
|
|
||||||
className="w-8 h-8 animate-spin"
|
|
||||||
/>
|
|
||||||
<span className="text-sm text-[#77828C]">
|
|
||||||
<Trans i18nKey={"connectingToVoiceServer"}>
|
|
||||||
Подключение к голосовому серверу
|
|
||||||
</Trans>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { PixelStreamingWrapper2 } from "../components/PixelStreamingWrapper2";
|
||||||
|
import api from "../utils/api";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
function StreamPage3() {
|
||||||
|
const params = useParams();
|
||||||
|
const [WSUrl, setWSUrl] = useState<string>("");
|
||||||
|
|
||||||
|
async function getActiveSession() {
|
||||||
|
const activeSession: any = await api
|
||||||
|
.get(`activeSessions/${params.id}`)
|
||||||
|
.json();
|
||||||
|
|
||||||
|
// if (activeSession?.endAt) {
|
||||||
|
// setEndAt(activeSession.endAt);
|
||||||
|
// }
|
||||||
|
|
||||||
|
return activeSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getWSUrl() {
|
||||||
|
const activeSession = await getActiveSession();
|
||||||
|
|
||||||
|
if (!activeSession || activeSession.status === "error") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setWSUrl(
|
||||||
|
`wss://${activeSession.location}.sess.stream.graff.tech/${activeSession.name}/${activeSession.cirrusPort}/`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getWSUrl();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-screen">
|
||||||
|
{WSUrl && (
|
||||||
|
<PixelStreamingWrapper2
|
||||||
|
initialSettings={{
|
||||||
|
AutoPlayVideo: true,
|
||||||
|
AutoConnect: true,
|
||||||
|
ss: WSUrl,
|
||||||
|
StartVideoMuted: true,
|
||||||
|
HoveringMouse: true,
|
||||||
|
WaitForStreamer: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StreamPage3;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { devtools, persist } from "zustand/middleware";
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Actions {
|
||||||
|
setName: (name: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStreamStore = create<State & Actions>()(
|
||||||
|
devtools(
|
||||||
|
persist(
|
||||||
|
(set) => ({
|
||||||
|
name: "",
|
||||||
|
setName: (name) => set({ name }),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "auth",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export default useStreamStore;
|
||||||
@@ -5,6 +5,7 @@ interface IUser {
|
|||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
isControlAllowed: boolean;
|
isControlAllowed: boolean;
|
||||||
isMicAllowed: boolean;
|
isMicAllowed: boolean;
|
||||||
|
peerId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default IUser;
|
export default IUser;
|
||||||
|
|||||||
@@ -43,6 +43,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
sdp "^3.1.0"
|
sdp "^3.1.0"
|
||||||
|
|
||||||
|
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.5@^0.0.12":
|
||||||
|
version "0.0.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@epicgames-ps/lib-pixelstreamingfrontend-ue5.5/-/lib-pixelstreamingfrontend-ue5.5-0.0.12.tgz#8840133405b811d12fd7ac79bec1b75db274b743"
|
||||||
|
integrity sha512-vLJTrjuxhG+TqDhTK+ETwEmN0jSorBMdKG5AH2C01jL6IRFI21ipxD8lQ9ySq9vnfjXmhh036pEtJwR5DlY6sQ==
|
||||||
|
dependencies:
|
||||||
|
sdp "^3.1.0"
|
||||||
|
|
||||||
"@esbuild/android-arm64@0.18.20":
|
"@esbuild/android-arm64@0.18.20":
|
||||||
version "0.18.20"
|
version "0.18.20"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622"
|
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622"
|
||||||
@@ -293,6 +300,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@livekit/components-styles/-/components-styles-1.0.10.tgz#41becdb7649629e586daea02b0b8bf4375f75ccf"
|
resolved "https://registry.yarnpkg.com/@livekit/components-styles/-/components-styles-1.0.10.tgz#41becdb7649629e586daea02b0b8bf4375f75ccf"
|
||||||
integrity sha512-WCTtXnMAcZiXgo2N+1SmlcpPwltpGp8fW+x83P9OnHHTK3qrKEzfcfKi1z0xS8VMu/KKLgJm+yhAlCmZPme/RA==
|
integrity sha512-WCTtXnMAcZiXgo2N+1SmlcpPwltpGp8fW+x83P9OnHHTK3qrKEzfcfKi1z0xS8VMu/KKLgJm+yhAlCmZPme/RA==
|
||||||
|
|
||||||
|
"@msgpack/msgpack@^2.8.0":
|
||||||
|
version "2.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@msgpack/msgpack/-/msgpack-2.8.0.tgz#4210deb771ee3912964f14a15ddfb5ff877e70b9"
|
||||||
|
integrity sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==
|
||||||
|
|
||||||
"@nodelib/fs.scandir@2.1.5":
|
"@nodelib/fs.scandir@2.1.5":
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||||
@@ -437,6 +449,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
undici-types "~5.26.4"
|
undici-types "~5.26.4"
|
||||||
|
|
||||||
|
"@types/peerjs@^1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/peerjs/-/peerjs-1.1.0.tgz#822962d78b26dc43c113fac0a8bf653e12851487"
|
||||||
|
integrity sha512-dVocsfYFg5QQuUB9OAxfrSvz4br4pyX+7M61ZJSRiYtE3NdayShk1p1Y8b9TmCj724TwHskVraeF7wyPl0rYcg==
|
||||||
|
dependencies:
|
||||||
|
peerjs "*"
|
||||||
|
|
||||||
"@types/prop-types@*":
|
"@types/prop-types@*":
|
||||||
version "15.7.11"
|
version "15.7.11"
|
||||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563"
|
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563"
|
||||||
@@ -1095,6 +1114,11 @@ esutils@^2.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||||
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
||||||
|
|
||||||
|
eventemitter3@^4.0.7:
|
||||||
|
version "4.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
|
||||||
|
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
|
||||||
|
|
||||||
events@^3.3.0:
|
events@^3.3.0:
|
||||||
version "3.3.0"
|
version "3.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
|
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
|
||||||
@@ -1849,6 +1873,21 @@ path-type@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
||||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||||
|
|
||||||
|
peerjs-js-binarypack@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/peerjs-js-binarypack/-/peerjs-js-binarypack-2.1.0.tgz#f0fc822d3cb54ab1022f4bd580308475e8f77b70"
|
||||||
|
integrity sha512-YIwCC+pTzp3Bi8jPI9UFKO0t0SLo6xALnHkiNt/iUFmUUZG0fEEmEyFKvjsDKweiFitzHRyhuh6NvyJZ4nNxMg==
|
||||||
|
|
||||||
|
peerjs@*, peerjs@^1.5.4:
|
||||||
|
version "1.5.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/peerjs/-/peerjs-1.5.4.tgz#bcf933406d07fad9b2a34ae2e8215ba3f1878672"
|
||||||
|
integrity sha512-yFsoLMnurJKlQbx6kVSBpOp+AlNldY1JQS2BrSsHLKCZnq6t7saHleuHM5svuLNbQkUJXHLF3sKOJB1K0xulOw==
|
||||||
|
dependencies:
|
||||||
|
"@msgpack/msgpack" "^2.8.0"
|
||||||
|
eventemitter3 "^4.0.7"
|
||||||
|
peerjs-js-binarypack "^2.1.0"
|
||||||
|
webrtc-adapter "^9.0.0"
|
||||||
|
|
||||||
picocolors@^1.0.0:
|
picocolors@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||||
@@ -2471,6 +2510,11 @@ use-clipboard-copy@^0.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
clipboard-copy "^3.0.0"
|
clipboard-copy "^3.0.0"
|
||||||
|
|
||||||
|
use-is-audio-active@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/use-is-audio-active/-/use-is-audio-active-1.0.0.tgz#4e0fcb99c9d75782da3f2e2cf7f7da00bf5da397"
|
||||||
|
integrity sha512-8GZaxCe7DaBi1VeDDiHY9+aO1vCb0OCQb6+Gwf3j46Nxo/sITcgPmKKt0TiJVgbDlbEVP4RWu9krpadA57V6Ag==
|
||||||
|
|
||||||
use-sync-external-store@1.2.0:
|
use-sync-external-store@1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
||||||
@@ -2530,6 +2574,13 @@ webrtc-adapter@^8.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
sdp "^3.2.0"
|
sdp "^3.2.0"
|
||||||
|
|
||||||
|
webrtc-adapter@^9.0.0:
|
||||||
|
version "9.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/webrtc-adapter/-/webrtc-adapter-9.0.1.tgz#d4efa22ca9604cb2c8cdb9e492815ba37acfa0b2"
|
||||||
|
integrity sha512-1AQO+d4ElfVSXyzNVTOewgGT/tAomwwztX/6e3totvyyzXPvXIIuUUjAmyZGbKBKbZOXauuJooZm3g6IuFuiNQ==
|
||||||
|
dependencies:
|
||||||
|
sdp "^3.2.0"
|
||||||
|
|
||||||
which@^2.0.1:
|
which@^2.0.1:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
||||||
|
|||||||
Reference in New Issue
Block a user