Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
January 2, 2021 12:23 pm GMT

Video Chatting and Screen Sharing with React, Node, WebRTC(peerjs)

To create a video chatting and screen sharing application requires three major setup

  1. Basic React setup for handling UI.

  2. Needs Backend (Nodejs) for maintaining socket connection.

  3. Needs a peer server to maintain create peer-to-peer connection and to maintain it.

1) React basic setup with join button which makes an API call to backend and gets a unique id and redirects the user to join the room (React running at the port 3000)

Frontend - ./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;
Enter fullscreen mode Exit fullscreen mode

Here our backend is running at port localhost 5000, as a response will be getting a unique id that will be used as a room id with upcoming steps.

2) Backend - Node basic setup with a server listening in port 5000 and defining router with "/join" to generate a unique id and return it to frontend

Backend - ./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;// Middlewaresapp.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);});
Enter fullscreen mode Exit fullscreen mode

Here using uuid package to generate a unique string.

3) At the frontend creating a new route with the id got in the response(looks something like this "http://localhost:3000/join/a7dc3a79-858b-420b-a9c3-55eec5cf199b"). A new component - RoomComponent is created with the disconnect button and having a div container with id="room-container" to hold our video elements

Frontend - ../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;
Enter fullscreen mode Exit fullscreen mode

4) Now we need our stream from our device cam and mic, we can use the navigator to get the device stream data. For this, we can use a helper class (Connection) to maintain all the incoming and outgoing stream data and to maintain the socket connection with the backend.

Frontend - ./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 SocketConnection(settings);}
Enter fullscreen mode Exit fullscreen mode

Here we have created a Connection class to maintain all our socket and peer connection, Don't worry we will walk through all the functions above.

  1. we have a constructor that gets a settings object (optional) that can be used to send some data from our component for setting up our connection class like (sending video frame to be used)
  2. Inside constructor we are invoking two methods initializeSocketEvents() and initializePeersEvents()
    • initializeSocketEvents() - Will start socket connection with our backend.
    • initializePeersEvents() - Will start peer connection with our peer server.
  3. Then we have setNavigatorToStream() which has getVideoAndAudio() function which will get the audio and video stream from the navigator. We can specify the video frame rate in the navigator.
  4. If the stream is available then we will be resolving in .then(streamObj) and now we can create a video element to display our stream bypassing stream object to createVideo().
  5. Now after getting our own stream it's time to listen to the peer events in function setPeersListeners() where we will be listening for any incoming video stream from another user and will stream our data in peer.answer(ourStream).
  6. And the we will be setting newUserConnection(), where we will be sending our stream, if we are connecting to the existing room and also keeping track of the current peer connection by userID in peers Object.
  7. Finally we have removeVideo to remove the video element from dom when any user dissconnected.

5) Now the backend needs to listen to the socket connection. Using socket "socket.io" to make the socket connection easy.

Backend - ./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);        });    });});
Enter fullscreen mode Exit fullscreen mode

Now we have added socket connection to backend to listen to join room, which will be triggred from frontend with userData containing roomID and userID. The userID is available when creating the peer connection.

Then the socket has now connected a room with the roomID (From unique id got as response in frontend) and now we can dispatch message to all the users in the room.

Now socket.to(roomID).broadcast.emit('new-user-connect', userData); with this we can dispatch message to all the user's connected except us. And this 'new-user-connect is listened at the frontend so all the user's connected in the room will receive the new user data.

6) Now you need create a peerjs server by using following commands

npm i -g peerjspeerjs --port 9000
Enter fullscreen mode Exit fullscreen mode

7) Now in Room Component we need to invoke the Connection class to start the call. In Room Component add this functionality.

Frontend - ./RoomComponent.js

    let socketInstance = useRef(null);        useEffect(() => {        startConnection();    }, []);    const startConnection = () => {        params = {quality: 12}        socketInstance.current = createSocketConnectionInstance({            updateInstance: updateFromInstance,            params        });    }
Enter fullscreen mode Exit fullscreen mode

Now you will be able to see that after creating a room, when a new user joins the user will be peer-to-peer connected.

8) Now for Screen Sharing You need to replace the current stream with the new screen sharing stream.

Frontend - ./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]);                    }                }            });        })    }
Enter fullscreen mode Exit fullscreen mode

Now the current stream needs to reInitializeStream() will be checking the type it needs to replace, if it is userMedia then it will be streaming from cam and mic, if its display media it gets the display stream object from getDisplayMedia() and then it will toggle the track to stop or start the cam or mic.

Then the new stream video element is created based on the userID and then it will place the new stream by replaceStream(). By getting the current call object store previosly will contain the curretn stream data will be replaced with the new stream data in replaceStream().

9) At roomConnection we need to create a button to toggle the video and screen sharing.

Frontend - ./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>    )
Enter fullscreen mode Exit fullscreen mode

Thats all you have Create a application with video chatting and screen sharing.

Good Luck !!!


Original Link: https://dev.to/arjhun777/video-chatting-and-screen-sharing-with-react-node-webrtc-peerjs-18fg

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To