Reputation: 1
This code, is handling then new User connected.
User Class
class User{
constructor(friendID){
this.friendID = friendID;
this.userName = null;
this.myPeer = new RTCPeerConnection(servers);
this.videoManager = new VideoManager();
this.setup_events();
}
get name(){
return this.userName;
}
get id(){
return this.friendID;
}
setup_events(){
let this_instance = this;
this.myPeer.onicecandidate = function(event){
if (event.candidate !== null){
LOG("ICE CANDIDATE SEND TO " + this_instance.friendID);
LOG_obj(event.candidate);
socket.emit("ice-candidate", event.candidate, this_instance.friendID);
}else{
LOG("EMPTY CANDIDATE");
}
}
this.myPeer.addEventListener('track', async(event) => {
const [remoteStream] = event.streams;
this_instance.videoManager.createVideo();
this_instance.videoManager.setStream(remoteStream);
LOG("ADDED stream to VideoObject");
});
}
add_candidate(candidate){
this.myPeer.addIceCandidate( new RTCIceCandidate(candidate) );
LOG("CANDIDATE ADDED TO PEER");
}
accept_offer(userOffer){
this.myPeer.setRemoteDescription(new RTCSessionDescription(userOffer));
LOG("ACCEPTED OFFER");
}
async create_offer(){
MediaRules.get_MediaRules()
.then( (mediaRules) => {
navigator.mediaDevices.getUserMedia(mediaRules).then( (mediaStream) =>{
let tracks = mediaStream.getTracks();
tracks.forEach( track => { this.myPeer.addTrack(track, mediaStream); });
LOG("ADDED ALL TRACKS");
}).then( () => {
this.myPeer.createOffer(mediaRules).then( (offerObj) => {
this.myPeer.setLocalDescription(offerObj);
socket.emit("user-offer", offerObj, this.friendID);
});
});
});
}
accept_answer(userAnswer){
this.myPeer.setRemoteDescription(new RTCSessionDescription(userAnswer));
LOG("ACCEPTED ANSWER");
}
async create_answer(){
MediaRules.get_MediaRules().then( (mediaRules) => {
navigator.mediaDevices.getUserMedia(mediaRules).then( (mediaStream) => {
let tracks = mediaStream.getTracks();
tracks.forEach( track => { this.myPeer.addTrack(track, mediaStream); });
LOG("ADDED ALL TRACKS");
}).then( () => {
this.myPeer.createAnswer(mediaRules).then( (answerObj) => {
this.myPeer.setLocalDescription(answerObj);
socket.emit("user-answer", answerObj, this.friendID);
});
});
});
}
}
User Pool
class UsersPool{
constructor(){
this.UsersMap = {};
}
addUser(userObj){
this.UsersMap[userObj.id] = userObj;
}
accept_IceCandidate(candidateObject, user_id){
this.UsersMap[user_id].add_candidate(candidateObject);
}
accept_Offer(offerObject, user_id){
LOG("ACCEPT OFFER FROM " + user_id);
this.UsersMap[user_id].accept_offer(offerObject);
}
accept_Answer(answerObject, user_id){
this.UsersMap[user_id].accept_answer(answerObject);
}
async CreateSendOffer(user_id){
await this.UsersMap[user_id].create_offer();
}
async CreateSendAnswer(user_id){
await this.UsersMap[user_id].create_answer();
}
}
Media Constraints
class MediaConstraints{
async get_MediaRules(){
let mediaRules = { video: false, audio: false };
let devicesEnum = await navigator.mediaDevices.enumerateDevices();
devicesEnum.forEach( device => {
if ( device.kind == "audioinput" ){
mediaRules["audio"] = true;
}
else if ( device.kind == "videoinput"){
mediaRules["video"] = true;
}
});
return mediaRules;
}
}
Video Manager (creates video element by user)
class VideoManager {
constructor(){
this.videoObject = null;
}
createVideo(){
let videoObject = document.createElement("video");
let divVideo = document.createElement("div");
videoObject.setAttribute('width', "600");
videoObject.setAttribute('height', "800");
divVideo.appendChild(videoObject);
document.body.appendChild(divVideo);
this.videoObject = videoObject;
}
setStream(stream){
this.videoObject.srcObject = stream;
this.videoObject.play();
}
}
Well, the problem is here. icecandidate is working nice, signaling server is working too. TURN/STUN server works fine.
My main question is how to create constraints and setup correctly Offer and Answer if User A don't have webcamera but User B has. At the moment i get error that STUN server is broken, but this is because peers can't finish establishing connection between each other.
How to make it, if i have only microphone on my Laptop, but on other Laptop i have video and microphone.
EDIT 0: Well, looks like WebRTC doesn't like if constraints are different, if User A create offer with {video: false, audio: true}, and send it to User B, and User B creates answer with {video: true, audio: true} then it fails to connect because constraints are different.
Still don't understand why this is a problem.
EDIT 1: Looks like the only way is to use addTransceiver and to control manually media.
Upvotes: 0
Views: 128
Reputation: 1
Actually, the problem was not in Linux Firefox version. The Problem was in Offer-Answer exchange and using AddTrack function.
Problem was that, if User A doesn't have WebCamera but User B have, it can't finish normally connection, and as a result ice-candidate is not triggered.
So, i solved it using AddTransceiver()
.
An example how to make connection if someone doesn't have webacamera or microphone.
Create RTCPeerConnection.
let myPeer = new RTCPeerConnection();
Make a function that is looking for active devices. so in result you need to get
real_devices = { video: false, audio: true }
for example if there is not webcamera
async get_MediaRules(){
let devicesEnum = await navigator.mediaDevices.enumerateDevices();
devicesEnum.forEach( device => {
if ( device.kind == "audioinput" ){
this.physicalRule["audio"] = true;
}
else if ( device.kind == "videoinput"){
this.physicalRule["video"] = true;
}
});
return this.physicalRule;
}
Get mediaStream
let myStream = navigator.mediaDevices.getUserMedia(real_devices);
Create Transceiver and add it to connection ( do this on Caller Side ).
async transceiverSetup(){
let videoTracks = myStream.getVideoTracks();
let audioTracks = myStream.getAudioTracks();
if (videoTracks.length > 0){
this.clientPeer.addTransceiver(videoTracks[0]);
}
else{
let video = this.myStream.addTransceiver("video");
video.direction = "recvonly";
}
if (audioTracks.length > 0){
this.myStream.addTransceiver(audioTracks[0]);
}
else{
let audio = this.myStream.addTransceiver("audio");
audio.direction = "recvonly";
}
}
After that you call
myPeer.createOffer()...
And on other side after you receiver Offer, you call remoteTrackSetup(). function and you setup on your side transceivers.
async configure_transceiver(mediaTracks, transceivers){
transceivers.forEach( async(any) => {
if (any.receiver.track){
if (mediaTracks.length > 0){
any.direction = "sendrecv";
await any.sender.replaceTrack(mediaTracks[0]);
}
else{
any.direction = "recvonly";
await any.sender.replaceTrack(null);
}
}else{
if (mediaTracks.length > 0){
any.direction = "sendonly";
await any.sender.replaceTrack(mediaTracks[0]);
}else{
any.direction = "inactive";
await mediaTracks.sender.replaceTrack(null);
}
}
});
}
async remoteTrackSetup(){
let mediaStream = GlobalState.Media;
let audioTracks = mediaStream.getAudioTracks();
let videoTracks = mediaStream.getVideoTracks();
let transceivers = this.clientPeer.getTransceivers();
let audioTransceivers = transceivers.filter(function(tr){
return tr.receiver.track.kind == "audio";
});
let videoTransceivers = transceivers.filter(function(tr){
return tr.receiver.track.kind == "video";
});
await this.configure_transceiver(audioTracks, audioTransceivers);
await this.configure_transceiver(videoTracks, videoTransceivers);
}
After those functions you call
myPeer.createAnswer()...
And connection is fully established.
Here is code for ontrack event.
setTransceiver(transceiver){
if(!this.videoObject.srcObject || this.videoObject.srcObject.getTracks().length == 2){
this.videoObject.srcObject = new MediaStream([transceiver.receiver.track]);
}else{
this.videoObject.srcObject.addTrack(transceiver.receiver.track);
}
}
Upvotes: 0