如何使用PeerJS和Node.js与WebRTC创建视频聊天

本文概述

  • 继续之前
  • 内容指南
  • 1.创建或获取一些SSL证书
  • 2.示范项目结构
  • 3.设置测试本地服务器
  • 4.设置PeerJS服务器
  • 5.设置客户端代码
  • 6.允许Node.js的入站连接(仅在本地工作)
  • 7.运行聊天
WebRTC给用户和开发人员带来了很多好处, 而这些好处过去在网络上创建通信和协作工具的可能性很小。例如, 它提供了多种连接方式, 可与实时聊天和视频共同使用。要创建实时语音或视频连接, PeerJS是最出色的库之一, 它使你可以在Web应用程序中实现这种功能而不会(太多)麻烦。 PeerJS包装了浏览器的WebRTC实现, 以提供完整, 可配置且易于使用的对等连接API。对等端仅配备一个ID, 即可轻松创建与远程对等端的P2P数据或媒体流连接。
在本文中, 你将学习如何使用自己的托管的带有Node.js的PeerJS服务器实现Videochat。
继续之前 你将需要耐心等待, 因为如果你没有正确配置所有功能, 那么初次尝试可能无法按预期进行。因此, 要坚持不懈, 请仔细阅读所有说明, 你可能会在第一次广告上获得成功。
如果你认为本教程很长, 我们建议你在Github的此存储库中克隆正式示例, 其中包含在本文中可以找到的所有示例代码, 但是没有任何解释。不过, 自己进行测试很有用。
内容指南 本教程将是无穷无尽的, 因此, 这里是你需要遵循的所有步骤的快速介绍:
  1. 获取一些自动签名的SSL证书进行测试。
  2. 创建演示项目结构
  3. 使用Express创建一个安全的本地服务器以提供我们的HTML, CSS和JS文件。
  4. 创建一个安全的PeerJS服务器来处理信息交换。
  5. 编写代码以处理视频聊天。
    • 了解PeerJS的工作原理。
    • 创建标记以创建示例聊天。
  6. 允许Node.js的入站连接并更新客户端主机(仅在本地测试时)
  7. 运行服务器并测试
1.创建或获取一些SSL证书 为了在生产环境或本地进行测试, 你将需要使用SSL证书处理项目, 否则浏览器上的某些内容可能会由于用户权限而失败。此步骤完全由你决定, 因此, 根据你的操作系统, 你可以搜索有关如何创建自己的自签名SSL证书的另一教程。本文介绍如何在Windows中使用openssl创建自签名证书。
另外, 你可以在此处从Github的示例存储库中下载自签名证书, 并在自己的实现中使用它们。
2.示范项目结构 要创建基本的视频聊天, 我们需要HTML项目和JavaScript的基本结构:
注意 所有文件(证书和peer.min.js除外)都应该为空, 因为稍后我们将告诉你在每个文件中写入什么内容。
YourProjectFolder├───certificates├── cert.pem└── key.pem├───public├── index.html├── website-server.js├── source│└── js|├── peer.min.js│└── scripts.js└── package.json├───server├── peer-server.js└── package.json

在测试文件夹中, 你将需要3个文件夹, 即证书, 公共和服务器。在证书文件上, 你将需要存储必需的文件, 以使服务器在HTTPS中运行(请参阅步骤1)。在公用文件夹上, 你将找到索引文件, 该文件允许用户与其他人聊天并执行视频通话, 除了源代码内的脚本是客户端peer.js的源代码以及将要编写的scripts.js在第5步。
请记住, 结构不必相同, 仅是示例, 但是代码中文件的路径将遵循此模式, 因此, 如果你更改结构, 请注意也要在代码中进行更改。
3.设置测试本地服务器 对于我们的示例, 我们将使https:// localhost:8443上的简单html文件(index.html)可访问。要创建我们的服务器, 我们将使用express模块??, 因此打开一个终端, 切换到project / public目录, 并至少使用以下数据修改package.json文件, 请注意, 你可以在以下位置更改项目的名称版本, 主要要点是你需要创建一个有效文件:
{"name": "peerjs-videochat-application-client", "version": "1.0.0"}

一旦package.json文件有效, 继续执行以下步骤安装Express模块??:
npm install express

【如何使用PeerJS和Node.js与WebRTC创建视频聊天】安装此模块后, 你将能够轻松设置本地服务器。现在转到项目的公共文件夹, 并使用以下代码修改website-server.js文件:
注意 服务器使用第一步中提到的证书文件, 因此, 如果你决定更改示例项目的结构, 请确保也更改证书文件的路径。
// project/public/website-server.js/** * This script starts a https server accessible at https://localhost:8443 * to test the chat * * @author Carlos Delgado (Our Code World) */var fs= require('fs'); var http= require('http'); var https= require('https'); var path= require("path"); var os= require('os'); var ifaces = os.networkInterfaces(); // Public Self-Signed Certificates for HTTPS connectionvar privateKey= fs.readFileSync('./../certificates/key.pem', 'utf8'); var certificate = fs.readFileSync('./../certificates/cert.pem', 'utf8'); var credentials = {key: privateKey, cert: certificate}; var express = require('express'); var app = express(); var httpServer = http.createServer(app); var httpsServer = https.createServer(credentials, app); /** *Show in the console the URL access for other devices in the network */Object.keys(ifaces).forEach(function (ifname) {var alias = 0; ifaces[ifname].forEach(function (iface) {if ('IPv4' !== iface.family || iface.internal !== false) {// skip over internal (i.e. 127.0.0.1) and non-ipv4 addressesreturn; }console.log(""); console.log("Welcome to the Chat Sandbox"); console.log(""); console.log("Test the chat interface from this device at : ", "https://localhost:8443"); console.log(""); console.log("And access the chat sandbox from another device through LAN using any of the IPS:"); console.log("Important: Node.js needs to accept inbound connections through the Host Firewall"); console.log(""); if (alias > = 1) {console.log("Multiple ipv4 addreses were found ... "); // this single interface has multiple ipv4 addressesconsole.log(ifname + ':' + alias, "https://"+ iface.address + ":8443"); } else {// this interface has only one ipv4 adressconsole.log(ifname, "https://"+ iface.address + ":8443"); }++alias; }); }); // Allow access from all the devices of the network (as long as connections are allowed by the firewall)var LANAccess = "0.0.0.0"; // For httphttpServer.listen(8080, LANAccess); // For httpshttpsServer.listen(8443, LANAccess); // Serve the index.html file as content of the / routeapp.get('/', function (req, res) {res.sendFile(path.join(__dirname+'/index.html')); }); // Expose the js resources as "resources"app.use('/resources', express.static('./source'));

这段代码设置了一个非常基本的Express服务器, 执行时可以在浏览器的本地端口8443(https)上访问它。除此之外, 它将列出(一旦在控制台中执行)可以从LAN中的其他设备访问它的地址(请参阅步骤6), 或者, 如果将其部署在生产服务器上, 则可以将其删除。
将更改保存在文件上, 然后转到下一步。
4.设置PeerJS服务器 PeerServer帮助PeerJS客户端之间的代理连接, 并且数据不通过服务器代理。要安装PeerJS的服务器端模块, 请打开在项目/服务器上创建的package.json文件, 并至少添加必需的参数以创建有效文件:
{"name": "peerjs-videochat-application-server", "version": "1.0.0"}

创建后, 在同一目录中运行以下命令来安装服务器
npm install peer

继续使用以下内容修改项目服务器文件夹(项目/服务器)中的peer-server.js文件:
// project/server/peer-server.jsvar fs = require('fs'); var PeerServer = require('peer').PeerServer; var server = PeerServer({port: 9000, path: '/peerjs', ssl: {key: fs.readFileSync('./../certificates/key.pem', 'utf8'), cert: fs.readFileSync('./../certificates/cert.pem', 'utf8')}});

如你所见, PeerJS的服务器端配置非常简单, 你无需在服务器端执行任何其他操作。你可以添加一些事件侦听器, 但这不是必需的, 因为Peer Server将自动处理所有必需的逻辑。该服务器将在执行时在端口9000上的服务器上运行。
5.设置客户端代码 客户端可以非常简单。可以将其想象为另一个无聊的网页, 但这确实很棒。你为项目提供的样式取决于你自己, 例如, 我们使用Bootstrap框架使用Bootswatch中的Cerulean Theme创建漂亮的布局。
我们将在示例中使用的标记(project / public / index.html)如下所示:
< !DOCTYPE html> < html lang="en"> < head> < meta charset="UTF-8"> < title> Video Chat with PeerJS< /title> < !-- Using some styles Bootswatch CSS from cdn --> < link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/cerulean/bootstrap.min.css"> < /head> < body> < div class="container"> < div class="row"> < div class="col-md-6 col-lg-6"> < !-- Display video of the current userNote: mute your own video, otherwise you'll hear yourself ...--> < div class="text-center"> < video id="my-camera"width="300" height="300" autoplay="autoplay" muted="true" class="mx-auto d-block"> < /video> < span class="label label-info"> You< /span> < /div> < /div> < div class="col-md-6 col-lg-6"> < !-- Display video of the connected peer --> < div class="text-center"> < video id="peer-camera" width="300" height="300" autoplay="autoplay" class="mx-auto d-block"> < /video> < span class="label label-info" id="connected_peer"> < /span> < /div> < /div> < /div> < div class="row"> < h1 class="text-center"> Videochat Example< br> < small> Share the following ID with the pal that wants to talk with you< /small> < /h1> < !-- The ID of your current session --> < h4 class="text-center"> < span id="peer-id-label"> < /span> < /h4> < div class="col-md-12 col-lg-12"> < div class="form-horizontal" id="connection-form"> < fieldset> < legend> Connection Form< /legend> < div class="form-group"> < label for="name" class="col-lg-2 control-label"> Username< /label> < div class="col-lg-10"> < input type="text" class="form-control" name="name" id="name" placeholder="Your random username"> < /div> < /div> < div class="form-group"> < label for="peer_id" class="col-lg-2 control-label"> Peer ID (id of your pal)< /label> < div class="col-lg-10"> < input type="text" class="form-control" name="peer_id" id="peer_id" placeholder="Peer ID" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"> < !-- Show message if someone connected to the client --> < div id="connected_peer_container" class="hidden"> An user is already connected to your session. Just provide a name to connect !< /div> < /div> < /div> < div class="form-group"> < div class="col-lg-10 col-lg-offset-2"> < button id="connect-to-peer-btn" class="btn btn-primary"> Connect to Peer< /button> < /div> < /div> < /fieldset> < /div> < /div> < div class="col-md-12 col-lg-12"> < div id="chat" class="hidden"> < div id="messages-container"> < div class="list-group" id="messages"> < /div> < /div> < div id="message-container"> < div class="form-group"> < label class="control-label"> Live chat< /label> < div class="input-group"> < span class="input-group-btn"> < button id="call" class="btn btn-info"> Call< /button> < /span> < input type="text" class="form-control" name="message" id="message" placeholder="Your messag here ..."> < span class="input-group-btn"> < button id="send-message" class="btn btn-success"> Send Message< /button> < /span> < /div> < /div> < /div> < /div> < /div> < /div> < /div> < !-- Include the Client Side version of peer.js using a script tag !--> < script src="http://www.srcmini.com/resources/js/peer.min.js"> < /script> < !-- Include the scripts that will handle the chat --> < script src="http://www.srcmini.com/resources/js/script.js"> < /script> < /body> < /html>

标记结构上重要的一点是, 你遵循要提供的ID来制作以下JavaScript。请注意, 第一个视频标签(将显示你自己的视频的标签)需要将静音属性设置为true, 否则, 一旦开始传输, 你将听到自己的声音。还需要包括Peer.js的客户端版本, 此文件可以从此处的官方存储库或任何免费的CDN中获得。关键是peer.min.js文件必须位于project / public / js中。
现在对于project / public / js / scripts.js文件, 我们将编写处理该代码的方法, 首先编写一个DOMContentLoaded事件侦听器:
// When the DOM is readydocument.addEventListener("DOMContentLoaded", function(event) {// All the code of scripts.js here ...}, false);

我们现在将解释的所有代码都必须位于上一个回调中。首先, 需要确定PeerJS客户端的初始化方式并创建一些全局变量(仅适用于scripts.js文件):
var peer_id; var username; var conn; /** * Important: the host needs to be changed according to your requirements. * e.g if you want to access the Peer server from another device, the * host would be the IP of your host namely 192.xxx.xxx.xx instead * of localhost. * * The iceServers on this example are public and can be used for your project. */var peer = new Peer({host: "localhost", port: 9000, path: '/peerjs', debug: 3, config: {'iceServers': [{ url: 'stun:stun1.l.google.com:19302' }, {url: 'turn:numb.viagenie.ca', credential: 'muazkh', username: 'webrtc@live.com'}]}});

在这一步中, WebRTC知识起着重要的作用, 因此, 如果你对此一无所知, 建议你在此处的HTML5 Rocks这篇写得很好的文章中阅读有关ice服务器的更多信息。本示例使用免费的Ice Server使其正常工作, 但是它们可能永远无法运行或永远处于活动状态, 因此建议你在经营企业时购买并拥有自己的STUN或TURN服务器。因此, 你将拥有部署生产级WebRTC应用程序所需的所有基础结构。
另一方面, 我们使用localhost作为主机, 通常足以让生产使其工作。如果你要进行测试, 则知道你不能使用同一台计算机来测试视频聊天, 因为2个浏览器无法同时访问摄像机, 因此你可能会将本地服务器暴露给LAN(解释)在下一步中), 方法是将主机更改为计算机的IP。
现在, 向对等方添加一些事件侦听器, 当对等方最重要的事件如视频通话等发生时, 你将允许你执行一些操作:
// Once the initialization succeeds:// Show the ID that allows other user to connect to your session.peer.on('open', function () {document.getElementById("peer-id-label").innerHTML = peer.id; }); // When someone connects to your session:// // 1. Hide the peer_id field of the connection form and set automatically its value// as the peer of the user that requested the connection.// 2. Update global variables with received valuespeer.on('connection', function (connection) {conn = connection; peer_id = connection.peer; // Use the handleMessage to callback when a message comes inconn.on('data', handleMessage); // Hide peer_id field and set the incoming peer id as valuedocument.getElementById("peer_id").className += " hidden"; document.getElementById("peer_id").value = http://www.srcmini.com/peer_id; document.getElementById("connected_peer").innerHTML = connection.metadata.username; }); peer.on('error', function(err){alert("An error ocurred with peer: " + err); console.error(err); }); /** * Handle the on receive call event */peer.on('call', function (call) {var acceptsCall = confirm("Videocall incoming, do you want to accept it ?"); if(acceptsCall){// Answer the call with your own video/audio streamcall.answer(window.localStream); // Receive datacall.on('stream', function (stream) {// Store a global reference of the other user streamwindow.peer_stream = stream; // Display the stream of the other user in the peer-camera video element !onReceiveStream(stream, 'peer-camera'); }); // Handle when the call finishescall.on('close', function(){alert("The videocall has finished"); }); // use call.close() to finish a call}else{console.log("Call denied !"); }});

现在添加一些辅助方法, 以在列表视图中显示已接收和已发送的数据, 并在浏览器上请求视频/音频:
/** * Starts the request of the camera and microphone * * @param {Object} callbacks */function requestLocalVideo(callbacks) {// Monkeypatch for crossbrowser geusermedianavigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; // Request audio an videonavigator.getUserMedia({ audio: true, video: true }, callbacks.success , callbacks.error); }/** * Handle the providen stream (video and audio) to the desired video element * * @param {*} stream * @param {*} element_id */function onReceiveStream(stream, element_id) {// Retrieve the video element according to the desiredvar video = document.getElementById(element_id); // Set the given stream as the video source video.src = http://www.srcmini.com/window.URL.createObjectURL(stream); // Store a global reference of the streamwindow.peer_stream = stream; }/** * Appends the received and sent message to the listview * * @param {Object} data */function handleMessage(data) {var orientation ="text-left"; // If the message is yours, set text to right !if(data.from == username){orientation = "text-right"}var messageHTML ='< a href="javascript:void(0); " class="list-group-item' + orientation + '"> '; messageHTML += '< h4 class="list-group-item-heading"> '+ data.from +'< /h4> '; messageHTML += '< p class="list-group-item-text"> '+ data.text +'< /p> '; messageHTML += '< /a> '; document.getElementById("messages").innerHTML += messageHTML; }

接下来, 将对用户界面中的每个动作做出反应的事件侦听器定义为登录事件, 开始调用等:
/** * Handle the send message button */document.getElementById("send-message").addEventListener("click", function(){// Get the text to sendvar text = document.getElementById("message").value; // Prepare the data to sendvar data = http://www.srcmini.com/{from: username, text: text }; // Send the message with Peerconn.send(data); // Handle the message on the UIhandleMessage(data); document.getElementById("message").valuehttp://www.srcmini.com/= ""; }, false); /** *Request a videocall the other user */document.getElementById("call").addEventListener("click", function(){console.log('Calling to ' + peer_id); console.log(peer); var call = peer.call(peer_id, window.localStream); call.on('stream', function (stream) {window.peer_stream = stream; onReceiveStream(stream, 'peer-camera'); }); }, false); /** * On click the connect button, initialize connection with peer */document.getElementById("connect-to-peer-btn").addEventListener("click", function(){username = document.getElementById("name").value; peer_id = document.getElementById("peer_id").value; if (peer_id) {conn = peer.connect(peer_id, {metadata: {'username': username}}); conn.on('data', handleMessage); }else{alert("You need to provide a peer to connect with !"); return false; }document.getElementById("chat").className = ""; document.getElementById("connection-form").className += " hidden"; }, false);

作为最后一步(不需要立即执行), 你可以调用requestLocalVideo方法以启动自己的流(该流将用于发送给其他用户):
/** * Initialize application by requesting your own video to test ! */requestLocalVideo({success: function(stream){window.localStream = stream; onReceiveStream(stream, 'my-camera'); }, error: function(err){alert("Cannot get access to your camera and video !"); console.error(err); }});

6.允许Node.js的入站连接(仅在本地工作) 如果你尝试通过移动设备(Android设备)或局域网中的其他设备使用计算机的IP地址(而不是localhost)访问计算机中提到的地址(localhost:8443), 以进行视频聊天测试(因为你可以在同一台计算机上测试视频聊天), 并且Node.js被防火墙的某些规则阻止, 它(可能)不会简单地工作:
如何使用PeerJS和Node.js与WebRTC创建视频聊天

文章图片
如果你确定服务器在计算机上运行, ??则问题可能是由防火墙限制引起的, 要使其正常运行, 你将需要允许与计算机中Node.js应用程序的所有入站连接。例如, 在Windows中, 你只需打开防火墙, 导航到” 入站规则” , 然后在列表中搜索Node.js:
如何使用PeerJS和Node.js与WebRTC创建视频聊天

文章图片
右键单击Node.js的选定项, 然后从上下文菜单中选择” 属性” 。在此菜单中, 导航到” 常规” 选项卡, 然后在” 操作” 区域中, 选择” 允许连接” 单选按钮:
如何使用PeerJS和Node.js与WebRTC创建视频聊天

文章图片
这应该立即生效, 但是可以肯定的是, 重新启动打开了Node的终端, 然后再次启动它。
7.运行聊天 如果一切都按预期进行, 你现在就可以自己测试视频聊天了。你需要做的就是使用Node运行每个目录(公共目录和服务器目录)的服务器文件, 并使它们在后台运行。
打开一个新终端并切换到project / public目录, 然后运行以下命令:
node website-server.js

这将为你的网站启动服务器以测试视频聊天。然后打开另一个终端, 切换到项目/服务器目录并运行以下命令:
node peer-server.js

这将启动与Peer的聊天服务器。让2终端处于活动状态, 并使用浏览器访问https:// localhost:8443网址, 你将看到Videochat模板。在此示例中, 我们将使用2个用户, 即Huskee先生(第一个用户)和Doge先生(第二个用户):
如何使用PeerJS和Node.js与WebRTC创建视频聊天

文章图片
在这种情况下, Huskee先生的摄像头将按照定义的行为自动启动, 可以根据需要进行明显更改。此时的界面要求某人使用屏幕中间的ID进行连接, 这意味着, 如果你想与某人开始视频聊天, 则只需将ID提供给其他用户即可。如果你不是在等待某人连接, 而是想与某人连接, 则需要其他用户的ID。 Doge希望使用另一台计算机或此示例中的移动设备与Huskee先生开始聊天, 因此我们需要以我们的形式和姓名输入Huskee先生的ID(在本例中为8n9hrtc80tzhvlb6):
如何使用PeerJS和Node.js与WebRTC创建视频聊天

文章图片
一旦Doge先生拥有表单的基本数据并单击Connect to Peer, Huskee先生的屏幕将自动更新, 并显示消息, 表明有人已连接到该会话, 即Doge先生, 他只需要提供用户名:
如何使用PeerJS和Node.js与WebRTC创建视频聊天

文章图片
现在, Huskee先生已登录并可以轻松与Doge先生聊天, 因此, 由于他已经建立了连接, 因此他无法提供Peer ID来连接到其他人。此刻的聊天没有视频聊天, 只有文本聊天:
如何使用PeerJS和Node.js与WebRTC创建视频聊天

文章图片
因此, 有人需要单击” 呼叫” 按钮, 在这种情况下, 将是移动设备上的Doge先生。然后, 如果他想开始通话, Huskee先生将在浏览器中收到提示:
如何使用PeerJS和Node.js与WebRTC创建视频聊天

文章图片
如果被接受, 视频聊天将毫无问题地开始, 他们可以像使用Skype这样的应用程序进行对话和编写内容:
如何使用PeerJS和Node.js与WebRTC创建视频聊天

文章图片
如本文开头所述, 你可以在Github的官方存储库中找到此示例的源代码。
编码愉快!

    推荐阅读