WebRTC信令服务器使用Express&'websocket'包

我正在使用Rob Manson的“ WebRTC入门”中的以下代码。 它使用Node.js实现 该代码启动了一个实时video通话,我已经得到它在网页浏览器中的2个标签之间运行。 我只是试图修改这个,所以它使用Express而不是'http'包。

我遇到的问题是没有video显示在我的版本中。 “呼叫者”按预期工作,但是“被叫者”在“请稍等…请连接您的呼叫…”消息时停顿。 在浏览器控制台或我的terminal中没有检测到错误,并花了一天的时间试图解决这个问题,我仍然不知道我哪里出了问题。

这里是原始的信令服务器文件:

// useful libs var http = require("http"); var fs = require("fs"); var websocket = require("websocket").server; // general variables var port = 8000; var webrtc_clients = []; var webrtc_discussions = {}; // web server functions var http_server = http.createServer(function(request, response) { var matches = undefined; if (matches = request.url.match("^/images/(.*)")) { var path = process.cwd()+"/images/"+matches[1]; fs.readFile(path, function(error, data) { if (error) { log_error(error); } else { response.end(data); } }); } else { response.end(page); } }); http_server.listen(port, function() { log_comment("server listening (port "+port+")"); }); var page = undefined; fs.readFile("basic_video_call.html", function(error, data) { if (error) { log_error(error); } else { page = data; } }); // web socket functions var websocket_server = new websocket({ httpServer: http_server }); websocket_server.on("request", function(request) { log_comment("new request ("+request.origin+")"); var connection = request.accept(null, request.origin); log_comment("new connection ("+connection.remoteAddress+")"); webrtc_clients.push(connection); connection.id = webrtc_clients.length-1; connection.on("message", function(message) { if (message.type === "utf8") { log_comment("got message "+message.utf8Data); var signal = undefined; try { signal = JSON.parse(message.utf8Data); } catch(e) { }; if (signal) { if (signal.type === "join" && signal.token !== undefined) { try { if (webrtc_discussions[signal.token] === undefined) { webrtc_discussions[signal.token] = {}; } } catch(e) { }; try { webrtc_discussions[signal.token][connection.id] = true; } catch(e) { }; } else if (signal.token !== undefined) { try { Object.keys(webrtc_discussions[signal.token]).forEach(function(id) { if (id != connection.id) { webrtc_clients[id].send(message.utf8Data, log_error); } }); } catch(e) { }; } else { log_comment("invalid signal: "+message.utf8Data); } } else { log_comment("invalid signal: "+message.utf8Data); } } }); connection.on("close", function(connection) { log_comment("connection closed ("+connection.remoteAddress+")"); Object.keys(webrtc_discussions).forEach(function(token) { Object.keys(webrtc_discussions[token]).forEach(function(id) { if (id === connection.id) { delete webrtc_discussions[token][id]; } }); }); }); }); // utility functions function log_error(error) { if (error !== "Connection closed" && error !== undefined) { log_comment("ERROR: "+error); } } function log_comment(comment) { console.log((new Date())+" "+comment); } 

这里是我的修改文件。 只是第一位已经被改变了:

 // useful libs var http = require("http"); var fs = require("fs"); var websocket = require("websocket").server; var express = require('express'); var morgan = require('morgan'); var bodyParser = require('body-parser'); // general variables var hostname = 'localhost'; var port = 8000; var webrtc_clients = []; var webrtc_discussions = {}; var expressApp = express(); expressApp.use(morgan('dev')); var myRouter = express.Router(); myRouter.use(bodyParser.json()); // web server functions myRouter.route('/').all(function(request,response,next) { var matches = undefined; if (matches = request.url.match("^/images/(.*)")) { var path = process.cwd() +"/images/"+matches[1]; debugger; console.log("PATH: " + path); fs.readFile(path, function(error, data) { if (error) { log_error(error); } else { response.end(data); } }); } else { response.end(page); } }); ////////////////////// expressApp.use('/',myRouter); expressApp.listen(port, hostname, function(){ console.log(`Server running at http://${hostname}:${port}/`); }); /////////////////////// **I CHANGED NOTHING BELOW HERE** //////////// var page = undefined; fs.readFile("basic_video_call.html", function(error, data) { if (error) { log_error(error); } else { page = data; } }); // web socket functions var websocket_server = new websocket({ httpServer: expressApp }); websocket_server.on("request", function(request) { log_comment("new request ("+request.origin+")"); var connection = request.accept(null, request.origin); log_comment("new connection ("+connection.remoteAddress+")"); webrtc_clients.push(connection); connection.id = webrtc_clients.length-1; connection.on("message", function(message) { if (message.type === "utf8") { log_comment("got message "+message.utf8Data); var signal = undefined; try { signal = JSON.parse(message.utf8Data); } catch(e) { }; if (signal) { if (signal.type === "join" && signal.token !== undefined) { try { if (webrtc_discussions[signal.token] === undefined) { webrtc_discussions[signal.token] = {}; } } catch(e) { }; try { webrtc_discussions[signal.token][connection.id] = true; } catch(e) { }; } else if (signal.token !== undefined) { try { Object.keys(webrtc_discussions[signal.token]).forEach(function(id) { if (id != connection.id) { webrtc_clients[id].send(message.utf8Data, log_error); } }); } catch(e) { }; } else { log_comment("invalid signal: "+message.utf8Data); } } else { log_comment("invalid signal: "+message.utf8Data); } } }); connection.on("close", function(connection) { log_comment("connection closed ("+connection.remoteAddress+")"); Object.keys(webrtc_discussions).forEach(function(token) { Object.keys(webrtc_discussions[token]).forEach(function(id) { if (id === connection.id) { delete webrtc_discussions[token][id]; } }); }); }); }); // utility functions function log_error(error) { if (error !== "Connection closed" && error !== undefined) { log_comment("ERROR: "+error); } } function log_comment(comment) { console.log((new Date())+" "+comment); } 

另外,这里是处理WebRTC调用的代码,我没有改变:

 <!DOCTYPE html> <html> <head> <script> /* webrtc_polyfill.js by Rob Manson NOTE: Based on adapter.js by Adam Barth The MIT License Copyright (c) 2010-2013 Rob Manson, http://buildAR.com. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ var webrtc_capable = true; var rtc_peer_connection = null; var rtc_session_description = null; var get_user_media = null; var connect_stream_to_src = null; var stun_server = "stun.l.google.com:19302"; if (navigator.getUserMedia) { // WebRTC 1.0 standard compliant browser rtc_peer_connection = RTCPeerConnection; rtc_session_description = RTCSessionDescription; get_user_media = navigator.getUserMedia.bind(navigator); connect_stream_to_src = function(media_stream, media_element) { // https://www.w3.org/Bugs/Public/show_bug.cgi?id=21606 media_element.srcObject = media_stream; media_element.play(); }; } else if (navigator.mediaDevices.getUserMedia) { // early firefox webrtc implementation rtc_peer_connection = mozRTCPeerConnection; rtc_session_description = mozRTCSessionDescription; get_user_media = navigator.mozGetUserMedia.bind(navigator); connect_stream_to_src = function(media_stream, media_element) { media_element.mozSrcObject = media_stream; media_element.play(); }; stun_server = "74.125.31.127:19302"; } else if (navigator.webkitGetUserMedia) { // early webkit webrtc implementation rtc_peer_connection = webkitRTCPeerConnection; rtc_session_description = RTCSessionDescription; get_user_media = navigator.webkitGetUserMedia.bind(navigator); connect_stream_to_src = function(media_stream, media_element) { media_element.src = webkitURL.createObjectURL(media_stream); }; } else { alert("This browser does not support WebRTC - visit WebRTC.org for more info"); webrtc_capable = false; } </script> <script> /* basic_video_call.js by Rob Manson The MIT License Copyright (c) 2010-2013 Rob Manson, http://buildAR.com. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ var call_token; // unique token for this call var signaling_server; // signaling server for this call var peer_connection; // peer connection object function start() { // create the WebRTC peer connection object peer_connection = new rtc_peer_connection({ // RTCPeerConnection configuration "iceServers": [ // information about ice servers { "url": "stun:"+stun_server }, // stun server info ] }); // generic handler that sends any ice candidates to the other peer peer_connection.onicecandidate = function (ice_event) { if (ice_event.candidate) { signaling_server.send( JSON.stringify({ token:call_token, type: "new_ice_candidate", candidate: ice_event.candidate , }) ); } }; // display remote video streams when they arrive using local <video> MediaElement peer_connection.onaddstream = function (event) { connect_stream_to_src(event.stream, document.getElementById("remote_video")); // hide placeholder and show remote video document.getElementById("loading_state").style.display = "none"; document.getElementById("open_call_state").style.display = "block"; }; // setup stream from the local camera setup_video(); // setup generic connection to the signaling server using the WebSocket API signaling_server = new WebSocket("ws://localhost:8000"); if (document.location.hash === "" || document.location.hash === undefined) { // you are the Caller // create the unique token for this call var token = Math.round(Math.random()*100); call_token = "#"+token; // set location.hash to the unique token for this call document.location.hash = token; signaling_server.onopen = function() { // setup caller signal handler signaling_server.onmessage = caller_signal_handler; // tell the signaling server you have joined the call signaling_server.send( JSON.stringify({ token:call_token, type:"join", }) ); } document.title = "You are the Caller"; document.getElementById("loading_state").innerHTML = "Ready for a call...ask your friend to visit:<br/><br/>"+document.location; } else { // you have a hash fragment so you must be the Callee // get the unique token for this call from location.hash call_token = document.location.hash; signaling_server.onopen = function() { // setup caller signal handler signaling_server.onmessage = callee_signal_handler; // tell the signaling server you have joined the call signaling_server.send( JSON.stringify({ token:call_token, type:"join", }) ); // let the caller know you have arrived so they can start the call signaling_server.send( JSON.stringify({ token:call_token, type:"callee_arrived", }) ); } document.title = "You are the Callee"; document.getElementById("loading_state").innerHTML = "One moment please...connecting your call..."; } } /* functions used above are defined below */ // handler to process new descriptions function new_description_created(description) { peer_connection.setLocalDescription( description, function () { signaling_server.send( JSON.stringify({ token:call_token, type:"new_description", sdp:description }) ); }, log_error ); } // handle signals as a caller function caller_signal_handler(event) { var signal = JSON.parse(event.data); if (signal.type === "callee_arrived") { peer_connection.createOffer( new_description_created, log_error ); } else if (signal.type === "new_ice_candidate") { peer_connection.addIceCandidate( new RTCIceCandidate(signal.candidate) ); } else if (signal.type === "new_description") { peer_connection.setRemoteDescription( new rtc_session_description(signal.sdp), function () { if (peer_connection.remoteDescription.type == "answer") { // extend with your own custom answer handling here } }, log_error ); } else { // extend with your own signal types here } } // handle signals as a callee function callee_signal_handler(event) { var signal = JSON.parse(event.data); if (signal.type === "new_ice_candidate") { peer_connection.addIceCandidate( new RTCIceCandidate(signal.candidate) ); } else if (signal.type === "new_description") { peer_connection.setRemoteDescription( new rtc_session_description(signal.sdp), function () { if (peer_connection.remoteDescription.type == "offer") { peer_connection.createAnswer(new_description_created, log_error); } }, log_error ); } else { // extend with your own signal types here } } // setup stream from the local camera function setup_video() { get_user_media( { "audio": true, // request access to local microphone "video": true // request access to local camera //"video": {mandatory: {minHeight:8, maxHeight:8, minWidth:8, maxWidth:8}} }, function (local_stream) { // success callback // display preview from the local camera & microphone using local <video> MediaElement connect_stream_to_src(local_stream, document.getElementById("local_video")); // add local camera stream to peer_connection ready to be sent to the remote peer peer_connection.addStream(local_stream); }, log_error ); } // generic error handler function log_error(error) { console.log(error); } </script> <style> html, body { padding: 0px; margin: 0px; font-family: "Arial","Helvetica",sans-serif; } #loading_state { position: absolute; top: 45%; left: 0px; width: 100%; font-size: 20px; text-align: center; } #open_call_state { display: none; } #local_video { position: absolute; top: 10px; left: 10px; width: 160px; height: 120px; background: #333333; } #remote_video { position: absolute; top: 0px; left: 0px; width: 1024px; height: 768px; background: #999999; } </style> </head> <body onload="start()"> <div id="loading_state"> loading... </div> <div id="open_call_state"> <video id="remote_video"></video> <video id="local_video"></video> </div> </body> </html> 

我也对不使用Express的解决scheme开放,但仍支持WebRTC中的身份validation。 非常感谢您的帮助。

那么,我发现了解决scheme。 它只是涉及replace这一行:

expressApp.listen(port,hostname,function(){console.log( Server running at http://${hostname}:${port}/ );

有了这个:

var webServer = http.createServer(expressApp).listen(8000);

这个工作的原因在这个线程中有所解释。 因为一个微不足道的问题而浪费任何人的时间。