I am trying to put together a little video-chat prototype using WebRTC.
I've been testing this on localhost for the last couple of days (using 2 browser instances) but I simply can't get chrome(V32) to display the remote stream correctly as it is always a black square.
I can see the streams arrive to both peers, get attached but always black.
In firefox (V26) everything works very well.
I am using SignalR as the signaling mechanism and adapter.js for browser interoperability.
This is the code I have for the webrtc module, what can I possibly be doing wrong for chrome to fail?
Thank you very much.
define(['services/logger', 'services/dataservice', 'services/messenger', 'knockout'], function (logger, dataservice, messenger, ko) {
var
webrtc = {
init: init,
call: call
},
_myMediaStream = null,
_myConstraints = null,
_myConnection = null,
_iceServers = [{ url: 'stun:74.125.142.127:19302' }]; // stun.l.google.com - Firefox does not support DNS names.
function init(constraints) {
getUserMedia(constraints, function (stream) {
var videoElement = document.querySelector('#myVideo');
//videoElement.muted = true;
videoElement.controls = true;
_myMediaStream = stream;
_myConstraints = constraints;
attachMediaStream(videoElement, _myMediaStream);
messenger.publish('LocalMediaStreamSet');
}, function (error) {
logger.logError(JSON.stringify(error), null, 'webrtc/init', true);
});
}
function call(user) {
_myConnection = _myConnection || _createConnection();
_myConnection.addStream(_myMediaStream);
_myConnection.createOffer(function (desc) {
_myConnection.setLocalDescription(desc, function () {
dataservice.sendRTCMessage(JSON.stringify({ sdp: desc, origin: '', target: user, constraints: _myConstraints, type:'offer' }));
});
});
}
function _createConnection() {
console.log('creating RTCPeerConnection...');
var connection = new RTCPeerConnection({ iceServers: _iceServers }); // null = no ICE servers
connection.onicecandidate = function (event) {
if (event.candidate) {
dataservice.sendICECandidate(JSON.stringify({ "candidate": event.candidate }))
.then(function () {
console.log('ice candidate sent to remote peer.')
});
}
};
connection.onaddstream = function (event) {
var videoElement = document.querySelector('#theirsVideo');
videoElement.controls = true;
console.log('attaching remote stream...')
attachMediaStream(videoElement, event.stream);
console.log('attaching remote stream done.')
};
connection.onremovestream = function () {
console.log('Remote stream removed.');
};
return connection;
}
function _subscribeToEvents() {
//subscribe to new RTCMessage events
messenger.subscribe(document, 'newRTCMessage', function (e, message) {
var
isConfirmed = true,
connection = _myConnection || _createConnection();
if (message.sdp.type === 'offer') {
//need confirmation to accept the call
isConfirmed = confirm("Incoming call from " + message.origin + ", accept?");
}
if (message.sdp && isConfirmed) {
connection.setRemoteDescription(new RTCSessionDescription(message.sdp), function () {
if (connection.remoteDescription.type === 'offer') {
//subscrive to localstream when ready (is setted on the init call below)
messenger.subscribe(document, 'LocalMediaStreamSet', function (e) {
console.log('received offer, sending answer...');
connection.addStream(_myMediaStream);
connection.createAnswer(function (desc) {
connection.setLocalDescription(desc, function () {
dataservice.sendRTCMessage(JSON.stringify({ sdp: connection.localDescription, origin: '', target: message.origin, callId: '', type: 'answer' }));
});
});
});
init(message.constraints);
} else if (connection.remoteDescription.type === 'answer') {
console.log('got an answer');
}
});
} else if (message.candidate) {
console.log('adding ice candidate from remote peer...');
connection.addIceCandidate(new RTCIceCandidate(message.candidate));
}
_myConnection = connection;
});
}
_subscribeToEvents();
return webrtc;
});
I finally made it work!!
It had to do with the fact that on connection.onicecandidate event I should be calling the dataservice.sendRTCMessage() function instead of the dataservice.sendICECandidate() one that was failing on my controller.
Both peers were not attaching remote ice candidates and thus the stream could not be shared.
Thanks anyways!
Related
I am developing a simple webRTC application, using my own server for signaling.
the javascript code is as follow (I have removed the signaling process and unnecessary logic):
const configuration = {
iceServers: [
{
urls: [
"stun:stun1.l.google.com:19302",
"stun:stun2.l.google.com:19302",
],
},
],
iceCandidatePoolSize: 10,
};
const callerCandidatesString = "callerCandidates";
const calleeCandidatesString = "calleeCandidates";
var received_offer = null;
var offer = null;
var answer = null;
var peerConnection = null;
let localStream = null;
let remoteStream = null;
var constraints = {
optional: [],
mandatory: {
OfferToReceiveAudio: true,
OfferToReceiveVideo: true
}
}
async function startMedia(e) {
const localStream = await navigator.mediaDevices.getUserMedia({video: true, audio: true});
document.getElementById("video1").srcObject = localStream;
remoteStream = new MediaStream();
document.getElementById("video2").srcObject = remoteStream;
if (I am the caller) {
create_the_offer();
}
if (I am the callee) {
get_the_offer();
}
}
async function create_the_offer() {
peerConnection = new RTCPeerConnection(configuration);
registerPeerConnectionListeners();
localStream.getTracks().forEach(track => {
peerConnection.addTrack(track, localStream);
});
var offer = await peerConnection.createOffer(constraints);
peerConnection.setLocalDescription(offer);
peerConnection.onicecandidate = function(candidate) {
if (candidate.candidate == null) {
//save the offer in the server
--> offer: JSON.stringify(peerConnection.localDescription)},
}
}
check_if_there_is_an_answer();
}
async function get_the_offer() {
// --> retrieve the offer from the server, then
create_answer(offer_from_server);
}
async function create_answer(received_offer) {
console.log("Create PeerConnection with configuration: ", configuration);
peerConnection = new RTCPeerConnection(configuration);
registerPeerConnectionListeners();
localStream.getTracks().forEach(track => {
peerConnection.addTrack(track, localStream);
});
console.log('received offer:' + received_offer)
my_offer = new RTCSessionDescription(JSON.parse(received_offer));
peerConnection.setRemoteDescription(my_offer);
// collectIceCandidates(peerConnection, calleeCandidatesString, callerCandidatesString);
peerConnection.addEventListener("track", event => {
console.log("Got remote track:", event.streams[0]);
event.streams[0].getTracks().forEach(track => {
console.log("Add a track to the remoteStream:", track);
remoteStream.addTrack(track);
});
});
const answer = await peerConnection.createAnswer(constraints);
console.log("Created answer:", answer);
await peerConnection.setLocalDescription(answer);
peerConnection.onicecandidate = function (e) {
if (e.candidate == null) {
// --> send the answer to the server
}
}
function check_if_there_is_an_answer() {
// retrieve answer from server. this function is executed several times until the answer is received.
// when there is an aswer:
start_remote_connection(answer);
}
async function start_remote_connection(passed_answer) {
my_answer = new RTCSessionDescription(JSON.parse(passed_answer));
peerConnection.setRemoteDescription(my_answer);
peerConnection.addEventListener("track", event => {
console.log("Got remote track:", event.streams[0]);
event.streams[0].getTracks().forEach(track => {
console.log("Add a track to the remoteStream:", track);
remoteStream.addTrack(track);
});
console.log("stream remoto: " + JSON.stringify(remoteStream.getVideoTracks()));
});
document.getElementById("video1").srcObject = localStream;
document.getElementById("video2").srcObject = remoteStream;
}
async function hangUp(e) {
const tracks = document.getElementById("video1").srcObject.getTracks();
tracks.forEach(track => {
track.stop();
});
remoteStream.getTracks().forEach(track => track.stop());
peerConnection.close();
document.getElementById("video1").srcObject = null;
document.getElementById("video2").srcObject = null;
}
// collect ICE Candidates function below
async function collectIceCandidates(peerConnection, localName, remoteName) {
const candidatesCollection = null;
peerConnection.addEventListener("icecandidate", event => {
if (event.candidate) {
const json = event.candidate.toJSON();
candidatesCollection.add(json);
}
});
}
// collect ICE Candidates function above
function registerPeerConnectionListeners() {
peerConnection.addEventListener("icegatheringstatechange", () => {
console.log(
`ICE gathering state changed: ${peerConnection.iceGatheringState}`);
});
peerConnection.addEventListener("connectionstatechange", () => {
console.log(`Connection state change: ${peerConnection.connectionState}`);
});
peerConnection.addEventListener("signalingstatechange", () => {
console.log(`Signaling state change: ${peerConnection.signalingState}`);
});
peerConnection.addEventListener("iceconnectionstatechange ", () => {
console.log(
`ICE connection state change: ${peerConnection.iceConnectionState}`);
});
}
window.onload = startMedia();
If the caller uses chrome and the callee uses FireFox (on localhost, same PC) the code works fine and both users can share their screen.
output with chrome
If the caller uses FireFox and the callee uses Chrome (still on localhost) the code still works fine, but the connection is not established and users cannot see the screen of the other person. I get no error in the console.
output with FireFox
In particular, with FF I am not getting "connection state change: connecting" and then "connection state change: connected".
My guess is that FF and chrome manage the async/await differently, and somehow with FF some values are not ready when actually needed, but cannot figure out why ...
with safari (MacOS) it does not work!!
I then tried it between the computer (with chrome) and an Android phone (chrome browser). It worked the first time I test it, and then never again :(
Does anyone of you has a clue ?
I'm currently working on a webrtc project that was written before from another developer.
While I'm currently new to webrtc and I tried hard to make it work after deleting obsolete functions and other things, now can I identify what is wrong here in my steps?
I'm following steps in here
my code here is about trigger .call button
$(dod)
.find(".call")
and I run throw signaling
wbsc.emit("SEND_EVENT_EMIT_CALL_AUDIO", {
data: { type: "login", id: id },
});
$(dod).hide();
//call*donecallProccess 1
setTimeout(() => {
wbsc.emit("SEND_EVENT_EMIT_CALL_AUDIO", {
data: { type: "doneoif", id: id },
});
}, 2e3);
that can trigger and process here
case "donecall":
call(data.id);
break;
case "showcall":
handleLogin(data.success, data.id);
break;
case "offercall":
handleOffer(data.offer, data.name);
break;
case "answercall":
handleAnswer(data.answer);
break;
case "candidatecall":
handleCandidate(data.candidate);
break;
case "leavecall":
handleLeave();
then this code run one after each handle login and getUserMediaSuccess
gather permission from media stream and create a new RTCPeerConnection(servers)
get tracks from my streams with addtrack if I implement it correctly
and if ontrack happened can I collect streaming to add to remote peer like this way or should just add
yourConn.ontrack = (event) => {
if (event.candidate !== null) {
remoteVideo.srcObject = event.streams[0];
} else {
console.log("there is an error with on trackevent", event);
}
};
complete code for previous handlelogin and call is
let handleLogin = async (success) => {
try {
if (success) {
localVideo = document.getElementById("wbrtclocal");
remoteVideo = document.getElementById("wbrtcremote");
var getUserMedia = navigator.mediaDevices.getUserMedia|| navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ;
if (getUserMedia) {
getUserMedia({
audio: { noiseSuppression: false },
})
.then(getUserMediaSuccess)
.catch(errorHandler);
} else {
alert("Your browser does not support getUserMedia API");
}
} else {
alert("Ooops...try a different username");
}
} catch (err) {
errorHandler(error);
}
};
let getUserMediaSuccess = async (streams) => {
try {
yourConn = new RTCPeerConnection(servers);
if(streams){
localStream = streams;
localVideo.srcObject = streams;
streams.getTracks().forEach((track) => yourConn.addTrack(track, streams));
}
yourConn.onicecandidate = (event) => {
if (event.candidate) {
wbsc.emit("SEND_EVENT_EMIT_CALL_AUDIO", {
data: {
type: "candidate",
candidate: event.candidate,
id: connectedUser,
},
});
}
console.log("nwe ice candidate is", event.candidate);
console.log("nwe stream is", streams);
};
remoteStream = new MediaStream();
remoteVideo.srcObject = remoteStream;
yourConn.ontrack = (event) => {
if (event.candidate !== null) {
event.streams[0].getTracks().forEach((track) => {
remoteStream.addTrack(track);
});
} else {
console.log("there is an error with on trackevent", event);
}
};
} catch (err) {
errorHandler(error);
}
console.log("stream is", streams);
};
call function
async function call(id) {
$("#videoCall").show();
if (id.length > 0) {
connectedUser = id;
try {
RTCPeerConnection.createOffer().
offer.
await yourConn
.createOffer()
.then((offer) => successCallback)
.catch((e) => {
fl(e);
});
let successCallback = async (offer) => {
try {
yourConn
.setLocalDescription(offer)
.then(
wbsc.emit("SEND_EVENT_EMIT_CALL_AUDIO", {
data: { type: "offer", offer: offer, id: connectedUser },
})
)
.catch((e) => {
fl(e);
});
} catch (e) {
fl(e);
}
};
} catch (e) {
fl(e);
}
const user = U_CASH[id];
if (user) {
$("#videoCall")
.find(".u-pic")
.css("background-image", "url(" + removegifpic(user.pic + ")"));
$("#videoCall").find(".u-topic").text(user.topic);
}
$(".statecall").text("جاري الإتصال");
hl($(".statecall"), "warning");
} else {
alert("username can't be blank!");
}
console.log("connectedUser", id);
console.log("offer", offer);
console.log();
console.log();
console.log();
}
and this about each one for those
[handleOffer , handleAnswer , handleCandidate , handleLeave]
let handleOffer = async (offer, name) => {
$("#callvideonot").show();
const user = U_CASH[name];
if (user) {
$("#callvideonot")
.find(".u-pic")
.css("background-image", "url(" + removegifpic(user.pic + ")"));
$("#callvideonot").find(".u-topic").text(user.topic);
}
$(".callvideoaccept").on("click", async () => {
connectedUser = name;
await yourConn.setRemoteDescription(offer);
yourConn
.createAnswer()
.then((answer) => t.setLocalDescription(answer))
.then(() => {
wbsc.emit("SEND_EVENT_EMIT_CALL_AUDIO", {
data: { type: "answer", answer: answer, id: connectedUser },
});
})
.catch(fl);
const user = U_CASH[name];
if (user) {
$(".statecall").text("متصل");
hl($(".statecall"), "success");
$("#videoCall")
.find(".u-pic")
.css("background-image", "url(" + removegifpic(user.pic + ")"));
$("#videoCall").find(".u-topic").text(user.topic);
}
$("#callvideonot").hide();
$("#videoCall").show();
});
$(".callvideodeny").on("click", function () {
wbsc.emit("SEND_EVENT_EMIT_CALL_AUDIO", {
data: { type: "leave", id: name },
});
});
};
let handleAnswer = async (answer) => {
try {
$(".statecall").text("متصل");
hl($(".statecall"), "success");
//here we delete new RTCSessionDescription because constructor is deprecated.
await yourConn.setRemoteDescription(answer);
} catch (e) {
fl(e);
}
};
let handleCandidate = async (candidate) => {
try {
var NewlyIceCandidate = new RTCIceCandidate(candidate)
.setRemoteDescription().
await yourConn.addIceCandidate(NewlyIceCandidate);
} catch (e) {
fl(e);
}
};
function handleLeave() {
$("#callvideonot").hide();
$(".statecall").text("رفض");
hl($(".statecall"), "danger");
$(".vloumemic").removeClass("fa-volume-off");
$(".vloumemic").addClass("fa-volume-up");
$(".mutemic").removeClass("fa-microphone-slash");
$(".mutemic").addClass("fa-microphone");
setTimeout(() => {
$("#videoCall").hide();
}, 1e3);
if (localStream) {
localStream.getTracks().forEach((e) => e.stop());
}
if (connectedUser) {
connectedUser = null;
}
remoteVideo.src = null;
if (yourConn) {
yourConn.close();
yourConn.onicecandidate = null;
yourConn.ontrack = null;
localStream = null;
}
}
in here here number 7 instruction they said should I add Wait for an incoming remote SDP description from the signaling service and set it using RTCPeerConnection.setRemoteDescription(). as the caller where can I add it? in handleCandidate function?
and in the callee side number 1 instruction the said that I should
Create a new RTCPeerConnection instance with the appropriate ICE configuration.
can I reuse
yourConn = new RTCPeerConnection(servers);
or should instantiate a new one to prevent conflict in website server process
as a not yourConn it's global value and in top level of this file and reuse it over all
the connection, is peers 2 or the callee should have another new RTCPeerConnection?
and for remoteVideo.srcObject = remoteStream; the remoteStream value is global and I overwrite it. can I here add new media stream or just it's good to dealing with the present one which is remoteVideo element?
// remoteStream = new MediaStream();
remoteVideo.srcObject = remoteStream;
the issue that was here is to split the RTCPeerConnection object and just create new one for each peer local and remote that what i did and it working corectly after i remove addtrack completly and replace it with addtranceiver and gettranceiver
u can find it here from previous issue's answer and i fix it here
and no need to overwrite or reset the srcObect value. The track transition on receiver side in the same MediaStream should be "seamless" RTCRtpSender.replaceTrack
This allows you to seamlessly change which track is being sent without having to renegotiate at the expense of another offer/answer cycle
this is my first post in this forum. So please forgive me the misstakes.
I want to write a NodeJS server which runs a WebSocket Server (npm ws module).
The NodeJS server contains also a Class Obj which i want to modify a funciton afterwards over the Websocket server.
My Problem is the modified functjion cant acces global variables.
Can someone help if there is a solution for this problem or why this happes because if you do this without the Websocket it works.
Here is the code:
Server code:
const WebSocket = require('ws');
// WebSocket Server
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
try {
message = JSON.parse(message);
if (message.type == "handler") {
handler.modify(message.data);
console.log("modifyed");
}
if (message.type == "func") {
handler.modify_func(message.data);
console.log("modifyed");
}
if (message.type == "run") {
eval(message.data);
}
}
catch (error) {
}
});
});
// Modifying class
class Handler {
constructor() {
this.functions = [];
}
modify(data) {
let temp_class = new Function('return ' + data)();
temp_class.functions.forEach(element => {
if (this.functions.indexOf(element) == -1) {
this.functions.push(element)
}
this[element] = temp_class[element];
});
}
modify_func(data) {
let temp_func = new Function('return ' + data)();
this[temp_func.name] = temp_func;
}
test_func_from_orginal() {
console.log("test_func_from_orginal says:");
console.log(test_val);
}
}
var test_val = "this is the global variable";
var handler = new Handler();
Client code:
const WebSocket = require('ws');
//WebSocket Client
var ws = new WebSocket('ws://localhost:8080');
ws.on('open', function open(event) {
// ws.send(JSON.stringify({ type: "handler", data: Handler.toString() }))
ws.send(JSON.stringify({ type: "func", data: test_func_from_func.toString() }))
console.log("open")
});
//Class Module
class Handler {
static get functions() {
return ["test"];
}
static test_func_from_class() {
console.log("test_func_from_class sayes:")
console.log(test_val);
}
}
function test_func_from_func() {
console.log("test_func_from_func sayes:")
console.log(test_val);
}
setTimeout(function () { ws.send(JSON.stringify({ type: "run", data: 'handler.test_func_from_orginal()' })) }, 1000);
// setTimeout(function () { ws.send(JSON.stringify({ type: "run", data: 'handler.test_func_from_class()' })) }, 1000);
setTimeout(function () { ws.send(JSON.stringify({ type: "run", data: 'handler.test_func_from_func()' })) }, 1000);
Ok, so this is what it's all about - a simple mistake. If you cut out all the websocket stuff (which is not really relevant here, as strings, and not contexts, got passed from and back anyway), you'll get this:
class ServerHandler {
constructor() {
this.functions = [];
}
modify(data) {
let temp_class = new Function('return ' + data)();
// not sure why not just `eval(data)` btw
temp_class.functions.forEach(funcName => {
if (this.functions.indexOf(funcName) == -1) {
this.functions.push(funcName)
}
this[funcName] = temp_class[funcName];
});
}
}
class ClientHandler {
static get functions() {
return ["test_func_from_class"];
// not "test" as in your example
// you actually don't even need this registry:
// all the static methods can be collected in runtime
}
static test_func_from_class() {
console.log("test_func_from_class sayes:")
console.log(test_val);
}
}
var test_val = 42;
var handler = new ServerHandler();
handler.modify(ClientHandler.toString());
eval(`handler.test_func_from_class()`); // 42
This all works fine, as there's no longer a mismatch between a name of method stored in static get functions ("test") and actual name of that method ("test_func_from_class"). The trick is that all the static functions created along with that temporary class are scoped the same way any other entity created in ServerHandler; that's how they 'see' that test_val.
But 'works' here is about mere possibility of this approach from technical perspective, and not about feasibility. Both new Function and eval with arbitrary input are very dangerous security holes - and they're left wide open here.
I found now a solution for my problem.
Server Code
const WebSocket = require('ws');
// WebSocket Server
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
message = JSON.parse(message);
try {
if (message.type == "run") {
eval(message.data);
}
if (message.type == "obj_handler") {
handler.modify(JSON.parse(message.data));
}
}
catch (error) {
console.log(error);
}
// console.log('received: %s', message);
});
ws.send('something');
});
class ServerHandler {
constructor() {
this.data = "hi";
}
modify(data) {
for (const func in data) {
this[func] = eval(data[func]);
}
}
}
var test_val = 42;
var handler = new ServerHandler();
Client Code:
const WebSocket = require('ws');
//WebSocket Client
try {
var ws = new WebSocket('ws://localhost:8080');
ws.on('open', function open(event) {
ws.send(JSON.stringify({ type: "obj_handler", data: update.convert() }))
});
}
catch (error) {
}
// Needed Update with 2 new Functions
update = {
func_test_global: () => {
console.log(test_val);
},
func_test_this: _ => {
console.log(this.data);
},
convert: function () {
let new_update = {};
for (const func in this) {
if (func != "convert")
new_update[func] = "" + this[func];
}
return JSON.stringify(new_update)
}
}
// setTimeout(function () { ws.send(JSON.stringify({ type: "run", data: 'handler.func_test_global()' })) }, 1000);
// setTimeout(function () { ws.send(JSON.stringify({ type: "run", data: 'handler.func_test_this()' })) }, 1000);
As per our web RTC requirements, there are two different client
Player (Players the screen shared by capture client)
Capture (Share Screen)
The two web clients communicate and exchange offers and ICE candidates using WebSocket.
In Chrome [Version 84.0.4147.105 (Official Build) (64-bit)]
There is no error in the Player and Capture javascript console in chrome.
But if we check chrome://webrtc-internals/ we can see the following event and transmission graph:
Player
Capture
Here the I can see the video streaming is transmission but not playing in payer end and an ICE Candidate error in showing up int he events log. Is that is the problem the video stream is not working in the payer end?
Firefox (v79.0)
Showing errors in the console:
DOMException: No remoteDescription.
In player.js line no: 33.
Any Idea why two different browsers have different errors?
Player.js
(function(){
var localVideo, remoteVideo, localConnection, remoteConnection;
const MESSAGE_TYPE = {
SDP: 'SDP',
CANDIDATE_LOCAL: 'LOCAL_CANDIDATE',
CANDIDATE_REMOTE: 'REMOTE_CANDIDATE'
};
const signaling = new WebSocket('ws://127.0.0.1:1337');
var configuration = {
offerToReceiveAudio: true,
offerToReceiveVideo: true
}
remoteConnection = new RTCPeerConnection({configuration: configuration, iceServers: [{ urls: 'stun:aalimoshaver.com:3478' }]});
remoteConnection.onicecandidate = function(e) { !e.candidate
|| signaling.send(JSON.stringify({message_type:MESSAGE_TYPE.CANDIDATE_REMOTE, content: e.candidate.toJSON()}));
}
remoteConnection.ontrack = function (e) {
const remoteVideo = document.getElementById('remote-view');
if (!remoteVideo.srcObject) {
remoteVideo.srcObject = e.streams[0];
}
};
signaling.onmessage = function (message){
const data = JSON.parse(message.data);
const message_type = data.message_type;
const content = data.content;
try {
if (message_type === MESSAGE_TYPE.CANDIDATE_LOCAL && content) {
remoteConnection.addIceCandidate(content)
.catch(function (e) {
console.error(e)
});
}else if (message_type === MESSAGE_TYPE.SDP && content) {
if (content.type === 'offer') {
remoteConnection.setRemoteDescription(content);
remoteConnection.createAnswer()
.then(function(answer){
remoteConnection.setLocalDescription(answer);
signaling.send(JSON.stringify({
message_type: MESSAGE_TYPE.SDP,
content: answer
}));
});
} else {
console.log('Unsupported SDP type.');
}
}
} catch (err) {
console.error(err);
}
};
})()
Capture.js
/**
* Created by Sowvik Roy on 30-07-2020.
*/
(function () {
var localVideo, remoteVideo, localConnection, remoteConnection;
const MESSAGE_TYPE = {
SDP_LOCAL: 'SDP',
CANDIDATE_LOCAL: 'LOCAL_CANDIDATE',
CANDIDATE_REMOTE: 'REMOTE_CANDIDATE'
};
var configuration = {
offerToReceiveAudio: true,
offerToReceiveVideo: true
};
const signaling = new WebSocket('ws://127.0.0.1:1337');
signaling.onmessage = function (message){
const data = JSON.parse(message.data);
const message_type = data.message_type;
const content = data.content;
try {
if (message_type === MESSAGE_TYPE.CANDIDATE_REMOTE && content) {
localConnection.addIceCandidate(content)
.catch(function (e) {
console.error(e)
});
} else if (message_type === MESSAGE_TYPE.SDP_LOCAL) {
if (content.type === 'answer') {
localConnection.setRemoteDescription(content);
} else {
console.log('Unsupported SDP type.');
}
}
} catch (err) {
console.error(err);
}
};
document.addEventListener('click', function (event) {
if (event.target.id === 'start') {
startChat();
localVideo = document.getElementById('self-view');
remoteVideo = document.getElementById('remote-view');
}
});
function startConnection(){
localConnection = new RTCPeerConnection({configuration: configuration, iceServers: [{ urls: 'stun:aalimoshaver.com:3478' }]});
localConnection.onicecandidate = function (e) {
!e.candidate
|| signaling.send(JSON.stringify({message_type:MESSAGE_TYPE.CANDIDATE_LOCAL, content: e.candidate.toJSON()}));
};
localConnection.createOffer()
.then(function (offer) {
if(offer){
localConnection.setLocalDescription(offer);
signaling.send(JSON.stringify({message_type:MESSAGE_TYPE.SDP_LOCAL, content: localConnection.localDescription}));
if (navigator.getDisplayMedia) {
navigator.getDisplayMedia({video: true}).then(onCaptureSuccess);
} else if (navigator.mediaDevices.getDisplayMedia) {
navigator.mediaDevices.getDisplayMedia({video: true}).then(onCaptureSuccess);
} else {
navigator.mediaDevices.getUserMedia({video: {mediaSource: 'screen'}}).then(onCaptureSuccess);
}
}
else{
console.error("RTC offer is null");
}
})
.catch(function (e) {
console.error(e)
});
}
function onCaptureSuccess(stream){
localVideo.srcObject = stream;
stream.getTracks().forEach(
function (track) {
localConnection.addTrack(
track,
stream
);
}
);
}
function startChat() {
if (navigator.getDisplayMedia) {
navigator.getDisplayMedia({video: true}).then(onMediaSuccess);
} else if (navigator.mediaDevices.getDisplayMedia) {
navigator.mediaDevices.getDisplayMedia({video: true}).then(onMediaSuccess);
} else {
navigator.mediaDevices.getUserMedia({video: {mediaSource: 'screen'}}).then(onMediaSuccess);
}
}
function onMediaSuccess(stream) {
localVideo.srcObject = stream;
// Set up the ICE candidates for the two peers
localConnection = new RTCPeerConnection({configuration: configuration, iceServers: [{ urls: 'stun:stun.xten.com:19302' }]});
localConnection.onicecandidate = function (e) {
!e.candidate
|| signaling.send(JSON.stringify({message_type:MESSAGE_TYPE.CANDIDATE_LOCAL, content: e.candidate.toJSON()}));
};
stream.getTracks().forEach(
function (track) {
localConnection.addTrack(
track,
stream
);
}
);
localConnection.createOffer()
.then(function (offer) {
if(offer){
localConnection.setLocalDescription(offer);
signaling.send(JSON.stringify({message_type:MESSAGE_TYPE.SDP_LOCAL, content: localConnection.localDescription}));
}
else{
console.error("RTC offer is null");
}
})
.catch(function (e) {
console.error(e)
});
}
})();
Can anybody explain or identify a loophole in the code? Please let me know if you need additional info.
I am currently figuring out how to configure webRTC for my test application. I am able to get the user media API part of webrtc to work but the ICE config is what causes me issues. I have tried to look here on stackoverflow and it seems like noone else have had a similar error returned from the client. I am testing it on a live server where the inital communcation is through WSS. I have left out the markup and websocket config because it's irrelevant.
let myPeerConnection;
/* step 1: get users media stream inputs */
function getUserMediaClient(type = ""){
let mediaConstraints = {video:true, audio:true};
createPeerConnection();
navigator.mediaDevices.getUserMedia(mediaConstraints)
.then(function(localStream) {
document.getElementById("myVideo").srcObject = localStream;
document.getElementById("myVideo").onloadedmetadata = function(){
document.getElementById("myVideo").play();
}
localStream.getTracks().forEach(track => myPeerConnection.addTrack(track, localStream));
})
.catch(handleGetUserMediaError);
}
getUserMediaClient();
/*
preparation for step 2:
get users Peer connection information when the user calls createPeerCandidate()
to send the offer or handleVideoOfferMsg() to send the answer
*/
function createPeerConnection(){
myPeerConnection = new RTCPeerConnection(
{
iceServers: [
{
urls: [
'stun:stun.l.google.com:19302',
'stun:stun1.l.google.com:19302'
]
}/*,
{
urls: 'turn:192.158.29.39:3478?transport=tcp',
credential: 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
username: '28224511:1379330808'
}*/
]
}
);
return myPeerConnection;
}
/*
step 2:
send information to opposite part through websocket with SDP info
*/
function handleNegotiationNeededEvent(myUsername = "", targetUsername = "") {
myPeerConnection.createOffer().then(function(offer) {
return myPeerConnection.setLocalDescription(offer);
})
.then(function() {
sendToServer({
name: myUsername,
target: targetUsername,
type: "video-offer",
sdp: myPeerConnection.localDescription
});
})
.catch(reportError);
}
/*
step 3:
"if" answering user accepts - load tracks to stream and respond with SDP config info
*/
function handleVideoOfferMsg(msg) {
let mediaConstraints = {video:true, audio:true};
var localStream = null;
targetUsername = msg.name;
let myUsername = document.getElementById("user1").value;
createPeerConnection();
var desc = new RTCSessionDescription(msg.sdp);
myPeerConnection.setRemoteDescription(desc).then(function () {
return navigator.mediaDevices.getUserMedia(mediaConstraints);
})
.then(function(stream) {
localStream = stream;
document.getElementById("myVideo").srcObject = localStream;
localStream.getTracks().forEach(track => myPeerConnection.addTrack(track, localStream));
})
.then(function() {
return myPeerConnection.createAnswer();
})
.then(function(answer) {
return myPeerConnection.setLocalDescription(answer);
})
.then(function() {
var msg = {
name: myUsername,
target: targetUsername,
type: "video-answer",
sdp: myPeerConnection.localDescription
};
sendToServer(msg);
})
.catch(handleGetUserMediaError);
}
/*
step 4:
when both users have exchanged information - the ice processing can begin
the user that initially sent the request can now reply with a communication method
*/
var candidateData = null;
function handleICECandidateEvent(event) {
if ((event.sdp)||(event.candidate)){
if (event.candidate){
candidateData = event.candidate;
} else if (event.sdp) {
candidateData = event.sdp;
}
sendToServer({
type: "new-ice-candidate",
target: event.target,
candidate: candidateData
});
}
}
///////////////////// non functional part under ////////////////////////////
function handleNewICECandidateMsg(msg) {
candidateData = msg.candidate;
myPeerConnection.addIceCandidate(new RTCIceCandidate({sdpMLineIndex:1,candidate: candidateData})).catch(e => {
console.log("Failure during addIceCandidate(): " + JSON.stringify(e));
});
////////////// non functional part above ///////////////
console.log("MSG: " + JSON.stringify(msg));
}
function handleGetUserMediaError(e){
//removed for simplicity
}
//wss connection estabilshment from client removed
wss.onmessage = function(e){
if (type == "video-offer" && document.getElementById("user1").value == target){
// create counteroffer
handleVideoOfferMsg(data);
} else if (type == "video-answer"){
handleICECandidateEvent(data);
} else if (type == "new-ice-candidate"){
handleNewICECandidateMsg(data);
}
}
Most of the code is from MDN's tutorial here: https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling
The issue is within the handleNewICECandidateMsg() function (or so i think). Because when i enter sdpMLineIndex and sdpMid i get an empty json string and when i leave it with only the candidates sdp info it throws a typeerror saying it needs either sdpMid or sdpMLineIndex.
Any thoughts, links. Anything is appreciated!