Skip to content

Instantly share code, notes, and snippets.

@ktcy
Last active November 27, 2018 11:12
Show Gist options
  • Save ktcy/669481ca55db8555beb87755cf136b14 to your computer and use it in GitHub Desktop.
Save ktcy/669481ca55db8555beb87755cf136b14 to your computer and use it in GitHub Desktop.
WebRTC sample
const clientList = document.getElementById('client-list');
const mainVideo = document.getElementById('main-video');
mainVideo.addEventListener('loadedmetadata', () => {
const {videoWidth, videoHeight} = mainVideo;
const longest = Math.max(videoWidth, videoHeight);
const size = 300;
mainVideo.width = size * videoWidth / longest;
mainVideo.height = size * videoHeight / longest;
});
const socket = io();
socket.on('clients', renderClients);
socket.on('webrtc:offer', respondToOffer);
function renderClients(data) {
const {id: self, clients} = data;
while (clientList.firstChild) {
clientList.removeChild(clientList.firstChild);
}
const fragment = document.createDocumentFragment();
for (const {id, name} of clients) {
if (id === self) {
continue;
}
const li = document.createElement('li');
li.textContent = name;
li.addEventListener('click', () => invite(self, id));
fragment.appendChild(li);
}
clientList.appendChild(fragment);
}
async function invite(self, target) {
const connection = createConnection(self, target);
const stream = await navigator.mediaDevices.getUserMedia({video: true, audio: false});
mainVideo.srcObject = stream;
mainVideo.play();
for (const track of stream.getTracks()) {
connection.addTrack(track);
}
// const channel = connection.createDataChannel('sendChannel');
// channel.addEventListener('open', handleSendChannelStatusChange);
// channel.addEventListener('close', handleSendChannelStatusChange);
// channel.addEventListener('message', handleMessage);
}
async function respondToOffer(data) {
const {sender, target, description} = data;
const connection = createConnection(target, sender);
await connection.setRemoteDescription(description);
const answer = await connection.createAnswer();
await connection.setLocalDescription(answer);
socket.emit('webrtc:answer', {
sender: target, target: sender,
description: connection.localDescription.toJSON()
});
}
function createConnection(self, target) {
const connection = new RTCPeerConnection();
connection.addEventListener('signalingstatechange', event => {
console.log(event.type, connection.signalingState);
});
connection.addEventListener('iceconnectionstatechange', event => {
console.log(event.type, connection.iceConnectionState);
});
connection.addEventListener('negotiationneeded', async event => {
console.log(event.type);
socket.on('webrtc:answer', async function accept(answer) {
if (answer.target === self) {
await connection.setRemoteDescription(answer.description);
socket.off('webrtc:answer', accept);
}
});
const offer = await connection.createOffer();
await connection.setLocalDescription(offer);
socket.emit('webrtc:offer', {
sender: self, target: target,
description: connection.localDescription.toJSON()
});
});
connection.addEventListener('icecandidate', event => {
console.log(event.type);
if (event.candidate) {
socket.emit('webrtc:icecandidate', {
sender: self, target: target,
candidate: event.candidate.toJSON()
});
}
});
async function receiveICECandidate(data) {
if (data.sender === target) {
await connection.addIceCandidate(data.candidate);
}
}
connection.addEventListener('icegatheringstatechange', event => {
console.log(event.type, connection.iceGatheringState);
switch (connection.iceGatheringState) {
case 'gathering':
socket.on('webrtc:icecandidate', receiveICECandidate);
break;
case 'complete':
socket.off('webrtc:icecandidate', receiveICECandidate);
break;
}
});
connection.addEventListener('track', event => {
console.log(event.type);
mainVideo.srcObject = event.streams[0];
mainVideo.play();
});
connection.addEventListener('removetrack', event => console.log(event.type));
connection.addEventListener('removestream', event => console.log(event.type));
connection.addEventListener('datachannel', event => {
console.log(event.type);
const {channel} = event;
channel.addEventListener('open', handleSendChannelStatusChange);
channel.addEventListener('close', handleSendChannelStatusChange);
channel.addEventListener('message', handleMessage);
});
return connection;
}
function handleSendChannelStatusChange(event) {
console.log('datachannel', event.type, event.target.readyState);
if (event.type === 'open') {
setTimeout(() => event.target.send('hello'), 1000);
}
}
function handleMessage(event) {
console.log('datachannel', event.type, event.data);
}
const http = require('http');
const express = require('express');
const socketIO = require('socket.io');
const {resolve} = require('path');
const PORT = 3000;
const CLIENT_DIR = resolve(__dirname, 'client');
const app = express();
const server = http.createServer(app);
const io = socketIO(server);
const createID = (id => () => id++)(0);
const clients = new Map();
function emitClients() {
const clientsJSON = [];
for (const {id, name} of clients.values()) {
clientsJSON.push({id, name});
}
for (const {id, socket} of clients.values()) {
socket.emit('clients', {id, clients: clientsJSON});
}
}
app.use(express.static(CLIENT_DIR));
io.on('connect', socket => {
const id = createID();
const name = `Client #${id}`;
const client = {id, name, socket};
clients.set(id, client);
emitClients();
console.log('connect', name);
socket.on('disconnect', () => {
clients.delete(id);
emitClients();
console.log('disconnect', name);
});
socket.on('webrtc:offer', data => {
const client = clients.get(data.target);
client && client.socket.emit('webrtc:offer', data);
});
socket.on('webrtc:answer', data => {
const client = clients.get(data.target);
client && client.socket.emit('webrtc:answer', data);
});
socket.on('webrtc:icecandidate', data => {
const client = clients.get(data.target);
client && client.socket.emit('webrtc:icecandidate', data);
});
});
server.listen(PORT, () => console.log(`Server is listening on port ${PORT}`));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment