I am create an app that will share the camera video to multiple peer connection using WebRTC. The server simply provide a room for users, all users in the room will see the camera video. The only not working was the onicecandidate did not fire, therefore no peer connection can be made.
I create new peer connection for each new users. All seems fine (broadcaster and viewers can exchange messages: 1) When a viewer joined into the room, the Broadcaster creates new peer connection, create offer, set local description, and send it to the new viewer. 2) New viewer will set the remote description, create answer, set local description and send the answer back to the broadcaster. 3)Broadcaster can receive answer, set remote description, but I could not create ice candidate.
Any insight why the RTCPeerConnection.onicecandidate do not fire??
"use strict";
// config
//Acess camera from the http://localhost:2013 is allowed because it is considered a safe environment
//Acess camera from the http://192.168.10.7:2013 is considered a non secure environment (therefore require https)
//var serverIP = "http://192.168.10.7:2013";
//var serverIP = "https://172.21.2.197:2013";
//var serverIP = "https://172.20.10.12:2013";
var serverIP = "https://localhost:2013";
// RTCPeerConnection Options
var server = {
// Uses Google's STUN server
iceServers: [{
"url": "stun:stun.xten.com"
},
{
// Use my TURN server on DigitalOcean
'url': 'turn:numb.viagenie.ca',
'credential': 'sunghiep',
'username': '[email protected]'
}]
};
//DOM Objects
var btnSend = document.getElementById('btn-send');
var btnVideoStop = document.getElementById('btn-video-stop');
var btnVideoStart = document.getElementById('btn-video-start');
var btnVideoJoin = document.getElementById('btn-video-join');
var localVideo = document.getElementById('local-video');
var remoteVideo = document.getElementById('remote-video');
var inputRoomName = document.getElementById('room-name');
//WebRTC Objects
var signallingServer;
var localStream;
var viewPeerConnection;
var sdpConstraints = {
optional: [],
mandatory: {
OfferToReceiveVideo: true
}
};
// declare RTCPeerConnection
window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection ||
window.webkitRTCPeerConnection || window.msRTCPeerConnection;
// declare RTCSessionDescription (SDP)
window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription ||
window.webkitRTCSessionDescription || window.msRTCSessionDescription;
// declare the getUserMedia
navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia ||
navigator.webkitGetUserMedia || navigator.msGetUserMedia;
//in other example I use: navigator.mediaDevices.getUserMedia. WHY is different here???? It is because I use adapter.js
window.SignallingServer = window.SignallingServer;
var peers = [];
// start the video a Room
btnVideoStart.onclick = function(e) {
e.preventDefault();
//Create room
createRoom()
};
btnVideoJoin.onclick = function(e) {
e.preventDefault();
// View video
trace("Click on Join");
joinRoom()
};
btnVideoStop.onclick = function(e) {
e.preventDefault();
// stop video stream
if (localStream != null) {
localStream.getVideoTracks().forEach(function (track) {
track.stop();
});
}
// kill all connections
if (localPeerConnection != null) {
localPeerConnection.removeStream(localStream);
localPeerConnection.close();
signallingServer.close();
localVideo.src = "";
remoteVideo.src = "";
}
btnVideoStart.disabled = false;
btnVideoJoin.disabled = false;
btnVideoStop.disabled = true;
};
function createRoom(){
//get room's name
var room = inputRoomName.value;
if (room == undefined || room.length <= 0) {
alert('Please enter room name');
return;
}
// create local data channel, send it to remote
navigator.getUserMedia({
video: true,
audio: true
}, function(stream) {
// get and save local stream
trace('Got stream, saving it now and starting RTC conn');
//show video on video object
localStream = stream;
localVideo.src = window.URL.createObjectURL(stream);
}, errorHandler);
btnVideoStart.disabled = true;
btnVideoJoin.disabled = true;
btnVideoStop.disabled = false;
// create signalling server
signallingServer = new SignallingServer(room, serverIP);
//create room
signallingServer.create();
// a remote peer has joined room, initiate sdp exchange
signallingServer.onGuestJoined = function(room, socketId) {
trace('Guest(' + socketId +') joined room ['+ room +']! ');
var guestId = socketId;
var roomName = room;
// Create new peer connection for new joiner
//trace("create new RTCPeerConnection");
//var pc = new RTCPeerConnection(server);
//peers.set(guestId, pc);
peers[socketId] = new RTCPeerConnection(server);
trace(peers);
// send ice to the new joiner
trace('Collecting ices ...');
peers[socketId].onicecandidate = function(evt) {
trace('ice ready ...');
if (evt.candidate){
signallingServer.sendICECandidate(room, socketId, evt.candidate);
}
};
//create local data channel, share remote
navigator.getUserMedia({
video: true,
audio: true
}, function(stream) {
// get and save local stream
trace('Got stream, saving it now and starting RTC conn');
// Add stream to peer connection
localStream = stream;
peers[socketId].addStream(localStream);
}, errorHandler);
// Create offer,
peers[socketId].createOffer(function(sessionDescription) {
trace('Created offer');
// Set local description
peers[socketId].setLocalDescription(sessionDescription, function(){
// Send local sdp to remote
trace('Set local sdp success');
signallingServer.sendOffer(guestId, roomName, sessionDescription);
trace('Sending sdp offer');
trace(peers);
}, function(){
trace('Set local sdp failed');
});
}, function(){
trace('Create offer failed');
});
};
//Receive the answer
signallingServer.onReceiveAnswer = function(sender, sdp) {
trace('Receive remote sdp with answer');
// Step 7 - Set the remote SDP
peers[sender].setRemoteDescription(new RTCSessionDescription(sdp), function () {
trace('Set Remote Description success');
}, function () {
trace('Set Remote Description fail');
});
trace(peers);
};
// when room is full, alert user
signallingServer.onRoomFull = function(room) {
window.alert('Room "' + room +
'"" is full! Please join or create another room');
};
}
function joinRoom(){
//get room's name
var room = inputRoomName.value;
if (room == undefined || room.length <= 0) {
alert('Please enter room name');
return;
}
btnVideoStart.disabled = true;
btnVideoJoin.disabled = true;
btnVideoStop.disabled = false;
// Step 1 - Create peer connection
viewPeerConnection = new RTCPeerConnection(server);
// create signalling server
signallingServer = new SignallingServer(room, serverIP);
//join room
signallingServer.join();
// got sdp offer
signallingServer.onReceiveOffer = function(offerCreatorId, roomName, sdp) {
// Step 7 - Set remote SDP
viewPeerConnection.setRemoteDescription(new RTCSessionDescription(sdp), function() {
trace('Set Remote Description');
trace(sdp);
// Step 8 - Create an answer
var answerPromise = viewPeerConnection.createAnswer();
answerPromise.then(function(answer){
// Step 9 - Set local description from the incoming SDP
trace('Set Local Description');
trace(answer);
viewPeerConnection.setLocalDescription(answer)});
answerPromise.then(function(answer){
trace('Send Answer Description');
trace(answer);
// Step 10 - Send local sdp(answer) to remote
signallingServer.sendAnswer(offerCreatorId, roomName, answer);
}).catch(handleGetUserMediaError);
});
};
// ICE Candidate is created when cannot establish the peer connection due to NAT/FIREWALL
// when received ICE candidate info
signallingServer.onReceiveICECandidate = function(candidate) {
trace('Add ice candidate');
viewPeerConnection.addIceCandidate(new RTCIceCandidate(candidate));
};
viewPeerConnection.onaddstream = function(data) {
trace("stream ready");
remoteVideo.src = window.URL.createObjectURL(data.stream);
};
}
function errorHandler(error) {
console.error('Something went wrong!');
console.error(error);
}
function trace(text) {
console.info(text);
}
function handleGetUserMediaError(){
trace("getUserMedia Error")
}
<!DOCTYPE html>
<html lang="en">
<!--[if lt IE 7]>
<html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="">
<![endif]-->
<!--[if IE 7]>
<html class="no-js lt-ie9 lt-ie8" lang="">
<![endif]-->
<!--[if IE 8]>
<html class="no-js lt-ie9" lang="">
<![endif]-->
<!--[if gt IE 8]>
<!-->
<html class="no-js" lang="">
<!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/bootstrap.min.css">
<style>
body {
padding-top: 50px;
padding-bottom: 20px;
}
</style>
<link rel="stylesheet" href="css/bootstrap-theme.min.css">
<link rel="stylesheet" href="css/main.css">
<script src="js/vendor/modernizr-2.8.3-respond-1.4.2.min.js"></script>
</head>
<body>
<!--[if lt IE 8]>
<p class="browserupgrade">
You are using an <strong>outdated</strong>
browser. Please
<a href="http://browsehappy.com/">upgrade your browser</a>
to improve your experience.
</p>
<![endif]-->
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">WebRTC Video Chat</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<!-- chatroom name form -->
<form class="navbar-form navbar-right form-inline">
<div class="form-group">
<input class="form-control" type="text" id="room-name" placeholder="Room name"/>
</div>
<button class="btn btn-primary" id="btn-video-start">Create Live Broadcast</button>
<button class="btn btn-default" id="btn-video-join">Join</button>
<button class="btn btn-default" disabled id="btn-video-stop">Stop</button>
</form>
</div>
<!--/.navbar-collapse --> </div>
</nav>
<div class="container main">
<div class="row videos">
<div class="remote-video">
<video width="280" height="250" autoplay="true" id="remote-video"></video>
</div>
<div class="local-video">
<video width="560" height="500" autoplay="true" id="local-video" muted></video>
</div>
</div>
</div>
</div>
<!-- /container -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-1.11.2.min.js"><\/script>')</script>
<script src="js/vendor/bootstrap.min.js"></script>
<script src="js/vendor/socket.io.js"></script>
<script src="js/main.js"></script>
<!--<script src="js/adapter.js"></script>-->
<script src="js/signalling.js"></script>
<!--<script src="//localhost:9010/livereload.js"></script>-->
<!-- Google Analytics: change UA-XXXXX-X to be your site's ID. -->
<script>
(function (b, o, i, l, e, r) {
b.GoogleAnalyticsObject = l;
b[l] || (b[l] =
function () {
(b[l].q = b[l].q || []).push(arguments)
});
b[l].l = +new Date;
e = o.createElement(i);
r = o.getElementsByTagName(i)[0];
e.src = '//www.google-analytics.com/analytics.js';
r.parentNode.insertBefore(e, r)
}(window, document, 'script', 'ga'));
ga('create', 'UA-XXXXX-X', 'auto');
ga('send', 'pageview');
</script>
</body>
</html>
In onGuestJoined
, you're calling createOffer
before addStream
. This will create an offer without any accommodation for media in it, or fail:
In Firefox, it'll fail with:
InternalError: Cannot create an offer with no local tracks, no
offerToReceiveAudio/Video, and no DataChannel.
In Chrome, it appears instead to create an unusable offer.
In neither case will things proceed, which is why ICE candidates are not firing.
Make sure to call createOffer
from the getUserMedia
success callback, or better yet, since you seem to be mixing callback and promise APIs in this code, use promises throughout for a little more clarity:
navigator.mediaDevices.getUserMedia({video: true, audio: true})
.then(stream => {
localStream = stream;
peers[socketId].addStream(localStream);
return peers[socketId].createOffer();
})
.then(desc => peers[socketId].setLocalDescription(desc))
.then(() => signallingServer.sendOffer(guestId, roomName,
peers[socketId].localDescription))
.catch(errorHandler);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With