This commit is contained in:
2024-07-05 20:08:41 +05:00
parent 9a8998501f
commit 5c6920843f
21 changed files with 798 additions and 282 deletions
+223 -207
View File
@@ -6,7 +6,7 @@
import "./StreamPage2.css";
import { PixelStreamingWrapper } from "../components/PixelStreamingWrapper";
import { useParams, useSearchParams } from "react-router-dom";
import { FormEvent, useEffect, useRef, useState } from "react";
import { FormEvent, useEffect, useRef } from "react";
import { Transition } from "react-transition-group";
import Button from "../components/ui/Button";
// import CloseIcon from "../components/icons/CloseIcon";
@@ -55,6 +55,7 @@ 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 (
@@ -64,6 +65,11 @@ const renderer = ({ minutes, seconds }: any) => {
);
};
interface IRemoteStream {
peerId: string;
mediaStream: MediaStream;
}
function StreamPage2() {
const { t, i18n } = useTranslation();
const { isPortrait } = useMobileOrientation();
@@ -73,12 +79,10 @@ function StreamPage2() {
const [wsUrl, setWsUrl] = useState<string>();
const [isEnded, setIsEnded] = useState<boolean>(false);
const { name, setName } = useStreamStore();
const [userId] = useState(uuidv4());
const userId = uuidv4();
const [step, setStep] = useState<number>(1);
const nameRef = useRef<HTMLInputElement>(null!);
const [users, setUsers] = useState<IUser[]>([]);
const usersRef = useRef<IUser[]>([]);
usersRef.current = users;
const fullscreenRef = useRef(null);
const [isFullscreen, { toggleFullscreen }] = useFullscreen(fullscreenRef);
const [isShowChat, setIsShowChat] = useState<boolean>(false);
@@ -86,7 +90,7 @@ function StreamPage2() {
const [isShowInviteModal, setIsShowInviteModal] = useState(false);
const [isVideoInitialized, setIsVideoInitialized] = useState<boolean>(false);
const [messageText, setMessageText] = useState("");
const [messages, setMessages] = useState<IMessage[]>([]);
const [messages] = useState<IMessage[]>([]);
const messagesRef = useRef<HTMLDivElement>(null);
const messageTextRef = useRef<HTMLInputElement>(null);
const parentElementRef = useRef<HTMLDivElement>(null);
@@ -94,21 +98,24 @@ function StreamPage2() {
const link = window.location.origin + window.location.pathname;
const [anyNewMessages, setAnyNewMessages] = useState(false);
const [endAt, setEndAt] = useState();
const localVideoRef = useRef<HTMLVideoElement>(null);
const [localStream, setLocalStream] = useState<MediaStream>(
new MediaStream()
);
const [remoteStreams, setRemoteStreams, remoteStreamsRef] = useState<
IRemoteStream[]
>([]);
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 [peerInstance, setPeerInstance] = useState<Peer>();
const [permission, setPermission] = useState<boolean>();
const [remoteStreams, setRemoteStreams] = useState<any[]>([]);
// const [errorMessage, setErrorMessage] = useState<string>("");
const isSpeaking = useIsAudioActive({
source: localStream.getTracks().length ? localStream : null,
});
const isCallInit = useRef<boolean>(false);
const [isVideoEnabled, setIsVideoEnabled] = useState<boolean>(false);
const [isAudioEnabled, setIsAudioEnabled] = useState<boolean>(false);
const isSpeaking = useIsAudioActive({
source: mediaStreamInstance.current,
});
async function getLang() {
const { countryCode, error }: { countryCode: string; error: string } =
@@ -195,121 +202,17 @@ function StreamPage2() {
return;
}
setSocket(
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,
},
},
})
);
setStep(2);
}
function setNameGuest() {
i18n.language === "ru" ? setName("Гость") : setName("Guest");
setSocket(
io(import.meta.env.VITE_SOCKET_URL, {
auth: {
roomId: params.id,
user: {
id: userId,
name: i18n.language === "ru" ? "Гость" : "Guest",
device: isMobile ? "mobile" : "desktop",
isAdmin: searchParams.has("admin", true),
peerId,
},
},
})
);
setStep(2);
}
async function getPermission() {
try {
const mediaStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
mediaStream.getAudioTracks().forEach((track) => {
track.enabled = !track.enabled;
});
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) {
const mediaStream = new MediaStream();
mediaStreamInstance.current = mediaStream;
videoRef.current!.srcObject = mediaStream;
videoRef.current!.onloadedmetadata = async () => {
try {
await videoRef.current?.play();
} catch (error) {
// toast.error((error as Error).message);
}
};
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 }]);
});
}
}
function disallowMic() {
setStep(3);
}
function toggleVideo() {
mediaStreamInstance.current!.getVideoTracks().forEach((track) => {
localStream.getVideoTracks().forEach((track) => {
track.enabled = !track.enabled;
if (!permission) return;
@@ -318,7 +221,7 @@ function StreamPage2() {
}
function toggleAudio() {
mediaStreamInstance.current!.getAudioTracks().forEach((track) => {
localStream.getAudioTracks().forEach((track) => {
track.enabled = !track.enabled;
if (!permission) return;
@@ -351,46 +254,135 @@ function StreamPage2() {
socket?.emit("kickUser", userId);
}
useEffect(() => {
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();
// Как только соединение с сервером 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 }]);
call.answer(localStream || undefined);
let accept = true;
call.on("stream", (remoteStream) => {
if (!accept) return;
setRemoteStreams((prev) => [
...prev,
{ peerId: call.peer, mediaStream: remoteStream },
]);
accept = false;
});
});
peerInstance.current = peer;
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(() => {
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(() => {
document.title = t("title");
}, [i18n.language]);
@@ -419,66 +411,72 @@ function StreamPage2() {
}
}, [isShowChat]);
useEffect(() => {
if (!socket) return;
// useEffect(() => {
// if (!peerId) return;
console.log(params.id, userId);
// 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,
// },
// },
// });
socket.on("update", (roomUsers: IUser[]) => {
setUsers(roomUsers);
// for (const user of roomUsers) {
// setRemoteStreams(
// remoteStreams.filter(({ peerId }) => peerId === user.peerId)
// );
// }
});
// // TODO
socket.on("message", (message: IMessage) => {
setMessages((prev) => [...prev, message]);
});
// socket.on("message", (message: IMessage) => {
// setMessages((prev) => [...prev, message]);
// });
socket.on("requestControl", (user: IUser) => {
if (!usersRef.current.find((user) => user.id === userId)?.isAdmin) return;
// socket.on("requestControl", (user: IUser) => {
// if (!usersRef.current.find((user) => user.id === userId)?.isAdmin) return;
toast.info(`${user.name} запрашивает разрешение на управление`, {
icon: <InfoBlueIcon />,
position: "top-center",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
});
// toast.info(`${user.name} запрашивает разрешение на управление`, {
// icon: <InfoBlueIcon />,
// 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;
// socket.on("transferControl", (user: IUser) => {
// if (user.id !== userId) return;
toast.info(`Вы получили разрешение на управление`, {
icon: <InfoBlueIcon />,
position: "top-center",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
});
// toast.info(`Вы получили разрешение на управление`, {
// icon: <InfoBlueIcon />,
// 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();
}
});
}, [socket]);
// socket.on("kickUser", (socketUserId: string) => {
// if (socketUserId === userId) {
// socket.disconnect();
// window.close();
// window.location.reload();
// }
// });
// setSocket(socket);
// }, [peerId]);
useEffect(() => {
if (!isMobile) return;
@@ -502,6 +500,24 @@ function StreamPage2() {
}
}, [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 ? (
@@ -772,7 +788,7 @@ function StreamPage2() {
<div className="absolute top-2 lg:left-2 lg:right-auto right-2 flex flex-col gap-2">
<video
ref={videoRef}
ref={localVideoRef}
className={`aspect-video bg-black rounded-lg -scale-x-100 object-cover ring-2 ${
isAudioEnabled && isSpeaking
? "ring-green-500"
@@ -786,10 +802,10 @@ function StreamPage2() {
autoPlay
muted
/>
{remoteStreams.map(({ remoteStream }, index) => (
{remoteStreams.map(({ peerId, mediaStream }) => (
<Video
key={index}
mediaStream={remoteStream}
key={peerId}
mediaStream={mediaStream}
muted={!permission}
/>
))}
@@ -1135,16 +1151,16 @@ function StreamPage2() {
</div>
</div>
<div className="flex gap-2">
<Button
{/* <Button
variant="secondary"
fullWidth
large
onClick={disallowMic}
>
<Trans i18nKey={"skip"}>Пропустить</Trans>
</Button>
<Button fullWidth large onClick={getPermission}>
<Trans i18nKey={"allow"}>Разрешить</Trans>
</Button> */}
<Button fullWidth large onClick={getUserMedia}>
<Trans i18nKey={"allow"}>Продолжить</Trans>
</Button>
</div>
</div>