Last active
November 27, 2018 11:12
-
-
Save ktcy/669481ca55db8555beb87755cf136b14 to your computer and use it in GitHub Desktop.
WebRTC sample
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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