Skip to main content

WebRTC Introduction - Client Side Signalling

Shenzhen, China

WIP

Resources:

WebRTC Introduction - Video Chat

Get Video Source

Get Video from Webcam with getUserMedia():

chat.js

const chatLobby = document.getElementById('chat-lobby')
const chatRoom = document.getElementById('room-name')
const chatJoin = document.getElementById('join')
const chatLocal = document.getElementById('video-local')

let constraints = {
    audio: false,
    video: { width: 320, height: 180 },
}

const browserSupportsMedia = () => {
    return navigator.mediaDevices.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.mzGetUserMedia
}

const fetchLocalStream = () => {
    navigator.mediaDevices
    .getUserMedia(constraints)
    .then(function (stream) {
      userStream = stream;
      chatLobby.style = "display:none";
      chatLocal.srcObject = stream;
      chatLocal.onloadedmetadata = function (e) {
        chatLocal.play();
      }
    })
    .catch(function (err) {
      /* handle the error */
      alert('ERROR ::  ' + error.name);
    })
}

chatJoin.addEventListener('click', function() {
    if (chatRoom.value == "") {
        alert('INFO :: Please enter a room name first!')
    } else {
        fetchLocalStream()
    }
})

Signalling with Websockets

Use socket.io to manage connections:

index.js

const express = require("express");
const socket = require("socket.io");

const port = 6969

var app = express();

var server = app.listen(port, function () {
    console.log('INFO :: Webserver is running on ::  http://localhost:' + port)
});

app.use(express.static("public"));

var io = socket(server);

io.on("connection", function (socket) {
  console.log("INFO :: Websocket connection established :: ", socket.id)

  socket.on("join", function (roomName) {
    // check if is already full = 2 participants
    let rooms = io.sockets.adapter.rooms;
    let room = rooms.get(roomName);
    // if room doesn't exists create it
    if(room == undefined) {
      socket.join(roomName)
      console.log('INFO :: Participant created room ::', roomName)
      // if room exists and only has 1 participant - join
    } else if(room.size == 1) {
      socket.join(roomName)
      console.log('INFO :: Participant joined room ::', roomName)
    } else {
      console.log('WARNING :: Room', roomName, 'is full!')
    }
  })
});

Use the websocket connection to emit the join event when a user clicked the join button:

chat.js

const socket = io.connect("http://localhost:6969")

chatJoin.addEventListener('click', function() {
    if (chatRoom.value == "") {
        alert('INFO :: Please enter a room name first!')
    } else {
        socket.emit("join", chatRoom.value)
        fetchLocalStream()
    }
})

Start the application and open the application frontend from three different devices:

npm start

> webrtc-intro@1.0.0 start
> node index.js

INFO :: Webserver is running on ::  http://localhost:6969
INFO :: Websocket connection established ::  _I-Guhml-JEa9VJwAAAF
INFO :: Participant created room :: test
INFO :: Websocket connection established ::  TAaYXAYJDbm4lDP5AAAH
INFO :: Participant joined room :: test
INFO :: Websocket connection established ::  lAgg07QE2DSltIwsAAAJ
WARNING :: Room test is full!

Scaffolding the Signalling Server

index.js

io.on("connection", function (socket) {
  console.log("INFO :: Websocket connection established :: ", socket.id)

  socket.on("join", function (roomName) {
    // check if is already full = 2 participants
    let rooms = io.sockets.adapter.rooms;
    let room = rooms.get(roomName);
    // if room doesn't exists create it
    if(room == undefined) {
      socket.join(roomName)
      socket.emit('created')
    } else if(room.size == 1) {
      socket.join(roomName)
      socket.emit('joined')
    } else {
      socket.emit('occupied')
    }
  })

  // emit ready state to room when created
  socket.on('ready', function(roomName) {
    socket.broadcast.to(roomName).emit('ready')
    console.log('INFO :: Room is ready')
  })

  // establish connection between candidates (ICE)
  socket.on('candidate', function(candidate, roomName) {
    socket.broadcast.to(roomName).emit('candidate', candidate)
    console.log('INFO :: Candidate ready to establish connection')
  })

  // make offer to other participant
  socket.on('offer', function(offer, roomName) {
    socket.broadcast.to(roomName).emit('offer', offer)
    console.log('INFO :: Offer to establish connection')
  })

  // send answer to connection offer
  socket.on('answer', function(answer, roomName) {
    socket.broadcast.to(roomName).emit('answer', answer)
    console.log('INFO :: Answer to connection offer')
  })

});

Client Side Signalling Events

The server now emits each of those events through the websocket connection. We now need to handle those events on the client side:

Created, Joined and Occupied

chat.js

let creator = false

chatJoin.addEventListener('click', function() {
    if (chatRoom.value == "") {
        alert('INFO :: Please enter a room name first!')
    } else {
        socket.emit("join", chatRoom.value)
    }
})

// Room created event -> fetch video
socket.on('created', function() {
  creator = true
  fetchLocalStream()
})

// Room joined event -> fetch video
socket.on('joined', function() {
  creator = false
  fetchLocalStream()
})

// Room is full event
socket.on('occupied', function() {
  alert("WARNING :: Room occupied. Please try again later.")
})