Skip to content
This repository has been archived by the owner on May 13, 2024. It is now read-only.

Change app.js and README to better reflect actual solution code #26

Open
wants to merge 49 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
78c0faf
notes on running tutorial...
Dec 17, 2020
1552351
hooray for gitbash being noninteractive?
Dec 17, 2020
ba51bce
interactive mode all the time.
Dec 17, 2020
4af5ef7
firebase has changed since tutorial written, I think.
Dec 17, 2020
a2a0a98
create the room code.
Dec 17, 2020
bc913a6
join the room code
Dec 17, 2020
7c0a882
I hate tutorials that are not written well or maintained...
Dec 17, 2020
fbe1bec
so annoyed.
Dec 17, 2020
8473c50
overwriting with original from the parent repo
meldaravaniel Dec 17, 2020
0b4a67a
Copying the instructions so I can make the better.
meldaravaniel Dec 17, 2020
2d0b258
Update correctedCodelab.md
meldaravaniel Dec 17, 2020
0eb3255
Update correctedCodelab.md
meldaravaniel Dec 17, 2020
3416c90
Update correctedCodelab.md
meldaravaniel Dec 17, 2020
fce0d9f
Update correctedCodelab.md
meldaravaniel Dec 17, 2020
16b0185
Update correctedCodelab.md
meldaravaniel Dec 17, 2020
2dfc596
Update correctedCodelab.md
meldaravaniel Dec 17, 2020
12510d1
Update correctedCodelab.md
meldaravaniel Dec 17, 2020
654ca0e
Update correctedCodelab.md
meldaravaniel Dec 17, 2020
8920e3a
Update correctedCodelab.md
meldaravaniel Dec 17, 2020
e85d459
Update correctedCodelab.md
meldaravaniel Dec 17, 2020
5060916
okay it's bed time, this isn't complete but at least it's better.
meldaravaniel Dec 17, 2020
9742817
Update correctedCodelab.md
meldaravaniel Dec 17, 2020
72a5681
gonna pick this apart to reverse engineer 'start'
meldaravaniel Dec 18, 2020
a193598
remove the room ref code from solution
Dec 18, 2020
5c6fe7d
fix 'create the room' instructions
meldaravaniel Dec 18, 2020
6c59901
remove the room ref code from solution
Dec 18, 2020
aa83102
remove the join room code from solution
Dec 18, 2020
6aa20bf
Merge branch 'master' of github.com:meldaravaniel/FirebaseRTC
Dec 18, 2020
002ed6c
fixed the collect ice candidates step
meldaravaniel Dec 18, 2020
225d3a3
Update correctedCodelab.md
meldaravaniel Dec 18, 2020
1e8e2eb
removed iceCollection and made match solution code, without spoilers
Dec 18, 2020
ca64579
Merge branch 'master' of github.com:meldaravaniel/FirebaseRTC
Dec 18, 2020
f4279cd
fix empty codelab starting point
meldaravaniel Dec 18, 2020
ce84293
Delete app_solution.js
meldaravaniel Dec 18, 2020
236c5a3
add links
meldaravaniel Dec 18, 2020
ce51e50
Delete personalNotes.md
meldaravaniel Dec 18, 2020
b674413
adding the corrected instructions to the README.
meldaravaniel Dec 18, 2020
83e6a49
Delete correctedCodelab.md
meldaravaniel Dec 18, 2020
fed80da
Update README.md
meldaravaniel Dec 18, 2020
e355e92
Update README.md
meldaravaniel Dec 18, 2020
8e1d682
updated README with corrections and suggestions
natkuhn Dec 19, 2020
fbfad4f
Merge pull request #1 from natkuhn/master
meldaravaniel Dec 19, 2020
68ae060
adding a missing word :)
meldaravaniel Dec 19, 2020
dbcedd3
formatting
meldaravaniel Dec 19, 2020
7c8bcea
Update README.md
meldaravaniel Dec 19, 2020
81fa0e3
removing `sh` from commands in favor of bash-based
meldaravaniel Dec 19, 2020
993c53f
Update README.md
meldaravaniel Jan 5, 2021
a3f4004
minor readme fixes
Fusselwurm Mar 11, 2021
a1cef75
Merge pull request #2 from Fusselwurm/patch-1
meldaravaniel Mar 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 207 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,210 @@
# Firebase + WebRTC Codelab
### Full code solution can be found under the branch: _solution_
This is the GitHub repo for the FirebaseRTC codelab. This will teach you how
to use Firebase Cloud Firestore for signalling in a WebRTC video chat application.

The solution to this codelab can be seen in the _solution_ branch.
### Full code solution can be found under the branch: [_solution_](https://github.com/meldaravaniel/FirebaseRTC/tree/solution)
This is the GitHub repo for the FirebaseRTC codelab. This will teach you how to use Firebase Cloud Firestore for signalling in a WebRTC video chat application.
The solution to this codelab can be seen in the _solution_ branch.

See http://webrtc.org for details.
See http://webrtc.org for details.

Note: the following instructions were copied and updated/corrected from https://webrtc.org/getting-started/firebase-rtc-codelab.

## Introduction

In this codelab, you'll learn how to build a simple video chat application using the WebRTC API in your browser and Cloud Firestore for signaling. The application is called FirebaseRTC and works as a simple example that will teach you the basics of building WebRTC enabled applications.

> Note: Another option for signaling could be Firebase Cloud Messaging. However, that is currently only supported in Chrome and in this codelab we will focus on a solution that works across all browsers supporting WebRTC.

### What you'll learn
* Initiating a video call in a web application using WebRTC
* Signaling to the remote party using Cloud Firestore

### What you'll need
Before starting this codelab, make sure that you've installed:
* npm which typically comes with Node.js - Node LTS is recommended

## Instructions

1. __Create and set up a Firebase project__
1. Create a Firebase project
- [In the Firebase console](https://console.firebase.google.com/), click __Create a project__, then name the Firebase project "FirebaseRTC".
- Remember the Project ID for your Firebase project.
>Note: If you go to the home page of your project, you can see it in Settings > Project Settings (or look at the URL!)
- Disable Google Analytics
- Click Create project.
- Creation takes a moment. When your project is created, click Continue.

The application that you're going to build uses two Firebase services available on the web:
* Cloud Firestore
- to save structured data on the Cloud and get instant notification when the data is updated
* Firebase Hosting
- to host and serve your static assets

For this specific codelab, you've already configured Firebase Hosting in the project you'll be cloning. However, for Cloud Firestore, we'll walk you through the configuration and enabling of the services using the Firebase console.

1. __Enable Cloud Firestore__

The app uses Cloud Firestore to save the chat messages and receive new chat messages.
1. In the Firebase sidebar, navigate to Build -> Cloud Firestore.
1. Click __Create database__ in the Cloud Firestore pane.
1. Select the __Start in test mode__ option, read the warning about database security, and click __Next__. Then click __Enable__ after reading the warning about the security rules.
Test mode ensures that you can freely write to the database during development.

1. __Get the sample code__
1. On your local machine, clone the codelab GitHub repository from the command line:
`git clone https://github.com/webrtc/FirebaseRTC` \[__DELETE in PR:__ for now, use `git clone https://github.com/meldaravaniel/FirebaseRTC`\]
- The sample code should have been cloned into the FirebaseRTC directory.
1. Make sure your command line is run from this directory from now on by executing
`cd FirebaseRTC`

As you work through the tutorial, open the files in `FirebaseRTC` in your editor and change them according to the instructions below. This directory contains the starting code for the codelab which consists of a not-yet functional WebRTC app. We'll make it functional throughout this codelab.

1. __Install the Firebase Command Line Interface__

The Firebase Command Line Interface (CLI) allows you to serve your web app locally and deploy your web app to Firebase Hosting.
> Note: To install the CLI, you need to install npm which typically comes with Node.js.
1. Install the CLI by running the following npm command: `npm -g install firebase-tools`
- On unix and doesn't work? You may need to run the command using sudo instead.
1. Verify that the CLI has been installed correctly by running the following command: `firebase --version`
- Make sure the version of the Firebase CLI is v6.7.1 or later.
1. Authorize the Firebase CLI by running the following command: `firebase login`
- if the terminal you use is 'non-interactive' (eg. git-bash) you may need to append `--interactive` to the above command.

You've set up the web app template to pull your app's configuration for Firebase Hosting from your app's local directory and files. But to do this, you need to associate your app with your Firebase project.
1. Associate your app with your Firebase project by running the following command: `firebase use --add`
- you may need to append `--interactive` to the above command.
- When prompted, select your Project ID (from the Create Firestore Project step), then give your Firebase project an alias. An alias is useful if you have multiple environments (production, staging, etc). However, for this codelab, let's just use the alias of default.
1. Follow the remaining instructions in your command line.

1. __Run the local server__

You're ready to actually start work on our app! Let's run the app locally!
1. Run the following Firebase CLI command: `firebase serve --only hosting`
- you may need to append `--interactive` to the above command.
- Your command line should display the following response:
> hosting: Local server: http://localhost:5000
- We're using the Firebase Hosting emulator to serve our app locally. The web app should now be available from http://localhost:5000.
1. Open your app at http://localhost:5000
You should see your copy of FirebaseRTC which has been connected to your Firebase project.
The app has automatically connected to your Firebase project.
> Note: if you want to access the server from other devices on your local network, you need to add `-o 0.0.0.0` at the end of the `firebase serve` command.

1. __Creating a new room__

In this application, each video chat session is called a __room__. A user can create a new room by clicking a button in their application. This will generate an ID that the remote party can use to join the same room. The ID is used as the key in Cloud Firestore for each room.

Each room will contain the `RTCSessionDescriptions` for both the offer and the answer, as well as two separate collections with [ICE candidates](https://webrtcglossary.com/ice/#:~:text=ICE%20stands%20for%20Interactive%20Connectivity,NAT%20traversal%20used%20in%20WebRTC.) from each party.

Your first task is to implement the missing code for creating a new room with the initial offer from the caller. Open public/app.js, find the comment `// Add code for creating a room below`, and add the following code. (You should add the code before the corresponding `// Add code for creating a room above`; the same goes for the rest of the code blocks.)

```
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
console.log('Created offer:', offer);
const roomWithOffer = {
'offer': {
type: offer.type,
sdp: offer.sdp
}
}
await roomRef.set(roomWithOffer);
roomId = roomRef.id;
console.log(`New room created with SDP offer. Room ID: ${roomRef.id}`);
document.querySelector('#currentRoom').innerText = `Current room is ${roomId} - You are the caller!`
```
The first line creates an `RTCSessionDescription` that will represent the offer from the caller. This is then set as the local description and, finally, written to the new room object in Cloud Firestore.

Next, we will listen for changes to the database and detect when an answer from the callee has been added. Find the comment `// Listening for remote session description below` and add the following code:

```
roomRef.onSnapshot(async snapshot => {
const data = snapshot.data();
if (!peerConnection.currentRemoteDescription && data && data.answer) {
console.log('Got remote description: ', data.answer);
const rtcSessionDescription = new RTCSessionDescription(data.answer);
await peerConnection.setRemoteDescription(rtcSessionDescription);
}
});
```
This will wait until the callee writes the `RTCSessionDescription` for the answer, and set that as the remote description on the caller `RTCPeerConnection`.

1. __Joining a room__

The next step is to implement the logic for joining an existing room. The user does this by clicking the "Join room" button and entering the ID for the room to join. Your task here is to implement the creation of the `RTCSessionDescription` for the answer and update the room in the database accordingly. Find the comment `// Code for creating SDP answer below` in the `joinRoomById(roomId)` method and add the following code:

```
const offer = roomSnapshot.data().offer;
console.log('Got offer:', offer);
await peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await peerConnection.createAnswer();
console.log('Created answer:', answer);
await peerConnection.setLocalDescription(answer);

const roomWithAnswer = {
answer: {
type: answer.type,
sdp: answer.sdp,
},
};
await roomRef.update(roomWithAnswer);
```
In the code above, we start by extracting the offer from the caller and creating a `RTCSessionDescription` that we set as the remote description. Next, we create the answer, set it as the local description, and update the database. The update of the database will trigger the `onSnapshot` callback that we added in the previous step on the caller side. That callback will set the remote description based on the answer from the callee. This completes the exchange of the `RTCSessionDescription` objects between the caller and the callee.

1. __Collect ICE candidates__

Before the caller and callee can connect to each other, they also need to exchange ICE candidates that tell WebRTC how to connect to the remote peer. Your next task is to implement the code that listens for ICE candidates and adds them to a collection in the database. Find the comment `// collect ICE Candidates function below` and add the following function:

```
async function collectIceCandidates(roomRef, peerConnection,
localName, remoteName) {
const candidatesCollection = roomRef.collection(localName);

peerConnection.addEventListener('icecandidate', event => {
if (event.candidate) {
const json = event.candidate.toJSON();
candidatesCollection.add(json);
}
});

roomRef.collection(remoteName).onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if (change.type === "added") {
const candidate = new RTCIceCandidate(change.doc.data());
peerConnection.addIceCandidate(candidate);
}
});
})
}
```
This function does two main things.
* collects ICE candidates from the WebRTC API and adds them to the database
* listens for added ICE candidates from the remote peer and adds them to its `RTCPeerConnection` instance.

It is important when listening to database changes to filter out anything that isn't a new addition, since we otherwise would have added the same set of ICE candidates over and over again.

Complete this step by uncommenting the calls to this function in both the `joinRoomById` and `createRoom` methods; you can find them after `// Uncomment to collect ICE candidates below`.

1. __Try it out__

1. Open a new browswer tab at http://localhost:5000, or refresh the tab you opened above.
1. Mute your speakers to avoid loud piercing feedback!
1. Click on __Open camera & microphone__; give permission to the app to use them, if requested.
1. Click on __Create room__. The app will display the room ID.
1. Open another browswer tab, click __Open camera & microphone__, and click __Join room__. Paste in the ID from the previous step. The two instances should connect.

To connect with a different device on your local network:

1. Make sure that your start the server with `-o 0.0.0.0`.
1. Find the IP address of your server. Let's say it's `192.168.1.5`.
1. On your remote device, you need to enable the camera for "insecure orgins," since you are connecting to the server via http, not https. For Chrome, you can do this by navigating to `chrome://flags/#unsafely-treat-insecure-origin-as-secure`, and adding `http://192.168.1.5:5000/` to the list of origins.
1. Connect as above, except that you will have to enter the room ID manually.


1. __Conclusion__

In this codelab you learned how to implement signaling for WebRTC using Cloud Firestore, as well as how to use that for creating a simple video chat application.

To learn more, visit the following resources:

* [FirebaseRTC Source Code](https://github.com/webrtc/FirebaseRTC)
* [WebRTC samples](https://webrtc.github.io/samples)
* [Cloud Firestore](https://firebase.google.com/docs/firestore/)
41 changes: 18 additions & 23 deletions public/app.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
mdc.ripple.MDCRipple.attachTo(document.querySelector('.mdc-button'));

// DEfault configuration - Change these if you have a different STUN or TURN server.
const configuration = {
iceServers: [
{
Expand All @@ -13,6 +12,9 @@ const configuration = {
iceCandidatePoolSize: 10,
};

const callerCandidatesString = 'callerCandidates';
const calleeCandidatesString = 'calleeCandidates';

let peerConnection = null;
let localStream = null;
let remoteStream = null;
Expand All @@ -31,28 +33,25 @@ async function createRoom() {
document.querySelector('#createBtn').disabled = true;
document.querySelector('#joinBtn').disabled = true;
const db = firebase.firestore();
const roomRef = await db.collection('rooms').doc();

console.log('Create PeerConnection with configuration: ', configuration);
peerConnection = new RTCPeerConnection(configuration);

registerPeerConnectionListeners();

// Add code for creating a room here

// Code for creating room above

localStream.getTracks().forEach(track => {
peerConnection.addTrack(track, localStream);
});


// Uncomment to collect ICE candidates below
// await collectIceCandidates(roomRef, peerConnection, callerCandidatesString, calleeCandidatesString);

// Code for creating a room below

// Code for creating a room above

// Code for collecting ICE candidates below

// Code for collecting ICE candidates above

peerConnection.addEventListener('track', event => {
console.log('Got remote track:', event.streams[0]);
event.streams[0].getTracks().forEach(track => {
Expand All @@ -64,10 +63,6 @@ async function createRoom() {
// Listening for remote session description below

// Listening for remote session description above

// Listen for remote ICE candidates below

// Listen for remote ICE candidates above
}

function joinRoom() {
Expand Down Expand Up @@ -99,9 +94,9 @@ async function joinRoomById(roomId) {
peerConnection.addTrack(track, localStream);
});

// Code for collecting ICE candidates below

// Code for collecting ICE candidates above
// Uncomment to collect ICE candidates below
// await collectIceCandidates(roomRef, peerConnection, calleeCandidatesString, callerCandidatesString);

peerConnection.addEventListener('track', event => {
console.log('Got remote track:', event.streams[0]);
Expand All @@ -114,13 +109,13 @@ async function joinRoomById(roomId) {
// Code for creating SDP answer below

// Code for creating SDP answer above

// Listening for remote ICE candidates below

// Listening for remote ICE candidates above
}
}

// collect ICE Candidates function below

// collect ICE Candidates function above

async function openUserMedia(e) {
const stream = await navigator.mediaDevices.getUserMedia(
{video: true, audio: true});
Expand Down Expand Up @@ -162,13 +157,13 @@ async function hangUp(e) {
if (roomId) {
const db = firebase.firestore();
const roomRef = db.collection('rooms').doc(roomId);
const calleeCandidates = await roomRef.collection('calleeCandidates').get();
const calleeCandidates = await roomRef.collection(calleeCandidatesString).get();
calleeCandidates.forEach(async candidate => {
await candidate.delete();
await candidate.ref.delete();
});
const callerCandidates = await roomRef.collection('callerCandidates').get();
const callerCandidates = await roomRef.collection(callerCandidatesString).get();
callerCandidates.forEach(async candidate => {
await candidate.delete();
await candidate.ref.delete();
});
await roomRef.delete();
}
Expand Down