title: Multi Party - Video Conference with peerJS
body: <p>I tried webRTC video conference with peerJS and succeeded in one-to-one conference, i need some assistance to take forward this for multiparty video conference, here i try to explain what i did please assist from where i struck</p>
  <p>here i am posting my full code so that anyone can use by modifying few info,</p>
  <blockquote>
  <p><strong>INTENSION</strong></p>
  </blockquote>
  <ol>
  <li>Alice login and calls to Bob</li>
  <li>Bob Joins Alice Call</li>
  <li>Alice calls Carole (using Add Call Button)</li>
  <li>Carole Joins Alice Call</li>
  <li>Create Conference between Alice, Bob and Carole(using Join Call button)</li>
  </ol>
  <blockquote>
  <p><em>achived</em></p>
  </blockquote>
  <ol>
  <li>Alice login and calls to Bob</li>
  <li>Bob Joins Alice Call</li>
  <li>Alice calls Carole</li>
  <li>Carole Joins Alice</li>
  </ol>
  <blockquote>
  <p><strong>mycase</strong></p>
  </blockquote>
  <ul>
  <li>Signup and created Alice.</li>
  <li>Alice logins. goes to CALLS using navigation.</li>
  <li>creates Bob and Carole using Add contact.</li>
  <li>Goes to Phone book made call to BOB.</li>
  <li>Bob joined Alice.</li>
  <li>Alice made another call to Carole using add call.</li>
  <li>Carole joins Alice.</li>
  <li>Alice has bob and carole where bob and carole missing each other.</li>
  <li>yet to merge their streams!</li>
  </ul>
  <blockquote>
  <p><strong>PENDING</strong> &amp; <em>need Assistance</em></p>
  </blockquote>
  <ol start="5">
  <li>Create Conference between Alice, Bob and Carole
  when i click on join conference bob and carole too should have alice</li>
  </ol>
  <p>how to make conference between Alice, Bob and Carole ?</p>
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>/*-----------------
      indexedDB
  ------------------*/
  var iDB;
  const openiDB = indexedDB.open('webRTC',1)
  openiDB.onupgradeneeded = function(){
      iDB = openiDB.result
      if(!iDB.objectStoreNames.contains('contacts')){
              iDB.createObjectStore('contacts',{keyPath:'mobile',unique:true})
      }
      if(!iDB.objectStoreNames.contains('callLog')){
          iDB.createObjectStore('callLog',{keyPath:'id',autoIncrement:true})
      }
      if(!iDB.objectStoreNames.contains('chatLog')){
          iDB.createObjectStore('chatLog',{keyPath:'mobile'})
      }
  }
  openiDB.onsuccess = function(){
      iDB = openiDB.result
  }
  openiDB.onerror = function(){
      console.log(openiDB.error)
  }
  
  /*-----------------
      jQuery
  ------------------*/
  $(document).ready(function(){
      /*-----------------
          functions
      ------------------*/
      function gIndex(){
          $("body").append(
              $("&lt;footer&gt;").text("2020 All rights reserved to SIKANDAR")
          )
      }
      function uIndex(){
          $("main").empty().append(
              $("&lt;div&gt;").attr('class','error'),
              $("&lt;article&gt;").attr('class','welCome').append(
                  $("&lt;h2&gt;").attr('class','aH2').text("Hello "+user.fName),
                  $("&lt;aside&gt;").attr('class','msg')
              ),
              $("&lt;nav&gt;").append(
                  $("&lt;span&gt;").attr({'class':'navIcon','id':'meetings'}).text("MEETINGS"),
                  $("&lt;span&gt;").attr({'class':'navIcon','id':'calls'}).text("CALLS"),
   $("&lt;span&gt;").attr({'id':'logo'}).text("HOME"),
                  $("&lt;span&gt;").attr({'class':'navIcon','id':'chats'}).text("CHATS"),
                  $("&lt;span&gt;").attr({'class':'navIcon','id':'settings'}).text("MENU")
              ),
              $("&lt;section&gt;").attr('class','modal').append(
                  $("&lt;span&gt;").attr('class','mClose').text("X"),
                  $("&lt;div&gt;").attr('class','mWrap').append(
                      $("&lt;aside&gt;").attr('class','contacts').append(
                          $("&lt;div&gt;").attr('class','formContact'),
                          $("&lt;div&gt;").attr('class','showContacts')
                      ),
                      $("&lt;aside&gt;").attr('class','players').append(
                          $("&lt;div&gt;").attr('class','row').append(
                              $("&lt;video playsinline muted&gt;").attr({'controls':false,'id':'lCam','preload':'none'})
                          ),
                          $("&lt;div&gt;").attr('class','row'),
                          $("&lt;div&gt;").attr('class','row2').append(
                              $("&lt;button&gt;").attr('id','aCall').text("ADD"),
                              $("&lt;button&gt;").attr('id','jCall').text("JOIN").prop('disabled',true),
                              $("&lt;button&gt;").attr('id','eCall').text("END")
                          )
                      )
                  ),
              ),
              $("&lt;section&gt;").attr('class','iScreen').append()
          )
          $('.modal,.contacts,.players,.iScreen,.error').hide()
      }
      function lCamCheck(cT,iCall){
          let lCam = $("#lCam")[0]
          switch(cT){
              case 'incoming':{
                  switch(lCam.srcObject){
                      case null:{
                          navigator.mediaDevices.getUserMedia(constraints).then(stream=&gt;{
                              lCam.srcObject = stream;
                              iCall.answer(stream)
                              lCam.play()
                          }).catch(error=&gt;console.error(error))
                          break;
                      }
                      default:{
                          iCall.answer(lCam.srcObject)
                          break;
                      }
                  }
                  break;
              }
              case 'outgoing':{
                  switch(lCam.srcObject){
                      case null:{
                          navigator.mediaDevices.getUserMedia(constraints).then(stream=&gt;{
                              lCam.srcObject = stream;
                              let outGoing = socket.call(iCall,stream)
                              lCam.play()
                              outGoing.on('stream',rStream=&gt;{
                                  rCamCheck(iCall,rStream)
                                  $("button[value='"+iCall+"']").prop('disabled',true)
                                  $(".players").show()
                              },err=&gt;console.error(err))
                              addLog(iCall,'outgoing')
                          }).catch(error=&gt;console.error(error))
                          break;
                      }
                      default:{
                          let outGoing = socket.call(iCall,lCam.srcObject)
                          lCam.play()
                          outGoing.on('stream',rStream=&gt;{
                              rCamCheck(iCall,rStream)
                              $("button[value='"+iCall+"']").prop('disabled',true)
                          },err=&gt;console.error(err))
                          addLog(iCall,'outgoing')
                          break;
                      }
                  }
                  break;
              }
          }
      }
      function rCamCheck(m,rS){
          let rCam = $("video[name='"+m+"']")[0]
          if(!rCam){
              $(".players .row:nth-child(2)").append(
                  $("&lt;video playinline&gt;").attr({'class':'rCam','name':m,'controls':false,'preload':'none'})
              )
              $("video[name='"+m+"']")[0].srcObject = rS
              $("video[name='"+m+"']")[0].play()
          }else{ // function is repeating two times hence writing else
              
              $("video[name='"+m+"']")[0].pause()
              //$("video[name='"+m+"']")[0].srcObject = rS
              $("video[name='"+m+"']")[0].play()
              console.log(socket)
          }
      }
      function addLog(m,t){
          let dt = new Date()
          let log = {
              'date' : dt.toLocaleDateString('en-CA'),
              'time' : dt.toLocaleTimeString('en-CA',{hour12:false}),
              'mobile': m,
              'type' : t
          }
          let addLog = iDB.transaction(["callLog"],'readwrite').objectStore("callLog").add(log)
          addLog.onerror = function(){
              console.error(addLog.error)
          }
      }
      function showLog(){
          let sLog = iDB.transaction(["callLog"]).objectStore("callLog").getAll()
          sLog.onsuccess = function(){
              if(sLog.result.length&gt;0){
                  $(".callLog").empty().append(
                      $("&lt;div&gt;").attr('class','row1').append(
                          $("&lt;span&gt;").attr('class','cSpan').text("DATE"),
                          $("&lt;span&gt;").attr('class','cSpan').text("TIME"),
                          $("&lt;span&gt;").attr('class','cSpan').text("MOBILE"),
                          $("&lt;span&gt;").attr('class','cSpan').text("TYPE")
                      )
                  )
                  $.each(sLog.result,function(k,v){
                      $("&lt;div&gt;").attr('class','row1').append(
                          $("&lt;span&gt;").attr('class','cSpan').text(v.date),
                          $("&lt;span&gt;").attr('class','cSpan').text(v.time),
                          $("&lt;span&gt;").attr('class','cSpan').text(v.mobile),
                          $("&lt;span&gt;").attr('class','cSpan').text(v.type)
                      ).appendTo(".callLog")
                  })
              }else{
                  $(".callLog").empty().append(
                      $("&lt;div&gt;").attr('class','row1').append(
                          $("&lt;span&gt;").attr('class','cSpan').text("DATE"),
                          $("&lt;span&gt;").attr('class','cSpan').text("TIME"),
                          $("&lt;span&gt;").attr('class','cSpan').text("MOBILE"),
                          $("&lt;span&gt;").attr('class','cSpan').text("TYPE")
                      ),
                      $("&lt;div&gt;").attr('class','row').text("No Call Records")
                  )
              }
          }
          sLog.onerror = function(){
              console.log(sLog.error)
          }
      }
      function phBook(){
          let lCNT = iDB.transaction(["contacts"]).objectStore("contacts").getAll()
          lCNT.onsuccess = function(){
              $(".showContacts").empty().append(
                  $("&lt;div&gt;").attr('class','row2').append(
                      $("&lt;span&gt;").text("DP"),$("&lt;span&gt;").text("NAME"),$("&lt;span&gt;").text("CALL")
                  )
              )
              if(lCNT.result.length&gt;0){
                  $.each(lCNT.result,function(k,v){
                      $(".showContacts").append(
                          $("&lt;div&gt;").attr('class','row2').append(
                              $("&lt;img&gt;").attr({'alt':'DP'}),
                              $("&lt;span&gt;").text(v.fName),
                              $("&lt;button&gt;").attr({'value':v.mobile,'class':'vCall'}).text("CALL")
                          )
                      )
                  })
              }else{
                  $(".showContacts").append(
                      $("&lt;div&gt;").attr('class','row').text("No Contacts")
                  )
              }
          }
          lCNT.onerror = function(){
              console.error(lCNT.error)
          }
      }
      function showLogin(){
          $("main").append(
              $("&lt;article&gt;").append(
                  $("&lt;aside&gt;").attr('class','login').append(
                      $("&lt;fieldset&gt;").append(
                          $("&lt;legend&gt;").text("User Login"),
                          $("&lt;form&gt;").attr('id','formLogin').append( 
                              $("&lt;div&gt;").attr('class','row').append(
                                  $("&lt;input&gt;").attr({
                                      'type':'text','name':'lMobile','placeholder':'Enter Mobile Number',
                                      'pattern':'[0-9]{10}','title':'Enter 10 digit mobilenumbers without country code'
                                  }).prop('required',true),
                                  $("&lt;label&gt;").attr('for','lMobile').text("*")
                              ),
                              $("&lt;div&gt;").attr('class','row').append(
                                  $("&lt;input&gt;").attr({'type':'submit','value':'LOGIN'}),
                                  $("&lt;input&gt;").attr({'type':'button','name':'signUP','value':'SIGNUP'})
                              )
                          )
                      )
                  ),
                  $("&lt;aside&gt;").attr('class','signup').append(
                      $("&lt;fieldset&gt;").append(
                          $("&lt;legend&gt;").text("User Registration"),
                          $("&lt;form&gt;").attr('id','formSignup').append(
                              $("&lt;div&gt;").attr('class','row').append(
                                  $("&lt;input&gt;").attr({
                                      'type':'text','name':'fName','placeholder':'Enter First Name'
                                  }).prop('required',true),
                                  $("&lt;label&gt;").attr('for','fName').text("*")
                              ),
                              $("&lt;div&gt;").attr('class','row').append(
                                  $("&lt;input&gt;").attr({
                                      'type':'text','name':'lName','placeholder':'Enter Last Name'
                                  }).prop('required',true),
                                  $("&lt;label&gt;").attr('for','lName').text("*")
                              ),
                              $("&lt;div&gt;").attr('class','row').append(
                                  $("&lt;input&gt;").attr({'type':'email','name':'email','placeholder':'Enter e-Mail Id'}),
                                  $("&lt;label&gt;").attr('for','email').text("*")
                              ),
                              $("&lt;div&gt;").attr('class','row').append(
                                  $("&lt;input&gt;").attr({
                                      'type':'tel','name':'mobile','placeholder':'Enter Mobile Number',
                                      'pattern':'[0-9]{10}','title':'Enter 10 digit mobile number with out country code'
                                  }).prop('required',true),
                                  $("&lt;label&gt;").attr('for','mobile').text("*")
                              ),
                              $("&lt;div&gt;").attr('class','row').append(
                                  $("&lt;input&gt;").attr({'type':'submit','value':'REGISTER'}),
                                  $("&lt;input&gt;").attr({'type':'reset','value':'reset'})
                              )
                          )   
                      )
                  )
              )
          )
          $(".signup").hide()
      }
  
      /*-----------------
          appShell
      ------------------*/
      let user;let uAuth = localStorage.getItem('uAuth');
      if(typeof(user)=="undefined"){
          user = JSON.parse(localStorage.getItem('user'))
      }
      let iceServers = {
          'iceServers':[{'urls':'stun:stun.l.google.com:19302'},{'urls':'stun:stun1.l.google.com:19302'}]
      }
      //console.log(navigator.mediaDevices.getSupportedConstraints())
      let constraints = {
          'video':{'width':640,'height':480,'frameRate':{'max':24}},
          'audio':{'echoCancellation':true,'noiseSuppression':true,'sampleSize':8}
      }
      let socket; let peerJS = {'host':'localhost','port':9000,'path':'/myapp','config':iceServers};
  
      switch(uAuth){
          case null:{
              gIndex();
              showLogin()
              break;
          }
          case 'true':{
              uIndex()
              socket = new Peer(user.mobile,peerJS)
              socket.on('open',function(){
                  console.log(socket.id+" Connceted")
              })
              socket.on('error',function(error){
                  $(".error").text(error.message).show()
                  //$(".aH1").css('color','firebrick')
                  let lCam = $("#lCam")[0];let rCams = $("video")
                  lCam.srcObject.getTracks().forEach(track=&gt;{track.stop();$(".modal").hide()})
                  if(rCams.length&gt;1){
                      let rCam = $("video")
                      rCam[1].srcObject.getTracks().forEach(track=&gt;track.stop())
                  }
              })
              socket.on('call',async function(inComing){
                  $('.iScreen').show()
                  await lCamCheck('incoming',inComing)
                  await phBook()
                  await inComing.on('stream',rStream=&gt;{
                      rCamCheck(inComing.peer,rStream)
                      $(".modal,.players").show()
                      $("button[value='"+inComing.peer+"']").prop('disabled',true)
                  },err=&gt;console.error(err))
                  await addLog(inComing.peer,'incoming')
              })
          }
      }
  
      $(document).on('submit','form',function(e){
          e.preventDefault()
      })
      $(document).on('click','input[name="signUP"]',function(){
          $(".login").hide(),
          $(".signup").show()
      })
      $(document).on('submit','#formSignup',async function(){
          let nUser = await {
              fName : $("input[name='fName']").val(),
              lName : $("input[name='lName']").val(),
              email : $("input[name='email']").val(),
              mobile : $("input[name='mobile']").val()
          }
          await localStorage.setItem('user',JSON.stringify(nUser))
          location.reload()
      })
      $(document).on('submit','#formLogin',function(){
          let mobile = $("input[name='lMobile']").val();
          switch(user){
              case null:{
                  $("input[name='lMobile']").val("")
                  $("label[for='lMobile']").text(" please signup").css('color','firebrick')
                  break;
              }
              default:{
                  if(mobile == user.mobile){
                      localStorage.setItem('uAuth',true)
                      location.reload()
                  }else{
                      $("input[name='lMobile']").val("")
                      $("label[for='lMobile']").text(" re-check mobile").css('color','firebrick')
                  }
              }
          }
          
      })
      $(document).on('click','.navIcon',function(){
          let text = $(this).attr('id')
          $("article").remove()
          $("&lt;article&gt;").empty().attr("class",text).append(
              $("&lt;h2&gt;").attr('class','aH2').text(text.toUpperCase())
          ).insertBefore("nav")
      })
      $(document).on('click','#calls',function(){
          $(".calls").append(
              $("&lt;aside&gt;").append(
                  $("&lt;div&gt;").attr('class','row2').append(
                      $("&lt;img&gt;").attr({
                          'alt':'addCNT','src':'assets/addContact.svg','type':'image/svg+xml','id':'aCNT','class':'cOPT'
                      }),
                      $("&lt;img&gt;").attr({
                          'alt':'phBook','src':'assets/phBook.svg','type':'image/svg+xml','id':'phBook','class':'cOPT'
                      })
                  )
              ),
              $("&lt;aside&gt;").attr('class','callLog').append(
                  $("&lt;h3&gt;").attr('class','aH3')
              )
          )
          showLog()
      })
      $(document).on('click','#aCNT',function(){
          $(".showContacts").hide()
          $(".formContact").empty().append(
              $("&lt;fieldset&gt;").append(
                  $("&lt;legend&gt;").text("ADD CONTACT"),
                  $("&lt;form&gt;").attr('id','formCNT').append(
                      $("&lt;div&gt;").attr('class','row').append(
                          $("&lt;input&gt;").attr({
                              'type':'text','name':'fName','placeholder':'Enter First Name'
                          }).prop('required',true),
                          $("&lt;label&gt;").attr('for','fName')
                      ),
                      $("&lt;div&gt;").attr('class','row').append(
                          $("&lt;input&gt;").attr({
                              'type':'text','name':'lName','placeholder':'Enter Last Name'
                          }).prop('required',true),
                          $("&lt;label&gt;").attr('for','lName')
                      ),
                      $("&lt;div&gt;").attr('class','row').append(
                          $("&lt;input&gt;").attr({
                              'type':'tel','name':'mobile','placeholder':'Enter Mobile Number',
                              'pattern':'[0-9]{10}','title':'Enter 10 digit mobile number with out country code'
                          }).prop('required',true),
                          $("&lt;label&gt;").attr('for','mobile')
                      ),
                      $("&lt;div&gt;").attr('class','row').append(
                          $("&lt;input&gt;").attr({'type':'submit','value':'ADD'}),
                          $("&lt;input&gt;").attr({'type':'reset','value':'reset'})
                      )
                  )
              )
          )
          $(".modal,.contacts").show()
      })
      $(document).on('submit','#formCNT',function(){
          let contact = {
              fName: $("input[name='fName']").val(),
              lName: $("input[name='lName']").val(),
              mobile: $("input[name='mobile']").val()
          }
          let aCNT = iDB.transaction(["contacts"],'readwrite').objectStore("contacts").put(contact)
          aCNT.onsuccess = function(){
              $(".formContact").empty()
              $(".modal,.contacts").hide()
          }
          aCNT.onerror = function(){
  
          }
      })
      $(document).on('click','#phBook',function(){
          $(".modal,.contacts,.showContacts").show()
          phBook()
      })
      $(document).on('click','.mClose',function(){
          $(".modal").hide()
      })
      $(document).on('click','.vCall',function(){
          $(".contacts").hide()
          lCamCheck('outgoing',$(this).val())
          //check rcam from here if possible
      })
      $(document).on('click','#aCall',function(){
          $(".contacts").toggle()
      })
      $(document).on('click','#eCall',function(){
          location.reload(true)
      })
  })</code></pre>
  <pre class="snippet-code-css lang-css prettyprint-override"><code>html,body{margin: 0;padding: 0;background-color: silver;}
  header{background-color: teal;text-align: center;box-shadow: 0 10px 8px black;}
  .aH1{margin: auto;}            
  .aH2{margin: auto 10px;}
  main{display: flex; flex-direction: column;margin-top: 20px;}
  article{display: flex;flex-direction: column;}
  aside{display: flex;flex-direction: column;padding: 5px;}
  .row{display: flex; flex-direction: row;justify-content: center;margin:5px;}
  .row&gt;input{margin:5px}
  .row1{display: flex;justify-content: space-around;}
  .row2{display: flex;justify-content: space-between;margin:5px 0}
  .cOPT{height:5vh}
  .error{border-radius: 4px;border:2px dashed firebrick; background-color: rgba(0,0,0,0.6);color: white;text-align: center;}
  nav{display: flex;align-items: center;position: fixed;
      bottom: 0;width: 100vw;justify-content: space-around;background-color: teal;border-top: 1px solid black;}
  .navIcon{height:5.4vh}
  video{width:30vw;transform: scaleX(-1);-webkit-transform: scaleX(-1);transform: rotateZ(1turn);-webkit-transform: rotateZ(1turn);}
  .rCam{margin:5px}
  footer{display: flex; justify-content: center;position: fixed;bottom: 0;width: 100vw;}
  .modal{position: fixed;top: 0;left: 0;width: 100vw;display: flex;flex-direction: column;padding: 0.5%;
                  background-color: rgba(0,0,0,0.6);height: 100vh;z-index: 1;justify-content: flex-start}
  .mClose{color: red;text-align: right;cursor: default;font-size: 1.5em;}
  .mWrap{color:white;overflow-y: scroll;flex-direction: column;width:99vw;margin-bottom:5.4vh}
  .players&gt;div:nth-child(3){flex-flow:row-wrap;}</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;!DOCTYPE html&gt;
  &lt;html lang="en-US"&gt;
      &lt;head&gt;
          &lt;meta charset="UTF-8"&gt;
          &lt;meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1"&gt;
          &lt;meta name="mobile-web-app-capable" content="yes"&gt;
          &lt;meta name="apple-mobile-web-app-capable" content="yes"&gt;
          &lt;meta name="google" content="notranslate"&gt;
          &lt;meta name="description" content="webRTC video social networking"&gt;
          &lt;meta name="author" content="Sikandar"&gt;
          &lt;script type="text/javascript" src="js/jquery-3.5.1.min.js"&gt;&lt;/script&gt;
          &lt;script type="text/javascript" src="js/peerjs.min.js"&gt;&lt;/script&gt;
          &lt;title&gt;webRTC&lt;/title&gt;
      &lt;/head&gt;
      &lt;body&gt;
          &lt;header&gt;&lt;h1 class="aH1"&gt;webRTC&lt;/h1&gt;&lt;/header&gt;
          &lt;main&gt;&lt;/main&gt;
      &lt;/body&gt;
  &lt;/html&gt;</code></pre>
  </div>
  </div>
  </p>