shanet
shanet

Reputation: 7324

WebRTC: Firefox unable to recieve video as callee

I'm new to WebRTC and am trying to create a simple test page that allows for 1-to-1 video conferences.

So far what I have below works for the following:

It does not work in the following situations:

Basically, Firefox is unable to receive the remote video stream when it is the callee. The onaddstream handler is never called, but I do not know why.

The only difference I have found between Firefox and Chrome is that Firefox does not trickle ICE candidates; they are included in the initial offer/answer creation causing the onicecandidate handler to never be called with a non-null candidate. On the other hand, Chrome does trickle candidates which are sent to the signaling server as separate messages. I'm not sure if this a/the potential problem or not.

I am testing with Firefox Nightly 34 and Chrome 36. The signaling server is hosted in my LAN and both browsers are running on the same computer.

The following code was based off of http://www.html5rocks.com/en/tutorials/webrtc/basics/.

var localVideo;
var remoteVideo;
var peerConnection;
var peerConnectionConfig = {'iceServers': [{'url': 'stun:stun.services.mozilla.com'}, {'url': 'stun:stun.l.google.com:19302'}]};
var isCaller;

navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;
window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;

function pageReady() {
    localVideo = document.getElementById('localVideo');
    remoteVideo = document.getElementById('remoteVideo');

    serverConnection = new WebSocket('ws://myserver:3434');
    serverConnection.onmessage = gotMessageFromServer;
}

function start(_isCaller) {
    isCaller = _isCaller

    peerConnection = new RTCPeerConnection(peerConnectionConfig);
    peerConnection.onicecandidate = gotIceCandidate;
    peerConnection.onaddstream = gotRemoteStream;

    var constraints = {
        video: true,
        audio: true, 
    };

    if(navigator.getUserMedia) {
        navigator.getUserMedia(constraints, getUserMediaSuccess, getUserMediaError);
    } else {
        alert('Your browser does not support getUserMedia API');
    }
}

function getUserMediaSuccess(stream) {
    localStream = stream;
    localVideo.src = window.URL.createObjectURL(stream);

    peerConnection.addStream(stream);

    if(isCaller) {
        peerConnection.createOffer(gotDescription, createOfferError);
    } else {
        peerConnection.createAnswer(gotDescription, createAnswerError);
    }
}

function gotMessageFromServer(message) {
    if(!peerConnection) start(false);

    var signal = JSON.parse(message.data);
    if(signal.sdp) {
        peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp));
    } else if(signal.ice) {
        peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice));
    }
}

function gotIceCandidate(event) {
    if(event.candidate != null) {
        serverConnection.send(JSON.stringify({'ice': event.candidate}));
    }
}

function gotDescription(description) {
    peerConnection.setLocalDescription(description);
    serverConnection.send(JSON.stringify({'sdp': description}));
}

function gotMessageFromServer(message) {
    if(!peerConnection) start(false);

    var signal = JSON.parse(message.data);
    if(signal.sdp) {
        peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp));
    } else if(signal.ice) {
        peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice));
    }
}

function gotRemoteStream(event) {
    console.log("got remote stream");
    remoteVideo.src = window.URL.createObjectURL(event.stream);
}

// Error functions....

function getUserMediaError(error) {
    console.log(error);
}

function createOfferError(error) {
    console.log(error);
}

function createAnswerError(error) {
    console.log(error);
}

If it's useful, here is my signaling server. It's a very basic Node.js script that uses the ws WebSocket library to simply broadcast received messages to all connected clients which is only one other client in this case.

var WebSocketServer = require('ws').Server;

var wss = new WebSocketServer({port: 3434});

wss.broadcast = function(data) {
    for(var i in this.clients) {
        this.clients[i].send(data);
    }
};

wss.on('connection', function(ws) {
    ws.on('message', function(message) {
        console.log('received: %s', message);
        wss.broadcast(message);
    });
});

Any help is greatly appreciated!

Upvotes: 3

Views: 2435

Answers (1)

shanet
shanet

Reputation: 7324

Turns out I had a race condition between the call to setRemoteDescription() and createAnswer(). Firefox asks for permission to use the webcam on every reload of the page whereas Chrome only asks on the first page load. This meant that When I went to call createAnswer() in Firefox, the video stream did not exist yet because the user had not allowed access to the webcam yet. The solution was to move the call to getUserMedia() to when the page is ready. This way, when an incoming call occurs the user has already allowed access to the webcam and the video stream can immediately be shared. It's not perfect, but it allows my little test page to work which is all I'm going for for now.

Here's the updated code if it helps anyone in the future:

var localVideo;
var remoteVideo;
var peerConnection;
var peerConnectionConfig = {'iceServers': [{'url': 'stun:stun.services.mozilla.com'}, {'url': 'stun:stun.l.google.com:19302'}]};

navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;
window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;

function pageReady() {
    localVideo = document.getElementById('localVideo');
    remoteVideo = document.getElementById('remoteVideo');

    serverConnection = new WebSocket('ws://sagan:3434');
    serverConnection.onmessage = gotMessageFromServer;

    var constraints = {
        video: true,
        audio: true, 
    };

    if(navigator.getUserMedia) {
        navigator.getUserMedia(constraints, getUserMediaSuccess, getUserMediaError);
    } else {
        alert('Your browser does not support getUserMedia API');
    }
}

function getUserMediaSuccess(stream) {
    localStream = stream;
    localVideo.src = window.URL.createObjectURL(stream);
}

function start(isCaller) {
    peerConnection = new RTCPeerConnection(peerConnectionConfig);
    peerConnection.onicecandidate = gotIceCandidate;
    peerConnection.onaddstream = gotRemoteStream;
    peerConnection.addStream(localStream);

    if(isCaller) {
        peerConnection.createOffer(gotDescription, createOfferError);
    }
}

function gotMessageFromServer(message) {
    if(!peerConnection) start(false);

    var signal = JSON.parse(message.data);
    if(signal.sdp) {
        peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp), function() {
            peerConnection.createAnswer(gotDescription, createAnswerError);
        });
    } else if(signal.ice) {
        peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice));
    }
}

function gotIceCandidate(event) {
    if(event.candidate != null) {
        serverConnection.send(JSON.stringify({'ice': event.candidate}));
    }
}

function gotDescription(description) {
    console.log('got description');
    peerConnection.setLocalDescription(description, function () {
        serverConnection.send(JSON.stringify({'sdp': description}));
    }, function() {console.log('set description error')});
}

function gotRemoteStream(event) {
    console.log("got remote stream");
    remoteVideo.src = window.URL.createObjectURL(event.stream);
}

// Error functions....

function getUserMediaError(error) {
    console.log(error);
}

function createOfferError(error) {
    console.log(error);
}

function createAnswerError(error) {
    console.log(error);
}

Upvotes: 1

Related Questions