Solomon Ayoola
Solomon Ayoola

Reputation: 2669

Serverless Implementation of WebRTC

I do not know what I am doing wrong with implementing a serverless WebRTC.

  1. Create Offer
  2. Pass the offer to remote
  3. Create an answer

I do not know what I am doing wrong with implementing a serverless WebRTC.

Here is what I have so far:

    var localStream, localPeerConnection, remotePeerConnection;
var servers = {"iceServers":[{"url":"stun:23.21.150.121"}]};

var sdpConstraints = {
        optional: [],
        mandatory: {
            OfferToReceiveAudio: true,
            OfferToReceiveVideo: true
        }
    };

var localVideo = document.getElementById("alice");
var remoteVideo = document.getElementById("bob");

var startButton = document.getElementById("startButton");
var callButton = document.getElementById("callButton");
var hangupButton = document.getElementById("hangupButton");
var joinButton = document.getElementById("joinButton");

var localOffer = document.getElementById("localOffer");
var remoteOffer = document.getElementById("remoteOffer");
var localAnswer = document.getElementById("localAnswer");
var remoteAnswer = document.getElementById("remoteAnswer");

var showLocalOffer = document.getElementById("showLocalOffer");
var getRemoteOffer = document.getElementById("getRemoteOffer");
var showLocalAnswer = document.getElementById("showLocalAnswer");
var getRemoteAnswer = document.getElementById("getRemoteAnswer");

var sentLocalOfferButton = document.getElementById("sentLocalOfferButton");
var pasteRemoteOfferButton = document.getElementById("pasteRemoteOfferButton");
var sentLocalAnswerButton = document.getElementById("sentLocalAnswerButton");
var pasteRemoteAnswerButton = document.getElementById("pasteRemoteAnswerButton");

startButton.disabled = false;
callButton.disabled = true;
hangupButton.disabled = true;
joinButton.disabled = true;

showLocalOffer.style.display = 'none';
getRemoteOffer.style.display = 'none';
showLocalAnswer.style.display = 'none';
getRemoteAnswer.style.display = 'none';

startButton.onclick = start;
callButton.onclick = call;
joinButton.onclick = join;
hangupButton.onclick = hangup;
sentLocalOfferButton.onclick = showRemote;
pasteRemoteOfferButton.onclick = answerCreate;
pasteRemoteAnswerButton.onclick = answerRemote;
sentLocalAnswerButton.onclick = hideRemoteAnswer;

function trace(text) {
  console.log((performance.now() / 1000).toFixed(3) + ": " + text);
}

function start() {
  trace("Requesting local stream");
  startButton.disabled = true;
  getUserMedia({audio:true, video:true}, gotStream,
    function(error) {
      trace("getUserMedia error: ", error);
    });
}

function gotStream(stream){
  trace("Received local stream");
  localVideo.src = URL.createObjectURL(stream);
  localStream = stream;
  callButton.disabled = false;
  joinButton.disabled = false;
}

// ALICE

function call() {
    showLocalOffer.style.display = 'block';
    callButton.disabled = true;
    joinButton.disabled = true;
    hangupButton.disabled = false;
    trace("Starting call");

    if (localStream.getVideoTracks().length > 0) {
      trace('Using video device: ' + localStream.getVideoTracks()[0].label);
    }
    if (localStream.getAudioTracks().length > 0) {
      trace('Using audio device: ' + localStream.getAudioTracks()[0].label);
    }

    localPeerConnection = new RTCPeerConnection(servers);
    trace("Created local peer connection object localPeerConnection");

    localPeerConnection.addStream(localStream);
    trace("Added localStream to localPeerConnection");
    localPeerConnection.createOffer(gotLocalDescription,handleError,sdpConstraints);
}

function gotLocalDescription(description){
  localPeerConnection.setLocalDescription(description);
  trace("Offer from localPeerConnection SDP: \n" + description.sdp);
  trace("Offer from localPeerConnection TYPE: \n" + description.type);
  localOffer.value = JSON.stringify(description);
}

function showRemote() {
    showLocalOffer.style.display = 'none';
    getRemoteAnswer.style.display = 'block';
}

function answerRemote() {
  getRemoteAnswer.style.display = 'none';
    var remoteSesssionDescription = new RTCSessionDescription(JSON.parse(remoteAnswer.value));
    localPeerConnection.setRemoteDescription(remoteSesssionDescription);
    localPeerConnection.onaddstream = gotRemoteStream;
  localPeerConnection.onicecandidate = gotRemoteIceCandidate;
}

function gotRemoteIceCandidate(evt) {
    if (event.candidate) {
      localPeerConnection.addIceCandidate(new RTCIceCandidate(evt.candidate));
      trace("Remote ICE candidate: \n " + evt.candidate.candidate);
    }
}


// BOB

function join() {
    trace("Joining call");
    getRemoteOffer.style.display = 'block';
    callButton.disabled = true;
    hangupButton.disabled = false;
    joinButton.disabled = true;

    remotePeerConnection = new RTCPeerConnection(servers);
    trace("Created remote peer connection object remotePeerConnection");
}

function answerCreate() {
    getRemoteOffer.style.display = 'none';
    showLocalAnswer.style.display = 'block';

    var sessionDescription = new RTCSessionDescription(JSON.parse(remoteOffer.value));
    remotePeerConnection.setRemoteDescription(sessionDescription);
    remotePeerConnection.createAnswer(gotRemoteDescription,handleError,sdpConstraints);
}

function gotRemoteDescription(answerSdp) {
    remotePeerConnection.setLocalDescription(answerSdp);
    trace("Answer from remotePeerConnection SDP: \n" + answerSdp.sdp);
    trace("Answer from remotePeerConnection TYPE: \n" + answerSdp.type);
    localAnswer.value = JSON.stringify(answerSdp);
    remotePeerConnection.onaddstream = gotRemoteStream;
    remotePeerConnection.onicecandidate = gotLocalIceCandidate;

}

function hideRemoteAnswer() {
    showLocalAnswer.style.display = 'none';
}

function gotRemoteStream(evt) {
    console.log('evt: ', evt);
    remoteVideo.src = URL.createObjectURL(evt.stream);
    trace("Received remote stream");
}

function gotLocalIceCandidate(evt){
  if (event.candidate) {
    remotePeerConnection.addIceCandidate(new RTCIceCandidate(evt.candidate));
    trace("Local ICE candidate: \n" + evt.candidate.candidate);
  }
}

// GENERAL USE

function hangup() {
  trace("Ending call");
  localPeerConnection = null;
  remotePeerConnection = null;
  hangupButton.disabled = true;
  callButton.disabled = false;
  joinButton.disabled = false;

  showLocalOffer.style.display = 'none';
  getRemoteOffer.style.display = 'none';
  showLocalAnswer.style.display = 'none';
  getRemoteAnswer.style.display = 'none';
}

function handleError(){
    trace("Cannot Create Offer");
}

Upvotes: 1

Views: 1642

Answers (2)

jib
jib

Reputation: 42430

I've written one of these, which you can try in Firefox in this other answer. Yes, typically with a server, you'd send ICE candidates over your signaling channel as soon as they become available.

But here that's impractical. I think you want one message to send to your friend. To do that, wait until the browser has had time to discover all ICE candidates. They will then be included in the offer/answer for you (Note: this may take up to 15 seconds on Windows in my experience).

One way to do this is to listen to onnegotationneeded until it returns a null candidate, signifying the end of ice gathering:

pc.onicecandidate = function(e) {
  if (e.candidate) return;
  var offerSdp = pc.localDescription.sdp;
  // send offerSdp string to friend
};

Hope that helps.

Upvotes: 1

Robert
Robert

Reputation: 5662

You should use the onnegotationneeded event to check if a negotiation is needed. If that event is fired, you should create an offer and exchange it. You might want to check my code at github that might help you.

Upvotes: 0

Related Questions