Create a React Native Video Calling App Using Twilio

Introduction

In this post, we are going to build a React Native Android app for Video calls using Twilio.
Let’s get started with Twilio.

What is Twilio?

Twilio is a cloud based service that enables powerful communication between mobile devices, applications, services, and systems throughout the business in order to bridge the gap between conventional communication.

For that, please create a Twilio account by clicking here.

When you create your account successfully you will be redirected to Dashboard. Now we need the ACCOUNT SID for our application to generate a token as shown below.

To generate a token we also need an API key and a secret key. For that go to Settings > API keys.

Click on plus icon to generate API keys as shown below.

Provide a name and click on Create API Key.

You will get the API key and Secret key as shown below.

You can generate a token by using the code below which uses an npm package.

npm install twilio --save

We can also generate tokens in different languages. For that, we can follow this link.  We need to select the video option on the page there. We are using the same code here.


const AccessToken = require('twilio').jwt.AccessToken;
const VideoGrant = AccessToken.VideoGrant;

var account_sid = '<Your-Account-SID>'
var api_key = '<Generated-API-Key>'
var api_secret = '<Generated-Secret-Key>'

var identity = <New-Identity>

const videoGrant = new VideoGrant({
  room: '<Room-Name>',
});

const token = new AccessToken(account_sid, api_key, api_secret);
token.addGrant(videoGrant);
token.identity = identity;

console.log(token.toJwt());

For every token, we need to change the identity value. We cannot use the same token at two different places.

The other way to create a token is by using Twilio tools by following this link, which resembles the image shown below.

To fetch the token we must provide an identity and a room name.
This was all about the Twilio part of the app.

Installing Dependencies: 

Here we are using the React Native starter kit which we can clone by using this github link. 
Run the below command in the terminal project directory.

npm install https://github.com/blackuy/react-native-twilio-video-webrtc --save

And replace the code in App.js file with the below code.

App.js:

import React, {
    Component
} from 'react';
import {
    StyleSheet,
    Text,
    TextInput,
    View,
    Button,
    TouchableOpacity,
    PermissionsAndroid, TouchableHighlight
} from 'react-native';
import {
    TwilioVideoLocalView, // to get local view 
    TwilioVideoParticipantView, //to get participant view
    TwilioVideo
} from 'react-native-twilio-video-webrtc';
// make sure you install vector icons and its dependencies
import MIcon from 'react-native-vector-icons/MaterialIcons';
import normalize from 'react-native-normalize';
import MCIcon from 'react-native-vector-icons/MaterialCommunityIcons';
import { widthPercentageToDP as wp, heightPercentageToDP as hp } from 'react-native-responsive-screen';
export async function GetAllPermissions() {
  // it will ask the permission for user 
  try {
    // if (Platform.OS === "android") {
      const userResponse = await PermissionsAndroid.requestMultiple([
        PermissionsAndroid.PERMISSIONS.CAMERA,
        PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
        PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
      ]);
      return userResponse;
    // }
  } catch (err) {
    console.log(err);
  }
  return null;
}
export default class Example extends Component {
    state = {
        isAudioEnabled: true,
        isVideoEnabled: true,
        isButtonDisplay: true,
        status: 'disconnected',
        participants: new Map(),
        videoTracks: new Map(),
        roomName: '',
        token: '',
    }
    
    componentDidMount() {
      // on start we are asking the permisions
      GetAllPermissions();
    }

_onConnectButtonPress = () => {
    console.log("in on connect button preess");
    this.refs.twilioVideo.connect({ roomName: this.state.roomName, accessToken: this.state.token})
    this.setState({status: 'connecting'})
    console.log(this.state.status);
  }

  _onEndButtonPress = () => {
    this.refs.twilioVideo.disconnect()
  }

  _onMuteButtonPress = () => {
    // on cliking the mic button we are setting it to mute or viceversa
    this.refs.twilioVideo.setLocalAudioEnabled(!this.state.isAudioEnabled)
      .then(isEnabled => this.setState({isAudioEnabled: isEnabled}))
  }

  _onFlipButtonPress = () => {
    // switches between fronst camera and Rare camera
    this.refs.twilioVideo.flipCamera()
  }
_onRoomDidConnect = () => {
    console.log("room did connected");
    this.setState({status: 'connected'})
    // console.log("over");
  }

  _onRoomDidDisconnect = ({roomName, error}) => {
    console.log("ERROR: ", JSON.stringify(error))
    console.log("disconnected")
    
    this.setState({status: 'disconnected'})
  }

  _onRoomDidFailToConnect = (error) => {
    console.log("ERROR: ", JSON.stringify(error));
    console.log("failed to connect");
    this.setState({status: 'disconnected'})
  }

  _onParticipantAddedVideoTrack = ({participant, track}) => {
    // call everytime a participant joins the same room
    console.log("onParticipantAddedVideoTrack: ", participant, track)

    this.setState({
      videoTracks: new Map([
        ...this.state.videoTracks,
        [track.trackSid, { participantSid: participant.sid, videoTrackSid: track.trackSid }]
      ]),
    });
    
    console.log("this.state.videoTracks", this.state.videoTracks);
  }

  _onParticipantRemovedVideoTrack = ({participant, track}) => {
    // gets called when a participant disconnects.
    console.log("onParticipantRemovedVideoTrack: ", participant, track)

    const videoTracks = this.state.videoTracks
    videoTracks.delete(track.trackSid)

    this.setState({videoTracks: { ...videoTracks }})
  }
   render() {
        return (
        <View style={styles.container} >
        {
            this.state.status === 'disconnected' &&
            <View>
                <Text style={styles.welcome}>
                React Native Twilio Video
                </Text>
                <View style={styles.spacing}>
                      <Text style={styles.inputLabel}>Room Name</Text>
                      <TextInput style={styles.inputBox}
                      placeholder="Room Name"
                      defaultValue={this.state.roomName}
                      onChangeText={(text) => this.setState({roomName: text})}
                      />
                  </View>
                <View style={styles.spacing}>
                      <Text style={styles.inputLabel}>Token</Text>
                      <TextInput style={styles.inputBox}
                      placeholder="Token"
                      defaultValue={this.state.token}
                      onChangeText={(text) => this.setState({token: text})}
                      />
                  </View>
                <TouchableHighlight style={[styles.buttonContainer, styles.loginButton]} onPress={this._onConnectButtonPress}>
                    <Text style={styles.Buttontext}>Connect</Text>
                </TouchableHighlight>
            </View>
        }

        {
          (this.state.status === 'connected' || this.state.status === 'connecting') &&
            <View style={styles.callContainer}>
            {
              this.state.status === 'connected' &&
              <View style={styles.remoteGrid}>
                <TouchableOpacity style = {styles.remoteVideo} onPress={()=>{this.setState({isButtonDisplay:!this.state.isButtonDisplay})}} >
                {
                  Array.from(this.state.videoTracks, ([trackSid, trackIdentifier]) => {
                    return (
                        <TwilioVideoParticipantView
                          style={styles.remoteVideo}
                          key={trackSid}
                          trackIdentifier={trackIdentifier}
                        />
                    )
                  })
                }
                </TouchableOpacity>
                <TwilioVideoLocalView
                  enabled={true}
                  style = {this.state.isButtonDisplay ? styles.localVideoOnButtonEnabled : styles.localVideoOnButtonDisabled} 
                />
              </View>
            }
            <View
              style = {
                {
                  display: this.state.isButtonDisplay ? "flex" : "none",
                  position: "absolute",
                  left: 0,
                  bottom: 0,
                  right: 0,
                  height: 100,
                  flexDirection: "row",
                  alignItems: "center",
                  justifyContent: "space-evenly",
                  // backgroundColor:"blue",
                  // zIndex: 2,
                  zIndex: this.state.isButtonDisplay ? 2 : 0,
                }
              } >
              <TouchableOpacity
                style={
                    {
                      display: this.state.isButtonDisplay ? "flex" : "none",
                      width: 60,
                      height: 60,
                      marginLeft: 10,
                      marginRight: 10,
                      borderRadius: 100 / 2,
                      backgroundColor: 'grey',
                      justifyContent: 'center',
                      alignItems: "center"
                    }
                  }
                onPress={this._onMuteButtonPress}>
                < MIcon name ={this.state.isAudioEnabled ? "mic" : "mic-off"} size={24} color='#fff' />
              </TouchableOpacity>
               <TouchableOpacity
                style={
                    {
                      display: this.state.isButtonDisplay ? "flex" : "none",
                      width: 60,
                      height: 60,
                      marginLeft: 10,
                      marginRight: 10,
                      borderRadius: 100 / 2,
                      backgroundColor: 'grey',
                      justifyContent: 'center',
                      alignItems: "center"
                    }
                  }
                onPress={this._onEndButtonPress}>
                {/* <Text style={{fontSize: 12}}>End</Text> */}
                < MIcon name = "call-end" size={28} color='#fff' />
              </TouchableOpacity>
              <TouchableOpacity
                style={
                    {
                      display: this.state.isButtonDisplay ? "flex" : "none",
                      width: 60,
                      height: 60,
                      marginLeft: 10,
                      marginRight: 10,
                      borderRadius: 100 / 2,
                      backgroundColor: 'grey',
                      justifyContent: 'center',
                      alignItems: "center"
                    }
                  }
                onPress={this._onFlipButtonPress}>
                {/* <Text style={{fontSize: 12}}>Flip</Text> */}
                < MCIcon name = "rotate-3d" size={28} color='#fff' />
              </TouchableOpacity>
            </View>
          
          </View>
        }
        <TwilioVideo
          ref="twilioVideo"
          onRoomDidConnect={ this._onRoomDidConnect }
          onRoomDidDisconnect={ this._onRoomDidDisconnect }
          onRoomDidFailToConnect= { this._onRoomDidFailToConnect }
          onParticipantAddedVideoTrack={ this._onParticipantAddedVideoTrack }
          onParticipantRemovedVideoTrack= { this._onParticipantRemovedVideoTrack }
        />
        </View>
        )
    }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'white'
  },
  callContainer: {
    flex: 1,
    position: "absolute",
    bottom: 0,
    top: 0,
    left: 0,
    right: 0,
    minHeight:"100%"
  },
  welcome: {
    fontSize: 30,
    textAlign: 'center',
    paddingTop: 40
  },
  input: {
    height: 50,
    borderWidth: 1,
    marginRight: 70,
    marginLeft: 70,
    marginTop: 50,
    textAlign: 'center',
    backgroundColor: 'white'
  },
  button: {
    marginTop: 100
  },
  localVideoOnButtonEnabled: {
    bottom: ("40%"),
    width: "35%",
    left: "64%",
    height: "25%",
    zIndex: 2,
  },
  localVideoOnButtonDisabled: {
    bottom: ("30%"),
    width: "35%",
    left: "64%",
    height: "25%",
    zIndex: 2,
  },
  remoteGrid: {
    flex: 1,
    flexDirection: "column",
  },
  remoteVideo: {
    width: wp("100%"),
    height: hp("100%"),
    zIndex: 1,
  },
  optionsContainer: {
    position: "absolute",
    left: 0,
    bottom: 0,
    right: 0,
    height: 100,
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "space-evenly",
    zIndex: 2,
  },
  optionButton: {
    width: 60,
    height: 60,
    marginLeft: 10,
    marginRight: 10,
    borderRadius: 100 / 2,
    backgroundColor: 'grey',
    justifyContent: 'center',
    alignItems: "center"
  },
  spacing: {
    padding: 10
  },
  inputLabel: {
    fontSize: 18
  },
  buttonContainer: {
    height: normalize(45),
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    marginBottom: 20,
    width: wp('90%'),
    borderRadius: 30,
  },
  loginButton: {
    backgroundColor: "#1E3378",
    width: wp('90%'),
    justifyContent: 'center',
    alignItems: 'center',
    marginLeft: 20,
    marginTop: 10
  },
  Buttontext: {
    color: 'white',
    fontWeight: '500',
    fontSize: 18
  },
  inputBox: {
    borderBottomColor: '#cccccc',
    fontSize: 16,
    width: wp("95%"),
    borderBottomWidth:1
  },
});

We must make sure that we install all the dependencies required by executing the following command inside the project directory.

npm install

Before running this app we need to make some configurations to use Twilio and use camera and audio.

To make those goto android folder and in settings.gradle file add the following two lines.

include ':react-native-twilio-video-webrtc'
project(':react-native-twilio-video-webrtc').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-twilio-video-webrtc/android')

In android > app > build.gradle file , Search for dependencies and add the below code inside the block.

compile project(':react-native-twilio-video-webrtc')

In android > app > src > main > java > com > reactnativestarter > MainApplication.java add the below line.

import com.twiliorn.library.TwilioPackage;

And replace the getPackages() method with below code.

@Override
   protected List<ReactPackage> getPackages() {
     @SuppressWarnings("UnnecessaryLocalVariable")
     List<ReactPackage> packages = new PackageList(this).getPackages();
 
     // Packages that cannot be autolinked yet can be added manually here, for example:
     // packages.add(new MyReactNativePackage());
     packages.add(new TwilioPackage());
 
     return packages;
   }

Now we need to modify AndroidManifest.xml for requesting the permissions to the user.
Add the below code in AndroidManifest.xml file.

 <uses-permission android:name="android.permission.CAMERA" />
   <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
   <uses-permission android:name="android.permission.RECORD_AUDIO" />
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
   <uses-feature android:name="android.hardware.camera" android:required="false" />
   <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
   <uses-feature android:name="android.hardware.microphone" android:required="false" />

And also make sure that the client side room creation is enabled in your twilio account as shown in the below image.

Running the App:

Run the application by executing the react-native run-android command from the terminal window. 

Below are the screenshots of the app running on two different android devices.

Thanks for the read!

This story is authored by Dheeraj Kumar and Santosh Kumar. Dheeraj is a software engineer specializing in React Native and React based frontend development. Santosh specializes on Cloud Services based development.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.