我想创建简单的视频聊天应用程序。我将WebRTC和WebSockets与Node.js一起使用。我想在同一网络中连接两个设备,但iceConnectionState会一直保持“检查”状态。可能是什么问题呢?
已编辑
server.js

//var uuid = require('uuid');

var fs = require('fs');
var webSocketServer = require('websocket').server;

var cfg = {
    ssl: false,
    port: 15555,
    ssl_key: '/etc/apache2/ssl/apache.key',
    ssl_cert: '/etc/apache2/ssl/apache.crt'
};

var httpServer = (cfg.ssl) ? require('https') : require('http');

var server = null;

if(cfg.ssl) {
    server = httpServer.createServer(
        {
            key: fs.readFileSync(cfg.ssl_key),
            cert: fs.readFileSync(cfg.ssl_cert)
        }, function() {} ).listen(cfg.port);
} else {
    server = httpServer.createServer().listen(cfg.port);
}

var wsServer = new webSocketServer({
    httpServer: server
});

rooms = {};

// this is executed each time the websocket
// server receives an request
wsServer.on('request', function(request) {

    // allow all incoming connections
    var connection = request.accept(null, request.origin);


    // here we read the incoming messages and try to parse them to JSON
    connection.on('message', function(message) {
        // try to parse JSON
        try {
            var data = JSON.parse(message.utf8Data);
        } catch (e) {
            console.log('This does not look like valid JSON');
        }

        // if JSON is valid process the request
        if (data !== undefined && data.type !== undefined) {
            switch (data.type) {
                case 'createRoom':
                    var roomName = data.roomName;
                    console.log('CREATE_ROOM request received');

                    rooms[roomName] = {
                        creatorConnection: connection,
                        partnerConnection: false,
                    }

                    var data = {
                        type: 'roomCreated',
                        payload: roomName
                    };
                    return send(rooms[roomName].creatorConnection, data);
                    break;
                case 'offer':
                    console.log('OFFER received from client');

                    if (rooms[data.roomName].partnerConnection) {
                        // send error to user
                        var data = {
                            type: 'error',
                            payload: 'room is already full'
                        };
                        return send(connection, data);
                    }
                    rooms[data.roomName].partnerConnection = this;

                    console.log('OFFER send to host');
                    return send(rooms[data.roomName].creatorConnection, data);
                    break;
                    // send to other guy
                default:
                    if (this === rooms[data.roomName].partnerConnection) {
                        console.log('send to creator : ' + data.type);
                        return send(rooms[data.roomName].creatorConnection, data);
                    }
                    console.log('send to parther : ' + data.type);
                    return send(rooms[data.roomName].partnerConnection, data);
                    break;
            }
        }
        // if JSON is invalid or type is missing send error
        else {
            var data = {
                type: 'error',
                payload: 'ERROR FROM SERVER: Incorrect data or no data received'
            };
            send(connection, data);
        }
    });

    // this function sends data to the other user
    var send = function(connection, data) {
        try {
            connection.sendUTF(JSON.stringify(data));
        } catch (e) {
            console.log('\n\n!!!### ERROR while sending message ###!!!\n');
            console.log(e + '\n');
            return;
        }
    };
});
client.js
function WebRTC() {

    var wsServer = false;
    var localStream = false;
    var remoteStream = false;
    var peerConnection = false;
    var roomName = null;
    var otherSDP = false;
    var othersCandidates = []; // other guy's icecandidates

    var socketEvent = document.createEvent('Event');
    socketEvent.initEvent('socketEvent', true, true);

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

    var peerConnectionConfig = {
        iceServers: [
            {url : 'stun:stun.l.google.com:19302'},
            {url : 'stun:stun.anyfirewall.com:3478'},
            {url : 'turn:turn.bistri.com:80',
                credential: 'homeo',
                username: 'homeo'},
            {url : 'turn:turn.anyfirewall.com:443?transport=tcp',
                credential: 'webrtc',
                username: 'webrtc'}
        ]
    };

    var offerAnswerConstraints = { mandatory: {
        OfferToReceiveAudio: true,
        OfferToReceiveVideo: true
    }};

    var sendToServer = function(data) {
        try {
            wsServer.send(JSON.stringify(data));
            return true;
        } catch (e) {
            logError(e);
            return false;
        }
    };

    var createRTCIceCandidate = function(candidate) {
        var iceCandidate;

        debug(candidate);
        debug(JSON.parse(candidate));

        if (typeof(webkitRTCIceCandidate) === 'function') {
            iceCandidate = new webkitRTCIceCandidate(candidate);
        } if (typeof(mozRTCIceCandidate) === 'function') {
            iceCandidate = new mozRTCIceCandidate((candidate));
        } else if (typeof(RTCIceCandidate) === 'function') {
            iceCandidate = new RTCIceCandidate(candidate);
        }

        return iceCandidate;
    };

    var createRTCSessionDescription = function(description) {
        var newSdp;

        if (typeof(RTCSessionDescription) === 'function') {
            newSdp = new RTCSessionDescription(description);
        } else if (typeof(webkitRTCSessionDescription) === 'function') {
            newSdp = new webkitRTCSessionDescription(description);
        } else if (typeof(mozRTCSessionDescription) === 'function') {
            newSdp = new mozRTCSessionDescription(description);
        }

        return newSdp;
    };

    var getRTCPeerConnection = function(stream) {
        var peerConnection = null;

        if (typeof(RTCPeerConnection) === 'function') {
            peerConnection = new RTCPeerConnection(peerConnectionConfig);
        } else if (typeof(webkitRTCPeerConnection) === 'function') {
            peerConnection = new webkitRTCPeerConnection(peerConnectionConfig);
        } else if (typeof(mozRTCPeerConnection) === 'function') {
            peerConnection = new mozRTCPeerConnection(peerConnectionConfig);
        }

        debug("Creating new RTCPeerConnection");

        peerConnection.addStream(stream);

        peerConnection.onaddstream = function(e) {
            debug("Remote stream received");

            remoteStream = e.stream;

            socketEvent.eventType = 'streamAdded';
            document.dispatchEvent(socketEvent);
        };

        peerConnection.onicecandidate = function(event) {
            debug("Retrieving ICE data status changed : " + event.target.iceGatheringState)

            var data = {
                type: 'iceCandidate',
                roomName: roomName,
                payload: event
            };

            sendToServer(data);
        };

        peerConnection.oniceconnectionstatechange = function(event) {
            debug("ICE connection status changed : " + event.target.iceConnectionState)
        };

        return peerConnection;
    };

    var setIceCandidates = function(iceCandidate) {
        // push icecandidate to array if no SDP of other guys is available
        if (!otherSDP) {
            othersCandidates.push(iceCandidate);
        }
        // add icecandidates immediately if not Firefox & if remoteDescription is set
        if (otherSDP && iceCandidate.candidate && iceCandidate.candidate !== null) {
            peerConnection.addIceCandidate(createRTCIceCandidate(iceCandidate.candidate));
        }
    };

    var handshakeDone = function() {
        console.log('handshakeDone');
        peerConnection.setRemoteDescription(createRTCSessionDescription(otherSDP), function() {
            // add other guy's ice-candidates to connection
            for (var i = 0; i < othersCandidates.length; i++) {
                if (othersCandidates[i].candidate) {
                    peerConnection.addIceCandidate(ceateRTCIceCandidate(othersCandidates[i].candidate));
                }
            }
            // fire event
            socketEvent.eventType = 'p2pConnectionReady';
            document.dispatchEvent(socketEvent);
        }, logError);

    };

    var createOffer = function() {
        peerConnection = getRTCPeerConnection(localStream);

        debug('Offer creating');
        peerConnection.createOffer(function(description) {
            debug('Offer created');
            debug('Local description setting');

            peerConnection.setLocalDescription(description, function() {
                debug('Local description set');
                var data = {
                    type: 'offer',
                    roomName: roomName,
                    payload: description
                };

                sendToServer(data);
            }, logError);
        }, logError);
    };

    var createAnswer = function() {
        peerConnection = getRTCPeerConnection(localStream);

        debug('Offer answering');
        debug('Remote description setting');

        peerConnection.setRemoteDescription(createRTCSessionDescription(otherSDP), function () {
            debug('Remote description set');
            debug('Answer creating');

            peerConnection.createAnswer(function(description) {
                debug('Answer created');
                debug('Local description setting');

                peerConnection.setLocalDescription(description, function() {
                    debug('Local description set');

                    for (var i = 0; i < othersCandidates.length; i++) {
                        if (othersCandidates[i].candidate) {
                            peerConnection.addIceCandidate(ceateRTCIceCandidate(othersCandidates[i].candidate));
                        }
                    }

                    // send SDP to other guy
                    var data = {
                        type: 'answer',
                        roomName: roomName,
                        payload: description
                    };

                    sendToServer(data);
                }, logError);
            }, logError);
        }, logError);
    };

    this.connectToSocket = function(wsUrl) {
        wsServer = new WebSocket(wsUrl);

        wsServer.onopen = function(event) {
            console.log((new Date()) + ' Connection successfully established');
        };

        wsServer.onerror = function(e) {
            console.log((new Date()) + ' WebSocket connection error: ');
            logError(e);
        };

        wsServer.onclose = function(event) {
            console.log((new Date()) + ' Connection was closed');
            logError(e);
        };

        wsServer.onmessage = function(message) {
            try {
                var data = JSON.parse(message.data);
            } catch (e) {
                logError(e);
                return;
            }

            switch (data.type) {
                case 'roomCreated':
                    roomName = data.payload;
                    socketEvent.eventType = 'roomCreated';
                    document.dispatchEvent(socketEvent);
                    break;
                case 'offer':
                    otherSDP = data.payload;
                    createAnswer();
                    break;
                case 'answer':
                    otherSDP = data.payload;
                    handshakeDone();
                    break;
                case 'iceCandidate':
                    setIceCandidates(data.payload);
                    break;
            }
        };
    };

    this.getRoomName = function() {
        return roomName;
    };

    this.createRoom = function(roomName) {
        var media = getMedia();

        var onSuccess = function(stream) {
            localVideo.attr('src', URL.createObjectURL(stream));
            localStream = stream;

            var data = {
                type: 'createRoom',
                roomName: roomName,
                payload: false
            };

            return sendToServer(data)
        };

        media(mediaConstraints, onSuccess, logError);
    };

    this.joinRoom = function(rName) {
        var media = getMedia();

        var onSuccess = function(stream) {
            localVideo.attr('src', URL.createObjectURL(stream));
            localStream = stream;
            //TODO
            roomName = rName;

            createOffer();
        };

        media(mediaConstraints, onSuccess, logError);
    };


    var getMedia = function() {
        var media = null;

        if (navigator.getUserMedia) {;
            media = navigator.getUserMedia.bind(navigator);
        } else if (navigator.webkitGetUserMedia) {
            media = navigator.webkitGetUserMedia.bind(navigator);
        } else if (navigator.mozGetUserMedia) {
            media = navigator.mozGetUserMedia.bind(navigator);
        }

        return media;
    };

    // get the other guys media stream
    this.getRemoteStream = function() {
        return remoteStream;
    };

}
main.js
var createRoomButton = $('#createRoomButton');
var joinRoomButton = $('#joinRoomButton');
var roomNameInput = $('#roomNameInput');
var localVideo = $('#localVideo');
var remoteVideo = $('#remoteVideo');
var roomNameField = $('#roomNameField');

var wsProtocol = location.protocol == 'http:' ? 'ws://' : 'wss://';
var wsServerAddress = wsProtocol + document.location.host + ":15555" ;

initialize();


function initialize() {
    WebRTC = new WebRTC();

    WebRTC.connectToSocket(wsServerAddress);

    createRoomButton.click(function() {
        WebRTC.createRoom(roomNameInput.val());
    });

    joinRoomButton.click(function() {
        WebRTC.joinRoom(roomNameInput.val());
    });

    document.addEventListener('socketEvent', function(socketEvent) {
        switch (socketEvent.eventType) {
            case 'roomCreated':
                $('#loginSection').hide();
                $('#roomSection').show();
                roomNameField.html(WebRTC.getRoomName());
                break;

            case 'p2pConnectionReady':
                $('#loginSection').hide();
                $('#roomSection').show();
                roomNameField.html(WebRTC.getRoomName());
                break;

            case 'streamAdded':
                var stream = WebRTC.getRemoteStream();
                remoteVideo.attr('src', URL.createObjectURL(stream));
                break;
        }
    });
}
common.js
function debug(message) {
    console.log(message);
}

function logError(e) {
    console.error(e);
}
index.html
<!doctype html>
<html>
    <head>
        <title>WebRTC</title>
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
        <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
        <link href="css/bootstrap.css" rel="stylesheet">
    </head>
    <body style="padding: 5em;">
        <section id="loginSection">
            <div class="row" style="margin-bottom: 2em;">
                <div clsss="col-xs-12">
                    <button id="createRoomButton" type="button" class="btn btn-default btn-lg center-block">
                      Create room
                    </button>
                </div>
            </div>
            <div class="row" style="margin-bottom: 4em;">
                <div clsss="col-xs-12">
                    <button id="joinRoomButton" type="button" class="btn btn-default btn-lg center-block">
                      Join room
                    </button>
                </div>
            </div>

            <div class="row">
                <div clsss="col-xs-4">
                    <input id="roomNameInput" type="text" style="width: 200px;" class="form-control center-block"
                           placeholder="Room's name" aria-describedby="basic-addon2">
                </div>
            </div>
        </section>

        <section id="roomSection" style="display: none;">
            <h3>
                Room's name:
                <span id="roomNameField" style="font-weight: bold;"></span>
            </h3>
            <div class="video-wrapper">
                <video id="remoteVideo" autoplay="true"></video>
                <video id="localVideo"  autoplay="true" muted="true" width="200" height="200"></video>
            </div>
        </section>

        <script src="js/jquery-2.1.4.js" type="text/javascript"></script>
        <script src="js/bootstrap.js" type="text/javascript"></script>
        <script src="js/common.js" type="text/javascript"></script>
        <script src="js/client.js" type="text/javascript"></script>
        <script src="js/main.js" type="text/javascript"></script>
    </body>
</html>

最佳答案

对于Mozilla vs. Chrome,您可能仍然需要对此进行填充,但问题似乎是您试图通过WebSocket发送RTCiceCandidateEvent而不是RTCiceCandidate,并且它没有通过套接字。

我尝试了这个:

peerConnection.onicecandidate = function(event) {
    debug("Retrieving ICE data status changed : " + event.target.iceGatheringState)
    console.log(event);
    console.log(event.candidate);
    if(event.candidate) {
        var data = {
            type: 'iceCandidate',
            roomName: roomName,
            payload: event.candidate
        };
        sendToServer(data);
    }
};

然后在wsServer.onmessage将其传递到setIceCandidates后立即将其添加到另一端(但是就像我说的那样,对于Firefox,您可能需要对它进行填充):
var setIceCandidates = function(iceCandidate) {
    peerConnection.addIceCandidate(new RTCIceCandidate(iceCandidate));

};

我建议在发送者端用undefined过滤掉iceCandidate peerConnection.onicecandidate,而不是在接收端用setIceCandidates过滤掉。另外,我认为您不需要将iceCandidate存储在该othersCandidates数组中,以后再添加它们。我从来没有在我的版本中做到这一点。 iceCandidate的东西似乎已经很好地实现了自动化,不需要额外的帮助。 (如果您知道这样做的好处,请告诉我。)

附带一提,使用您制作的debug函数执行console.log()的方法要紧凑一些,但是它将替换行号,该行号将为您提供错误源!

09-29 23:27