WebRTC works locally, but not over Internet [duplicate] - webrtc

This question already has answers here:
How do you get around NATs using WebRTC without a TURN server?
(2 answers)
Closed 2 years ago.
I'm trying to get a simple proof of concept WebRTC app going. I've got code that works on the local network, but not over the Internet.
I believe I've got a bug outlined here, but I can't figure it out.
server.js (expressjs)
require('dotenv').config();
const express = require('express');
const app = express();
const http = require('http').createServer(app);
const io = require('socket.io')(http);
app.use(express.static('public'));
io.on('connection', (socket) => {
socket.on('join', () => socket.broadcast.emit('join', socket.id));
socket.on('offer', (id, sdp) => socket.to(id).emit('offer', socket.id, sdp));
socket.on('answer', (id, sdp) => socket.to(id).emit('answer', socket.id, sdp));
socket.on('candidate', (id, candidate) => socket.to(id).emit('candidate', socket.id, candidate));
});
const port = process.env.PORT || 443;
http.listen(port, () => console.log("listening on :" + port));
client.js (built w/ webpack)
import io from 'socket.io-client';
console.log('getting video stream');
navigator.mediaDevices.getUserMedia({ video: true }).then(myStream => {
const socket = io.connect();
console.log('joining');
socket.emit('join');
let connections = {};
socket.on('join', async peer_id => {
const connection = newConnection(peer_id);
console.log('creating offer');
const sdp = await connection.createOffer();
console.log('setting local desc');
await connection.setLocalDescription(sdp);
console.log('sending offer');
socket.emit('offer', peer_id, connection.localDescription);
});
socket.on('offer', async (peer_id, remoteDescription) => {
const connection = newConnection(peer_id);
console.log('setting remote desc');
await connection.setRemoteDescription(remoteDescription);
console.log('creating answer');
const sdp = await connection.createAnswer();
console.log('setting local desc');
await connection.setLocalDescription(sdp);
console.log('sending answer');
socket.emit('answer', peer_id, connection.localDescription);
});
socket.on('answer', (peer_id, remoteDescription) => {
console.log('setting remote desc');
connections[peer_id].setRemoteDescription(remoteDescription);
});
socket.on('candidate', (peer_id, candidate) => {
console.log('adding candidate');
connections[peer_id].addIceCandidate(candidate);
});
function newConnection(peer_id) {
console.log('creating connection');
const connection = new RTCPeerConnection(
{ iceServers: [{ urls: ['stun:stun.l.google.com:19302'] }] }
);
connections[peer_id] = connection;
connection.onconnectionstatechange = () => console.log('connection state', connection.connectionState);
console.log('searching for candidates');
connection.onicecandidate = event => {
if (event.candidate) {
console.log('sending candidate');
socket.emit('candidate', peer_id, event.candidate);
} else {
console.log('all candidates found');
}
};
console.log('listening for tracks');
connection.ontrack = event => {
console.log('track received');
const stream = new MediaStream();
stream.addTrack(event.track);
document.getElementById('video').srcObject = stream;
};
for (const track of myStream.getTracks()) {
console.log('sending track');
connection.addTrack(track);
}
return connection;
}
});
Logs from local network peers (connected)
first peer
getting video stream
joining
creating connection
searching for candidates
listening for tracks
sending track
creating offer
setting local desc
sending offer
(2) sending candidate
setting remote desc
adding candidate
track received
connection state connecting
all candidates found
connection state connected
second peer
getting video stream
joining
creating connection
searching for candidates
listening for tracks
sending track
setting remote desc
track received
creating answer
setting local desc
sending answer
sending candidate
connection state connecting
all candidates found
connection state connected
(2) adding candidate
Logs from peers over the Internet (failed)
first peer
getting video stream
joining
creating connection
searching for candidates
listening for tracks
sending track
creating offer
setting local desc
sending offer
(3) sending candidate
all candidates found
setting remote desc
track received
connection state connecting
(5) adding candidate
connection state failed
second peer
getting video stream
joining
creating connection
searching for candidates
listening for tracks
sending track
setting remote desc
track received
creating answer
setting local desc
sending answer
(5) sending candidate
(3) adding candidate
connection state connecting
connection state failed
all candidates found

WebRTC isn't going to work on all connection pairings unless you use TURN.
You will see some peers able to connect though, you can read about all the cases that matter in my answer here

Related

webRTC Coturn server return null iceCandidate?

I installed coturn server on an Ubuntu VPS for testing purposes, the testing of the coturn server on Trickle ICE looks great and everything is fine, but when I try to make a connection it returns null iceCandidate when peers are from different networks and it works fine when peers are in the same local network.
This is my Trickle ICE test results:
This is what I get when console logging the iceCandidate:
And this is my iceservers:
const iceServers = {
iceServer: [
{
urls: "stun:stun.biodietfood.com:3478",
},
{
urls: "turn:turn.biodietfood.com:3478?transport=tcp",
username: "myuser",
credential: "mypassword",
},
],
};
And this is my code to make peer connection and sending an offer:
rtcPeerConnection = new RTCPeerConnection(); //create peer connection
rtcPeerConnection.setConfiguration(iceServers);
rtcPeerConnection.onicecandidate = onIceCandidate; //generate ice candidate
rtcPeerConnection.onicecandidateerror = iceCandidateError;
rtcPeerConnection.ontrack = onAddStream; // generate the remote stream to send to other user
rtcPeerConnection.addTrack(localStream.getTracks()[0], localStream); // adding video
rtcPeerConnection.addTrack(localStream.getTracks()[1], localStream); // adding audio
rtcPeerConnection // creating offer
.createOffer({ iceRestart: true })
.then((sessionDescription) => {
rtcPeerConnection.setLocalDescription(sessionDescription);
socket.emit("offer", {
type: "offer",
sdp: sessionDescription,
room: roomNumber,
});
})
.catch((err) => {
console.log(err);
});
The same code is used for creating an answer with little modifications.
How can I solve the problem?

Market data routing to frontend: Alpaca WebSocket -> Node.js WebSocket -> React Frontend

I'm trying to use the websocket example from:
https://github.com/alpacahq/alpaca-trade-api-js/blob/master/examples/websocket_example_datav2.js
In order to connect to the Alpaca V2 data stream. Currently, my stream is working but I'm trying to route my data to the client side using Server Sent Events. My data flow seems like it should be:
Alpaca Data Stream API -> My Node.js server -> React Frontend.
The issue I have is using the DataStream object in the example in order to route the data to the frontend. Since, with the object alone, I don't have any route to subscribe to via Server Sent Events, does this mean that I should also be using either express, socket.io, or ws? Since the all of the ".on_xyz" methods are defined within the DataStream object, I'm not sure how to set up the endpoint properly to allow my frontend to subscribe to it. If anyone knows how to route this datastream information forward it would be greatly appreciated- I'm particularly trying to work with the .onStockQuote method but any of them is fine! I'm simply trying to use Node as an inbetween router so that I don't have to subscribe directly from the frontend (and not use the sdk), because that limits scalability of the API's use.
"use strict";
/**
* This examples shows how to use tha alpaca data v2 websocket to subscribe to events.
* You should use the alpaca api's data_steam_v2, also add feed besides the other parameters.
* For subscribing (and unsubscribing) to trades, quotes and bars you should call
* a function for each like below.
*/
import express from 'express';
const app = express()
const Alpaca = require("#alpacahq/alpaca-trade-api");
const API_KEY = "XYZ_Key";
const API_SECRET = "XYZ_Secret";
const PORT = 3000;
// Add a new message and send it to all subscribed clients
const addMessage = (req, res) => {
const message = req.body;
// Return the message as a response for the "/message" call
res.json(message);
return ;
};
class DataStream {
constructor({ apiKey, secretKey, feed }) {
this.alpaca = new Alpaca({
keyId: apiKey,
secretKey,
feed,
});
const socket = this.alpaca.data_stream_v2;
socket.onConnect(function () {
console.log("Connected");
socket.subscribeForQuotes(["AAPL"]);
// socket.subscribeForTrades(["FB"]);
// socket.subscribeForBars(["SPY"]);
// socket.subscribeForStatuses(["*"]);
});
socket.onError((err) => {
console.log(err);
});
socket.onStockTrade((trade) => {
console.log(trade);
});
socket.onStockQuote((quote) => {
console.log(quote);
});
socket.onStockBar((bar) => {
console.log(bar);
});
socket.onStatuses((s) => {
console.log(s);
});
socket.onStateChange((state) => {
console.log(state);
});
socket.onDisconnect(() => {
console.log("Disconnected");
});
socket.connect();
// unsubscribe from FB after a second
// setTimeout(() => {
// socket.unsubscribeFromTrades(["FB"]);
// }, 1000);
}
}
app.post("/message", addMessage);
let stream = new DataStream({
apiKey: API_KEY,
secretKey: API_SECRET,
feed: "sip",
paper: false,
});
app.listen(PORT, () => {
console.log(`App listening on port ${PORT}`);
});

WebRTC needing setup on pageload?

I have a webRTC implementation working, but it requires a setup function to be run on page load.
setup = () => {
peerConnection = new RTCPeerConnection(servers)
peerConnection.onicecandidate = onicecandidate
peerConnection.onconnectionstatechange = onconnectionstatechange(
peerConnection,
)
peerConnection.onnegotiationneeded = onnegotiationneeded
peerConnection.ontrack = function({streams: [stream]}) {
const remoteVideo = document.getElementById('remote-video')
if (remoteVideo) {
remoteVideo.srcObject = stream
}
}
mediaStream.getTracks().forEach(track => peerConnection.addTrack(track, mediaStream))
}
Only then can I make the offer:
makeOffer = () => {
peerConnection
.createOffer()
.then(offer => {
return peerConnection
.setLocalDescription(new RTCSessionDescription(offer))
.then(() => offer)
})
.then(offer => {
sendOffer(offer)
})
}
If i attempt to run the setup just prior to the makeOffer function I get the error:
DOMException: Failed to execute 'setLocalDescription' on
'RTCPeerConnection': Failed to set local offer sdp: The order of
m-lines in subsequent offer doesn't match order from previous
offer/answer.
My assumption is that it is due to the makeOffer running before something in the setup has completed, but nothing in the setup seems to be asynchronous. What do I need to do to be able to setup the connection and send the offer all at once?

addTrack after etablished connection

I need to add my track and send to other peers after having etablished the connection I've followed the MDN example
pc.onnegotiationneeded = async () => {
try {
makingOffer = true;
await pc.setLocalDescription();
signaler.send({ description: pc.localDescription });
} catch(err) {
console.error(err);
} finally {
makingOffer = false;
}
};
onnegotiationneeded is fired when we call pc.addTrack so I will remake the process offer -> answer -> ICE.
So I have a function which call getUserMedia and set the local video I've added a callback to apply addTrack to my peers
const handleActiveVideo = async (cb) => {
const devices = await getVideoDevices();
const localStream = await createLocalStream(devices[0].deviceId);
setLocalVideoEl(localStream, devices[0].deviceId);
cb((pc) => {
localStream.getTracks().forEach((track) => {
pc.connection.addTrack(track, localStream);
});
});
};
But if I do that with an etablished connection when I add my local stream to the track with one peer it's ok all work fine but with my second peer I have this error on Firefox
Remote description indicates ICE restart but offer did not request ICE
restart (new remote description changes either the ice-ufrag or
ice-pwd)
with Chrome
DOMException: Failed to execute 'setRemoteDescription' on
'RTCPeerConnection': Failed to set remote answer sdp: Failed to apply
the description for m= section with mid='0': Failed to set SSL role
for the transport.
To recapitulate
connect my 2 peers without any tracks on both sides
start the video with my Peer1 ok I can see the video with my Peer2
start the video with my Peer2 error on Peer2 in the answer
setRemoteDescription(description);

how to get socketid in socket.io(nodejs)

In my nodejs application, i am using socket.io for sockets connection.
I am configuring my server side code like this
socket.io configuration in separate file.
//socket_io.js
var socket_io = require('socket.io');
var io = socket_io();
var socketApi = {};
socketApi.io = io;
module.exports = socketApi;
below is my server.js file in which i am attaching my socket io to the server like this
var socketApi = require('./server/socket_io');
// Create HTTP server.
const server = http.createServer(app);
// Attach Socket IO
var io = socketApi.io;
io.attach(server);
// Listen on provided port, on all network interfaces.
server.listen(port, () => console.log(`API running on localhost:${port}`));
and then i am using socket.io in my game.js file to emit updated user coins like this.
//game.js
var socketIO = require('../socket_io');
function updateUserCoins(userBet) {
userId = mongoose.Types.ObjectId(userBet.user);
User.findUserWithId(userId).then((user) => {
user.coins = user.coins - userBet.betAmount;
user.save((err, updatedUser) => {
socketIO.io.sockets.emit('user coins', {
userCoins: updatedUser.coins,
});
});
})
}
and then in my client side, i am doing something like this,
socket.on('user coins', (data) => {
this.coins = data.userCoins;
});
but with the above implementation, updating coins of any user, updates all user coins at the client side, since all the clients are listening to the same socket user coins.
To solve the above problem, i know that i have to do something like this,
// sending to individual socketid (private message)
socketIO.io.sockets.to(<socketid>).emit('user coins', {
userCoins: updatedUser.coins,
});
but my concern is that how will get <socketid> with my current implementation.
You can get it by listening to connection to your socket.io server :
io.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
});
Here you have a socket object ( io.on('connection', function (socket) ) and you can get his id with socket.id
So you'll probably need to wrap your code with the connection listener.
Source of the exemple for the connection listener
Socket object description