Building a Programmable Video using ReactJS

Introduction


Twilio is a cloud communications platform as a service company based in San Francisco, California. Twilio allows software developers to programmatically make and receive phone calls, send and receive text messages, and perform other communication functions using its web service APIs.

Programmable Video

 
Programmable Video is the service provided by Twilio, Connect multiple users (limited) in a single video Conference, so that those users can interact with each other with the live conference or run-time video chat. 
Step 1 - Build Node.Js application
 
Node js code is used for API calls, to authenticate the user and in return will get token of the user. After getting various user-specific tokens, we can connect to the same room created by Twilio to make a video conference.
  1. require('dotenv').load()  
  2. const express = require('express')  
  3. const app = express()  
  4. var AccessToken = require("twilio").jwt.AccessToken;  
  5. var VideoGrant = AccessToken.VideoGrant;  
  6.   
  7. app.get('/token/:identity'function (req, res) {  
  8.   const identity = req.params.identity;  
  9.   
  10.   // Create an access token which we will sign and return to the client,  
  11.   // containing the grant we just created  
  12.   var token = new AccessToken(  
  13.     process.env.TWILIO_ACCOUNT_SID,  
  14.     process.env.TWILIO_API_KEY,  
  15.     process.env.TWILIO_API_SECRET  
  16.   );  
  17.   
  18.   // Assign the generated identity to the token  
  19.   token.identity = identity;  
  20.   
  21.   const grant = new VideoGrant();  
  22.   // Grant token access to the Video API features  
  23.   token.addGrant(grant);  
  24.   
  25.   // Serialize the token to a JWT string and include it in a JSON response  
  26.   res.send({  
  27.     identity: identity,  
  28.     jwt: token.toJwt()  
  29.   })  
  30. })  
  31.   
  32. app.listen(3001, function () {  
  33.   console.log('Programmable Video Chat token server listening on port 3001!')  
  34. }) 
Step 2 - Build React.Js application
 
Configured basic ReactJs application using the create-react-app command. Using this command will get the default well-structured application of React.

 
Step 3 - Twilio Configuration
 
If you do not have an account on Twilio, you can sign up (Register), then configure your account.
 

You can get your configuration key(s) from:
  • TWILIO_ACCOUNT_SID: Get you to TWILIO_ACCOUNT_SID from the Twilio account dashboard.

  • Let’s make our .env file and add the following code to it, replacing each key and token with the one found in our Twilio console.

Twilio Code - Predefined Functions(App.js)

 
Twilio API provided predefined functions to get connected with different users. Provided we get token, create a room, join a room, attachTracks, attachParticipants,  detachTracks, etc.. with the use of predefined function we can get connect with different users and make a track of each user who is connected or who is not.
  1. import React, { Component } from 'react'  
  2. import Video from 'twilio-video';  
  3. import axios from 'axios';  
  4. import './global.css';  
  5. import { ToastsContainer, ToastsStore } from 'react-toasts';  
  6. import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";  
  7. import Loader from 'react-loader-spinner';  
  8.   
  9. class App extends Component {  
  10.   constructor(props) {  
  11.     super(props);  
  12.     this.state = {  
  13.       userName: "",  
  14.       identity: null,  
  15.       peerUserId: 0,  
  16.       peerIdentity: "",  
  17.       roomName: '*****',  // Room Name   
  18.       roomNameErr: false// Track error for room name TextField  
  19.       previewTracks: null,  
  20.       localMediaAvailable: false,  
  21.       hasJoinedRoom: false,  
  22.       hasParticipantsJoinedRoom: false,  
  23.       activeRoom: ''// Track the current active room  
  24.       jwt: ''  
  25.     }  
  26.   
  27.     this.joinRoom = this.joinRoom.bind(this);  
  28.     this.roomJoined = this.roomJoined.bind(this);  
  29.     this.leaveRoom = this.leaveRoom.bind(this);  
  30.     this.detachTracks = this.detachTracks.bind(this);  
  31.     this.detachParticipantTracks = this.detachParticipantTracks.bind(this);  
  32.   }  
  33.   
  34.   getTwillioToken = () => {  
  35.     const currentUserName = this.refs["yourname"].value;  
  36.     if (currentUserName.length === 0) {  
  37.       ToastsStore.error("Please enter the username!");  
  38.       return;  
  39.     }  
  40.   
  41.     axios.get('/token/' + currentUserName).then(results => {  
  42.       const { identity, jwt } = results.data;  
  43.       this.setState(  
  44.         {  
  45.           identity,  
  46.           jwt  
  47.         }, () => {  
  48.           if (jwt.length === 0 || identity.length === 0) {  
  49.             ToastsStore.error("Issue to fetch token!");  
  50.           } else {  
  51.             this.setState({ userName: currentUserName });  
  52.             this.joinRoom();  
  53.           }  
  54.         });  
  55.     });  
  56.   }  
  57.   
  58.   joinRoom() {  
  59.     if (!this.state.roomName.trim()) {  
  60.       this.setState({ roomNameErr: true });  
  61.       return;  
  62.     }  
  63.   
  64.     console.log("Joining room '" + this.state.roomName + "'...");  
  65.     let connectOptions = {  
  66.       name: this.state.roomName  
  67.     };  
  68.   
  69.     if (this.state.previewTracks) {  
  70.       connectOptions.tracks = this.state.previewTracks;  
  71.     }  
  72.   
  73.     // Join the Room with the token from the server and the  
  74.     // LocalParticipant's Tracks.  
  75.     Video.connect(this.state.jwt, connectOptions).then(this.roomJoined, error => {  
  76.       ToastsStore.error('Please verify your connection of webcam!');  
  77.       ToastsStore.error('Webcam-Video permission should not block!');  
  78.     });  
  79.   }  
  80.   
  81.   attachTracks(tracks, container) {  
  82.     tracks.forEach(track => {  
  83.       container.appendChild(track.attach());  
  84.     });  
  85.   }  
  86.   
  87.   // Attaches a track to a specified DOM container  
  88.   attachParticipantTracks(participant, container) {  
  89.     var tracks = Array.from(participant.tracks.values());  
  90.     this.attachTracks(tracks, container);  
  91.   }  
  92.   
  93.   detachTracks(tracks) {  
  94.     tracks.forEach(track => {  
  95.       track.detach().forEach(detachedElement => {  
  96.         detachedElement.remove();  
  97.       });  
  98.     });  
  99.   }  
  100.   
  101.   detachParticipantTracks(participant) {  
  102.     var tracks = Array.from(participant.tracks.values());  
  103.     this.detachTracks(tracks);  
  104.   }  
  105.   
  106.   roomJoined(room) {  
  107.     // Called when a participant joins a room  
  108.     console.log("Joined as '" + this.state.identity + "'");  
  109.     this.setState({  
  110.       activeRoom: room,  
  111.       localMediaAvailable: true,  
  112.       hasJoinedRoom: true  
  113.     });  
  114.   
  115.     // Attach LocalParticipant's Tracks, if not already attached.  
  116.     var previewContainer = this.refs.groupChat_localMedia;  
  117.     console.log('previewContainer.querySelector(video)', previewContainer.querySelector('.video'));  
  118.   
  119.     if (!previewContainer.querySelector('.video')) {  
  120.       this.attachParticipantTracks(room.localParticipant, this.refs.groupChat_localMedia);  
  121.     }  
  122.   
  123.     // Attach the Tracks of the Room's Participants.  
  124.     room.participants.forEach(participant => {  
  125.       console.log("Already in Room: '" + participant.identity + "'");  
  126.       this.setState({  
  127.         peerIdentity: participant.identity  
  128.       })  
  129.       var previewContainer = this.refs.remoteMedia;  
  130.       this.attachParticipantTracks(participant, previewContainer);  
  131.     });  
  132.   
  133.     // When a Participant joins the Room, log the event.  
  134.     room.on('participantConnected', participant => {  
  135.       console.log("Joining: '" + participant.identity + "'");  
  136.       this.setState({  
  137.         peerIdentity: participant.identity,  
  138.         partnerConnected: true  
  139.       })  
  140.     });  
  141.   
  142.     // When a Participant adds a Track, attach it to the DOM.  
  143.     room.on('trackAdded', (track, participant) => {  
  144.       console.log(participant.identity + ' added track: ' + track.kind);  
  145.       var previewContainer = this.refs.remoteMedia;  
  146.       this.attachTracks([track], previewContainer);  
  147.     });  
  148.   
  149.     // When a Participant removes a Track, detach it from the DOM.  
  150.     room.on('trackRemoved', (track, participant) => {  
  151.       console.log(participant.identity + ' removed track: ' + track.kind);  
  152.       this.detachTracks([track]);  
  153.     });  
  154.   
  155.     // When a Participant leaves the Room, detach its Tracks.  
  156.     room.on('participantDisconnected', participant => {  
  157.       console.log("Participant '" + participant.identity + "' left the room");  
  158.       this.detachParticipantTracks(participant);  
  159.     });  
  160.   
  161.     // Once the LocalParticipant leaves the room, detach the Tracks  
  162.     // of all Participants, including that of the LocalParticipant.  
  163.     room.on('disconnected', () => {  
  164.       if (this.state.previewTracks) {  
  165.         this.state.previewTracks.forEach(track => {  
  166.           track.stop();  
  167.         });  
  168.       }  
  169.       this.detachParticipantTracks(room.localParticipant);  
  170.       room.participants.forEach(this.detachParticipantTracks);  
  171.       this.state.activeRoom = null;  
  172.       this.setState({ hasJoinedRoom: false, localMediaAvailable: false });  
  173.     });  
  174.   }  
  175.   
  176.   leaveRoom() {  
  177.     this.state.activeRoom.disconnect();  
  178.     this.setState({ hasJoinedRoom: false, localMediaAvailable: false, peerIdentity: '' });  
  179.   }  
  180.   
  181.   render() {  
  182.   
  183.     /* Hide 'Join Room' button if user has already joined a room */  
  184.     let joinOrLeaveRoomButton = this.state.hasJoinedRoom ? (  
  185.       <button className="btn btn-warning" onClick={this.leaveRoom} > Leave Room</button>  
  186.     ) : (  
  187.         <button className="btn btn-success ml-2" onClick={this.getTwillioToken} >Join Room</button>  
  188.       );  
  189.     /** */  
  190.   
  191.     return (  
  192.       <React.Fragment>  
  193.         <div className="container">  
  194.           <div className="row mt-3">  
  195.             <div className="col-6">  
  196.               <div className="card">  
  197.                 <div className="card-body">  
  198.                   <div ref="groupChat_localMedia"></div>  
  199.                   <div className="text-center">  
  200.                     {!this.state.hasJoinedRoom && <Loader type="Puff" color="#00BFFF" />}  
  201.                   </div>  
  202.                 </div>  
  203.                 <div className="card-footer">{this.state.hasJoinedRoom ? <button className="btn btn-warning" onClick={this.leaveRoom} > Leave Room</button> : <span> </span>}</div>  
  204.               </div>  
  205.             </div>  
  206.             <div className="col-6">  
  207.               <div className="card">  
  208.                 <div className="card-body">  
  209.                   <div ref="remoteMedia"></div>  
  210.                   <div className="text-center">  
  211.                     {!this.state.hasParticipantsJoinedRoom && !this.state.peerIdentity && <Loader type="Puff" color="#00BFFF" />}  
  212.                   </div>  
  213.                 </div>  
  214.                 <div className="card-footer text-center">  
  215.                   {(!this.state.hasParticipantsJoinedRoom && !this.state.peerIdentity) ? <span>Wait for peer user to connect channel  !!!</span> : <span>Peer User Name : {`${this.state.peerIdentity}`}</span >}  
  216.                 </div>  
  217.               </div>  
  218.             </div>  
  219.           </div>  
  220.         </div>  
  221.         <ToastsContainer store={ToastsStore} />  
  222.       </React.Fragment>  
  223.     )  
  224.   }  
  225. }  
  226.   
  227. export default App;   
TwilioProgrammableVideo.gif
 
For More Details: