I have stood a fully functional webRTC Video Conferencing client using Quickblox Javascript SDK + Angular + webRTC. A strange thing is happening, Every time I clear cache and log in from scratch, when I initiate a call I am getting the following error:
MediaStream {id: "iAmALongalphanumericStringThatGoesHere", active: true, onaddtrack: null, onremovetrack: null, onactive: null…}
quickblox.min.js:86149 [QBWebRTC]: Call, extension: {"name":"Erik Grosskurth","id":6184}
quickblox.min.js:86149 [QBWebRTC]: _createPeer, iceServers: {"iceServers":[{"url":"stun:stun.l.google.com:19302","urls":"stun:stun.l.google.com:19302"},{"url":"stun:turn.quickblox.com","username":"quickblox","credential":"iAmALongalphanumericStringThatGoesHere","urls":"stun:turn.quickblox.com"},{"url":"turn:turn.quickblox.com:3478?transport=udp","username":"quickblox","credential":"iAmALongalphanumericStringThatGoesHere","urls":"turn:turn.quickblox.com:3478?transport=udp"},{"url":"turn:turn.quickblox.com:3478?transport=tcp","username":"quickblox","credential":"iAmALongalphanumericStringThatGoesHere","urls":"turn:turn.quickblox.com:3478?transport=tcp"}]}
quickblox.min.js:86149 [QBWebRTC]: RTCPeerConnection init. userID: 6184, sessionID: 73eabb0a-21f1-4aa4-b928-f669090041d3, type: offer
telemed.js:467 null
quickblox.min.js:86149 [QBWebRTC]: getAndSetLocalSessionDescription success
quickblox.min.js:86149 [QBWebRTC]: _startDialingTimer, dialingTimeInterval: 5000
quickblox.min.js:86149 [QBWebRTC]: _dialingCallback, answerTimeInterval: 0
quickblox.min.js:73302 Uncaught TypeError: Cannot read property 'send' of undefined
at Strophe.Websocket._onIdle (quickblox.min.js:73302)
at Strophe.Connection._onIdle (quickblox.min.js:71559)
at Strophe.Connection.flush (quickblox.min.js:70444)
at Strophe.Websocket._send (quickblox.min.js:73407)
at Strophe.Connection.send (quickblox.min.js:70429)
at WebRTCSignalingProvider.sendMessage (quickblox.min.js:87369)
at WebRTCSession.processCall (quickblox.min.js:86798)
at _dialingCallback (quickblox.min.js:85422)
at RTCPeerConnection._startDialingTimer (quickblox.min.js:85429)
at quickblox.min.js:86422
Funny thing is that if I refresh the page, It works fine with no problem at all. I had seen this during development when I had a $scope issue but I have backtracked and cannot identify when it started happening. Can someone identify what the cause of this error is?
Here is my controller code:
app.controller('patientCtrl', function($scope, $http, $location) {
QB.init(QBApp.appId, QBApp.authKey, QBApp.authSecret, config);
$scope.peers = [];
$scope.occupants = [];
$scope.recipient = {};
$scope.recipients = {};
$scope.session = {};
$scope.dialogs = {};
$scope.modal = false;
$scope.callWaiting = false;
$scope.$watch('peers');
$scope.$watch('occupants');
$scope.$watch('session');
$scope.$watch('modal');
$scope.$watch('callOptions');
$scope.$watch('callWaiting');
$scope.$watch('toggleConnCtrl');
$scope.user = JSON.parse(sessionStorage.getItem('userParams'));
var patient = {
userId: $scope.user.id,
password: $scope.user.password,
login: $scope.user.full_name
};
$scope.localMediaParams = {
audio: true,
video: true,
options: {
muted: true,
mirror: true
},
elemId: 'localVideoEl',
optional: {
minWidth: 240,
maxWidth: 320,
minHeight: 160,
maxHeight: 240
}
};
// HANDLE VISIT DATA AND SET UP CHAT
$scope.reqVisit = {
sKey : sessionStorage.getItem('key'),
sType: 'visit',
iObjectId: 1142606//sessionStorage.getItem('sessionId')
};
$http.post('/ws/Util.asmx/returnObject',$scope.reqVisit).then(function(response) {
$scope.visit = response.data.d;
QB.createSession(function(err,result){
if (result) {
QB.login($scope.user, function(loginErr, loginUser){
if (loginErr) {
console.log('log in error');
console.log(loginErr);
}else {
$scope.user = loginUser;
console.log($scope.user);
QB.chat.connect(patient, function(err, result) {
if (result) {
$scope.roomData = JSON.parse(sessionStorage.getItem('userParams'));
$scope.user.user_tags = $scope.roomData.tag_list;
QB.users.update($scope.user.id, {tag_list: $scope.roomData.tag_list}, function(err, user){
if (user) {
console.log('updated room');
} else {
console.log(err);
}
});
$scope.updatePeerList($scope);
QB.chat.dialog.list({name: $scope.user.user_tags}, function(err, resDialogs) {
if (resDialogs) {
if (resDialogs.total_entries === 0) {
var chatParams = {
type: 2,
occupants_ids: $scope.occupants,
name: $scope.user.user_tags
};
QB.chat.dialog.create(chatParams, function(err, createdDialog) {
if (createdDialog) {
console.log(createdDialog);
} else {
console.log(err);
}
});
}else {
angular.forEach(resDialogs.items, function(item, i, arr) {
console.log('item found');
$scope.chatSession = item;
// join room
if ($scope.chatSession.type !== 3) {
QB.chat.muc.join($scope.chatSession.xmpp_room_jid, function() {
});
}
$scope.occupants = [];
$scope.chatSession.occupants_ids.map(function(userId) {
if ($scope.user.id !== userId && $scope.occupants.indexOf(userId) < 1) {
$scope.occupants.push(userId);
$scope.$apply($scope.occupants);
}
});
angular.forEach($scope.occupants, function (user_id) {
if (user_id !== $scope.user.id) {
var msg = {
type: 'chat',
extension: {
notification_type: 1,
_id: $scope.chatSession.xmpp_room_jid,
name: $scope.user.full_name,
occupant: $scope.user.id
}
};
console.log(user_id);
QB.chat.send(user_id, msg);
}
});
});
}
} else {
console.log('error with chat.dialog.list');
console.log(err);
}
});
} else {
console.log('chat.connect failed');
console.log(res);
}
});
}
});
}else if (err) {
console.log(err);
}
});
},function(errorHandler) {
console.log(errorHandler);
$scope.logout();
});
// HANDLE VIDEO CALLING
$scope.startCall = function() {
if (angular.equals($scope.recipients, {})) {
$scope.flyOutPeers = !$scope.flyOutPeers;
alert('Please choose a person to call');
}else {
if (!angular.equals($scope.session, {}) && !angular.equals($scope.session, undefined)) {
console.log('session hasn\'t been started');
$scope.session.stop({});
$scope.session = {};
return false;
}else {
$scope.session = QB.webrtc.createNewSession($scope.occupants, QB.webrtc.CallType.VIDEO);
$scope.modal = true;
$scope.callWaiting = true;
$scope.session.getUserMedia($scope.localMediaParams, function(err, stream) {
if (err){
console.log(err);
}else{
console.log(stream);
$scope.session.call($scope.recipient, function(error) {
console.log(error);
});
}
});
}
}
};
$scope.answerCall = function() {
$scope.modal = false;
$scope.callOptions = false;
$scope.toggleConnCtrl = true;
$scope.session.getUserMedia($scope.localMediaParams, function(err, stream) {
if (err){
console.log(err);
$scope.session.stop({});
}else{
console.log(stream);
$scope.session.accept({});
}
});
};
$scope.declineCall = function() {
$scope.session.reject({});
//$scope.session.stop({});
$scope.modal = false;
$scope.callOptions = false;
$scope.toggleConnCtrl = false;
$scope.session = {};
};
$scope.endCall = function() {
$scope.session.stop({});
$scope.modal = false;
$scope.callWaiting = false;
$scope.toggleConnCtrl = false;
$scope.session = {};
};
// HANDLE LISTENERS
QB.webrtc.getMediaDevices('videoinput').then(function(devices) {
if(devices.length > 1) {
//console.log(devices);
console.log('you have more than one media device')
}
}).catch(function(error) {
console.warn('getMediaDevices', error);
});
// Call was placed
QB.webrtc.onCallListener = function(session, extension) {
$scope.callerData = extension;
$scope.modal = true;$scope.$apply($scope.modal);
$scope.callOptions = true;$scope.$apply($scope.callOptions);
$scope.session = {};$scope.$apply($scope.session);
};
// No answer
QB.webrtc.onUserNotAnswerListener = function(session, userId) {
console.log('User '+session.currentUserID+' is not answering');
$scope.toggleConnCtrl = true;$scope.$apply($scope.toggleConnCtrl);
$scope.modal = false;$scope.$apply($scope.modal);
$scope.callWaiting = false;$scope.$apply($scope.callWaiting);
};
// Call was answered
QB.webrtc.onAcceptCallListener = function(session, userId, extension) {
console.log('User '+session.currentUserID+' just answered');
$scope.toggleConnCtrl = true;$scope.$apply($scope.toggleConnCtrl);
$scope.modal = false;$scope.$apply($scope.modal);
$scope.callWaiting = false;$scope.$apply($scope.callWaiting);
};
// Call was declined
QB.webrtc.onRejectCallListener = function(session, userId, extension) {
console.log('User '+session.currentUserID+' sent you to voicemail');
$scope.toggleConnCtrl = false;$scope.$apply($scope.toggleConnCtrl);
$scope.modal = false;$scope.$apply($scope.modal);
$scope.callWaiting = false;$scope.$apply($scope.callWaiting);
$scope.session = {};$scope.$apply($scope.session);
};
// End call
QB.webrtc.onStopCallListener = function(session, userId, extension) {
console.log('User '+session.currentUserID+' hung up');
$scope.toggleConnCtrl = false;$scope.$apply($scope.toggleConnCtrl);
$scope.modal = false;$scope.$apply($scope.modal);
$scope.callOptions = false;$scope.$apply($scope.callOptions);
$scope.session = {};$scope.$apply($scope.session);
};
QB.webrtc.onRemoteStreamListener = function(session, userID, remoteStream) {
$scope.session.attachMediaStream('remoteVideoEl', remoteStream);
};
QB.webrtc.onSessionConnectionStateChangedListener = function(session, userID, connectionState) {
};
QB.chat.onMessageListener = function onMessage(userId, message) {
if (message.extension && message.extension.notification_type === '1') {
//console.log(message);
console.log(message.extension.name+' just logged on');
$scope.updatePeerList($scope);
}else if (message.extension && message.extension.notification_type === '2') {
//console.log(message);
console.log(message.extension.name+' just logged out');
$scope.updatePeerList($scope);
}
};
// HANDLE USERS
$scope.updatePeerList = function($scope) {
QB.users.get({tags: [$scope.user.user_tags]}, function(err, result){
if (result) {
var newObj = {};
$scope.peers = [];
$scope.occupants = [];
angular.forEach(result.items, function(e) {
if ($scope.user.id !== e.user.id && $scope.occupants.indexOf(e.user.id) < 1) {
$scope.occupants.push(e.user.id);
$scope.$apply($scope.occupants);
}
if (e.user.full_name !== $scope.user.full_name) {
var ONE_HOUR = 60 * 60 * 1000,
d = new Date(e.user.last_request_at);
if (((new Date) - d) < ONE_HOUR) {
newObj.name = e.user.full_name;
newObj.userData = e.user;
newObj.status = true;
$scope.peers.push(newObj);
}else {
newObj.name = e.user.full_name;
newObj.userData = e.user;
newObj.status = false;
$scope.peers.push(newObj);
}
}
});
$scope.$apply($scope.peers);
}else {
console.log('error getting peer list');
console.log(err);
}
});
}
$scope.setRecipient = function(ele, name, index) {
if (angular.equals($scope.recipients, {})) {
$scope.recipients[index] = false;
}else if (!angular.equals($scope.recipients, {}) && $scope.recipients[index]) {
$scope.recipients[index] = true;
}else {
angular.forEach($scope.recipients, function(value, key) {
if (key === index) {
$scope.recipients[index] = true;
}else {
$scope.recipients[key] = false;
}
});
}
if($scope.recipients[index]) {
$scope.recipients[index] = false;
$scope.recipient = "";
} else {
$scope.recipients[index] = true;
$scope.recipient = {
name: name,
id: ele.peer.userData.id
}
}
};
// HANDLE LOG OUT and UNLOAD
$scope.logout = function() {
QB.logout(function(err, result){
if (result) {
// success
} else {
// error
}
});
QB.users.update($scope.user.id, {tag_list: ""}, function(err, user){
if (user) {
console.log('changed rooms');
console.log(user);
} else {
console.log(err);
}
});
console.log('change path');
$location.path('/');
}
$scope.$on('onBeforeUnload', function (e, confirmation) {
confirmation.message = "All data will be lost.";
e.preventDefault();
QB.users.update($scope.user.id, {tag_list: ""}, function(err, user){
if (user) {
console.log('changed rooms');
console.log(user);
} else {
console.log(err);
}
});
var msg = {
type: 'chat',
extension: {
notification_type: 2,
_id: $scope.chatSession.xmpp_room_jid,
name: $scope.user.full_name,
occupant: $scope.user.id
}
};
angular.forEach($scope.occupants, function(e) {
if (e !== $scope.user.id) {
QB.chat.send(e, msg);
}
});
});
$scope.$on('onUnload', function () {
console.log('onUnload'); // Use 'Preserve Log' option in Console
//$scope.logout();
});
});
Here is the block of code from the SDK where the error is occurring:
_onIdle: function () {
var data = this._conn._data;
if (data.length > 0 && !this._conn.paused) {
for (var i = 0; i < data.length; i++) {
if (data[i] !== null) {
var stanza, rawStanza;
if (data[i] === "restart") {
stanza = this._buildStream().tree();
} else {
stanza = data[i];
}
rawStanza = Strophe.serialize(stanza);
console.log(rawStanza);
this._conn.xmlOutput(stanza);
this._conn.rawOutput(rawStanza);
// HERE IS WHERE I GET THE ERROR
this.socket.send(rawStanza);
// HERE IS WHERE I GET THE ERROR
}
}
this._conn._data = [];
}
}
When the call initiates a message is sent to the opponent you are trying to connect with.
Here is the message from the call that doesn't work:
<message to="6184-5@chatcaduceustelemed.quickblox.com" type="headline" id="58d15d3311d18b4a8d000001" xmlns="jabber:client">
<extraParams xmlns="jabber:client">
<name>Erik Grosskurth</name>
<id>6184</id>
<sessionID>5ce4c0e0-02cc-4baa-86b0-91dfe28ac0d4</sessionID>
<callType>1</callType>
<callerID>NaN</callerID>
<opponentsIDs>
<opponentID>6184</opponentID>
</opponentsIDs>
<sdp> </sdp>
<moduleIdentifier>WebRTCVideoChat</moduleIdentifier>
<signalType>call</signalType>
<platform>web</platform>
</extraParams>
</message>
And here is the message after I refresh and the call initiates:
<message to="6184-5@chatcaduceustelemed.quickblox.com" type="headline" id="58d15c8cd15a7a2513000001" xmlns="jabber:client">
<extraParams xmlns="jabber:client">
<name>Erik Grosskurth</name>
<id>6184</id>
<sessionID>1a37ad44-c408-4b1d-bda8-436f3322d7e9</sessionID>
<callType>1</callType>
<callerID>6186</callerID>
<opponentsIDs>
<opponentID>6184</opponentID>
</opponentsIDs>
<sdp></sdp>
<moduleIdentifier>WebRTCVideoChat</moduleIdentifier>
<signalType>call</signalType>
<platform>web</platform>
</extraParams>
</message>
As you can see the CallerID is NaN on the call that doesn't work and is correct on the one that does work. I tried manually setting this initiatorID like this:
$scope.session = QB.webrtc.createNewSession($scope.occupants, QB.webrtc.CallType.VIDEO);
$scope.session.initiatorID = $scope.user.id;
$scope.modal = true;
$scope.callWaiting = true;
$scope.session.getUserMedia($scope.localMediaParams, function(err, stream) {
if (err){
console.log(err);
}else{
console.log(stream);
console.log('placing a call to '+$scope.recipient.name);
$scope.session.call($scope.recipient, function(error) {
if(error) {
console.log(error);
} else {
console.log('successfully placed call with no errors');
}
});
}
});
But that doesn't work. Please can someone explain why this is occurring??
So, if you get an error "Cannot read property 'send' of undefined" to make sure that you init the SDK one time.
If you are using Angular use this
app.run(function ($rootScope) {
QB.init(QBApp.appId, QBApp.authKey, QBApp.authSecret, config);
});