如何使用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的官方存储库中找到此示例的源代码。

编码愉快!

来源:

https://www.srcmini02.com/61865.html

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?