0

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??

Erik Grosskurth
  • 3,762
  • 5
  • 29
  • 44
  • So digging in a little I have found on the session object my >> initiatorId = NaN << Why would this be NaN??? – Erik Grosskurth Mar 21 '17 at 16:24
  • Tried manually setting the initiatorID but it threw the same error – Erik Grosskurth Mar 21 '17 at 16:35
  • could you provide step-by-step guide how to reproduce this error. And also which browser do you use. – Rubycon Mar 21 '17 at 17:10
  • Step 1: session = QB.webrtc.createNewSession, Step 2: session.getUserMedia, Step 3: session.call Step 4: Call, extension: {"name":"Erik Marta","id":6184} Step 5:: _createPeer, Step 6: RTCPeerConnection init Step 7: getAndSetLocalSessionDescription success Step 8: _startDialingTimer, dialingTimeInterval: 5000 STep 9: _dialingCallback – Erik Grosskurth Mar 21 '17 at 17:28
  • I am back and forth between Chrome Version 57.0.2987.98 (64-bit) and Firefox 52.0 (32-bit) – Erik Grosskurth Mar 21 '17 at 17:33

3 Answers3

2

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);
 });
Dima
  • 46
  • 4
0

For each call, you need to create new WebRTC session.

I find this line of code.

$scope.session.call($scope.recipient, function(error) {
    console.log(error);
});

Try to make a call and set an empty object instead of $scope.recipient like this:

$scope.session.call({}, function(error) {
    console.log(error);
});

Also, can you check, if your WebRTC session exists on QB.chat.connect callback? Here you can find an example of code.

https://github.com/QuickBlox/quickblox-javascript-sdk/blob/gh-pages/samples/webrtc/js/app.js#L236

Your solution should be like this:

if (!angular.equals($scope.session, {}) && !angular.equals($scope.session, undefined)) {
    $scope.session.stop({});
    $scope.session = {};
}
Iegor Kozakov
  • 351
  • 3
  • 3
  • I have determined the issue and it is directly caused by this: http://stackoverflow.com/questions/42940140/quickblox-javascript-sdk-angular-webrtc-chat-room-filter-by-name-is-not-sh – Erik Grosskurth Mar 23 '17 at 14:12
  • The problem comes when User 1 logs in (creates user) and sets its tag_list to the room string. When user 2 logs in (creates user) it doesn't return the chat dialog created by user 1 despite setting filters to return dialogs by tag_list because user 2's opponent id had not been created when that dialog was generated in the DB. So when user 2 logs in (creates its user) it checks for dialogs with matching tag_list but since user 2's user id wasn't attached to the chat dialog when it was created the total_results return as 0. So my code then generates a new dialog with the opponents correctly. – Erik Grosskurth Mar 23 '17 at 14:17
  • Problem is User 1 has no way of receiving notification that user 2 has logged in – Erik Grosskurth Mar 23 '17 at 14:17
  • The debug packet spits out an xml message with the opponents are identified but the initiator id is NaN and I believe its because the two users are logged into different rooms. – Erik Grosskurth Mar 23 '17 at 15:04
  • Also I already have the suggested block of code (Your solution should be like this) added on the startCall click event. Are you suggesting it should go somewhere else? Please see my code above and look at the $scope.startCall function. – Erik Grosskurth Mar 23 '17 at 18:27
  • Also can you explain why adding an extension to the call would cause issue? Is there a reason I would send {} instead of $scope.recipients? – Erik Grosskurth Mar 23 '17 at 18:28
0

calllerId set up here: https://github.com/QuickBlox/quickblox-javascript-sdk/blob/gh-pages/src/modules/webrtc/qbWebRTCClient.js#L93

When you create a new session you could put your Id. Could you try to make so? If this will resolve this issue I think you have a trouble with chat connect.

Give feedback, please.

Dima
  • 46
  • 4