VyacheslavShtyrlin
2022-12-26 22:47:34 +05:00
6 changed files with 489 additions and 25 deletions
-25
View File
@@ -19,28 +19,3 @@ export const PopupShare: React.FC<any> = ({ setClose, data }) => {
useEffect(() => () => setCopy(false), []);
return (
<div className='share-popup-container'>
<div className='mobile-users-part-header share-header-popup'>
<span className='mobile-users-part-header-title'>Пригласить на демонстрацию</span>
<button onClick={closePopup} className='mobile-users-part-header-close-button'></button>
</div>
<div className='share-popup-data-container'>
<span className='share-popup-data-title'>Код подключения</span>
<input className='share-popup-data-input share-popup-data-input code' value={data.connection_code} readOnly></input>
</div>
<div className='border-line'></div>
<div className='share-popup-data-container'>
<span className='share-popup-data-title'>Ссылка для подключения</span>
<input className='share-popup-data-input href' value={window.location.href} readOnly></input>
</div>
<div className='share-popup-data-container'>
<button className='share-popup-copy-button '>
<span className='share-popup-copy-button-icon'></span>
<span onClick={copyLink} className='share-popup-copy-button-title'>{copy ? 'Скопировано' : 'Скопировать'}</span>
</button>
</div>
</div>
)
}
+22
View File
@@ -0,0 +1,22 @@
import { useState, useRef, useEffect, useCallback } from "react";
export const useStateWithCallback = (intialState) => {
const [state, setState] = useState(intialState);
const cbRef = useRef(null);
const updateState = useCallback((newState, cb) => {
cbRef.current = cb;
setState((prev) =>
typeof newState === "function" ? newState(prev) : newState
);
}, []);
useEffect(() => {
if (cbRef.current) {
cbRef.current(state);
cbRef.current = null;
}
}, [state]);
return [state, updateState];
};
+401
View File
@@ -0,0 +1,401 @@
import { useEffect, useState, useRef, useCallback } from "react";
import { ACTIONS } from "../socket/actions";
import socketInit from "../socket";
import freeice from "freeice";
import { useStateWithCallback } from "./useStateWithCallback";
import { useHistory } from "react-router-dom";
export const useWebRTC = (roomId, user) => {
const history = useHistory();
const [clients, setClients] = useStateWithCallback([]);
const audioElements = useRef({});
const [message, setMessages] = useState("");
const [control, setControl] = useState('')
const [warning, setWarning] = useState("");
const connections = useRef({});
const socket = useRef(null);
const localMediaStream = useRef(null);
const clientsRef = useRef(null);
const addNewClient = useCallback(
(newClient, cb) => {
const lookingFor = clients.find((client) => client.id === newClient.id);
if (lookingFor === undefined) {
setClients((existingClients) => [...existingClients, newClient], cb);
}
},
[clients, setClients]
);
useEffect(() => {
clientsRef.current = clients;
}, [clients]);
useEffect(() => {
const initChat = async () => {
socket.current = socketInit();
console.log(localMediaStream.current)
await captureMedia();
addNewClient(
{
...user,
muted: true,
admin: false,
control: false,
},
() => {
const localElement = audioElements.current[user.id];
if (localElement) {
localElement.volume = 0;
localElement.srcObject = localMediaStream.current;
}
}
);
socket.current.on(ACTIONS.MUTE_INFO, ({ userId, isMute, isAdmin }) => {
handleSetMute(isMute, userId);
});
socket.current.on(ACTIONS.ADD_PEER, handleNewPeer);
socket.current.on(ACTIONS.REMOVE_PEER, handleRemovePeer);
socket.current.on(ACTIONS.ICE_CANDIDATE, handleIceCandidate);
socket.current.on(ACTIONS.SESSION_DESCRIPTION, setRemoteMedia);
socket.current.on(ACTIONS.MUTE, ({ peerId, userId }) => {
handleSetMute(true, userId);
});
socket.current.on("ADMIN-LEFT", ({ isLeft }) => {
setWarning(isLeft);
});
socket.current.on("ADMIN-SET", ({ admin }) => {
handleSetAdmin(true, admin);
setControl(admin)
});
socket.current.on(ACTIONS.UNMUTE, ({ peerId, userId }) => {
handleSetMute(false, userId);
});
socket.current.emit(ACTIONS.JOIN, {
roomId,
user,
});
socket.current.on("CONTROL-SET", ({ userId }) => {
handleSetControl(userId);
setControl(userId)
});
async function captureMedia() {
// Start capturing local audio stream.
localMediaStream.current = await navigator.mediaDevices.getUserMedia({
audio: true,
}).catch(() => {
alert('Подключите микрофон!')
});
}
socket.current.on("REQUEST-CONTROL", ({ userID }) => {
setMessages(userID);
});
async function handleNewPeer({ peerId, createOffer, user: remoteUser }) {
if (peerId in connections.current) {
return console.warn(
`You are already connected with ${peerId} (${user.name})`
);
}
// Store it to connections
connections.current[peerId] = new RTCPeerConnection({
iceServers: freeice(),
});
// Handle new ice candidate on this peer connection
connections.current[peerId].onicecandidate = (event) => {
socket.current.emit(ACTIONS.RELAY_ICE, {
peerId,
icecandidate: event.candidate,
});
};
// Handle on track event on this connection
connections.current[peerId].ontrack = ({ streams: [remoteStream] }) => {
addNewClient(
{
...remoteUser,
muted: true,
admin: false,
control: false,
},
() => {
// get current users mute info
const currentUser = clientsRef.current.find(
(client) => client.id === user.id
);
if (currentUser) {
socket.current.emit(ACTIONS.MUTE_INFO, {
userId: user.id,
roomId,
isMute: currentUser.muted,
isAdmin: currentUser.admin,
isControl: currentUser.control,
});
}
if (audioElements.current[remoteUser.id]) {
audioElements.current[remoteUser.id].srcObject = remoteStream;
} else {
let settled = false;
const interval = setInterval(() => {
if (audioElements.current[remoteUser.id]) {
audioElements.current[remoteUser.id].srcObject =
remoteStream;
settled = true;
}
if (settled) {
clearInterval(interval);
}
}, 300);
}
}
);
};
// Add connection to peer connections track
localMediaStream.current.getTracks().forEach((track) => {
connections.current[peerId].addTrack(track, localMediaStream.current);
});
// Create an offer if required
if (createOffer) {
const offer = await connections.current[peerId].createOffer();
// Set as local description
await connections.current[peerId].setLocalDescription(offer);
// send offer to the server
socket.current.emit(ACTIONS.RELAY_SDP, {
peerId,
sessionDescription: offer,
});
}
}
async function handleRemovePeer({ peerId, userId }) {
console.log(userId);
// Correction: peerID to peerId
if (connections.current[peerId]) {
connections.current[peerId].close();
}
delete connections.current[peerId];
delete audioElements.current[peerId];
setClients((list) => handleLogout(list, userId));
}
async function handleIceCandidate({ peerId, icecandidate }) {
if (icecandidate) {
connections.current[peerId].addIceCandidate(icecandidate);
}
}
async function setRemoteMedia({
peerId,
sessionDescription: remoteSessionDescription,
}) {
connections.current[peerId].setRemoteDescription(
new RTCSessionDescription(remoteSessionDescription)
);
// If session descrition is offer then create an answer
if (remoteSessionDescription.type === "offer") {
const connection = connections.current[peerId];
const answer = await connection.createAnswer();
connection.setLocalDescription(answer);
socket.current.emit(ACTIONS.RELAY_SDP, {
peerId,
sessionDescription: answer,
});
}
}
async function handleSetMute(mute, userId) {
console.log(clientsRef.current);
const clientIdx = clientsRef.current
.map((client) => client.id)
.indexOf(userId);
const allConnectedClients = JSON.parse(
JSON.stringify(clientsRef.current)
);
if (clientIdx > -1) {
allConnectedClients[clientIdx].muted = mute;
setClients(allConnectedClients);
}
}
async function handleSetControl(userId) {
const clientIdx = clientsRef.current
.map((client) => client.id)
.indexOf(userId);
const allConnectedClients = JSON.parse(
JSON.stringify(clientsRef.current)
);
for (let i = 0; i < allConnectedClients.length; i++) {
allConnectedClients[i].control = false;
}
if (clientIdx > -1) {
allConnectedClients[clientIdx].control = true;
setClients(allConnectedClients);
}
}
async function handleSetAdmin(admin, userId) {
const clientIdx = clientsRef.current
.map((client) => client.id)
.indexOf(userId);
const allConnectedClients = JSON.parse(
JSON.stringify(clientsRef.current)
);
if (clientIdx > -1) {
allConnectedClients[clientIdx].admin = admin;
allConnectedClients[clientIdx].control = true;
setClients(allConnectedClients);
}
}
};
initChat();
return () => {
if (localMediaStream.current) {
localMediaStream.current.getTracks().forEach((track) => track.stop());
}
socket.current.emit(ACTIONS.LEAVE, { roomId });
for (let peerId in connections.current) {
connections.current[peerId].close();
delete connections.current[peerId];
delete audioElements.current[peerId];
}
socket.current.off(ACTIONS.ADD_PEER);
socket.current.off(ACTIONS.REMOVE_PEER);
socket.current.off(ACTIONS.ICE_CANDIDATE);
socket.current.off(ACTIONS.SESSION_DESCRIPTION);
socket.current.off(ACTIONS.MUTE);
socket.current.off(ACTIONS.UNMUTE);
};
}, []);
const provideRef = (instance, userId) => {
audioElements.current[userId] = instance;
};
const handleAdmin = () => {
let settled = false;
let interval = setInterval(() => {
socket.current.emit("ADMIN-SET", {
roomId,
});
settled = true;
if (settled) {
clearInterval(interval);
}
}, 200);
};
const handleMute = (isMute, userId) => {
let settled = false;
if (userId === user.id) {
let interval = setInterval(() => {
if (localMediaStream.current) {
localMediaStream.current.getTracks()[0].enabled = !isMute;
if (isMute) {
socket.current.emit(ACTIONS.MUTE, {
roomId,
userId: user.id,
});
} else {
socket.current.emit(ACTIONS.UNMUTE, {
roomId,
userId: user.id,
});
}
settled = true;
}
if (settled) {
clearInterval(interval);
}
}, 200);
}
};
const handleControl = (userId) => {
let settled = false;
let interval = setInterval(() => {
socket.current.emit("CONTROL-SET", {
roomId,
userId: userId,
});
settled = true;
if (settled) {
clearInterval(interval);
}
}, 200);
};
const handleLogout = (list, userId) => {
const isAdmin = list.some((i) => i.admin === true && i.id === userId);
const isControl = list.some((i) => i.control === true && i.id === userId);
console.log(isControl);
if (isAdmin) {
socket.current.emit("ADMIN-LEFT", {
isLeft: true,
});
} else if (isControl) {
handleChangeControl(list);
}
return list.filter((c) => c.id !== userId);
};
const handleChangeControl = (list) => {
const admin = list.find((i) => i.admin === true);
if (admin) {
handleControl(admin.id);
} else {
return;
}
};
const handleReturnControl = () => {
const admin = clients.find((i) => i.admin === true);
if (admin) {
handleControl(admin.id);
} else {
return;
}
};
const sendRequset = (user) => {
socket.current.emit("REQUEST-CONTROL", {
userID: user.id,
roomId,
});
};
return {
clients,
provideRef,
warning,
handleMute,
handleControl,
sendRequset,
message,
control,
handleAdmin,
handleReturnControl,
};
};
+13
View File
@@ -0,0 +1,13 @@
export const ACTIONS = {
JOIN: 'join',
LEAVE: 'leave',
ADD_PEER: 'add-peer',
REMOVE_PEER: 'remove-peer',
RELAY_ICE: 'relay-ice',
RELAY_SDP: 'relay-sdp',
SESSION_DESCRIPTION: 'session-description',
ICE_CANDIDATE: 'ice-candidate',
MUTE: 'mute',
UNMUTE: 'unmute',
MUTE_INFO: 'mute-info',
};
+13
View File
@@ -0,0 +1,13 @@
import { io } from "socket.io-client";
const socketInit = () => {
const options = {
"force new connection": true,
reconnectionAttempts: "Infinity",
timeout: 10000,
transports: ["websocket"],
};
return io('localhost:5500', options);
};
export default socketInit;
+40
View File
@@ -0,0 +1,40 @@
useEffect(() => {
let socket = new WebSocket('wss://stream.graff.tech:13001')
setSocket(socket)
socket.onopen = () => {
socket.send('{"message" : "NEW_USER"}')
setConnected(true)
}
socket.onmessage = (e) => {
const response = JSON.parse(e.data)
console.log(response, 'res')
switch (response.message) {
case 'NEW_USER':
console.log('user');
break
case 'SESS_CREATION':
setData({ id: response.id, port: response.port })
history.push(`/stream/${response.id}`)
break
case 'SESS_CONNECT':
setData({ id: response.id, port: response.content })
break
case 'SESS_NOT_EXISTS':
break
}
}
socket.onclose = () => {
socket.close()
setConnected(false)
};
socket.onerror = () => {
console.log("WS Error");
setConnected(false)
};
}, [])