In this guide we will build a video chat application using python+flask in the back-end and React + WebRTC and Metered Video SDK in front-end to build a video calling application.
Our video chat application would allow users to have group video chat, with the ability to share their screen.
The application would run on all modern browsers as well iOS Safari and in Android web browser.
Prerequisite
To build the application we would use Metered Video API and SDK, if you don't have an account, you can signup for account.
Go to https://www.metered.ca/ and click "Signup and Start Building" button.
After you have create the account, come back here for the next steps.
Application Structure - Backend
Our application would have Python+Flask backend and React Front-End, the backend would provide API to our front-end React Application.
The Application structure of our backend code is very simple, as shown in the screenshot below.
We are creating a simple flask application, the project directory contains
flaskr/
- This folder will contain the python code of our flask application
__init__.py
- This file contains our Python+Flask Application Code.
venv
- Virtual environment folder created using the venv command
.env
- This file contains our METERED_SECRET_KEY
AND METERED_DOMAIN
(I will share more info on how to obtain those below)
requirements.txt
- Contains a list of python dependencies required for our project
Building the Backend
We will first build out our Python+Flask backend and then move on to building our front-end using React.
In the backend we will build the our API's that will be required by our front-end application. We will call the Metered REST API from the backend.
We do not want to call the Metered REST API directly from our front-end application because we do not want to expose our METERED_SECRET_KEY in the front-end.
Installing Dependencies
We will use virtual environment to manage dependencies, we will create our project directory and initialize the virtual environment in the project directory.
mkdir myapp
cd myapp
mkdir backend
cd backend
python3 -m venv venv
Create file requirements.txt
and add the following
flask
requests
python-dotenv
flask-cors
Run the command to install dependencies
pip install -r requirements.txt
Creating .env file
Create a .env
in the root of your project directory and add the following
export FLASK_APP=./flaskr
export METERED_DOMAIN=yourappname.metered.live
export METERED_SECRET_KEY=hoHqpIkn8MqONVIZvwHReHt8tm_6K0SRMgg6vHwPrBoKz
To obtain your METERED_DOMAIN
and METERED_SECRET_KEY
go to Metered Dashboard -> Developers
Building the Backend REST API
We will create a file named __init__.py
inside the flaskr/
folder.
This file will contain our flask code with our REST API that would be needed by our front-end React Application.
We need our backend service to provide primarily 2 services:
- Able to create a new meeting room
- Validate existing meeting room
So we will be creating the following routes:
/api/create/room
- This Endpoint will allow us to create a new meeting room and get the ID of the meeting room/api/validate-meeting
- This Endpoint will accept the roomId and will check if the room exists or not/api/metered-domain
- We will use this endpoint to fetch our Metered Domain from the backed. This is as optional endpoint, you can directly add the Metered Domain in your front-end application, but we are creating an endpoint for flexibility.
Here is the boilerplate code for our backend server, we will go through each route and build it as we go along.
Creating API to Create a Meeting Room
We will use the Metered Create Room API to create a meeting room. Which is /api/v1/room
This endpoint returns the following response
{
"__v": 0,
"_id": "62a1218be0a28612ff36a9f5",
"app": "61002fccfa1937440e5d1134",
"archived": false,
"audioOnlyRoom": false,
"autoJoin": false,
"compositionLayout": "grid",
"compositionOrientation": "wide",
"created": "2022-06-08T22:24:11.259Z",
"deleteOnExp": false,
"ejectAtRoomExp": false,
"enableChat": true,
"enableComposition": false,
"enableLiveStreaming": false,
"enableRTMPOut": false,
"enableRecording": false,
"enableRequestToJoin": true,
"enableScreenSharing": true,
"enableWatermark": false,
"joinAudioOn": true,
"joinVideoOn": true,
"lang": "en",
"newChatForMeetingSession": true,
"ownerOnlyBroadcast": false,
"privacy": "public",
"recordComposition": false,
"recordRoom": false,
"roomName": "jfbkg78pca",
"showInviteBox": true,
"watermarkPosition": "bottom_right"
}
For us roomName
is the property of interest, each time we will call this API, and if we do not provide a roomName
it will create a new room with a unique room name.
If we specify the roomName
then it will create a new room of the specified roomName.
But for our use case, the unqiue auto-generated roomName is sufficient.
Creating an API to Validate a Meeting Room
After we have created a meeting room, we need an API to validate the Meeting Room.
This endpoint will be used validate the room name entered by the user when they are trying to join a room.
Using the API we will check if the room is valid, and if it is valid then we will allow the user to join the room.
# API Route to validate meeting
@app.route("/api/validate-meeting")
def validate_meeting():
roomName = request.args.get("roomName")
if roomName:
r = requests.get("https://" + METERED_DOMAIN + "/api/v1/room/" +
roomName + "?secretKey=" + METERED_SECRET_KEY)
data = r.json()
if (data.get("roomName")):
return {"roomFound": True}
else:
return {"roomFound": False}
else:
return {
"success": False,
"message": "Please specify roomName"
}
API to Fetch Metered Domain
The API to fetch Metered Domain is very straightforward, we will just send the METERED_DOMAIN
variable as response.
# API Route to fetch the Metered Domain
@app.route("/api/metered-domain")
def get_metered_domain():
return {"METERED_DOMAIN": METERED_DOMAIN}
Putting it all together
Here is our final backend service __init__.py
Using Metered Pre-Built UI
Instead of building the custom front-end in React we can use the Metered Pre-built UI to Embed Video Chat into your web application.
Your roomURL is simply <your_metered_domain>.metered.live/<your_room_name
Each Room you create in Metered Video can be used with the pre-built UI. Just open the roomURL in your browser and you will be presented with the pre-built UI.
The Metered Pre-Built UI has built-in Chat, Video Calling, and Screen Sharing capabilities and the options can be enabled/disabled using dashboard or using the API.
To Embed the Pre-Built UI into an existing application you can use the following Embed Code.
Just replace the roomURL
with your own roomURL
.
<div id="metered-frame"></div>
<script src="https://cdn.metered.ca/sdk/frame/1.4.2/sdk-frame.min.js"></script>
<script>
var frame = new MeteredFrame();
frame.init({
roomURL: "<your-app-name>.metered.live/<your-room-name>",
}, document.getElementById("metered-frame"));
</script>
Build the Custom Front-End in React
If you choose to build you custom front-end in React then follow along.
Our front-end application would allow 3 main area:
- Join/Create Meeting: Here we will allow the user to join an existing meeting or create a new meeting
- Meeting Area: The main meeting interface
- Meeting Ended Screen: We will take the user to this area after the meeting has ended.
Installing the dependencies
We will use Create React App
to scaffold our single page React application.
cd myapp
npx create-react-app react-frontend
Scaffolding the Application UI
We will create 3 components one for each of the areas:
App.js
- Will be the main container of the application
Join.js
- UI to Join and Existing Meeting or Create a new Meeting
Meeting.js
- Will contain the main Meeting Screen
MeetingEnded.js
- Interface to show when the meeting ends
Including the Metered JavaScript SDK
We will include the latest Metered JavaScript in our application.
To add the Metered SDK open public/index.html
and paste the SDK before closing the of the head tag
<script src="//cdn.metered.ca/sdk/video/1.4.3/sdk.min.js"></script>
Initializing the SDK
We will initialize the Metered SDK in App.js
and handle all the meeting events in App.js
import { useEffect, useState } from "react";
import Join from "./Join";
import Meeting from "./Meeting";
// Initializing the SDK
const meteredMeeting = new window.Metered.Meeting();
function App() {
// Will set it to true when the user joins the meeting
// and update the UI.
const [meetingJoined, setMeetingJoined] = useState(false);
// Storing onlineUsers, updating this when a user joins
// or leaves the meeting
const [onlineUsers, setOnlineUsers] = useState([]);
// This useEffect hooks will contain all
// event handler, like participantJoined, participantLeft etc.
useEffect(() => {}, []);
// Will call the API to create a new
// room and join the user.
function handleCreateMeeting(username) {}
// Will call th API to validate the room
// and join the user
function handleJoinMeeting(roomName, username) {}
return (
<div className="App">
{meetingJoined ? (
<Meeting onlineUsers={onlineUsers} />
) : (
<Join
handleCreateMeeting={handleCreateMeeting}
handleJoinMeeting={handleJoinMeeting}
/>
)}
</div>
);
}
export default App;
Join Meeting Component
Lets build the Join Meeting Component, the Join Meeting component is very simple, it will allow the user to join an existing meeting by entering the roomName or creating a new meeting.
In the Join Meeting Component we are just handling the events and calls the props which has methods from the App Component, and the logic to handle "Join Existing Meeting" and "Create a new Meeting" will be handled in the App Component
Implementing Logic to Create and Join the Meeting
In the App.js
we will add the logic to handle the events triggered by pressing the "Join Existing Meeting" and "Create a New Meeting" buttons in the Join Component.
The logic to handleCreateMeeting
is very simple, we call our backend API /api/create/room
to create a room.
Then we call /api/metered-domain
to fetch our Metered Domain.
And finally we call the join
method of the Metered Javascript SDK.
// Will call the API to create a new
// room and join the user.
async function handleCreateMeeting(username) {
// Calling API to create room
const { data } = await axios.post(API_LOCATION + "/api/create/room");
// Calling API to fetch Metered Domain
const response = await axios.get(API_LOCATION + "/api/metered-domain");
// Extracting Metered Domain and Room Name
// From responses.
const METERED_DOMAIN = response.data.METERED_DOMAIN;
const roomName = data.roomName;
// Calling the join() of Metered SDK
const joinResponse = await meteredMeeting.join({
name: username,
roomURL: METERED_DOMAIN + "/" + roomName,
});
// Updating the state meetingJoined to true
setMeetingJoined(true);
}
join()
method you can read hereThe logic for handleJoinMeeting
is also very straightforward, here we already have the roomName
which will be provided by the user, we need to validate the roomName and if the roomName is valid then we will call the join method of the Metered JavaScript SDK.
// Will call th API to validate the room
// and join the user
async function handleJoinMeeting(roomName, username) {
// Calling API to validate the roomName
const response = await axios.get(
API_LOCATION + "/api/validate-meeting?roomName=" + roomName
);
if (response.data.roomFound) {
// Calling API to fetch Metered Domain
const { data } = await axios.get(API_LOCATION + "/api/metered-domain");
// Extracting Metered Domain and Room Name
// From responses.
const METERED_DOMAIN = data.METERED_DOMAIN;
// Calling the join() of Metered SDK
const joinResponse = await meteredMeeting.join({
name: username,
roomURL: METERED_DOMAIN + "/" + roomName,
});
setMeetingJoined(true);
} else {
alert("Invalid roomName");
}
}
To validate the roomName we will call our backend API /api/validate-meeting?roomName=
The we will be checking if the roomFound
is True, if it is True then we will fetch our Metered Domain and call the join()
method and update the meetingJoined
state variable.
Handling Events
We need to handle the following events in our application:
participantJoined
: When a paritcipant joins the meeting this event is triggered, we will add the user to the onlineUsers array.participantLeft
: When a participant leaves the meeting this event is triggered, we will remove the user from the onlineUsers array.remoteTrackStarted
: When a remote participant shares their camera/microphone/screen this event is emitted.remoteTrackStopped
: When a remote participant stops sharing their camera/microphone/screen this event is emitted.onlineParticipants
: This event is emitted multiple times during the lifecycle of the meeting. It contains that array of users currently in the meeting.
We will create a useEffect
hook and in the hook to handle the events and return a function that will do the cleanup of the event listener.
useEffect(() => {
meteredMeeting.on("remoteTrackStarted", (trackItem) => {});
meteredMeeting.on("remoteTrackStopped", (trackItem) => {});
meteredMeeting.on("participantJoined", (localTrackItem) => {});
meteredMeeting.on("participantLeft", (localTrackItem) => {});
meteredMeeting.on("onlineParticipants", (onlineParticipants) => {});
return () => {
meteredMeeting.removeListener("remoteTrackStarted");
meteredMeeting.removeListener("remoteTrackStopped");
meteredMeeting.removeListener("participantJoined");
meteredMeeting.removeListener("participantLeft");
meteredMeeting.removeListener("onlineParticipants");
};
});
We will create two array as state variables, one array will store the list of onlineParticipants and another array will store the list of remote video and audio tracks.
const [onlineUsers, setOnlineUsers] = useState([]);
const [remoteTracks, setRemoteTracks] = useState([]);
// This useEffect hooks will contain all
// event handler, like participantJoined, participantLeft etc.
useEffect(() => {
meteredMeeting.on("remoteTrackStarted", (trackItem) => {
remoteTracks.push(trackItem);
setRemoteTracks([...remoteTracks]);
});
meteredMeeting.on("remoteTrackStopped", (trackItem) => {
for (let i = 0; i < remoteTracks.length; i++) {
if (trackItem.streamId === remoteTracks[i].streamId) {
remoteTracks.splice(i, 1);
}
}
setRemoteTracks([...remoteTracks]);
});
meteredMeeting.on("participantJoined", (localTrackItem) => {});
meteredMeeting.on("participantLeft", (localTrackItem) => {});
meteredMeeting.on("onlineParticipants", (onlineParticipants) => {
setOnlineUsers([...onlineParticipants]);
});
return () => {
meteredMeeting.removeListener("remoteTrackStarted");
meteredMeeting.removeListener("remoteTrackStopped");
meteredMeeting.removeListener("participantJoined");
meteredMeeting.removeListener("participantLeft");
meteredMeeting.removeListener("onlineParticipants");
};
});
We can show a notification and play a sound when a participant enters or leaves the meeting in the participantJoined
and participantLeft
event handlers.
The onlineParticipants
event handler is triggered each time a participant enters or leaves and meeting and returns the array of participants, so we can use just that event handler to load the list of online participants.
The remoteTrackStarted
event handler we are just pushing the remoteTrack item to the remoteVideoTracks array and setting the state.
In the remoteTrackStopped
event handler, we are looping through the array to find the remoteTrackItem that was stoppped and and removing it from the array and setting the state.
Displaying the Remote Streams
We have handled the remoteTrackStarted
event and we are storing the remote tracks in the remoteTracks
state variable. The remote tracks can be played in a videoTag
.
The videoTag
has an srcObject
attribute and we can pass the MediaStream
to the srcObject
attribute the play the remote streams.
We will create a custom VideoTag
component that will accept our mediaStream as prop and create a videoTag with srcObject attribute and will play the video when the stream is ready.
Creating Component to Display MediaStream
The video and audio stream, can be added to a video tag, but they have to added to the srcObject
property, to handle this we will create our own <VideoTag />
component where we can provide srcObject as prop and it handles the reset.
This component is very simple, here we have created a useEffect
hook and in the hook we can see if srcObject prop has a value, if it has then we are assigning it to the video tag, and we are handling the onCanPlay
event emiited by the video tag, and when that event is emitted we are calling play()
method of the video tag.
Implementing the Meeting Area
Now we have added the logic to handle the onlineParticipants and their remote tracks, now let's build the Meeting
The Meeting Area is saved in the Meeting.js
file.
In the Meeting Area we will show the video/audio of the remote participants, add the ability to allow the user to share his/her microphone, camera and screen, and show the user their own video if they are sharing camera/screen.
In our App.js
component we will check if the user has joined the Meeting, if yes then we will show the Meeting component. If the user has not joined the meeting then we will show the Join Component.
We will also pass the onlineUsers
and remoteTracks
as props to the Meeting.js
component, and also methods to handle the camera, screen, microphone button click events.
We have scaffold out the Meeting.js
Component
import VideoTag from "./VideoTag";
function Meeting({
handleMicBtn,
handleCameraBtn,
handelScreenBtn,
handleLeaveBtn,
localVideoStream,
onlineUsers,
remoteTracks,
username,
roomName,
meetingInfo,
}) {
let userStreamMap = {};
for (let trackItem of remoteTracks) {
if (!userStreamMap[trackItem.participantSessionId]) {
userStreamMap[trackItem.participantSessionId] = [];
}
userStreamMap[trackItem.participantSessionId].push(trackItem);
}
let remoteParticipantTags = [];
for (let user of onlineUsers) {
// Skip if self
if (user._id === meetingInfo.participantSessionId) {
continue;
}
let videoTags = [];
if (userStreamMap[user._id] && userStreamMap[user._id].length > 0) {
// User has remote tracks
for (let trackItem of userStreamMap[user._id]) {
let stream = new MediaStream();
stream.addTrack(trackItem.track);
if (trackItem.type === "video") {
videoTags.push(<VideoTag srcObject={stream} />);
}
if (trackItem.type === "audio") {
videoTags.push(
<VideoTag
key={trackItem.streamId}
srcObject={stream}
style={{ display: "none" }}
/>
);
}
}
}
remoteParticipantTags.push(
<div key={user._id}>
<div id="remoteVideos">{videoTags}</div>
<div id="username">{user.name}</div>
</div>
);
}
return (
<div id="meetingView" className="flex flex-col">
<div className="h-8 text-center bg-black">MeetingID: {roomName}</div>
<div
className="flex-1 grid grid-cols-2 grid-rows-2"
id="remoteParticipantContainer"
style={{ display: "flex" }}
>
{remoteParticipantTags}
</div>
<div className="flex flex-col bg-base-300" style={{ width: "150px" }}>
{localVideoStream ? (
<VideoTag
id="meetingAreaLocalVideo"
muted={true}
srcObject={localVideoStream}
style={{
padding: 0,
margin: 0,
width: "150px",
height: "100px",
}}
/>
) : (
""
)}
<div
id="meetingAreaUsername"
className="bg-base-300 bg-black"
style={{
textAlign: "center",
}}
>
{username}
</div>
</div>
<div
style={{
display: "flex",
justifyContent: "center",
marginTop: "20px",
}}
className="space-x-4"
>
<button
id="meetingViewMicrophone"
className="btn"
onClick={handleMicBtn}
>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"
/>
</svg>
</button>
<button
id="meetingViewCamera"
className="btn"
onClick={handleCameraBtn}
>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
/>
</svg>
</button>
<button
id="meetingViewScreen"
className="btn"
onClick={handelScreenBtn}
>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
/>
</svg>
</button>
<button id="meetingViewLeave" className="btn" onClick={handleLeaveBtn}>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
/>
</svg>
</button>
</div>
</div>
);
}
export default Meeting;
Handling the sharing of camera, microphone and screen
In App.js
we create the methods to handle the click events on Microphone, Camera, Screen and Leave Meeting Buttons.
We will call the methods from the Metered Video SDK to handle the click events:
async function handleMicBtn() {
if (micShared) {
await meteredMeeting.stopAudio();
setMicShared(false);
} else {
await meteredMeeting.startAudio();
setMicShared(true);
}
}
async function handleCameraBtn() {
if (cameraShared) {
await meteredMeeting.stopVideo();
setLocalVideoStream(null);
setCameraShared(false);
} else {
await meteredMeeting.startVideo();
var stream = await meteredMeeting.getLocalVideoStream();
setLocalVideoStream(stream);
setCameraShared(true);
}
}
async function handelScreenBtn() {
if (!screenShared) {
await meteredMeeting.startScreenShare();
setScreenShared(false);
} else {
await meteredMeeting.stopVideo();
setCameraShared(false);
setScreenShared(true);
}
}
async function handleLeaveBtn() { }
Building Meeting Ended/Leave Meeting Screen
To build the Meeting Ended screen, we will create a state variable called meetingEnded
and in the handleLeaveBtn()
method we will set it to true, and call the leaveMeeting()
method of Metered Video SDK.
async function handleLeaveBtn() {
await meteredMeeting.leaveMeeting();
setMeetingEnded(true);
}
Then we will check if meetingEnded
is true
and if it is true
then we will hide the Meeting component and show the MeetingEnded.js
component instead.
That's it!
This is how our final App.js
file looks like:
import axios from "axios";
import { useEffect, useState } from "react";
import Join from "./Join";
import Meeting from "./Meeting";
import MeetingEnded from "./MeetingEnded";
// Initializing the SDK
const meteredMeeting = new window.Metered.Meeting();
const API_LOCATION = "http://localhost:5000";
function App() {
// Will set it to true when the user joins the meeting
// and update the UI.
const [meetingJoined, setMeetingJoined] = useState(false);
// Storing onlineUsers, updating this when a user joins
// or leaves the meeting
const [onlineUsers, setOnlineUsers] = useState([]);
const [remoteTracks, setRemoteTracks] = useState([]);
const [username, setUsername] = useState("");
const [localVideoStream, setLocalVideoStream] = useState(null);
const [micShared, setMicShared] = useState(false);
const [cameraShared, setCameraShared] = useState(false);
const [screenShared, setScreenShared] = useState(false);
const [meetingEnded, setMeetingEnded] = useState(false);
const [roomName, setRoomName] = useState(null);
const [meetingInfo, setMeetingInfo] = useState({});
// This useEffect hooks will contain all
// event handler, like participantJoined, participantLeft etc.
useEffect(() => {
meteredMeeting.on("remoteTrackStarted", (trackItem) => {
remoteTracks.push(trackItem);
setRemoteTracks([...remoteTracks]);
});
meteredMeeting.on("remoteTrackStopped", (trackItem) => {
for (let i = 0; i < remoteTracks.length; i++) {
if (trackItem.streamId === remoteTracks[i].streamId) {
remoteTracks.splice(i, 1);
}
}
setRemoteTracks([...remoteTracks]);
});
meteredMeeting.on("participantJoined", (localTrackItem) => {});
meteredMeeting.on("participantLeft", (localTrackItem) => {});
meteredMeeting.on("onlineParticipants", (onlineParticipants) => {
setOnlineUsers([...onlineParticipants]);
});
meteredMeeting.on("localTrackUpdated", (item) => {
const stream = new MediaStream(item.track);
setLocalVideoStream(stream);
});
return () => {
meteredMeeting.removeListener("remoteTrackStarted");
meteredMeeting.removeListener("remoteTrackStopped");
meteredMeeting.removeListener("participantJoined");
meteredMeeting.removeListener("participantLeft");
meteredMeeting.removeListener("onlineParticipants");
meteredMeeting.removeListener("localTrackUpdated");
};
});
// Will call the API to create a new
// room and join the user.
async function handleCreateMeeting(username) {
// Calling API to create room
const { data } = await axios.post(API_LOCATION + "/api/create/room");
// Calling API to fetch Metered Domain
const response = await axios.get(API_LOCATION + "/api/metered-domain");
// Extracting Metered Domain and Room Name
// From responses.
const METERED_DOMAIN = response.data.METERED_DOMAIN;
const roomName = data.roomName;
// Calling the join() of Metered SDK
const joinResponse = await meteredMeeting.join({
name: username,
roomURL: METERED_DOMAIN + "/" + roomName,
});
setUsername(username);
setRoomName(roomName);
setMeetingInfo(joinResponse);
setMeetingJoined(true);
}
// Will call th API to validate the room
// and join the user
async function handleJoinMeeting(roomName, username) {
// Calling API to validate the roomName
const response = await axios.get(
API_LOCATION + "/api/validate-meeting?roomName=" + roomName
);
if (response.data.roomFound) {
// Calling API to fetch Metered Domain
const { data } = await axios.get(API_LOCATION + "/api/metered-domain");
// Extracting Metered Domain and Room Name
// From responses.
const METERED_DOMAIN = data.METERED_DOMAIN;
// Calling the join() of Metered SDK
const joinResponse = await meteredMeeting.join({
name: username,
roomURL: METERED_DOMAIN + "/" + roomName,
});
setUsername(username);
setRoomName(roomName);
setMeetingInfo(joinResponse);
setMeetingJoined(true);
} else {
alert("Invalid roomName");
}
}
async function handleMicBtn() {
if (micShared) {
await meteredMeeting.stopAudio();
setMicShared(false);
} else {
await meteredMeeting.startAudio();
setMicShared(true);
}
}
async function handleCameraBtn() {
if (cameraShared) {
await meteredMeeting.stopVideo();
setLocalVideoStream(null);
setCameraShared(false);
} else {
await meteredMeeting.startVideo();
var stream = await meteredMeeting.getLocalVideoStream();
setLocalVideoStream(stream);
setCameraShared(true);
}
}
async function handelScreenBtn() {
if (!screenShared) {
await meteredMeeting.startScreenShare();
setScreenShared(false);
} else {
await meteredMeeting.stopVideo();
setCameraShared(false);
setScreenShared(true);
}
}
async function handleLeaveBtn() {
await meteredMeeting.leaveMeeting();
setMeetingEnded(true);
}
return (
<div className="App">
{meetingJoined ? (
meetingEnded ? (
<MeetingEnded />
) : (
<Meeting
handleMicBtn={handleMicBtn}
handleCameraBtn={handleCameraBtn}
handelScreenBtn={handelScreenBtn}
handleLeaveBtn={handleLeaveBtn}
localVideoStream={localVideoStream}
onlineUsers={onlineUsers}
remoteTracks={remoteTracks}
username={username}
roomName={roomName}
meetingInfo={meetingInfo}
/>
)
) : (
<Join
handleCreateMeeting={handleCreateMeeting}
handleJoinMeeting={handleJoinMeeting}
/>
)}
</div>
);
}
export default App;
Conclusion
We have successfully built the group video calling application with Python Backend and React front-end.
You can grab the complete source code from Github: https://github.com/metered-ca/python-react-video-chat-app
The application is also avaliable as Docker Containers:
Backend: https://hub.docker.com/r/metered/python-video-demo
Frontend: https://hub.docker.com/r/metered/react-video-demo
Metered TURN servers
- API: TURN server management with powerful API. You can do things like Add/ Remove credentials via the API, Retrieve Per User / Credentials and User metrics via the API, Enable/ Disable credentials via the API, Retrive Usage data by date via the API.
- Global Geo-Location targeting: Automatically directs traffic to the nearest servers, for lowest possible latency and highest quality performance. less than 50 ms latency anywhere around the world
- Servers in 12 Regions of the world: Toronto, Miami, San Francisco, Amsterdam, London, Frankfurt, Bangalore, Singapore,Sydney (Coming Soon: South Korea, Japan and Oman)
- Low Latency: less than 50 ms latency, anywhere across the world.
- Cost-Effective: pay-as-you-go pricing with bandwidth and volume discounts available.
- Easy Administration: Get usage logs, emails when accounts reach threshold limits, billing records and email and phone support.
- Standards Compliant: Conforms to RFCs 5389, 5769, 5780, 5766, 6062, 6156, 5245, 5768, 6336, 6544, 5928 over UDP, TCP, TLS, and DTLS.
- Multi‑Tenancy: Create multiple credentials and separate the usage by customer, or different apps. Get Usage logs, billing records and threshold alerts.
- Enterprise Reliability: 99.999% Uptime with SLA.
- Enterprise Scale: With no limit on concurrent traffic or total traffic. Metered TURN Servers provide Enterprise Scalability
- 50 GB/mo Free: Get 50 GB every month free TURN server usage with the Free Plan
- Runs on port 80 and 443
- Support TURNS + SSL to allow connections through deep packet inspection firewalls.
- Support STUN
- Supports both TCP and UDP