Quickblox Javascript SDK + Angular + webRTC - 无法读取未定义的属性“发送”

Posted

技术标签:

【中文标题】Quickblox Javascript SDK + Angular + webRTC - 无法读取未定义的属性“发送”【英文标题】:Quickblox Javascript SDK + Angular + webRTC - Cannot read property 'send' of undefined 【发布时间】:2017-03-21 13:48:04 【问题描述】:

我有一个使用 Quickblox javascript SDK + Angular + webRTC 的功能齐全的 webRTC 视频会议客户端。发生了一件奇怪的事情,每次我清除缓存并从头开始登录时,当我发起呼叫时,我都会收到以下错误:

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

有趣的是,如果我刷新页面,它可以正常工作,完全没有问题。当我遇到 $scope 问题时,我在开发过程中看到了这一点,但我已经回溯并且无法确定它何时开始发生。谁能确定这个错误的原因是什么?

这是我的控制器代码:

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();
        );





    );

这是发生错误的 SDK 中的代码块:

 _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 = [];
        
    

当呼叫发起时,一条消息会发送给您尝试连接的对手。

以下是通话失败的消息:

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

这是我刷新并启动呼叫后的消息:

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

如您所见,CallerID 在不起作用的呼叫上是 NaN,而在起作用的呼叫上是正确的。我尝试像这样手动设置这个initiatorID:

$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');
            
        );
    
);

但这不起作用。请问有人能解释一下为什么会这样吗??

【问题讨论】:

所以我在会话对象上发现了一点我的>>initiatorId = NaN 尝试手动设置initiatorID,但报同样的错误 您能否提供如何重现此错误的分步指南。还有你用什么浏览器。 第 1 步:session = QB.webrtc.createNewSession,第 2 步:session.getUserMedia,第 3 步:session.call 第 4 步:呼叫,分机:"name":"Erik Marta"," id":6184 Step 5:: _createPeer, Step 6: RTCPeerConnection init Step 7: getAndSetLocalSessionDescription 成功 Step 8: _startDialingTimer, dialingTimeInterval: 5000 STEP 9: _dialingCallback 我在 Chrome 版本 57.0.2987.98(64 位)和 Firefox 52.0(32 位)之间来回切换 【参考方案1】:

因此,如果您收到错误“无法读取未定义的属性 'send'”,请确保您初始化 SDK 一次。

如果您使用的是 Angular,请使用此

 app.run(function ($rootScope) 
  QB.init(QBApp.appId, QBApp.authKey, QBApp.authSecret, config);
 );

【讨论】:

【参考方案2】:

对于每个调用,您需要创建新的 WebRTC 会话。

我找到了这行代码。

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

尝试调用并设置一个空对象,而不是像这样的 $scope.recipient:

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

另外,您能否检查一下您的 WebRTC 会话是否存在于 QB.chat.connect 回调中?在这里您可以找到代码示例。

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

你的解决方案应该是这样的:

if (!angular.equals($scope.session, ) && !angular.equals($scope.session, undefined)) 
    $scope.session.stop();
    $scope.session = ;

【讨论】:

我已经确定了这个问题,它是由这个直接引起的:***.com/questions/42940140/… 当用户 1 登录(创建用户)并将其 tag_list 设置为房间字符串时,问题就出现了。当用户 2 登录(创建用户)时,尽管设置过滤器以通过 tag_list 返回对话框,但它不会返回用户 1 创建的聊天对话框,因为在数据库中生成该对话框时尚未创建用户 2 的对手 ID。因此,当用户 2 登录(创建其用户)时,它会检查具有匹配 tag_list 的对话框,但由于用户 2 的用户 ID 在创建时未附加到聊天对话框,所以 total_results 返回为 0。所以我的代码然后生成一个新对话框正确地与对手打交道。 问题是用户 1 无法收到用户 2 已登录的通知 调试包吐出一个xml消息,对手被识别,但发起者ID是NaN,我相信这是因为两个用户登录到不同的房间。 另外,我已经在 startCall 点击事件中添加了建议的代码块(您的解决方案应该是这样的)。你是说它应该去别的地方吗?请查看我上面的代码并查看 $scope.startCall 函数。【参考方案3】:

此处设置的calllerId:https://github.com/QuickBlox/quickblox-javascript-sdk/blob/gh-pages/src/modules/webrtc/qbWebRTCClient.js#L93

当您创建一个新会话时,您可以输入您的 ID。你能试着这样做吗? 如果这样可以解决此问题,我认为您在使用聊天连接时遇到了问题。

请提供反馈。

【讨论】:

以上是关于Quickblox Javascript SDK + Angular + webRTC - 无法读取未定义的属性“发送”的主要内容,如果未能解决你的问题,请参考以下文章

如何管理 quickblox 会话 Javascript SDK

无法使用 QuickBlox Javascript SDK 发送聊天消息

quickblox javascript sdk 一对一视频聊天

Quickblox Javascript SDK + Angular + webRTC - Firefox 错误:SecurityError:操作不安全

Quickblox Javascript SDK + Angular + webRTC - 无法读取未定义的属性“发送”

Quickblox JS SDK notifyOccupants 创建对话框