RTCPeerConnection.onicecandidate不火

RTCPeerConnection.onicecandidate not fire

我正在创建一个应用程序,它将使用 WebRTC 将摄像头视频共享到多个对等连接。服务器简单地为用户提供一个房间,房间内的所有用户都会看到摄像头视频。唯一不起作用的是 onicecandidate 没有触发,因此无法建立对等连接。

我为每个新用户创建新的对等连接。一切似乎都很好(广播员和观众可以交换消息: 1) 当观众加入房间时,广播者创建新的对等连接,创建报价,设置本地描述,并将其发送给新观众。 2) 新观众将设置远程描述,创建答案,设置本地描述并将答案返回给广播者。 3)主播可以接收应答,设置远程描述,但是我无法创建ice candidate

知道为什么 RTCPeerConnection.onicecandidate 不开火吗??

"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': 'nghiepnds@yahoo.com'
    }]
};

//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>

onGuestJoined 中,您在 addStream 之前调用 createOffer。这将创建一个没有任何媒体住宿的报价,或者失败:

在 Firefox 中,它会失败:

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

在 Chrome 中,它似乎创建了一个无法使用的报价。

在这两种情况下,事情都不会继续,这就是 ICE 候选人没有开火的原因。

解决方案:

确保从 getUserMedia 成功回调中调用 createOffer,或者更好,因为您似乎在此代码中混合了回调和承诺 API,请在整个过程中使用承诺以更加清晰:

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);