Building Real-Time Communication: Harnessing WebRTC with FastAPI Part 2
Wassaf Shahzasd
Posted on March 10, 2024
Welcome to the second part of my series where we will be building a google-meet-clone using FastAPI and WebRTC. If you haven't read the previous article you can read it here.
In this tutorial we will be going through the ๐ป meat of things.
๐ฎ On the last episode of DBZ
When we last left, we created a basic hello world app in FastAPI and provided an introductory overview of how WebRTC functions.
Our Folder structure looked something like this.
๐
|-- ๐ main.py
|-- ๐ requirements.py
|-- ๐ .env
๐ผ Business at the Front.
So first lets get started with front-end. As described in the previous tutorial we will be using Jinja templates. So create a templates folder in your root directory. The folder structure then becomes the following.
๐
|-- ๐ main.py
|-- ๐ requirements.py
|-- ๐ templates
|-- ๐ .env
You can name this folder whatever you want but for consistency, I will be using templates. You can get an overview of jinja template from here. Don't worry about python dependencies, we handled that in the previous tutorial.
Now create a main.html
file in templates directory. This will be our entry point and any JS or CSS dependencies or stylesheets will be loaded here.
Paste to following code in main.html
, We will go over the important part line by line.
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title>PeerChat</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
{% block script %} {% endblock %}
<link href=" https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
<body>
{% block content %} {% endblock %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
The first important part is the {% block script %} {% endblock %}
.
This is a Jinja inheritance, which allows us to use this main.html
as a parent for other templates. Since each child template will need to load its on scripts tags, we use this feature to dynamically load those. You can find more info here.
Other then that we are just loading some bootstrap dependencies and creating a block for out content using jinja inheritance mentioned above.
๐ Creating the Home Page
We will be creating a simple Home page with a title, input field and button.
For this create a home.html
inside the the template directory.
๐
|-- ๐ main.py
|-- ๐ requirements.py
|-- ๐ templates
|-- |-- main.html
|-- |-- home.html
|-- ๐ .env
with the following code.
{% extends "main.html" %}
{% block script %}
<link rel='stylesheet' type='text/css' media='screen' href="{{ url_for('static', path='/home.css') }}">
<script src="{{ url_for('static', path='/home.js') }}""></script>
{% endblock %}
{% block content %}
<div class="main-container">
<div>
<h1>Video Call for Every One</h1>
<div class="input-row">
<div>
<button type="button" class="btn btn-success" onclick="createRoom()">Create room</button>
</div>
<div class="input-container">
<input id="createRoom" class="form-control" placeholder="Enter room name">
</div>
</div>
</div>
</div>
{% endblock %}
Don't worry about the CSS or the JS, we will create that later.
Know open up the ๐ main.py
and use the following code to load and return templates
Add the following imports
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.templating import Jinja2Templates
We will be using Jinja2Templates
here to load the templates.
The following code loads the templates
object from the templates directory.
templates = Jinja2Templates(directory="templates")
Add the following code to enable CORS Middleware. Since we will be making requests moving forward.
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Now lets update the read_root function and rename it as well while we are at it.
@app.get("/")
def home(request: Request):
return templates.TemplateResponse(request=request, name="home.html")
Run the fast application and you should get the following page.
๐งฉ Adding Static files
As you can see we need to add a bit of styling and some onclick functionality to our home page. For that we will be using the static files.
Static files are files which are downloaded by the client on first load. Unlike templates which are sent from the server on a per request basis, static files are automatically downloading by the user client (browser) when the first connection is made.
Create a static directory in your root folder. The folder structure becomes something like this.
๐
|-- ๐ main.py
|-- ๐ requirements.py
|-- ๐ static
|-- ๐ templates
|-- |-- main.html
|-- |-- home.html
|-- ๐ .env
To load static files from the server, add the following code to main.py
from fastapi import staticfiles
app.mount("/static", staticfiles.StaticFiles(directory="static"), name="static")
In the above code we are telling out application to mount the static directory under /static
url.
Create a home.css, home.js file under the static directory and add the following code.
for home.css
.main-container {
display: flex;
flex-direction: column;
align-items: center;
}
.main-container * {
padding-top: 5%;
padding-bottom: 5%;
}
.input-row {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.input-container {
width: 70%;
}
In home.js, add the following
let createRoom = () => {
const roomName = document.getElementById("createRoom").value
if (roomName){
window.location.href = `/room/${roomName}`
}
else {
alert("Room Name cannot be empty")
}
}
The above is just a JS funtion which tells the browser to redirect to /room/roomName
where roomName is the value given in the input field.
Update home.html with the following code
{% extends "main.html" %}
{% block script %}
<link rel='stylesheet' type='text/css' media='screen' href="{{ url_for('static', path='/home.css') }}">
<script src="{{ url_for('static', path='/home.js') }}""></script>
{% endblock %}
{% block content %}
<div class="main-container">
<div>
<h1>Video Call for Every One</h1>
<div class="input-row">
<div>
<button type="button" class="btn btn-success" onclick="createRoom()">Create room</button>
</div>
<div class="input-container">
<input id="createRoom" class="form-control" placeholder="Enter room name">
</div>
</div>
</div>
</div>
{% endblock %}
Here we update the onclick button property with out function and the script block with the valid static file paths. Now reload the fast app and you Would get Something like this.
๐งจ Setting up the Video Page.
Create a new template called video.html
and paste the following code.
{% extends "main.html" %}
{% block script %}
<link rel='stylesheet' type='text/css' media='screen' href="{{ url_for('static', path='/index.css') }}">
<script src="{{ url_for('static', path='/index.js') }}""></script>
{% endblock %}
{% block content %}
<div id="videos">
<video class="video-player" id="user-1" autoplay></video>
<video class="video-player" id="user-2" autoplay></video>
</div>
{% endblock %}
Here 2 video players for people who will join the video call.
Now for the css. Create a video.css in the static
folder and paste the following code.
.main-container {
display: flex;
flex-direction: column;
align-items: center;
}
.main-container * {
padding-top: 5%;
padding-bottom: 5%;
}
.input-row {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.input-container {
width: 70%;
}
I think this is good enough for now. In the next tutorial we will wrapping up the RTCpeerConnection logic and the singling server logic.
Posted on March 10, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.