Skip to contentSkip to navigationSkip to topbar
Rate this page:
On this page

Get Started with Twilio Video Part 2: Creating the Frontend


(warning)

Warning

This documentation is for reference only. We are no longer onboarding new customers to Programmable Video. Existing customers can continue to use the product until December 5, 2026(link takes you to an external page).

We recommend migrating your application to the API provided by our preferred video partner, Zoom. We've prepared this migration guide(link takes you to an external page) to assist you in minimizing any service disruption.

This is the second part of the tutorial for getting started with Twilio Video using Python/Flask and JavaScript. In this part, you'll create a JavaScript frontend that can connect to the backend Flask web server you created in Part One. If you haven't already created a backend server, refer back to the server code and setup in Part One, and then come back to complete this section.

You can also view the completed code from both parts of the tutorial(link takes you to an external page) in GitHub.

Before beginning this tutorial, you should ensure you are using one of the supported browsers for the Twilio Video JavaScript SDK.


Create the HTML file

create-the-html-file page anchor

In the project's root directory (video_tutorial or whatever you named the project's main directory), create a templates directory. This is where you'll store your HTML files. To make the new directory, run the following command in your terminal:


_10
mkdir templates

In this application, you'll only need one HTML file. Create a new file called index.html in the templates directory. This file will contain the HTML structure for the video application, which should include:

  • A link to the Twilio Video JavaScript SDK CDN.
  • A form where a user can enter the name of the Room they'd like to join.
  • A <div> where Participants' videos will be displayed after they've joined.
  • A link to a main.js file, which is where you'll write the code for controlling the video application.

Add the following code to templates/index.html:


_17
<!DOCTYPE html>
_17
<html>
_17
<head>
_17
<meta charset="utf-8" />
_17
<title>Twilio Video Demo</title>
_17
{/* Twilio Video CDN */}
_17
<script src="https://sdk.twilio.com/js/video/releases/2.15.2/twilio-video.min.js"></script>
_17
</head>
_17
<body>
_17
<form id="room-name-form">
_17
Enter a Room Name to join: <input name="room_name" id="room-name-input" />
_17
<button type="submit">Join Room</button>
_17
</form>
_17
<div id="video-container"></div>
_17
<script src="static/main.js"></script>
_17
</body>
_17
</html>

Note that this index.html file links to version 2.15.2 of the Twilio Video JavaScript SDK. You can find the CDN link for the most recent version of the Twilio Video JavaScript SDK here(link takes you to an external page).

Next, you'll want to have your Flask server render this index.html template when someone goes to your web app, instead of showing the string "In progress!" as it does right now. In your app.py file, change the line that returns "In progress!" to return render_template("index.html").


_10
@app.route("/")
_10
def serve_homepage():
_10
return render_template("index.html")

If your server is not running, make sure you are in your virtual environment by running source venv/bin/activate (MacOS/Unix) or venv\Scripts\activate.bat (Windows). Then, start up the server by running the following command:


_10
python app.py

After the server starts, navigate to localhost:5000 in your browser. You should see the form with an input box and a submit button.


Start the JavaScript file

start-the-javascript-file page anchor

In index.html, you linked to a JavaScript file that doesn't exist yet. In this step, you'll create it and start adding code.

In a second terminal window, navigate to your main project directory, and create a new directory called static in the root directory of the project:


_10
mkdir static

Then, create a file called main.js within the static directory, and open that file in a text editor.

First, you'll create variables for the form, video container div, and input HTML elements, to identify them later. Copy and paste the following code into the static/main.js file:


_10
const form = document.getElementById("room-name-form");
_10
const roomNameInput = document.getElementById("room-name-input");
_10
const container = document.getElementById("video-container");

Next, you'll create a function called startRoom that you'll call when the form is submitted. Copy and paste the following code into the static/main.js file, under the variable declarations:


_20
const startRoom = async (event) => {
_20
// prevent a page reload when a user submits the form
_20
event.preventDefault();
_20
// hide the join form
_20
form.style.visibility = "hidden";
_20
// retrieve the room name
_20
const roomName = roomNameInput.value;
_20
_20
// fetch an Access Token from the join-room route
_20
const response = await fetch("/join-room", {
_20
method: "POST",
_20
headers: {
_20
Accept: "application/json",
_20
"Content-Type": "application/json",
_20
},
_20
body: JSON.stringify({ room_name: roomName }),
_20
});
_20
const { token } = await response.json();
_20
console.log(token);
_20
};

The startRoom function hides the Join Room form. Then it submits a request to the /join-room route with the value the user entered in room-name-input. The /join-room route either finds an existing Video room with that name or creates one, and gives back an Access Token that this participant can use to join the room.

For now, the code just retrieves the Access Token and prints the token to the console with console.log. In the next step, you'll use the token to join the video room.

At the bottom of the main.js file, below the startRoom function, add an event listener on the form so that when it's submitted, startRoom runs:


_10
form.addEventListener("submit", startRoom);

You can now navigate to localhost:5000 in your browser, enter a room name in the form, click "Join Room", and see the Access Token in the browser's console. (You'll need to open the the Developer Tools for your browser to see the console.)

Here's the full code for static/main.js so far:


_26
const form = document.getElementById("room-name-form");
_26
const roomNameInput = document.getElementById("room-name-input");
_26
const container = document.getElementById("video-container");
_26
_26
const startRoom = async (event) => {
_26
// prevent a page reload when a user submits the form
_26
event.preventDefault();
_26
// hide the join form
_26
form.style.visibility = "hidden";
_26
// retrieve the room name
_26
const roomName = roomNameInput.value;
_26
_26
// fetch an Access Token from the join-room route
_26
const response = await fetch("/join-room", {
_26
method: "POST",
_26
headers: {
_26
Accept: "application/json",
_26
"Content-Type": "application/json",
_26
},
_26
body: JSON.stringify({ room_name: roomName }),
_26
});
_26
const { token } = await response.json();
_26
console.log(token);
_26
};
_26
_26
form.addEventListener("submit", startRoom);

(information)

Info

This tutorial is using WebRTC Go rooms, which are free rooms that allow a maximum of two participants in the room at one time. If you request Access Tokens for more than the maximum number of participants in one room, you'll receive an error that there are too many participants in the room. This tutorial doesn't cover error handling in these cases — for now, if you run into this error, you can try joining a new room with a different name. You'll see the error appear in your browser's console if it happens.


Join a Twilio Video Room

join-a-twilio-video-room page anchor

Now that your application can retrieve an Access Token, you can use that token to join the video room in the browser. The Twilio Video JavaScript SDK has a connect method that you can call to connect to a Twilio Video room.

Copy and paste the following code underneath the startRoom function in static/main.js.


_10
const joinVideoRoom = async (roomName, token) => {
_10
// join the video room with the Access Token and the given room name
_10
const room = await Twilio.Video.connect(token, {
_10
room: roomName,
_10
});
_10
return room;
_10
};

This creates a function called joinVideoRoom, which passes the Access Token and an object of ConnectOptions to the Twilio video connect method. The only ConnectOption you pass in is the name of the room that you're connecting to.

The connect method returns a Promise(link takes you to an external page) that will eventually either resolve to a Room(link takes you to an external page) object or be rejected with an error(link takes you to an external page).

Then, call the joinVideoRoom function after you've retrieved the Access Token in the startRoom function. For now, you can console.log the video room object. In the next step, you'll start adding code to display participants' video streams.

Update the startRoom function with the following lines of code. Note that you're removing the console.log(token) line and replacing it with a call to the joinVideoRoom function:


_15
// fetch an Access Token from the join-room route
_15
const response = await fetch("/join-room", {
_15
method: "POST",
_15
headers: {
_15
Accept: "application/json",
_15
"Content-Type": "application/json",
_15
},
_15
body: JSON.stringify({ room_name: roomName }),
_15
});
_15
const { token } = await response.json();
_15
_15
// join the video room with the token
_15
const room = await joinVideoRoom(roomName, token);
_15
console.log(room);
_15
};

Now if you navigate to localhost:5000 in your browser, enter a room name, and click "Join Room", you'll connect to a Twilio Video room. If you open your browser's console, you should see a log line with the room object. You might also be prompted to grant browser access to your camera and microphone devices, because once you connect to a video room, the room will start receiving your audio and video data.

Here's the full main.js with these additions up to now.


_37
const form = document.getElementById("room-name-form");
_37
const roomNameInput = document.getElementById("room-name-input");
_37
const container = document.getElementById("video-container");
_37
_37
const startRoom = async (event) => {
_37
// prevent a page reload when a user submits the form
_37
event.preventDefault();
_37
// hide the join form
_37
form.style.visibility = "hidden";
_37
// retrieve the room name
_37
const roomName = roomNameInput.value;
_37
_37
// fetch an Access Token from the join-room route
_37
const response = await fetch("/join-room", {
_37
method: "POST",
_37
headers: {
_37
Accept: "application/json",
_37
"Content-Type": "application/json",
_37
},
_37
body: JSON.stringify({ room_name: roomName }),
_37
});
_37
const { token } = await response.json();
_37
_37
// join the video room with the token
_37
const room = await joinVideoRoom(roomName, token);
_37
console.log(room);
_37
};
_37
_37
const joinVideoRoom = async (roomName, token) => {
_37
// join the video room with the Access Token and the given room name
_37
const room = await Twilio.Video.connect(token, {
_37
room: roomName,
_37
});
_37
return room;
_37
};
_37
_37
form.addEventListener("submit", startRoom);


Understand and handle Participants

understand-and-handle-participants page anchor

Once a web client joins a video room with an Access Token, it becomes a Participant in the room. The Twilio Video JavaScript SDK distinguishes between local and remote participants; a web client's local participant is the one who joined the video room from that browser, and all other participants are considered remote participants.

After you've successfully connected to a video room, you'll want to display each participant's video and audio on the page. The room object you got back from the connect method has an attribute called localParticipant, which represents the local participant, and an attribute called participants, which is a list of the remote participants that are connected to the room.

Display Participants' audio and video data

display-participants-audio-and-video-data page anchor

The next step in this code is to display the video and audio data for the participants. This will involve:

  • Creating an element on the web page where you'll put a participant's audio and video
  • Adding the video and audio data from each participant to that element

The logic for adding the video and audio data on the page will come later; right now, you can create an empty function called handleConnectedParticipant under the startRoom function. You'll fill out the body of the function in the next section.


_10
const handleConnectedParticipant = (participant) => {};

Next, you'll add calls to the handleConnectedParticipant function. In main.js, add the following lines to the startRoom function, right after the call the joinVideoRoom function. Note that you're removing the console.log(room) line and replacing it with a call to handleConnectedParticipant on the local participant.


_10
// join the video room with the token
_10
const room = await joinVideoRoom(roomName, token);
_10
_10
// render the local and remote participants' video and audio tracks
_10
handleConnectedParticipant(room.localParticipant);
_10
room.participants.forEach(handleConnectedParticipant);
_10
room.on("participantConnected", handleConnectedParticipant);
_10
};

In these new lines of code, you call handleConnectedParticipant on the local participant and any remote participants in the room. (In this application, you're using WebRTC Go Rooms, which have a maximum of two participants, so there will only be one remote participant.)

The room will send a participantConnected(link takes you to an external page) event whenever a new participant connects to the room. The code also listens for this event and calls the handleConnectedParticipant function whenever that event triggers.

Here's the full main.js code up to this point:


_43
const form = document.getElementById("room-name-form");
_43
const roomNameInput = document.getElementById("room-name-input");
_43
const container = document.getElementById("video-container");
_43
_43
const startRoom = async (event) => {
_43
// prevent a page reload when a user submits the form
_43
event.preventDefault();
_43
// hide the join form
_43
form.style.visibility = "hidden";
_43
// retrieve the room name
_43
const roomName = roomNameInput.value;
_43
_43
// fetch an Access Token from the join-room route
_43
const response = await fetch("/join-room", {
_43
method: "POST",
_43
headers: {
_43
Accept: "application/json",
_43
"Content-Type": "application/json",
_43
},
_43
body: JSON.stringify({ room_name: roomName }),
_43
});
_43
const { token } = await response.json();
_43
_43
// join the video room with the token
_43
const room = await joinVideoRoom(roomName, token);
_43
_43
// render the local and remote participants' video and audio tracks
_43
handleConnectedParticipant(room.localParticipant);
_43
room.participants.forEach(handleConnectedParticipant);
_43
room.on("participantConnected", handleConnectedParticipant);
_43
};
_43
_43
const handleConnectedParticipant = (participant) => {};
_43
_43
const joinVideoRoom = async (roomName, token) => {
_43
// join the video room with the Access Token and the given room name
_43
const room = await Twilio.Video.connect(token, {
_43
room: roomName,
_43
});
_43
return room;
_43
};
_43
_43
form.addEventListener("submit", startRoom);


Display Participants' audio and video tracks

display-participants-audio-and-video-tracks page anchor

In this section, you'll complete the logic for showing each participant's audio and video. Before doing this, you should understand the concept of participant tracks.

All participants have tracks. There are three types of tracks:

  • Video : data from video sources such as cameras or screens.
  • Audio : data from audio inputs such as microphones.
  • Data : other data generated by a participant within the application. This can be used for features like building a whiteboarding application(link takes you to an external page) , in-video chat, and more.

By default, when a participant connects to a video room, Twilio will request their local audio and video data. You can use ConnectOptions when you join a video room with the connect method to control whether or not a local participant sends their local video and audio data. For example, if you wanted to create an audio-only chat room, you could create a room and set video: false in ConnectOptions, and then the room would not get participants' video data:


_10
const room = await Twilio.Video.connect(token, {
_10
room: roomName,
_10
video: false,
_10
});

In this application, you'll receive both the audio and video data from participants.

Publish and subscribe to tracks

publish-and-subscribe-to-tracks page anchor

Video room tracks follow a publish/subscribe(link takes you to an external page) pattern. A participant publishes their video, audio, and/or data tracks, and all other participants can subscribe to those published tracks. A participant cannot subscribe to an unpublished track.

When a participant publishes a track, it means they are making the data from that track available to other participants. By default, participants will publish their video and audio tracks when they join a room. A local participant can choose to unpublish or disable certain tracks as well. You can take advantage of this to build something like mute functionality; you could write a function that allows a local participant to stop publishing their audio track. This would make the audio track no longer available to other participants in the room, while still keeping their video track available.

You can update the default track subscription setting with the Track Subscriptions API, which allows you to specify which tracks to subscribe to automatically. For example, if you were building a video presentation application, you might want participants to only subscribe to one participant's audio track (the presenter), and not subscribe to anyone else's (the audience).

In this tutorial, you'll stick with the default track publication setting, with every participant publishing their audio and video tracks, and other participants automatically subscribing to those tracks.

Display data from tracks

display-data-from-tracks page anchor

Given these details, you can now complete the handleConnectedParticipant function in main.js. You'll separate the logic into a few different pieces.

Remove the empty handleConnnectedParticipant function and replace it with the code below.


_17
const handleConnectedParticipant = (participant) => {
_17
// create a div for this participant's tracks
_17
const participantDiv = document.createElement("div");
_17
participantDiv.setAttribute("id", participant.identity);
_17
container.appendChild(participantDiv);
_17
_17
// iterate through the participant's published tracks and
_17
// call `handleTrackPublication` on them
_17
participant.tracks.forEach((trackPublication) => {
_17
handleTrackPublication(trackPublication, participant);
_17
});
_17
_17
// listen for any new track publications
_17
participant.on("trackPublished", handleTrackPublication);
_17
};
_17
_17
const handleTrackPublication = () => {}

In the body of handleConnectedParticipant, you first create a new <div> element for a participant, where you'll put all of this participant's tracks. (This will be helpful when a participant disconnects from the video app — you can remove all of their tracks from the page by removing this <div>.)

Then, you loop through all of the participant's published tracks by looping over the participant's tracks attribute(link takes you to an external page), which contains the participant's TrackPublication(link takes you to an external page) objects. A TrackPublication object represents a track that has been published.

(information)

Info

TrackPublication objects can be either LocalTrackPublication(link takes you to an external page) or RemoteTrackPublication(link takes you to an external page) objects. In this application's case, you don't need to distinguish between local or remote objects, because they're treated the same. You might want to distinguish between local and remote tracks if you wanted to do something like display the local participant's video differently than other participants' videos.

You pass those TrackPublication objects to a new function called handleTrackPublication, which will eventually add the track onto the page. handleTrackPublication is an empty function underneath the handleConnectedParticipant function now, but you'll complete it in the next step.

You also listen for any new track publications from a participant. Whenever there's a trackPublished event, the handleTrackPublication function runs.

(information)

Info

This section is complex, as you're dealing with several different objects: Participants(link takes you to an external page), TrackPublications(link takes you to an external page), and Tracks(link takes you to an external page).

It can be helpful to console.log these objects as you're starting to understand what they do. Feel free to add console.log statements throughout the code and take time to look at what the objects include. As an example, you might add console.log lines to the handleConnectedParticipant function and inspect the participant and trackPublication objects:


_10
// iterate through the participant's published tracks and
_10
// call `handleTrackPublication` on them
_10
console.log("participant: ", participant);
_10
participant.tracks.forEach((trackPublication) => {
_10
console.log("trackPublication: ", trackPublication);
_10
handleTrackPublication(trackPublication, participant);
_10
});

In the next function, handleTrackPublication, you'll be working with individual trackPublication(link takes you to an external page)s from a participant.

Remove the empty handleTrackPublication function, and replace it with the following code:


_10
const handleTrackPublication = (trackPublication, participant) => {
_10
function displayTrack(track) {
_10
// append this track to the participant's div and render it on the page
_10
const participantDiv = document.getElementById(participant.identity);
_10
// track.attach creates an HTMLVideoElement or HTMLAudioElement
_10
// (depending on the type of track) and adds the video or audio stream
_10
participantDiv.append(track.attach());
_10
}
_10
};

First, you create an internal function, displayTrack, to handle rendering the data on the page. It will receive a track and find the <div> that you created earlier for containing all of a participant's tracks. Then, you'll call track.attach() and append that to the participant's <div>.

track.attach() creates either an HTMLVideoElement(link takes you to an external page), if the track is a VideoTrack(link takes you to an external page), or an HTMLAudioElement(link takes you to an external page) for an AudioTrack(link takes you to an external page). It then adds the video or audio data stream to that HTML element. This is how video will ultimately show up on the page, and how audio will be played.

You'll only call displayTrack on tracks that you are subscribed to. Update the handleTrackPublication function with the following code, underneath the displayTrack function:


_18
const handleTrackPublication = (trackPublication, participant) => {
_18
function displayTrack(track) {
_18
// append this track to the participant's div and render it on the page
_18
const participantDiv = document.getElementById(participant.identity);
_18
// track.attach creates an HTMLVideoElement or HTMLAudioElement
_18
// (depending on the type of track) and adds the video or audio stream
_18
participantDiv.append(track.attach());
_18
}
_18
_18
// check if the trackPublication contains a `track` attribute. If it does,
_18
// we are subscribed to this track. If not, we are not subscribed.
_18
if (trackPublication.track) {
_18
displayTrack(trackPublication.track);
_18
}
_18
_18
// listen for any new subscriptions to this track publication
_18
trackPublication.on("subscribed", displayTrack);
_18
};

You can identify whether or not you are subscribed to a published track by seeing if the trackPublication has a track(link takes you to an external page) attribute. The track is the stream of audio, video, or data that you'll add to the page. If the trackPublication does have a track, this participant is subscribed and you can display the data. Otherwise, this local participant has not been subscribed to the published track.

This code also adds an event listener so that any time you subscribe to a new track, displayTrack runs.

Here's the full code for the main.js file:


_76
const form = document.getElementById("room-name-form");
_76
const roomNameInput = document.getElementById("room-name-input");
_76
const container = document.getElementById("video-container");
_76
_76
const startRoom = async (event) => {
_76
// prevent a page reload when a user submits the form
_76
event.preventDefault();
_76
// hide the join form
_76
form.style.visibility = "hidden";
_76
// retrieve the room name
_76
const roomName = roomNameInput.value;
_76
_76
// fetch an Access Token from the join-room route
_76
const response = await fetch("/join-room", {
_76
method: "POST",
_76
headers: {
_76
Accept: "application/json",
_76
"Content-Type": "application/json",
_76
},
_76
body: JSON.stringify({ room_name: roomName }),
_76
});
_76
const { token } = await response.json();
_76
_76
// join the video room with the token
_76
const room = await joinVideoRoom(roomName, token);
_76
_76
// render the local and remote participants' video and audio tracks
_76
handleConnectedParticipant(room.localParticipant);
_76
room.participants.forEach(handleConnectedParticipant);
_76
room.on("participantConnected", handleConnectedParticipant);
_76
};
_76
_76
const handleConnectedParticipant = (participant) => {
_76
// create a div for this participant's tracks
_76
const participantDiv = document.createElement("div");
_76
participantDiv.setAttribute("id", participant.identity);
_76
container.appendChild(participantDiv);
_76
_76
// iterate through the participant's published tracks and
_76
// call `handleTrackPublication` on them
_76
participant.tracks.forEach((trackPublication) => {
_76
handleTrackPublication(trackPublication, participant);
_76
});
_76
_76
// listen for any new track publications
_76
participant.on("trackPublished", handleTrackPublication);
_76
};
_76
_76
const handleTrackPublication = (trackPublication, participant) => {
_76
function displayTrack(track) {
_76
// append this track to the participant's div and render it on the page
_76
const participantDiv = document.getElementById(participant.identity);
_76
// track.attach creates an HTMLVideoElement or HTMLAudioElement
_76
// (depending on the type of track) and adds the video or audio stream
_76
participantDiv.append(track.attach());
_76
}
_76
_76
// check if the trackPublication contains a `track` attribute. If it does,
_76
// we are subscribed to this track. If not, we are not subscribed.
_76
if (trackPublication.track) {
_76
displayTrack(trackPublication.track);
_76
}
_76
_76
// listen for any new subscriptions to this track publication
_76
trackPublication.on("subscribed", displayTrack);
_76
};
_76
_76
const joinVideoRoom = async (roomName, token) => {
_76
// join the video room with the Access Token and the given room name
_76
const room = await Twilio.Video.connect(token, {
_76
room: roomName,
_76
});
_76
return room;
_76
};
_76
_76
form.addEventListener("submit", startRoom);

You should now be able to go to localhost:5000, join a video room, and see your video. Then, from a separate tab, you can join the same room and see both participants' video and hear audio.


You now have a working Video application! The last piece is to clean up after a participant closes their browser or disconnects. In those cases, you should disconnect them from the room and stop displaying their video.

Copy and paste the following handleDisconnectedParticipant function in static/main.js under the handleTrackPublication function.


_10
const handleDisconnectedParticipant = (participant) => {
_10
// stop listening for this participant
_10
participant.removeAllListeners();
_10
// remove this participant's div from the page
_10
const participantDiv = document.getElementById(participant.identity);
_10
participantDiv.remove();
_10
};

In this function, when a participant disconnects, you remove all the listeners you had on the participant, and remove the <div> containing their video and audio tracks.

Then, add an event listener in the startRoom function, underneath the calls to handleConnectedParticipant. When a participantDisconnected event happens, call the handleDisconnectedParticipant function that you wrote.


_10
// render the local and remote participants' video and audio tracks
_10
handleConnectedParticipant(room.localParticipant);
_10
room.participants.forEach(handleConnectedParticipant);
_10
room.on("participantConnected", handleConnectedParticipant);
_10
_10
// handle cleanup when a participant disconnects
_10
room.on("participantDisconnected", handleDisconnectedParticipant);
_10
};

You'll also want to detect when someone closes the browser or navigates to a new page, so that you can disconnect them from the room. This way, other participants can be alerted that the participant left the room. Underneath the event listener for participantDisconnected, add two more event listeners: one for pagehide(link takes you to an external page), and one for beforeunload(link takes you to an external page). When either of these events occur, the local participant should disconnect from the room.


_10
// handle cleanup when a participant disconnects
_10
room.on("participantDisconnected", handleDisconnectedParticipant);
_10
window.addEventListener("pagehide", () => room.disconnect());
_10
window.addEventListener("beforeunload", () => room.disconnect());
_10
};

Here's the final main.js with all of this functionality:


_89
const form = document.getElementById("room-name-form");
_89
const roomNameInput = document.getElementById("room-name-input");
_89
const container = document.getElementById("video-container");
_89
_89
const startRoom = async (event) => {
_89
// prevent a page reload when a user submits the form
_89
event.preventDefault();
_89
// hide the join form
_89
form.style.visibility = "hidden";
_89
// retrieve the room name
_89
const roomName = roomNameInput.value;
_89
_89
// fetch an Access Token from the join-room route
_89
const response = await fetch("/join-room", {
_89
method: "POST",
_89
headers: {
_89
Accept: "application/json",
_89
"Content-Type": "application/json",
_89
},
_89
body: JSON.stringify({ room_name: roomName }),
_89
});
_89
const { token } = await response.json();
_89
_89
// join the video room with the token
_89
const room = await joinVideoRoom(roomName, token);
_89
_89
// render the local and remote participants' video and audio tracks
_89
handleConnectedParticipant(room.localParticipant);
_89
room.participants.forEach(handleConnectedParticipant);
_89
room.on("participantConnected", handleConnectedParticipant);
_89
_89
// handle cleanup when a participant disconnects
_89
room.on("participantDisconnected", handleDisconnectedParticipant);
_89
window.addEventListener("pagehide", () => room.disconnect());
_89
window.addEventListener("beforeunload", () => room.disconnect());
_89
};
_89
_89
const handleConnectedParticipant = (participant) => {
_89
// create a div for this participant's tracks
_89
const participantDiv = document.createElement("div");
_89
participantDiv.setAttribute("id", participant.identity);
_89
container.appendChild(participantDiv);
_89
_89
// iterate through the participant's published tracks and
_89
// call `handleTrackPublication` on them
_89
participant.tracks.forEach((trackPublication) => {
_89
handleTrackPublication(trackPublication, participant);
_89
});
_89
_89
// listen for any new track publications
_89
participant.on("trackPublished", handleTrackPublication);
_89
};
_89
_89
const handleTrackPublication = (trackPublication, participant) => {
_89
function displayTrack(track) {
_89
// append this track to the participant's div and render it on the page
_89
const participantDiv = document.getElementById(participant.identity);
_89
// track.attach creates an HTMLVideoElement or HTMLAudioElement
_89
// (depending on the type of track) and adds the video or audio stream
_89
participantDiv.append(track.attach());
_89
}
_89
_89
// check if the trackPublication contains a `track` attribute. If it does,
_89
// we are subscribed to this track. If not, we are not subscribed.
_89
if (trackPublication.track) {
_89
displayTrack(trackPublication.track);
_89
}
_89
_89
// listen for any new subscriptions to this track publication
_89
trackPublication.on("subscribed", displayTrack);
_89
};
_89
_89
const handleDisconnectedParticipant = (participant) => {
_89
// stop listening for this participant
_89
participant.removeAllListeners();
_89
// remove this participant's div from the page
_89
const participantDiv = document.getElementById(participant.identity);
_89
participantDiv.remove();
_89
};
_89
_89
const joinVideoRoom = async (roomName, token) => {
_89
// join the video room with the Access Token and the given room name
_89
const room = await Twilio.Video.connect(token, {
_89
room: roomName,
_89
});
_89
return room;
_89
};
_89
_89
form.addEventListener("submit", startRoom);


You're done! You now have a video chat application that you can use for connecting two people with audio and video.

Right now, the application is only running locally. To make it accessible to others without deploying the application, you can use ngrok. Download ngrok(link takes you to an external page) and follow the setup instructions (you'll unzip the ngrok download file and can then start it up). Once it's downloaded, start ngrok on port 5000.


_10
./ngrok http 5000

You should see output that looks like this:


_12
ngrok by @inconshreveable (Ctrl+C to quit)
_12
_12
Session Status online
_12
Update update available (version 2.3.40, Ctrl-U to update)
_12
Version 2.3.35
_12
Region United States (us)
_12
Web Interface http://127.0.0.1:4040
_12
Forwarding http://04ae1f9f6d2b.ngrok.io -> http://localhost:5000
_12
Forwarding https://04ae1f9f6d2b.ngrok.io -> http://localhost:5000
_12
_12
Connections ttl opn rt1 rt5 p50 p90
_12
0 0 0.00 0.00 0.00 0.00

Copy the "Forwarding" address and send that to a friend so they can join your video chat!

There's so much more that you can add on to your application now that you've built the foundation. For example, you can:

For more inspiration, check out the Twilio Video React quick deploy app(link takes you to an external page), which demonstrates a wide range of video functionality, or try out more Video tutorials on the Twilio Blog(link takes you to an external page).


Rate this page: