upd
This commit is contained in:
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.3": "^1.0.1",
|
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.3": "^1.0.4",
|
||||||
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.5": "^0.0.12",
|
"@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",
|
||||||
|
|||||||
@@ -80,90 +80,6 @@ function ChatNew({ isShow, socket, userId, name, onClose }: ChatNewProps) {
|
|||||||
}, [isShow]);
|
}, [isShow]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// <div className={`max-h-[calc(100vh-48px)]`}>
|
|
||||||
// <div
|
|
||||||
// className={`overflow-y-scroll relative h-full bg-white flex flex-col ${
|
|
||||||
// isShow ? "w-[296px]" : "w-0 hidden"
|
|
||||||
// }`}
|
|
||||||
// >
|
|
||||||
// <div
|
|
||||||
// className={`fixed z-10 top-0 bg-white w-full flex items-center justify-between p-2 pl-4`}
|
|
||||||
// >
|
|
||||||
// <p className="font-semibold">Чат</p>
|
|
||||||
// <div className="flex">
|
|
||||||
// <Button
|
|
||||||
// variant="tertiary"
|
|
||||||
// icon={<CloseIcon />}
|
|
||||||
// onlyIcon
|
|
||||||
// onClick={onClose}
|
|
||||||
// />
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// <div
|
|
||||||
// ref={messagesRef}
|
|
||||||
// className="flex flex-col gap-2 p-4 pt-12 pb-[72px]"
|
|
||||||
// >
|
|
||||||
// {messages.map((message) => (
|
|
||||||
// <div
|
|
||||||
// className={`flex gap-1 items-end ${
|
|
||||||
// message.user.id === userId ? "self-end" : ""
|
|
||||||
// }`}
|
|
||||||
// >
|
|
||||||
// {message.user.id !== userId && (
|
|
||||||
// <div className="">
|
|
||||||
// <div className="h-6 w-6 flex items-center justify-center bg-[#E6ECF2] font-semibold text-[10px] rounded-full">
|
|
||||||
// {message.user.name[0].toUpperCase()}
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// )}
|
|
||||||
// <div className="relative">
|
|
||||||
// <div
|
|
||||||
// className={`p-2 rounded-tl-[4px] rounded-r-lg flex flex-col gap-1 ${
|
|
||||||
// message.user.id !== userId ? "bg-[#F0F1F2]" : "bg-[#C4DDF5]"
|
|
||||||
// }`}
|
|
||||||
// >
|
|
||||||
// {message.user.id !== userId && (
|
|
||||||
// <p className="text-sm text-[#49A1F5] font-semibold">
|
|
||||||
// {message.user.name}
|
|
||||||
// </p>
|
|
||||||
// )}
|
|
||||||
// <p
|
|
||||||
// className="text-sm break-words"
|
|
||||||
// style={{ wordBreak: "break-word" }}
|
|
||||||
// >
|
|
||||||
// {message.text}
|
|
||||||
// </p>
|
|
||||||
// <p className="text-xs self-end text-[#767676]">
|
|
||||||
// {message.time}
|
|
||||||
// </p>
|
|
||||||
// </div>
|
|
||||||
// {message.user.id !== userId && (
|
|
||||||
// <div className="absolute bottom-0 -left-[7px]">
|
|
||||||
// <SubtracktIcon />
|
|
||||||
// </div>
|
|
||||||
// )}
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// ))}
|
|
||||||
// </div>
|
|
||||||
// <div className="fixed z-10 bottom-0 p-4 bg-white w-full">
|
|
||||||
// <form onSubmit={sendMessage} className="flex gap-3 border-t pt-3">
|
|
||||||
// <input
|
|
||||||
// ref={textRef}
|
|
||||||
// type="text"
|
|
||||||
// placeholder="Напишите сообщение..."
|
|
||||||
// className="outline-none w-full bg-white text-sm"
|
|
||||||
// value={message}
|
|
||||||
// onChange={(e) => setMessage(e.target.value)}
|
|
||||||
// />
|
|
||||||
// <button type="submit" className="text-[#49A1F5]">
|
|
||||||
// <SendChatIcon />
|
|
||||||
// </button>
|
|
||||||
// </form>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`h-full flex flex-col ${
|
className={`h-full flex flex-col ${
|
||||||
isShow ? "w-[296px] p-4" : "w-0 overflow-hidden"
|
isShow ? "w-[296px] p-4" : "w-0 overflow-hidden"
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ function Video({ mediaStream, muted, user }: Props) {
|
|||||||
const remoteVideoRef = useRef<HTMLVideoElement>(null);
|
const remoteVideoRef = useRef<HTMLVideoElement>(null);
|
||||||
const isSpeaking = useIsAudioActive({ source: mediaStream });
|
const isSpeaking = useIsAudioActive({ source: mediaStream });
|
||||||
const [_muted, setMuted] = useState(muted);
|
const [_muted, setMuted] = useState(muted);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
function toggleSound() {
|
function toggleSound() {
|
||||||
if (!remoteVideoRef.current) return;
|
if (!remoteVideoRef.current) return;
|
||||||
@@ -30,6 +31,10 @@ function Video({ mediaStream, muted, user }: Props) {
|
|||||||
remoteVideoRef.current.onloadedmetadata = () => {
|
remoteVideoRef.current.onloadedmetadata = () => {
|
||||||
remoteVideoRef.current?.play();
|
remoteVideoRef.current?.play();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
remoteVideoRef.current.onplay = () => {
|
||||||
|
setIsLoading(false);
|
||||||
|
};
|
||||||
}, [mediaStream]);
|
}, [mediaStream]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -55,6 +60,15 @@ function Video({ mediaStream, muted, user }: Props) {
|
|||||||
{_muted ? <SoundOffIcon /> : <SoundOnIcon />}
|
{_muted ? <SoundOffIcon /> : <SoundOnIcon />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
{isLoading && (
|
||||||
|
<div className="absolute top-0 left-0 w-full h-full flex items-center justify-center">
|
||||||
|
<img
|
||||||
|
src="/icons/Loader.png"
|
||||||
|
alt=""
|
||||||
|
className="animate-spin w-6 h-6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import { Trans } from "react-i18next";
|
||||||
|
import QRCode from "react-qr-code";
|
||||||
|
import CloseIcon from "../../icons/CloseIcon";
|
||||||
|
import LinkIcon from "../../icons/LinkIcon";
|
||||||
|
import Button from "../../ui/Button";
|
||||||
|
import useModalStore from "../../../stores/useModalStore";
|
||||||
|
import { useClipboard } from "use-clipboard-copy";
|
||||||
|
import { toast, Bounce, ToastContainer } from "react-toastify";
|
||||||
|
import InfoIcon from "../../icons/InfoBlueIcon";
|
||||||
|
|
||||||
|
function InviteModal() {
|
||||||
|
const { setModal } = useModalStore();
|
||||||
|
const clipboard = useClipboard();
|
||||||
|
const link = window.location.origin + window.location.pathname;
|
||||||
|
|
||||||
|
function handleClickClipboard() {
|
||||||
|
clipboard.copy();
|
||||||
|
|
||||||
|
toast.info("Ссылка скопирована в буфер обмена", {
|
||||||
|
icon: <InfoIcon className="text-blue-500" />,
|
||||||
|
position: "top-center",
|
||||||
|
autoClose: 3000,
|
||||||
|
hideProgressBar: true,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
transition: Bounce,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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="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">
|
||||||
|
<Trans i18nKey={"invite"}>Пригласить</Trans>
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
variant="tertiary"
|
||||||
|
icon={<CloseIcon />}
|
||||||
|
onlyIcon
|
||||||
|
onClick={() => setModal(null)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="py-4 px-6 flex-1 flex flex-col gap-8">
|
||||||
|
<div className="flex items-center lg:justify-between justify-center gap-8">
|
||||||
|
<QRCode
|
||||||
|
size={128}
|
||||||
|
value={link}
|
||||||
|
className="rounded-lg p-3 shadow-lg"
|
||||||
|
/>
|
||||||
|
<p className="font-semibold text-right">
|
||||||
|
<Trans i18nKey={"scanQRCode"}>
|
||||||
|
Отсканируйте QR-код,
|
||||||
|
<br />
|
||||||
|
чтобы присоедениться
|
||||||
|
<br />к демонстрации
|
||||||
|
</Trans>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="">
|
||||||
|
<Button
|
||||||
|
icon={<LinkIcon />}
|
||||||
|
fullWidth
|
||||||
|
large
|
||||||
|
onClick={handleClickClipboard}
|
||||||
|
>
|
||||||
|
<Trans i18nKey={"copyLinkToConnect"}>
|
||||||
|
Скопировать ссылку для подключения
|
||||||
|
</Trans>
|
||||||
|
</Button>
|
||||||
|
<input ref={clipboard.target} type="hidden" value={link} />
|
||||||
|
</div>
|
||||||
|
{/* <div>
|
||||||
|
<form onSubmit={(e) => e.preventDefault()} className="flex gap-2">
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
placeholder="Email"
|
||||||
|
className="text-sm bg-transparent border border-[#DAE0E5] rounded-lg px-2 pb-0.5 outline-none h-10 w-full"
|
||||||
|
/>
|
||||||
|
<Button type="submit" large className="" disabled>
|
||||||
|
<Trans i18nKey={"invite"}>Пригласить</Trans>
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ToastContainer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InviteModal;
|
||||||
+196
-120
@@ -24,6 +24,13 @@ import MicroOnIcon from "../components/icons/MicroOnIcon";
|
|||||||
import MicroOffIcon from "../components/icons/MicroOffIcon";
|
import MicroOffIcon from "../components/icons/MicroOffIcon";
|
||||||
import CameraOnIcon from "../components/icons/CameraOnIcon";
|
import CameraOnIcon from "../components/icons/CameraOnIcon";
|
||||||
import CameraOffIcon from "../components/icons/CameraOffIcon";
|
import CameraOffIcon from "../components/icons/CameraOffIcon";
|
||||||
|
import { Trans } from "react-i18next";
|
||||||
|
import { isIOS } from "react-device-detect";
|
||||||
|
import WindowIcon from "../components/icons/WindowIcon";
|
||||||
|
import FullscreenIcon from "../components/icons/FullscreenIcon";
|
||||||
|
import ShareIcon from "../components/icons/ShareIcon";
|
||||||
|
import { useFullscreen } from "ahooks";
|
||||||
|
import InviteModal from "../components/modals/stream/InviteModal";
|
||||||
|
|
||||||
// import MoreIcon from "../components/icons/MoreIcon";
|
// import MoreIcon from "../components/icons/MoreIcon";
|
||||||
|
|
||||||
@@ -55,6 +62,10 @@ function StreamPage3() {
|
|||||||
const { name } = useStreamStore();
|
const { name } = useStreamStore();
|
||||||
const [isMicEnabled, setIsMicEnabled] = useState(true);
|
const [isMicEnabled, setIsMicEnabled] = useState(true);
|
||||||
const [isCameraEnabled, setIsCameraEnabled] = useState(true);
|
const [isCameraEnabled, setIsCameraEnabled] = useState(true);
|
||||||
|
const [isEnded, setIsEnded] = useState<boolean>();
|
||||||
|
const [, setEndAt] = useState<Date>();
|
||||||
|
const fullscreenRef = useRef(null);
|
||||||
|
const [isFullscreen, { toggleFullscreen }] = useFullscreen(fullscreenRef);
|
||||||
|
|
||||||
async function startCall(remotePeerId: string) {
|
async function startCall(remotePeerId: string) {
|
||||||
if (!peerInstance) return;
|
if (!peerInstance) return;
|
||||||
@@ -152,6 +163,10 @@ function StreamPage3() {
|
|||||||
setMe(users.find((user) => user.id === userId));
|
setMe(users.find((user) => user.id === userId));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("request-control", (userId) => {
|
||||||
|
console.log("request-control", userId);
|
||||||
|
});
|
||||||
|
|
||||||
socket.on("connect", () => {
|
socket.on("connect", () => {
|
||||||
setSocket(socket);
|
setSocket(socket);
|
||||||
});
|
});
|
||||||
@@ -192,37 +207,59 @@ function StreamPage3() {
|
|||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getActiveSession() {
|
|
||||||
const activeSession: any = await api
|
|
||||||
.get(`activeSessions/${params.id}`)
|
|
||||||
.json();
|
|
||||||
|
|
||||||
// if (activeSession?.endAt) {
|
|
||||||
// setEndAt(activeSession.endAt);
|
|
||||||
// }
|
|
||||||
|
|
||||||
return activeSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getWSUrl() {
|
async function getWSUrl() {
|
||||||
const activeSession = await getActiveSession();
|
const activeSession = await getActiveSession();
|
||||||
|
|
||||||
if (!activeSession || activeSession.status === "error") {
|
if (!activeSession || activeSession.status === "error") {
|
||||||
|
setIsEnded(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsEnded(false);
|
||||||
setWSUrl(
|
setWSUrl(
|
||||||
`wss://${activeSession.location}.sess.stream.graff.tech/${activeSession.name}/${activeSession.cirrusPort}/`
|
`wss://${activeSession.location}.sess.stream.graff.tech/${activeSession.name}/${activeSession.cirrusPort}/`
|
||||||
);
|
);
|
||||||
|
setModal(<SetNameModal onAction={getUserMedia} />);
|
||||||
|
|
||||||
|
checkSessionStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function transferControl(userId: string) {
|
function transferControl(userId: string) {
|
||||||
socket?.emit("transfer-control", userId);
|
socket?.emit("transfer-control", userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestControl(userId: string) {
|
||||||
|
console.log("requestControl func", userId);
|
||||||
|
socket?.emit("request-control", userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getActiveSession() {
|
||||||
|
const activeSession: any = await api
|
||||||
|
.get(`activeSessions/${params.id}`)
|
||||||
|
.json();
|
||||||
|
|
||||||
|
if (activeSession?.endAt) {
|
||||||
|
setEndAt(activeSession.endAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return activeSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkSessionStatus() {
|
||||||
|
const activeSession = await getActiveSession();
|
||||||
|
|
||||||
|
if (!activeSession || activeSession.status === "error") {
|
||||||
|
setIsEnded(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
await checkSessionStatus();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getWSUrl();
|
getWSUrl();
|
||||||
setModal(<SetNameModal onAction={getUserMedia} />);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -244,123 +281,162 @@ function StreamPage3() {
|
|||||||
}, [users.length]);
|
}, [users.length]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen flex flex-col bg-[#111C26]">
|
<div ref={fullscreenRef} className="h-[100dvh] flex flex-col bg-[#111C26]">
|
||||||
<div className="flex items-center bg-white h-12">
|
{isEnded === false ? (
|
||||||
<div className="px-6">
|
<>
|
||||||
<img src="/images/logo24.svg" alt="" />
|
<div className="flex items-center bg-white h-12 px-6">
|
||||||
</div>
|
<div className="pr-6">
|
||||||
<div className="flex items-center gap-4">
|
<img src="/images/logo24.svg" alt="" />
|
||||||
<div 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>
|
|
||||||
{me?.isControlAllowed && (
|
|
||||||
<div className="absolute bottom-0 right-0 bg-[#49A1F5] w-2 h-2 rounded-full border border-white"></div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs">{name}</p>
|
<div className="flex items-center gap-4">
|
||||||
</div>
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="relative w-6 h-6 bg-[#E6ECF2] rounded-full flex items-center justify-center">
|
||||||
<Button
|
<p className="text-xs font-semibold">
|
||||||
variant="secondary"
|
{name[0]?.toUpperCase()}
|
||||||
icon={me?.isControlAllowed ? <HandOnIcon /> : <HandOffIcon />}
|
</p>
|
||||||
onlyIcon
|
{me?.isControlAllowed && (
|
||||||
onClick={() => me?.isAdmin && transferControl(me.id)}
|
<div className="absolute bottom-0 right-0 bg-[#49A1F5] w-2 h-2 rounded-full border border-white"></div>
|
||||||
/>
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="text-xs">{name}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
icon={me?.isControlAllowed ? <HandOnIcon /> : <HandOffIcon />}
|
||||||
|
onlyIcon
|
||||||
|
onClick={() =>
|
||||||
|
me!.isAdmin
|
||||||
|
? transferControl(me!.id)
|
||||||
|
: requestControl(me!.id)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
icon={isMicEnabled ? <MicroOnIcon /> : <MicroOffIcon />}
|
||||||
|
onlyIcon
|
||||||
|
onClick={toggleMic}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
icon={isCameraEnabled ? <CameraOnIcon /> : <CameraOffIcon />}
|
||||||
|
onlyIcon
|
||||||
|
onClick={toggleCamera}
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
|
||||||
<Button
|
{me?.isAdmin && me?.isControlAllowed && (
|
||||||
variant="secondary"
|
<div className="relative">
|
||||||
icon={isMicEnabled ? <MicroOnIcon /> : <MicroOffIcon />}
|
{/* <Button
|
||||||
onlyIcon
|
|
||||||
onClick={toggleMic}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
icon={isCameraEnabled ? <CameraOnIcon /> : <CameraOffIcon />}
|
|
||||||
onlyIcon
|
|
||||||
onClick={toggleCamera}
|
|
||||||
/>
|
|
||||||
</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>
|
|
||||||
|
|
||||||
{me?.isAdmin && me?.isControlAllowed && (
|
|
||||||
<div className="relative">
|
|
||||||
{/* <Button
|
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
icon={<MoreIcon />}
|
icon={<MoreIcon />}
|
||||||
onlyIcon
|
onlyIcon
|
||||||
/> */}
|
/> */}
|
||||||
{/* <div className="absolute"> */}
|
{/* <div className="absolute"> */}
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
icon={<HandOnIcon />}
|
icon={<HandOnIcon />}
|
||||||
onlyIcon
|
onlyIcon
|
||||||
onClick={() => transferControl(user.id)}
|
onClick={() => transferControl(user.id)}
|
||||||
/>
|
/>
|
||||||
{/* </div> */}
|
{/* </div> */}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
);
|
||||||
</div>
|
}
|
||||||
);
|
})}
|
||||||
}
|
</div>
|
||||||
})}
|
<div className="flex gap-2 ml-auto">
|
||||||
</div>
|
<Button
|
||||||
</div>
|
variant="secondary"
|
||||||
<div className="relative flex-1">
|
icon={<ShareIcon />}
|
||||||
{WSUrl && (
|
onlyIcon
|
||||||
<PixelStreamingWrapper2
|
onClick={() => setModal(<InviteModal />)}
|
||||||
initialSettings={{
|
/>
|
||||||
AutoPlayVideo: true,
|
{!isIOS && (
|
||||||
AutoConnect: true,
|
<Button
|
||||||
ss: WSUrl,
|
variant="secondary"
|
||||||
StartVideoMuted: true,
|
icon={isFullscreen ? <WindowIcon /> : <FullscreenIcon />}
|
||||||
HoveringMouse: true,
|
onlyIcon
|
||||||
WaitForStreamer: true,
|
onClick={toggleFullscreen}
|
||||||
}}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="absolute top-2 left-2 space-y-2">
|
|
||||||
<div className="relative">
|
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{remoteStreams.map(({ peerId, mediaStream }) => (
|
<div className="relative flex-1 flex">
|
||||||
<Video
|
{WSUrl && (
|
||||||
key={peerId}
|
<PixelStreamingWrapper2
|
||||||
mediaStream={mediaStream}
|
initialSettings={{
|
||||||
muted={!permission}
|
AutoPlayVideo: true,
|
||||||
user={users.find((user) => user.peerId === peerId)}
|
AutoConnect: true,
|
||||||
/>
|
ss: WSUrl,
|
||||||
))}
|
StartVideoMuted: true,
|
||||||
</div>
|
HoveringMouse: true,
|
||||||
</div>
|
WaitForStreamer: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<ModalContainer2 />
|
{!users.find((user) => user.id === userId)?.isControlAllowed && (
|
||||||
|
<div
|
||||||
|
className="absolute top-0 left-0 w-full h-full"
|
||||||
|
onClick={() => alert("")}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="absolute top-2 left-2 space-y-2">
|
||||||
|
<div className="relative">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{remoteStreams.map(({ peerId, mediaStream }) => (
|
||||||
|
<Video
|
||||||
|
key={peerId}
|
||||||
|
mediaStream={mediaStream}
|
||||||
|
muted={!permission}
|
||||||
|
user={users.find((user) => user.peerId === peerId)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ModalContainer2 />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="flex-1 flex items-center justify-center p-8">
|
||||||
|
<p className="text-2xl text-white font-gilroy text-center">
|
||||||
|
<Trans i18nKey={"demonstrationCompleted"}>
|
||||||
|
Данная демонстрация была завершена
|
||||||
|
</Trans>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,10 +36,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.1.tgz#e93c13942592cf5ef01aa8297444dc192beee52f"
|
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.1.tgz#e93c13942592cf5ef01aa8297444dc192beee52f"
|
||||||
integrity sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg==
|
integrity sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg==
|
||||||
|
|
||||||
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.3@^1.0.1":
|
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.3@^1.0.4":
|
||||||
version "1.0.1"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@epicgames-ps/lib-pixelstreamingfrontend-ue5.3/-/lib-pixelstreamingfrontend-ue5.3-1.0.1.tgz#ba7d0fb42ede74109fcbb2510d7f6a4442bed7a0"
|
resolved "https://registry.yarnpkg.com/@epicgames-ps/lib-pixelstreamingfrontend-ue5.3/-/lib-pixelstreamingfrontend-ue5.3-1.0.4.tgz#71533a4f940627702d26896eb3380d08c6d05523"
|
||||||
integrity sha512-DLeMbwi/szf4/rQAPXFl1YH5lT5kHJ2GcnxYNPvYVWZ9xgX5hjieREXyi8DaxCAPF81e+ev57TjZKJy7R3tvpw==
|
integrity sha512-gqt2lFGLys3YOvloK9X/gpk+ZPgRlLi+uB9kWWWMJd+Gih0jgRwc6GJ0b+LuIVmZau4qNUbTmfOklKpti0G07g==
|
||||||
dependencies:
|
dependencies:
|
||||||
sdp "^3.1.0"
|
sdp "^3.1.0"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user