This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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];
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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',
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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)
|
||||
};
|
||||
}, [])
|
||||
Reference in New Issue
Block a user