首页 > 解决方案 > 使用node js创建webrtc视频、语音通话和文件传输一步一步QA

问题描述

如果任何人需要添加信息或编辑一些信息,欢迎他们。

嗨,首先,这些问题的主要目标是将我的经验分享给其他找到关于 webrtc 的好教程的开发人员。我不打算解释 webrtc。在此我添加了我测试的工作 webrtc 视频、语音通话和文件传输示例的课程代码。

我从 https://webrtc.org/ 获取 webrtc 信息并从 https://nodejs.org/en/获取nodejs

好的,让我们开始吧

web rtc 是否需要 ssl 认证?

如果您在本地 PC 服务器上进行实验,则无需。但是当您添加到实时服务器时,是的,您需要它。

我如何获得 ssl 证书?

我的一个朋友帮我拿到了那个SSl。有很多教程供您阅读和观看

我如何获得转弯和眩晕服务器?

如果您进入生产级别,则需要设置这些服务器,但是为了测试您的项目,您可以免费获得 stun 服务器和打开服务器。

对于 Stun 服务器 - https://gist.github.com/zziuni/3741933

对于 Turn 服务器 - 使用此链接并免费创建一个 ( http://numb.viagenie.ca/ )。

我在下面添加我的工作代码作为答案

标签: node.jsexpresshttpssocket.iowebrtc

解决方案


这是带有节点 js 的 webrtc 的工作代码

此代码和注释不是由我编写的代码。当我得到代码时,他们已经在那里了。我找不到代码原始所有者。但我感谢那个开发者。如果有人发现那个开发者,请编辑这个并添加那个开发者链接:)

var express = require('express');

var socket = require('socket.io');
var app = express();
var fs = require('fs');
var https = require('https');

// link  your  https  certicate  path 
var options = {
    key: fs.readFileSync('/../../etc/ssl/private/apache-selfsigned.key'),
    cert: fs.readFileSync('/../../etc/ssl/certs/apache-selfsigned.crt')
};


var main = https.createServer(options, app);

var server = main.listen(8443, function() {
    console.log('server up and running at %s port', 8443);
});

/*var server = app.listen(443, function () {
});*/
app.use(express.static('public'));
var io = socket(server);
/*************************/
/*** INTERESTING STUFF ***/
/*************************/
var channels = {};
var sockets = {};
/**
 * Users will connect to the signaling server, after which they'll issue a "join"
 * to join a particular channel. The signaling server keeps track of all sockets
 * who are in a channel, and on join will send out 'addPeer' events to each pair
 * of users in a channel. When clients receive the 'addPeer' even they'll begin
 * setting up an RTCPeerConnection with one another. During this process they'll
 * need to relay ICECandidate information to one another, as well as SessionDescription
 * information. After all of that happens, they'll finally be able to complete
 * the peer connection and will be streaming audio/video between eachother.
 */
io.on('connection', function (socket) {
    var channel;
    socket.channels = {};
    sockets[socket.id] = socket;
    console.log("[" + socket.id + "] connection accepted");
    socket.on('disconnect', function () {
        for (var channel in socket.channels) {
            part(channel);
        }
        console.log("[" + socket.id + "] disconnected");
        delete sockets[socket.id];
    });
    socket.on('join-room', function (config) {
        if (config) {
            channel = config.channel;
            var userdata = config.userdata;
            var userID = config.userdata.userID;
            if (channel in socket.channels) {
                console.log("[" + socket.id + "] ERROR: already joined ", channel);
                return;
            }
            if (!(channel in channels)) {
                channels[channel] = {};
            }
            for (id in channels[channel]) {
                channels[channel][id].emit('addPeer-room', {'peer_id': socket.id, 'should_create_offer': false});
                socket.emit('addPeer-room', {'peer_id': id, 'should_create_offer': true});
                console.log("what  is this  id -> ", id);
            }
            console.log(config.userdata.name, ' joining room', config.channel);
            socket.join(config.channel);
            socket.broadcast.in(config.channel).emit('room-users', config);
            channels[channel][socket.id] = socket;
            socket.channels[channel] = channel;
        }
    });
    function part(channel) {
        console.log("[" + socket.id + "] part ");
        if (!(channel in socket.channels)) {
            console.log("[" + socket.id + "] ERROR: not in ", channel);
            return;
        }
        delete socket.channels[channel];
        delete channels[channel][socket.id];
        for (id in channels[channel]) {
            channels[channel][id].emit('removePeer', {'peer_id': socket.id});
            socket.emit('removePeer', {'peer_id': id});
        }
    }

    socket.on('part', part);
    socket.on('relayICECandidate-room', function (config) {
        var peer_id = config.peer_id;
        var ice_candidate = config.ice_candidate;
        console.log("[" + socket.id + "] relaying ICE candidate to [" + peer_id + "] ", ice_candidate);
        if (peer_id in sockets) {
            sockets[peer_id].emit('iceCandidate-room', {'peer_id': socket.id, 'ice_candidate': ice_candidate});
        }
    });
    socket.on('relaySessionDescription-room', function (config) {
        var peer_id = config.peer_id;
        var session_description = config.session_description;
        console.log("[" + socket.id + "] relaying session description to [" + peer_id + "] ", session_description);
        if (peer_id in sockets) {
            sockets[peer_id].emit('sessionDescription-room', {
                'peer_id': socket.id,
                'session_description': session_description
            });
        }
    });
    // this for  file  transfer
    socket.on('file-send-room', function (file) {
        console.log(file);
        socket.to(channel).emit('file-out-room', file);
    });
    socket.on('file-send-room-result', function (file) {
        console.log(file);
        socket.to(channel).emit('file-out-room-result', file);
    });
});
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>

    <script src="https://cdn.socket.io/socket.io-1.4.5.js"></script>
    <script>
        function getParameterByName(name, url) {
            if (!url) url = window.location.href;
            name = name.replace(/[\[\]]/g, "\\$&");
            var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
                results = regex.exec(url);
            if (!results) return null;
            if (!results[2]) return '';
            return decodeURIComponent(results[2].replace(/\+/g, " "));
        }
        var fileInput = document.querySelector('input#fileInput');
        var downloadAnchor = document.querySelector('a#download');
        
        // this function use to get url parameters
        
        
        var room = getParameterByName('room');
        var userID = getParameterByName('userid');
        var name = getParameterByName('name');
        /** CONFIG **/
        var SIGNALING_SERVER = "https://xxx.xx.xx.xxx:8443"; //your  node server addres  or  IP adress
        var USE_AUDIO = true;
        var USE_VIDEO = true;
        var MUTE_AUDIO_BY_DEFAULT = false;
        /** You should probably use a different stun server doing commercial stuff **/
        /** Also see: https://gist.github.com/zziuni/3741933 **/
        var ICE_SERVERS = [
            {urls: "stun:stun.l.google.com:19302"},{
                urls: 'turn:numb.viagenie.ca:3478',
                credential: '12344', //your  password
                username: 'your@email.com'
            }
        ];
        var socket = null;
        /* our socket.io connection to our webserver */
        var local_media_stream = null;
        /* our own microphone / webcam */
        var peers = {};
        /* keep track of our peer connections, indexed by peer_id (aka socket.io id) */
        var peer_media_elements = {};
        /* keep track of our <video>/<audio> tags, indexed by peer_id */
        $(document).ready(function (a) {
            socket = io(SIGNALING_SERVER);
            socket = io();
            //----------------------------------------------------------------------->>>>> Files Send Start
            const BYTES_PER_CHUNK = 1200;
            var file;
            var currentChunk;
            var fileInput = $('input[type=file]');
            var fileReader = new FileReader();

            function readNextChunk() {
                var start = BYTES_PER_CHUNK * currentChunk;
                var end = Math.min(file.size, start + BYTES_PER_CHUNK);
                fileReader.readAsArrayBuffer(file.slice(start, end));
            }

            fileReader.onload = function () {
                socket.emit('file-send-room-result', fileReader.result);
                //p2pConnection.send( fileReader.result );
                currentChunk++;
                if (BYTES_PER_CHUNK * currentChunk < file.size) {
                    readNextChunk();
                }
            };
            fileInput.on('change', function () {
                file = fileInput[0].files[0];
                currentChunk = 0;
                // send some metadata about our file
                // to the receiver
                socket.emit('file-send-room', JSON.stringify({
                    fileName: file.name,
                    fileSize: file.size
                }));
                readNextChunk();
            });
            var incomingFileInfo;
            var incomingFileData;
            var bytesReceived;
            var downloadInProgress = false;
            socket.on('file-out-room', function (data) {
                startDownload(data);

                console.log(data);
            });
            socket.on('file-out-room-result', function (data) {
                progressDownload(data);
                console.log(data);            });
            function startDownload(data) {
                incomingFileInfo = JSON.parse(data.toString());
                incomingFileData = [];
                bytesReceived = 0;
                downloadInProgress = true;
                console.log('incoming file <b>' + incomingFileInfo.fileName + '</b> of ' + incomingFileInfo.fileSize + ' bytes');
            }

            function progressDownload(data) {
                bytesReceived += data.byteLength;
                incomingFileData.push(data);
                console.log('progress: ' + ((bytesReceived / incomingFileInfo.fileSize ) * 100).toFixed(2) + '%');
                if (bytesReceived === incomingFileInfo.fileSize) {
                    endDownload();
                }
            }

            function endDownload() {
                downloadInProgress = false;
                var blob = new Blob(incomingFileData);
               
                var a = document.createElement("a");
                document.body.appendChild(a);
                a.style = "display: none";
                var blob = new Blob(incomingFileData);
                var url = window.URL.createObjectURL(blob);
                a.href = url;
                a.download = incomingFileInfo.fileName;
                a.click();
                window.URL.revokeObjectURL(url);
            }

            //==================================================================<<< Filse Send End
            //------------------------ Funtion
            function join_chat_channel(channel, userdata) {
                socket.emit('join-room', {"channel": channel, "userdata": userdata});
            }

            socket.on('connect', function (userID) {
                console.log("Connected to signaling server");
                setup_local_media(function () {
                    /* once the user has given us access to their
                     * microphone/camcorder, join the channel and start peering up */
                    join_chat_channel(room, {'name': name, 'userID': userID});
                });
            });
            socket.on('room-user', function (data) {
                console.log(data);
                $("#online-user").append('<tr><td>Name = ' + data.userdata.name + ' <br> User ID= ' + data.userdata.userID + '</td><td><button class="call" id="' + data.userdata.userID + '">Call</button></td></tr>');
            });
            $('body').on('click', '.call', function () {
                var callerID = $(this).attr('id');
            
                socket.emit('call', {"callToId": callerID, "callFromId": userID});
            });
            /**
             * When we join a group, our signaling server will send out 'addPeer' events to each pair
             * of users in the group (creating a fully-connected graph of users, ie if there are 6 people
             * in the channel you will connect directly to the other 5, so there will be a total of 15
             * connections in the network).
             */
            socket.on('addPeer-room', function (config) {
                console.log('Signaling server said to add peer:', config);
                var peer_id = config.peer_id;
                if (peer_id in peers) {
                    /* This could happen if the user joins multiple channels where the other peer is also in. */
                    console.log("Already connected to peer ", peer_id);
                    return;
                }
                var peer_connection = new RTCPeerConnection(
                    {"iceServers": ICE_SERVERS},
                    {"optional": [{"DtlsSrtpKeyAgreement": true}]} /* this will no longer be needed by chrome
                         * eventually (supposedly), but is necessary
                         * for now to get firefox to talk to chrome */
                );
                peers[peer_id] = peer_connection;
                peer_connection.onicecandidate = function (event) {
                    if (event.candidate) {
                        socket.emit('relayICECandidate-room', {
                            'peer_id': peer_id,
                            'ice_candidate': {
                                'sdpMLineIndex': event.candidate.sdpMLineIndex,
                                'candidate': event.candidate.candidate
                            }
                        });
                    }
                }
                peer_connection.onaddstream = function (event) {
                    console.log("onAddStream", event);
                    var remote_media = USE_VIDEO ? $("<video>") : $("<audio>");
                    remote_media.attr("autoplay", "autoplay");
                    if (MUTE_AUDIO_BY_DEFAULT) {
                        remote_media.attr("muted", "true");
                    }
                    remote_media.attr("controls", "");
                    peer_media_elements[peer_id] = remote_media;
                    $('body').append(remote_media);
                    attachMediaStream(remote_media[0], event.stream);
                }
                /* Add our local stream */
                peer_connection.addStream(local_media_stream);
                /* Only one side of the peer connection should create the
                 * offer, the signaling server picks one to be the offerer.
                 * The other user will get a 'sessionDescription' event and will
                 * create an offer, then send back an answer 'sessionDescription' to us
                 */
                if (config.should_create_offer) {
                    console.log("Creating RTC offer to ", peer_id);
                    peer_connection.createOffer(
                        function (local_description) {
                            console.log("Local offer description is: ", local_description);
                            peer_connection.setLocalDescription(local_description,
                                function () {
                                    socket.emit('relaySessionDescription-room',
                                        {'peer_id': peer_id, 'session_description': local_description});
                                    console.log("Offer setLocalDescription succeeded");
                                },
                                function () {
                                    Alert("Offer setLocalDescription failed!");
                                }
                            );
                        },
                        function (error) {
                            console.log("Error sending offer: ", error);
                        });
                }
            });
            /**
             * Peers exchange session descriptions which contains information
             * about their audio / video settings and that sort of stuff. First
             * the 'offerer' sends a description to the 'answerer' (with type
             * "offer"), then the answerer sends one back (with type "answer").
             */
            socket.on('sessionDescription-room', function (config) {
                console.log('Remote description received: ', config);
                var peer_id = config.peer_id;
                var peer = peers[peer_id];
                var remote_description = config.session_description;
                console.log(config.session_description);
                var desc = new RTCSessionDescription(remote_description);
                var stuff = peer.setRemoteDescription(desc,
                    function () {
                        console.log("setRemoteDescription succeeded");
                        if (remote_description.type == "offer") {
                            console.log("Creating answer");
                            peer.createAnswer(
                                function (local_description) {
                                    console.log("Answer description is: ", local_description);
                                    peer.setLocalDescription(local_description,
                                        function () {
                                            socket.emit('relaySessionDescription-room',
                                                {'peer_id': peer_id, 'session_description': local_description});
                                            console.log("Answer setLocalDescription succeeded");
                                        },
                                        function () {
                                            Alert("Answer setLocalDescription failed!");
                                        }
                                    );
                                },
                                function (error) {
                                    console.log("Error creating answer: ", error);
                                    console.log(peer);
                                });
                        }
                    },
                    function (error) {
                        console.log("setRemoteDescription error: ", error);
                    }
                );
                console.log("Description Object: ", desc);
            });
            /**
             * The offerer will send a number of ICE Candidate blobs to the answerer so they
             * can begin trying to find the best path to one another on the net.
             */
            socket.on('iceCandidate-room', function (config) {
                var peer = peers[config.peer_id];
                var ice_candidate = config.ice_candidate;
                peer.addIceCandidate(new RTCIceCandidate(ice_candidate));
            });
            /**
             * When a user leaves a channel (or is disconnected from the
             * signaling server) everyone will recieve a 'removePeer' message
             * telling them to trash the media channels they have open for those
             * that peer. If it was this client that left a channel, they'll also
             * receive the removePeers. If this client was disconnected, they
             * wont receive removePeers, but rather the
             * signaling_socket.on('disconnect') code will kick in and tear down
             * all the peer sessions.
             */
            socket.on('removePeer-room', function (config) {
                console.log('Signaling server said to remove peer:', config);
                var peer_id = config.peer_id;
                if (peer_id in peer_media_elements) {
                    peer_media_elements[peer_id].remove();
                }
                if (peer_id in peers) {
                    peers[peer_id].close();
                }
                delete peers[peer_id];
                delete peer_media_elements[config.peer_id];
            });
        });
        function setup_local_media(callback, errorback) {
            if (local_media_stream != null) {  /* ie, if we've already been initialized */
                if (callback) callback();
                return;
            }
            /* Ask user for permission to use the computers microphone and/or camera,
             * attach it to an <audio> or <video> tag if they give us access. */
            console.log("Requesting access to local audio / video inputs");
            navigator.getUserMedia = ( navigator.getUserMedia ||
            navigator.webkitGetUserMedia ||
            navigator.mozGetUserMedia ||
            navigator.msGetUserMedia);
            attachMediaStream = function (element, stream) {
                console.log('DEPRECATED, attachMediaStream will soon be removed.');
                element.srcObject = stream;
            };
            navigator.getUserMedia({"audio": USE_AUDIO, "video": USE_VIDEO},
                function (stream) { /* user accepted access to a/v */
                    console.log("Access granted to audio/video");
                    local_media_stream = stream;
                    var local_media = USE_VIDEO ? $("<video>") : $("<audio>");
                    local_media.attr("autoplay", "autoplay");
                    local_media.attr("muted", "true");
                    /* always mute ourselves by default */
                    local_media.attr("controls", "");
                    $('body').append(local_media);
                    attachMediaStream(local_media[0], stream);
                    if (callback) callback();
                },
                function () { /* user denied access to a/v */
                    console.log("Access denied for audio/video");
                    alert("You chose not to provide access to the camera/microphone, demo will not work.");
                    if (errorback) errorback();
                });
        }
    </script>
</head>
<body>

<form id="fileInfo">
    <input type="file" id="fileInput" name="files"/>
</form>
<a id="download"></a>
</body>
</html>


推荐阅读