My last project requires audio/video call feature where user can turn on audio video during the call and can initiate it without other part is online and waiting for the connection. After some research i’ve found WebRTC solution that fit my requirements: RTCMultiConnection

Here i want to demonstrate step by step instruction for audio/video broadcast implementation with firebase stream id sharing

Full source code for this sample can be found on GitHub

To start using this lib we need to include it. On demo i’ll use demo server for streaming purpose, but you can deploy your own server.

<script src="https://rtcmulticonnection.herokuapp.com/dist/RTCMultiConnection.min.js"></script>
<script src="https://rtcmulticonnection.herokuapp.com/node_modules/webrtc-adapter/out/adapter.js"></script>
<script src="https://rtcmulticonnection.herokuapp.com/socket.io/socket.io.js"></script>

To support firebase database, also add required dependencies:

<script src="https://www.gstatic.com/firebasejs/6.1.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/6.1.0/firebase-database.js"></script>

Let’go code. To handle audio/video state i will use simple “model” with 2 fields which reflects checkbox state

  var model = {
    video: $('#video').is(':checked'),
    mic: $('#audio').is(':checked')
  }

On model change we will try to determine what should we do with the sender stream: start/toggle mic,video/stop

If there is no current streaming - create connection and start it.

this.createConnection(videoElem,streamId).then(()=>{
  if (model.video){
    this.connection.mediaConstraints = {
      audio: model.mic,
      video: {
        width: 320,
        height: 240
      },
    };
  }else {
    this.connection.mediaConstraints = {
      audio: true,
      video: false,
    };
  }

  this.connection.session = {
      audio: model.mic,
      video: model.video,
      oneway: true
  };
  this.connection.open(streamId, () =>{    
    if (this.streamPublished){
      this.streamPublished(streamId);
    }    
  });

If we are already online - change stream options - stop/start video or audio stream

  if (model.video && !this.isStreamingVideo()){
      this.connection.sdpConstraints.mandatory = {
          OfferToReceiveAudio: false,
          OfferToReceiveVideo: true
      };
      this.connection.session = {
          audio: false,
          video: true
      };
      this.connection.mediaConstraints = {
          audio: false,
          video: {
            width: 320,
            height: 240
          }
      };
      this.connection.addStream({video:true, oneway:true});
  }
  if (model.mic && !this.isStreamingMic()){
      this.connection.sdpConstraints.mandatory = {
          OfferToReceiveAudio: true,
          OfferToReceiveVideo: false
      };
      this.connection.session = {
          audio: true,
          video: false
      };
      this.connection.mediaConstraints = {
          audio: true,
          video: false
      };
      this.connection.addStream({audio:true, oneway:true});
  }
  if (!model.video && this.isStreamingVideo()){
    const video = this.videoStream();
    video.stop();
  }
  if (!model.mic && this.isStreamingMic()){
    const mic = this.micStream();
    mic.stop();
  }

To stop where both toggles are off

if (this.connection){
  this.connection.getAllParticipants().forEach((pid)=> {
       this.connection.disconnectWith(pid);
  });

   // stop all local cameras
  this.connection.attachStreams.forEach((localStream)=> {
    localStream.stop();
  });

   // close socket.io connection
  this.connection.closeSocket();
  this.connection = null;
}
if (this.streamPublished){
  this.streamPublished(streamId);
}

To send information to other side of broadcast, we need to share streamId, which is just a random genenerated string in demo. To use this we use firebase database storage. When we get streamPublished callback, just update ref on firebase database. Make sure we remove ref on disconnect, so clients can handle disconnection.

var child = database.ref('multi-video').child(streamId);
child.set(true);
child.onDisconnect().remove();

Clients listening the ref on page load, and handle new items appear in it and start playing:

database.ref('multi-video').on('value', function(snapshot){
  snapshot.forEach(function(elem){
      if (elem.key !== userId){
        var remote = new Broadcast({});
        if ($('#video-container #'+elem.key).length === 0){
          $('<div id="'+elem.key+'"/>').appendTo($('#video-container'));
          this.createConnection('#video-container #'+elem.key, elem.key).then(()=>{
            this.connection.sdpConstraints.mandatory = {
                OfferToReceiveAudio: true,
                OfferToReceiveVideo: true
            };
            this.connection.session = {
                audio: true,
                video: true,
                oneway: true
            };
            this.connection.join(streamId);
          });          
        }
      }
  })  
});