Search code examples
node.jssslsocket.iowebrtc

WebRTC not connecting to my local server even in browser locally or from my Mobile device


I have this server running in nodejs:

    const express = require('express');
    const socketio = require('socket.io');
    const http = require('http');

    // Create server
    const app = express();
    const server = http.Server(app);

    // Map HTML and Javascript files as static
    app.use(express.static('public'));

    // Init Socket IO Server
    const io = socketio(server);

    // Array to map all clients connected in socket
    let connectedUsers = [];

    // Called whend a client start a socket connection
    io.on('connection', (socket) => {
    // It's necessary to socket knows all clients connected
    connectedUsers.push(socket.id);

    // Emit to myself the other users connected array to start a connection with each them
    const otherUsers = connectedUsers.filter(socketId => socketId !== socket.id);
    socket.emit('other-users', otherUsers);
    console.log(socket.id);

    // Send Offer To Start Connection
    socket.on('offer', (socketId, description) => {
        socket.to(socketId).emit('offer', socket.id, description);
    });

    // Send Answer From Offer Request
    socket.on('answer', (socketId, description) => {
        socket.to(socketId).emit('answer', description);
    });

    // Send Signals to Establish the Communication Channel
    socket.on('candidate', (socketId, candidate) => {
        socket.to(socketId).emit('candidate', candidate);
    });

    // Remove client when socket is disconnected
    socket.on('disconnect', () => {
        connectedUsers = connectedUsers.filter(socketId => socketId !== socket.id);
    });
    });

    // Return Index HTML when access root route
    app.get('/', (req, res) => {
    res.sendFile(path.resolve(__dirname, 'public', 'index.html'));
    });

    // Start server in port 3000 or the port passed at "PORT" env variable
    server.listen(process.env.PORT || 3000,
    () => console.log('Server Listen On: *:', process.env.PORT || 3000));

This is the site.js:

        console.log('Main JS!');

    // Map All HTML Elements

    var messagesEl = document.querySelector('.messages');
    var messageInput = document.getElementById('message-input');
    var remoteVideo;
    $(document).ready(function () {


        var videoGrid = document.getElementById('video-grid');
        var localVideo = document.getElementById('local-video');
        var sendButton = document.getElementById('message-button');
        remoteVideo = document.getElementById('remote-video');

        // Open Camera To Capture Audio and Video
        navigator.mediaDevices.getUserMedia({ video: true, audio: true })
            .then(stream => {
                // Show My Video
                videoGrid.style.display = 'grid';
                localVideo.srcObject = stream;

                // Start a Peer Connection to Transmit Stream
                initConnection(stream);
            })
            .catch(error => console.log(error));

        // Map the 'message-button' click
        sendButton.addEventListener('click', () => {
            // GET message from input
            const message = messageInput.value;
            // Clean input
            messageInput.value = '';
            // Log Message Like Sended
            logMessage(`Send: ${message}`);

            // GET the channel (can be local or remote)
            const channel = localChannel || remoteChannel;
            // Send message. The other client will receive this message in 'onmessage' function from channel
            channel.send(message);
        });
    })


    const logMessage = (message) => {
      const newMessage = document.createElement('div');
      newMessage.innerText = message;
      messagesEl.appendChild(newMessage);
    };

    var pcConfig = {
        'iceServers': [
            {
                'urls': 'stun:stun.l.google.com:19302'
            }
        ]
    };
    const initConnection = (stream) => {
      const socket = io('http://192.168.1.217:3000',{transports: ['websocket']});
      let localConnection;
      let remoteConnection;
      let localChannel;
      let remoteChannel;

      // Start a RTCPeerConnection to each client
      socket.on('other-users', (otherUsers) => {
        // Ignore when not exists other users connected
        if (!otherUsers || !otherUsers.length) return;

        const socketId = otherUsers[0];

        // Ininit peer connection
          localConnection = new RTCPeerConnection(pcConfig);

        // Add all tracks from stream to peer connection
        stream.getTracks().forEach(track => localConnection.addTrack(track, stream));

        // Send Candidtates to establish a channel communication to send stream and data
        localConnection.onicecandidate = ({ candidate }) => {
          candidate && socket.emit('candidate', socketId, candidate);
        };

        // Receive stream from remote client and add to remote video area
        localConnection.ontrack = ({ streams: [ stream ] }) => {
          remoteVideo.srcObject = stream;
        };

        // Start the channel to chat
        localChannel = localConnection.createDataChannel('chat_channel');

        // Function Called When Receive Message in Channel
        localChannel.onmessage = (event) => logMessage(`Receive: ${event.data}`);
        // Function Called When Channel is Opened
        localChannel.onopen = (event) => logMessage(`Channel Changed: ${event.type}`);
        // Function Called When Channel is Closed
        localChannel.onclose = (event) => logMessage(`Channel Changed: ${event.type}`);

        // Create Offer, Set Local Description and Send Offer to other users connected
        localConnection
          .createOffer()
          .then(offer => localConnection.setLocalDescription(offer))
          .then(() => {
            socket.emit('offer', socketId, localConnection.localDescription);
          });
      });

      // Receive Offer From Other Client
      socket.on('offer', (socketId, description) => {
        // Ininit peer connection
          remoteConnection = new RTCPeerConnection(pcConfig);

        // Add all tracks from stream to peer connection
        stream.getTracks().forEach(track => remoteConnection.addTrack(track, stream));

        // Send Candidtates to establish a channel communication to send stream and data
        remoteConnection.onicecandidate = ({ candidate }) => {
          candidate && socket.emit('candidate', socketId, candidate);
        };

        // Receive stream from remote client and add to remote video area
        remoteConnection.ontrack = ({ streams: [ stream ] }) => {
          remoteVideo.srcObject = stream;
        };

        // Chanel Received
        remoteConnection.ondatachannel = ({ channel }) => {
          // Store Channel
          remoteChannel = channel;

          // Function Called When Receive Message in Channel
          remoteChannel.onmessage = (event) => logMessage(`Receive: ${event.data}`);
          // Function Called When Channel is Opened
          remoteChannel.onopen = (event) => logMessage(`Channel Changed: ${event.type}`);
          // Function Called When Channel is Closed
          remoteChannel.onclose = (event) => logMessage(`Channel Changed: ${event.type}`);
        }

        // Set Local And Remote description and create answer
        remoteConnection
          .setRemoteDescription(description)
          .then(() => remoteConnection.createAnswer())
          .then(answer => remoteConnection.setLocalDescription(answer))
          .then(() => {
            socket.emit('answer', socketId, remoteConnection.localDescription);
          });
      });

      // Receive Answer to establish peer connection
      socket.on('answer', (description) => {
        localConnection.setRemoteDescription(description);
      });

      // Receive candidates and add to peer connection
      socket.on('candidate', (candidate) => {
        // GET Local or Remote Connection
        const conn = localConnection || remoteConnection;
        conn.addIceCandidate(new RTCIceCandidate(candidate));
      });


    }

And the frontend:

    @page
    @model IndexModel
    <h1>Hello!</h1>

    <!-- My Video and Remote Video from connection -->
    <div id="video-grid">
        <video playsinline autoplay muted id="local-video"></video>
        <video playsinline autoplay id="remote-video"></video>
    </div>

    <!-- Input to send messages -->
    <div>
        <span style="font-weight: bold">Message: </span>
        <input type="text" id="message-input" title="Message to Send!">
        <button id="message-button">Send</button>
    </div>

    <!-- Area to Print Images -->
    <div class="messages"></div>

What i have done is that i have setup the frontend with the site.js and index.html inside IIS windows 10 locally so that i can test and access the streaming on my browser and mobile device.

Successfully i have managed to get the local stream working but the remote stream is not working for some reason. I added a turn server to fix the issue. But i figured out that the server running on my local laptop / pc is not picking up the connections to the mobile device or even the browser.

When i change this to "localhost", the browser can connect and the local and remote video work fine:

 const socket = io('http://localhost:3000',{transports: ['websocket']});

But when i change the ip address to the local machine the browsers cannot seem to connect to the server and in turn the mobile device cannot connect to the server at all.

I have added https, ssl to the website in iis, when i did not do this the mobile device local video was not working but once i added the ssl to the website the local video started working fine.

So my issue is that the remote stream is not displaying from the mobile and browser device.

Has this anything to do with ssl?


Solution

  • Yes, that's because to access camera / mic in browser the page should have secure context.

    From Mozilla docs:

    getUserMedia() is a powerful feature that can only be used in secure contexts; in insecure contexts, navigator.mediaDevices is undefined, preventing access to getUserMedia(). A secure context is, in short, a page loaded using HTTPS or the file:/// URL scheme, or a page loaded from localhost.

    For testing purposes this limitation can be bypassed by using https proxy like ngrok or by proxying localhost requests to your server using reverse proxy like nginx. Also, chromium-based browsers allow you to pass --unsafely-treat-insecure-origin-as-secure="http://youraddress" which should grant "youraddress" http pages secure context.

    For production, ssl is needed.