Table of Contents

Video conference room

wrtc_kit supports group calls by using mesh architecture there every participant is conntected to all other call participants. This solution is useful for ad-hoc meetings, but do not scale well. The number of connections increase exponentially.

Instead you can host meeting rooms on remote servers. Usually this requires advanced soft- and hardware to mix video streams in real time. Using wrtc_kit and screenshare you can however create your own video conference room servers.

It is done by creating a special page participants can call to. The page renders the received video streams to a webpage - and sends out a screenshare of that webpage to all participants. Thus video stream mixing is done by web server and hardware. Audio mixing is done using html5 audio contexts. Everyone calls in to the conference room as a single call.

Please note this is not the way you usually implement group calls. The usual way is described here. This is an alternative way.

The code

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<meta http-equiv="X-UA-Compatible" content="chrome=1"/>
<script src="/luci-static/resources/adapter.js"></script>
<script src="/luci-static/resources/wrtc_kit.js"></script>
</head><body style="overflow:hidden;">

<script type="text/javascript">
//# Log many (but unfortunately still not all) errors appearing in the javascript log into wrtc_log too
window.onerror = function(e, url, line) {wrtc.moduleLog('SYSTEM', e + ' at ' + url + ':' + line)};

var nr_calls = 0;
var audioContext = new window.AudioContext();
var dummyAudio = audioContext.createMediaStreamDestination();

wrtc.init = function() {
  wrtc.maxNrCalls = 16;
  wrtc.addressName = "conference";
  wrtc.videoBandwidth = 900;
  wrtc.adv.requestBandwidth = 150;
  document.title = "CONFERENCE";
  wrtc.adv.getMedia = false;
  wrtc.getAudio = false;
  wrtc.getScreen(true);
}

wrtc.onLocalMedia = function(success) {
  if (success) {
    document.getElementById("status").innerHTML = "";
    wrtc.i.localStream.addTrack(dummyAudio.stream.getAudioTracks()[0]);   // placeholder to be replaced in onTalking
  }
}

wrtc.onIncomingCall = function(callobj) {
  callobj.answerCall();
}

// For each new call object dynamically create a div containing a status field and video window
wrtc.onNewCallObject = function(callobj) {
  var div = document.createElement('div');
  var statusdiv = document.createElement('div');
  statusdiv.style = "width:100%;white-space:nowrap;overflow:hidden;position:absolute;text-shadow:1px 1px white;text-align:center;font-size:28;";
  statusdiv.innerHTML = callobj.getPartner();
  div.appendChild(statusdiv);
  var video = document.createElement('video');
  video.autoplay = "autoplay";
  video.volume = 0.01;
  video.style.height = "100%";
  video.style.width = "100%";
  div.appendChild(video);
  document.body.appendChild(div);
  div.style.position = "absolute";
  div.style.display = "block";
  callobj.remoteVideo = video;
  callobj.user = {};
  callobj.user.div = div;
  callobj.user.status = statusdiv;
  callobj.user.mixedAudio = audioContext.createMediaStreamDestination();
  callobj.user.div.style.top = ((nr_calls / 4) | 0) * 25 + "%";
  callobj.user.div.style.left = (nr_calls % 4) * 25 + "%";
  callobj.user.div.style.width = "25%";
  callobj.user.div.style.height = "25%";
  nr_calls += 1;
  window.scrollTo(0,0);
}

// replace dummy audio track placeholder with a mix of all incoming audio tracks except this one (caller will hear everyone except herself)
wrtc.onTalking = function(callobj) {
  if (!callobj.user.audio) {
    callobj.x.pc.getSenders().forEach(function(pc_sender) {if ((pc_sender.track) && (pc_sender.track.kind == "audio")) audiosender = pc_sender});
    callobj.user.rcvdAudio = audioContext.createMediaStreamSource(callobj.x.pc.getRemoteStreams()[0]);
    wrtc.getCalls().forEach(function(call) {
      if (call != callobj) {
        call.user.rcvdAudio.connect(callobj.user.mixedAudio);
        callobj.user.rcvdAudio.connect(call.user.mixedAudio);
      }
    });
    audiosender.replaceTrack(callobj.user.mixedAudio.stream.getAudioTracks()[0]);
    callobj.user.audio = true;
  }
}

// Delete dynamically created elements
wrtc.onEndCallObject = function(callobj) {
  callobj.user.div.outerHTML = "";
  callobj.user.rcvdAudio.disconnect();
}

// forward incoming chat messages to everyone else in conference,
wrtc.onChatMessage = function(msg) {
    wrtc.getCalls().forEach(function(call) {
      if (msg.fromAddress != call.getPartner()) {
        if (msg.format == "iscomposing")
          wrtc.sendChatMessage(call.getPartner(), 0);
        else
          wrtc.sendChatMessage(call.getPartner(), msg.fromAddress + ": " + msg.message);
      }
    });
}
</script>

<div id="status" style="font-family:Arial">Please share<br/>Chrome tab CONFERENCE</div>
</body></html>

wrtc.init()

By setting wrtc.adv.getMedia = false we specify that page should not request video stream from local camera.
By setting wrtc.getAudio = false we specify that page should not rquest audio from local microphone either.
Otherwise wrtc_kit does these requests automatically upon start, as you will need them when calling someone. But now you will not use local sources, only mix received audiovideo streams.

wrtc.getScreen(true) requests screen sharing instead. All connecting clients will receive a screen share of this browser tab that displays all received video streams. To be able to work properly user must allow screen share of this web server tab. That is why we request "Please share Chrome tab CONFERENCE" in the html body.

wrtc.onLocalMedia()

When user has allowed screen share wrtc.onLocalMedia(true) will occur. As screen share does not contain any audio we need to add a fake audio track as a placeholder for the future mixed audio track.

wrtc.onIncomingCall()

If anyone calls in to the conference room the call is automatically answered.

wrtc.onNewCallObject()

The new call will create a new call object. In its initialization we create a status field that holds the caller's name, a video object that will display the received media stream, position the object on next available spot on screen (this example creates a 4x4 array of callers) - and create a MediaStreamDestination object that will contain the mixed audio that will be transmitted back to the caller.

wrtc.onTalking()

Once we start receiving media stream from the caller wrtc.onTalking() will occur. Then we do the following: we add the received audio stream to all other clients and add all other clients' audio streams to this one. This is important: all clients calling this conference room will receive back a custom audio stream containing all other clients' voice. You do not send back the callers own voice as it would result in audio feedback. Thus each client has it's own MediaStreamDestination object that mixes audio. The received audio stream is stored in user.rcvdAudio of each call object.

If you have clients A, B and C client A will receive a mixed audio of B and C, client B will receive a mixed audio of A and C, and client C will receive a mixed audio of A and B.

wrtc.onEndCallObject()

Once call ends you need to disconnect the received audio to properly end the audio mixer. You should also add code that rearranges remaining video windows.

wrtc.onChatMessage()

If anyone sends a chat message to the conference room it is broadcasted to everyone else in the room.

Usage

You open the conference room in a web browser, enable screen share of that tab and keep the page running. It is extremely important to share only the contents of Chrome tab CONFERENCE. Not the entire screen nor a window - just the tab contents. Otherwise the transmitted video stream will be wrong and confusing.

To call in to the conference you open a webRTC client in a separate window and call the conference room's address (just like a regular call). Everyone else connecting to the conference does the same.

Now the conference room page will mix the received audio and video and send out a mixed media stream to everyone:

Regular webrtc_kit group calls use mesh architecture there every participant is connected to everyone else. Conference room uses a star architecture instead, there each participant is connected using a single connection only to the conference room.

You can host one or more conference rooms on a central PC that anyone can use. Or people (with reasonably powerful PC-s) start their own conference room on a separate browser tab next to their ordinary webRTC clients.