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.
Related
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
I want to add notifications to an application I've developed.
Unfortunately, Deno has removed the ws package.(https://deno.land/std#0.110.0/ws/mod.ts)
That's why I'm using the websocket inside the denon itself. Since it doesn't have many functions, I have to add some things myself.
For example, sending all messages to open clients.
What I want to do is when the pdf is created, a (data, message) comes from the socket and update the notifications on the page according to the incoming data.
I keep all open clients in a Map. and when the pdf is created, I return this Map and send it to all sockets (data, message).
However, this works for one time.
server conf...
import {
path,
paths,
ctid,
} from "../deps.ts";
const users = new Map();
const sockets = new Map()
const userArr = [];
export const startNotif = (socket,req) => {
const claims = req.get("claims");
const org = req.get("org");
claims.org = org;
console.log("connected")
users.set(claims.sub, {"username":claims.sub,"socket":socket})
users.forEach((user)=>{
if(userArr.length === 0){
userArr.push(user)
}
else if(userArr.every((w)=> w.username !== user.username) )
userArr.push(user)
})
sockets.set(org, userArr)
function broadcastMessage(message) {
sockets.get(org).map((u)=>{
console.log(u.socket.readyState)
u.socket.send(message)
})
}
if (socket.readyState === 3) {
sockets.delete(uid)
return
}
const init = (msg) => {
socket.send(
JSON.stringify({
status: "creating",
})
);
};
const ondata = async (msg) => {
const upfilepath = path.join(paths.work, `CT_${msg.sid}_report.pdf`);
try {
const s=await Deno.readTextFile(upfilepath);
if(s){
socket.send(
JSON.stringify({
status: "end",
})
);
} else {
socket.send(
JSON.stringify({
status: "creating",
})
);
}
} catch(e) {
if(e instanceof Deno.errors.NotFound)
console.error('file does not exists');
}
};
const end = () => {
try {
const endTime = Date.now()
const msg = "Your PDF has been created"
const id = ctid(12) // random id create
broadcastMessage(
JSON.stringify({
id: id,
date: endTime,
status: "done",
message: msg,
read: 'negative',
action: 'pdf'
})
);
} catch (e) {
console.log(400, "Cannot send.", e);
}
}
socket.onmessage = async (e) => {
const cmd = JSON.parse(e.data);
if(cmd.bid === 'start'){
await init(cmd)
}
if(!cmd.bid && cmd.sid){
await ondata(cmd)
}
if(cmd.bid === 'end'){
await end();
}
}
socket.onerror = (e) => {
console.log(e);
};
}
client conf...
export const webSocketHandler = (request) =>
new Promise((res, rej) => {
let url;
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
url = `http://localhost:8080/api/notifications/ws`.replace('http', 'ws');
} else {
url = `${window.location.origin}/api/notifications/ws`.replace('http', 'ws');
}
const token = JSON.parse(sessionStorage.getItem('token'));
const orgname = localStorage.getItem('orgname');
const protocol = `${token}_org_${orgname}`;
const socket = new WebSocket(url, protocol);
const response = Object.create({});
socket.onopen = function () {
socket.send(
JSON.stringify({
bid: 'start',
})
);
};
socket.onmessage = function (event) {
response.data = JSON.parse(event.data);
if (response.data.status === 'creating') {
socket.send(
JSON.stringify({
sid: request.sid,
})
);
} else if (response.data.status === 'end') {
socket.send(
JSON.stringify({
bid: 'end',
})
);
} else if (response.data.status === 'done') {
try {
res(response);
} catch (err) {
rej(err);
}
}
};
socket.onclose = function (event) {
response.state = event.returnValue;
};
socket.onerror = function (error) {
rej(error);
};
});
onclick function of button I use in component...
const donwloadReport = async (type) => {
const query = `?sid=${sid}&reportType=${type}`;
const fileName = `CT_${sid}_report.${type}`;
try {
type === 'pdf' && setLoading(true);
const response = await getScanReportAction(query);
const request = {
sid,
};
webSocketHandler(request)
.then((data) => {
console.log(data);
dispatch({
type: 'update',
data: {
id: data.data.id,
date: data.data.date,
message: data.data.message,
action: data.data.action,
read: data.data.read,
},
});
})
.catch((err) => {
console.log(err);
});
if (type === 'html') {
downloadText(response.data, fileName);
} else {
const blobUrl = await readStream(response.data);
setLoading(false);
downloadURL(blobUrl, fileName);
}
} catch (err) {
displayMessage(err.message);
}
};
Everything works perfectly the first time. When I press the download button for the pdf, the socket works, then a data is returned and I update the notification count with the context I applied according to this data.
Later I realized that this works in a single tab. When I open a new client in the side tab, my notification count does not increase. For this, I wanted to keep all sockets in Map and return them all and send a message to each socket separately. But in this case, when I press the download button for the second time, no data comes from the socket.
Actually, I think that I should do the socket initialization process on the client in the context. When you do this, it starts the socket 2 times in a meaningless way.
In summary, consider an application with organizations and users belonging to those organizations. If the clients of A, B, C users belonging to X organization are open at the same time and user A pressed a pdf download button, I want A, B, C users to be notified when the pdf is downloaded.
I would be very grateful if someone could show me a way around this issue.
Have you looked at the BroadcastChannel API? Maybe that could solve your issue. See for example:
Deno specific: https://medium.com/deno-the-complete-reference/broadcast-channel-in-deno-f76a0b8893f5
Web/Browser API: https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API
I have a very simple code for video calling using WebRTC. The entire system working different for different browsers.
Capture Browser
Player Browser
Working
Chrome
Firefox
✔
Chrome
Chrome
X
Firefox
Chrome
X
Firefox
Firefox
✔
The capture code is
JS:
(function () {
var localVideo, localConnection;
const signaling = new WebSocket('wss://crs4kx11s1/websockets');
signaling.onmessage = function (message) {
var data = JSON.parse(message.data);
if (data.sdp) {
var answerSDP = data.sdp;
if (answerSDP.type == "answer") {
localConnection.setRemoteDescription(answerSDP);
}
}
if (data.candidate && data.candidateType == "answerClient") {
localConnection.addIceCandidate(data.candidate);
}
}
localConnection = new RTCPeerConnection({
iceServers: [{
urls: 'turn:127.0.0.1:8043?transport=tcp',
credential: 'jupiter',
username: 'simpleshare'
}]
});
document.addEventListener("DOMContentLoaded", function (event) {
$("#share").click(function (event) {
navigator.mediaDevices.getUserMedia({ video: true })
.then(function (stream) {
stream.getTracks().forEach(
function (track) {
localConnection.addTrack(
track,
stream
);
}
);
localVideo = document.getElementById('local');
localVideo.srcObject = stream;
localConnection.onnegotiationneeded = function () {
localConnection.createOffer()
.then(offer => {
localConnection.setLocalDescription(offer)
.then(() => {
signaling.send(JSON.stringify({ sdp: offer }));
})
});
}
localConnection.onicecandidate = function (e) {
if (e.candidate) {
signaling.send(JSON.stringify({
candidateType: 'offerClient',
candidate: e.candidate.toJSON()
}));
}
console.log('offerClient is on icecandidate');
};
});
});
});
})();
HTML
<div>
<button id="share">Share</button>
<video id="local" autoplay></video>
</div>
Now the player code
JS
(function () {
var localVideo, localConnection;
const signaling = new WebSocket('wss://crs4kx11s1/websockets');
signaling.onmessage = function (message) {
const data = JSON.parse(message.data);
// const content = data.content;
try {
if (data.sdp) {
let offerSDP = data.sdp;
if (offerSDP.type == "offer") {
console.log("Accepting the offer.")
localConnection.setRemoteDescription(offerSDP);
localConnection.createAnswer().then(function (answer) {
console.log("Answer created!")
localConnection.setLocalDescription(answer);
signaling.send(JSON.stringify({ sdp: answer }));
});
}
}
if (data.candidate && data.candidateType == "offerClient") {
console.log("ICE candidate added!");
localConnection.addIceCandidate(data.candidate);
}
} catch (err) {
console.error(err);
}
};
document.addEventListener("DOMContentLoaded", function (event) {
startConnection();
localVideo = document.getElementById('self-view');
});
function startConnection() {
console.info("Starting connection");
localConnection = new RTCPeerConnection({iceServers: [{
urls: 'turn:127.0.0.1:8043?transport=tcp',
credential: 'jupiter',
username: 'simpleshare'
}]
});
//startCapture();
localConnection.onicecandidate = function (e) {
console.info("onicecandidate", e);
if (e.candidate) {
signaling.send(JSON.stringify({
candidateType: 'answerClient',
candidate: e.candidate.toJSON()
}));
}
console.log('answerClient is on icecandidate');
};
localConnection.onconnectionstatechange = function (e) {
console.log("Current state", localConnection.connectionState);
}
localConnection.ontrack = function (e) {
localVideo.srcObject = e.streams[0];
}
}
})();
HTML
<div id="chat-room">
<div id="videos">
<video id="self-view" autoplay></video>
</div>
</div>
Apart from these there is a WebSocket server which relays the SDP offers and candidates.
Please note that I have used our own TURN server for.
Got it worked. It was because of new autoplay policy in chrome. Just added localVideo.play(); and it worked.
RTCPeerConnection gets established and receives the clients reply which is 'answer' but does not show the remote video stream. The console log is shown below
console.log
From the log, the offer is sent to the peer which the peer sends back an answer that is console logged to display that it did actually send the answer back. I would greatly appreciate if you could take a look at my code pasted below and advise me how to go about rectifying it.
'use strict';
var localStream;
var remoteStream;
var isInitiator;
var configuration = {
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
}
]
};
var pc = new RTCPeerConnection(configuration);
// Define action buttons.
const callButton = document.getElementById('callButton');
const hangupButton = document.getElementById('hangupButton');
/////////////////////////////////////////////
window.room = prompt('Enter room name:');
var socket = io.connect();
if (room !== '') {
console.log('Message from client: Asking to join room ' + room);
socket.emit('create or join', room);
}
socket.on('created', function(room) {
console.log('Created room ' + room);
isInitiator = true;
startVideo();
});
socket.on('joined', function(room) {
console.log('joined: ' + room);
startVideo();
});
socket.on('log', function(array) {
console.log.apply(console, array);
});
////////////////////////////////////////////////
function sendMessage(message) {
socket.emit('message', message);
}
// This client receives a message
socket.on('message', function(message) {
if (message.type === 'offer') {
pc.setRemoteDescription(message);
console.log('Sending answer to peer.');
pc.createAnswer().then(
setLocalAndSendMessage,
onCreateSessionDescriptionError
);
} else if (message.type === 'answer') {
console.log('This is to check if answer was returned');
remoteStream = event.stream;
remoteVideo.srcObject = remoteStream;
pc.setRemoteDescription(message);
} else if (message.type === 'candidate') {
pc.addIceCandidate(candidate);
}
});
////////////////////////////////////////////////////
const localVideo = document.querySelector('#localVideo');
const remoteVideo = document.querySelector('#remoteVideo');
// Set up initial action buttons status: disable call and hangup.
callButton.disabled = true;
hangupButton.disabled = true;
// Add click event handlers for buttons.
callButton.addEventListener('click', callStart);
hangupButton.addEventListener('click', hangupCall);
function startVideo() {
navigator.mediaDevices
.getUserMedia({
audio: true,
video: true
})
.then(gotStream)
.catch(function(e) {
alert('getUserMedia() error: ' + e.name);
});
}
function gotStream(stream) {
localVideo.srcObject = stream;
localStream = stream;
callButton.disabled = false;
}
function callStart() {
createPeerConnection();
callButton.disabled = true;
hangupButton.disabled = false;
if (isInitiator) {
console.log('Sending offer to peer');
pc.createOffer(setLocalAndSendMessage, handleCreateOfferError);
}
}
/////////////////////////////////////////////////////////
function createPeerConnection() {
try {
pc.onicecandidate = ({ candidate }) => sendMessage({ candidate });
pc.ontrack = event => {
if (remoteVideo.srcObject) return;
remoteVideo.srcObject = event.stream;
};
console.log('Created RTCPeerConnnection');
} catch (e) {
console.log('Failed to create PeerConnection, exception: ' + e.message);
alert('Cannot create RTCPeerConnection object.');
return;
}
}
function handleCreateOfferError(event) {
console.log('createOffer() error: ', event);
}
function setLocalAndSendMessage(sessionDescription) {
console.log('setLocalAndSendMessage sending message', sessionDescription);
pc.setLocalDescription(sessionDescription);
sendMessage(sessionDescription);
}
function onCreateSessionDescriptionError(error) {
console.log('Failed to create session description: ' + error.toString());
}
function hangupCall() {
pc.close();
pc = null;
}
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!