Creating Peer Connection for Web RTC Data Channel - webrtc

I have been trying to establish peer connection between browsers, to use data channel, but i am unsuccessful.
Everytime I correct one statement another error appears.
First I established a socketting server using socket.io and Node.js. In the server when any client is connection I am sending 'beacon' packets. On listening 'beacon' packet 1st client requests to join a 'room'. Then I allow the second client to join the same 'room'.
As soon as the second client connects, Server sends a confirmation packet to Client 1.
Then Client 1 sends the RTC Peer Connection 'offer' to Client 2, after setting local Description.
if( isChrome ) {
localPC = new window.webkitRTCPeerConnection(server, contraints);
rslt.innerHTML = "Webkit Variables Set";
}else {
localPC = new mozRTCPeerConnection(server, contraints);
rslt.innerHTML = "Mozilla Variables Set";
}
localPC.onicecandidate = function(event) {
if( event.candidate )
localPC.addIceCandidate( event.candidate );
};
localPC.onnegotiationneeded = function() {
localPC.createOffer( setOffer, sendFail );
};
sendChannel = localPC.createDataChannel( "sendDataChannel", {reliable: false} );
localPC.ondatachannel = function(event) {
receiveChannel = event.channel;
receiveChannel.onmessage = function(event) {
rslt.innerHTML = event.data;
};
};
localPC.createOffer( setOffer, sendFail );
function setOffer( offer ) {
lDescp = new RTCSessionDescription(offer);
localPC.setLocalDescription( lDescp );
socket.emit( 'offer', JSON.stringify(offer) );
rslt.innerHTML += "Offer Sent...<br/>";//+offer.sdp;
}//End Of setOffer()
Client 2 on receiving the 'offer' sets its as remote Description and creates a 'reply'. Sets the 'reply' as local Description, and sends it.
if( message.type == 'offer' ) {
rDescp = new RTCSessionDescription(message.sdp);
localPC.setRemoteDescription( rDescp );
localPC.createAnswer(
function( answer ) {
lDescp = new RTCSessionDescription(answer);
localPC.setLocalDescription( lDescp );
socket.emit( 'reply', JSON.stringify(answer) );
}, sendFail
);
}else {
localPC.addIceCandidate = new RTCIceCandidate( message.candidate );
}//End Of IF ELse
Client 1 on receiving the 'reply' sets it as remote Description and the connection should get established???
localPC.setRemoteDescription( new RTCSessionDescription( message.sdp ) );
But its not working!!! Pleease Help.

Seems like you got the flow correct, although I don't see the entire code.
One thing that strikes me weird is this:
localPC.onicecandidate = function(event) {
if( event.candidate )
localPC.addIceCandidate( event.candidate );
};
You need to send the icecandidate recieved in the onicecandidate event to the other peer. and not add it yourself.

Related

Cannot send data if the connection is not in the 'Connected' State | SignalR, Vuejs

I've searched similar topics here, but I still haven't resolved my problem. Maybe somebody can help me with this.
My goal is to display the logs from various services. To do that I am using signalR with vuejs as a client. I want to connect to the group of services on button click and fetch logs. When I click on a different button I want to leave the group and join another.
selectItem(name) {
this.groups.push(name)
document.getElementById('messageList').innerHTML = ''
const connection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:5000/logs")
.configureLogging(signalR.LogLevel.Information)
.build()
if (this.groups.length > 1) {
let previousGroup = this.groups[this.groups.length - 2]
connection.invoke("LeaveGroup", previousGroup)
console.log('leaving group:', previousGroup)
}
connection.on("PushEventLog", (message) => {
const div = document.createElement('div')
div.textContent = message
document.getElementById('messageList').appendChild(div)
})
connection.start().then(() => {
connection.invoke("JoinGroup", name)
console.log('joining group: ', name)
})
}
On my first joining to group everything works fine, but when I leaving the group and want to join another I receive an error:
Any help?
You are creating a new connection every time selectItem runs. You should store the connection object somewhere and only use it after it has started.
Added a couple comments to your code to point out what you're doing wrong.
// new connection object
const connection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:5000/logs")
.configureLogging(signalR.LogLevel.Information)
.build()
if (this.groups.length > 1) {
let previousGroup = this.groups[this.groups.length - 2]
// using new connection that hasn't started yet
connection.invoke("LeaveGroup", previousGroup)
console.log('leaving group:', previousGroup)
}
I've found a solution - I have moved connection and logs to the created hook. Working code below.
methods: {
selectItem(name, i) {
this.groups.push(name)
document.getElementById('messageList').innerHTML = ''
if (this.groups.length > 1) {
let previousGroup = this.groups[this.groups.length - 2]
this.connection.invoke("LeaveGroup", previousGroup)
} else {
this.connection.invoke("JoinGroup", name)
}
}
}
created() {
this.connection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:5000/logs")
.build()
this.connection.start()
this.connection.on("PushEventLog", (message) => {
const div = document.createElement('div')
div.textContent = message
document.getElementById('messageList').appendChild(div)
})
}

How to fix InvalidStateError: Cannot add ICE candidate when there is no remote SDP

I am creating a webRTC video chat that shows a caller all active members when initiating a call from firefox and the receiver is using chrome this error is displayed "Uncaught (in promise) DOMException: Failed to execute 'addIceCandidate' on 'RTCPeerConnection': Error processing ICE candidate". And when a call is initiated from firefox and receiver uses firefox I get two errors Invalidstate: cannot add ICE candidate when there is no remote SDP and ICE failed, add a STUN and see about:webrtc for details
I dont know where I am making a mistake
/ define all data here
var usersOnline,id,currentCaller,room,caller,localUser,media,memberInfo;
// All subscribed members.
var users = [];
var token = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
// create random user id
var userId = Math.random().toString(36).substring(2, 15);
// create random username
var username = token;
// authonticating user
var currentUser = {
username: token,
userId: userId
}
// stringify user data
currentUser = JSON.stringify(currentUser);
var pusher = new Pusher('KEY', {
authEndpoint: '../auth.php',
auth: {
params: JSON.parse(currentUser)
},
cluster: 'ap2',
forceTLS: true
});
var state = pusher.connection.state;
var channel = pusher.subscribe('presence-conference');
channel.bind("pusher:subscription_succeeded", function (members) {
console.log(members);
id = channel.members.me.id;
document.getElementById('mydetails').innerHTML = 'Online Now: ' + ' ( ' + (members.count - 1) +')';
members.each(member => {
if (member.id != channel.members.me.id) {
users.push(member.id);
}
});
renderOnline();
});
// Add user online
channel.bind("pusher:member_added", member => {
users.push(member.id);
renderOnline();
});
channel.bind("pusher:member_removed", member => {
// for remove member from list:
var index = users.indexOf(member.id);
users.splice(index, 1);
if (member.id == room) {
endCall();
}
renderOnline();
});
function renderOnline(){
var list = "";
users.forEach(function(user) {
list +=
`<li>` +
user +//this will call user
` <input type="button" style="float:right;" value="Call" onclick="callUser('` +
user +
`')" id="makeCall" /></li>`;
});
document.getElementById("userDetails").innerHTML = list;
}
//To iron over browser implementation anomalies like prefixes
GetRTCPeerConnection();
GetRTCSessionDescription();
GetRTCIceCandidate();
prepareCaller();
function prepareCaller() {
//Initializing a peer connection
caller = new window.RTCPeerConnection();
//Listen for ICE Candidates and send them to remote peers
caller.onicecandidate = function(evt) {
if (!evt.candidate) return;
console.log("onicecandidate called");
onIceCandidate(caller, evt);
};
//onaddstream handler to receive remote feed and show in remoteview video element
caller.onaddstream = function(evt) {
console.log("onaddstream called");
if("srcObject" in document.getElementById("selfview")){
document.getElementById("selfview").srcObject = evt.stream;
}else{
if (window.URL) {
document.getElementById("remoteview").src = window.URL.createObjectURL(
evt.stream
);
} else {
document.getElementById("remoteview").src = evt.stream;
}
}
};
}
function getCam() {
//Get local audio/video feed and show it in selfview video element
return navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
sampleSize:8
},
video: {
width: 1080,
height: 720,
aspectRatio: { ideal: 1.777778 }
}
});
}
function GetRTCIceCandidate() {
window.RTCIceCandidate =
window.RTCIceCandidate ||
window.webkitRTCIceCandidate ||
window.mozRTCIceCandidate ||
window.msRTCIceCandidate;
return window.RTCIceCandidate;
}
function GetRTCPeerConnection() {
window.RTCPeerConnection =
window.RTCPeerConnection ||
window.webkitRTCPeerConnection ||
window.mozRTCPeerConnection ||
window.msRTCPeerConnection;
return window.RTCPeerConnection;
}
function GetRTCSessionDescription() {
window.RTCSessionDescription =
window.RTCSessionDescription ||
window.webkitRTCSessionDescription ||
window.mozRTCSessionDescription ||
window.msRTCSessionDescription;
return window.RTCSessionDescription;
}
//Create and send offer to remote peer on button click
function callUser(user) {
getCam()
.then(stream => {
if("srcObject" in document.getElementById("selfview")){
document.getElementById("selfview").srcObject = stream;
}else{
if (window.URL) {
document.getElementById("selfview").src = window.URL.createObjectURL(
stream
);
} else {
document.getElementById("selfview").src = stream;
}
}
toggleEndCallButton();
caller.addStream(stream);
localUserMedia = stream;
caller.createOffer().then(function(desc) {
caller.setLocalDescription(new RTCSessionDescription(desc));
channel.trigger("client-sdp", {
sdp: desc,
room: user,
from: id
});
room = user;
});
})
.catch(error => {
console.log("an error occured", error);
});
}
function endCall() {
room = undefined;
caller.close();
for (let track of localUserMedia.getTracks()) {
track.stop();
}
prepareCaller();
toggleEndCallButton();
}
function endCurrentCall() {
channel.trigger("client-endcall", {
room: room
});
endCall();
}
//Send the ICE Candidate to the remote peer
function onIceCandidate(peer, evt) {
if (evt.candidate) {
channel.trigger("client-candidate", {
candidate: evt.candidate,
room: room
});
}
}
function toggleEndCallButton() {
if (document.getElementById("endCall").style.display == "block") {
document.getElementById("endCall").style.display = "none";
} else {
document.getElementById("endCall").style.display = "block";
}
}
//Listening for the candidate message from a peer sent from onicecandidate handler
channel.bind("client-candidate", function(msg) {
if (msg.room == room) {
console.log("candidate received");
caller.addIceCandidate(new RTCIceCandidate(msg.candidate));
}
});
//Listening for Session Description Protocol message with session details from remote peer
channel.bind("client-sdp", function(msg) {
if (msg.room == id) {
console.log("sdp received");
var answer = confirm(
"You have a call from: " + msg.from + "Would you like to answer?"
);
if (!answer) {
return channel.trigger("client-reject", { room: msg.room, rejected: id });
}
room = msg.room;
getCam()
.then(stream => {
localUserMedia = stream;
toggleEndCallButton();
if("srcObject" in document.getElementById("selfview")){
document.getElementById("selfview").srcObject = stream;
}else{
if (window.URL) {
document.getElementById("selfview").src = window.URL.createObjectURL(
stream
);
} else {
document.getElementById("selfview").src = stream;
}
}
caller.addStream(stream);
var sessionDesc = new RTCSessionDescription(msg.sdp);
caller.setRemoteDescription(sessionDesc);
caller.createAnswer().then(function(sdp) {
caller.setLocalDescription(new RTCSessionDescription(sdp));
channel.trigger("client-answer", {
sdp: sdp,
room: room
});
});
})
.catch(error => {
console.log("an error occured", error);
});
}
});
//Listening for answer to offer sent to remote peer
channel.bind("client-answer", function(answer) {
if (answer.room == room) {
console.log("answer received");
caller.setRemoteDescription(new RTCSessionDescription(answer.sdp));
}
});
channel.bind("client-reject", function(answer) {
if (answer.room == room) {
console.log("Call declined");
alert("call to " + answer.rejected + "was politely declined");
endCall();
}
});
channel.bind("client-endcall", function(answer) {
if (answer.room == room) {
console.log("Call Ended");
endCall();
}
});
I EXPECTED that the video call will work don't want to use any API, help me see where I went wrong.
Call setRemoteDescription(offer) before requesting the camera.
This puts the RTCPeerConnection in the right signaling state ("have-remote-offer") to receive and process remote ICE candidates correctly.
There's no time to request the camera first when an offer comes in. Incoming offers are typically followed closely by trickled ICE candidates on your signaling channel. addIceCandidate won't know what to do with those if it hasn't seen an offer.
Move the setRemoteDescription call ahead of the getMedia call in the promise chain to fix it. You have more time then before returning an answer.
Though that's still not great, since this approach often ends up blocking initial WebRTC negotiation on a user permission prompt for the camera. This is called tight coupling. Sadly, the current state of WebRTC encourages it, since getting the best IP mode is gated on getUserMedia in most browsers.
Lastly, there's a lot of old API usage here. See my other answer for newer APIs to use.

Unable to record again after stopping record in Kurento

I am working on this Kurento application and there is a strange problem i am facing where once I start recording the video and stop the recording, I cant start another recording again. The event goes to the server but nothing seems to be happening! PFB the code:
room.pipeline.create('WebRtcEndpoint', function (error, outgoingMedia) {
if (error) {
console.error('no participant in room');
// no participants in room yet release pipeline
if (Object.keys(room.participants).length == 0) {
room.pipeline.release();
}
return callback(error);
}
outgoingMedia.setMaxVideoRecvBandwidth(256);
userSession.outgoingMedia = outgoingMedia;
// add ice candidate the get sent before endpoint is established
var iceCandidateQueue = userSession.iceCandidateQueue[socket.id];
if (iceCandidateQueue) {
while (iceCandidateQueue.length) {
var message = iceCandidateQueue.shift();
console.error('user : ' + userSession.id + ' collect candidate for outgoing media');
userSession.outgoingMedia.addIceCandidate(message.candidate);
}
}
userSession.outgoingMedia.on('OnIceCandidate', function (event) {
console.log("generate outgoing candidate : " + userSession.id);
var candidate = kurento.register.complexTypes.IceCandidate(event.candidate);
userSession.sendMessage({
id: 'iceCandidate',
sessionId: userSession.id,
candidate: candidate
});
});
// notify other user that new user is joining
var usersInRoom = room.participants;
var data = {
id: 'newParticipantArrived',
new_user_id: userSession.id,
receiveVid: receiveVid
};
// notify existing user
for (var i in usersInRoom) {
usersInRoom[i].sendMessage(data);
}
var existingUserIds = [];
for (var i in room.participants) {
existingUserIds.push({id: usersInRoom[i].id, receiveVid: usersInRoom[i].receiveVid});
}
// send list of current user in the room to current participant
userSession.sendMessage({
id: 'existingParticipants',
data: existingUserIds,
roomName: room.name,
receiveVid: receiveVid
});
// register user to room
room.participants[userSession.id] = userSession;
var recorderParams = {
mediaProfile: 'WEBM',
uri: "file:///tmp/Room_"+room.name+"_file"+userSession.id +".webm"
};
//make recorder endpoint
room.pipeline.create('RecorderEndpoint', recorderParams, function(error, recorderEndpoint){
userSession.outgoingMedia.recorderEndpoint = recorderEndpoint;
outgoingMedia.connect(recorderEndpoint);
});
On the screen when I click the record button, the function on the server is:
function startRecord(socket) {
console.log("in func");
var userSession = userRegistry.getById(socket.id);
if (!userSession) {
return;
}
var room = rooms[userSession.roomName];
if(!room){
return;
}
var usersInRoom = room.participants;
var data = {
id: 'startRecording'
};
for (var i in usersInRoom) {
console.log("in loop");
var user = usersInRoom[i];
// release viewer from this
user.outgoingMedia.recorderEndpoint.record();
// notify all user in the room
user.sendMessage(data);
console.log(user.id);
}}
The thing is, for the very 1st time, it records properly i.e. file created on server and video&audio recorded properly.
When I press stop to stop recording, the intended effect is seen i.e. recording stops.
NOW, when I press record again, a video file is not made. The event reaches the server properly (console.log says so)
Can anyone help me pls?!
Thanks.

Restrict access to RPC endpoints

I wonder if deepstream provides ready-to-use solution to make endpoints private/public. If it doesn't I wonder how I can track proper deepstream calls on server side to allow only certain endpoints? I believe I need to provider permissionHandler that implements canPerformAction and check whether it's an RPC call required authorization and whether a caller authorized properly to do that. Is that right thinking? I'm looking at documentation and understand that I'm interested in topic P but I don't know what is a right action to check.
https://deepstream.io/docs/constants.html
Thanks in advance!
You're spot on with your approach. Here's a code sample on how to permission different users for different RPCs. In a real-world use-case you would most likely get the variables users and rpcs from a database.
So now whenever a client calls ds.rpc.make( 'set-user-data',... the server looks up which permission the rpc requires ('canEditUser') and if the user has that permission (mike: true, lisa: false)
var DeepstreamServer = require( 'deepstream.io' );
var server = new DeepstreamServer();
var C = server.constants;
var users = {
'mike': { canEditUser: true },
'lisa': { canEditUser: false }
};
var rpcs = {
'set-user-data': 'canEditUser'
};
server.set( 'permissionHandler', {
isValidUser: function( connectionData, authData, callback ) {
if( !authData.username ) {
callback( 'no username specified' );
}
else if( users[ authData.username ] ) {
callback( null, authData.username );
}
},
canPerformAction: function( username, message, callback ) {
var isIncomingRpc = message.topic === C.TOPIC.RPC && message.action === C.ACTIONS.REQUEST;
if( isIncomingRpc ) {
var rpcName = message.data[ 0 ];
if( rpcs[ rpcName ] === undefined ) {
callback( 'Unknown RPC ' + rpcName );
return;
}
var userPermissions = users[ username ];
var requiredRpcPermissions = rpcs[ rpcName ];
var isPermissioned = userPermissions[ requiredRpcPermissions ];
callback( null, isPermissioned );
}
}
});
server.start();

How do we know when webRTC already finished collecting ICE candidates

I am using Kurento Utils for WebRTC connection with Kurento Media Server (ver 5.x)
Inside the kurento-utils-js library during init the simplify codes are shown below:
if (!this.pc) {
this.pc = new RTCPeerConnection(server, options);
}
var ended = false;
pc.onicecandidate = function(e) {
// candidate exists in e.candidate
if (e.candidate) {
ended = false;
return;
}
if (ended) {
return;
}
var offerSdp = pc.localDescription.sdp;
console.log('ICE negotiation completed');
self.onsdpoffer(offerSdp, self);
ended = true;
};
My question is it seems like it is waiting until onicecandidate passing "null" value that signifies the process has ended and thus able to continue with creating SDP offer, but I couldn't find this behaviour in WebRTC specs?
My next question is, how else we can know the process of finding ice candidates has ended?
One of my PC in my office couldn't reached the code console.log('ICE negotiation completed'); as null value is not passed.
You could check the iceGatheringState property (run in chrome):
var config = {'iceServers': [{ url: 'stun:stun.l.google.com:19302' }] };
var pc = new webkitRTCPeerConnection(config);
pc.onicecandidate = function(event) {
if (event && event.target && event.target.iceGatheringState === 'complete') {
alert('done gathering candidates - got iceGatheringState complete');
} else if (event && event.candidate == null) {
alert('done gathering candidates - got null candidate');
} else {
console.log(event.target.iceGatheringState, event);
}
};
pc.createOffer(function(offer) {
pc.setLocalDescription(offer);
}, function(err) {
console.log(err);
}, {'mandatory': {'OfferToReceiveAudio': true}});
window.pc = pc;
http://www.w3.org/TR/webrtc/
4.3.1
" If the intent of the ICE Agent is to notify the script that:
[...]
The gathering process is done.
Set connection's ice gathering state to completed and let newCandidate be null."
So, you can either check for the ice gathering state against "completed" (in real life, this is not very reliable), or wait for a null candidate (super reliable).