This commit is contained in:
2024-08-07 16:46:48 +05:00
parent 04c5316b68
commit 3fdf64415e
16 changed files with 349 additions and 665 deletions
+2 -1
View File
@@ -4,4 +4,5 @@ VITE_COORD_URL=https://coord.graff.tech
VITE_CRM_API_URL=https://crm.stream.graff.tech/api
# VITE_API_URL=http://localhost:5002
VITE_API_URL=https://stream.graff.tech/api
VITE_SOCKET_URL=http://localhost:5003
# VITE_SOCKET_URL=http://localhost:5003
VITE_SOCKET_URL=https://stream.graff.tech
+2 -5
View File
@@ -10,17 +10,14 @@
"preview": "vite preview"
},
"dependencies": {
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.3": "^1.0.4",
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.5": "^0.0.12",
"@livekit/components-react": "^2.0.3",
"@livekit/components-styles": "^1.0.10",
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.3": "^1.0.6",
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.5": "^0.1.4",
"@uidotdev/usehooks": "^2.4.1",
"ahooks": "^3.7.10",
"date-fns": "^2.30.0",
"i18next": "^23.8.2",
"i18next-browser-languagedetector": "^7.2.0",
"ky": "^1.1.3",
"livekit-client": "^1.13.2",
"peerjs": "^1.5.4",
"react": "^18.2.0",
"react-calendar": "^4.3.0",
+3 -3
View File
@@ -167,8 +167,8 @@ function App() {
</p>
</div>
<div className="sm:mt-16 mt-8 grid sm:grid-cols-2 lg:gap-4 sm:gap-3 gap-2">
<div
<div className="sm:mt-16 mt-8 grid sm:grid-cols-1 lg:gap-4 sm:gap-3 gap-2">
{/* <div
className="group relative sm:h-full h-[264px] bg-gray-700 bg-no-repeat bg-center bg-cover"
style={{
backgroundImage: `url("/images/cards/upside.jpg")`,
@@ -200,7 +200,7 @@ function App() {
<ArrowRightIcon />
</button>
</div>
</div>
</div> */}
<div className="grid lg:grid-cols-2 lg:gap-4 sm:gap-3 gap-2">
{i18n.language === "ru" ? (
-418
View File
@@ -1,418 +0,0 @@
/* eslint-disable no-irregular-whitespace */
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useParams, useSearchParams } from "react-router-dom";
import { FullScreen, useFullScreenHandle } from "react-full-screen";
import { useEffect, useRef, useState } from "react";
import ky from "ky";
import { Socket, io } from "socket.io-client";
import userAgentParser from "ua-parser-js";
import { Player } from "./components/Player";
import { Trans, useTranslation } from "react-i18next";
import ShareIcon from "./components/icons/ShareIcon";
import ModalContainer from "./components/ModalContainer";
import useModalStore from "./stores/useModalStore";
import ShareModal from "./components/modals/ShareModal";
import { Transition } from "react-transition-group";
import FullscreenIcon from "./components/icons/FullscreenIcon";
import WindowedModeIcon from "./components/icons/WindowedModeIcon";
import QRIcon from "./components/icons/QRIcon";
import QRCodeModal from "./components/modals/QRCodeModal";
import PersonsIcon from "./components/icons/PersonsIcon";
import UsersManagementModal from "./components/modals/UsersManagementModal";
import useStreamUserStore from "./stores/useStreamUserStore";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
// import UserIcon from "./components/icons/UserIcon";
// import HandOnIcon from "./components/icons/HandOnIcon";
import AlertIcon from "./components/icons/AlertIcon";
import useSocketStore from "./stores/useSocketStore";
import { LiveKitRoom, RoomAudioRenderer } from "@livekit/components-react";
import Chat from "./components/Chat";
// import AFKTimerModal from "./components/modals/AFKTimerModal";
import { differenceInMilliseconds, format, parseISO } from "date-fns";
import HandOnIcon from "./components/icons/HandOnIcon";
import { useMobileOrientation } from "react-device-detect";
import RotateDeviceIcon from "./components/icons/RotateDeviceIcon";
function StreamPage() {
const { t } = useTranslation();
const handleFullScreen = useFullScreenHandle();
const [streamUrl, setStreamUrl] = useState<string>("");
const [isStreamEnded, setIsStreamEnded] = useState<boolean>(false);
const [isStreamLoaded, setStreamLoaded] = useState<boolean>(false);
const [me, setMe] = useState<any>({});
const meRef = useRef<any>({});
meRef.current = me;
const [modal, setModal] = useModalStore((state) => [
state.modal,
state.setModal,
]);
const params = useParams();
const [searchParams] = useSearchParams();
const roomId = params.id;
const livekitServerUrl = "wss://livekit.stream.graff.tech";
const [token, setToken] = useState<string>();
const [socket, setSocket] = useSocketStore((state) => [
state.socket,
state.setSocket,
]);
const [users, setUsers] = useStreamUserStore((state) => [
state.users,
state.setUsers,
]);
const [isShowChat, setIsShowChat] = useState<boolean>(false);
const [stopwatch, setStopwatch] = useState<string>();
const { orientation } = useMobileOrientation();
async function getToken() {
if (!socket) return;
const { token }: any = await ky
.get(
`https://coord.graff.tech/getToken?roomName=${roomId}&participantName=${socket.id}`
)
.json();
setToken(token);
}
function updateStartDate(date: Date) {
const diffMs = differenceInMilliseconds(new Date(), date);
setStopwatch(format(diffMs, "mm:ss"));
setTimeout(() => {
updateStartDate(date);
}, 1000);
}
async function connect() {
const activeSession: any = await ky
.get(`${import.meta.env.VITE_COORD_URL}/active_sessions/${params.id}`)
.json();
if (activeSession) {
setStreamUrl(
`wss://${activeSession.location}.sess.stream.graff.tech/${activeSession.server}/${activeSession.cirrusPort}/`
);
updateStartDate(parseISO(activeSession.createdAt));
setStreamLoaded(true);
} else {
setIsStreamEnded(true);
}
}
function update(socketId: string, data: { [key: string]: any }) {
if (!socket) return;
socket.emit("update", socketId, data);
}
function kick(socketId: string) {
if (!socket) return;
socket.emit("kick", socketId);
}
function toastWarn(text: string) {
toast.warn(text, {
position: "top-center",
autoClose: 2000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
theme: "dark",
icon: <AlertIcon />,
closeButton: false,
});
}
// function toastUser(text: string) {
// toast.info(text, {
// position: "top-center",
// autoClose: 2000,
// hideProgressBar: true,
// closeOnClick: true,
// pauseOnHover: true,
// draggable: true,
// theme: "dark",
// icon: <UserIcon />,
// closeButton: false,
// });
// }
function toastHandOn(text: string) {
toast.info(text, {
position: "top-center",
autoClose: 2000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
theme: "dark",
icon: <HandOnIcon />,
closeButton: false,
});
}
async function checkIsActiveSession() {
const activeSession: any = await ky
.get(`${import.meta.env.VITE_COORD_URL}/active_sessions/${params.id}`)
.json();
if (!activeSession) {
setIsStreamEnded(true);
return;
}
if (!isStreamEnded) {
setTimeout(() => {
checkIsActiveSession();
}, 1000);
}
}
useEffect(() => {
connect();
const socket: Socket = io(import.meta.env.VITE_COORD_URL, {
query: {
roomId,
admin: searchParams.has("admin", "true"),
},
});
socket.on("connect", () => {
setSocket(socket);
});
socket.on("join", (_socketId, sockets) => {
setUsers(sockets);
toastHandOn(t("notification.newMember"));
});
socket.on("update", (_socketId, _data, sockets) => {
setUsers(sockets);
});
socket.on("kick", () => {
socket.close();
window.close();
});
socket.on("leave", (socketId) => {
setUsers(
useStreamUserStore
.getState()
.users.filter((user: any) => user.id !== socketId)
);
});
socket.on("notification", (type) => {
if (type === "afk-timer") {
// setModal(<AFKTimerModal />);
}
});
checkIsActiveSession();
return () => {
socket.off("connect");
socket.off("join");
socket.off("update");
socket.off("kick");
socket.off("leave");
socket.close();
};
}, []);
useEffect(() => {
if (!socket) return;
getToken();
}, [socket]);
useEffect(() => {
if (socket) {
setMe(users.find((user: any) => user.id === socket.id));
}
}, [users]);
useEffect(() => {
if (me && me.allowControl && !me.admin) {
toastHandOn(t("notification.controlReceived"));
}
}, [me]);
return (
<FullScreen handle={handleFullScreen} className="h-screen text-[#F2F2F2]">
{!isStreamEnded ? (
isStreamLoaded ? (
<>
{orientation === "landscape" ? (
<>{streamUrl && <Player ss={streamUrl} />}</>
) : (
<div className="absolute top-0 left-0 w-full h-full flex flex-col justify-center items-center gap-2">
<RotateDeviceIcon />
<Trans i18nKey="rotateDevice">Поверните устройство</Trans>
</div>
)}
</>
) : (
<div className="absolute top-0 left-0 w-full h-full flex justify-center items-center gap-4">
<p className="flex items-center gap-4">
<img
src="/icons/Loader.png"
alt=""
className="animate-spin w-6 h-6"
/>
<Trans i18nKey="streamWaiting">Ожидание потока</Trans>
</p>
</div>
)
) : (
<div className="absolute top-0 left-0 w-full h-full flex justify-center items-center">
<p className="text-2xl font-gilroy">
<Trans i18nKey="streamEnded">Трансляция была завершена</Trans>
</p>
</div>
)}
{me && !me.allowControl && (
<>
{new userAgentParser(me.ua).getDevice().type === "mobile" ? (
<div
className="absolute top-0 left-0 w-full h-full"
onTouchStart={() => toastWarn(t("notification.getAccess"))}
></div>
) : (
<div
className="absolute top-0 left-0 w-full h-full"
onMouseDown={() => toastWarn(t("notification.getAccess"))}
></div>
)}
</>
)}
<div className="absolute top-0 left-0 min-h-screen flex flex-col justify-center transition-all">
<div className="flex flex-col gap-4 lg:p-4 p-2">
{new userAgentParser().getDevice().model !== "iPhone" && (
<>
{!handleFullScreen.active ? (
<button
onClick={() => handleFullScreen.enter()}
className="relative group outline-none bg-[#131313] rounded-full shadow-lg shadow-[#131313] p-2 opacity-90"
>
<FullscreenIcon />
<span className="absolute left-12 top-[50%] -translate-y-[50%] invisible group-hover:visible opacity-0 group-hover:opacity-100 text-xs transition-all px-2 py-1 bg-[#131313] rounded">
<Trans i18nKey={"fullscreenMode"}>
Полноэкранный режим
</Trans>
</span>
</button>
) : (
<button
onClick={() => handleFullScreen.exit()}
className="relative group outline-none bg-[#131313] rounded-full shadow-lg shadow-[#131313] p-2 opacity-90"
>
<WindowedModeIcon />
<span className="absolute left-12 top-[50%] -translate-y-[50%] invisible group-hover:visible opacity-0 group-hover:opacity-100 text-xs transition-all px-2 py-1 bg-[#131313] rounded">
<Trans i18nKey={"windowedMode"}>Оконный режим</Trans>
</span>
</button>
)}
</>
)}
<button
onClick={() => setModal(<QRCodeModal />)}
className="relative group outline-none bg-[#131313] rounded-full shadow-lg shadow-[#131313] p-2 opacity-90"
>
<QRIcon />
<span className="absolute left-12 top-[50%] -translate-y-[50%] invisible group-hover:visible opacity-0 group-hover:opacity-100 text-xs transition-all px-2 py-1 bg-[#131313] rounded">
<Trans i18nKey={"inviteByQRCode"}>Пригласить по QR коду</Trans>
</span>
</button>
<button
onClick={() => setModal(<ShareModal />)}
className="relative group outline-none bg-[#131313] rounded-full shadow-lg shadow-[#131313] p-2 opacity-90"
>
<ShareIcon />
<span className="absolute left-12 top-[50%] -translate-y-[50%] invisible group-hover:visible opacity-0 group-hover:opacity-100 text-xs transition-all px-2 py-1 bg-[#131313] rounded">
<Trans i18nKey={"inviteByLink"}>Пригласить по ссылке</Trans>
</span>
</button>
<button
onClick={() =>
setModal(
<UsersManagementModal
me={me}
handleUpdate={update}
handleKick={kick}
/>
)
}
className="relative group outline-none bg-[#131313] rounded-full shadow-lg shadow-[#131313] p-2 opacity-90"
>
<PersonsIcon />
<span className="absolute left-12 top-[50%] -translate-y-[50%] invisible group-hover:visible opacity-0 group-hover:opacity-100 text-xs transition-all px-2 py-1 bg-[#131313] rounded">
<Trans i18nKey={"members"}>Участники</Trans>
</span>
<div className="absolute px-2 py-1 text-xs flex flex-col justify-center items-center rounded-full -top-2 -right-2 bg-[#131313] -z-10">
{users.length}
</div>
</button>
{/* <button
onClick={() => setIsShowChat(true)}
className="relative group outline-none bg-[#131313] rounded-full shadow-lg shadow-[#131313] p-2 opacity-90 flex justify-center items-center"
>
<ChatIcon className="w-5 h-5" />
<span className="absolute left-12 top-[50%] -translate-y-[50%] invisible group-hover:visible opacity-0 group-hover:opacity-100 text-xs transition-all px-2 py-1 bg-[#131313] rounded">
<Trans i18nKey={"chat"}>Чат</Trans>
</span>
</button> */}
<LiveKitRoom
video={true}
audio={true}
token={token}
serverUrl={livekitServerUrl}
>
<RoomAudioRenderer />
</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">
<p className="text-xs">{stopwatch}</p>
</div>
</div>
</div>
<Transition in={isShowChat} timeout={200} mountOnEnter unmountOnExit>
{(state) => (
<div className="absolute right-0 bottom-0">
<Chat className={state} handleClose={() => setIsShowChat(false)} />
</div>
)}
</Transition>
<Transition
in={modal ? true : false}
timeout={200}
mountOnEnter
unmountOnExit
>
{(state) => <ModalContainer className={state} />}
</Transition>
<ToastContainer />
</FullScreen>
);
}
export default StreamPage;
+86
View File
@@ -0,0 +1,86 @@
import { format } from "date-fns";
import { t } from "i18next";
import { Trans } from "react-i18next";
// import useSocketStore from "../stores/useSocketStore";
import CloseIcon from "./icons/CloseIcon";
import SendChatIcon from "./icons/SendChatIcon";
import Button from "./ui/Button";
import useStreamUserStore from "../stores/useStreamUserStore";
import { FormEvent, useRef, useState } from "react";
import IMessage from "../types/IMessage";
import useStateRef from "react-usestateref";
interface Props {
onClose: () => void;
}
function Chat2({ onClose }: Props) {
// const { socket } = useSocketStore();
const { me, users } = useStreamUserStore();
const [messages] = useStateRef<IMessage[]>([]);
const messagesRef = useRef<HTMLDivElement>(null);
const [messageText, setMessageText] = useState<string>("");
const messageTextRef = useRef<HTMLInputElement>(null);
function sendMessage(e: FormEvent) {
e.preventDefault();
}
return (
<div className={`chat relative h-full flex flex-col w-[296px] bg-white`}>
<div className="flex items-center justify-between p-2 pl-4 border-b border-[#DAE0E5]">
<p className="text-sm font-semibold">
<Trans i18nKey={"chat"}>Чат</Trans>
</p>
<Button
variant="tertiary"
icon={<CloseIcon />}
onlyIcon
onClick={onClose}
/>
</div>
<div
ref={messagesRef}
className="flex-1 overflow-y-scroll flex flex-col gap-1 p-3 border-b border-[#DAE0E5]"
>
{messages.map((message, index) => (
<div
key={index}
className={`text-sm p-2 flex flex-col gap-1 rounded-lg ${
users.find((user) => user.id === me?.id)?.name === message.name
? "bg-[#C4DDF5]"
: "bg-[#F0F1F2]"
}`}
>
{users.find((user) => user.id === me?.id)?.name !==
message.name && (
<p className="text-[#49A1F5] font-semibold">{message.name}</p>
)}
<p className="break-words">{message.text}</p>
<p className="text-[#767676] translate-x-1 translate-y-1 self-end leading-none">
{format(new Date(), "HH:mm")}
</p>
</div>
))}
</div>
<div className="p-3 pl-4">
<form onSubmit={sendMessage} className="flex items-center gap-3">
<input
ref={messageTextRef}
type="text"
placeholder={t("writeAMessage")}
className="w-full bg-transparent text-sm outline-none"
value={messageText}
onChange={(e) => setMessageText(e.target.value)}
/>
<button type="submit" className="text-[#49A1F5] outline-none">
<SendChatIcon />
</button>
</form>
</div>
</div>
);
}
export default Chat2;
+9 -3
View File
@@ -3,9 +3,15 @@ import useModalStore from "../stores/useModalStore";
function ModalContainer2() {
const { modal } = useModalStore();
if (modal) {
return <div className="fixed top-0 left-0 w-full h-full">{modal}</div>;
}
return (
<div
className={`fixed top-0 left-0 w-full h-full ${
!modal ? "hidden" : ""
}`}
>
{modal}
</div>
);
}
export default ModalContainer2;
+1 -1
View File
@@ -6,7 +6,7 @@ import {
Config,
AllSettings,
PixelStreaming,
} from "@epicgames-ps/lib-pixelstreamingfrontend-ue5.5";
} from "@epicgames-ps/lib-pixelstreamingfrontend-ue5.3";
export interface PixelStreamingWrapperProps {
initialSettings?: Partial<AllSettings>;
+1 -1
View File
@@ -4,7 +4,7 @@ interface TooltipProps {
function Tooltip({ text }: TooltipProps) {
return (
<div className="group-hover:opacity-100 opacity-0 transition-opacity absolute top-[calc(100%+12px)] -left-1.5 bg-[#111C26] rounded-lg px-4 py-2 z-10 pointer-events-none whitespace-nowrap">
<div className="absolute top-[calc(100%+12px)] -left-1.5 z-20 group-hover:opacity-100 opacity-0 transition-opacity bg-[#111C26] rounded-lg px-4 py-2 pointer-events-none whitespace-nowrap">
<svg
width="12"
height="10"
+1 -1
View File
@@ -47,7 +47,7 @@ function Video({ mediaStream, muted, user }: Props) {
<div className="relative">
<video
ref={remoteVideoRef}
className={`aspect-video w-[216px] h-[162px] rounded-lg object-cover bg-gray-500 ring-2 ${
className={`aspect-video lg:w-[216px] w-[160px] lg:h-[162px] h-[120px] rounded-lg object-cover bg-gray-500 ring-2 ${
!_muted && isSpeaking ? "ring-green-500" : "ring-transparent"
}`}
playsInline
+1 -1
View File
@@ -31,7 +31,7 @@ function InviteModal() {
}
return (
<div className="absolute top-0 lg:left-0 left-12 lg:w-full w-[calc(100vw-48px)] h-full lg:bg-black lg:bg-opacity-50 bg-white sm:border-none border-l border-[#DAE0E5] flex flex-col lg:items-center lg:justify-center">
<div className="relative top-0 lg:left-0 left-12 lg:w-full w-[calc(100vw-48px)] h-full lg:bg-black lg:bg-opacity-50 bg-white sm:border-none border-l border-[#DAE0E5] flex flex-col lg:items-center lg:justify-center">
<div className="bg-white flex flex-col lg:rounded-lg lg:w-[400px] lg:flex-none flex-1">
<div className="p-2 pl-6 flex items-center justify-between border-b border-[#DAE0E5]">
<p className="text-sm font-semibold">
@@ -30,7 +30,7 @@ function SetNameModal({ onAction }: Props) {
return (
<div className="flex items-center justify-center w-full h-full bg-opacity-50 backdrop-blur-2xl">
<div className="bg-white p-12 rounded-lg space-y-6">
<div className="bg-white sm:p-12 p-6 sm:rounded-lg space-y-6 sm:h-auto h-full sm:w-auto w-full flex flex-col max-sm:justify-center">
<p className="text-2xl font-semibold">Здравствуйте!</p>
<div className="space-y-2">
<p className="font-semibold">Представьтесь, пожалуйста</p>
@@ -46,6 +46,7 @@ function SetNameModal({ onAction }: Props) {
onChange={handleChangeName}
autoFocus={!name}
required
className="max-sm:w-full"
/>
</div>
<div className="flex gap-2">
@@ -54,10 +55,11 @@ function SetNameModal({ onAction }: Props) {
type="submit"
large
onClick={handleClickNoName}
className="max-sm:w-full"
>
Не указывать
</Button>
<Button type="submit" large>
<Button type="submit" large className="max-sm:w-full">
Продолжить
</Button>
</div>
+3 -1
View File
@@ -6,6 +6,7 @@ interface InputProps {
autoFocus?: boolean;
required?: boolean;
value?: string;
className?: string;
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
}
@@ -15,13 +16,14 @@ function Input({
autoFocus,
required,
value,
className,
onChange,
}: InputProps) {
return (
<input
type={type}
placeholder={placeholder}
className="bg-white border border-[#DAE0E5] w-[296px] h-10 px-2 py-2.5 rounded-lg text-sm outline-none"
className={`bg-white border border-[#DAE0E5] w-[296px] h-10 px-2 py-2.5 rounded-lg text-sm outline-none ${className}`}
autoFocus={autoFocus}
required={required}
value={value}
-4
View File
@@ -31,7 +31,3 @@
width: auto;
margin-inline-end: 8px;
}
/* LiveKit */
+123 -83
View File
@@ -9,7 +9,7 @@ import useStateRef from "react-usestateref";
import Peer from "peerjs";
import useIsAudioActive from "use-is-audio-active";
import { v4 as uuidv4 } from "uuid";
import { io, Socket } from "socket.io-client";
import { io } from "socket.io-client";
import IRemoteStream from "../types/IRemoteStream";
import Video from "../components/Video";
import ModalContainer2 from "../components/ModalContainer2";
@@ -25,7 +25,7 @@ import MicroOffIcon from "../components/icons/MicroOffIcon";
import CameraOnIcon from "../components/icons/CameraOnIcon";
import CameraOffIcon from "../components/icons/CameraOffIcon";
import { Trans } from "react-i18next";
import { isIOS } from "react-device-detect";
import { isIOS, useMobileOrientation } from "react-device-detect";
import WindowIcon from "../components/icons/WindowIcon";
import FullscreenIcon from "../components/icons/FullscreenIcon";
import ShareIcon from "../components/icons/ShareIcon";
@@ -34,6 +34,12 @@ import InviteModal from "../components/modals/stream/InviteModal";
import Tooltip from "../components/Tooltip";
import { toast, ToastContainer } from "react-toastify";
import LoadingModal from "../components/modals/stream/LoadingModal";
import useSocketStore from "../stores/useSocketStore";
import useStreamUserStore from "../stores/useStreamUserStore";
import Chat2 from "../components/Chat2";
import Rotate64Icon from "../components/icons/Rotate64Icon";
import Draggable from "react-draggable";
// import ChatIcon from "../components/icons/ChatIcon";
// import MoreIcon from "../components/icons/MoreIcon";
@@ -57,11 +63,10 @@ function StreamPage3() {
const isSpeaking = useIsAudioActive({
source: localStream.getTracks().length ? localStream : null,
});
const [users, setUsers, usersRef] = useStateRef<IUser[]>([]);
const [me, setMe, meRef] = useStateRef<IUser>();
const { me, users, setMe, setUsers } = useStreamUserStore();
const isCallInit = useRef<boolean>(false);
const [roomId] = useState<string>(params.id!);
const [socket, setSocket] = useState<Socket>();
const { socket, setSocket } = useSocketStore();
const { setModal } = useModalStore();
const { name } = useStreamStore();
const [isMicEnabled, setIsMicEnabled] = useState(true);
@@ -72,6 +77,8 @@ function StreamPage3() {
const [isFullscreen, { toggleFullscreen }] = useFullscreen(fullscreenRef);
const [isVideoInitialized, setIsVideoInitialized] = useState<boolean>(false);
const [step, setStep] = useState<number>(1);
const [isShowChat, setIsShowChat] = useState<boolean>(false);
const { isPortrait } = useMobileOrientation();
async function startCall(remotePeerId: string) {
if (!peerInstance) return;
@@ -187,20 +194,20 @@ function StreamPage3() {
isCallInit.current = true;
}
setUsers(users);
setMe(users.find((user) => user.id === userId));
setUsers(users);
});
socket.on("request-control", (userId) => {
const user = usersRef.current.find((user) => user.id === userId);
const user = users.find((user) => user.id === userId);
if (user?.id === meRef.current?.id || !meRef.current?.isAdmin) return;
if (user?.id === me?.id || !me?.isAdmin) return;
toast.info(`${user?.name} запрашивает разрешение на управление`);
});
socket.on("transfer-control", (userId) => {
if (meRef.current?.id !== userId) return;
if (me?.id !== userId) return;
toast.info(`Вы получили разрешение на управление`);
});
@@ -336,16 +343,20 @@ function StreamPage3() {
return (
<div
ref={fullscreenRef}
className="h-[100dvh] flex flex-col bg-[#111C26] overflow-hidden"
className="h-[100dvh] flex lg:flex-col bg-[#111C26] overflow-hidden"
>
{isEnded === false ? (
{isEnded === false && (
<>
<div className="flex items-center bg-white h-12 px-6">
<div className="pr-6">
<img src="/images/logo24.svg" alt="" />
<div className="flex max-lg:flex-col items-center justify-between bg-white lg:h-12 lg:w-auto w-12 lg:px-6 max-lg:py-2">
<div className="lg:pr-6">
<img
src="/images/logo24.svg"
alt=""
className="lg:block hidden"
/>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<div className="lg:flex hidden items-center gap-2">
<div className="relative w-6 h-6 bg-[#E6ECF2] rounded-full flex items-center justify-center">
<p className="text-xs font-semibold">
{name[0]?.toUpperCase()}
@@ -356,7 +367,7 @@ function StreamPage3() {
</div>
<p className="text-xs">{name}</p>
</div>
<div className="flex items-center gap-2">
<div className="flex max-lg:flex-col items-center gap-2">
<div className="relative group">
<Button
variant="secondary"
@@ -416,47 +427,58 @@ function StreamPage3() {
</>
)}
</div>
<div className="h-4 w-px bg-[#DAE0E5]"></div>
{users.map((user) => {
if (user.id !== userId) {
return (
<div key={user.id} className="flex items-center gap-2">
<div className="relative w-6 h-6 bg-[#E6ECF2] rounded-full flex items-center justify-center">
<p className="text-xs font-semibold">
{name[0]?.toUpperCase()}
</p>
{user?.isControlAllowed && (
<div className="absolute bottom-0 right-0 bg-[#49A1F5] w-2 h-2 rounded-full border border-white"></div>
)}
</div>
<p className="text-xs">{user.name}</p>
<div className="h-4 w-px bg-[#DAE0E5] lg:block hidden"></div>
<div className="lg:flex hidden">
{users.map((user) => {
if (user.id !== userId) {
return (
<div key={user.id} className="flex items-center gap-2">
<div className="relative w-6 h-6 bg-[#E6ECF2] rounded-full flex items-center justify-center">
<p className="text-xs font-semibold">
{name[0]?.toUpperCase()}
</p>
{user?.isControlAllowed && (
<div className="absolute bottom-0 right-0 bg-[#49A1F5] w-2 h-2 rounded-full border border-white"></div>
)}
</div>
<p className="text-xs">{user.name}</p>
{me?.isAdmin && me?.isControlAllowed && (
<div className="relative">
{/* <Button
{me?.isAdmin && me?.isControlAllowed && (
<div className="relative">
{/* <Button
variant="secondary"
icon={<MoreIcon />}
onlyIcon
/> */}
{/* <div className="absolute"> */}
<div className="relative group">
<Button
variant="secondary"
icon={<HandOnIcon />}
onlyIcon
onClick={() => transferControl(user.id)}
/>
<Tooltip text={"Передать управление"} />
{/* <div className="absolute"> */}
<div className="relative group">
<Button
variant="secondary"
icon={<HandOnIcon />}
onlyIcon
onClick={() => transferControl(user.id)}
/>
<Tooltip text={"Передать управление"} />
</div>
{/* </div> */}
</div>
{/* </div> */}
</div>
)}
</div>
);
}
})}
)}
</div>
);
}
})}
</div>
</div>
<div className="flex gap-2 ml-auto">
<div className="flex max-lg:flex-col gap-2 lg:ml-auto">
{/* <div className="relative group">
<Button
variant="secondary"
icon={<ChatIcon />}
onlyIcon
onClick={() => setIsShowChat((prev) => !prev)}
/>
<Tooltip text={isShowChat ? "Скрыть чат" : "Показать чат"} />
</div> */}
<div className="relative group">
<Button
variant="secondary"
@@ -498,46 +520,64 @@ function StreamPage3() {
/>
)}
{!users.find((user) => user.id === userId)?.isControlAllowed && (
<div
className="absolute top-0 left-0 w-full h-full"
onClick={() =>
toast.warn("Необходимо запросить разрешение на управление")
}
></div>
)}
{step !== 1 &&
!users.find((user) => user.id === userId)?.isControlAllowed && (
<div
className="absolute top-0 left-0 w-full h-full"
onClick={() =>
toast.warn("Необходимо запросить разрешение на управление")
}
></div>
)}
<div className="absolute top-2 left-2 space-y-2">
<div className={`relative ${!permission ? "hidden" : ""}`}>
<video
ref={localVideoRef}
className={`aspect-video w-[216px] h-[162px] rounded-lg object-cover bg-gray-500 -scale-x-100 ring-2 ${
isMicEnabled && isSpeaking
? "ring-green-500"
: "ring-transparent"
}`}
playsInline
autoPlay
muted
></video>
<div className="absolute bottom-0 p-2">
<p className="text-sm text-white">{name}</p>
<Draggable
defaultClassName="cursor-grab"
defaultClassNameDragging="cursor-grabbing"
>
<div className="absolute top-2 lg:left-2 max-lg:right-2 space-y-2">
<div className={`relative ${!permission ? "hidden" : ""}`}>
<video
ref={localVideoRef}
className={`aspect-video lg:w-[216px] w-[160px] lg:h-[162px] h-[120px] rounded-lg object-cover bg-gray-500 -scale-x-100 ring-2 ${
isMicEnabled && isSpeaking
? "ring-green-500"
: "ring-transparent"
}`}
playsInline
autoPlay
muted
></video>
<div className="absolute bottom-0 p-2">
<p className="text-sm text-white">{name}</p>
</div>
</div>
{remoteStreams.map(({ peerId, mediaStream }) => (
<Video
key={peerId}
mediaStream={mediaStream}
muted={!permission}
user={users.find((user) => user.peerId === peerId)}
/>
))}
</div>
{remoteStreams.map(({ peerId, mediaStream }) => (
<Video
key={peerId}
mediaStream={mediaStream}
muted={!permission}
user={users.find((user) => user.peerId === peerId)}
/>
))}
</div>
</Draggable>
{isShowChat && <Chat2 onClose={() => setIsShowChat(false)} />}
</div>
{isPortrait && (
<div className="absolute top-0 left-0 w-full h-full bg-white flex flex-col items-center justify-center gap-2">
<Rotate64Icon />
<p className="font-semibold">Поверните устройство</p>
</div>
)}
<ModalContainer2 />
<ToastContainer />
</>
) : (
)}
{isEnded === true && (
<div className="flex-1 flex items-center justify-center p-8">
<p className="text-2xl text-white font-gilroy text-center">
<Trans i18nKey={"demonstrationCompleted"}>
+4
View File
@@ -4,13 +4,17 @@ import { devtools } from "zustand/middleware";
import IUser from "../types/IUser";
interface StreamUserState {
me: IUser | undefined;
users: IUser[];
setMe: (me: IUser | undefined) => void;
setUsers: (users: IUser[]) => void;
}
const useStreamUserStore = create<StreamUserState>()(
devtools((set) => ({
me: undefined,
users: [],
setMe: (me) => set({ me }),
setUsers: (users) => set({ users }),
}))
);
+109 -141
View File
@@ -19,11 +19,6 @@
dependencies:
regenerator-runtime "^0.14.0"
"@bufbuild/protobuf@^1.3.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-1.8.0.tgz#1c8651ea34adb8019b483e09de02aeeb1cd57d79"
integrity sha512-qR9FwI8QKIveDnUYutvfzbC21UZJJryYrLuZGjeZ/VGz+vXelUkK+xgkOHsvPEdYEdxtgUUq4313N8QtOehJ1Q==
"@emotion/is-prop-valid@^0.7.3":
version "0.7.3"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.7.3.tgz#a6bf4fa5387cbba59d44e698a4680f481a8da6cc"
@@ -36,18 +31,28 @@
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.1.tgz#e93c13942592cf5ef01aa8297444dc192beee52f"
integrity sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg==
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.3@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@epicgames-ps/lib-pixelstreamingfrontend-ue5.3/-/lib-pixelstreamingfrontend-ue5.3-1.0.4.tgz#71533a4f940627702d26896eb3380d08c6d05523"
integrity sha512-gqt2lFGLys3YOvloK9X/gpk+ZPgRlLi+uB9kWWWMJd+Gih0jgRwc6GJ0b+LuIVmZau4qNUbTmfOklKpti0G07g==
"@epicgames-ps/lib-pixelstreamingcommon-ue5.5@^0.0.14":
version "0.0.14"
resolved "https://registry.yarnpkg.com/@epicgames-ps/lib-pixelstreamingcommon-ue5.5/-/lib-pixelstreamingcommon-ue5.5-0.0.14.tgz#bb719f1ed1da509888774f22820207e6c82de626"
integrity sha512-GXu1GJUfXAWnTHylBYAfvE9SVPXl8B6ovD/6CbQh0AXxktadbA4KsDbuEFuZHIVEVLLn6vXsBkkKf7yZODphOQ==
dependencies:
"@protobuf-ts/plugin" "^2.9.3"
"@types/ws" "^8.5.10"
ws "^8.17.1"
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.3@^1.0.6":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@epicgames-ps/lib-pixelstreamingfrontend-ue5.3/-/lib-pixelstreamingfrontend-ue5.3-1.0.6.tgz#8b6603e7ffc99f564576623bf94494f6f3a98ec4"
integrity sha512-IEeGZbSGSha+fJkIJm3VyoVAFpsqhkd5ktiV9aBeEN98wt+oKTGSJFHjXIBmyfxpZQqXmS3hrtIOUJ31XPNmdw==
dependencies:
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==
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.5@^0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@epicgames-ps/lib-pixelstreamingfrontend-ue5.5/-/lib-pixelstreamingfrontend-ue5.5-0.1.4.tgz#4279fb0a396e51e16567bc18f9df41860a96367d"
integrity sha512-1/fS60vir++vJ5n0LY4lzZEO1hdi1uMlOrD/Okbr7yT6dqRWAo7EarPlHwlIMrjxvhPe4t7Ox06CdSsZDIg7gg==
dependencies:
"@epicgames-ps/lib-pixelstreamingcommon-ue5.5" "^0.0.14"
sdp "^3.1.0"
"@esbuild/android-arm64@0.18.20":
@@ -192,26 +197,6 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f"
integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==
"@floating-ui/core@^1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1"
integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==
dependencies:
"@floating-ui/utils" "^0.2.1"
"@floating-ui/dom@1.6.1":
version "1.6.1"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.1.tgz#d552e8444f77f2d88534372369b3771dc3a2fa5d"
integrity sha512-iA8qE43/H5iGozC3W0YSnVSW42Vh522yyM1gj+BqRwVsTNOyr231PsXDaV04yT39PsO0QL2QpbI/M0ZaLUQgRQ==
dependencies:
"@floating-ui/core" "^1.6.0"
"@floating-ui/utils" "^0.2.1"
"@floating-ui/utils@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2"
integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==
"@humanwhocodes/config-array@^0.11.14":
version "0.11.14"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"
@@ -275,31 +260,6 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@livekit/components-core@0.9.1":
version "0.9.1"
resolved "https://registry.yarnpkg.com/@livekit/components-core/-/components-core-0.9.1.tgz#f06a45e7d490b48b5404374a8a13469a9ad5cf99"
integrity sha512-zD0TsiT+uW09GrHrsqEz9kzJ+raSXmu2YYgWrq5OEA7pO6ZbB8aGk2tPJthVTUwySOzW4rE7rNusOVEFvMkvxA==
dependencies:
"@floating-ui/dom" "1.6.1"
email-regex "5.0.0"
loglevel "1.9.1"
rxjs "7.8.1"
"@livekit/components-react@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@livekit/components-react/-/components-react-2.0.3.tgz#f1fb97ed53b5eda42cacf21b70ca2fa167aebb67"
integrity sha512-ejwoCbeHk94TolGlFmua6cRVmIdSCx5VKeviYoajnNcOJn1qw5u09JVU6bR24MCLYVvuxGZK5aHua1BvnW5Ryw==
dependencies:
"@livekit/components-core" "0.9.1"
"@react-hook/latest" "1.0.3"
clsx "2.1.0"
usehooks-ts "2.14.0"
"@livekit/components-styles@^1.0.10":
version "1.0.10"
resolved "https://registry.yarnpkg.com/@livekit/components-styles/-/components-styles-1.0.10.tgz#41becdb7649629e586daea02b0b8bf4375f75ccf"
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"
@@ -331,10 +291,41 @@
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
"@react-hook/latest@1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@react-hook/latest/-/latest-1.0.3.tgz#c2d1d0b0af8b69ec6e2b3a2412ba0768ac82db80"
integrity sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg==
"@protobuf-ts/plugin-framework@^2.9.4":
version "2.9.4"
resolved "https://registry.yarnpkg.com/@protobuf-ts/plugin-framework/-/plugin-framework-2.9.4.tgz#d7a617dedda4a12c568fdc1db5aa67d5e4da2406"
integrity sha512-9nuX1kjdMliv+Pes8dQCKyVhjKgNNfwxVHg+tx3fLXSfZZRcUHMc1PMwB9/vTvc6gBKt9QGz5ERqSqZc0++E9A==
dependencies:
"@protobuf-ts/runtime" "^2.9.4"
typescript "^3.9"
"@protobuf-ts/plugin@^2.9.3":
version "2.9.4"
resolved "https://registry.yarnpkg.com/@protobuf-ts/plugin/-/plugin-2.9.4.tgz#4e593e59013aaad313e7abbabe6e61964ef0ca28"
integrity sha512-Db5Laq5T3mc6ERZvhIhkj1rn57/p8gbWiCKxQWbZBBl20wMuqKoHbRw4tuD7FyXi+IkwTToaNVXymv5CY3E8Rw==
dependencies:
"@protobuf-ts/plugin-framework" "^2.9.4"
"@protobuf-ts/protoc" "^2.9.4"
"@protobuf-ts/runtime" "^2.9.4"
"@protobuf-ts/runtime-rpc" "^2.9.4"
typescript "^3.9"
"@protobuf-ts/protoc@^2.9.4":
version "2.9.4"
resolved "https://registry.yarnpkg.com/@protobuf-ts/protoc/-/protoc-2.9.4.tgz#a92262ee64d252998540238701d2140f4ffec081"
integrity sha512-hQX+nOhFtrA+YdAXsXEDrLoGJqXHpgv4+BueYF0S9hy/Jq0VRTVlJS1Etmf4qlMt/WdigEes5LOd/LDzui4GIQ==
"@protobuf-ts/runtime-rpc@^2.9.4":
version "2.9.4"
resolved "https://registry.yarnpkg.com/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.9.4.tgz#d6ab2316c0ba67ce5a08863bb23203a837ff2a3b"
integrity sha512-y9L9JgnZxXFqH5vD4d7j9duWvIJ7AShyBRoNKJGhu9Q27qIbchfzli66H9RvrQNIFk5ER7z1Twe059WZGqERcA==
dependencies:
"@protobuf-ts/runtime" "^2.9.4"
"@protobuf-ts/runtime@^2.9.4":
version "2.9.4"
resolved "https://registry.yarnpkg.com/@protobuf-ts/runtime/-/runtime-2.9.4.tgz#db8a78b1c409e26d258ca39464f4757d804add8f"
integrity sha512-vHRFWtJJB/SiogWDF0ypoKfRIZ41Kq+G9cEFj6Qm1eQaAhJ1LDFvgZ7Ja4tb3iLOQhz0PaoPnnOijF1qmEqTxg==
"@remix-run/router@1.15.3":
version "1.15.3"
@@ -442,6 +433,13 @@
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.0.tgz#d774355e41f372d5350a4d0714abb48194a489c3"
integrity sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==
"@types/node@*":
version "22.0.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.0.0.tgz#04862a2a71e62264426083abe1e27e87cac05a30"
integrity sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw==
dependencies:
undici-types "~6.11.1"
"@types/node@^20.11.17":
version "20.11.28"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.28.tgz#4fd5b2daff2e580c12316e457473d68f15ee6f66"
@@ -511,6 +509,13 @@
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba"
integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==
"@types/ws@^8.5.10":
version "8.5.11"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.11.tgz#90ad17b3df7719ce3e6bc32f83ff954d38656508"
integrity sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w==
dependencies:
"@types/node" "*"
"@typescript-eslint/eslint-plugin@^5.57.1":
version "5.62.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db"
@@ -798,16 +803,16 @@ clipboard-copy@^3.0.0:
resolved "https://registry.yarnpkg.com/clipboard-copy/-/clipboard-copy-3.2.0.tgz#3c5b8651d3512dcfad295d77a9eb09e7fac8d5fb"
integrity sha512-vooFaGFL6ulEP1liiaWFBmmfuPm3cY3y7T9eB83ZTnYc/oFeAKsq3NcDrOkBC8XaauEE8zHQwI7k0+JSYiVQSQ==
clsx@2.1.0, clsx@^2.0.0, clsx@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb"
integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==
clsx@^1.1.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
clsx@^2.0.0, clsx@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb"
integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
@@ -932,11 +937,6 @@ electron-to-chromium@^1.4.668:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.707.tgz#77904f87432b8b50b8b8b654ba3940d2bef48d63"
integrity sha512-qRq74Mo7ChePOU6GHdfAJ0NREXU8vQTlVlfWz3wNygFay6xrd/fY2J7oGHwrhFeU30OVctGLdTh/FcnokTWpng==
email-regex@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/email-regex/-/email-regex-5.0.0.tgz#c8b1f4c7f251929b53586a7a3891da09c8dea26d"
integrity sha512-he76Cm8JFxb6OGQHabLBPdsiStgPmJeAEhctmw0uhonUh1pCBsHpI6/rB62s2GNzjBb0YlhIcF/1l9Lp5AfH0Q==
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
@@ -1119,11 +1119,6 @@ eventemitter3@^4.0.7:
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
events@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@@ -1649,20 +1644,6 @@ lines-and-columns@^1.1.6:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
livekit-client@^1.13.2:
version "1.15.12"
resolved "https://registry.yarnpkg.com/livekit-client/-/livekit-client-1.15.12.tgz#426c6c1d5e1b0d0bf01b742980fd5448da0d9b6f"
integrity sha512-1mjVFNOkF7f0HHAY1Z9zZw38eCxA2xCULah5z8whPkb61kG4LJU0aNCo9LWjAynO4ODLjfq2+fZvZoCHIya+6A==
dependencies:
"@bufbuild/protobuf" "^1.3.0"
events "^3.3.0"
loglevel "^1.8.0"
sdp-transform "^2.14.1"
ts-debounce "^4.0.0"
tslib "2.6.2"
typed-emitter "^2.1.0"
webrtc-adapter "^8.1.1"
locate-path@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
@@ -1690,11 +1671,6 @@ lodash@^4.17.21:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
loglevel@1.9.1, loglevel@^1.8.0:
version "1.9.1"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.1.tgz#d63976ac9bcd03c7c873116d41c2a85bafff1be7"
integrity sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -2209,13 +2185,6 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
rxjs@7.8.1, rxjs@^7.5.2:
version "7.8.1"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
dependencies:
tslib "^2.1.0"
scheduler@^0.23.0:
version "0.23.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
@@ -2228,11 +2197,6 @@ screenfull@^5.0.0:
resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.2.0.tgz#6533d524d30621fc1283b9692146f3f13a93d1ba"
integrity sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==
sdp-transform@^2.14.1:
version "2.14.2"
resolved "https://registry.yarnpkg.com/sdp-transform/-/sdp-transform-2.14.2.tgz#d2cee6a1f7abe44e6332ac6cbb94e8600f32d813"
integrity sha512-icY6jVao7MfKCieyo1AyxFYm1baiM+fA00qW/KrNNVlkxHAd34riEKuEkUe4bBb3gJwLJZM+xT60Yj1QL8rHiA==
sdp@^3.1.0, sdp@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/sdp/-/sdp-3.2.0.tgz#8961420552b36663b4d13ddba6f478d1461896a5"
@@ -2295,8 +2259,16 @@ source-map-js@^1.0.2:
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
name string-width-cjs
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^4.1.0:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -2314,7 +2286,14 @@ string-width@^5.0.1, string-width@^5.1.2:
emoji-regex "^9.2.2"
strip-ansi "^7.0.1"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -2432,26 +2411,21 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
ts-debounce@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/ts-debounce/-/ts-debounce-4.0.0.tgz#33440ef64fab53793c3d546a8ca6ae539ec15841"
integrity sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==
ts-interface-checker@^0.1.9:
version "0.1.13"
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
tslib@2.6.2, tslib@^2.1.0, tslib@^2.4.1:
version "2.6.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
tslib@^1.8.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.4.1:
version "2.6.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
@@ -2471,12 +2445,10 @@ type-fest@^0.20.2:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
typed-emitter@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/typed-emitter/-/typed-emitter-2.1.0.tgz#ca78e3d8ef1476f228f548d62e04e3d4d3fd77fb"
integrity sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==
optionalDependencies:
rxjs "^7.5.2"
typescript@^3.9:
version "3.9.10"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8"
integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==
typescript@^5.0.2:
version "5.4.2"
@@ -2493,6 +2465,11 @@ undici-types@~5.26.4:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
undici-types@~6.11.1:
version "6.11.1"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.11.1.tgz#432ea6e8efd54a48569705a699e62d8f4981b197"
integrity sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ==
update-browserslist-db@^1.0.13:
version "1.0.13"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4"
@@ -2525,13 +2502,6 @@ use-sync-external-store@1.2.0:
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
usehooks-ts@2.14.0:
version "2.14.0"
resolved "https://registry.yarnpkg.com/usehooks-ts/-/usehooks-ts-2.14.0.tgz#8c38fdb9133a7278f88ff8c762695ccaec4abfb8"
integrity sha512-jnhrjTRJoJS7cFxz63tRYc5mzTKf/h+Ii8P0PDHymT9qDe4ZA2/gzDRmDR4WGausg5X8wMIdghwi3BBCN9JKow==
dependencies:
lodash.debounce "^4.0.8"
usehooks-ts@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/usehooks-ts/-/usehooks-ts-3.0.1.tgz#fc7b7ecf4ba893f75de1023e0c89068360f165ba"
@@ -2572,13 +2542,6 @@ warning@^4.0.0, warning@^4.0.2:
dependencies:
loose-envify "^1.0.0"
webrtc-adapter@^8.1.1:
version "8.2.3"
resolved "https://registry.yarnpkg.com/webrtc-adapter/-/webrtc-adapter-8.2.3.tgz#85e5e52ea68e808be8d6db85e338aa5c95e80022"
integrity sha512-gnmRz++suzmvxtp3ehQts6s2JtAGPuDPjA1F3a9ckNpG1kYdYuHWYpazoAnL9FS5/B21tKlhkorbdCXat0+4xQ==
dependencies:
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"
@@ -2616,6 +2579,11 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
ws@^8.17.1:
version "8.18.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
ws@~8.11.0:
version "8.11.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"