From 9e2bb5b37bb80a66c7712f0f701e8eefb90b20ee Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 16 Nov 2017 16:29:45 +0000 Subject: [PATCH] Allow answer to be called again after failing * Store the answer we generate so if we fail to send it, we can try to send it again (doing the same again doesn't work as webrtc is in the wrong state). * Don't send ICE candidates if the call is ringing: queue them up so we can send them later if we manage to actually send the answer. --- src/webrtc/call.js | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/webrtc/call.js b/src/webrtc/call.js index 9f2d8a907..4a1c41b4d 100644 --- a/src/webrtc/call.js +++ b/src/webrtc/call.js @@ -70,6 +70,8 @@ function MatrixCall(opts) { this.mediaPromises = Object.create(null); this.screenSharingStream = null; + + this._answerContent = null; } /** The length of time a call can be ringing for. */ MatrixCall.CALL_TIMEOUT_MS = 60000; @@ -375,6 +377,11 @@ MatrixCall.prototype.answer = function() { debuglog("Answering call %s of type %s", this.callId, this.type); const self = this; + if (self._answerContent) { + self._sendAnswer(); + return; + } + if (!this.localAVStream && !this.waitForLocalAVStream) { this.webRtc.getUserMedia( _getUserMediaVideoContraints(this.type), @@ -561,6 +568,27 @@ MatrixCall.prototype._maybeGotUserMediaForInvite = function(stream) { setState(self, 'create_offer'); }; +MatrixCall.prototype._sendAnswer = function(stream) { + sendEvent(this, 'm.call.answer', this._answerContent).then(() => { + setState(this, 'connecting'); + // If this isn't the first time we've tried to send the answer, + // we may have candidates queued up, so send them now. + _sendCandidateQueue(this); + }).catch((error) => { + // We've failed to answer: back to the ringing state + setState(this, 'ringing'); + this.client.cancelPendingEvent(error.event); + this.emit( + "error", + callError( + MatrixCall.ERR_UNKNOWN_DEVICES, + "Unknown devices present in the room", + ), + ); + throw error; + }); +}; + /** * Internal * @private @@ -609,7 +637,7 @@ MatrixCall.prototype._maybeGotUserMediaForAnswer = function(stream) { self.peerConn.createAnswer(function(description) { debuglog("Created answer: " + description); self.peerConn.setLocalDescription(description, function() { - const content = { + self._answerContent = { version: 0, call_id: self.callId, answer: { @@ -617,8 +645,7 @@ MatrixCall.prototype._maybeGotUserMediaForAnswer = function(stream) { type: self.peerConn.localDescription.type, }, }; - sendEvent(self, 'm.call.answer', content); - setState(self, 'connecting'); + self._sendAnswer(); }, function() { debuglog("Error setting local description!"); }, constraints); @@ -962,6 +989,13 @@ const sendCandidate = function(self, content) { // Sends candidates with are sent in a special way because we try to amalgamate // them into one message self.candidateSendQueue.push(content); + + // Don't send the ICE candidates yet if the call is in the ringing state: this + // means we tried to pick (ie. started generating candidates) and then failed to + // send the answer and went back to the ringing state. Queue up the candidates + // to send if we sucessfully send the answer. + if (self.state == 'ringing') return; + if (self.candidateSendTries === 0) { setTimeout(function() { _sendCandidateQueue(self);