PeerJs close video call not firing close event - javascript

I am trying to create a one-directional video app with PeerJs. I've succesfully been able to run my own peer server and connect on a button click, but I'm unable to close the connection so that the peers can receive/establish a new connection with a new peer.
Every user will either be a host or a client, it will never be backwards. So the host can choose which client to connect to and the client will start to stream its camera feed back to the host. The closeCon() function is called with a button click.
$(document).ready(function(){
peer = new Peer('100001', {host: 'my.url', port: '9900', path: '/peerjs', key: 'peerjs', secure: true, debug: 3});
peer.on("open", function(id) {
console.log("My peer ID is: " + id);
});
video = document.getElementById('vidSrc');
})
function callTheGuy(id){
var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
getUserMedia({video: true, audio: false}, function(stream) {
window.call = peer.call(id, stream);
localStream = stream;
window.call.on('stream', function(remoteStream) {
let video = document.getElementById('vidArea');
video.srcObject = remoteStream;
video.play();
$("#videoModal").modal('show')
});
}, function(err) {
console.log('Failed to get local stream' ,err);
});
}
function closeCon(){
window.call.close();
}
This all works great, i get my video feed, no problem. Here is my client code:
peer = new Peer(serverId, {
host: "my.url",
port: "9900",
path: "/peerjs",
key: "peerjs",
debug: 3,
secure: true
});
peer.on("open", function(id) {
console.log("My peer ID is: " + id);
});
var getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia;
peer.on("call", function(call) {
getUserMedia(
{ video: true, audio: false },
function(stream) {
localStream = stream;
call.answer(stream); // Answer the call with an A/V stream.
},
function(err) {
console.log("Failed to get local stream", err);
}
);
call.on("close", function() {
console.log("closing");
});
});
The issue is that when I call closeCon(), the client file is not receiving the close event. The part of
call.on("close", function() {
console.log("closing");
});
never gets fired. I'm not really sure why this is happening but unless that close event gets processed, the client stays connected to the original host and can't accept connections from subsequent host requests. Does anyone have any advice?

I ran into the issue after discovering my peerConnections continued to send stream data using chrome://webrtc-internals. I am currently using the public peerjs server. The MediaConnection does not fire the close event, but the DataConnection still class does. My particular flow waits for the remote to initiate a (data)connection and then starts a call.
I was able too close the MediaConnection by:
opening both a DataConnection and a MediaConnection to a remote peer
monitoring the MediaConnection close event
Closing all WebRTCpeerConnections after as part of the DataConnection close handler
This looks like:
function handlePeerDisconnect() {
// manually close the peer connections
for (let conns in peer.connections) {
peer.connections[conns].forEach((conn, index, array) => {
console.log(`closing ${conn.connectionId} peerConnection (${index + 1}/${array.length})`, conn.peerConnection);
conn.peerConnection.close();
// close it using peerjs methods
if (conn.close)
conn.close();
});
}
}
peer.on('connection', conn => {
let call = peer.call(peerToCall, localStream);
// peerjs bug prevents this from firing: https://github.com/peers/peerjs/issues/636
call.on('close', () => {
console.log("call close event");
handlePeerDisconnect();
});
}
// this one works
conn.on('close', () => {
console.log("conn close event");
handlePeerDisconnect();
});
});

Update on 7 Mar 2021
I found that is an issue, PeerJs hasn't fixed yet. (Issue link: https://github.com/peers/peerjs/issues/636)
You can trigger the close event in Socketio "disconnect" for a workaround solution
Server
socket.on("disconnect", (reason)=>{
socket.broadcast.emit("user-disconnected", userId);
});
Client
socket.on("user-disconnected", (userId)=>{
// remove video or add your code here
});

No, it fires the close event. but you first do as follows:
server
socket.on('disconnect', () =>{
socket.broadcast.to(roomId).emit('user-disconnected', userId);
})
client
const peers = {};
function connectToNewUser (userId, stream){
...
call.on('close', () =>{
video.remove();
})
peers[userId] = call;
}
and then in client emit the event:
socket.on('user-disconnected', userId =>{
console.log(userId);
if (peers[userId]){
peers[userId].close();
}
})

Related

webRTC losses stream when a phone call is answered

In the scenario where two users are connected in a video call, one of them gets a phone call and he answers the call. this blocks the ongoing webRTC stream and the call session ends.
So is there a way we can maintain both the steam as well as the call session and resume the video call once the person return on our application.
I am using QuickBlox js-Sdk to make calls.
below is the code snippet attached to initiate the call.
var userCredentials = {
userId: 'XXXXXX',
password: "XXXXXXX"
};
QB.chat.connect(userCredentials, function (error, contactList) {
var calleesIds = [XXXXX];
var sessionType = QB.webrtc.CallType.VIDEO;
var session = QB.webrtc.createNewSession(calleesIds, sessionType);
this.userAuthService.markBusy(XXXXX).subscribe(data => data);
var mediaParams = {
audio: true,
video: true,
options: {
muted: true,
mirror: true
},
elemId: "selfStream"
};
session.getUserMedia(mediaParams, function (err, stream) {
if (err) {
console.log('getUserMedia err', err);
} else {
console.log('getUserMedia succ', stream);
// make call
var extension = {};
session.call(extension, function (error) {
console.log('call error: ', error);
});
}
});
});
and on the other side to receive the call.
QB.webrtc.onCallListener = function (session, extension) {
session.getUserMedia(self.getMediaParams('selfStream'), function (err, stream) {
if (err) {
console.log('getUserMedia err', err);
} else {
self.callSession.accept(self.callExt);
}
});
};
I have seen the same issue in some other webApps as well, Is there a fix/workaround for this problem, thanks in advance.
Get around this by adding an event on the remote user's stream. If the stream is null it'll start checking for the stream every second for 2 mins if stream is not restored back in 2 mins the call will be disconnected. else I'll use the restored stream and remove the timeInterval to check every second.

No ICE candidates generated when I run my local webRTC application on google chrome browser

I have a basic webRTC application that supports video/audio communication and file sharing between two peers, The app runs as intended when I open it on Mozilla Firefox but when I run it on Google Chrome the onicecandidate returns null
My RTCPeerConnection
myConnection = new RTCPeerConnection();
Setting up the peer connection
myConnection.createOffer().then(offer => {
currentoffer = offer
myConnection.setLocalDescription(offer);
})
.then(function () {
myConnection.onicecandidate = function (event) {
console.log(event.candidate);
if (event.candidate) {
send({
type: "candidate",
candidate: event.candidate
});
}
};
send({
type: "offer",
offer: currentoffer
});
})
.catch(function (reason) {
alert("Problem with creating offer. " + reason);
});
On Mozilla Firefox you can see in the console log all the ICE candidates that are collected on each "onicecandidate" event
On Chrome the output is null
You should pass options object when calling createOffer() method, e.g.:
myConnection = new RTCPeerConnection();
var mediaConstraints = {
'offerToReceiveAudio': true,
'offerToReceiveVideo': true
};
myConnection.createOffer(mediaConstraints).then(offer => {
currentoffer = offer
myConnection.setLocalDescription(offer);
})
...// the rest of you code goes here
Alternatively, you can specify RTCRtpTransceiver before creating an offer:
myConnection = new RTCPeerConnection();
myConnection.addTransceiver("audio");
myConnection.addTransceiver("video");
myConnection.createOffer().then(offer => {
currentoffer = offer
myConnection.setLocalDescription(offer);
})
...// the rest of you code goes here
Sources:WebRTC 1.0MDN RTCPeerConnection.createOffer()MDN RTCPeerConnection.addTransceiver()Example -- GitHub
You have to pass STUN/TURN servers when create a peer connection.
Otherwise you will only local candidates and hence will be able to connect locally only
var STUN = {
'url': 'stun:stun.l.google.com:19302',
};
var iceServers =
{
iceServers: [STUN]
};
var pc = new RTCPeerConnection(iceServers);

Can a MediaStream be used immediately after getUserMedia() returns?

I'm trying to capture the audio from a website user's phone, and transmit it to a remote RTCPeerConnection.
Assume that I have a function to get the local MediaStream:
function getLocalAudioStream(): Promise<*> {
const devices = navigator.mediaDevices;
if (!devices) {
return Promise.reject(new Error('[webrtc] Audio is not supported'));
} else {
return devices
.getUserMedia({
audio: true,
video: false,
})
.then(function(stream) {
return stream;
});
}
}
The following code works fine:
// variable is in 'global' scope
var LOCAL_STREAM: any = null;
// At application startup:
getLocalAudioStream().then(function(stream) {
LOCAL_STREAM = stream;
});
...
// Later, when the peer connection has been established:
// `pc` is an RTCPeerConnection
LOCAL_STREAM.getTracks().forEach(function(track) {
pc.addTrack(track, LOCAL_STREAM);
});
However, I don't want to have to keep a MediaStream open, and I would like to
delay fetching the stream later, so I tried this:
getLocalAudioStream().then(function(localStream) {
localStream.getTracks().forEach(function(track) {
pc.addTrack(track, localStream);
});
});
This does not work (the other end does not receive the sound.)
I tried keeping the global variable around, in case of a weird scoping / garbage collection issue:
// variable is in 'global' scope
var LOCAL_STREAM: any = null;
getLocalAudioStream().then(function(localStream) {
LOCAL_STREAM = localStream;
localStream.getTracks().forEach(function(track) {
pc.addTrack(track, localStream);
});
});
What am I missing here ?
Is there a delay to wait between the moment the getUserMedia promise is returned, and the moment it can be added to an RTCPeerConnection ? Or can I wait for a specific event ?
-- EDIT --
As #kontrollanten suggested, I made it work under Chrome by resetting my local description of the RTCPeerConnection:
getLocalAudioStream().then(function(localStream) {
localStream.getTracks().forEach(function(track) {
pc.addTrack(track, localStream);
});
pc
.createOffer({
voiceActivityDetection: false,
})
.then(offer => {
return pc.setLocalDescription(offer);
})
});
However:
it does not work on Firefox
I must still be doing something wrong, because I can not stop when I want to hang up:
I tried stopping with:
getLocalAudioStream().then(stream => {
stream.getTracks().forEach(track => {
track.stop();
});
});
No, there's no such delay. As soon as you have the media returned, you can send it to the RTCPeerConnection.
In your example
getLocalAudioStream().then(function(localStream) {
pc.addTrack(track, localStream);
});
It's unclear how stream is defined. Can it be that it's undefined?
Why can't you go with the following?
getLocalAudioStream()
.then(function (stream) {
stream
.getTracks()
.forEach(function(track) {
pc.addTrack(track, stream);
});
});

Duration of Service Worker Registration for Samsung Internet Push Notifications

I'm trying to build a web application that will send push notifications to a user who subscribes to it (testing on Samsung Internet). However, I'm facing an issue where after several hours, the phone stops receiving the push notifications, and I will need to re-open the web application and re-subscribe to resume receiving push notifications. Below is the code for my service worker and its registration:
Service Worker:
var windowActive = true;
var numMessages = 0;
var sAdder;
self.addEventListener('push', function(event) {
console.log('[Service Worker] Push Received.');
// console.log(`[Service Worker] Push had this data: "${event.data.text()}"`);
console.log(windowActive);
numMessages++;
if(numMessages == 1) {
sAdder = "";
} else {
sAdder = "s";
}
var title = 'Convers8te';
var options = {
body: numMessages + ' message' + sAdder + ' received!',
icon: '/images/logo/8-icon.png',
badge: '/images/badge.png',
tag: 'c8t',
};
if(windowActive == false) {
event.waitUntil(self.registration.showNotification(title, options));
}
});
self.addEventListener('notificationclick', function(event) {
console.log('[Service Worker] Notification click Received.');
event.notification.close();
numMessages = 0;
event.waitUntil(
clients.openWindow('')
);
});
self.addEventListener('message', function (evt) {
windowActive = evt.data['windowActive'];
console.log('postMessage received', evt.data);
});
Registration:
function subscribeUser() {
const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);
swRegistration.pushManager.subscribe({
userVisibleOnly: true,
})
.then(function(subscription) {
console.log('User is subscribed.');
updateSubscriptionOnServer(subscription);
isSubscribed = true;
updateBtn();
})
.catch(function(err) {
console.log('Failed to subscribe the user: ', err);
updateBtn();
});
}
Update subscription on server then sends the push token to the server to be used through the Firebase service.
I've tried several things such as lengthening the session duration, and testing on several other devices but it seems to stop receiving push notifications after several hours.
Also, a side question is whether it is possible for push notifications work even when the Samsung Internet explorer app is closed? Currently they only work for me when the tab is closed, but not when the entire app is closed.
Thanks for taking the time to read and help!

Session error description: Couldn't set up DTLS-SRTP on RTP channel

I am currently facing this error of not being able to set up remote SDP as there is an error in session description - Couldn't set up DTLS-SRTP on RTP channel
Also there occurs a two round of offer/answer round in peer to peer(which shouldn't) as onnegotiationneeded fires twice. This occurs during video conference setup in multi peer connectivity, otherwise the datachannel set up in the multiple peer connection(used to stream external video) works fine. The code below initiates the peer connection -
// Initiating peer connection with the host
function initiatePeerConnection(currentPeer, callback){
peerConnection[currentPeer] = new RTCPeerConnection(serverConfig); // Initiation of RTC connection of peers other than host
console.log(peerConnection[currentPeer]);
console.log(currentPeer);
peerConnection[currentPeer].onicecandidate = function(evt){
console.log("ice candidate");
console.log(peerID);
signalServer.send(JSON.stringify({"candidate": evt.candidate, "peerID": peerID, "senderID": senderID, "sendTo": currentPeer}));
};
peerConnection[currentPeer].onnegotiationneeded = function(){
console.log("negotiation initiated"+currentPeer.toString());
peerConnection[currentPeer].createOffer()
.then(function(offer){
peerConnection[currentPeer].setLocalDescription(offer)
})
.then(function(){
console.log("offer sent to "+currentPeer.toString());
console.log(peerID);
console.log(peerConnection[currentPeer].localDescription);
signalServer.send(JSON.stringify({"sessionDescriptionProtocol": peerConnection[currentPeer].localDescription, "peerID": peerID, "senderID": senderID, "sendTo": currentPeer}));
console.log("Done negotiation");
})
.catch(logError)
};
// console.log(localStream);
// peerConnection[currentPeer].addStream(localStream);
// peerConnection[currentPeer].ontrack = gotRemoteStream;
navigator.getUserMedia(constraints, function(stream){
localStream = stream;
console.log(localStream);
gotLocalStream(localStream, currentPeer);
}, fallbackUserMedia);
peerConnection[currentPeer].ontrack = function(e){
console.log("on track");
gotRemoteStream(e);
};
callback(currentPeer, setupChannel); // callback is createDataChannel. calling the callback with setupChannel
}
the code below answers to the offer/message received from server -
if(message.sessionDescriptionProtocol) {
console.log(message.sessionDescriptionProtocol.type)
if(message.sessionDescriptionProtocol.type == 'offer') {
peerConnection[currentPeer].setRemoteDescription(message.sessionDescriptionProtocol)
.then(function(){
return peerConnection[currentPeer].createAnswer();
})
.then(function(answer){
createLocalDescription(answer);
})
.catch(logError)
}else{
console.log(currentPeer);
peerConnection[currentPeer].setRemoteDescription(message.sessionDescriptionProtocol)
.catch(logError);
}
// });
} else if(message.candidate) {
console.log("adding");
peerConnection[currentPeer].addIceCandidate(message.candidate);
console.log("added");
}
function createLocalDescription(answer){
peerConnection[currentPeer].setLocalDescription(answer)
.then(function(){
console.log("answer sent to "+currentPeer.toString());
console.log(peerID);
console.log(peerConnection[currentPeer].localDescription);
signalServer.send(JSON.stringify({"sessionDescriptionProtocol": peerConnection[currentPeer].localDescription, "peerID": peerID, "senderID": senderID, "sendTo": currentPeer}));
console.log("Done");
})
}
Here is the link to the repository if needed
Thanks in advance :D

Categories