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?
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.