I'm trying to learn about webrtc and I have some code that is meant to be run in two different firefox windows on my laptop with me copying relevant info between the developer consoles, but I run into the error DOMException: "Unknown ufrag (..fragsnippet..)" and I can't tell what I'm messing up. Anyone know what causes this?
I know that signaling servers can be setup to do what I'm doing, but I wanted to break the steps down to understand the order and inspect the result of each step. I included the source for anyone to see if there's any clear issues happening. For explanation each window will be called pc1 or pc2.
steps
pc1 calls initConnection which creates the variable localcon of
type RTCPeerConnection using no stunServers because the example is
supposed to use local ice candidates
then we copy the locOffer variable which already contains
icecandidate entries
in window pc2 i execute reactConnection which makes a localcon
for this window and then I call ingestOffer providing the copied
locOffer from the pc1 window this sets remoteDescription for the
localcon of pc2 and then creates an answer that I would like to
copy back to pc1,
the steps end there because I run into the dom exception error and
localcon for pc2 changes to iceConnectionState:fail
Any ideas about how to fix this would be very helpful!
/* new process
create a single pc, and setup binding of candidate stuff and offer stuff to be set, and trigger other parts of the process to continue,
hopefully no servers is going to work
*/
'use strict'
var localcon, remotecon, datachannel, pcconstraint, dataconstraint, recchannel, config
export function setup() {
var servers = null
pcconstraint = null
dataconstraint = null
config = {
iceServers: [],
iceTransportPolicy: "all",
iceCandidatePoolSize: "0"
}
window.ingestOffer = ingestOffer
window.handleAnswer = handleAnswer
window.desc1 = desc1
window.setAnswer = setAnswer
window.initConnection = initConnection
window.reactConnection = reactConnection
// answer will be created in desc1
}
function reactConnection() {
window.localcon = localcon = new RTCPeerConnection(config)
localcon.onicecandidate = iceCB2
// bind different channel events
localcon.ondatachannel = handleDC
}
function initConnection() {
window.localcon = localcon = new RTCPeerConnection(config)
window.datachannel = datachannel = localcon.createDataChannel('sendDataChannel', dataconstraint)
// bind the ice events
// attach the datachannel events
datachannel.onopen = openDC
datachannel.onclose = closeDC
localcon.onicecandidate = iceCB1
localcon.oniceconnectionstatechange = handleConnectionChange
window.locCanInfo = []
// start the offers
localcon.createOffer().then(
desc1,
descriptionErr
)
}
function handleConnectionChange(event) {
console.log("connection change",event);
console.log("peer connection ",event.target.iceConnectionState);
}
function openDC () {
console.log("opening first data channel");
}
function closeDC() {
console.log("closing first data channel");
}
function handleDC(event) {
console.log("handling rec channel connect")
recchannel = event.channel
recchannel.onmessage = recmessage
recchannel.onopen = recopen
recchannel.onclose = recclose
}
function recmessage(event) {
console.log("rec channel got",event.data);
}
function recopen() {
console.log("rec channel open");
}
function recclose() {
console.log("rec channel close");
}
function setAnswer(desc) {
localcon.setRemoteDescription(new RTCSessionDescription(desc)).then(()=> console.log("success"))
}
function ingestCandidate(msg) {
console.log("ingesting candidate")
let candidate = new RTCIceCandidate({
sdpMLineIndex: msg.label,
candidate: msg.candidate
})
localcon.addIceCandidate(candidate)
}
function ingestOffer(desc,ocand) {
// ?? what part of desc should be provided?
window.theirOffer = desc
console.log("setting remote desc")
localcon.setRemoteDescription(new RTCSessionDescription(desc)).then(()=> console.log("ingest success")).catch(e=> console.log("ingest error",e))
// set window otherCands and use them at add time
window.otherCands = ocand
// create an answer
localcon.createAnswer().then(handleAnswer)
}
function handleAnswer(desc) {
window.locAnswer = desc
localcon.setLocalDescription(desc).then(()=> console.log("handle answer success")).catch(e=> console.log("handle answer error",e))
// now copy and use in other window
}
function descriptionErr(e) {
console.log("error ", e)
}
// no send Data func, because we will do this from the window
function closeChannels() {
console.log("closing send");
datachannel.close()
console.log("closing rec");
recchannel.close()
localcon.close()
remotecon.close()
}
function desc1(desc) {
// assign desc to remote, as remote desc and local to local
localcon.setLocalDescription(desc)
console.log("local offer", desc.sdp)
// put the offer in text
window.locOffer = desc
}
function desc2(desc) {
// this is using the answer
console.log("answer text", desc.sdp)
remotecon.setLocalDescription(desc)
localcon.setRemoteDescription(desc)
}
function iceCB1(event) {
// this is local dealing with ice candidate
console.log("lcal ice callbac", event.candidate);
if (event.candidate != null && event.candidate.candidate != "") {
window.locCanInfo.push({
type: 'candidate',
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate
})
//localcon.addIceCandidate(event.candidate)
}
}
function iceCB2(event) {
console.log("remote ice callback");
if (event.candidate) {
localcon.addIceCandidate(
event.candidate
).then(
iceSuccess,
iceFail
)
console.log("remote ice candidate", event.candidate, "and", event.candidate.candidate)
}
}
function iceSuccess() {
console.log("addice success");
}
function iceFail(e) {
console.log("ice fail", e);
}
function onChannelStateChange() {
let readystate = datachannel.readyState
if (readystate === "open") {
console.log("og channel ope");
} else {
console.log("og channel is not open");
}
}
function onRecChannelStateChange() {
console.log("rec channel state is ", recchannel.readyState);
}
Related
So I found a way to make a peer connection multiple times by a lot..but I can't make the video work although there is no error shown...
UPDATE: DEMO but this demo is not allow working localStream so try it in your own browser index.html
First let say we have this html file
// This one is for multiple videos
<div class="item-videos">
//This first video is a start video
<video id="video1" playsinline autoplay muted></video>
//This is join videos
</div>
<div>
<button id="start"> Start </button>
<button id="join"> Join </button>
<button id="hangup"> Hang Up </button>
</div>
First I will takes the initial inputs for starter in script.js
let containers = document.querySelector('.item-videos');
const startButton = document.querySelector('#start')
const joinButton = document.querySelector("#join")
const video1 = document.querySelector('video#video1');
let localStream;
// This is the RTCPeerConnections arrays.
let pcLocals = [];
let pcRemotes = [];
const offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
};
const servers = {
iceServers: [
{
urls: ['stun:stun1.l.google.com:19302', 'stun:stun2.l.google.com:19302'],
},
],
iceCandidatePoolSize: 10,
};
And then let say first we will start our call server..which will be created.
So now we will make a start click then our code
...
function gotStream(stream) {
console.log('Received local stream');
video1.srcObject = stream;
localStream = stream;
joinButton.disabled = false;
}
function start() {
console.log('Requesting local stream');
startButton.disabled = true;
navigator.mediaDevices
.getUserMedia({
audio: true,
video: true
})
.then(gotStream)
.catch(e => console.log('getUserMedia() error: ', e));
}
startButton.addEventListener("click",start)
Now this is for join button in the server...let say I have let count = 0
and I will createElement each video I click the button
So our code for the join button click is
let count = 0;
joinButton.addEventListener("click",() => {
count += 1
//Creating Video Element
const addVideo = document.createElement('video')
addVideo.setAttribute('id',`video${count + 1}`)
addVideo.setAttribute('class',`try-${count + 1}`)
// Here I believe this part was my error where in the video is set up yet for the RTCPeerConnection functions.
containers.appendChild(addVideo)
const videoCall = containers.querySelectorAll('video')[count]
// Here I will create RTCPeerConnections and push it in the pcLocals and pcRemotes;
const init_localStreams = new RTCPeerConnection(servers);
const init_remoteStreams = new RTCPeerConnection(servers);
pcLocals.push(init_localStreams)
pcRemotes.push(init_remoteStreams)
console.log(pcLocals)
console.log(pcRemotes)
//Here I'm passing the stream videos in RTCPeer Arrays...
pcRemotes[count - 1].ontrack = (ev) => {
function gotRemoteStream(e,video,idx) {
if (video.srcObject !== e.streams[0]) {
video.srcObject = e.streams[0]
console.log(`pc${idx+1}: received remote stream`);
}
}
gotRemoteStream(ev,videoCall,count - 1)
}
//Here I'm passing the tracks of the video in each locals
localStream.getTracks().forEach((track) =>
{
pcLocals[count - 1].addTrack(track, localStream)
});
function onAddIceCandidateSuccess() {
console.log('AddIceCandidate success.');
}
function onAddIceCandidateError(error) {
console.log(`Failed to add ICE candidate: ${error.toString()}`);
}
function handleCandidate(candidate, dest, prefix, type) {
dest.addIceCandidate(candidate)
.then(onAddIceCandidateSuccess, onAddIceCandidateError);
console.log(`${prefix}New ${type} ICE candidate: ${candidate ? candidate.candidate : '(null)'}`);
}
function iceCallbackRemote(e,local_) {
handleCandidate(e.candidate,local_,`pc${count}: `, 'remote')
}
function iceCallbackLocal(e,remote_) {
handleCandidate(e.candidate,remote_,`pc${count}: `, 'local')
}
pcLocals[count - 1].onicecandidate = (ev) => {
iceCallbackRemote(ev,pcLocals[count - 1])
}
pcRemotes[count - 1].onicecandidate = (ev) => {
iceCallbackLocal(ev,pcRemotes[count - 1])
}
function gotDescriptionRemote(desc) {
pcRemotes[count-1].setLocalDescription(desc);
// console.log(`Answer from pc1Remote\n${desc.sdp}`);
pcLocals[count-1].setRemoteDescription(desc);
}
function gotDescriptionLocal(desc) {
pcLocals[count-1].setLocalDescription(desc);
// console.log(`Answer from pc1Remote\n${desc.sdp}`);
pcRemotes[count-1].setRemoteDescription(desc);
pcRemotes[count-1].createAnswer().then(gotDescriptionRemote,onCreateSessionDescriptionError)
}
function onCreateSessionDescriptionError(error) {
console.log(`Failed to create session description: ${error.toString()}`);
}
pcLocals[count - 1].
createOffer(offerOptions)
.then(gotDescriptionLocal, onCreateSessionDescriptionError)
})
I'm somehow doubt my if my video was not to pass yet before the RTCPeerConnection operations happening..I don't know if where my errors here... I just tried to make a multiple peerconnection that was documentary here at WEBRTC TUTORIAL
I've looked at you codesandbox code and found two issues:
The playback of your created video is never started
Your icecandidates are set on the wrong peerconnection
To adress the first problem, you need to start your playback either with autoplay or by starting it with addVideo.play(). For the autoplay solution, you can simply add:
addVideo.setAttribute("autoplay", true);
addVideo.setAttribute("playsinline", true);
To address the second problem, you need to change the passed peerconnections in the onicecandidate event handlers:
pcLocals[count - 1].onicecandidate = (ev) => {
//old: iceCallbackRemote(ev, pcLocals[count - 1]);
//new:
iceCallbackRemote(ev, pcRemotes[count - 1]);
};
pcRemotes[count - 1].onicecandidate = (ev) => {
//old: iceCallbackRemote(ev, pcRemotes[count - 1]);
//new:
iceCallbackLocal(ev, pcLocals[count - 1]);
};
The ice candidates need to be exchanged, meaning, ice candidates gathered by local must be passed to the remote and vice versa. Before, you added the ice candidates from local to your local connection, thats why it didn't work. This why the connectionState was "connecting", and never changed to "connected", as the connection was never fully connected and was still expecting ice candidate exchange.
I am using twilio TURN server for webRTC peer connecting two browsers located on different sides of the world, still the connection does not open.
Log shows the local and remote descriptions are set on both sides. Audio/video tracks are also pushed and received, but the "onopen" method on either of the data channels are not firing. Below is the code extract.
create offer code
async createOffer(){
this.initiated = true
this.conn = new RTCPeerConnection(this.servers);
if (this.conn){
this.conn.ontrack = e => {
e.streams[0].getTracks().forEach(track => {
this.calleeStream?.addTrack(track);
this.logs.push('received track:' + track.label);
})
}
if (this.callerStream)
{
const s = this.callerStream;
this.callerStream.getTracks().forEach(track =>
{
this.conn?.addTrack(track,s);
this.logs.push('pushed track:' + track.label);
});
}
}
this.channel = this.conn.createDataChannel('channelX');
this.channel.onmessage = e => this.logs.push('received =>'+ e.data);
this.channel.onopen = e => {
this.logs.push('connection OPENED!!!');
this.enabletestmessage = true;
};
this.conn.onicecandidate = async e=> {
if (e.candidate===null && !this.iceCandiSent){
this.iceCandiSent = true;
this.logs.push('new ICE candidate received- reprinting SDP'+JSON.stringify(this.conn?.localDescription));
await this.dataService.createOffer(this.data.callerid,this.data.calleeid,JSON.stringify(this.conn?.localDescription));
this.logs.push('offer set in db');
this.logs.push('waiting for answer...');
}
}
const offer = await this.conn.createOffer();
await this.conn?.setLocalDescription(offer);
this.logs.push('local description (offer) set');
}
create answer code
async createAnswer(offerSDP:string){
this.initiated = true;
this.conn = new RTCPeerConnection(this.servers);
if (this.conn)
{
this.conn.ontrack = e => {
e.streams[0].getTracks().forEach(track => {
this.callerStream?.addTrack(track);
this.logs.push('received track:' + track.label);
})
}
if (this.calleeStream)
{
const s = this.calleeStream;
this.calleeStream.getTracks().forEach(track =>
{
this.conn?.addTrack(track,s);
this.logs.push('pushed track:' + track.label);
});
}
}
await this.conn.setRemoteDescription(JSON.parse(offerSDP));
this.logs.push('remote description (offer) set');
this.conn.onicecandidate = async e => {
if (e.candidate === null && !this.iceCandiSent){
this.iceCandiSent=true;
this.logs.push('new ICE candidate received- reprinting SDP'+JSON.stringify(this.conn?.localDescription));
await this.dataService.updateAnswer(this.data.callerid,this.data.calleeid,JSON.stringify(this.conn?.localDescription));
this.logs.push('answer set in db');
}
}
this.conn.ondatachannel = e => {
this.channel = e.channel;
this.channel.onmessage = e => this.logs.push('received =>'+ e.data);
this.channel.onopen = e => {
this.logs.push('connection RECEIVED!!!');
this.enabletestmessage = true;
};
}
const answer = await this.conn.createAnswer();
await this.conn.setLocalDescription(answer);
this.logs.push('local description (answer) set');
}
server side code for retrieving ice servers from Twillio
const twilio = require('twilio');
const client = twilio(<MY ACCOUNT SID>,<MY AUTH TOKEN>);
const result = await client.tokens.create();
return result.iceServers; //this is set to this.servers in the code above
Everything works when I run on two browser windows in my local machine. However even afer implementing TURN they dont work between browsers in Nepal and USA. The onopen event handlers on data channel does notfire even though local and remote descriptions are set on both sides. What am I missing ?
NOTE: signalling is done inside the onicecandidate event handler ( the line that calls dataService createOffer/updateAnswer methods)
I am writing a cloud function in which i am creating or initializing firebase app in a for loop with new names, my question is that , is it necessary to call app.delete() function on every instance or not?
snapshot.forEach(doc => {
counter++;
// console.log(counter," in first");
// console.log(doc.id, " is the doc ID");
var home = doc.get('home');//e.g: hyperoffice
var switches = doc.get('switches');
var action = doc.get('action');//ON or OFF
var repeat=doc.get('repeat');// true or false
try {
// console.log("home ", home, " switches ", switches);
let defapp = admin.initializeApp({ databaseURL: databaseUrl }, `${home}${counter}`);
var databaseUrl = `https://${home}.firebaseio.com/`;
switches.forEach(s => {
let dbRef = admin.database(defapp).ref(`Controls/${s}`);
dbRef.once("value").then(val => {
var data = val.val();
// console.log("this is data", data);
var payload_on = data.payload_on;
var payload_off = data.payload_off;
let valRef = admin.database(defapp).ref(`Controls/${s}/value`);
if (action === "ON") {
// on the device
return valRef.set(payload_on);
}
else if (action === "OFF") {
//off the device
return valRef.set(payload_off);
} else {
console.log("Undefined action field in firestore");
}
return null;
}).catch(err => {
console.log("Error", err);
});
});
} catch (error) {
console.log("Eroor: ", error);
}
finally {
console.log("in finally block 67");
}
});
Strictly speaking, you don't have to, but you will leak a lot of memory over time, and your function might crash in a future invocation. Your function should always clean up unused memory before it terminates. This means that you should delete any initialized apps, unless you want to use that exact same app instance in a future invocation.
Source: https://github.com/anoek/webrtc-group-chat-example/blob/master/client.html
I'm trying to modify this Webrtc example to add the ability of changing camera (Cross-browser support).
Normal usage works perfectly, after changing camera, failed in renegotiation.
1) Get a list of devices via navigator.mediaDevices.enumerateDevices()
2) Change local_media_stream after getting new stream
local_media_stream.getTracks().forEach(function(track) {
track.stop();
});
local_media_stream = stream;
3) Trigger renegotiation function (Copied from line 132 of Source code)
function renegotiate(){
console.log("Creating RTC offer to ", peer_id);
peer_connection.createOffer(
function (local_description) {
console.log("Local offer description is: ", local_description);
peer_connection.setLocalDescription(local_description,
function() {
signaling_socket.emit('relaySessionDescription',
{'peer_id': peer_id, 'session_description': local_description});
console.log("Offer setLocalDescription succeeded");
},
function() { Alert("Offer setLocalDescription failed!"); }
);
},
function (error) {
console.log("Error sending offer: ", error);
});
};
I believe that my approaches are wrong, but I've tried many different ways found on google to edit the codes for renegotiation, however I'm not familiar to WebRTC and Socket.io, still can't make the thing works.
After changing the camera, the video shown on other participant just became a static image from video last frame.
Can anybody please help to point my mistake? Thanks in advance.
Previously I done it in the following way (an order is important).
Let's say you list all our available devices:
var devicesIds = [];
navigator.mediaDevices.enumerateDevices().then(function(devices) {
devices.forEach(function(device) {
devicesIds.push(device.deviceId);
});
});
And now you want to switch:
1) Stop current tracks
localStream.getTracks().forEach(function(track) {
track.stop();
});
2) Obtain new stream
var constraints = {video: {deviceId: devicesIds[1]}, audio: true};
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
replaceTracks(stream);
}).catch(function(error) {
});
3) Replace tracks:
function replaceTracks(newStream){
detachMediaStream(elementId);
newStream.getTracks().forEach(function(track) {
localStream.addTrack(track);
});
attachMediaStream(elementId, newStream);
// optionally, if you have active peer connections:
_replaceTracksForPeer(peerConnection);
function _replaceTracksForPeer(peer) {
peer.getSenders().map(function(sender) {
sender.replaceTrack(newStream.getTracks().find(function(track) {
return track.kind === sender.track.kind;
}));
});
}
}
function detachMediaStream = function(id) {
var elem = document.getElementById(id);
if (elem) {
elem.pause();
if (typeof elem.srcObject === 'object') {
elem.srcObject = null;
} else {
elem.src = '';
}
}
};
function attachMediaStream = function(id, stream) {
var elem = document.getElementById(id);
if (elem) {
if (typeof elem.srcObject === 'object') {
elem.srcObject = stream;
} else {
elem.src = window.URL.createObjectURL(stream);
}
elem.onloadedmetadata = function(e) {
elem.play();
};
} else {
throw new Error('Unable to attach media stream');
}
};
I'll start by saying I've found several similar issues posted on this site. None of them apply to my situation though.
I have a server and client (as is the norm with node.js/socket.io) and call emit a socket event when a button is pressed. This works fine... Except it seems to emit three times (at least the server runs the function three times). I've been staring at the code for way too long at this point and need another set of eyes.
Hopefully someone has an idea.
client code:
importJS('/js/pages/admin_base.js',function(){
var restartLMC = function(io){
toggleLoad();
var user = localStorage.getItem('User');
io.emit('restart_request',{session: user});
};
AdminIO = new io('http://localhost:26266');
AdminIO.on('restart_success',function(dat){
toggleLoad();
dropInfo(dat);
});
AdminIO.on('sendError',function(dat){
dropInfo(dat,{level: 'error'});
});
AdminIO.on('restart_fail',function(dat){
toggleLoad();
dropInfo(dat,{level: 'error'});
});
$('#restart').on('click',function(){
restartLMC(AdminIO);
});
});
Admin code:
process.stdout.write('\033c');
console.log('\x1b[36m', "Admin server starting...", '\x1b[0m');
var
ini = require('node-ini')
, conf = ini.parseSync('../config.ini')
, CS = require('../lm_modules/CoreSync.js')
, CoreSync = new CS()
, checkSession = function (session, callback) {
var res;
if (!CoreSync) { throw "Fatal error, there is no connection to the Core service!"; }
if (CoreSync.sessions) {
if (CoreSync.sessions[session]) {
res = CoreSync.sessions[session];
callback(res);
}
else {
CoreSync.sync('session', function (err, dat) {
if (CoreSync.sessions[session]) {
res = CoreSync.sessions[session];
callback(res);
} else { res = false; callback(res); }
});
}
} else {
res = false; callback(res);
}
if (res === "undefined") { callback(false); }
}
, runCMD = function(cmd,errCB,callback){
var
command
, args;
if(cmd.cmd){ command = cmd.cmd; } else { command = cmd; }
if(cmd.args){ args = cmd.args; }
const spawn = require('child_process').spawn;
const ex = spawn(command, args);
ex.stdout.on('data', (data) => {
callback(data);
});
ex.stderr.on('data', (data) => {
errCB(data);
});
ex.on('close', (code) => {
});
}
, executeCMD = function(cmd,callback){
const exec = require('child_process').exec
, cdw = (__dirname + '/../');
exec(cmd, {cwd: cdw}, (err, stdout, stderr) => {
if (err) {
callback(err,null);
return;
}
callback(stderr,stdout);
});
}
, io = require('socket.io').listen(26266) // can use up to 26485
console.log('\x1b[32m', "Admin server started.", '\x1b[0m');
console.log("Admin server listening at " + "http://" + conf["Server"]["binding"] + ":26266");
io.on('connection', function (socket) {
socket.on('restart_request', function(req){
console.log('Recieved restart request');
var success = false
, session = JSON.parse(req.session)
, sessionID = session.sessionID;
checkSession(sessionID, function (ses) {
if (ses === false) { console.error('CheckSession failed: No session exists'); return; }
if (ses.user.uuid !== session.uuid) { console.error('CheckSession failed: UUID mismatched'); return; }
if (ses.user.role < conf['Permissions']['lm_restart']){ socket.emit('restart_fail','Insufficient permissions.'); return; }
if(process.platform === 'win32'){
executeCMD('cd',function(err,res){
var errSent = false;
if(err){
console.error(err);
if(!errSent){ socket.emit('sendError','Restart failed'); }
errSent = true;
if(res === null){return;}
}
console.log(res);
socket.emit('restart_success','LM successfully restarted.');
});
}
else if(process.platform === 'linux'){
}
});
});
});
For those of you who may have seen this and found it a curious question/situation... I found two parts to this.
The first part is the $().on binding. For some reason (even though it's by no means called multiple times in the js code) adding unbind() in front of the binding resolved the issue in part... it cut the extra emits down from 3 to two (until I started another server app, then it went back up to three...)
The other part I found was that (for some reason) the socket.io connection is being duplicated as many times as there are socket servers running. More details on this issue here... I believe that once the cause for this is found, my issue will be resolved.