javascript - WEBRTC 带 socket 的开关量输入设备

标签 javascript websocket webrtc

我有一个应用程序。在此应用程序中,我无法更改发送给对方的视频。

'use strict';

var Meeting = function (socketioHost) { 
    var exports = {};
    
    var _isInitiator = false;
    var _localStream;
    var _remoteStream;
    var _turnReady;
    var _pcConfig = {'iceServers': [{'url': 'stun:stun.l.google.com:19302'}]};
    var _constraints = {
        video: {
          width: {ideal: 320},
          height: {ideal: 240},
          frameRate: {ideal: 20}
        },
        audio: {
          googEchoCancellation: true,
          googAutoGainControl: true,
          googNoiseSuppression: true,
          googHighpassFilter: true,
          googEchoCancellation2: true,
          googAutoGainControl2: true,
          googNoiseSuppression2: true
        },
        options: {
            mirror: true
        }
    };

    if(navigator.userAgent.includes("iPhone")) {
        var _constraints =  {
            video : true
        }
    }

    var _defaultChannel;
    var _privateAnswerChannel;
    var _offerChannels = {};
    var _opc = {};
    var _apc = {};
    var _sendChannel = {};
    var _room;
    var _myID;
    var _onRemoteVideoCallback;
    var _onLocalVideoCallback;
    var _onChatMessageCallback;
    var _onChatReadyCallback;
    var _onChatNotReadyCallback;
    var _onParticipantHangupCallback;
    var _host = socketioHost;

    
    ////////////////////////////////////////////////
    // PUBLIC FUNCTIONS
    ////////////////////////////////////////////////
     /**
     *
     * Add callback function to be called when a chat message is available.
     *
     * @param name of the room to join
     */   
    function joinRoom(name) {
        _room = name;
        
        _myID = generateID();
        
        // Open up a default communication channel
        initDefaultChannel();

        if (_room !== '') {
            console.log('Create or join room', _room);
            _defaultChannel.emit('create or join', {room:_room, from:_myID});
        }

        // Open up a private communication channel
        initPrivateChannel();

        //console.log(_devices);

        navigator.mediaDevices.getUserMedia(_constraints)
                                .then(handleUserMedia)
                                .catch(handleUserMediaError);

        window.onbeforeunload = function(e){
            _defaultChannel.emit('message',{type: 'bye', from:_myID});
        }
    }
    
    
    /**
     *
     * Send a chat message to all channels.
     *
     * @param message String message to be send
     */
    function sendChatMessage(message) {
        console.log("Sending "+message)
        for (var channel in _sendChannel) {
            if (_sendChannel.hasOwnProperty(channel)) {
                _sendChannel[channel].send(message);
            }
        }
    }
    
    /**
     *
     * Toggle microphone availability.
     *
     */
    function toggleMic() {
        var tracks = _localStream.getTracks();
        for (var i = 0; i < tracks.length; i++) {
            if (tracks[i].kind=="audio") {
                tracks[i].enabled = !tracks[i].enabled; 
            }
        }
    }
    
    
    /**
     *
     * Toggle video availability.
     *
     */
    function toggleVideo() {
        var tracks = _localStream.getTracks();
        for (var i = 0; i < tracks.length; i++) {
            if (tracks[i].kind=="video") {
                tracks[i].enabled = !tracks[i].enabled; 
            }
        }
    }
    
    /**
     *
     * Add callback function to be called when remote video is available.
     *
     * @param callback of type function(stream, participantID)
     */
    function onRemoteVideo(callback) {
        _onRemoteVideoCallback = callback;
    }
    
    /**
     *
     * Add callback function to be called when local video is available.
     *
     * @param callback function of type function(stream)
     */
    function onLocalVideo(callback) {
        _onLocalVideoCallback = callback;
    }
    
    /**
     *
     * Add callback function to be called when chat is available.
     *
     * @parama callback function of type function()
     */
    function onChatReady(callback) {
        _onChatReadyCallback = callback;
    }
    
    /**
     *
     * Add callback function to be called when chat is no more available.
     *
     * @parama callback function of type function()
     */
    function onChatNotReady(callback) {
        _onChatNotReadyCallback = callback;
    }
    
    /**
     *
     * Add callback function to be called when a chat message is available.
     *
     * @parama callback function of type function(message)
     */
    function onChatMessage(callback) {
        _onChatMessageCallback = callback;
    }
    
    /**
     *
     * Add callback function to be called when a a participant left the conference.
     *
     * @parama callback function of type function(participantID)
     */
    function onParticipantHangup(callback) {
        _onParticipantHangupCallback = callback;
    }
    
    ////////////////////////////////////////////////
    // INIT FUNCTIONS
    ////////////////////////////////////////////////
    
    function initDefaultChannel() {
        _defaultChannel = openSignalingChannel('');
        
        _defaultChannel.on('created', function (room){
          console.log('Created room ' + room);
          _isInitiator = true;
        });

        _defaultChannel.on('join', function (room){
            console.log('Another peer made a request to join room ' + room);
        });

        _defaultChannel.on('joined', function (room){
            console.log('This peer has joined room ' + room);
        });
        
        _defaultChannel.on('message', function (message){
            console.log('Client received message:', message);
            if (message.type === 'newparticipant') {
                var partID = message.from;
                
                // Open a new communication channel to the new participant
                _offerChannels[partID] = openSignalingChannel(partID);

                // Wait for answers (to offers) from the new participant
                _offerChannels[partID].on('message', function (msg){
                    if (msg.dest===_myID) {
                        if (msg.type === 'answer') {
                            _opc[msg.from].setRemoteDescription(new RTCSessionDescription(msg.snDescription))
                                            .then(setRemoteDescriptionSuccess)
                                            .catch(setRemoteDescriptionError);
                        } else if (msg.type === 'candidate') {
                            var candidate = new RTCIceCandidate({sdpMLineIndex: msg.label, candidate: msg.candidate});
                            console.log('got ice candidate from '+msg.from);
                            _opc[msg.from].addIceCandidate(candidate, addIceCandidateSuccess, addIceCandidateError);
                        }
                    }
                });

                // Send an offer to the new participant
                createOffer(partID);

            } else if (message.type === 'bye') {
                hangup(message.from);
            }   else if(message.type === 'change') {
                $('#' + message.from).remove();
                if(_myID !== message.from) {
                    createOffer(message.from);
                }

            }
        });
    }
      
    function initPrivateChannel() {
        // Open a private channel (namespace = _myID) to receive offers
        _privateAnswerChannel = openSignalingChannel(_myID);

        // Wait for offers or ice candidates
        _privateAnswerChannel.on('message', function (message){
            if (message.dest===_myID) {
                if(message.type === 'offer') {
                    var to = message.from;
                    createAnswer(message, _privateAnswerChannel, to);
                } else if (message.type === 'candidate') {
                    var candidate = new RTCIceCandidate({sdpMLineIndex: message.label, candidate: message.candidate});
                    _apc[message.from].addIceCandidate(candidate, addIceCandidateSuccess, addIceCandidateError);
                }
            }
        });
    }
    
    function requestTurn(turn_url) {
        var turnExists = false;
        for (var i in _pcConfig.iceServers) {
            if (_pcConfig.iceServers[i].url.substr(0, 5) === 'turn:') {
                turnExists = true;
                _turnReady = true;
                break;
            }
        }

        if (!turnExists) {
            console.log('Getting TURN server from ', turn_url);
            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function(){
                if (xhr.readyState === 4 && xhr.status === 200) {
                    var turnServer = JSON.parse(xhr.responseText);
                     console.log('Got TURN server: ', turnServer);
                    _pcConfig.iceServers.push({
                        'url': 'turn:' + turnServer.username + '@' + turnServer.turn,
                        'credential': turnServer.password
                    });
                    _turnReady = true;
                }
            }
            xhr.open('GET', turn_url, true);
            xhr.send();
        }
    }

    
    ///////////////////////////////////////////
    // UTIL FUNCTIONS
    ///////////////////////////////////////////
    
    /**
     *
     * Call the registered _onRemoteVideoCallback
     *
     */
    function addRemoteVideo(stream, from) {
        // call the callback
        _onRemoteVideoCallback(stream, from);
    }


    /**
     *
     * Generates a random ID.
     *
     * @return a random ID
     */
    function generateID() {
        var s4 = function() {
            return Math.floor(Math.random() * 0x10000).toString(16);
        };
        return s4() + s4() + "-" + s4() + "-" + s4() + "-" + s4() + "-" + s4() + s4() + s4();
    }

    
    ////////////////////////////////////////////////
    // COMMUNICATION FUNCTIONS
    ////////////////////////////////////////////////
    
    /**
     *
     * Connect to the server and open a signal channel using channel as the channel's name.
     *
     * @return the socket
     */
    function openSignalingChannel(channel) {
        var namespace = _host + '/' + channel;
        var sckt = io.connect(namespace);
        return sckt;
    }

    function logout(from) {
        hangup(from)
        window.dispatchEvent(new Event('beforeunload'))
    }

    /**
     *
     * Send an offer to peer with id participantId
     *
     * @param participantId the participant's unique ID we want to send an offer
     */
    function createOffer(participantId) {
        console.log('Creating offer for peer '+participantId);

        _opc[participantId] = new RTCPeerConnection(_pcConfig);
        _opc[participantId].onicecandidate = handleIceCandidateAnswerWrapper(_offerChannels[participantId], participantId);
        _opc[participantId].onaddstream = handleRemoteStreamAdded(participantId);
        _opc[participantId].onremovestream = handleRemoteStreamRemoved; 
        _opc[participantId].addStream(_localStream);

        try {
            // Reliable Data Channels not yet supported in Chrome
            _sendChannel[participantId] = _opc[participantId].createDataChannel("sendDataChannel", {reliable: false});
            _sendChannel[participantId].onmessage = handleMessage;
            //console.log('Created send data channel');
        } catch (e) {
            alert('Failed to create data channel. ' + 'You need Chrome M25 or later with RtpDataChannel enabled');
            //console.log('createDataChannel() failed with exception: ' + e.message);
        }
        _sendChannel[participantId].onopen = handleSendChannelStateChange(participantId);
        _sendChannel[participantId].onclose = handleSendChannelStateChange(participantId);

        var onSuccess = function(participantId) {
            return function(sessionDescription) {
                var channel = _offerChannels[participantId];

                // Set Opus as the preferred codec in SDP if Opus is present.
                sessionDescription.sdp = preferOpus(sessionDescription.sdp);

                _opc[participantId].setLocalDescription(sessionDescription);  
                console.log('Sending offer to channel '+ channel.name);
                channel.emit('message', {snDescription: sessionDescription, from:_myID, type:'offer', dest:participantId});        
            }
        }

        _opc[participantId].createOffer(onSuccess(participantId), handleCreateOfferError);
    }

    function createAnswer(sdp, cnl, to) {
        _apc[to] = new RTCPeerConnection(_pcConfig);
        _apc[to].onicecandidate = handleIceCandidateAnswerWrapper(cnl, to);
        _apc[to].onaddstream = handleRemoteStreamAdded(to);
        _apc[to].onremovestream = handleRemoteStreamRemoved;
        _apc[to].addStream(_localStream);
        _apc[to].setRemoteDescription(new RTCSessionDescription(sdp.snDescription))
                .then(setRemoteDescriptionSuccess)
                .catch(setRemoteDescriptionError);

        _apc[to].ondatachannel = gotReceiveChannel(to);
        
        var onSuccess = function(channel) {
            return function(sessionDescription) {
                // Set Opus as the preferred codec in SDP if Opus is present.
                sessionDescription.sdp = preferOpus(sessionDescription.sdp);

                _apc[to].setLocalDescription(sessionDescription); 
                console.log('Sending answer to channel '+ channel.name);
                channel.emit('message', {snDescription:sessionDescription, from:_myID,  type:'answer', dest:to});
            }
        }

        _apc[to].createAnswer(onSuccess(cnl), handleCreateAnswerError);
    }

    function hangup(from) {
        console.log('Bye received from '+ from);

            if (_opc.hasOwnProperty(from)) {
                _opc[from].close();
                _opc[from] = null;  
            }
            
            if (_apc.hasOwnProperty(from)) {
                _apc[from].close();
                _apc[from] = null;
            }

            _onParticipantHangupCallback(from);
    }


    ////////////////////////////////////////////////
    // HANDLERS
    ////////////////////////////////////////////////
    
    // SUCCESS HANDLERS

    function handleUserMedia(stream) {
        console.log('Adding local stream');
        _onLocalVideoCallback(stream);
        _localStream = stream;
        _defaultChannel.emit('message', {type:'newparticipant', from: _myID});
    }

    function changeHandleUserMedia(stream) {
        _onLocalVideoCallback(stream);
        _localStream = stream;

        _defaultChannel.emit('message', {type:'change', from: _myID});
    }

    function handleRemoteStreamRemoved(event) {
        console.log('Remote stream removed. Event: ', event);
    }

    function handleRemoteStreamAdded(from) {
        return function(event) {
            //console.log('Remote stream added');
            addRemoteVideo(event.stream, from);
            _remoteStream = event.stream;
        }
    }

    function handleIceCandidateAnswerWrapper(channel, to) {
        return function handleIceCandidate(event) {
            console.log('handleIceCandidate event');
            if (event.candidate) {
                channel.emit('message',
                        {type: 'candidate',
                        label: event.candidate.sdpMLineIndex,
                        id: event.candidate.sdpMid,
                        candidate: event.candidate.candidate,
                        from: _myID, 
                        dest:to}
                    );

            } else {
                console.log('End of candidates.');
            }
        }
    }

    function setLocalDescriptionSuccess() {}

    function setRemoteDescriptionSuccess() {}

    function addIceCandidateSuccess() {}

    function gotReceiveChannel(id) {
        return function(event) {
            console.log('Receive Channel Callback');
            _sendChannel[id] = event.channel;
            _sendChannel[id].onmessage = handleMessage;
            _sendChannel[id].onopen = handleReceiveChannelStateChange(id);
            _sendChannel[id].onclose = handleReceiveChannelStateChange(id);
        }
    }
    
    function handleMessage(event) {
        console.log('Received message: ' + event.data);
        _onChatMessageCallback(event.data);
    }
    
    function handleSendChannelStateChange(participantId) {
        return function() {
            var readyState = _sendChannel[participantId].readyState;
            console.log('Send channel state is: ' + readyState);
            
            // check if we have at least one open channel before we set hat ready to false.
            var open = checkIfOpenChannel();
            enableMessageInterface(open);
        }
    }
    
    function handleReceiveChannelStateChange(participantId) {
        return function() {
            var readyState = _sendChannel[participantId].readyState;            
            // check if we have at least one open channel before we set hat ready to false.
            var open = checkIfOpenChannel();
            enableMessageInterface(open);
        }
    }
    
    function checkIfOpenChannel() {
        var open = false;
        for (var channel in _sendChannel) {
            if (_sendChannel.hasOwnProperty(channel)) {
                open = (_sendChannel[channel].readyState == "open");
                if (open == true) {
                    break;
                }
            }
        }
        
        return open;
    }
    
    function enableMessageInterface(shouldEnable) {
        if (shouldEnable) {
            _onChatReadyCallback();
        } else {
            _onChatNotReadyCallback();
        }
    }
    
    // ERROR HANDLERS
    
    function handleCreateOfferError(event){
        console.log('createOffer() error: ', event);
    }

    function handleCreateAnswerError(event){
        console.log('createAnswer() error: ', event);
    }

    function handleUserMediaError(error){
        console.log('getUserMedia error: ', error);
    }

    function setLocalDescriptionError(error) {
        console.log('setLocalDescription error: ', error);
    }

    function setRemoteDescriptionError(error) {
        console.log('setRemoteDescription error: ', error);
    }

    function addIceCandidateError(error) {}
    
    
    ////////////////////////////////////////////////
    // CODEC
    ////////////////////////////////////////////////

    // Set Opus as the default audio codec if it's present.
    function preferOpus(sdp) {
      var sdpLines = sdp.split('\r\n');
      var mLineIndex;
      // Search for m line.
      for (var i = 0; i < sdpLines.length; i++) {
          if (sdpLines[i].search('m=audio') !== -1) {
            mLineIndex = i;
            break;
          }
      }
      if (mLineIndex === null || mLineIndex === undefined) {
        return sdp;
      }

      // If Opus is available, set it as the default in m line.
      for (i = 0; i < sdpLines.length; i++) {
        if (sdpLines[i].search('opus/48000') !== -1) {
          var opusPayload = extractSdp(sdpLines[i], /:(\d+) opus\/48000/i);
          if (opusPayload) {
            sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], opusPayload);
          }
          break;
        }
      }

      // Remove CN in m line and sdp.
      sdpLines = removeCN(sdpLines, mLineIndex);

      sdp = sdpLines.join('\r\n');
      return sdp;
    }

    function extractSdp(sdpLine, pattern) {
      var result = sdpLine.match(pattern);
      return result && result.length === 2 ? result[1] : null;
    }

    // Set the selected codec to the first in m line.
    function setDefaultCodec(mLine, payload) {
      var elements = mLine.split(' ');
      var newLine = [];
      var index = 0;
      for (var i = 0; i < elements.length; i++) {
        if (index === 3) { // Format of media starts from the fourth.
          newLine[index++] = payload; // Put target payload to the first.
        }
        if (elements[i] !== payload) {
          newLine[index++] = elements[i];
        }
      }
      return newLine.join(' ');
    }

    // Strip CN from sdp before CN constraints is ready.
    function removeCN(sdpLines, mLineIndex) {
      var mLineElements = sdpLines[mLineIndex].split(' ');
      // Scan from end for the convenience of removing an item.
      for (var i = sdpLines.length-1; i >= 0; i--) {
        var payload = extractSdp(sdpLines[i], /a=rtpmap:(\d+) CN\/\d+/i);
        if (payload) {
          var cnPos = mLineElements.indexOf(payload);
          if (cnPos !== -1) {
            // Remove CN payload from m line.
            mLineElements.splice(cnPos, 1);
          }
          // Remove CN line in sdp
          sdpLines.splice(i, 1);
        }
      }

      sdpLines[mLineIndex] = mLineElements.join(' ');
      return sdpLines;
    }
    

    ////////////////////////////////////////////////
    // EXPORT PUBLIC FUNCTIONS
    ////////////////////////////////////////////////
    
    exports.joinRoom            =       joinRoom;
    exports.toggleMic           =       toggleMic;
    exports.toggleVideo         =       toggleVideo;
    exports.onLocalVideo        =       onLocalVideo;
    exports.onRemoteVideo       =       onRemoteVideo;
    exports.onChatReady         =       onChatReady;
    exports.onChatNotReady      =       onChatNotReady;
    exports.onChatMessage       =       onChatMessage;
    exports.sendChatMessage     =       sendChatMessage;
    exports.onParticipantHangup =       onParticipantHangup;
    exports.changeHandleUserMedia =       changeHandleUserMedia;
    exports.logout              =       logout;
    exports.opc                 =       _opc;
    exports.apc                 =       _apc;
    return exports;
    
};


我从这里提供我的链接,而且效果很好。你能举例说明我如何做到这一点吗?
$( document ).ready(function() {
    /////////////////////////////////
    // CREATE MEETING
    /////////////////////////////////
    meeting = new Meeting(host);
    
    meeting.onLocalVideo(function(stream) {
            //alert(stream.getVideoTracks().length);
            document.querySelector('#localVideo').srcObject = stream;

            
            $("#micMenu").on("click",function callback(e) {
                $(this).toggleText("mic_off", "mic");
                meeting.toggleMic();
            });
            
            $("#videoMenu").on("click",function callback(e) {
                $(this).toggleText("videocam_off", "videocam");
                meeting.toggleVideo();
            });

            $("#speakerMenu").on("click", function callback(e) {
                $(this).toggleText("volume_off", "volume_up");
                $("#localVideo").prop('muted', true);
            });

            $('#chatMenu').on('click', function callback(e) {
                $(this).toggleText('speaker_notes_off', 'chat');
            });

            $('#close').on('click', function callback(e) {
                meeting.logout($('.videoWrap').eq(1).attr('id'));
            });


            $(document).on('change', '#videoInput', function callback(e) {

                var mediaParams = {
                    video: {mandatory: {sourceId: $(this).val()}}
                };

                navigator.mediaDevices.getUserMedia(mediaParams)
                    .then(function(stream){
                    meeting.handleUserMedia(stream);

                })
                .catch(function(e) { });
            });

        }
    );
    
    meeting.onRemoteVideo(function(stream, participantID) {
            addRemoteVideo(stream, participantID);  
        }
    );
    
    meeting.onParticipantHangup(function(participantID) {
            // Someone just left the meeting. Remove the participants video
            removeRemoteVideo(participantID);
        }
    );
    
    meeting.onChatReady(function() {
            console.log("Chat is ready");
        }
    );

    meeting.onChatNotReady(function() {
            console.log("Chat is not ready");
        }
    );
    
    var room = window.location.pathname.match(/([^\/]*)\/*$/)[1];
    meeting.joinRoom(room);

}); // end of document.ready

显然我正在更改本地视频。但我无法为其他用户更改它。

最佳答案

在我的实践中,本地和远程视频都可以查看,我的代码如下
包.json:

{
  "name": "peer to peer",
  "version": "1.0.0",
  "description": "point to point by webrtc & websocket",
  "main": "index.js",
  "scripts": {
    "dev": "node index.js"
  },
  "author": "webrtc",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1",
    "express-ws": "^4.0.0"
  }
}
索引.js:
const app = require('express')();
const wsInstance = require('express-ws')(app);

app.ws('/', ws => {
    ws.on('message', data => {
        // post cast
        wsInstance.getWss().clients.forEach(server => {
            if (server !== ws) {
                server.send(data);
            }
        });
    });
});

app.get('/', (req, res) => {
    res.sendFile('./client/index.html', { root: __dirname });
});

app.get('/p2p', (req, res) => {
    res.sendFile('./client/p2p.html', { root: __dirname });
});

app.listen(8091);
索引.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>p2p webrtc</title>
    <style>
    .container {
        width: 250px;
        margin: 100px auto;
        padding: 10px 30px;
        border-radius: 4px;
    border: 1px solid #ebeef5;
    box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
    color: #303133;
    }
    </style>
</head>
<body>
    <div class="container">
        <p>seheme:</p>
        <ul>
            <li>open<a href="/p2p?type=answer" target="_blank">answer</a>;</li>
            <li>open<a href="/p2p?type=offer" target="_blank">offer</a>;</li>
            <li> comfirm connected ws ;</li>
            <li> offer send 'start' button;</li>
        </ul>
    </div>
</body>
</html>
p2p.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title></title>
    <style>
        * {
            padding: 0;
            margin: 0;
            box-sizing: border-box;
        }
        .container {
            width: 100%;
            display: flex;
            display: -webkit-flex;
            justify-content: space-around;
            padding-top: 20px;
        }
        .video-box {
            position: relative;
            width: 800px;
            height: 400px;
        }
        #remote-video {
            width: 100%;
            height: 100%;
            display: block;
            object-fit: cover;
            border: 1px solid #eee;
            background-color: #F2F6FC;
        }
        #local-video {
            position: absolute;
            right: 0;
            bottom: 0;
            width: 240px;
            height: 120px;
            object-fit: cover;
            border: 1px solid #eee;
            background-color: #EBEEF5;
        }
        .start-button {
            position: absolute;
            left: 50%;
            top: 50%;
            width: 100px;
            display: none;
            line-height: 40px;
            outline: none;
            color: #fff;
            background-color: #409eff;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            transform: translate(-50%, -50%);
        }
        .logger {
            width: 40%;
            padding: 14px;
            line-height: 1.5;
            color: #4fbf40;
            border-radius: 6px;
            background-color: #272727;
        }
        .logger .error {
            color: #DD4A68;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="video-box">
            <video id="remote-video"></video>
            <video id="local-video" muted></video>
            <button class="start-button" onclick="startLive()">start</button>
        </div>
        <div class="logger"></div>
    </div>
    <script>
        const message = {
            el: document.querySelector('.logger'),
            log (msg) {
                this.el.innerHTML += `<span>${new Date().toLocaleTimeString()}:${msg}</span><br/>`;
            },
            error (msg) {
                this.el.innerHTML += `<span class="error">${new Date().toLocaleTimeString()}:${msg}</span><br/>`;
            }
        };
        
        const target = location.search.slice(6);
        const localVideo = document.querySelector('#local-video');
        const remoteVideo = document.querySelector('#remote-video');
        const button = document.querySelector('.start-button');

        localVideo.onloadeddata = () => {
            message.log('play local video');
            localVideo.play();
        }
        remoteVideo.onloadeddata = () => {
            message.log('play remote video');
            remoteVideo.play();
        }

        document.title = target === 'offer' ? 'offer' : 'answer';

        message.log('Multi-hole channel (WebSocket) is being created...');
        const socket = new WebSocket('ws://localhost:8091');
        socket.onopen = () => {
            message.log('The signaling channel is successfully created!');
            target === 'offer' && (button.style.display = 'block');
        }
        socket.onerror = () => message.error('Failed to create signaling channel!');
        socket.onmessage = e => {
            const { type, sdp, iceCandidate } = JSON.parse(e.data)
            if (type === 'answer') {
                peer.setRemoteDescription(new RTCSessionDescription({ type, sdp }));
            } else if (type === 'answer_ice') {
                peer.addIceCandidate(iceCandidate);
            } else if (type === 'offer') {
                startLive(new RTCSessionDescription({ type, sdp }));
            } else if (type === 'offer_ice') {
                peer.addIceCandidate(iceCandidate);
            }
        };

        const PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
        !PeerConnection && message.error('The browser does not support WebRTC!');
        const peer = new PeerConnection();

        peer.ontrack = e => {
            if (e && e.streams) {
                message.log('Receive the other party's audio/video stream data...');
                remoteVideo.srcObject = e.streams[0];
            }
        };

        peer.onicecandidate = e => {
            if (e.candidate) {
                message.log('Collect and send candidates');
                socket.send(JSON.stringify({
                    type: `${target}_ice`,
                    iceCandidate: e.candidate
                }));
            } else {
                message.log('The candidate collection is complete!');
            }
        };

        async function startLive (offerSdp) {
            target === 'offer' && (button.style.display = 'none');
            let stream;
            try {
                message.log('Try to call the local camera/microphone');
                stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
                message.log('The camera/microphone is successfully acquired!');
                localVideo.srcObject = stream;
            } catch {
                message.error('Camera/microphone acquisition failed!');
                return;
            }

            message.log(`------ WebRTC ${target === 'offer' ? 'offer' : 'answer'}seheme ------`);
            message.log('Add a media track to the track set');
            stream.getTracks().forEach(track => {
                peer.addTrack(track, stream);
            });

            if (!offerSdp) {
                message.log('Create a local SDP');
                const offer = await peer.createOffer();
                await peer.setLocalDescription(offer);
                
                message.log(`Transmission initiator local SDP`);
                socket.send(JSON.stringify(offer));
            } else {
                message.log('SDP received from the sender');
                await peer.setRemoteDescription(offerSdp);

                message.log('Create receiver (answer) SDP');
                const answer = await peer.createAnswer();
                message.log(`Transmission receiver (response) SDP`);
                socket.send(JSON.stringify(answer));
                await peer.setLocalDescription(answer);
            }
        }
    </script>
</body>
</html>
使用代码:
npm install                 //npm install dependency

npm run dev                 //open localhost:8091
希望能帮到你!

关于javascript - WEBRTC 带 socket 的开关量输入设备,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68906519/

相关文章:

javascript - 如何在预先输入的 Angular 输入搜索框中分离两组数据?

javascript - Backbone.js 路由器和 Internet Explorer 重定向问题

javascript - 使用下划线修改另一个对象内部的对象内部的数组?

在虚拟机和主 Web 服务器中运行的 Node.js 实例

javascript - Node (Express) 上的套接字 IO 的 cURL 请求

javascript - 如何将多项选择中选定的元素插入到输入中?

flutter - 服务器发送请求后运行功能的最佳方法是什么?

javascript - 是否有可能捕捉到 webRTC 流视频分辨率的变化?

google-chrome - WebRTC:允许 Chrome 通过 Chrome 扩展访问麦克风

canvas - 将网络摄像头流作为 WebGL 纹理进行操作?