From dd2b933a0d92f24421a56ec350ae0f80e32d2d3e Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 16 Sep 2014 14:46:13 +0100 Subject: [PATCH] Use event age to recognise which calls are current and which aren't and hence support answering calls that were placed before we loaded the page. --- webclient/app-controller.js | 4 ++ webclient/components/matrix/matrix-call.js | 26 ++++++-- .../components/matrix/matrix-phone-service.js | 62 ++++++++++++++++--- webclient/index.html | 3 +- 4 files changed, 79 insertions(+), 16 deletions(-) diff --git a/webclient/app-controller.js b/webclient/app-controller.js index 6c3759878..633862448 100644 --- a/webclient/app-controller.js +++ b/webclient/app-controller.js @@ -130,6 +130,10 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even angular.element('#ringAudio')[0].pause(); angular.element('#ringbackAudio')[0].pause(); angular.element('#busyAudio')[0].play(); + } else if (newVal == 'ended' && oldVal == 'invite_sent' && $rootScope.currentCall.hangupParty == 'local' && $rootScope.currentCall.hangupReason == 'invite_timeout') { + angular.element('#ringAudio')[0].pause(); + angular.element('#ringbackAudio')[0].pause(); + angular.element('#busyAudio')[0].play(); } else if (oldVal == 'invite_sent') { angular.element('#ringbackAudio')[0].pause(); } else if (oldVal == 'ringing') { diff --git a/webclient/components/matrix/matrix-call.js b/webclient/components/matrix/matrix-call.js index fd21198d2..650c415c8 100644 --- a/webclient/components/matrix/matrix-call.js +++ b/webclient/components/matrix/matrix-call.js @@ -53,6 +53,8 @@ angular.module('MatrixCall', []) this.candidateSendTries = 0; } + MatrixCall.CALL_TIMEOUT = 60000; + MatrixCall.prototype.createPeerConnection = function() { var stunServer = 'stun:stun.l.google.com:19302'; var pc; @@ -86,6 +88,14 @@ angular.module('MatrixCall', []) this.direction = 'inbound'; }; + // perverse as it may seem, sometimes we want to instantiate a call with a hangup message + // (because when getting the state of the room on load, events come in reverse order and + // we want to remember that a call has been hung up) + MatrixCall.prototype.initWithHangup = function(msg) { + this.msg = msg; + this.state = 'ended'; + }; + MatrixCall.prototype.answer = function() { console.log("Answering call "+this.call_id); var self = this; @@ -188,14 +198,12 @@ angular.module('MatrixCall', []) console.log("Ignoring remote ICE candidate because call has ended"); return; } - var candidateObject = new RTCIceCandidate({ - sdpMLineIndex: cand.label, - candidate: cand.candidate - }); - this.peerConn.addIceCandidate(candidateObject, function() {}, function(e) {}); + this.peerConn.addIceCandidate(new RTCIceCandidate(cand), function() {}, function(e) {}); }; MatrixCall.prototype.receivedAnswer = function(msg) { + if (this.state == 'ended') return; + this.peerConn.setRemoteDescription(new RTCSessionDescription(msg.answer), this.onSetRemoteDescriptionSuccess, this.onSetRemoteDescriptionError); this.state = 'connecting'; }; @@ -213,11 +221,17 @@ angular.module('MatrixCall', []) var content = { version: 0, call_id: this.call_id, - offer: description + offer: description, + lifetime: MatrixCall.CALL_TIMEOUT }; this.sendEventWithRetry('m.call.invite', content); var self = this; + $timeout(function() { + self.hangupReason = 'invite_timeout'; + self.hangup(); + }, MatrixCall.CALL_TIMEOUT); + $rootScope.$apply(function() { self.state = 'invite_sent'; }); diff --git a/webclient/components/matrix/matrix-phone-service.js b/webclient/components/matrix/matrix-phone-service.js index 2d0732a8d..3e99a7d11 100644 --- a/webclient/components/matrix/matrix-phone-service.js +++ b/webclient/components/matrix/matrix-phone-service.js @@ -24,22 +24,52 @@ angular.module('matrixPhoneService', []) matrixPhoneService.INCOMING_CALL_EVENT = "INCOMING_CALL_EVENT"; matrixPhoneService.REPLACED_CALL_EVENT = "REPLACED_CALL_EVENT"; matrixPhoneService.allCalls = {}; + // a place to save candidates that come in for calls we haven't got invites for yet (when paginating backwards) + matrixPhoneService.candidatesByCall = {}; matrixPhoneService.callPlaced = function(call) { matrixPhoneService.allCalls[call.call_id] = call; }; $rootScope.$on(eventHandlerService.CALL_EVENT, function(ngEvent, event, isLive) { - if (!isLive) return; // until matrix supports expiring messages if (event.user_id == matrixService.config().user_id) return; + var msg = event.content; + if (event.type == 'm.call.invite') { + if (event.age == undefined || msg.lifetime == undefined) { + // if the event doesn't have either an age (the HS is too old) or a lifetime + // (the sending client was too old when it sent it) then fall back to old behaviour + if (!isLive) return; // until matrix supports expiring messages + } + + if (event.age > msg.lifetime) { + console.log("Ignoring expired call event of type "+event.type); + return; + } + + var call = undefined; + if (!isLive) { + // if this event wasn't live then this call may already be over + call = matrixPhoneService.allCalls[msg.call_id]; + if (call && call.state == 'ended') { + return; + } + } + var MatrixCall = $injector.get('MatrixCall'); var call = new MatrixCall(event.room_id); call.call_id = msg.call_id; call.initWithInvite(msg); matrixPhoneService.allCalls[call.call_id] = call; + // if we stashed candidate events for that call ID, play them back now + if (!isLive && matrixPhoneService.candidatesByCall[call.call_id] != undefined) { + for (var i = 0; i < matrixPhoneService.candidatesByCall[call.call_id].length; ++i) { + call.gotRemoteIceCandidate(matrixPhoneService.candidatesByCall[call.call_id][i]); + } + } + // Were we trying to call that user (room)? var existingCall; var callIds = Object.keys(matrixPhoneService.allCalls); @@ -79,21 +109,35 @@ angular.module('matrixPhoneService', []) call.receivedAnswer(msg); } else if (event.type == 'm.call.candidates') { var call = matrixPhoneService.allCalls[msg.call_id]; - if (!call) { + if (!call && isLive) { console.log("Got candidates for unknown call ID "+msg.call_id); return; - } - for (var i = 0; i < msg.candidates.length; ++i) { - call.gotRemoteIceCandidate(msg.candidates[i]); + } else if (!call) { + if (matrixPhoneService.candidatesByCall[msg.call_id] == undefined) { + matrixPhoneService.candidatesByCall[msg.call_id] = []; + } + matrixPhoneService.candidatesByCall[msg.call_id] = matrixPhoneService.candidatesByCall[msg.call_id].concat(msg.candidates); + } else { + for (var i = 0; i < msg.candidates.length; ++i) { + call.gotRemoteIceCandidate(msg.candidates[i]); + } } } else if (event.type == 'm.call.hangup') { var call = matrixPhoneService.allCalls[msg.call_id]; - if (!call) { + if (!call && isLive) { console.log("Got hangup for unknown call ID "+msg.call_id); - return; + } else if (!call) { + // if not live, store the fact that the call has ended because we're probably getting events backwards so + // the hangup will come before the invite + var MatrixCall = $injector.get('MatrixCall'); + var call = new MatrixCall(event.room_id); + call.call_id = msg.call_id; + call.initWithHangup(msg); + matrixPhoneService.allCalls[msg.call_id] = call; + } else { + call.onHangupReceived(); + delete(matrixPhoneService.allCalls[msg.call_id]); } - call.onHangupReceived(); - delete(matrixPhoneService.allCalls[msg.call_id]); } }); diff --git a/webclient/index.html b/webclient/index.html index 9eea08215..7e4dcb834 100644 --- a/webclient/index.html +++ b/webclient/index.html @@ -62,7 +62,8 @@ Call Connecting... Call Connected Call Rejected - Call Canceled + Call Canceled + User Not Responding Call Ended Call Canceled Call Ended