2
votes

I'm trying to implement webRTC with the API's that are available in the browser. I'm using this tutorial as my guide: https://www.webrtc-experiment.com/docs/WebRTC-PeerConnection.html

Here's what I'm currently doing. First I get the audio element in the page. I also have an audioStream variable for storing the stream that I get from navigator.webkitGetUserMedia when call button is clicked by the initiating user or when answer button is clicked by the receiving user. Then a variable for storing the current call.

var audio = document.querySelector('audio');
var audioStream;
var call = {}; 

Then I have the following settings:

var iceServers = [
    { url: 'stun:stun1.l.google.com:19302' },
    { url: 'turn:numb.viagenie.ca', credential: 'muazkh', username: '[email protected]' }
];

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

var DtlsSrtpKeyAgreement = {
   DtlsSrtpKeyAgreement: true
};

On page load I create a new peer:

var peer = new webkitRTCPeerConnection({
    'iceServers': iceServers
});

On the add stream event I just assign the event to the call variable.

peer.onaddstream = function(event){
    call = event;
};

On ice candidate event, I send the candidate to the peer.

peer.onicecandidate = function(event){

    var candidate = event.candidate;

    if(candidate){
        SocketService.emit('message', {
            'conversation_id': me.conversation_id,
            'targetUser': to,
            'candidate': candidate
        });
    }

    if(typeof candidate == 'undefined'){
        send_SDP();
    }

};

Once the gathering state is completed, I send the SDP.

peer.ongatheringchange = function(e){

    if(e.currentTarget && e.currentTarget.iceGatheringState === 'complete'){

        send_SDP();
    }
};

What the send_SDP method does is send the local description to the peer.

function send_SDP(){
    SocketService.emit('message', {
        'conversation_id': me.conversation_id,
        'targetUser': to,
        'sdp': peer.localDescription
    });
}

Here's what I have inside the event listener for the CALL button. So first it gets the audio then assigns the stream to the current peer object. Then it creates a new offer, it that's successful, the local description is set and once that's done, it sends an offer to the other peer.

getAudio(
    function(stream){
        peer.addStream(stream);
        audioStream = stream;
        peer.createOffer(function(offerSDP){
            peer.setLocalDescription(offerSDP, function(){

                    SocketService.emit('message', {
                        'conversation_id': me.conversation_id,
                        'targetUser': to,
                        'offerSDP': offerSDP
                    });
                },
                function(){});
            }, 
            function(){}, 
            sdpConstraints
        );
    },
    function(err){}); 

On the receiving peer, the offer is captured, so it shows the modal that somebody is calling. The receiving peer can then click on the ANSWER button. But here I'm setting the session description using the offer even before the ANSWER button is clicked.

SocketService.on('message', function(msg){
    if(msg.offerSDP){
        //show calling modal on the receiving peer
        var remoteDescription = new RTCSessionDescription(msg.offerSDP);
        peer.setRemoteDescription(remoteDescription, function(){
        createAnswer(msg.offerSDP);
        },
        function(){});

    }
});

Once the remote description is set, the answer is created. First by getting the audio then adding the stream to the local peer object. Next the remote session description is created by using the offerSDP, this remote session description is then set to the local peer object. After that, the answer is created, the local description is set on the local peer and then it sends the answerSDP to the peer who initiated the call.

function createAnswer(offerSDP) {
    getAudio(
        function(stream){
            peer.addStream(stream);
            audioStream = stream;

            var remoteDescription = new RTCSessionDescription(offerSDP);
            peer.setRemoteDescription(remoteDescription);

            peer.createAnswer(function(answerSDP) {

                peer.setLocalDescription(answerSDP, function(){

                    SocketService.emit('message', {
                        'conversation_id': me.conversation_id,
                        'targetUser': to,
                        'answerSDP': answerSDP
                    });
                },
                function(){});
            }, function(err){}, sdpConstraints);

        },
        function(err){
        }
    );
};

The peer who initiated the call receives the answerSDP. Once it does, it creates a remote description using the answerSDP and uses it to set the remote description for its local peer object

if(msg.answerSDP){

        var remoteDescription = new RTCSessionDescription(msg.answerSDP);
        peer.setRemoteDescription(remoteDescription, function(){
        },
        function(){});
}

After that, I'm not really sure what happens next. Based on what I understand, the onicecandidate event is fired on the intiating peer (caller) and it sends a candidate to the receiving peer. Which then executes the following code:

if(msg.candidate){

    var candidate = msg.candidate.candidate;
    var sdpMLineIndex = msg.candidate.sdpMLineIndex;

    peer.addIceCandidate(new RTCIceCandidate({
        sdpMLineIndex: sdpMLineIndex,
        candidate: candidate
    }));
} 

Now once the ANSWER button is clicked, a message is sent to the initiating peer that the receiving peer picked up. And it uses the call stream as source for the audio element, once all meta data is load it plays the audio.

SocketService.emit('message', {
    'answerCall': true,
    'conversation_id': me.conversation_id,
    'targetUser': to
});

audio.src = window.URL.createObjectURL(call.stream);
audio.onloadedmetadata = function(e){
    audio.play();
}

Something might be wrong here. That's why the audio is only one-way. Only the user who initiated the call can hear the input from the receiving user. Sounds produced by the initiating user can also be heard, so its like echoing what you're saying. Any ideas?

If you know any tutorial or book that shows how to implement webRTC using native API calls, that will also help. Thanks in advance.

1
You're hard-coding to a specific browser (Chrome) using outdated APIs here, rather than using the standard. Might I suggest using adapter.js the cross-browser polyfill?jib

1 Answers

1
votes

The simplest example I can recommend is the webrtc sample Peer Connection for basic peer connection.

As for the echo, you can set audio.muted on the local peer's audio element, to prevent it from playing and causing an echo (a user doesn't need to hear her own audio, so you can mute that element).