I can't make an RTCPeerConnection working. After i create an RTCPeerConnection object, nothing happen. Events are not fired. This is the method that create the connection :
createPeerConnection() {
console.log('new RtCPeerConnection with stun server.');
this.myPeerConnection = new RTCPeerConnection({stunServer}]
});
console.log('PeerConnection is ', this.myPeerConnection);
this.myPeerConnection.onicecandidate = this.handleICECandidateEvent;
this.myPeerConnection.ontrack = this.handleAddTrackEvent;
this.myPeerConnection.removeTrack = this.handleRemoveStreamEvent;
this.myPeerConnection.oniceconnectionstatechange = this.handleICEConnectionStateChangeEvent;
// this.myPeerConnection.onicegatheringstatechange = this.handleICEGatheringStateChangeEvent;
// this.myPeerConnection.onsignalingstatechange = this.handleSignalingStateChangeEvent;
this.myPeerConnection.onnegotiationneeded = this.handleNegotiationNeededEvent;
}
I have not error in console. Just nothing happened.
This method is called when user click on another user to connect :
connect() {
console.log('Creating RTCPeerConnetion...');
this.createPeerConnection();
console.log('RTCPeerConnection created');
console.log('Creating new local stream ...');
navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then((localStream) => {
this.localVideo.nativeElement.srcObject = localStream;
console.log('Local stream created', localStream);
}).catch(this.handleGetUserMediaError);
}
I used Firefox and Angular2 and i test this on localhost. Don't know if this can be the cause. any idea?
I have just forget to add the steam to the peerConnection like this :
navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then((localStream) => {
this.localVideo.nativeElement.srcObject = localStream;
console.log('Local stream created', localStream);
localStream.getTracks().forEach(track =>
this.myPeerConnection.addTrack(track, localStream)
);
Related
I am new to PeerJs and recently starting developing an app for my school during this Covid pandemic.
I have been able to deploy code to NodeJs server with express and was able to establish connection between 2 users.
But the problem arises when video is turned off from the beginning of stream for both users and a user wants to initiate a video call.
What I need is, to send some kind of notification to user 2 that user 1 is requesting for video. So that user 2 will turn on video.
My existing code is:
var url = new URL(window.location.href);
var disableStreamInBeginning = url.searchParams.get("disableStreamInBeginning"); // To disable video in the beginning
var passwordProtectedRoom = url.searchParams.get("passwordProtectedRoom");
var muteAllInBeginning = url.searchParams.get("muteAllInBeginning");
const socket = io('/')
const localVideoDiv = document.getElementById('local-video-div')
const oneOnOneSelf = document.getElementById('local-video')
const oneOnOneRemote = document.getElementById('remote-video')
if(typeof disableStreamInBeginning !== 'undefined' && disableStreamInBeginning == 'true'){
var disbaleSelfStream = true
} else {
var disbaleSelfStream = false
}
if(typeof passwordProtectedRoom !== 'undefined' && passwordProtectedRoom == 'true'){
var passwordProtected = true
} else {
var passwordProtected = false
}
if(typeof muteAllInBeginning !== 'undefined' && muteAllInBeginning == 'true'){
var muteAll = true
} else {
var muteAll = false
}
var systemStream
oneOnOneSelf.style.opacity = 0
oneOnOneRemote.style.opacity = 0
const myPeer = new Peer(undefined, {
host: '/',
port: '443',
path: '/myapp',
secure: true
})
const ownVideoView = document.createElement('video')
const peers = {}
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(ownStream => {
systemStream = ownStream
addVideoStream(ownStream, oneOnOneSelf)
myPeer.on('call', call => {
call.answer(ownStream)
call.on('stream', remoteStream => {
addVideoStream(remoteStream, oneOnOneRemote)
})
})
socket.on('user-connected', userId => {
//connectToNewUser(userId, stream)
setTimeout(connectToNewUser, 1000, userId, ownStream)
})
})
socket.on('user-disconnected', userId => {
if (peers[userId]) peers[userId].close()
})
myPeer.on('open', id => {
//Android.onPeerConnected();
socket.emit('join-room', ROOM_ID, id)
})
function connectToNewUser(userId, stream) {
const call = myPeer.call(userId, stream)
call.on('stream', remoteStream => {
//console.log('Testing')
addVideoStream(remoteStream, oneOnOneRemote)
})
call.on('close', () => {
oneOnOneRemote.remove()
})
peers[userId] = call
}
function addVideoStream(stream, videoView) {
videoView.srcObject = stream
videoView.addEventListener('loadedmetadata', () => {
if(disbaleSelfStream){
audioVideo(true)
} else {
localVideoDiv.style.opacity = 0
videoView.style.opacity = 1
videoView.play()
}
})
}
function audioVideo(bool) {
if(bool == true){
localVideoDiv.style.opacity = 1
oneOnOneSelf.style.opacity = 0
systemStream.getVideoTracks()[0].enabled = false
} else {
if(disbaleSelfStream){
console.log('Waiting For Another User To Accept') // Here is need to inform user 2 to tun on video call
} else {
localVideoDiv.style.opacity = 0
oneOnOneSelf.style.opacity = 1
systemStream.getVideoTracks()[0].enabled = true
}
}
}
function muteUnmute(bool) {
if(bool == true){
systemStream.getAudioTracks()[0].enabled = true
} else {
systemStream.getAudioTracks()[0].enabled = false
}
}
function remoteVideoClick(){
alert('Hi');
}
Please help.
You can send messages back and forth directly using peer itself
const dataConnection = peer.connect(id) will connect you to the remote peer, it returns a dataConnection class instance that you can later use with the send method of that class.
Just remember that you also want to setup listener on the other side to listen for this events, like "open" to know when the data channel is open:
dataConnection.on('open', and dataConnection.on('data...
You have a bug in your code above, I know you didn't ask about it, it is hard to see and not always will manifest. The problem will occur when your originator sends a call before the destination has had time to receive the promise back with its local video/audio stream. The solution is to invert the order of the calls and to start by setting up the event handler for peer.on("call", ... rather than by starting by waiting for a promise to return when we ask for the video stream. The failure mode will depend on how long does it take for your destination client to signal it wants and call to the originator plus how long it takes for the originator to respond versus how long it takes for the stream promise to return on the destination client. You can see a complete working example, where messages are also sent back and forth here.
// Function to obtain stream and then await until after it is obtained to go into video chat call and answer code. Critical to start the event listener ahead of everything to ensure not to miss an incoming call.
peer.on("call", async (call) => {
let stream = null;
console.log('*** "call" event received, calling call.answer(strem)');
// Obtain the stream object
try {
stream = await navigator.mediaDevices.getUserMedia(
{
audio: true,
video: true,
});
// Set up event listener for a peer media call -- peer.call, returns a mediaConnection that I name call
// Answer the call by sending this clients video stream --myVideo-- to calling remote user
call.answer(stream);
// Create new DOM element to place the remote user video when it comes
const video = document.createElement('video');
// Set up event listener for a stream coming from the remote user in response to this client answering its call
call.on("stream", (userVideoStream) => {
console.log('***"stream" event received, calling addVideoStream(UserVideoStream)');
// Add remote user video stream to this client's active videos in the DOM
addVideoStream(video, userVideoStream);
});
} catch (err) {
/* handle the error */
console.log('*** ERROR returning the stream: ' + err);
};
});
I'm having problems with the logic to build behind the webRTC multi peer connections handling.
Basically I'm trying to make a Room full of people in a videoconference call.
I'm using the basic WebSocket library provided by js, and React for the frontend and Java (spring boot) for the backend.
As of my understanding right now this is what I managed to write down (filtered based on what I "think" is relevant)
This is my web socket init method (adding listeners)
let webSocketConnection = new WebSocket(webSocketUrl);
webSocketConnection.onmessage = (msg) => {
const message = JSON.parse(msg.data);
switch (message.type) {
case "offer":
handleOfferMessage(message);
break;
case "text":
handleReceivedTextMessage(message);
break;
case "answer":
handleAnswerMessage(message);
break;
case "ice":
handleNewICECandidateMessage(message);
break;
case "join":
initFirstUserMedia(message);
break;
case "room":
setRoomID(message.data);
break;
case "peer-init":
handlePeerConnection(message);
break;
default:
console.error("Wrong type message received from server");
}
Plus of course the 'on error', 'on close' and 'on open' listeners
This is the method handling the incoming offer
const handleOfferMessage = (message) => {
console.log("Accepting Offer Message");
console.log(message);
let desc = new RTCSessionDescription(message.sdp);
let newPeerConnection = new RTCPeerConnection(peerConnectionConfig);
newPeerConnection.onicecandidate = handleICECandidateEvent;
newPeerConnection.ontrack = handleTrackEvent;
if (desc != null && message.sdp != null) {
console.log("RTC Signalling state: " + newPeerConnection.signalingState);
newPeerConnection
.setRemoteDescription(desc)
.then(function () {
console.log("Set up local media stream");
return navigator.mediaDevices.getUserMedia(mediaConstraints);
})
.then(function (stream) {
console.log("-- Local video stream obtained");
localStream = stream;
try {
videoSelf.current.srcObject = localStream;
} catch (error) {
videoSelf.current.src = window.URL.createObjectURL(stream);
}
console.log("-- Adding stream to the RTCPeerConnection");
localStream
.getTracks()
.forEach((track) => newPeerConnection.addTrack(track, localStream));
})
.then(function () {
console.log("-- Creating answer");
return newPeerConnection.createAnswer();
})
.then(function (answer) {
console.log("-- Setting local description after creating answer");
return newPeerConnection.setLocalDescription(answer);
})
.then(function () {
console.log("Sending answer packet back to other peer");
webSocketConnection.send(
JSON.stringify({
from: user,
type: "answer",
sdp: newPeerConnection.localDescription,
destination: message.from
})
);
})
.catch(handleErrorMessage);
}
peerConnections[message.from.id] = newPeerConnection;
console.log("Peer connections updated now ", peerConnections);
};
SN: I got the peer connections defined as an array of RTCPeerConnection indexed by the user unique id
let [peerConnections, setPeerConnections] = useState([]);
And here comes the part that I think I got wrong and on which I'm having trouble understanding
const handleAnswerMessage = (message) => {
console.log("The peer has accepted request");
let currentPeerConnection = peerConnections[message.from.id];
if (currentPeerConnection) {
currentPeerConnection.setRemoteDescription(message.sdp).catch(handleErrorMessage);
peerConnections[message.from.id] = currentPeerConnection;
} else {
console.error("No user was found with id ", message.from.id);
}
console.log("Peer connections updated now ", peerConnections);
};
currentPeerConnection.setRemoteDescription(message.sdp).catch(handleErrorMessage);
peerConnections[message.from.id] = currentPeerConnection;
console.log("Peer connections updated now ", peerConnections);
};
The answer and the offer work perfectly, I can clearly see the two peers communicating one by sending the offer and the other one responding with an answer. The only problem is that after that nothing happens, but from what I read about webRTC it should actually start gathering ice candidates as soon as a local description has been set.
I can understand why the peer handling the answer (caller) actually does not fire up iceecandidate and that's probably because I do not set a local description on the answer message (I don't know if it would be correct). the callee on the other hand, handling the offer message should actually start gathering iceecandidates tho, I'm setting the local description on there.
This some additional code that might help
function getMedia(constraints, peerCnnct, initiator) {
if (localStream) {
localStream.getTracks().forEach((track) => {
track.stop();
});
}
navigator.mediaDevices
.getUserMedia(constraints)
.then(stream => {
return getLocalMediaStream(stream, peerCnnct, initiator);
})
.catch(handleGetUserMediaError);
}
function getLocalMediaStream(mediaStream, peerConnection, initiator) {
localStream = mediaStream;
const video = videoSelf.current;
if (video) {
video.srcObject = mediaStream;
video.play();
}
//localVideo.srcObject = mediaStream;
console.log("Adding stream tracks to the peer connection: ", peerConnection);
if (!initiator) {
localStream
.getTracks()
.forEach((track) => peerConnection.addTrack(track, localStream));
}
}
const handlePeerConnection = (message) => {
console.info("Creating new peer connection for user ", message.from);
let newPeerConnection = new RTCPeerConnection(peerConnectionConfig);
// event handlers for the ICE negotiation process
newPeerConnection.ontrack = handleTrackEvent;
newPeerConnection.onicecandidate = handleICECandidateEvent;
getMedia(mediaConstraints, newPeerConnection, false);
newPeerConnection.onnegotiationneeded = handleNegotiationNeededEvent(newPeerConnection, webSocketConnection, user, message.from);
peerConnections[message.from.id] = newPeerConnection;
};
Here you can clearly see my desperate attempt in finding a solution and creating a peer connection just for the sake of sending the offer.
I cannot index a peer connection that has no end user because I would need his id, that I receive only after I received an answer from him when I first join the room.
(The backend should work but either way putting a debugger on the ice candidate handler method I could clearly see that it's just not fired)
What am I doing wrong?
EDIT: Now the WebSocketMessage Server side has also a destination user. This way the the new peer that connects to the room receives as many peer-init messages as the already connected peers are. Then proceeds to make one offer per peer setting it as a destination.
The problem still persists though
This feels actually wierd but I fixed it calling the getUserMedia() (which means calling the addTrack before adding the onicecandidate event definition to the peerConnection.
This at least fixed my problem
I am building a video chat application using simple-peer where users can also share the screen. The flow of the app is such that when user A creates a room, he can grab the page url and share with user B. When user B joins, an initiator peer is created for him as seen here.
function createPeer(partnerID, callerID, stream) {
const peer = new Peer({
initiator: true,
trickle: false,
stream,
});
peer.on("signal", signal => {
const payload = {
partnerID,
callerID,
signal
}
socketRef.current.emit("call partner", payload);
});
peer.on("stream", handleStream);
return peer;
}
When user A gets the offer from user B, a non initiator peer is created for him as seen here.
function addPeer(incomingSignal, callerID, stream) {
const peer = new Peer({
initiator: false,
trickle: false,
stream,
});
peer.on("signal", signal => {
const payload = {
callerID,
signal
}
socketRef.current.emit("accept call", payload);
});
peer.on("stream", handleStream);
peer.signal(incomingSignal);
return peer;
}
Now when any user decides to share their screen, this function gets called.
function shareScreen() {
navigator.mediaDevices.getDisplayMedia().then(stream => {
const track = stream.getTracks()[0];
peerRef.current.removeStream(videoStream.current);
peerRef.current.addStream(stream);
userVideoRef.current.srcObject = stream;
track.onended = function () {
userVideoRef.current.srcObject = videoStream.current;
peerRef.current.removeTrack(track, stream);
};
});
}
What's really strange about the behavior that I am getting, is that when user B, in other words the calling peer, wants to share his screen, all works well, but when user A, the callee peer, wants to share his screen, I get the following error.
index.js:17 Uncaught Error: [object RTCErrorEvent]
at makeError (index.js:17)
at RTCDataChannel._channel.onerror (index.js:490)
I am not really sure where I am going wrong.
After some testing, I discovered where the error is coming from.
What you need to do is to call the function peer.signal(incomingSignal) after calling the function addPeer(), along with the peer variable returned from the addPeer() function.
Here's an example:
function addPeer(incomingSignal, callerID, stream) {
const peer = new Peer({
initiator: false,
trickle: false,
stream,
});
peer.on("signal", signal => {
const payload = {
callerID,
signal
}
socketRef.current.emit("accept call", payload);
});
peer.on("stream", handleStream);
// peer.signal(incomingSignal);
return peer;
}
let peer = addPeer(incomingSignal, callerId, stream);
peer.signal(incomingSignal);
and it will work fine
hello guys How I can reject o accepted a call or offer is coming from the sender peer, I just using Peerjs client and peer server
this is my sender client
const peer = new Peer('sender', { host: '1.0.0.99', port: 9000, path: '/' })
var call = document.getElementById('call');
call.addEventListener('click',startChat);
function startChat(){
navigator.mediaDevices.getUserMedia({ video: true}).then((localStream) =>{
document.querySelector('video#local').srcObject = localStream;
var call = peer.call('receiver',localStream);
call.on('stream',remoteStream => {
document.querySelector('video#remote').srcObject = remoteStream
})
})
}
this is my receiver
const peer = new Peer('receiver', { host: '1.0.0.99', port: 9000, path: '/' })
peer.on('call', call => {
const startChat = async () => {
const localStream = await navigator.mediaDevices.getUserMedia({
video: true
})
document.querySelector('video#local').srcObject = localStream
// call.answer(localStream)
call.close(mediaStream);
call.on('stream', remoteStream => {
document.querySelector('video#remote').srcObject = remoteStream
})
}
startChat();
})
my goal on receiver can decline and accepted sorry I am new in this, thanks for any help
According to the oficial documentation, the MediaConnection API, states that the close() method should be used to reject the call, and the answer() method to accept it. In your code, you have tried both, even though you're passing an argument to the close() function, which doesn't take any. Now if you close the Media Connection, I assume that the callback on the the 'stream' message is invalid.
well I found the way
peer.on('call', call => {
var acceptsCall = confirm("Videocall incoming, do you want to accept it ?");
if (acceptsCall) {
const startChat = async () => {
const localStream = await navigator.mediaDevices.getUserMedia({
video: true
})
document.querySelector('video#local').srcObject = localStream
call.answer(localStream)
call.on('stream', remoteStream => {
document.querySelector('video#remote').srcObject = remoteStream
});
// Handle when the call finishes
call.on('close', function () {
alert("The videocall has finished");
});
}
startChat();
} else {
alert('call decline!');
}
})
one of the solutions I found was to start a conversation and after 0.1 seconds, close the connection, it is not one of the best solutions, but it was the one that served me the most.
I'm getting next error when it fires RTCPeerConnection.createOffer():
DOMException
code: 11
columnNumber: 0
data: null
filename: ""
lineNumber: 0
message: "Cannot create offer when there are no valid transceivers."
name: "InvalidStateError"
result: 2152923147
stack: ""
__proto__: DOMExceptionPrototype { name: Getter, message: Getter, INDEX_SIZE_ERR: 1, … } index.js:176:3
The full snippet of code is next (the error is fired by the line: pc.createOffer().then(...):
let pc = {};
let localStream = {};
const btnStart = document.querySelector("button[id='btnstart']");
let localVideoDisplay = document.getElementById('vOwn');
btnStart.addEventListener("click",(e)=>{
pc = new RTCPeerConnection();
pc.onicecandidate = (event)=>{
console.log(event);
}
/*
It captures local media
*/
navigator.mediaDevices.getUserMedia({ audio: 1, video: 1 }).then((stream)=>{
localStream = stream;
localVideoDisplay.srcObject = localStream;
}).catch((err)=>{
console.log(err);
});
/*
It creates sdpOffer
*/
pc.createOffer().then((rtcSession)=>{
console.log(rtcSession);
}).catch((error)=>{
console.log(error);
});
});
<button id="btnstart">Start</button>
<video id="vOwn" autoplay></video>
The code works flawlessly on Chrome (mobile and desktop) and Safari (mobile v11.3.1 and desktop v11.0.2) my question is next: Is this a firefox bug or Am I doing something wrong?
You are calling pc.createOffer() before adding any streams or creating a datachannel.
This is typically not what you want. Try this:
navigator.mediaDevices.getUserMedia({ audio: 1, video: 1 }).then((stream)=>{
localStream = stream;
localVideoDisplay.srcObject = localStream;
stream.getTracks().forEach((t) => pc.addTrack(t, stream));
return pc.createOffer()
})
.then((rtcSession)=>{
console.log(rtcSession);
}).catch((error)=>{
console.log(error);
});