Dev. To /arjhun777/v…

Creating video chat and screen sharing applications requires three main Settings

  1. Handle the basic React setup of the UI.

  2. A back end (No DeJS) is required to maintain socket connections.

  3. Peer servers are required to maintain the peer connection and maintain it.

1) Reaction basic Settings with join button to make an API call back end and get a unique ID and redirect the user to join the room (reaction runs on port 3000)

The front -. / Home. Js

import Axios from 'axios'; import React from 'react'; function Home(props) { const handleJoin = () => { Axios.get(`http://localhost:5000/join`).then(res => { props.history? .push(`/join/${res.data.link}? quality=${quality}`); }) } return ( <React.Fragment> <button onClick={handleJoin}>join</button> </React.Fragment> ) } export default Home;Copy the code

Here, our back end runs on port Localhost 5000 and in response gets a unique ID that will be used as the room ID in the upcoming step.

2) Back-end – Basic setup of the node where the server listens on port 5000 and defines the router with “/join” to generate a unique ID and return it to the front end

The back-end -. / server. Js

import express from 'express';

import cors from 'cors';

import server from 'http';

import { v4 as uuidV4 } from 'uuid';



const app = express();

const serve = server.Server(app);

const port = process.env.PORT || 5000;



// Middlewares

app.use(cors());

app.use(express.json());

app.use(express.urlencoded({ extended: true }));



app.get('/join', (req, res) => {

    res.send({ link: uuidV4() });

});



serve.listen(port, () => {

    console.log(`Listening on the port ${port}`);

}).on('error', e => {

    console.error(e);

});
Copy the code

The UUID package is used here to generate unique strings.

3) Create a new route in the front end and get the ID in the response (it looks like this: “Hypertext Transfer protocol: //localhost: 3000/join/ a7dc3a79-858B-420B-a9C3-55eec5CF199b”). A new component-room Component creates a Room container with a break button and a div container id= Room container to hold our video elements

The front -… /RoomComponent.js

const RoomComponent = (props) => { const handleDisconnect = () => { socketInstance.current? .destoryConnection(); props.history.push('/'); } return ( <React.Fragment> <div id="room-container"></div> <button onClick={handleDisconnect}>Disconnect</button> </React.Fragment> ) } export default RoomComponent;Copy the code

4) Now we need our device CAM and microphone stream, we can use navigator to get device stream data. To do this, we can use a helper class (Connection) to maintain all incoming and outgoing stream data and to maintain connections to the socket at the back end.

The front -. / connection. Js

import openSocket from 'socket.io-client'; import Peer from 'peerjs'; const { websocket, peerjsEndpoint } = env_config; const initializePeerConnection = () => { return new Peer('', { host: peerjsEndpoint, // need to provide peerjs server endpoint // (something like localhost:9000) secure: true }); } const initializeSocketConnection = () => { return openSocket.connect(websocket, {// need to provide backend server endpoint // (ws://localhost:5000) if ssl provided then // (wss://localhost:5000) secure: true, reconnection: true, rejectUnauthorized: false, reconnectionAttempts: 10 }); } class Connection { videoContainer = {}; message = []; settings; streaming = false; myPeer; socket; myID = ''; constructor(settings) { this.settings = settings; this.myPeer = initializePeerConnection(); this.socket = initializeSocketConnection(); this.initializeSocketEvents(); this.initializePeersEvents(); } initializeSocketEvents = () => { this.socket.on('connect', () => { console.log('socket connected'); }); this.socket.on('user-disconnected', (userID) => { console.log('user disconnected-- closing peers', userID); peers[userID] && peers[userID].close(); this.removeVideo(userID); }); this.socket.on('disconnect', () => { console.log('socket disconnected --'); }); this.socket.on('error', (err) => { console.log('socket error --', err); }); } initializePeersEvents = () => { this.myPeer.on('open', (id) => { this.myID = id; const roomID = window.location.pathname.split('/')[2]; const userData = { userID: id, roomID } console.log('peers established and joined room', userData); this.socket.emit('join-room', userData); this.setNavigatorToStream(); }); this.myPeer.on('error', (err) => { console.log('peer connection error', err); this.myPeer.reconnect(); }) } setNavigatorToStream = () => { this.getVideoAudioStream().then((stream) => { if (stream) { this.streaming = true; this.createVideo({ id: this.myID, stream }); this.setPeersListeners(stream); this.newUserConnection(stream); } }) } getVideoAudioStream = (video=true, audio=true) => { let quality = this.settings.params? .quality; if (quality) quality = parseInt(quality); const myNavigator = navigator.mediaDevices.getUserMedia || navigator.mediaDevices.webkitGetUserMedia || navigator.mediaDevices.mozGetUserMedia || navigator.mediaDevices.msGetUserMedia; return myNavigator({ video: video ? { frameRate: quality ? quality : 12, noiseSuppression: true, width: {min: 640, ideal: 1280, max: 1920}, height: {min: 480, ideal: 720, max: 1080} } : false, audio: audio, }); } createVideo = (createObj) => { if (! this.videoContainer[createObj.id]) { this.videoContainer[createObj.id] = { ... createObj, }; const roomContainer = document.getElementById('room-container'); const videoContainer = document.createElement('div'); const video = document.createElement('video'); video.srcObject = this.videoContainer[createObj.id].stream; video.id = createObj.id; video.autoplay = true; if (this.myID === createObj.id) video.muted = true; videoContainer.appendChild(video) roomContainer.append(videoContainer); } else { // @ts-ignore document.getElementById(createObj.id)? .srcObject = createObj.stream; } } setPeersListeners = (stream) => { this.myPeer.on('call', (call) => { call.answer(stream); call.on('stream', (userVideoStream) => {console.log('user stream data', userVideoStream) this.createVideo({ id: call.metadata.id, stream: userVideoStream }); }); call.on('close', () => { console.log('closing peers listeners', call.metadata.id); this.removeVideo(call.metadata.id); }); call.on('error', () => { console.log('peer error ------'); this.removeVideo(call.metadata.id); }); peers[call.metadata.id] = call; }); } newUserConnection = (stream) => { this.socket.on('new-user-connect', (userData) => { console.log('New User Connected', userData); this.connectToNewUser(userData, stream); }); } connectToNewUser(userData, stream) { const { userID } = userData; const call = this.myPeer.call(userID, stream, { metadata: { id: this.myID }}); call.on('stream', (userVideoStream) => { this.createVideo({ id: userID, stream: userVideoStream, userData }); }); call.on('close', () => { console.log('closing new user', userID); this.removeVideo(userID); }); call.on('error', () => { console.log('peer error ------') this.removeVideo(userID); }) peers[userID] = call; } removeVideo = (id) => { delete this.videoContainer[id]; const video = document.getElementById(id); if (video) video.remove(); } destoryConnection = () => { const myMediaTracks = this.videoContainer[this.myID]? .stream.getTracks(); myMediaTracks? .forEach((track:any) => { track.stop(); }) socketInstance? .socket.disconnect(); this.myPeer.destroy(); } } export function createSocketConnectionInstance(settings={}) { return socketInstance = new Connection(settings); }Copy the code

Here we have created a connection class to maintain all of our sockets and peer connections, don’t worry, we will browse all of the above functions.

  1. We have a constructor that gets a Settings object (optional) that can be used to send some data from our component to set up our connection class, for example (send video frames)

  2. In the constructor, we call two methods to initialize Socket Events () and Peers Events ()

    • Initialize Socket Events () – will initiate a Socket connection to our back end.
  3. Initialize Peers Events () – This will initiate a peer connection to our peer server.

  4. Then we have set Navigator To Stream (), which has the get Video And Audio () function, which will get the Audio And Video streams from the Navigator. We can specify the video frame rate in the navigator.

  5. If the stream is available, we will parse it in.than (stream Obj). Now we can create a video element to display our stream, bypassing the stream object to create the video ().

  6. Now, having obtained our own streams, it’s time to listen for peer events in the function Set Peers (), where we’ll listen for any incoming video streams from another user and Stream our data in peer-.answer (our Stream).

  7. We will set up new User Connection (), where we will send the flow if we connect to an existing room and trace the current peer Connection through the userID in the peer object.

  8. Finally, when any user disconnects, we can remove the video element from the DOM.

5) Now the back end needs to listen for socket connections. Use the socket “socket. IO” to make socket connections easy.

The back-end -. / server. Js

import socketIO from 'socket.io';

io.on('connection', socket => {

    console.log('socket established')

    socket.on('join-room', (userData) => {

        const { roomID, userID } = userData;

        socket.join(roomID);

        socket.to(roomID).broadcast.emit('new-user-connect', userData);

        socket.on('disconnect', () => {

            socket.to(roomID).broadcast.emit('user-disconnected', userID);

        });

    });

});
Copy the code

We have now added a socket connection to the back end to listen for joining the room, which will be triggered from the User Data front end containing the room ID and userID. The user ID is available when creating a peer connection.

Then the socket has now connected to a room with the room ID (the response from the unique ID from the front end), and we can now send messages to all users in the room.

Now socket.to(roomID).broadcast.emit (‘ new-user-connect ‘, user Data); With it, we can send messages to all users’ connections except ours. This “new user connection” is listened on the front end, so all connected users in the room will receive the new user data.

6) Now you need to create a PeerJS server using the following command

npm i -g peerjs

peerjs --port 9000
Copy the code

7) Now in the Room Component, we need to call the Connection class to initiate the call. Add this functionality to the room component.

The front -. / RoomComponent. Js

let socketInstance = useRef(null); useEffect(() => { startConnection(); } []); const startConnection = () => { params = {quality: 12} socketInstance.current = createSocketConnectionInstance({ params }); }Copy the code

You will now be able to see that after creating a room, when a new user joins, that user is connected point-to-point.

8) Now for screen sharing, you need to replace the current stream with a new screen sharing stream.

The front -. / connection. Js

    reInitializeStream = (video, audio, type='userMedia') => {

        const media = type === 'userMedia' ? this.getVideoAudioStream(video, audio) : 

        navigator.mediaDevices.getDisplayMedia();

        return new Promise((resolve) => {

            media.then((stream) => {

                if (type === 'displayMedia') {

                    this.toggleVideoTrack({audio, video});

                }

                this.createVideo({ id: this.myID, stream });

                replaceStream(stream);

                resolve(true);

            });

        });

    }

    toggleVideoTrack = (status) => {

        const myVideo = this.getMyVideo();

        if (myVideo && !status.video) 

            myVideo.srcObject?.getVideoTracks().forEach((track) => {

                if (track.kind === 'video') {

                    !status.video && track.stop();

                }

            });

        else if (myVideo) {

            this.reInitializeStream(status.video, status.audio);

        }

    }

    replaceStream = (mediaStream) => {

        Object.values(peers).map((peer) => {

            peer.peerConnection?.getSenders().map((sender) => {

                if(sender.track.kind == "audio") {

                    if(mediaStream.getAudioTracks().length > 0){

                        sender.replaceTrack(mediaStream.getAudioTracks()[0]);

                    }

                }

                if(sender.track.kind == "video") {

                    if(mediaStream.getVideoTracks().length > 0){

                        sender.replaceTrack(mediaStream.getVideoTracks()[0]);

                    }

                }

            });

        })

    }
Copy the code

Now, the current Stream that needs to be reinitialized Stream () will check for the type it needs to replace. If it is User Media, it will be transmitted from CAM and micro, and if its Display Media, it will get the Display Stream object from Get Display Media (), It will then switch tracks to stop or start CAM or Micro.

A new streaming video element is then created based on the userID, which will then place the new stream through the replacement stream (). The curr ETN Stream data contained by fetching the current call to the object repository pre Vio Sly will be replaced with the new Stream data in the replacement Stream ().

9) In Room Connection, we need to create a button to switch between video and screen sharing.

The front -. / RoomConnection. Js

const [mediaType, setMediaType] = useState(false); const toggleScreenShare = (displayStream ) => { const { reInitializeStream, toggleVideoTrack } = socketInstance.current;  displayStream === 'displayMedia' && toggleVideoTrack({ video: false, audio: true }); reInitializeStream(false, true, displayStream).then(() => { setMediaType(! mediaType) }); } return ( <React.Fragment> <div id="room-container"></div> <button onClick={handleDisconnect}>Disconnect</button> <button onClick={() => reInitializeStream(mediaType ? 'userMedia' : 'displayMedia')} > {mediaType ? 'screen sharing' : 'stop sharing'}</button> </React.Fragment> )Copy the code

This is all you have to create a video chat and screen sharing application.

Good luck!!

This is a demo video of my work

Check out my blog – https://dev-ajs.blogspot.com/