I tried to test an exercise with WebRTC socket.io for a video call and chat .
I could not get the console errors on firefox but not attacking me the two local / remote stream between them .
my browser code is this
'use strict';
var p = navigator.mediaDevices.getUserMedia ({video: true, audio: true});
// Clean-up function:
// collect garbage before unloading browser's window
window.onbeforeunload = function(e){
hangup();
}
// Data channel information
var sendChannel, receiveChannel;
var sendButton = document.getElementById("sendButton");
var sendTextarea = document.getElementById("dataChannelSend");
var receiveTextarea = document.getElementById("dataChannelReceive");
// HTML5 <video> elements
var localVideo = document.querySelector('#localVideo');
var remoteVideo = document.querySelector('#remoteVideo');
// Handler associated with Send button
sendButton.onclick = sendData;
// Flags...
var isChannelReady = false;
var isInitiator = false;
var isStarted = false;
// WebRTC data structures
// Streams
var SStream;
var BStream;
var localStream;
var remoteStream;
// PeerConnection
var pc;
var pc_constraints = {
'optional': [
{'DtlsSrtpKeyAgreement': true}
]};
var sdpConstraints = {};
// Let's get started: prompt user for input (room name)
var room = prompt('Enter room name:');
// Connect to signaling server
var socket = io.connect("http://localhost:8181");
// Send 'Create or join' message to singnaling server
if (room !== '') {
console.log('Create or join room ', room);
socket.emit('create or join',room);
}
// Server-mediated message exchanging...
// 1. Server-->Client...
// this peer is the initiator
socket.on('created', function (room){
console.log('Created room ' + room);
isInitiator = true;
// Call getUserMedia()
p.then (function (MediaStream) {
SStream=MediaStream;
localVideo.src = window.URL.createObjectURL (MediaStream);
localVideo.onloadedmetadata = function (e) {
console.log("add local stream");
sendMessage('got user media');
};
});
p.catch (function (err) {console.log (err.name);});
checkAndStart();
});
// Handle 'join' message coming back from server:
// another peer is joining the channel
socket.on('join',function(room){
console.log('this peer is the iniator of room '+room+' !');
isChannelReady=true;
})
// Handle 'joined' message coming back from server:
// this is the second peer joining the channel
socket.on('joined', function (room){
console.log('This peer has joined room ' + room);
isChannelReady = true;
// Call getUserMedia()
p.then (function (MediaStream) {
isChannelReady = true;
BStream=MediaStream;
remoteVideo.src = window.URL.createObjectURL (MediaStream);
remoteVideo.onloadedmetadata = function (e) {
console.log("add remote stream");
sendMessage('got user media');
};
});
p.catch (function (err) {console.log (err.name);});
});
// Server-sent log message...
socket.on('log', function (array){
console.log.apply(console, array);
});
// Receive message from the other peer via the signaling server
socket.on('message', function (message){
console.log('Received message: ', message);
if (message === 'got user media') {
console.log('sono in if');
checkAndStart();
}
else if (message.type === 'offer') {
if (!isInitiator && !isStarted) {
checkAndStart();
}
pc.setRemoteDescription(new RTCSessionDescription(message));
doAnswer();
}
else if (message.type === 'answer' && isStarted) {
pc.setRemoteDescription(new RTCSessionDescription(message));
}
else if (message.type === 'candidate' && isStarted) {
var candidate = new RTCIceCandidate({sdpMLineIndex:message.label,candidate:message.candidate});
pc.addIceCandidate(candidate);
}
else if (message === 'bye' && isStarted) {
handleRemoteHangup();
}
});
// 2. Client-->Server
// Send message to the other peer via the signaling server
function sendMessage(message){
console.log('Sending message: ', message);
socket.emit('message', message);
}
// Channel negotiation trigger function
function checkAndStart() {
if (!isStarted && isChannelReady) {
createPeerConnection();
isStarted = true;
if (isInitiator) {
doCall();
}
}
}
// PeerConnection management...
function createPeerConnection() {
try {
/*posso aggiungere turn, google ecc...*/
pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:93.33.165.245' }] }, {
'optional': [
{'DtlsSrtpKeyAgreement': true}
]});
if(isInitiator){
SStream.getTracks().forEach(track => pc.addTrack(track,SStream));
pc.ontrack= handleRemoteStreamAdded;}
else{
BStream.getTracks().forEach(track => pc.addTrack(track,BStream));}
pc.onicecandidate = handleIceCandidate;
console.log('create RTCPeerConnection');
}
catch (e) {
console.log('Failed to create PeerConnection, exception: ' + e.message);
alert('Cannot create RTCPeerConnection object.');
return;
}
pc.ontrack= handleRemoteStreamAdded;
pc.onremovestream = handleRemoteStreamRemoved;
if (isInitiator) {
try {
// Create a reliable data channel
sendChannel = pc.createDataChannel("sendDataChannel",
{reliable: true});
console.log('Created send data channel');
} catch (e) {
alert('Failed to create data channel. ');
console.log('createDataChannel() failed with exception: ' + e.message);
}
sendChannel.onopen = handleSendChannelStateChange;
sendChannel.onmessage = handleMessage;
sendChannel.onclose = handleSendChannelStateChange;
} else { // Joiner
pc.ondatachannel = gotReceiveChannel;
}
}
// Data channel management
function sendData() {
var data = sendTextarea.value;
if(isInitiator) sendChannel.send(data);
else receiveChannel.send(data);
console.log('Sent data: ' + data);
}
// Handlers...
function gotReceiveChannel(event) {
console.log('Receive Channel Callback');
receiveChannel = event.channel;
receiveChannel.onmessage = handleMessage;
receiveChannel.onopen = handleReceiveChannelStateChange;
receiveChannel.onclose = handleReceiveChannelStateChange;
}
function handleMessage(event) {
console.log('Received message: ' + event.data);
receiveTextarea.value += event.data + '\n';
}
function handleSendChannelStateChange() {
var readyState = sendChannel.readyState;
console.log('Send channel state is: ' + readyState);
// If channel ready, enable user's input
if (readyState == "open") {
dataChannelSend.disabled = false;
dataChannelSend.focus();
dataChannelSend.placeholder = "";
sendButton.disabled = false;
} else {
dataChannelSend.disabled = true;
sendButton.disabled = true;
}
}
function handleReceiveChannelStateChange() {
var readyState = receiveChannel.readyState;
console.log('Receive channel state is: ' + readyState);
// If channel ready, enable user's input
if (readyState == "open") {
dataChannelSend.disabled = false;
dataChannelSend.focus();
dataChannelSend.placeholder = "";
sendButton.disabled = false;
} else {
dataChannelSend.disabled = true;
sendButton.disabled = true;
}
}
// ICE candidates management
function handleIceCandidate(event) {
console.log('handleIceCandidate event: ', event);
if (event.candidate) {
sendMessage({
type: 'candidate',
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate});
} else {
console.log('End of candidates.');
}
}
// Create Offer
function doCall() {
console.log('Creating Offer...');
pc.createOffer(setLocalAndSendMessage, onSignalingError, sdpConstraints);
}
// Signaling error handler
function onSignalingError(error) {
console.log('Failed to create signaling message : ' + error.name);
}
// Create Answer
function doAnswer() {
console.log('Sending answer to peer.');
pc.createAnswer(setLocalAndSendMessage, onSignalingError, sdpConstraints);
}
// Success handler for both createOffer()
// and createAnswer()
function setLocalAndSendMessage(sessionDescription) {
pc.setLocalDescription(sessionDescription);
sendMessage(sessionDescription);
}
// Remote stream handlers...
function handleRemoteStreamAdded(event) {
attachMediaStream(BStream, event.stream);
BStream = event.stream;
}
function handleRemoteStreamRemoved(event) {
console.log('Remote stream removed. Event: ', event);
}
// Clean-up functions...
function hangup() {
console.log('Hanging up.');
stop();
sendMessage('bye');
}
function handleRemoteHangup() {
console.log('Session terminated.');
stop();
isInitiator = false;
}
function stop() {
isStarted = false;
if (sendChannel) sendChannel.close();
if (receiveChannel) receiveChannel.close();
if (pc) pc.close();
pc = null;
sendButton.disabled=true;
}
someone tell me where I'm wrong and how to fix ?
attachMediaStream is not part of WebRTC. It's a shim adapter.js used to expose for setting video.src or video.srcObject (which Chrome still doesn't support, but Canary does).
In any case, you're passing in the wrong arguments, which should be an element and a stream, not two streams. I.e. make it:
attachMediaStream(remoteVideo, event.streams[0]);
or better, use the spec-way that adapter.js now supports on all browsers:
remoteVideo.srcObject = event.streams[0];
Important: The pc.ontrack event contains event.streams (plural), not event.stream! - It's an array since a track may (but rarely does) exist in more than one stream.
If you're not using the latest adapter.js, then note that pc.ontrack is only natively available in Firefox at the moment, so in Chrome you would need the older pc.onaddstream (and its event.stream).
PS: You're currently setting remoteVideo.src to the local video. Don't do that.
PPS: Remove pc_constraints. Really old stuff that will break Chrome.
Here's a demo of ontrack (use https fiddle in Chrome):
var pc1 = new RTCPeerConnection(), pc2 = new RTCPeerConnection();
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => pc1.addStream(video1.srcObject = stream))
.catch(log);
var add = (pc, can) => can && pc.addIceCandidate(can).catch(log);
pc1.onicecandidate = e => add(pc2, e.candidate);
pc2.onicecandidate = e => add(pc1, e.candidate);
pc2.ontrack = e => video2.srcObject = e.streams[0];
pc1.oniceconnectionstatechange = e => log(pc1.iceConnectionState);
pc1.onnegotiationneeded = e =>
pc1.createOffer().then(d => pc1.setLocalDescription(d))
.then(() => pc2.setRemoteDescription(pc1.localDescription))
.then(() => pc2.createAnswer()).then(d => pc2.setLocalDescription(d))
.then(() => pc1.setRemoteDescription(pc2.localDescription))
.catch(log);
var log = msg => div.innerHTML += "<br>" + msg;
<video id="video1" height="120" width="160" autoplay muted></video>
<video id="video2" height="120" width="160" autoplay></video><br>
<div id="div"></div>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
Related
I can't get my WebRTC code to work properly.. I did everything right I believe and it's still not working. There is something strange why ontrack gets called so early maybe it's suppose to be like that.
The website uses javascript code, the server code I didn't post but thats where WebSockets connect is just a exchanger, what you send to server it sends the same information back to the other partner (stranger) you are connected too.
Server code looks like this little sample
private void writeStranger(UserProfile you, String msg) {
UserProfile stranger = you.stranger;
if(stranger != null)
sendMessage(stranger.getWebSocket(), msg);
}
public void sendMessage(WebSocket websocket, String msg) {
try {
websocket.send(msg);
} catch ( WebsocketNotConnectedException e ) {
disconnnectClient(websocket);
}
}
//...
case "ice_candidate":
JSONObject candidatePackage = (JSONObject) packet.get(1);
JSONObject candidate = (JSONObject) candidatePackage.get("candidate");
obj = new JSONObject();
list = new JSONArray();
list.put("iceCandidate");
obj.put("candidate", candidate);
list.put(obj);
System.out.println("Sent = " + list.toString());
writeStranger(you, list.toString()); //send ice candidate to stranger
break;
case "send_answer":
JSONObject sendAnswerPackage = (JSONObject) packet.get(1);
JSONObject answer = (JSONObject) sendAnswerPackage.get("answer");
obj = new JSONObject();
list = new JSONArray();
list.put("getAnswer");
obj.put("answer", answer);
list.put(obj);
System.out.println("Sent = " + list.toString());
writeStranger(you, list.toString()); //send answer to stranger
break;
case "send_offer":
JSONObject offerPackage = (JSONObject) packet.get(1);
JSONObject offer = (JSONObject) offerPackage.get("offer");
obj = new JSONObject();
list = new JSONArray();
list.put("getOffer");
obj.put("offer", offer);
list.put(obj);
System.out.println("Sent = " + list.toString());
writeStranger(you, list.toString()); //send ice candidate to stranger
break;
Here are my outputs.
RAW Text: https://pastebin.com/raw/FL8g29gG
JSON colored: https://pastebin.com/FL8g29gG
My javascript Code below
var ws;
var peerConnection, localStream;
var rtc_server = {
iceServers: [
{urls: "stun:stun.l.google.com:19302"},
{urls: "stun:stun.services.mozilla.com"},
{urls: "stun:stun.stunprotocol.org:3478"},
{url: "stun:stun.l.google.com:19302"},
{url: "stun:stun.services.mozilla.com"},
{url: "stun:stun.stunprotocol.org:3478"},
]
}
//offer SDP's tells other peers what you would like
var rtc_media_constraints = {
mandatory: {
OfferToReceiveAudio: true,
OfferToReceiveVideo: true
}
};
var rtc_peer_options = {
optional: [
{DtlsSrtpKeyAgreement: true}, //To make Chrome and Firefox to interoperate.
]
}
var PeerConnection = RTCPeerConnection || window.PeerConnection || window.webkitPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
var IceCandidate = RTCIceCandidate || window.mozRTCIceCandidate || window.RTCIceCandidate;
var SessionDescription = RTCSessionDescription || window.mozRTCSessionDescription || window.RTCSessionDescription;
var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
function hasSupportForVideoChat() {
return window.RTCPeerConnection && window.RTCIceCandidate && window.RTCSessionDescription && navigator.mediaDevices && navigator.mediaDevices.getUserMedia && (RTCPeerConnection.prototype.addStream || RTCPeerConnection.prototype.addTrack) ? true : false;
}
function loadMyCameraStream() {
if (getUserMedia) {
getUserMedia.call(navigator, { video: {facingMode: "user", aspectRatio: 4 / 3/*height: 272, width: 322*/}, audio: { echoCancellation : true } },
function(localMediaStream) {
//Add my video
$("div#videoBox video#you")[0].muted = true;
$("div#videoBox video#you")[0].autoplay = true;
$("div#videoBox video#you").attr('playsinline', '');
$("div#videoBox video#you").attr('webkit-playsinline', '');
$("div#videoBox video#you")[0].srcObject = localMediaStream;
localStream = localMediaStream;
},
function(e) {
addStatusMsg("Your Video has error : " + e);
}
);
} else {
addStatusMsg("Your browser does not support WebRTC (Camera/Voice chat).");
return;
}
}
function loadStrangerCameraStream() {
if(!hasSupportForVideoChat())
return;
peerConnection = new PeerConnection(rtc_server, rtc_peer_options);
if (peerConnection.addTrack !== undefined)
localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
else
peerConnection.addStream(localStream);
peerConnection.onicecandidate = function(e) {
if (!e || !e.candidate)
return;
ws.send(JSON.stringify(['ice_candidate', {"candidate": e.candidate}]));
};
if (peerConnection.addTrack !== undefined) {
//newer technology
peerConnection.ontrack = function(e) {
//e.streams.forEach(stream => doAddStream(stream));
addStatusMsg("ontrack called");
//Add stranger video
$("div#videoBox video#stranger").attr('playsinline', '');
$("div#videoBox video#stranger").attr('webkit-playsinline', '');
$('div#videoBox video#stranger')[0].srcObject = e.streams[0];
$("div#videoBox video#stranger")[0].autoplay = true;
};
} else {
//older technology
peerConnection.onaddstream = function(e) {
addStatusMsg("onaddstream called");
//Add stranger video
$("div#videoBox video#stranger").attr('playsinline', '');
$("div#videoBox video#stranger").attr('webkit-playsinline', '');
$('div#videoBox video#stranger')[0].srcObject = e.stream;
$("div#videoBox video#stranger")[0].autoplay = true;
};
}
peerConnection.createOffer(
function(offer) {
peerConnection.setLocalDescription(offer, function () {
//both offer and peerConnection.localDescription are the same.
addStatusMsg('createOffer, localDescription: ' + JSON.stringify(peerConnection.localDescription));
//addStatusMsg('createOffer, offer: ' + JSON.stringify(offer));
ws.send(JSON.stringify(['send_offer', {"offer": peerConnection.localDescription}]));
},
function(e) {
addStatusMsg('createOffer, set description error' + e);
});
},
function(e) {
addStatusMsg("createOffer error: " + e);
},
rtc_media_constraints
);
}
function closeStrangerCameraStream() {
$('div#videoBox video#stranger')[0].srcObject = null
if(peerConnection)
peerConnection.close();
}
function iceCandidate(candidate) {
//ICE = Interactive Connectivity Establishment
if(peerConnection)
peerConnection.addIceCandidate(new IceCandidate(candidate));
else
addStatusMsg("peerConnection not created error");
addStatusMsg("Peer Ice Candidate = " + JSON.stringify(candidate));
}
function getAnswer(answer) {
if(!hasSupportForVideoChat())
return;
if(peerConnection) {
peerConnection.setRemoteDescription(new SessionDescription(answer), function() {
console.log("get answer ok");
addStatusMsg("peerConnection, SessionDescription answer is ok");
},
function(e) {
addStatusMsg("peerConnection, SessionDescription fail error: " + e);
});
}
}
function getOffer(offer) {
if(!hasSupportForVideoChat())
return;
addStatusMsg("peerConnection, setRemoteDescription offer: " + JSON.stringify(offer));
if(peerConnection) {
peerConnection.setRemoteDescription(new SessionDescription(offer), function() {
peerConnection.createAnswer(
function(answer) {
peerConnection.setLocalDescription(answer);
addStatusMsg("create answer sent: " + JSON.stringify(answer));
ws.send(JSON.stringify(['send_answer', {"answer": answer}]));
},
function(e) {
addStatusMsg("peerConnection, setRemoteDescription create answer fail: " + e);
}
);
});
}
}
My website where I use it: https://www.camspark.com/
Fixed myself I figured out I had 2 problems with this code.
First problem was then createOffer() must only be sent by 1 person not both people.. you have to randomly pick which person which does the createOffer().
Second problem is the ICE Candidate's you have to create a queue/array for both sides, which holds all the incoming ice_candidates. Only do the peerConnection.addIceCandidate(new IceCandidate(candidate)); when the response to createOffer() is received and the setRemoteDescription from createOffer() response is set up.
Both getAnswer() and getOffer() use exactly same code, but one is received for 1 client while the other is received for the other client. Both need to flush the IceCandidates array when either of them is triggered.. Maybe if anyone wants you could combine both functions into 1 function as the code is the same.
Final working code looks like this
var ws;
var peerConnection, localStream;
//STUN = (Session Traversal Utilities for NAT)
var rtc_server = {
iceServers: [
{urls: "stun:stun.l.google.com:19302"},
{urls: "stun:stun.services.mozilla.com"},
{urls: "stun:stun.stunprotocol.org:3478"},
{url: "stun:stun.l.google.com:19302"},
{url: "stun:stun.services.mozilla.com"},
{url: "stun:stun.stunprotocol.org:3478"},
]
}
//offer SDP = [Session Description Protocol] tells other peers what you would like
var rtc_media_constraints = {
mandatory: {
OfferToReceiveAudio: true,
OfferToReceiveVideo: true
}
};
var rtc_peer_options = {
optional: [
{DtlsSrtpKeyAgreement: true}, //To make Chrome and Firefox to interoperate.
]
}
var finishSDPVideoOffer = false;
var isOfferer = false;
var iceCandidates = [];
var PeerConnection = RTCPeerConnection || window.PeerConnection || window.webkitPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
var IceCandidate = RTCIceCandidate || window.mozRTCIceCandidate || window.RTCIceCandidate;
var SessionDescription = RTCSessionDescription || window.mozRTCSessionDescription || window.RTCSessionDescription;
var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
function hasSupportForVideoChat() {
return window.RTCPeerConnection && window.RTCIceCandidate && window.RTCSessionDescription && navigator.mediaDevices && navigator.mediaDevices.getUserMedia && (RTCPeerConnection.prototype.addStream || RTCPeerConnection.prototype.addTrack) ? true : false;
}
function loadMyCameraStream() {
if (getUserMedia) {
getUserMedia.call(navigator, { video: {facingMode: "user", aspectRatio: 4 / 3/*height: 272, width: 322*/}, audio: { echoCancellation : true } },
function(localMediaStream) {
//Add my video
$("div#videoBox video#you")[0].muted = true;
$("div#videoBox video#you")[0].autoplay = true;
$("div#videoBox video#you").attr('playsinline', '');
$("div#videoBox video#you").attr('webkit-playsinline', '');
$("div#videoBox video#you")[0].srcObject = localMediaStream;
localStream = localMediaStream;
},
function(e) {
addStatusMsg("Your Video has error : " + e);
}
);
} else {
addStatusMsg("Your browser does not support WebRTC (Camera/Voice chat).");
return;
}
}
function loadStrangerCameraStream(isOfferer_) {
if(!hasSupportForVideoChat())
return;
//Only add pending ICE Candidates when getOffer() is finished.
finishSDPVideoOfferOrAnswer = false;
iceCandidates = []; //clear ICE Candidates array.
isOfferer = isOfferer_;
peerConnection = new PeerConnection(rtc_server, rtc_peer_options);
if (peerConnection.addTrack !== undefined)
localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
else
peerConnection.addStream(localStream);
peerConnection.onicecandidate = function(e) {
if (!e || !e.candidate)
return;
ws.send(JSON.stringify(['ice_candidate', {"candidate": e.candidate}]));
};
if (peerConnection.addTrack !== undefined) {
//newer technology
peerConnection.ontrack = function(e) {
//e.streams.forEach(stream => doAddStream(stream));
addStatusMsg("ontrack called");
//Add stranger video
$("div#videoBox video#stranger").attr('playsinline', '');
$("div#videoBox video#stranger").attr('webkit-playsinline', '');
$('div#videoBox video#stranger')[0].srcObject = e.streams[0];
$("div#videoBox video#stranger")[0].autoplay = true;
};
} else {
//older technology
peerConnection.onaddstream = function(e) {
addStatusMsg("onaddstream called");
//Add stranger video
$("div#videoBox video#stranger").attr('playsinline', '');
$("div#videoBox video#stranger").attr('webkit-playsinline', '');
$('div#videoBox video#stranger')[0].srcObject = e.stream;
$("div#videoBox video#stranger")[0].autoplay = true;
};
}
if(isOfferer) {
peerConnection.createOffer(
function(offer) {
peerConnection.setLocalDescription(offer, function () {
//both offer and peerConnection.localDescription are the same.
addStatusMsg('createOffer, localDescription: ' + JSON.stringify(peerConnection.localDescription));
//addStatusMsg('createOffer, offer: ' + JSON.stringify(offer));
ws.send(JSON.stringify(['send_offer', {"offer": peerConnection.localDescription}]));
},
function(e) {
addStatusMsg('createOffer, set description error' + e);
});
},
function(e) {
addStatusMsg("createOffer error: " + e);
},
rtc_media_constraints
);
}
}
function closeStrangerCameraStream() {
$('div#videoBox video#stranger')[0].srcObject = null
if(peerConnection)
peerConnection.close();
}
function iceCandidate(candidate) {
//ICE = Interactive Connectivity Establishment
if(!finishSDPVideoOfferOrAnswer) {
iceCandidates.push(candidate);
addStatusMsg("Queued iceCandidate");
return;
}
if(!peerConnection) {
addStatusMsg("iceCandidate peerConnection not created error.");
return;
}
peerConnection.addIceCandidate(new IceCandidate(candidate));
addStatusMsg("Added on time, Peer Ice Candidate = " + JSON.stringify(candidate));
}
function getAnswer(answer) {
if(!hasSupportForVideoChat())
return;
if(!peerConnection) {
addStatusMsg("getAnswer peerConnection not created error.");
return;
}
peerConnection.setRemoteDescription(new SessionDescription(answer), function() {
addStatusMsg("getAnswer SessionDescription answer is ok");
finishSDPVideoOfferOrAnswer = true;
while (iceCandidates.length) {
var candidate = iceCandidates.shift();
try {
peerConnection.addIceCandidate(new IceCandidate(candidate));
addStatusMsg("Adding queued ICE Candidates");
} catch(e) {
addStatusMsg("Error adding queued ICE Candidates error:" + e);
}
}
iceCandidates = [];
},
function(e) {
addStatusMsg("getAnswer SessionDescription fail error: " + e);
});
}
function getOffer(offer) {
if(!hasSupportForVideoChat())
return;
if(!peerConnection) {
addStatusMsg("getOffer peerConnection not created error.");
return;
}
addStatusMsg("getOffer setRemoteDescription offer: " + JSON.stringify(offer));
peerConnection.setRemoteDescription(new SessionDescription(offer), function() {
finishSDPVideoOfferOrAnswer = true;
while (iceCandidates.length) {
var candidate = iceCandidates.shift();
try {
peerConnection.addIceCandidate(new IceCandidate(candidate));
addStatusMsg("Adding queued ICE Candidates");
} catch(e) {
addStatusMsg("Error adding queued ICE Candidates error:" + e);
}
}
iceCandidates = [];
if(!isOfferer) {
peerConnection.createAnswer(
function(answer) {
peerConnection.setLocalDescription(answer);
addStatusMsg("getOffer create answer sent: " + JSON.stringify(answer));
ws.send(JSON.stringify(['send_answer', {"answer": answer}]));
},
function(e) {
addStatusMsg("getOffer setRemoteDescription create answer fail: " + e);
}
);
}
});
}
Here is the patch I did on server-side WebSocket (Java) server.
//JSON
//["connected", {videoChatOfferer: true}]
//["connected", {videoChatOfferer: false}]
JSONObject obj = new JSONObject();
JSONArray list = new JSONArray();
list.put("loadStrangerCameraStream");
obj.put("videoChatOfferer", true); //first guy offerer for WebRTC.
list.put(obj);
server.sendMessage(websocket, list.toString()); //connected to chat partner
obj.put("videoChatOfferer", false); //second guy isn't offerer.
list.put(obj);
server.sendMessage(stranger.getWebSocket(), list.toString()); //connected to chat partner
I'm setting my video-conference service for academic purposes using WebRTC and everything works fine except for the audio capture. When I go to my website, it will only ask permission for the video camera, but not for the audio, and of course it just won't be any audio...
My main.js
'use strict';
var isChannelReady = false;
var isInitiator = false;
var isStarted = false;
var localStream;
var pc;
var remoteStream;
var turnReady;
var pcConfig = {
'iceServers': [{
'urls': 'stun:stun.l.google.com:19302'
}]
};
// Set up audio and video regardless of what devices are present.
var sdpConstraints = {
offerToReceiveAudio: true,
offerToReceiveVideo: true
};
/////////////////////////////////////////////
var room = 'foo';
// Could prompt for room name:
// room = prompt('Enter room name:');
var socket = io.connect();
if (room !== '') {
socket.emit('create or join', room);
console.log('Attempted to create or join room', room);
}
socket.on('created', function(room) {
console.log('Created room ' + room);
isInitiator = true;
});
socket.on('full', function(room) {
console.log('Room ' + room + ' is full');
});
socket.on('join', function (room){
console.log('Another peer made a request to join room ' + room);
console.log('This peer is the initiator of room ' + room + '!');
isChannelReady = true;
});
socket.on('joined', function(room) {
console.log('joined: ' + room);
isChannelReady = true;
});
socket.on('log', function(array) {
console.log.apply(console, array);
});
////////////////////////////////////////////////
function sendMessage(message) {
console.log('Client sending message: ', message);
socket.emit('message', message);
}
// This client receives a message
socket.on('message', function(message) {
console.log('Client received message:', message);
if (message === 'got user media') {
maybeStart();
} else if (message.type === 'offer') {
if (!isInitiator && !isStarted) {
maybeStart();
}
pc.setRemoteDescription(new RTCSessionDescription(message));
doAnswer();
} else if (message.type === 'answer' && isStarted) {
pc.setRemoteDescription(new RTCSessionDescription(message));
} else if (message.type === 'candidate' && isStarted) {
var candidate = new RTCIceCandidate({
sdpMLineIndex: message.label,
candidate: message.candidate
});
pc.addIceCandidate(candidate);
} else if (message === 'bye' && isStarted) {
handleRemoteHangup();
}
});
////////////////////////////////////////////////////
var localVideo = document.querySelector('#localVideo');
var remoteVideo = document.querySelector('#remoteVideo');
navigator.mediaDevices.getUserMedia({
audio: true,
video: true
})
.then(gotStream)
.catch(function(e) {
alert('getUserMedia() error: ' + e.name);
});
function gotStream(stream) {
console.log('Adding local stream.');
localVideo.src = window.URL.createObjectURL(stream);
localStream = stream;
sendMessage('got user media');
if (isInitiator) {
maybeStart();
}
}
var constraints = {
audio: true,
video: true
};
console.log('Getting user media with constraints', constraints);
if (location.hostname !== 'localhost') {
requestTurn(
'https://computeengineondemand.appspot.com/turn?username=41784574&key=4080218913'
);
}
function maybeStart() {
console.log('>>>>>>> maybeStart() ', isStarted, localStream, isChannelReady);
if (!isStarted && typeof localStream !== 'undefined' && isChannelReady) {
console.log('>>>>>> creating peer connection');
createPeerConnection();
pc.addStream(localStream);
isStarted = true;
console.log('isInitiator', isInitiator);
if (isInitiator) {
doCall();
}
}
}
window.onbeforeunload = function() {
sendMessage('bye');
};
/////////////////////////////////////////////////////////
function createPeerConnection() {
try {
pc = new RTCPeerConnection(null);
pc.onicecandidate = handleIceCandidate;
pc.onaddstream = handleRemoteStreamAdded;
pc.onremovestream = handleRemoteStreamRemoved;
console.log('Created RTCPeerConnnection');
} catch (e) {
console.log('Failed to create PeerConnection, exception: ' + e.message);
alert('Cannot create RTCPeerConnection object.');
return;
}
}
function handleIceCandidate(event) {
console.log('icecandidate event: ', event);
if (event.candidate) {
sendMessage({
type: 'candidate',
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate
});
} else {
console.log('End of candidates.');
}
}
function handleRemoteStreamAdded(event) {
console.log('Remote stream added.');
remoteVideo.src = window.URL.createObjectURL(event.stream);
remoteStream = event.stream;
}
function handleCreateOfferError(event) {
console.log('createOffer() error: ', event);
}
function doCall() {
console.log('Sending offer to peer');
pc.createOffer(setLocalAndSendMessage, handleCreateOfferError);
}
function doAnswer() {
console.log('Sending answer to peer.');
pc.createAnswer().then(
setLocalAndSendMessage,
onCreateSessionDescriptionError
);
}
function setLocalAndSendMessage(sessionDescription) {
// Set Opus as the preferred codec in SDP if Opus is present.
// sessionDescription.sdp = preferOpus(sessionDescription.sdp);
pc.setLocalDescription(sessionDescription);
console.log('setLocalAndSendMessage sending message', sessionDescription);
sendMessage(sessionDescription);
}
function onCreateSessionDescriptionError(error) {
trace('Failed to create session description: ' + error.toString());
}
function requestTurn(turnURL) {
var turnExists = false;
for (var i in pcConfig.iceServers) {
if (pcConfig.iceServers[i].url.substr(0, 5) === 'turn:') {
turnExists = true;
turnReady = true;
break;
}
}
if (!turnExists) {
console.log('Getting TURN server from ', turnURL);
// No TURN server. Get one from computeengineondemand.appspot.com:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var turnServer = JSON.parse(xhr.responseText);
console.log('Got TURN server: ', turnServer);
pcConfig.iceServers.push({
'url': 'turn:' + turnServer.username + '#' + turnServer.turn,
'credential': turnServer.password
});
turnReady = true;
}
};
xhr.open('GET', turnURL, true);
xhr.send();
}
}
function handleRemoteStreamAdded(event) {
console.log('Remote stream added.');
remoteVideo.src = window.URL.createObjectURL(event.stream);
remoteStream = event.stream;
}
function handleRemoteStreamRemoved(event) {
console.log('Remote stream removed. Event: ', event);
}
function hangup() {
console.log('Hanging up.');
stop();
sendMessage('bye');
}
function handleRemoteHangup() {
console.log('Session terminated.');
stop();
isInitiator = false;
}
function stop() {
isStarted = false;
// isAudioMuted = false;
// isVideoMuted = false;
pc.close();
pc = null;
}
///////////////////////////////////////////
// Set Opus as the default audio codec if it's present.
function preferOpus(sdp) {
var sdpLines = sdp.split('\r\n');
var mLineIndex;
// Search for m line.
for (var i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].search('m=audio') !== -1) {
mLineIndex = i;
break;
}
}
if (mLineIndex === null) {
return sdp;
}
// If Opus is available, set it as the default in m line.
for (i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].search('opus/48000') !== -1) {
var opusPayload = extractSdp(sdpLines[i], /:(\d+) opus\/48000/i);
if (opusPayload) {
sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex],
opusPayload);
}
break;
}
}
// Remove CN in m line and sdp.
sdpLines = removeCN(sdpLines, mLineIndex);
sdp = sdpLines.join('\r\n');
return sdp;
}
function extractSdp(sdpLine, pattern) {
var result = sdpLine.match(pattern);
return result && result.length === 2 ? result[1] : null;
}
// Set the selected codec to the first in m line.
function setDefaultCodec(mLine, payload) {
var elements = mLine.split(' ');
var newLine = [];
var index = 0;
for (var i = 0; i < elements.length; i++) {
if (index === 3) { // Format of media starts from the fourth.
newLine[index++] = payload; // Put target payload to the first.
}
if (elements[i] !== payload) {
newLine[index++] = elements[i];
}
}
return newLine.join(' ');
}
// Strip CN from sdp before CN constraints is ready.
function removeCN(sdpLines, mLineIndex) {
var mLineElements = sdpLines[mLineIndex].split(' ');
// Scan from end for the convenience of removing an item.
for (var i = sdpLines.length - 1; i >= 0; i--) {
var payload = extractSdp(sdpLines[i], /a=rtpmap:(\d+) CN\/\d+/i);
if (payload) {
var cnPos = mLineElements.indexOf(payload);
if (cnPos !== -1) {
// Remove CN payload from m line.
mLineElements.splice(cnPos, 1);
}
// Remove CN line in sdp
sdpLines.splice(i, 1);
}
}
sdpLines[mLineIndex] = mLineElements.join(' ');
return sdpLines;
}
and my index.js (server)
var fs = require('fs');
var http = require('http');
var https = require('https');
var cors = require('cors');
var privateKey = fs.readFileSync('host.key', 'utf8');
var certificate = fs.readFileSync('host.cert', 'utf8');
var os = require('os');
var credentials = {key: privateKey, cert: certificate};
var express = require('express');
var app = express();
app.use(cors());
var path = require('path');
app.use(express.static(path.join(__dirname, 'public')));
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
//console.log('Servidor en marxa.');
});
console.log('Servidor ON');
var httpServer = http.createServer(app);
var httpsServer = https.createServer(credentials,app);
httpServer.listen(80,"0.0.0.0");
httpsServer.listen(443,"0.0.0.0");
var io = require("socket.io");
io = io.listen(httpServer);
io = io.listen(httpsServer);
//EL SERVER ESTA CONFIGURAT I FUNCIONA//
var usersConnected = [];
var nUsersConnected = 0;
io.sockets.on('connection',function(socket){
function log() {
var array = ['Message from server:'];
array.push.apply(array, arguments);
socket.emit('log', array);
}
usersConnected.push(socket.id);
nUsersConnected++;
log('Welcome to WebRTC Server: Miguel & Pavel');
console.log('New user connected: '+socket.id+' #Users connected = '+nUsersConnected);
socket.on('message', function(message) {
log('Client said: ', message);
// for a real app, would be room-only (not broadcast)
socket.broadcast.emit('message', message);
});
socket.on('create or join', function(room) {
log('Received request to create or join room ' + room);
var numClients = io.sockets.sockets.length;
log('Room ' + room + ' now has ' + numClients + ' client(s)');
if (numClients === 1) {
socket.join(room);
log('Client ID ' + socket.id + ' created room ' + room);
socket.emit('created', room, socket.id);
} else if (numClients === 2) {
log('Client ID ' + socket.id + ' joined room ' + room);
io.sockets.in(room).emit('join', room);
socket.join(room);
socket.emit('joined', room, socket.id);
io.sockets.in(room).emit('ready');
} else { // max two clients
socket.emit('full', room);
}
});
socket.on('ipaddr', function() {
var ifaces = os.networkInterfaces();
for (var dev in ifaces) {
ifaces[dev].forEach(function(details) {
if (details.family === 'IPv4' && details.address !== '127.0.0.1') {
socket.emit('ipaddr', details.address);
}
});
}
});
socket.on('disconnect', function (message) {
nUsersConnected--;
console.log('User: '+socket.id+' disconnected #Users connected = '+nUsersConnected);
/*if(usuari1 == socket.id){
io.to(usuari2).emit('message', {type:'hangup'});
}else{
io.to(usuari1).emit('message', {type:'hangup'});
}*/
//tots els usuaris s'han desconnectat
if(nUsersConnected == 0){
console.log('All users have been deleted');
nUsersConnected = 0;
usersConnected = [];
}
})
socket.on('bye', function(){
console.log('received bye');
});
});
Thanks!
When I go to my website, it will only ask permission for the video camera, but not for the audio, and of course it just won't be any audio
Am suspecting issue with your system microphone.
If your microphone is proper, then your localstream should have audioTracks localStream.getAudioTracks(). And if we unmute localVideo element, it should echo the audio.
Try this demo https://webrtc.github.io/samples/src/content/devices/input-output/ to test your microphone. Update your question with the results.
Google stopped providing turn servers with below url, so you need to setup your TurnServer. Try CoTURN.
requestTurn(
'https://computeengineondemand.appspot.com/turn?username=41784574&key=4080218913'
);
I am making a WebRTC video chat application and it was working before i started to add or subtract more code and in the process i deleted or changed the order in a way that now i am getting this error. Sadly i don't have a backup code and it has consumed so much of my time.
You will need socket.io and node-static packages installed on your node server.
Okay i managed to solve the issue with the error but now clients are not connecting with each other, it seems two clients are unable to exchange messages via the server.
My server.js code is below
var static = require('node-static');
var http = require('http');
var file = new(static.Server)();
var app = http.createServer(function (req, res) {
file.serve(req, res);
}).listen(8080, '127.0.0.1');
var io = require('socket.io').listen(app);
io.sockets.on('connection', function (socket){
socket.emit('emit(): client ' + socket.id + ' joined room ' + room);
socket.broadcast.emit('broadcast(): client ' + socket.id + ' joined room ' + room);
function log(){
var array = [">>> Message from server: "];
for (var i = 0; i < arguments.length; i++) {
array.push(arguments[i]);
}
socket.emit('log', array);
}
socket.on('message', function (message) {
log('Got message: ', message);
// For a real app, should be room only (not broadcast)
socket.broadcast.emit('message', message);
});
socket.on('create or join', function (room) {
var numClients = io.sockets.clients(room).length;
log('Room ' + room + ' has ' + numClients + ' client(s)');
log('Request to create or join room', room);
if (numClients == 0){
socket.join(room);
socket.emit('created', room);
} else if (numClients == 1) {
io.sockets.in(room).emit('join', room);
socket.join(room);
socket.emit('joined', room);
} else { // max two clients
socket.emit('full', room);
}
});
});
my application.js file is as follows
'use strict';
var isChannelReady;
var isInitiator = false;
var isStarted = false;
var localStream;
var pc;
var remoteStream;
var pc_config = webrtcDetectedBrowser === 'firefox' ?
{'iceServers':[{'url':'stun:23.21.150.121'}]} : // number IP
{'iceServers': [{'url': 'stun:stun.l.google.com:19302'}]};
var pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': true}]};
// Set up audio and video regardless of what devices are present.
var sdpConstraints = {'mandatory': {
'OfferToReceiveAudio':true,
'OfferToReceiveVideo':true }};
var room = location.pathname.substring(1);
if (room === '') {
room = window.prompt('Enter room name:');
room = '';
}
var socket = io.connect();
if (room !== '') {
console.log('Create or join room', room);
socket.emit('create or join', room);
}
socket.on('created', function (room){
console.log('Created room ' + room);
isInitiator = true;
});
socket.on('full', function (room){
console.log('Room ' + room + ' is full');
});
socket.on('join', function (room){
console.log('Another peer made a request to join room ' + room);
console.log('This peer is the initiator of room ' + room + '!');
isChannelReady = true;
});
socket.on('joined', function (room){
console.log('This peer has joined room ' + room);
isChannelReady = true;
});
socket.on('log', function (array){
console.log.apply(console, array);
});
////////////////////////////////////////////////
function sendMessage(message){
console.log('Client sending message: ', message);
// if (typeof message === 'object') {
// message = JSON.stringify(message);
// }
socket.emit('message', message);
}
socket.on('message', function (message){
console.log('Client received message:', message);
if (message === 'got user media') {
maybeStart();
} else if (message.type === 'offer') {
if (!isInitiator && !isStarted) {
maybeStart();
}
pc.setRemoteDescription(new RTCSessionDescription(message));
doAnswer();
} else if (message.type === 'answer' && isStarted) {
pc.setRemoteDescription(new RTCSessionDescription(message));
} else if (message.type === 'candidate' && isStarted) {
var candidate = new RTCIceCandidate({
sdpMLineIndex: message.label,
candidate: message.candidate
});
pc.addIceCandidate(candidate);
} else if (message === 'bye' && isStarted) {
handleRemoteHangup();
}
});
////////////////////////////////////////////////////
function handleUserMedia(stream) {
console.log('Adding local stream.');
localVideo.src = window.URL.createObjectURL(stream);
localStream = stream;
sendMessage('got user media');
if (isInitiator) {
maybeStart();
}
}
function handleUserMediaError(error){
console.log('getUserMedia error: ', error);
}
var constraints = {video: true, audio:true};
getUserMedia(constraints, handleUserMedia, handleUserMediaError);
console.log('Getting user media with constraints', constraints);
function maybeStart() {
if (!isStarted && typeof localStream != 'undefined' && isChannelReady) {
createPeerConnection();
pc.addStream(localStream);
isStarted = true;
console.log('isInitiator', isInitiator);
if (isInitiator) {
doCall();
}
}
}
window.onbeforeunload = function(e){
sendMessage('bye');
}
/////////////////////////////////////////////////////////
function createPeerConnection() {
try {
pc = new RTCPeerConnection(null);
pc.onicecandidate = handleIceCandidate;
pc.onaddstream = handleRemoteStreamAdded;
pc.onremovestream = handleRemoteStreamRemoved;
console.log('Created RTCPeerConnnection');
} catch (e) {
console.log('Failed to create PeerConnection, exception: ' + e.message);
alert('Cannot create RTCPeerConnection object.');
return;
}
}
function handleIceCandidate(event) {
console.log('handleIceCandidate event: ', event);
if (event.candidate) {
sendMessage({
type: 'candidate',
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate});
} else {
console.log('End of candidates.');
}
}
function handleRemoteStreamAdded(event) {
console.log('Remote stream added.');
remoteVideo.src = window.URL.createObjectURL(event.stream);
remoteStream = event.stream;
}
function handleCreateOfferError(event){
console.log('createOffer() error: ', e);
}
function doCall() {
console.log('Sending offer to peer');
pc.createOffer(setLocalAndSendMessage, handleCreateOfferError);
}
function doAnswer() {
console.log('Sending answer to peer.');
pc.createAnswer(setLocalAndSendMessage, null, sdpConstraints);
}
function setLocalAndSendMessage(sessionDescription) {
// Set Opus as the preferred codec in SDP if Opus is present.
sessionDescription.sdp = preferOpus(sessionDescription.sdp);
pc.setLocalDescription(sessionDescription);
console.log('setLocalAndSendMessage sending message' , sessionDescription);
sendMessage(sessionDescription);
}
function handleRemoteStreamAdded(event) {
console.log('Remote stream added.');
remoteVideo.src = window.URL.createObjectURL(event.stream);
remoteStream = event.stream;
}
function handleRemoteStreamRemoved(event) {
console.log('Remote stream removed. Event: ', event);
}
function hangup() {
console.log('Hanging up.');
stop();
sendMessage('bye');
}
function handleRemoteHangup() {
// console.log('Session terminated.');
// stop();
// isInitiator = false;
}
function stop() {
isStarted = false;
// isAudioMuted = false;
// isVideoMuted = false;
pc.close();
pc = null;
}
///////////////////////////////////////////
// Set Opus as the default audio codec if it's present.
function preferOpus(sdp) {
var sdpLines = sdp.split('\r\n');
var mLineIndex = null;
// Search for m line.
for (var i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].search('m=audio') !== -1) {
mLineIndex = i;
break;
}
}
if (mLineIndex === null) {
return sdp;
}
// If Opus is available, set it as the default in m line.
for (i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].search('opus/48000') !== -1) {
var opusPayload = extractSdp(sdpLines[i], /:(\d+) opus\/48000/i);
if (opusPayload) {
sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], opusPayload);
}
break;
}
}
// Remove CN in m line and sdp.
sdpLines = removeCN(sdpLines, mLineIndex);
sdp = sdpLines.join('\r\n');
return sdp;
}
function extractSdp(sdpLine, pattern) {
var result = sdpLine.match(pattern);
return result && result.length === 2 ? result[1] : null;
}
// Set the selected codec to the first in m line.
function setDefaultCodec(mLine, payload) {
var elements = mLine.split(' ');
var newLine = [];
var index = 0;
for (var i = 0; i < elements.length; i++) {
if (index === 3) { // Format of media starts from the fourth.
newLine[index++] = payload; // Put target payload to the first.
}
if (elements[i] !== payload) {
newLine[index++] = elements[i];
}
}
return newLine.join(' ');
}
// Strip CN from sdp before CN constraints is ready.
function removeCN(sdpLines, mLineIndex) {
var mLineElements = sdpLines[mLineIndex].split(' ');
// Scan from end for the convenience of removing an item.
for (var i = sdpLines.length-1; i >= 0; i--) {
var payload = extractSdp(sdpLines[i], /a=rtpmap:(\d+) CN\/\d+/i);
if (payload) {
var cnPos = mLineElements.indexOf(payload);
if (cnPos !== -1) {
// Remove CN payload from m line.
mLineElements.splice(cnPos, 1);
}
// Remove CN line in sdp
sdpLines.splice(i, 1);
}
}
sdpLines[mLineIndex] = mLineElements.join(' ');
return sdpLines;
}
Index.html
<!DOCTYPE html>
<html>
<head>
<meta name='keywords' content='WebRTC, HTML5, JavaScript' />
<meta name='description' content='WebRTC Reference App' />
<meta name='viewport' content='width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1'>
<base target='_blank'>
<title>WebRTC client</title>
<link rel='stylesheet' href='css/style.css' />
</head>
<body id='body'>
<p style=" font-size:24px" align="center">WebRTC Video Share</p>
<div id='container'>
<div>
<video id='localVideo' autoplay muted></video>
<video id='remoteVideo' autoplay></video>
</div>
</div>
<script src='/socket.io/socket.io.js'></script>
<script src='js/lib/adapter.js'></script>
<script src='js/main.js'></script>
</body>
</html>
In your server.js code:
var io = require('socket.io').listen(app);
io.sockets.on('connection', function (socket){
socket.emit('emit(): client ' + socket.id + ' joined room ' + room);
at the end you refer to a variable room, but you haven't created it yet.
This code works in google chrome fine ,
but i tried to convert it to support Firefox
and i always get no error in the console .
the cam it seems ruining but there's no video.
here's the script
var socket = new WebSocket('ws://127.0.0.1:1338/'); // change the IP address to your websocket server
var stunServer = "stun.l.google.com:19302";
var sourcevid = document.getElementById('sourcevid');
var remotevid = document.getElementById('remotevid');
var localStream = null;
var remoteStream;
var peerConn = null;
var started = false;
var isRTCPeerConnection = true;
var mediaConstraints = {mandatory: {
OfferToReceiveAudio:true,
OfferToReceiveVideo:true }};
var logg = function(s) { console.log(s); };
// send the message to websocket server
function sendMessage(message) {
var mymsg = JSON.stringify(message);
logg("SEND: " + mymsg);
socket.send(mymsg);
}
function createPeerConnection() {
try {
logg("Creating peer connection");
var servers = [];
servers.push({'url':'stun:' + stunServer});
var pc_config = {'iceServers':servers};
peerConn = new webkitRTCPeerConnection(pc_config);
peerConn.onicecandidate = onIceCandidate;
} catch (e) {
try {
peerConn = new RTCPeerConnection('STUN ' + stunServer, onIceCandidate00);
isRTCPeerConnection = false;
} catch (e) {
logg("Failed to create PeerConnection, exception: " + e.message);
}
}
peerConn.onaddstream = onRemoteStreamAdded;
peerConn.onremovestream = onRemoteStreamRemoved;
}
// when remote adds a stream, hand it on to the local video element
function onRemoteStreamAdded(event) {
logg("Added remote stream");
remotevid.src = window.webkitURL.createObjectURL(event.stream);
}
function waitForRemoteVideo() {
if (remoteStream.videoTracks.length === 0 || remotevid.currentTime > 0) {
transitionToActive();
} else {
setTimeout(waitForRemoteVideo, 100);
}
}
function transitionToActive() {
remotevid.style.opacity = 1;
card.style.webkitTransform = "rotateY(180deg)";
setTimeout(function() { sourcevid.src = ""; }, 500);
setStatus("<input type=\"button\" id=\"hangup\" value=\"Hang up\" onclick=\"onHangup()\" />");
}
// when remote removes a stream, remove it from the local video element
function onRemoteStreamRemoved(event) {
logg("Remove remote stream");
remotevid.src = "";
}
function onIceCandidate(event) {
if (event.candidate) {
sendMessage({type: 'candidate',
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate});
} else {
logg("End of candidates.");
}
}
function onIceCandidate00(candidate, moreToFollow) {
if (candidate) {
sendMessage({type: 'candidate', label: candidate.label, candidate: candidate.toSdp()});
}
if (!moreToFollow) {
logg("End of candidates.");
}
}
// start the connection upon user request
function connect() {
if (!started && localStream) {
console.log("Creating PeerConnection.");
createPeerConnection();
logg('Adding local stream...');
peerConn.addStream(localStream);
started = true;
logg("isRTCPeerConnection: " + isRTCPeerConnection);
//create offer
if (isRTCPeerConnection) {
peerConn.createOffer(setLocalAndSendMessage, null, mediaConstraints);
} else {
var offer = peerConn.createOffer(mediaConstraints);
peerConn.setLocalDescription(peerConn.SDP_OFFER, offer);
sendMessage({type: 'offer', sdp: offer.toSdp()});
peerConn.startIce();
}
} else {
alert("Local stream not running yet.");
}
}
// accept connection request
socket.addEventListener("message", onMessage, false);
function onMessage(evt) {
logg("RECEIVED: " + evt.data);
if (isRTCPeerConnection)
processSignalingMessage(evt.data);
else
processSignalingMessage00(evt.data);
}
function processSignalingMessage(message) {
var msg = JSON.parse(message);
if (msg.type === 'offer') {
if (!started && localStream) {
createPeerConnection();
logg('Adding local stream...');
peerConn.addStream(localStream);
started = true;
logg("isRTCPeerConnection: " + isRTCPeerConnection);
if (isRTCPeerConnection) {
//set remote description
peerConn.setRemoteDescription(new RTCSessionDescription(msg));
//create answer
console.log("Sending answer to peer.");
peerConn.createAnswer(setLocalAndSendMessage, null, mediaConstraints);
} else {
//set remote description
peerConn.setRemoteDescription(peerConn.SDP_OFFER, new SessionDescription(msg.sdp));
//create answer
var offer = peerConn.remoteDescription;
var answer = peerConn.createAnswer(offer.toSdp(), mediaConstraints);
console.log("Sending answer to peer.");
setLocalAndSendMessage00(answer);
}
}
} else if (msg.type === 'answer' && started) {
peerConn.setRemoteDescription(new RTCSessionDescription(msg));
} else if (msg.type === 'candidate' && started) {
var candidate = new RTCIceCandidate({sdpMLineIndex:msg.label, candidate:msg.candidate});
peerConn.addIceCandidate(candidate);
} else if (msg.type == 'chat'){
addChatMsg(msg.nick, msg.cid, msg.data);
}
else if (msg.type === 'bye' && started) {
onRemoteHangUp();
}
}
function processSignalingMessage00(message) {
var msg = JSON.parse(message);
// if (msg.type === 'offer') --> will never happened since isRTCPeerConnection=true initially
if (msg.type === 'answer' && started) {
peerConn.setRemoteDescription(peerConn.SDP_ANSWER, new SessionDescription(msg.sdp));
} else if (msg.type === 'candidate' && started) {
var candidate = new IceCandidate(msg.label, msg.candidate);
peerConn.processIceMessage(candidate);
} else if (msg.type === 'bye' && started) {
onRemoteHangUp();
}
}
function setLocalAndSendMessage(sessionDescription) {
peerConn.setLocalDescription(sessionDescription);
sendMessage(sessionDescription);
}
function setLocalAndSendMessage00(answer) {
peerConn.setLocalDescription(peerConn.SDP_ANSWER, answer);
sendMessage({type: 'answer', sdp: answer.toSdp()});
peerConn.startIce();
}
function onRemoteHangUp() {
logg("Remote Hang up.");
closeSession();
}
function onHangUp() {
logg("Hang up.");
if (started) {
sendMessage({type: 'bye'});
closeSession();
}
}
function closeSession() {
peerConn.close();
peerConn = null;
started = false;
remotevid.src = "";
}
window.onbeforeunload = function() {
if (started) {
sendMessage({type: 'bye'});
}
}
function startVideo() {
// Replace the source of the video element with the stream from the camera
if (navigator.mozGetUserMedia) {
try {
navigator.mozGetUserMedia({audio: true, video: true}, successCallback, errorCallback);
} catch (e) {
navigator.mozGetUserMedia("video,audio", successCallback, errorCallback);
}
}
else {
try {
navigator.webkitGetUserMedia({audio: true, video: true}, successCallback, errorCallback);
} catch (e) {
navigator.webkitGetUserMedia("video,audio", successCallback, errorCallback);
}
}
function successCallback(stream) {
if (navigator.mozGetUserMedia) {
sourcevid.mozSrcObject = stream;
sourcevid.style.webkitTransform = "rotateY(180deg)";
localStream = stream;
}
if(navigator.webkitGetUserMedia){
sourcevid.src = window.webkitURL.createObjectURL(stream);
sourcevid.style.webkitTransform = "rotateY(180deg)";
localStream = stream;
}
}
function errorCallback(error) {
logg('An error occurred: [CODE ' + error.code + ']');
}
}
function stopVideo() {
sourcevid.src = "";
}
and here is the html
<script type="text/javascript" src="{{ asset('bundles/PFESiivt/js/visio.js') }}"></script>
<div id="main">
<div id="" style="height:280px;width:700;">
<div id="livevideodivk" style="float:left;">
<video id="sourcevid" style="height:280px;width:320px;" autoplay></video>
</div>
<div id="remotevideodivk" style="float:left;margin-left:10px">
<video id="remotevid" style="height:280px;width:320px;" autoplay></video>
</div>
</div>
<center>
<button id="btn" type="button" onclick="startVideo();">Start video</button>
<button id="btn" type="button" onclick="stopVideo();">Stop video</button>
<button id="btn" type="button" onclick="connect();">Connect</button>
<button id="btn" type="button" onclick="onHangUp();">Hang Up</button>
</center>
</div>
Have not gone through the complete code, but for starters...
for firefox it is mozRTCPeerConnection not RTCPeerConnection.
secondly, for firefox PeerConnection object, the onicecandidate handler is missing.
P. S: I think it is a very bad idea to post complete code, would advice to do bit of debugging yourself( to identify the block causing issue) and then post the relevant block when not able to solve it.
I'm new to WebRTC and am trying to create a simple test page that allows for 1-to-1 video conferences.
So far what I have below works for the following:
Chrome -> Chrome
Firefox -> Chrome
It does not work in the following situations:
Firefox -> Firefox
Chrome -> Firefox
Basically, Firefox is unable to receive the remote video stream when it is the callee. The onaddstream handler is never called, but I do not know why.
The only difference I have found between Firefox and Chrome is that Firefox does not trickle ICE candidates; they are included in the initial offer/answer creation causing the onicecandidate handler to never be called with a non-null candidate. On the other hand, Chrome does trickle candidates which are sent to the signaling server as separate messages. I'm not sure if this a/the potential problem or not.
I am testing with Firefox Nightly 34 and Chrome 36. The signaling server is hosted in my LAN and both browsers are running on the same computer.
The following code was based off of http://www.html5rocks.com/en/tutorials/webrtc/basics/.
var localVideo;
var remoteVideo;
var peerConnection;
var peerConnectionConfig = {'iceServers': [{'url': 'stun:stun.services.mozilla.com'}, {'url': 'stun:stun.l.google.com:19302'}]};
var isCaller;
navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;
window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
function pageReady() {
localVideo = document.getElementById('localVideo');
remoteVideo = document.getElementById('remoteVideo');
serverConnection = new WebSocket('ws://myserver:3434');
serverConnection.onmessage = gotMessageFromServer;
}
function start(_isCaller) {
isCaller = _isCaller
peerConnection = new RTCPeerConnection(peerConnectionConfig);
peerConnection.onicecandidate = gotIceCandidate;
peerConnection.onaddstream = gotRemoteStream;
var constraints = {
video: true,
audio: true,
};
if(navigator.getUserMedia) {
navigator.getUserMedia(constraints, getUserMediaSuccess, getUserMediaError);
} else {
alert('Your browser does not support getUserMedia API');
}
}
function getUserMediaSuccess(stream) {
localStream = stream;
localVideo.src = window.URL.createObjectURL(stream);
peerConnection.addStream(stream);
if(isCaller) {
peerConnection.createOffer(gotDescription, createOfferError);
} else {
peerConnection.createAnswer(gotDescription, createAnswerError);
}
}
function gotMessageFromServer(message) {
if(!peerConnection) start(false);
var signal = JSON.parse(message.data);
if(signal.sdp) {
peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp));
} else if(signal.ice) {
peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice));
}
}
function gotIceCandidate(event) {
if(event.candidate != null) {
serverConnection.send(JSON.stringify({'ice': event.candidate}));
}
}
function gotDescription(description) {
peerConnection.setLocalDescription(description);
serverConnection.send(JSON.stringify({'sdp': description}));
}
function gotMessageFromServer(message) {
if(!peerConnection) start(false);
var signal = JSON.parse(message.data);
if(signal.sdp) {
peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp));
} else if(signal.ice) {
peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice));
}
}
function gotRemoteStream(event) {
console.log("got remote stream");
remoteVideo.src = window.URL.createObjectURL(event.stream);
}
// Error functions....
function getUserMediaError(error) {
console.log(error);
}
function createOfferError(error) {
console.log(error);
}
function createAnswerError(error) {
console.log(error);
}
If it's useful, here is my signaling server. It's a very basic Node.js script that uses the ws WebSocket library to simply broadcast received messages to all connected clients which is only one other client in this case.
var WebSocketServer = require('ws').Server;
var wss = new WebSocketServer({port: 3434});
wss.broadcast = function(data) {
for(var i in this.clients) {
this.clients[i].send(data);
}
};
wss.on('connection', function(ws) {
ws.on('message', function(message) {
console.log('received: %s', message);
wss.broadcast(message);
});
});
Any help is greatly appreciated!
Turns out I had a race condition between the call to setRemoteDescription() and createAnswer(). Firefox asks for permission to use the webcam on every reload of the page whereas Chrome only asks on the first page load. This meant that When I went to call createAnswer() in Firefox, the video stream did not exist yet because the user had not allowed access to the webcam yet. The solution was to move the call to getUserMedia() to when the page is ready. This way, when an incoming call occurs the user has already allowed access to the webcam and the video stream can immediately be shared. It's not perfect, but it allows my little test page to work which is all I'm going for for now.
Here's the updated code if it helps anyone in the future:
var localVideo;
var remoteVideo;
var peerConnection;
var peerConnectionConfig = {'iceServers': [{'url': 'stun:stun.services.mozilla.com'}, {'url': 'stun:stun.l.google.com:19302'}]};
navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;
window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
function pageReady() {
localVideo = document.getElementById('localVideo');
remoteVideo = document.getElementById('remoteVideo');
serverConnection = new WebSocket('ws://sagan:3434');
serverConnection.onmessage = gotMessageFromServer;
var constraints = {
video: true,
audio: true,
};
if(navigator.getUserMedia) {
navigator.getUserMedia(constraints, getUserMediaSuccess, getUserMediaError);
} else {
alert('Your browser does not support getUserMedia API');
}
}
function getUserMediaSuccess(stream) {
localStream = stream;
localVideo.src = window.URL.createObjectURL(stream);
}
function start(isCaller) {
peerConnection = new RTCPeerConnection(peerConnectionConfig);
peerConnection.onicecandidate = gotIceCandidate;
peerConnection.onaddstream = gotRemoteStream;
peerConnection.addStream(localStream);
if(isCaller) {
peerConnection.createOffer(gotDescription, createOfferError);
}
}
function gotMessageFromServer(message) {
if(!peerConnection) start(false);
var signal = JSON.parse(message.data);
if(signal.sdp) {
peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp), function() {
peerConnection.createAnswer(gotDescription, createAnswerError);
});
} else if(signal.ice) {
peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice));
}
}
function gotIceCandidate(event) {
if(event.candidate != null) {
serverConnection.send(JSON.stringify({'ice': event.candidate}));
}
}
function gotDescription(description) {
console.log('got description');
peerConnection.setLocalDescription(description, function () {
serverConnection.send(JSON.stringify({'sdp': description}));
}, function() {console.log('set description error')});
}
function gotRemoteStream(event) {
console.log("got remote stream");
remoteVideo.src = window.URL.createObjectURL(event.stream);
}
// Error functions....
function getUserMediaError(error) {
console.log(error);
}
function createOfferError(error) {
console.log(error);
}
function createAnswerError(error) {
console.log(error);
}