Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RTCPeerConnection.onicecandidate not fire

Tags:

chat

video

webrtc

I am create an app that will share the camera video to multiple peer connection using WebRTC. The server simply provide a room for users, all users in the room will see the camera video. The only not working was the onicecandidate did not fire, therefore no peer connection can be made.

I create new peer connection for each new users. All seems fine (broadcaster and viewers can exchange messages: 1) When a viewer joined into the room, the Broadcaster creates new peer connection, create offer, set local description, and send it to the new viewer. 2) New viewer will set the remote description, create answer, set local description and send the answer back to the broadcaster. 3)Broadcaster can receive answer, set remote description, but I could not create ice candidate.

Any insight why the RTCPeerConnection.onicecandidate do not fire??

"use strict";

// config


//Acess camera from the http://localhost:2013 is allowed because it is considered a safe environment
//Acess camera from the http://192.168.10.7:2013 is considered a non secure environment (therefore require https)
//var serverIP = "http://192.168.10.7:2013";
//var serverIP = "https://172.21.2.197:2013";
//var serverIP = "https://172.20.10.12:2013";
var serverIP = "https://localhost:2013";

// RTCPeerConnection Options
var server = {
    // Uses Google's STUN server
    iceServers: [{
        "url": "stun:stun.xten.com"
    }, 
    {
    // Use my TURN server on DigitalOcean
        'url': 'turn:numb.viagenie.ca',
        'credential': 'sunghiep',
        'username': '[email protected]'
    }]
};

//DOM Objects
var btnSend = document.getElementById('btn-send');
var btnVideoStop = document.getElementById('btn-video-stop');
var btnVideoStart = document.getElementById('btn-video-start');
var btnVideoJoin = document.getElementById('btn-video-join');
var localVideo = document.getElementById('local-video');
var remoteVideo = document.getElementById('remote-video');
var inputRoomName = document.getElementById('room-name');

//WebRTC Objects
var signallingServer;
var localStream;
var viewPeerConnection;

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

// declare RTCPeerConnection
window.RTCPeerConnection =  window.RTCPeerConnection || window.mozRTCPeerConnection ||
                            window.webkitRTCPeerConnection || window.msRTCPeerConnection;
// declare RTCSessionDescription (SDP)
window.RTCSessionDescription =  window.RTCSessionDescription || window.mozRTCSessionDescription ||
                                window.webkitRTCSessionDescription || window.msRTCSessionDescription;

// declare the getUserMedia
navigator.getUserMedia =    navigator.getUserMedia || navigator.mozGetUserMedia ||
                            navigator.webkitGetUserMedia || navigator.msGetUserMedia;

//in other example I use: navigator.mediaDevices.getUserMedia. WHY is different here???? It is because I use adapter.js

window.SignallingServer = window.SignallingServer;

var peers = [];

// start the video a Room
btnVideoStart.onclick = function(e) {
    e.preventDefault();

    //Create room
   createRoom()
};

btnVideoJoin.onclick = function(e) {
    e.preventDefault();
    // View video
    trace("Click on Join");
    joinRoom()
};

btnVideoStop.onclick = function(e) {
    e.preventDefault();
    // stop video stream
    if (localStream != null) {
        localStream.getVideoTracks().forEach(function (track) {
            track.stop();
        });
    }

    // kill all connections
    if (localPeerConnection != null) {
        localPeerConnection.removeStream(localStream);
        localPeerConnection.close();
        signallingServer.close();
        localVideo.src = "";
        remoteVideo.src = "";
    }

    btnVideoStart.disabled = false;
    btnVideoJoin.disabled = false;
    btnVideoStop.disabled = true;
};


function createRoom(){
    //get room's name
    var room = inputRoomName.value;

    if (room == undefined || room.length <= 0) {
        alert('Please enter room name');
        return;
    }

    // create local data channel, send it to remote
    navigator.getUserMedia({
        video: true,
        audio: true
    }, function(stream) {
        // get and save local stream
        trace('Got stream, saving it now and starting RTC conn');
        //show video on video object
        localStream = stream;
        localVideo.src = window.URL.createObjectURL(stream);
    }, errorHandler);

    btnVideoStart.disabled = true;
    btnVideoJoin.disabled = true;
    btnVideoStop.disabled = false;

    // create signalling server
    signallingServer = new SignallingServer(room, serverIP);

    //create room
    signallingServer.create();


    // a remote peer has joined room, initiate sdp exchange
    signallingServer.onGuestJoined = function(room, socketId) {
        trace('Guest(' + socketId +') joined room ['+ room +']! ');
        var guestId = socketId;
        var roomName =  room;

        // Create new peer connection for new joiner
        //trace("create new RTCPeerConnection");
        //var pc = new RTCPeerConnection(server);
        //peers.set(guestId, pc);
        peers[socketId] = new RTCPeerConnection(server);
        trace(peers);

        // send ice to the new joiner
        trace('Collecting ices ...');
        peers[socketId].onicecandidate  = function(evt) {
            trace('ice ready ...');
            if (evt.candidate){
                signallingServer.sendICECandidate(room, socketId, evt.candidate);
            }

        };


         //create local data channel, share remote
        navigator.getUserMedia({
            video: true,
            audio: true
        }, function(stream) {
            // get and save local stream
            trace('Got stream, saving it now and starting RTC conn');
            // Add stream to peer connection
            localStream = stream;
            peers[socketId].addStream(localStream);
        }, errorHandler);


        // Create offer,
        peers[socketId].createOffer(function(sessionDescription) {
            trace('Created offer');
            // Set local description
            peers[socketId].setLocalDescription(sessionDescription, function(){
                // Send local sdp to remote
                trace('Set local sdp success');

                signallingServer.sendOffer(guestId, roomName, sessionDescription);
                trace('Sending sdp offer');

                trace(peers);

            }, function(){
                trace('Set local sdp failed');
            });

        }, function(){
            trace('Create offer failed');
        });

    };




    //Receive the answer
    signallingServer.onReceiveAnswer = function(sender, sdp) {
        trace('Receive remote sdp with answer');
        // Step 7 - Set the remote SDP
        peers[sender].setRemoteDescription(new RTCSessionDescription(sdp), function () {
            trace('Set Remote Description success');
        }, function () {
            trace('Set Remote Description fail');
        });

        trace(peers);
    };

    // when room is full, alert user
    signallingServer.onRoomFull = function(room) {
        window.alert('Room "' + room +
            '"" is full! Please join or create another room');
    };


}

function joinRoom(){
    //get room's name
    var room = inputRoomName.value;

    if (room == undefined || room.length <= 0) {
        alert('Please enter room name');
        return;
    }

    btnVideoStart.disabled = true;
    btnVideoJoin.disabled = true;
    btnVideoStop.disabled = false;

    // Step 1 - Create peer connection
    viewPeerConnection = new RTCPeerConnection(server);

    // create signalling server
    signallingServer = new SignallingServer(room, serverIP);

    //join room
    signallingServer.join();

    // got sdp offer
    signallingServer.onReceiveOffer = function(offerCreatorId, roomName, sdp) {
        // Step 7 - Set remote SDP
        viewPeerConnection.setRemoteDescription(new RTCSessionDescription(sdp), function() {
            trace('Set Remote Description');
            trace(sdp);
            // Step 8 - Create an answer
            var answerPromise = viewPeerConnection.createAnswer();
            answerPromise.then(function(answer){
                // Step 9 - Set local description from the incoming SDP
                trace('Set Local Description');
                trace(answer);
                viewPeerConnection.setLocalDescription(answer)});

            answerPromise.then(function(answer){
                trace('Send Answer Description');
                trace(answer);
                // Step 10 - Send local sdp(answer) to remote
                signallingServer.sendAnswer(offerCreatorId, roomName, answer);
            }).catch(handleGetUserMediaError);
        });

    };

    // ICE Candidate is created when cannot establish the peer connection due to NAT/FIREWALL
    // when received ICE candidate info
    signallingServer.onReceiveICECandidate = function(candidate) {
        trace('Add ice candidate');
        viewPeerConnection.addIceCandidate(new RTCIceCandidate(candidate));
    };

    viewPeerConnection.onaddstream = function(data) {
        trace("stream ready");
        remoteVideo.src = window.URL.createObjectURL(data.stream);
    };
}

function errorHandler(error) {
    console.error('Something went wrong!');
    console.error(error);
}

function trace(text) {
    console.info(text);
}

function handleGetUserMediaError(){
    trace("getUserMedia Error")
}
<!DOCTYPE html>
<html lang="en">
<!--[if lt IE 7]>
<html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="">
<![endif]-->
<!--[if IE 7]>
<html class="no-js lt-ie9 lt-ie8" lang="">
<![endif]-->
<!--[if IE 8]>
<html class="no-js lt-ie9" lang="">
<![endif]-->
<!--[if gt IE 8]>
<!-->
<html class="no-js" lang="">
<!--<![endif]-->
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title></title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="stylesheet" href="css/bootstrap.min.css">
    <style>
        body {
            padding-top: 50px;
            padding-bottom: 20px;
        }
    </style>
    <link rel="stylesheet" href="css/bootstrap-theme.min.css">
    <link rel="stylesheet" href="css/main.css">

    <script src="js/vendor/modernizr-2.8.3-respond-1.4.2.min.js"></script>
</head>
<body>
<!--[if lt IE 8]>
<p class="browserupgrade">
    You are using an <strong>outdated</strong>
    browser. Please
    <a href="http://browsehappy.com/">upgrade your browser</a>
    to improve your experience.
</p>
<![endif]-->
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
                    aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">WebRTC Video Chat</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
            <!-- chatroom name form -->
            <form class="navbar-form navbar-right form-inline">
                <div class="form-group">
                    <input class="form-control" type="text" id="room-name" placeholder="Room name"/>
                </div>
                <button class="btn btn-primary" id="btn-video-start">Create Live Broadcast</button>
                <button class="btn btn-default" id="btn-video-join">Join</button>
                <button class="btn btn-default" disabled id="btn-video-stop">Stop</button>
            </form>
        </div>

        <!--/.navbar-collapse --> </div>

</nav>

<div class="container main">
    <div class="row videos">
        <div class="remote-video">
            <video width="280" height="250" autoplay="true" id="remote-video"></video>
        </div>
        <div class="local-video">
            <video width="560" height="500" autoplay="true" id="local-video" muted></video>
        </div>
    </div>
</div>
</div>
<!-- /container -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-1.11.2.min.js"><\/script>')</script>

<script src="js/vendor/bootstrap.min.js"></script>
<script src="js/vendor/socket.io.js"></script>

<script src="js/main.js"></script>
<!--<script src="js/adapter.js"></script>-->
<script src="js/signalling.js"></script>

<!--<script src="//localhost:9010/livereload.js"></script>-->

<!-- Google Analytics: change UA-XXXXX-X to be your site's ID. -->
<script>
    (function (b, o, i, l, e, r) {
        b.GoogleAnalyticsObject = l;
        b[l] || (b[l] =
                function () {
                    (b[l].q = b[l].q || []).push(arguments)
                });
        b[l].l = +new Date;
        e = o.createElement(i);
        r = o.getElementsByTagName(i)[0];
        e.src = '//www.google-analytics.com/analytics.js';
        r.parentNode.insertBefore(e, r)
    }(window, document, 'script', 'ga'));
    ga('create', 'UA-XXXXX-X', 'auto');
    ga('send', 'pageview');
</script>
</body>
</html>
like image 374
banlong Avatar asked Jun 26 '16 08:06

banlong


1 Answers

In onGuestJoined, you're calling createOffer before addStream. This will create an offer without any accommodation for media in it, or fail:

In Firefox, it'll fail with:

InternalError: Cannot create an offer with no local tracks, no
offerToReceiveAudio/Video, and no DataChannel.

In Chrome, it appears instead to create an unusable offer.

In neither case will things proceed, which is why ICE candidates are not firing.

Solution:

Make sure to call createOffer from the getUserMedia success callback, or better yet, since you seem to be mixing callback and promise APIs in this code, use promises throughout for a little more clarity:

navigator.mediaDevices.getUserMedia({video: true, audio: true})
.then(stream => {
    localStream = stream;
    peers[socketId].addStream(localStream);
    return peers[socketId].createOffer();
})
.then(desc => peers[socketId].setLocalDescription(desc))
.then(() => signallingServer.sendOffer(guestId, roomName,
                                       peers[socketId].localDescription))
.catch(errorHandler);
like image 113
jib Avatar answered Oct 21 '22 13:10

jib