本文概述
- 继续之前
- 内容指南
- 1.创建或获取一些SSL证书
- 2.示范项目结构
- 3.设置测试本地服务器
- 4.设置PeerJS服务器
- 5.设置客户端代码
- 6.允许Node.js的入站连接(仅在本地工作)
- 7.运行聊天
WebRTC给用户和开发人员带来了很多好处, 而这些好处过去在网络上创建通信和协作工具的可能性很小。例如, 它提供了多种连接方式, 可与实时聊天和视频共同使用。要创建实时语音或视频连接, PeerJS是最出色的库之一, 它使你可以在Web应用程序中实现这种功能而不会(太多)麻烦。 PeerJS包装了浏览器的WebRTC实现, 以提供完整, 可配置且易于使用的对等连接API。对等端仅配备一个ID, 即可轻松创建与远程对等端的P2P数据或媒体流连接。
在本文中, 你将学习如何使用自己的托管的带有Node.js的PeerJS服务器实现Videochat。
继续之前
你将需要耐心等待, 因为如果你没有正确配置所有功能, 那么初次尝试可能无法按预期进行。因此, 要坚持不懈, 请仔细阅读所有说明, 你可能会在第一次广告上获得成功。
如果你认为本教程很长, 我们建议你在Github的此存储库中克隆正式示例, 其中包含在本文中可以找到的所有示例代码, 但是没有任何解释。不过, 自己进行测试很有用。
内容指南
本教程将是无穷无尽的, 因此, 这里是你需要遵循的所有步骤的快速介绍:
- 获取一些自动签名的SSL证书进行测试。
- 创建演示项目结构
- 使用Express创建一个安全的本地服务器以提供我们的HTML, CSS和JS文件。
- 创建一个安全的PeerJS服务器来处理信息交换。
- 编写代码以处理视频聊天。
- 了解PeerJS的工作原理。
- 创建标记以创建示例聊天。
- 允许Node.js的入站连接并更新客户端主机(仅在本地测试时)
- 运行服务器并测试
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
安装此模块后, 你将能够轻松设置本地服务器。现在转到项目的公共文件夹, 并使用以下代码修改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 connection
var 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 addresses
return;
}
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 addresses
console.log(ifname + ':' + alias, "https://"+ iface.address + ":8443");
} else {
// this interface has only one ipv4 adress
console.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 http
httpServer.listen(8080, LANAccess);
// For https
httpsServer.listen(8443, LANAccess);
// Serve the index.html file as content of the / route
app.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.js
var 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 user
Note: 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="resources/js/peer.min.js"></script>
<!-- Include the scripts that will handle the chat -->
<script src="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 ready
document.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 values
peer.on('connection', function (connection) {
conn = connection;
peer_id = connection.peer;
// Use the handleMessage to callback when a message comes in
conn.on('data', handleMessage);
// Hide peer_id field and set the incoming peer id as value
document.getElementById("peer_id").className += " hidden";
document.getElementById("peer_id").value = 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 stream
call.answer(window.localStream);
// Receive data
call.on('stream', function (stream) {
// Store a global reference of the other user stream
window.peer_stream = stream;
// Display the stream of the other user in the peer-camera video element !
onReceiveStream(stream, 'peer-camera');
});
// Handle when the call finishes
call.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 geusermedia
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
// Request audio an video
navigator.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 desired
var video = document.getElementById(element_id);
// Set the given stream as the video source
video.src = window.URL.createObjectURL(stream);
// Store a global reference of the stream
window.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 send
var text = document.getElementById("message").value;
// Prepare the data to send
var data = {
from: username, text: text
};
// Send the message with Peer
conn.send(data);
// Handle the message on the UI
handleMessage(data);
document.getElementById("message").value = "";
}, 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被防火墙的某些规则阻止, 它(可能)不会简单地工作:
如果你确定服务器在计算机上运行, 则问题可能是由防火墙限制引起的, 要使其正常运行, 你将需要允许与计算机中Node.js应用程序的所有入站连接。例如, 在Windows中, 你只需打开防火墙, 导航到”入站规则”, 然后在列表中搜索Node.js:
右键单击Node.js的选定项, 然后从上下文菜单中选择”属性”。在此菜单中, 导航到”常规”选项卡, 然后在”操作”区域中, 选择”允许连接”单选按钮:
这应该立即生效, 但是可以肯定的是, 重新启动打开了Node的终端, 然后再次启动它。
7.运行聊天
如果一切都按预期进行, 你现在就可以自己测试视频聊天了。你需要做的就是使用Node运行每个目录(公共目录和服务器目录)的服务器文件, 并使它们在后台运行。
打开一个新终端并切换到project / public目录, 然后运行以下命令:
node website-server.js
这将为你的网站启动服务器以测试视频聊天。然后打开另一个终端, 切换到项目/服务器目录并运行以下命令:
node peer-server.js
这将启动与Peer的聊天服务器。让2终端处于活动状态, 并使用浏览器访问https:// localhost:8443网址, 你将看到Videochat模板。在此示例中, 我们将使用2个用户, 即Huskee先生(第一个用户)和Doge先生(第二个用户):
在这种情况下, Huskee先生的摄像头将按照定义的行为自动启动, 可以根据需要进行明显更改。此时的界面要求某人使用屏幕中间的ID进行连接, 这意味着, 如果你想与某人开始视频聊天, 则只需将ID提供给其他用户即可。如果你不是在等待某人连接, 而是想与某人连接, 则需要其他用户的ID。 Doge希望使用另一台计算机或此示例中的移动设备与Huskee先生开始聊天, 因此我们需要以我们的形式和姓名输入Huskee先生的ID(在本例中为8n9hrtc80tzhvlb6):
一旦Doge先生拥有表单的基本数据并单击Connect to Peer, Huskee先生的屏幕将自动更新, 并显示消息, 表明有人已连接到该会话, 即Doge先生, 他只需要提供用户名:
现在, Huskee先生已登录并可以轻松与Doge先生聊天, 因此, 由于他已经建立了连接, 因此他无法提供Peer ID来连接到其他人。此刻的聊天没有视频聊天, 只有文本聊天:
因此, 有人需要单击”呼叫”按钮, 在这种情况下, 将是移动设备上的Doge先生。然后, 如果他想开始通话, Huskee先生将在浏览器中收到提示:
如果被接受, 视频聊天将毫无问题地开始, 他们可以像使用Skype这样的应用程序进行对话和编写内容:
如本文开头所述, 你可以在Github的官方存储库中找到此示例的源代码。
编码愉快!
评论前必须登录!
注册