/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable react-hooks/rules-of-hooks */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable react-hooks/exhaustive-deps */ import "./StreamPage2.css"; import { PixelStreamingWrapper } from "../components/PixelStreamingWrapper"; import { useParams, useSearchParams } from "react-router-dom"; import { FormEvent, useEffect, useRef } from "react"; import { Transition } from "react-transition-group"; import Button from "../components/ui/Button"; // import CloseIcon from "../components/icons/CloseIcon"; import HandOffIcon from "../components/icons/HandOffIcon"; // import MicroOffIcon from "../components/icons/MicroOffIcon"; import PersonsIcon from "../components/icons/PersonsIcon"; import MicroOnIcon from "../components/icons/MicroOnIcon"; // import MoreIcon from "../components/icons/MoreIcon"; import FullscreenIcon from "../components/icons/FullscreenIcon"; import WindowIcon from "../components/icons/WindowIcon"; import ChatIcon from "../components/icons/ChatIcon"; import ShareIcon from "../components/icons/ShareIcon"; // import GearIcon from "../components/icons/GearIcon"; import { useFullscreen } from "ahooks"; import api from "../utils/api"; import { Socket, io } from "socket.io-client"; import { v4 as uuidv4 } from "uuid"; import HandOnIcon from "../components/icons/HandOnIcon"; import User from "../components/User"; import Tooltip from "../components/Tooltip"; import { Bounce, ToastContainer, toast } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; import InfoBlueIcon from "../components/icons/InfoBlueIcon"; import CloseIcon from "../components/icons/CloseIcon"; import SendChatIcon from "../components/icons/SendChatIcon"; import UserIcon from "../components/icons/UserIcon"; import { isIOS, isMobile, useMobileOrientation } from "react-device-detect"; import LinkIcon from "../components/icons/LinkIcon"; import removeSpaces from "../utils/removeSpaces"; import MobileIcon from "../components/icons/MobileIcon"; import DesktopIcon from "../components/icons/DesktopIcon"; import { format } from "date-fns"; import IUser from "../types/IUser"; import IMessage from "../types/IMessage"; import InfoIcon from "../components/icons/InfoIcon"; import Rotate64Icon from "../components/icons/Rotate64Icon"; import { useClipboard } from "use-clipboard-copy"; import QRCode from "react-qr-code"; import Star12Icon from "../components/icons/Star12Icon"; import { Trans, useTranslation } from "react-i18next"; 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"; import useState from "react-usestateref"; const renderer = ({ minutes, seconds }: any) => { return ( <> {String(minutes).padStart(2, "0")}:{String(seconds).padStart(2, "0")} ); }; interface IRemoteStream { peerId: string; mediaStream: MediaStream; } function StreamPage2() { const { t, i18n } = useTranslation(); const { isPortrait } = useMobileOrientation(); const params = useParams(); const [searchParams] = useSearchParams(); const [socket, setSocket] = useState(); const [wsUrl, setWsUrl] = useState(); const [isEnded, setIsEnded] = useState(false); const { name, setName } = useStreamStore(); const userId = uuidv4(); const [step, setStep] = useState(1); const nameRef = useRef(null!); const [users, setUsers] = useState([]); const fullscreenRef = useRef(null); const [isFullscreen, { toggleFullscreen }] = useFullscreen(fullscreenRef); const [isShowChat, setIsShowChat] = useState(false); const [isShowUsers, setIsShowUsers] = useState(false); const [isShowInviteModal, setIsShowInviteModal] = useState(false); const [isVideoInitialized, setIsVideoInitialized] = useState(false); const [messageText, setMessageText] = useState(""); const [messages] = useState([]); const messagesRef = useRef(null); const messageTextRef = useRef(null); const parentElementRef = useRef(null); const clipboard = useClipboard(); const link = window.location.origin + window.location.pathname; const [anyNewMessages, setAnyNewMessages] = useState(false); const [endAt, setEndAt] = useState(); const localVideoRef = useRef(null); const [localStream, setLocalStream] = useState( new MediaStream() ); const [remoteStreams, setRemoteStreams, remoteStreamsRef] = useState< IRemoteStream[] >([]); const [peerId, setPeerId] = useState(""); const [peerInstance, setPeerInstance] = useState(); const [permission, setPermission] = useState(); const isSpeaking = useIsAudioActive({ source: localStream.getTracks().length ? localStream : null, }); const isCallInit = useRef(false); const [isVideoEnabled, setIsVideoEnabled] = useState(false); const [isAudioEnabled, setIsAudioEnabled] = useState(false); async function getLang() { const { countryCode, error }: { countryCode: string; error: string } = await api.get("getCountryCode").json(); if (!error && countryCode !== "RU") { i18n.changeLanguage("en"); } } function handleClickClipboard() { clipboard.copy(); toast.info("Ссылка скопирована в буфер обмена", { icon: , position: "top-center", autoClose: 3000, hideProgressBar: true, closeOnClick: true, pauseOnHover: true, draggable: true, progress: undefined, theme: "light", transition: Bounce, }); } async function sendMessage(e: FormEvent) { e.preventDefault(); const text = removeSpaces(messageText); if (text) { const name = users.find((user) => user.id === userId)?.name; socket?.emit("message", { name, text }); } setMessageText(""); messageTextRef.current?.focus(); } 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; } } async function getWsUrl() { const activeSession = await getActiveSession(); if (!activeSession || activeSession.status === "error") { setIsEnded(true); return; } setIsEnded(false); setWsUrl( `wss://${activeSession.location}.sess.stream.graff.tech/${activeSession.name}/${activeSession.cirrusPort}/` ); checkSessionStatus(); } function handleSetName(e: FormEvent) { e.preventDefault(); if (!name) { nameRef.current.focus(); return; } setStep(2); } function setNameGuest() { i18n.language === "ru" ? setName("Гость") : setName("Guest"); setStep(2); } function toggleVideo() { localStream.getVideoTracks().forEach((track) => { track.enabled = !track.enabled; if (!permission) return; setIsVideoEnabled(track.enabled); }); } function toggleAudio() { localStream.getAudioTracks().forEach((track) => { track.enabled = !track.enabled; if (!permission) return; setIsAudioEnabled(track.enabled); }); } function transferControl(userId: string) { socket?.emit("transferControl", userId); } function requestControl(userId: string) { socket?.emit("requestControl", userId); toast.info(`Вы запросили разрешение на управление`, { icon: , position: "top-center", autoClose: 3000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: true, progress: undefined, theme: "light", transition: Bounce, }); } function kickUser(userId: string) { socket?.emit("kickUser", userId); } async function startCall(remotePeerId: string) { if (!peerInstance) return; console.log("startCall", remotePeerId); const options = { constraints: { offerToReceiveVideo: true, offerToReceiveAudio: true, }, }; const call = peerInstance.call(remotePeerId, localStream, options as any); let accept = true; call.on("stream", (remoteStream) => { if (!accept) return; console.log("setRemoteStreams", remoteStream); setRemoteStreams((prev) => [ ...prev, { peerId: remotePeerId, mediaStream: remoteStream }, ]); accept = false; }); } async function getUserMedia() { try { const mediaStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true, }); if (!localVideoRef.current) return; localVideoRef.current.srcObject = mediaStream; localVideoRef.current.onloadedmetadata = () => { localVideoRef.current?.play(); }; setLocalStream(mediaStream); setPermission(true); console.log("setLocalStream mediaStream", mediaStream); } catch (error) { setPermission(false); console.log("ERROR: ", error); } } function initPeer() { const peer = new Peer(); peer.on("open", (id) => { setPeerId(id); }); peer.on("call", (call) => { call.answer(localStream || undefined); let accept = true; call.on("stream", (remoteStream) => { if (!accept) return; setRemoteStreams((prev) => [ ...prev, { peerId: call.peer, mediaStream: remoteStream }, ]); accept = false; }); }); setPeerInstance(peer); } function initSocket() { const socket = io(import.meta.env.VITE_SOCKET_URL, { transports: ["websocket"], auth: { roomId: params.id, user: { id: userId, name: name, device: isMobile ? "mobile" : "desktop", isAdmin: searchParams.has("admin", true), peerId, }, }, }); socket.on("update", async (users: IUser[]) => { console.log("isCallInit", isCallInit.current); if (!isCallInit.current) { for (const user of users) { if (userId === user.id) continue; await startCall(user.peerId); } isCallInit.current = true; } setUsers(users); }); setSocket(socket); setStep(3); } useEffect(() => { console.log("users", users); }, [users]); function updateRemoteStreams() { setTimeout(() => { console.log("users", users); const newRemoteStreams = remoteStreamsRef.current.filter((remoteStream) => users.some((user) => user.peerId === remoteStream.peerId) ); setRemoteStreams(newRemoteStreams); }, 500); } useEffect(() => { getLang(); getWsUrl(); }, []); useEffect(() => { document.title = t("title"); }, [i18n.language]); useEffect(() => { if (!name) return; setName(name.trim()); }, [name]); useEffect(() => { if (!isShowChat || messagesRef.current?.scrollTop === undefined) return; messagesRef.current.scrollTop = messagesRef.current.scrollHeight; }, [messages, isShowChat]); useEffect(() => { if (!messages.length || isShowChat) return; setAnyNewMessages(true); }, [messages]); useEffect(() => { if (isShowChat) { setAnyNewMessages(false); } }, [isShowChat]); // useEffect(() => { // if (!peerId) return; // const socket = io(import.meta.env.VITE_SOCKET_URL, { // auth: { // roomId: params.id, // user: { // id: userId, // name: name, // device: isMobile ? "mobile" : "desktop", // isAdmin: searchParams.has("admin", true), // peerId, // }, // }, // }); // // TODO // socket.on("message", (message: IMessage) => { // setMessages((prev) => [...prev, message]); // }); // socket.on("requestControl", (user: IUser) => { // if (!usersRef.current.find((user) => user.id === userId)?.isAdmin) return; // toast.info(`${user.name} запрашивает разрешение на управление`, { // icon: , // position: "top-center", // autoClose: 5000, // hideProgressBar: false, // closeOnClick: true, // pauseOnHover: true, // draggable: true, // progress: undefined, // theme: "light", // transition: Bounce, // }); // }); // socket.on("transferControl", (user: IUser) => { // if (user.id !== userId) return; // toast.info(`Вы получили разрешение на управление`, { // icon: , // position: "top-center", // autoClose: 3000, // hideProgressBar: false, // closeOnClick: true, // pauseOnHover: true, // draggable: true, // progress: undefined, // theme: "light", // transition: Bounce, // }); // }); // socket.on("kickUser", (socketUserId: string) => { // if (socketUserId === userId) { // socket.disconnect(); // window.close(); // window.location.reload(); // } // }); // setSocket(socket); // }, [peerId]); useEffect(() => { if (!isMobile) return; if (isShowUsers) { setIsShowChat(false); } }, [isShowUsers]); useEffect(() => { if (!isMobile) return; if (isShowChat) { setIsShowUsers(false); } }, [isShowChat]); useEffect(() => { if (step === 3) { setInterval(async () => { await checkSessionStatus(); }, 1000); } }, [step]); useEffect(() => { if (permission === undefined) return; initPeer(); }, [permission]); useEffect(() => { if (!peerId) return; initSocket(); }, [peerId]); useEffect(() => { if (!users.length) return; updateRemoteStreams(); }, [users.length]); return ( <> {isEnded === false ? (
{name && name[0].toUpperCase()} {users.find((user) => user.id === userId) ?.isControlAllowed && (
)} {users.find((user) => user.id === userId)?.isAdmin && (
)}

{name}

{users.find((user) => user.id === userId)?.isControlAllowed ? ( ) : ( )}
{users .filter((user) => user.id !== userId) .map( (user, index) => index < 3 && (
{user.name[0].toUpperCase()} {user.isControlAllowed && (
)} {user.isAdmin && (
)}

{user.name}

{user.device === "mobile" ? ( ) : ( )}
{users.find((user) => user.id === userId) ?.isAdmin && ( transferControl(userId) } onKickUser={(userId) => { kickUser(userId); }} /> )}
) )} {users.length > 4 && (
{!isIOS && (

{wsUrl && ( setIsVideoInitialized(true)} /> )} {!users.find((user) => user.id === userId)?.isControlAllowed && (
toast.error( `Запросите доступ на управление у администратора!`, { icon: , position: "top-center", autoClose: 5000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: true, progress: undefined, theme: "light", transition: Bounce, } ) } onMouseDown={() => toast.error( `Запросите доступ на управление у администратора!`, { icon: , position: "top-center", autoClose: 5000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: true, progress: undefined, theme: "light", transition: Bounce, } ) } >
)}
{(isShowUsers || isShowChat) && (
{isShowUsers && (

Участники

{users.map((user) => (
{user.name[0].toUpperCase()} {user.isControlAllowed && (
)} {user.isAdmin && (
)}

{user.name}

{user.device === "mobile" ? ( ) : ( )}
{users.find((user) => user.id === userId) ?.isAdmin && ( transferControl(userId) } onKickUser={(userId) => { kickUser(userId); }} /> )}
))}
)} {isShowChat && (

Чат

{messages.map((message, index) => (
user.id === userId)?.name === message.name ? "bg-[#C4DDF5]" : "bg-[#F0F1F2]" }`} > {users.find((user) => user.id === userId)?.name !== message.name && (

{message.name}

)}

{message.text}

{format(new Date(), "HH:mm")}

))}
setMessageText(e.target.value)} />
)}
)}
{isShowInviteModal && (

Пригласить

Отсканируйте QR-код,
чтобы присоедениться
к демонстрации

e.preventDefault()} className="flex gap-2" >
{/*

Участники

{users.map((user) => (

{user.name[0].toUpperCase()}

{user.name}

))}
*/}
)} {(state) => (
{(state) => (

Здравствуйте!

Представьтесь, пожалуйста

Так мы будем знать, как к вам обращаться

setName(e.target.value)} />
)}
{(state) => (

Хотите принять участие в обсуждении?

Разрешите использование камеры и микрофона

Выключить камеру и микрофон можно в любой момент

{/* */}
)}
{(state) => (

Пожалуйста, подождите

Подключение

)}
)}
{step === 3 && isMobile && isPortrait && (

Поверните устройство

)} ) : (

Данная демонстрация была завершена

)} ); } export default StreamPage2;