个性化阅读
专注于IT技术分析

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

本文概述

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

安装此模块后, 你将能够轻松设置本地服务器。现在转到项目的公共文件夹, 并使用以下代码修改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被防火墙的某些规则阻止, 它(可能)不会简单地工作:

Artyom沙箱内部IP LAN错误

如果你确定服务器在计算机上运行, ​​则问题可能是由防火墙限制引起的, 要使其正常运行, 你将需要允许与计算机中Node.js应用程序的所有入站连接。例如, 在Windows中, 你只需打开防火墙, 导航到”入站规则”, 然后在列表中搜索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先生(第二个用户):

使用Node.js与PeerJS进行WebRTC视频聊天

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

移动设备对等JS Videochat Node.js

一旦Doge先生拥有表单的基本数据并单击Connect to Peer, Huskee先生的屏幕将自动更新, 并显示消息, 表明有人已连接到该会话, 即Doge先生, 他只需要提供用户名:

Videochat WebRTC PeerJS登录

现在, Huskee先生已登录并可以轻松与Doge先生聊天, 因此, 由于他已经建立了连接, 因此他无法提供Peer ID来连接到其他人。此刻的聊天没有视频聊天, 只有文本聊天:

Videochat WebRTC PeerJS Node.js

因此, 有人需要单击”呼叫”按钮, 在这种情况下, 将是移动设备上的Doge先生。然后, 如果他想开始通话, Huskee先生将在浏览器中收到提示:

接受视频通话Videochat Peerjs Webrtc

如果被接受, 视频聊天将毫无问题地开始, 他们可以像使用Skype这样的应用程序进行对话和编写内容:

Videochat WebRTC PeerJS Node.js

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

编码愉快!

赞(0)
未经允许不得转载:srcmini » 如何使用PeerJS和Node.js与WebRTC创建视频聊天

评论 抢沙发

评论前必须登录!