Skip to content

Instantly share code, notes, and snippets.

@tsh-code
Last active November 10, 2022 23:53
Show Gist options
  • Save tsh-code/5d34542fe5274341439ef92de7861cd3 to your computer and use it in GitHub Desktop.
Save tsh-code/5d34542fe5274341439ef92de7861cd3 to your computer and use it in GitHub Desktop.
WebRTC
import express, { Application } from "express";
import socketIO, { Server as SocketIOServer } from "socket.io";
import { createServer, Server as HTTPServer } from "http";
export class Server {
private httpServer: HTTPServer;
private app: Application;
private io: SocketIOServer;
private readonly DEFAULT_PORT = 5000;
constructor() {
this.initialize();
this.handleRoutes();
this.handleSocketConnection();
}
private initialize(): void {
this.app = express();
this.httpServer = createServer(this.app);
this.io = socketIO(this.httpServer);
}
private handleRoutes(): void {
this.app.get("/", (req, res) => {
res.send(`<h1>Hello World</h1>`);
});
}
private handleSocketConnection(): void {
this.io.on("connection", socket => {
console.log("Socket connected.");
});
}
public listen(callback: (port: number) => void): void {
this.httpServer.listen(this.DEFAULT_PORT, () =>
callback(this.DEFAULT_PORT)
);
}
}
async function callUser(socketId) {
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(new RTCSessionDescription(offer));
socket.emit("call-user", {
offer,
to: socketId
});
}
socket.on("call-user", data => {
socket.to(data.to).emit("call-made", {
offer: data.offer,
socket: socket.id
});
});
function createUserItemContainer(socketId) {
const userContainerEl = document.createElement("div");
const usernameEl = document.createElement("p");
userContainerEl.setAttribute("class", "active-user");
userContainerEl.setAttribute("id", socketId);
usernameEl.setAttribute("class", "username");
usernameEl.innerHTML = `Socket: ${socketId}`;
userContainerEl.appendChild(usernameEl);
userContainerEl.addEventListener("click", () => {
unselectUsersFromList();
userContainerEl.setAttribute("class", "active-user active-user--selected");
const talkingWithInfo = document.getElementById("talking-with-info");
talkingWithInfo.innerHTML = `Talking with: "Socket: ${socketId}"`;
callUser(socketId);
});
return userContainerEl;
}
navigator.getUserMedia(
{ video: true, audio: true },
stream => {
const localVideo = document.getElementById("local-video");
if (localVideo) {
localVideo.srcObject = stream;
}
},
error => {
console.warn(error.message);
}
);
private configureApp(): void {
this.app.use(express.static(path.join(__dirname, "../public")));
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Dogeller</title>
<link
href="https://fonts.googleapis.com/css?family=Montserrat:300,400,500,700&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="./styles.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js"></script>
</head>
<body>
<div class="container">
<header class="header">
<div class="logo-container">
<img src="./img/doge.png" alt="doge logo" class="logo-img" />
<h1 class="logo-text">
Doge<span class="logo-highlight">ller</span>
</h1>
</div>
</header>
<div class="content-container">
<div class="active-users-panel" id="active-user-container">
<h3 class="panel-title">Active Users:</h3>
</div>
<div class="video-chat-container">
<h2 class="talk-info" id="talking-with-info">
Select active user on the left menu.
</h2>
<div class="video-container">
<video autoplay class="remote-video" id="remote-video"></video>
<video autoplay muted class="local-video" id="local-video"></video>
</div>
</div>
</div>
</div>
<script src="./scripts/index.js"></script>
</body>
</html>
import { Server } from "./server";
const server = new Server();
server.listen(port => {
console.log(`Server is listening on http://localhost:${port}`);
});
private activeSockets: string[] = [];
socket.on("make-answer", data => {
socket.to(data.to).emit("answer-made", {
socket: socket.id,
answer: data.answer
});
});
socket.on("call-made", async data => {
await peerConnection.setRemoteDescription(
new RTCSessionDescription(data.offer)
);
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(new RTCSessionDescription(answer));
socket.emit("make-answer", {
answer,
to: data.socket
});
});
socket.on("answer-made", async data => {
await peerConnection.setRemoteDescription(
new RTCSessionDescription(data.answer)
);
if (!isAlreadyCalling) {
callUser(data.socket);
isAlreadyCalling = true;
}
});
socket.on("update-user-list", ({ users }) => {
updateUserList(users);
});
socket.on("remove-user", ({ socketId }) => {
const elToRemove = document.getElementById(socketId);
if (elToRemove) {
elToRemove.remove();
}
});
{
"scripts": {
"start": "ts-node src/index.ts",
"dev": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts"
},
"devDependencies": {
"@types/express": "^4.17.2",
"@types/socket.io": "^2.1.4",
"nodemon": "^1.19.4",
"ts-node": "^8.4.1",
"typescript": "^3.7.2"
},
"dependencies": {
"express": "^4.17.1",
"socket.io": "^2.3.0"
}
}
navigator.getUserMedia(
{ video: true, audio: true },
stream => {
const localVideo = document.getElementById("local-video");
if (localVideo) {
localVideo.srcObject = stream;
}
stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
},
error => {
console.warn(error.message);
}
);
const { RTCPeerConnection, RTCSessionDescription } = window;
private initialize(): void {
this.app = express();
this.httpServer = createServer(this.app);
this.io = socketIO(this.httpServer);
this.configureApp();
this.handleSocketConnection();
}
peerConnection.ontrack = function({ streams: [stream] }) {
const remoteVideo = document.getElementById("remote-video");
if (remoteVideo) {
remoteVideo.srcObject = stream;
}
};
const socket = io.connect("localhost:5000");
this.io.on("connection", socket => {
const existingSocket = this.activeSockets.find(
existingSocket => existingSocket === socket.id
);
if (!existingSocket) {
this.activeSockets.push(socket.id);
socket.emit("update-user-list", {
users: this.activeSockets.filter(
existingSocket => existingSocket !== socket.id
)
});
socket.broadcast.emit("update-user-list", {
users: [socket.id]
});
}
}
socket.on("disconnect", () => {
this.activeSockets = this.activeSockets.filter(
existingSocket => existingSocket !== socket.id
);
socket.broadcast.emit("remove-user", {
socketId: socket.id
});
});
function updateUserList(socketIds) {
const activeUserContainer = document.getElementById("active-user-container");
socketIds.forEach(socketId => {
const alreadyExistingUser = document.getElementById(socketId);
if (!alreadyExistingUser) {
const userContainerEl = createUserItemContainer(socketId);
activeUserContainer.appendChild(userContainerEl);
}
});
}
@pleerock
Copy link

I guess you missed the part where peerConnection is being created

@tsh-code
Copy link
Author

Yep, there should be const peerConnection = new RTCPeerConnection(); on top of the public/scripts/index.js

@Oberin98
Copy link

Oberin98 commented Apr 2, 2020

Can you help please with such error:
Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote answer sdp: Called in wrong state: kStable
I did everything according to your tutorial, but still getting this mistake! tnx

@ryotoooh
Copy link

ryotoooh commented Apr 6, 2020

Hi,

Could anyone please help me. I'm fairy new to be a developer.
I've got an error after creating index.ts and server.ts then npm run dev.
Is there any solution for that? I've googled it, then installed typescript package globally, but not idea what this error is.

/node_modules/ts-node/src/index.ts:421  
    return new TSError(diagnosticText, diagnosticCodes)
           ^
TSError: ⨯ Unable to compile TypeScript:

@ded
Copy link

ded commented Apr 24, 2020

I followed this tutorial, and based on the logic, why would you run callUser after answer-made? That callback is only called by the original caller to begin with. if you put a log inside callUser(), it occurs twice.

@ded
Copy link

ded commented Apr 24, 2020

Also, I had a question as to why no ICE candidate information is required in this tutorial, yet the end result still ends up working?

@mderoide
Copy link

mderoide commented May 5, 2020

Thanks for this good tuto !
It works very well with Chrome but not with Safari or Firefox (TypeError: navigator.getUserMedia is not a function).
I tired to replace navigator.getUserMedia by navigator.mediaDevice.getUserMedia but i have an "uncaught exception: Object".
Do you have an idea ?

@ggoubert-bow
Copy link

ggoubert-bow commented May 5, 2020

Hi,
Be careful with "navigator.mediaDevice.getUserMedia" and the syntax to use, i modified it:
navigator.mediaDevices.getUserMedia( { video: true, audio: true }).then( (stream) =>{ const localVideo = document.getElementById("local-video"); ....

If it can help...

@Allensy
Copy link

Allensy commented Jun 4, 2020

I think on your article you confused the server code with the client (see image).
The code here is not the one should be in index.js right?
I totally missed the part where the client connects to the server. Where is that part?
image

@sagarparker
Copy link

Hi,

Could anyone please help me. I'm fairy new to be a developer.
I've got an error after creating index.ts and server.ts then npm run dev.
Is there any solution for that? I've googled it, then installed typescript package globally, but not idea what this error is.

/node_modules/ts-node/src/index.ts:421  
    return new TSError(diagnosticText, diagnosticCodes)
           ^
TSError: ⨯ Unable to compile TypeScript:

Did You find a solution to this problem I am facing the same issue, Thanks.

@davor-bulic
Copy link

I think on your article you confused the server code with the client (see image).
The code here is not the one should be in index.js right?
I totally missed the part where the client connects to the server. Where is that part?
image

He made a mistake, this code is in server.ts file, inside of handleSocketConnection function.

@whitemd
Copy link

whitemd commented Jul 30, 2020

Hi,
Could anyone please help me. I'm fairy new to be a developer.
I've got an error after creating index.ts and server.ts then npm run dev.
Is there any solution for that? I've googled it, then installed typescript package globally, but not idea what this error is.

/node_modules/ts-node/src/index.ts:421  
    return new TSError(diagnosticText, diagnosticCodes)
           ^
TSError: ⨯ Unable to compile TypeScript:

Did You find a solution to this problem I am facing the same issue, Thanks.

It happened to me and I determined it was just a generic error. The real error was somewhere else. Check the full stack trace that it gives you.

Copy link

ghost commented Aug 12, 2020

Hi, I was trying to recreate the code and when started with Handle socket connection snippet, author was recommending to look for the socket connected message in console. apparently, I was not able to see "Socket connected" message. I have checked all the git repository files as well. Am I missing something.
Thank you.
V Raj

@sagarparker
Copy link

sagarparker commented Aug 12, 2020 via email

Copy link

ghost commented Aug 12, 2020

It is broken it doesn't work mate

On Wed 12 Aug, 2020, 10:28 AM VISHNU RAJ C L, @.> wrote: @.* commented on this gist. ------------------------------ Hi, I was trying to recreate the code and when started with Handle socket connection snippet, author was recommending to look for the socket connected message in console. apparently, I was not able to see "Socket connected" message. I have checked all the git repository files as well. Am I missing something. Thank you. V Raj — You are receiving this because you commented. Reply to this email directly, view it on GitHub https://gist.github.com/5d34542fe5274341439ef92de7861cd3#gistcomment-3413866, or unsubscribe https://github.com/notifications/unsubscribe-auth/AH2GJKLJXJ2C3F4BAFTSTTTSAIORDANCNFSM4LQSCDOA .

Thanks Sagarparker ! Is there any other workaround?

@sagarparker
Copy link

sagarparker commented Aug 12, 2020 via email

@fherdlcruz
Copy link

fherdlcruz commented Aug 26, 2020 via email

@chebotaevroman
Copy link

in tsconfig.json

{
  "compilerOptions": {
    "esModuleInterop": true
  }
}

to fix import errors

@EPGPpareit
Copy link

Does anyone know why this code is needed?

on-made-answer.js

if (!isAlreadyCalling) {
callUser(data.socket);
isAlreadyCalling = true;
}

When I place a console.log(socketId); in the callUser function.
I see that the callUser function is called twice with the same socket id as argument.

This code is needed, because without it the application doesn't work.
But can't find a sollution why.

@exzos28
Copy link

exzos28 commented Apr 10, 2021

I made an example and fixed all the mistakes of the author.
https://github.com/exzos28/webrtc-echo-server

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment