代码之家  ›  专栏  ›  技术社区  ›  Supun Abesekara

使用节点JS逐步创建WebRTC视频、语音呼叫和文件传输Q-A

  •  2
  • Supun Abesekara  · 技术社区  · 6 年前

    如果需要添加信息或编辑某些信息,欢迎使用。

    你好, 首先,这些问题的主要目标是将我的经验剪切到其他开发人员身上,他们发现了关于WebRTC的优秀教程。我不打算解释WebRTC。在本文中,我添加了工作WebRTC视频、语音呼叫和文件传输示例的搜索代码。

    我从中获取WebRTC信息 https://webrtc.org/ 然后点点头 从 https://nodejs.org/en/

    好的,我们开始吧

    Web RTC是否需要通过SSL认证?

    如果你在本地PC服务器上做实验,就不需要了。但是当您将添加到Live服务器时,是的,您需要它。

    如何获得SSL证书?

    我的一个朋友帮我得到那个SSL。有很多教程供你阅读和观看

    我是怎么转晕服务器的?

    如果你进入生产级,你需要设置这些服务器,但为了测试你的项目,你可以得到特技服务器和免费转服务器。

    用于特技服务器- https://gist.github.com/zziuni/3741933

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

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

    1 回复  |  直到 6 年前
        1
  •  1
  •   Supun Abesekara    6 年前

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

    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>