Repository: Dirvann/webrtc-video-conference-simple-peer
Branch: master
Commit: 0d95c6fd417c
Files: 11
Total size: 17.0 KB
Directory structure:
gitextract_ufu3lduj/
├── .gitignore
├── LICENSE
├── README.md
├── package.json
├── public/
│ ├── index.html
│ └── js/
│ └── main.js
├── src/
│ ├── app.js
│ ├── routes.js
│ └── socketController.js
└── ssl/
├── cert.pem
└── key.pem
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Dirk Vanbeveren
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# WebRTC Video Conferencing with simple-peer
A simple video conferencing example using simple-peer.
This project allows multiple devices to connect with eachother with audio and video using webrtc.
The package [simple-peer](https://github.com/feross/simple-peer) is used for webrtc.
The implementation of the signaling server is done with [socket.io](https://socket.io/)
## Demo
[Demo on heroku](https://dirvann-webrtc-video.herokuapp.com/)
## Running
run `npm install` and then `npm start` in the main directory.
Then open the browser at `localhost:3012` or `[your network ip/ public dns]:3012`.
## Configuration
Configurations can be found in `app.js` and `public/js/main.js`.
Replace the ssl certificates `ssl/key.pem` and `ssl/cert.pem` with your own.
================================================
FILE: package.json
================================================
{
"name": "mywebrtc",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"express": "^4.17.1",
"httpolyglot": "^0.1.2",
"simple-peer": "^9.7.0",
"socket.io": "^2.4.0"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node src/app.js"
},
"author": "",
"license": "ISC"
}
================================================
FILE: public/index.html
================================================
================================================
FILE: public/js/main.js
================================================
/**
* Socket.io socket
*/
let socket;
/**
* The stream object used to send media
*/
let localStream = null;
/**
* All peer connections
*/
let peers = {}
// redirect if not https
if(location.href.substr(0,5) !== 'https')
location.href = 'https' + location.href.substr(4, location.href.length - 4)
//////////// CONFIGURATION //////////////////
/**
* RTCPeerConnection configuration
*/
const configuration = {
// Using From https://www.metered.ca/tools/openrelay/
"iceServers": [
{
urls: "stun:openrelay.metered.ca:80"
},
{
urls: "turn:openrelay.metered.ca:80",
username: "openrelayproject",
credential: "openrelayproject"
},
{
urls: "turn:openrelay.metered.ca:443",
username: "openrelayproject",
credential: "openrelayproject"
},
{
urls: "turn:openrelay.metered.ca:443?transport=tcp",
username: "openrelayproject",
credential: "openrelayproject"
}
]
}
/**
* UserMedia constraints
*/
let constraints = {
audio: true,
video: {
width: {
max: 300
},
height: {
max: 300
}
}
}
/////////////////////////////////////////////////////////
constraints.video.facingMode = {
ideal: "user"
}
// enabling the camera at startup
navigator.mediaDevices.getUserMedia(constraints).then(stream => {
console.log('Received local stream');
localVideo.srcObject = stream;
localStream = stream;
init()
}).catch(e => alert(`getusermedia error ${e.name}`))
/**
* initialize the socket connections
*/
function init() {
socket = io()
socket.on('initReceive', socket_id => {
console.log('INIT RECEIVE ' + socket_id)
addPeer(socket_id, false)
socket.emit('initSend', socket_id)
})
socket.on('initSend', socket_id => {
console.log('INIT SEND ' + socket_id)
addPeer(socket_id, true)
})
socket.on('removePeer', socket_id => {
console.log('removing peer ' + socket_id)
removePeer(socket_id)
})
socket.on('disconnect', () => {
console.log('GOT DISCONNECTED')
for (let socket_id in peers) {
removePeer(socket_id)
}
})
socket.on('signal', data => {
peers[data.socket_id].signal(data.signal)
})
}
/**
* Remove a peer with given socket_id.
* Removes the video element and deletes the connection
* @param {String} socket_id
*/
function removePeer(socket_id) {
let videoEl = document.getElementById(socket_id)
if (videoEl) {
const tracks = videoEl.srcObject.getTracks();
tracks.forEach(function (track) {
track.stop()
})
videoEl.srcObject = null
videoEl.parentNode.removeChild(videoEl)
}
if (peers[socket_id]) peers[socket_id].destroy()
delete peers[socket_id]
}
/**
* Creates a new peer connection and sets the event listeners
* @param {String} socket_id
* ID of the peer
* @param {Boolean} am_initiator
* Set to true if the peer initiates the connection process.
* Set to false if the peer receives the connection.
*/
function addPeer(socket_id, am_initiator) {
peers[socket_id] = new SimplePeer({
initiator: am_initiator,
stream: localStream,
config: configuration
})
peers[socket_id].on('signal', data => {
socket.emit('signal', {
signal: data,
socket_id: socket_id
})
})
peers[socket_id].on('stream', stream => {
let newVid = document.createElement('video')
newVid.srcObject = stream
newVid.id = socket_id
newVid.playsinline = false
newVid.autoplay = true
newVid.className = "vid"
newVid.onclick = () => openPictureMode(newVid)
newVid.ontouchstart = (e) => openPictureMode(newVid)
videos.appendChild(newVid)
})
}
/**
* Opens an element in Picture-in-Picture mode
* @param {HTMLVideoElement} el video element to put in pip mode
*/
function openPictureMode(el) {
console.log('opening pip')
el.requestPictureInPicture()
}
/**
* Switches the camera between user and environment. It will just enable the camera 2 cameras not supported.
*/
function switchMedia() {
if (constraints.video.facingMode.ideal === 'user') {
constraints.video.facingMode.ideal = 'environment'
} else {
constraints.video.facingMode.ideal = 'user'
}
const tracks = localStream.getTracks();
tracks.forEach(function (track) {
track.stop()
})
localVideo.srcObject = null
navigator.mediaDevices.getUserMedia(constraints).then(stream => {
for (let socket_id in peers) {
for (let index in peers[socket_id].streams[0].getTracks()) {
for (let index2 in stream.getTracks()) {
if (peers[socket_id].streams[0].getTracks()[index].kind === stream.getTracks()[index2].kind) {
peers[socket_id].replaceTrack(peers[socket_id].streams[0].getTracks()[index], stream.getTracks()[index2], peers[socket_id].streams[0])
break;
}
}
}
}
localStream = stream
localVideo.srcObject = stream
updateButtons()
})
}
/**
* Enable screen share
*/
function setScreen() {
navigator.mediaDevices.getDisplayMedia().then(stream => {
for (let socket_id in peers) {
for (let index in peers[socket_id].streams[0].getTracks()) {
for (let index2 in stream.getTracks()) {
if (peers[socket_id].streams[0].getTracks()[index].kind === stream.getTracks()[index2].kind) {
peers[socket_id].replaceTrack(peers[socket_id].streams[0].getTracks()[index], stream.getTracks()[index2], peers[socket_id].streams[0])
break;
}
}
}
}
localStream = stream
localVideo.srcObject = localStream
socket.emit('removeUpdatePeer', '')
})
updateButtons()
}
/**
* Disables and removes the local stream and all the connections to other peers.
*/
function removeLocalStream() {
if (localStream) {
const tracks = localStream.getTracks();
tracks.forEach(function (track) {
track.stop()
})
localVideo.srcObject = null
}
for (let socket_id in peers) {
removePeer(socket_id)
}
}
/**
* Enable/disable microphone
*/
function toggleMute() {
for (let index in localStream.getAudioTracks()) {
localStream.getAudioTracks()[index].enabled = !localStream.getAudioTracks()[index].enabled
muteButton.innerText = localStream.getAudioTracks()[index].enabled ? "Unmuted" : "Muted"
}
}
/**
* Enable/disable video
*/
function toggleVid() {
for (let index in localStream.getVideoTracks()) {
localStream.getVideoTracks()[index].enabled = !localStream.getVideoTracks()[index].enabled
vidButton.innerText = localStream.getVideoTracks()[index].enabled ? "Video Enabled" : "Video Disabled"
}
}
/**
* updating text of buttons
*/
function updateButtons() {
for (let index in localStream.getVideoTracks()) {
vidButton.innerText = localStream.getVideoTracks()[index].enabled ? "Video Enabled" : "Video Disabled"
}
for (let index in localStream.getAudioTracks()) {
muteButton.innerText = localStream.getAudioTracks()[index].enabled ? "Unmuted" : "Muted"
}
}
================================================
FILE: src/app.js
================================================
const fs = require('fs')
const path = require('path')
const express = require('express')
const app = express()
const httpolyglot = require('httpolyglot')
const https = require('https')
//////// CONFIGURATION ///////////
// insert your own ssl certificate and keys
const options = {
key: fs.readFileSync(path.join(__dirname,'..','ssl','key.pem'), 'utf-8'),
cert: fs.readFileSync(path.join(__dirname,'..','ssl','cert.pem'), 'utf-8')
}
const port = process.env.PORT || 3012
////////////////////////////
require('./routes')(app)
const httpsServer = httpolyglot.createServer(options, app)
const io = require('socket.io')(httpsServer)
require('./socketController')(io)
httpsServer.listen(port, () => {
console.log(`listening on port ${port}`)
})
================================================
FILE: src/routes.js
================================================
const path = require('path')
const express = require('express')
module.exports = (app) => {
// redirect http traffic to https traffic
/*app.use('*', (req, res, next) => {
if(!req.socket.encrypted){
console.log('unsecure connection: redirecting..')
res.redirect('https://' + req.headers.host + req.path)
} else {
next()
}
})*/
app.use(express.static(path.join(__dirname, '..','public')))
app.use(express.static(path.join(__dirname, '..','node_modules')))
}
================================================
FILE: src/socketController.js
================================================
peers = {}
module.exports = (io) => {
io.on('connect', (socket) => {
console.log('a client is connected')
// Initiate the connection process as soon as the client connects
peers[socket.id] = socket
// Asking all other clients to setup the peer connection receiver
for(let id in peers) {
if(id === socket.id) continue
console.log('sending init receive to ' + socket.id)
peers[id].emit('initReceive', socket.id)
}
/**
* relay a peerconnection signal to a specific socket
*/
socket.on('signal', data => {
console.log('sending signal from ' + socket.id + ' to ', data)
if(!peers[data.socket_id])return
peers[data.socket_id].emit('signal', {
socket_id: socket.id,
signal: data.signal
})
})
/**
* remove the disconnected peer connection from all other connected clients
*/
socket.on('disconnect', () => {
console.log('socket disconnected ' + socket.id)
socket.broadcast.emit('removePeer', socket.id)
delete peers[socket.id]
})
/**
* Send message to client to initiate a connection
* The sender has already setup a peer connection receiver
*/
socket.on('initSend', init_socket_id => {
console.log('INIT SEND by ' + socket.id + ' for ' + init_socket_id)
peers[init_socket_id].emit('initSend', socket.id)
})
})
}
================================================
FILE: ssl/cert.pem
================================================
-----BEGIN CERTIFICATE-----
MIICAzCCAWwCCQDUB0G6UpqG9DANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMCAXDTIwMDUwMzE1MjA0MloYDzIyOTQwMjE2MTUyMDQyWjBFMQsw
CQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJu
ZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1
NxC8z2k7jEQkF0YkuOSqCBtc4dsb+RgPSkXu6NFMf8g59Sg7EBK29ledRlrszF1j
9ztpgvZCy2PwpJCWu5efCacifRpDege2fPACDre/v04vWfRCzQzgwu21kM9tBPyf
VZlM1Z5tjcbmAKrE8/DSMWxgiBP/o343UpZkQ5VLYQIDAQABMA0GCSqGSIb3DQEB
CwUAA4GBAK+uEcDRU4BnLkr47TZ4mYm7F9h2OXAqf/9FXrH6iwBqiVenG9j8GHop
lLZ0uAkTGUkSKlqe7imaRogKOKWzB89BTl4M3NqheeIlR5rgb8T6h73CIZViVdyF
nid/BTSwWY3eu9qeIL6tLf4977ExfIUZCVlL739pasTx6wXQ1h5i
-----END CERTIFICATE-----
================================================
FILE: ssl/key.pem
================================================
-----BEGIN RSA PRIVATE KEY-----
MIICXwIBAAKBgQC1NxC8z2k7jEQkF0YkuOSqCBtc4dsb+RgPSkXu6NFMf8g59Sg7
EBK29ledRlrszF1j9ztpgvZCy2PwpJCWu5efCacifRpDege2fPACDre/v04vWfRC
zQzgwu21kM9tBPyfVZlM1Z5tjcbmAKrE8/DSMWxgiBP/o343UpZkQ5VLYQIDAQAB
AoGBAKdQplWOM51Vjvy7uHnjdM1BGvKRXlrfjMVhUFyb/dNiEB7jKjgOSRlBLff6
o60LrBbGiMaMso9Nd4MSjqV9oykYQ3zhdSV/W8W7nA5OhXzgJF4cngkrwnxWhLYA
mqH8GJWCvz7/7A8Ckeutx8Zn1GTJWcd/CfSd9SDNGAJqJA4BAkEA3RYhdATWwjhZ
KsWwFMVzG7fBqCvJLO/Ep9kIvpLtiaQjyNtftS8Nw91IpEVodTAtxxiVBiFRiFxS
3yTWVUOEsQJBANHVDejleEYvT/Rc7aN0bhJVcZuhaN9qPDIXY5UIQTYR1NzgcQKA
qCo7u0x0+vQiYJ15YqrRo8ZkdqvyCNlmnbECQQC2KrYF0rbh8WwHQjyD4O2nuRFo
cCujSyzO4JXD8WyoLQcPSTLjJ5JAAOUJ9ebMKJaPpkGke2+i2++szb2NI8UBAkEA
kKoBmAK0hDbUOdXjpGB+DrfHxpNmmTlF3QcRCcuSIfPzPICkiSQoTE24GMNBzRTy
ZT8tzjUQY5QZ2PvaLAA6UQJBAJXLnYMIX0ZkO1nG6mMIWoZtrLpfg+pSFa0VUsHs
7G3iYf9v31Wd6HipMRzg/q63m90nwI5emTYe+EEo4lkGnz8=
-----END RSA PRIVATE KEY-----