WEBRTC video chat app not working in different networks - node.js

My video chat app works properly in the same network. It generates and connects IceCandidate using stun as well. But for some reason the peer videos don't play in different networks. I am having trouble debugging the issue.
I haven't used any turn server but I doubt that is the problem as the peers from different networks already joins using stun, only videos don't play
var socket= io('/')
var divVideoChatLobby = document.getElementById("video-chat-lobby")
var divVideoChat = document.getElementById("video-chat-room")
var joinButton = document.getElementById("join")
var userVideo = document.getElementById("user-video")
var peerVideo = document.getElementById("peer-video")
var roomInput = document.getElementById("roomName")
var roomname= roomInput.value
var rtcPeerConnection
var userStream
const iceServers = {
iceServers:[
{urls: "stun:stun.services.mozilla.com"},
{urls: "stun:stun.l.google.com:19302"},
{urls: "stun:stun1.l.google.com:19302"},
{urls: "stun:stun3.l.google.com:19302"},
{urls: "stun:stun4.l.google.com:19302"},
{urls: "stun:stun.ekiga.net"},
]
}
userVideo.muted= "muted"
// var roomDiv = document.getElementById("room-div")
// roomDiv.style="display:none"
var creator=false
joinButton.addEventListener('click', function () {
console.log('Room Name:', roomInput.value)
if (roomInput.value == "") {
alert("Please enter a room name")
}
else {
socket.emit("join",roomInput.value)
}
})
socket.on("created",function(){
creator=true
navigator.getUserMedia(
{
audio: true,
video:true
// { width: 1280, height: 720 }
},
function(stream) {
divVideoChatLobby.style="display:none"
// roomInput.value
// roomDiv.style="visibility: visible"
// console.log('room name',roomInput)
console.log("got user media stream")
userStream= stream
userVideo.srcObject = stream
userVideo.onloadedmetadata = function(e){
userVideo.play()}
},
function() {
alert("Couldn't acces User Media")
}
)
})
socket.on("joined",function(){
creator=false
navigator.getUserMedia(
{
audio: true,
video:true
// { width: 1280, height: 720 }
},
function(stream) {
divVideoChatLobby.style="display:none"
// roomInput.value
// roomDiv.style="visibility: visible"
// console.log('room name',roomInput)
userStream=stream
userVideo.srcObject = stream
userVideo.onloadedmetadata = function(e){
userVideo.play()}
socket.emit("ready",roomInput.value)
console.log("haha to you")
},
function() {
alert("Couldn't acces User Media")
}
)
})
socket.on("full",function(){
alert("The room is full. You cannot join now")
})
socket.on("ready",function(){
console.log("haha to you 3")
if(creator){
rtcPeerConnection= new RTCPeerConnection(iceServers)
rtcPeerConnection.onicecandidate= OnIceCandidateFunction
rtcPeerConnection.ontrack = OnTrackFunction
rtcPeerConnection.addTrack(userStream.getTracks()[0],userStream)
rtcPeerConnection.addTrack(userStream.getTracks()[1],userStream)
rtcPeerConnection.createOffer(function(offer){
rtcPeerConnection.setLocalDescription(offer)
socket.emit("offer", offer, roomInput.value)
},function(error){
console.log(error)
})
}
})
socket.on("candidate", function (candidate) {
var icecandidate = new RTCIceCandidate(
{candidate: candidate.candidate,
sdpMID:candidate.sdpMID,
sdpMLineIndex:candidate.sdpMLineIndex,})
console.log("INSIDE CANDIDATEEEEEEEEEEEEEEE")
rtcPeerConnection.addIceCandidate(icecandidate)
});
// socket.on("candidate",function(candidate){
// rtcPeerConnection.addIceCandidate(candidate)
// })
socket.on("offer",function(offer){
if(!creator){
rtcPeerConnection= new RTCPeerConnection(iceServers)
rtcPeerConnection.onicecandidate= OnIceCandidateFunction
rtcPeerConnection.ontrack = OnTrackFunction
rtcPeerConnection.addTrack(userStream.getTracks()[0],userStream)
rtcPeerConnection.addTrack(userStream.getTracks()[1],userStream)
rtcPeerConnection.setRemoteDescription(offer)
rtcPeerConnection.createAnswer(function(answer){
rtcPeerConnection.setLocalDescription(answer)
socket.emit("answer", answer, roomInput.value)
},function(error){
console.log(error)
})
}
})
socket.on("answer",function(answer){
rtcPeerConnection.setRemoteDescription(answer)
})
function OnIceCandidateFunction(event){
console.log('EVENT CANDIDATE',event.candidate)
if(event.candidate){
// console.log('EVENT CANDIDATE',event.candidate)
socket.emit("candidate",event.candidate,roomInput.value)
}
}
function OnTrackFunction(event){
peerVideo.srcObject = event.streams[0]
console.log("EVENT STREAM 0", event.streams[0])
peerVideo.onloadedmetadata = function(e){
console.log("IN THE ONTRACKFUNC")
peerVideo.play()
}
}

WebRTC can connect in a few ways, and falls down progressively to lower preference choices as it fails at its first choices.
naive direct p2p with own ip
if that fails, use a STUN server to determine what ip (e.g., router) we're behind
if that fails, true p2p is not possible, use a TURN server instead to relay traffic.
WebRTC tries everything it can do to make a p2p connection, but there are times that it will fail. The turn server acts as a last resort so that the peers can both connect through the turn server. Obviously this is not a p2p connection, so there will be extra latency, and you will have to make sure that your turn server has enough bandwidth to cover all of the connections you expect.
Usually about 20% of connections require a TURN server. It may work fine for you on your network, but try accessing your webRTC service from a different network which has firewall and different network configurations (which will usually require TURN), and you'll see that not all connections are equal when it comes to p2p. so basically this is what is happening to you that different peer are in different network so you are not getting video of peers.
Basically what happens is that since peers have different networks so It become harder to do correct Ice candidate exchange so no media transmission happens that was decided during sdp negotiation, so we need a a public common server(TURN server) which acts as a peer to other peers for smooth ice-candidate exchange so that media transmission can happens (where this TURN server act as a relay for the media transmission between peers) I am sure in your case along with video, audio too is not working for this reasons.

Related

Twitch bot with node.js

Im new to node and tried to make a simple twitch chat bot with node.js following a youtube tutorial but
everything works untill i try to use "!" command... I use the command and the bot simply doesnt respond to the command.
Here is what i have so far:
const tmi = require('tmi.js'),
{channel , username , password } = require('./settings.json');
const options = {
options: {debug: true},
connection: {
reconnct: true,
secure: true
},
identity : {
username,
password
},
channel: [channel]
};
const client = new tmi.Client(options);
client.connect().catch(console.error);
client.on('connected', () => {
client.say(channel, `${username} joined the chat`)
})
client.on('message', (channel, user, message ,self) => {
if(self) return;
if(message == '!ping') {
client.say(channel, `#${user.username}, pong`);
}
});
I cant figure it out i just want it to respond
I only played a bit with twitch bots a while ago, but the problem might be the part where you return on self:
if(self) return;
This means that if a message comes from the bot itself, it will be ignored and the function will return (and therefore stop). Meaning that if you linked the bot to the same account from which you are sending messages, those messages are ignored.
Also:
you mispelled reconnect with reconnct in the connection options
in JS I would recommend to use === instead of == because the latter attempts to convert and compare operands that are of different types (you can learn more here)
Edit: what also helps in general is to use console.log() to debug and find out where the problem is

How to listen to a socket.io event on a route in order to redirect to another route in express?

I am trying to replicate the web whatsapp QRCode authentication for my Final Year Project which is an Online Medical Management System. In this system long story short, the doctor should be able to view the patients prescriptions only if the patient allows him and that permission should be given when he scans the QRcode generated by the doctors profile from his app which sends an authStatus on a socket event which will enable the server to redirect to the patients record route.
The server socket setup is given as follows:
router.get('/patientinfoqrcode',redirectRoutes.redirectLoginDoctor,function(req,res){
let usernameQR = req.query.usernameQR
let message='';
io.on("connection",function(socket){
console.log("The user has been connected")
io.on("disconnect",function(){
console.log("The user has been disconnected")
})
socket.on("Auth",function(data){
if(data.authStatus=='authorized'){
res.redirect(`/dashboard?usernamePatient=${usernameQR}`)
}
});
})
res.render('doctor/patientinfoqrcode',{usernameQR})
});
The nativescript app setup is given below:
const socketio = SocketIO.connect('https://secret-falls-76814.herokuapp.com/patientinfoqrcode'
, options);
export function scanQR(args) {
barcodescanner.scan({
formats: "QR_CODE", // Pass in of you want to restrict scanning to certain type
message: "Use the volume buttons for extra light", // Android only, default is 'Place a barcode inside the viewfinder rectangle to scan it.'
showFlipCameraButton: true, // default false
preferFrontCamera: false, // default false
showTorchButton: true, // default false
beepOnScan: true, // Play or Suppress beep on scan (default true)
torchOn: false, // launch with the flashlight on (default false)
closeCallback: function () { console.log("Scanner closed"); }, // invoked when the scanner was closed (success or abort)
resultDisplayDuration: 500, // Android only, default 1500 (ms), set to 0 to disable echoing the scanned text
orientation: "landscape", // Android only, optionally lock the orientation to either "portrait" or "landscape"
openSettingsIfPermissionWasPreviouslyDenied: true // On iOS you can send the user to the settings app if access was previously denied
}).then(
function(result) {
console.log("Scan format: " + result.format);
console.log("Scan text: " + result.text);
const button = args.object;
button.text = result.text;
socketio.emit('Auth',{
authStatus:'authorized'
});
},
function(error) {
console.log("No scan: " + error);
}
);
}
And it isnt working like its supposed to. The only problem I think there is that the socket.io listening to the event is inside the /patientinfoqrcode route and that route performs all the functions inside it the moment it loads. Then even if some thing happens within the body of the router.get() nothing will happen because it has already rendered the document. The server whole project is uploaded on heroku

NodeJS + Cluster + Socket.io + Redis - iOS not leaving rooms after disconnect

I'm writing a nodejs/socket.io application that uses cluster and Redis in AWS Elasticache as the RedisStore backend. The app works heavily around rooms, and i'm having a really hard time understanding why only Mobile Safari (iPad mini retina iOS7 ) cannot leave rooms it subscribes to after it emits a request to do so. Even closing the connection from the client side leaves the socket humming along on the server, and the subscription to the room intact, but other browsers can exit without a problem.
NodeJS v0.10.25
Socket.io v0.9.16
Redis v2.8.6 (AWS Elasticache)
Ubuntu 14.04 LTS (EC2 behind loadbalancer in TCP mode)
Now, in my code, i've been using the io.sockets.manager.roomClients object to iterate through and see what rooms are actually in service. This is because io.sockets.clients() reports completely inaccurate data once connections are opened and closed for a period of time.
My code is really too long to put here, and also fairly private, but here's essentially what i've got:
Server:
if (cluster.isMaster) {
function heartbeat(){
// io.sockets.clients() doesn't splice dropped connections off the array
// so we have to use io.sockets.manager.roomClients to see active rooms
for( var i in io.sockets.manager.roomClients ) {
console.log("Client:", i, io.sockets.manager.roomClients[i] );
}
setTimeout(heartbeat,5000);
}
heartbeat();
} else {
io.sockets.on('connection', function (socket) {
socket.on('subscribe', function (room) {
console.log("Starting:",room);
socket.join(room);
});
socket.on('unsubscribe', function (room) {
console.log("Leaving:",room);
socket.leave(room);
});
socket.on('disconnect', function () {
console.log("Close Shop");
});
});
}
The Client:
socket.emit('subscribe', 'some-room');
Server Log
Starting: some-room
And then i get a Client log with each timeout tick:
Client: lhZsH1oL2vV7BML9QkSW { '': true, '/some-room': true }
Client: lhZsH1oL2vV7BML9QkSW { '': true, '/some-room': true }
Client: lhZsH1oL2vV7BML9QkSW { '': true, '/some-room': true }
Client: lhZsH1oL2vV7BML9QkSW { '': true, '/some-room': true }
Client: lhZsH1oL2vV7BML9QkSW { '': true, '/some-room': true }
Now, the issue is here. If i unsubscribe or disconnect from a desktop browser:
socket.emit('unsubscribe', 'some-room');
Leaving: some-room
Or
socket.disconnect();
Close Shop
The ticks look like this:
Client: lhZsH1oL2vV7BML9QkSW { '': true }
Which i expect, because as we know, socket.io sucks at connection cleanup, but at least the room subscriptions are gone. However, on my tablet, after unsubscribe or disconnect, the room subscription remains in the io.sockets.manager.roomClients object:
Client: lhZsH1oL2vV7BML9QkSW { '': true, '/some-room': true }
I'm fairly new at socket programming, so I'm sure i'm missing something obvious, but has anyone had similar issues with Mobile websockets?
So I discovered that by using my own methods of reference counting, i can bypass needing socket.io to be accurate. Since i'm already using Redis to back my sockets through pubsub, i just create another client connection to redis and setex the socket.id's on an expiration. When my sockets unsubscribe or disconnect, i delete all the keys in Redis associated with the socket.id. This way, i have an instant snapshot of whos on, and if the key for some reason didn't get deleted on disconnect/unsub, it will expire based on what i set for the setex call.
I'll try and remember to come back in two days to mark my own answer as correct.

node.js only one client is receiving the messages

As far as I know, unless told otherwise, if the server sends a message, all clients should receive it. But in my case only one client is getting the messages.
client :
(function () {
window.Network = {
socket : null,
initialize : function(socketURL) {
this.socket = io.connect(socketURL);
this.socket.on('new move', this.add);
},
add : function(data) {
var msg = $('<div class="msg"></div>')
.append('<span class="text">' + data.x+'/'+data.y + '</span>');
$('#messages')
.append(msg)
.animate({scrollTop: $('#messages').prop('scrollHeight')}, 0);
},
send : function(data) {
this.socket.emit("move",data);
return false;
}
};
}());
server:
io.sockets.on('connection', function (socket) {
socket.on('move', function (data) {
console.log(data);
socket.emit("new move",data);
});
});
If I open several clients and use "send" function, only the client that sent it receives the emit from the server.
Any idea what I am doing wrong?
Thanks
To emit globally on the server side use this :
io.sockets.emit('new move', 'data1');
To emit to the current socket :
socket.emit('function', 'data1', 'data2');
To broadcast to everyone but the client :
socket.broadcast.emit('function', 'data1', 'data2');
Source
When you are using socket, you are talking directly to the connected client.
You can use rooms however, un this snippet i add the socket to room1
// Add the player socket, to the room.
socket.join('room1');
Then you can emit to all client in a room by
io.sockets.in('room1').emit('startGame', true);
The code is working just as it is supposed to. You are doing socket.emit("new move",data);, which will emit a new move event back to that specific socket, which in your case is the same socket that connected.
If you also want it to be sent to other clients that are connected, then you need to explicitly emit the event to other clients.
socket.emit("new move",data);
socket.broadcast.emit("new move",data);

How to disable Multiplexing with Socket.io

I am using Socket.io to stream live tweets to my users using Twitter's Streaming API (my implementation is more or less based on this tutorial).
The problem is that every time a connection event is fired by Socket.io the newly connected client causes every other client connected to the server to cease updating. While it would take too long to go through all the hacks that I tried, I will say that I played with it enough that I believe the problem is caused by Socket.io's multiplexing of the connections from multiple clients (enabled by default) as a performance boost to allow multiple clients or connections to share the same underlying socket. In short, I believe this to be the case because I don't think it would be possible for new connections to affect older connections in this manner if not for the connection multiplexing. In other words, if a new, independent connection with its own underlying (TCP) socket were created every time a client connected it would be impossible for this to occur since one connection would know nothing about the other and therefore couldn't affect any other client's state as is currently happening. This also leads me to believe that simply disabling the multiplexing functionality would be the simplest way to get around this problem since I am not concerned about scaling because Node.js already handles all the concurrency I'm likely to need to handle very adequately.
I have gone through Socket.io's documentation but could not see where the ability to "demultiplex" the connections is exposed via the API, so if anyone knows how to do this I'd create appreciate your response.
My code below is pretty standard and simple. But just to be clear, the issue is that whenever a new client connects to Socket.io every other client stops receiving new tweets and updates are no longer pushed to the older client unless I refresh the browser in which case the newly refreshed client will begin to update and receive fresh tweets again, but the other still connected clients will then stop updating.
Server-side Code:
// Code also uses ntwitter (https://github.com/AvianFlu/ntwitter) as an abstraction over Twitter's Streaming API
io.sockets.on('connection', function (socket) {
tweet.stream('statuses/filter', { track : 'new orleans' }, function (stream) {
stream.on('data', function (data) {
// The following lines simply pre-process data sent from Twitter so junk isn't
// unnecessarily sent to the client.
if (data.user) {
tweets = {
text : data.text,
image : data.user.profile_image_url,
user : data.user.name
};
var t = JSON.stringify(tweets);
console.log(t);
socket.send(t);
}
});
});
});
Client-Side Code
// Notice the option that I passed in as the second argument. This supposedly forces every
// new client to create a new connection with the server but it either doesn't work or I'm
// implementing it incorrectly. It is the very last configuration option listed in the
// documentation linked to above.
var socket = io.connect('http://' + location.host, {'force new connection' : true });
socket.on('message', function (tweet) {
var t = JSON.parse(tweet);
if (t.image) {
$('.hero-unit').prepend('<div class="media"><a class="pull-left" href="#"><img class="media-object" alt="64x64" style="width: 64px; height: 64px;" src="' + t.image + '"></a><div class="media-body"><h4 class="media-heading">' + t.user + '</h4>' + t.text + '</div></div>');
}
});
If I am thinking of this incorrectly or if there's something wrong with my code I am definitely open to any suggestions. I'd also be happy to reply with any additional details.
I would try something like this
Serverside:
io.sockets.on('connection', function (socket) {
//Other Connectiony goodness here.
});
});
tweet.stream('statuses/filter', { track : 'new orleans' }, function (stream) {
stream.on('data', function (data) {
// The following lines simply pre-process data sent from Twitter so junk isn't
// unnecessarily sent to the client.
if (data.user) {
tweets = {
text : data.text,
image : data.user.profile_image_url,
user : data.user.name
};
var t = JSON.stringify(tweets);
console.log(t);
io.sockets.emit("tweet", t);
}
});
Client-side:
var socket = io.connect('http://' + location.host, {'force new connection' : true });
socket.on('tweet', function (tweet) {
var t = JSON.parse(tweet);
if (t.image) {
$('.hero-unit').prepend('<div class="media"><a class="pull-left" href="#"><img class="media-object" alt="64x64" style="width: 64px; height: 64px;" src="' + t.image + '"></a><div class="media-body"><h4 class="media-heading">' + t.user + '</h4>' + t.text + '</div></div>');
}
});
Basically have the stream from twitter outside your socket, and then on a new tweet emit a message to all connected.

Resources