diff options
author | David Baker <dbkr@matrix.org> | 2014-08-28 19:03:34 +0100 |
---|---|---|
committer | David Baker <dbkr@matrix.org> | 2014-08-28 19:03:34 +0100 |
commit | ca7426eee0f1d421815ff1921bfd2a5cd03c960f (patch) | |
tree | 2d85533f4462c81481369c8ffa90c97e38f00fef /webclient/components | |
parent | WIP voip support on web client (diff) | |
download | synapse-ca7426eee0f1d421815ff1921bfd2a5cd03c960f.tar.xz |
First basic working VoIP call support
Diffstat (limited to 'webclient/components')
-rw-r--r-- | webclient/components/matrix/matrix-call.js | 117 | ||||
-rw-r--r-- | webclient/components/matrix/matrix-phone-service.js | 32 |
2 files changed, 134 insertions, 15 deletions
diff --git a/webclient/components/matrix/matrix-call.js b/webclient/components/matrix/matrix-call.js index 1bed843c44..a5f2529b87 100644 --- a/webclient/components/matrix/matrix-call.js +++ b/webclient/components/matrix/matrix-call.js @@ -21,6 +21,7 @@ angular.module('MatrixCall', []) var MatrixCall = function(room_id) { this.room_id = room_id; this.call_id = "c" + new Date().getTime(); + this.state = 'fledgling'; } navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; @@ -30,19 +31,75 @@ angular.module('MatrixCall', []) MatrixCall.prototype.placeCall = function() { self = this; matrixPhoneService.callPlaced(this); - navigator.getUserMedia({audio: true, video: false}, function(s) { self.gotUserMedia(s); }, function(e) { self.getUserMediaFailed(e); }); + navigator.getUserMedia({audio: true, video: false}, function(s) { self.gotUserMediaForInvite(s); }, function(e) { self.getUserMediaFailed(e); }); + self.state = 'wait_local_media'; }; - MatrixCall.prototype.gotUserMedia = function(stream) { + MatrixCall.prototype.initWithInvite = function(msg) { + this.msg = msg; this.peerConn = new window.RTCPeerConnection({"iceServers":[{"urls":"stun:stun.l.google.com:19302"}]}) - this.peerConn.addStream(stream); + self= this; + this.peerConn.oniceconnectionstatechange = function() { self.onIceConnectionStateChanged(); }; + this.peerConn.onicecandidate = function(c) { self.gotLocalIceCandidate(c); }; + this.peerConn.onsignalingstatechange = function() { self.onSignallingStateChanged(); }; + this.peerConn.onaddstream = function(s) { self.onAddStream(s); }; + this.peerConn.setRemoteDescription(new RTCSessionDescription(this.msg.offer), self.onSetRemoteDescriptionSuccess, self.onSetRemoteDescriptionError); + this.state = 'ringing'; + }; + + MatrixCall.prototype.answer = function() { + console.trace("Answering call "+this.call_id); self = this; + navigator.getUserMedia({audio: true, video: false}, function(s) { self.gotUserMediaForAnswer(s); }, function(e) { self.getUserMediaFailed(e); }); + this.state = 'wait_local_media'; + }; + + MatrixCall.prototype.hangup = function() { + console.trace("Rejecting call "+this.call_id); + var content = { + msgtype: "m.call.hangup", + version: 0, + call_id: this.call_id, + }; + matrixService.sendMessage(this.room_id, undefined, content).then(this.messageSent, this.messageSendFailed); + this.state = 'ended'; + }; + + MatrixCall.prototype.gotUserMediaForInvite = function(stream) { + var audioTracks = stream.getAudioTracks(); + for (var i = 0; i < audioTracks.length; i++) { + audioTracks[i].enabled = true; + } + this.peerConn = new window.RTCPeerConnection({"iceServers":[{"urls":"stun:stun.l.google.com:19302"}]}) + self = this; + this.peerConn.oniceconnectionstatechange = function() { self.onIceConnectionStateChanged(); }; + this.peerConn.onsignalingstatechange = function() { self.onSignallingStateChanged(); }; this.peerConn.onicecandidate = function(c) { self.gotLocalIceCandidate(c); }; + this.peerConn.onaddstream = function(s) { self.onAddStream(s); }; + this.peerConn.addStream(stream); this.peerConn.createOffer(function(d) { self.gotLocalOffer(d); }, function(e) { self.getLocalOfferFailed(e); }); + this.state = 'create_offer'; + }; + + MatrixCall.prototype.gotUserMediaForAnswer = function(stream) { + var audioTracks = stream.getAudioTracks(); + for (var i = 0; i < audioTracks.length; i++) { + audioTracks[i].enabled = true; + } + this.peerConn.addStream(stream); + self = this; + var constraints = { + 'mandatory': { + 'OfferToReceiveAudio': true, + 'OfferToReceiveVideo': false + }, + }; + this.peerConn.createAnswer(function(d) { self.createdAnswer(d); }, function(e) {}, constraints); + this.state = 'create_answer'; }; MatrixCall.prototype.gotLocalIceCandidate = function(event) { @@ -59,11 +116,21 @@ angular.module('MatrixCall', []) } MatrixCall.prototype.gotRemoteIceCandidate = function(cand) { - this.peerConn.addIceCandidate(cand); + console.trace("Got ICE candidate from remote: "+cand); + var candidateObject = new RTCIceCandidate({ + sdpMLineIndex: cand.label, + candidate: cand.candidate + }); + this.peerConn.addIceCandidate(candidateObject, function() {}, function(e) {}); + }; + + MatrixCall.prototype.receivedAnswer = function(msg) { + this.peerConn.setRemoteDescription(new RTCSessionDescription(msg.answer), self.onSetRemoteDescriptionSuccess, self.onSetRemoteDescriptionError); + this.state = 'connecting'; }; MatrixCall.prototype.gotLocalOffer = function(description) { - console.trace(description); + console.trace("Created offer: "+description); this.peerConn.setLocalDescription(description); var content = { @@ -73,6 +140,20 @@ angular.module('MatrixCall', []) offer: description }; matrixService.sendMessage(this.room_id, undefined, content).then(this.messageSent, this.messageSendFailed); + this.state = 'invite_sent'; + }; + + MatrixCall.prototype.createdAnswer = function(description) { + console.trace("Created answer: "+description); + this.peerConn.setLocalDescription(description); + var content = { + msgtype: "m.call.answer", + version: 0, + call_id: this.call_id, + answer: description + }; + matrixService.sendMessage(this.room_id, undefined, content).then(this.messageSent, this.messageSendFailed); + this.state = 'connecting'; }; MatrixCall.prototype.messageSent = function() { @@ -88,6 +169,32 @@ angular.module('MatrixCall', []) MatrixCall.prototype.getUserMediaFailed = function() { this.onError("Couldn't start capturing audio! Is your microphone set up?"); }; + + MatrixCall.prototype.onIceConnectionStateChanged = function() { + console.trace("Ice connection state changed to: "+this.peerConn.iceConnectionState); + if (this.peerConn.iceConnectionState == 'completed') { + this.state = 'connected'; + } + }; + + MatrixCall.prototype.onSignallingStateChanged = function() { + console.trace("Signalling state changed to: "+this.peerConn.signalingState); + }; + + MatrixCall.prototype.onSetRemoteDescriptionSuccess = function() { + console.trace("Set remote description"); + }; + MatrixCall.prototype.onSetRemoteDescriptionError = function(e) { + console.trace("Failed to set remote description"+e); + }; + + MatrixCall.prototype.onAddStream = function(event) { + console.trace("Stream added"+event); + var player = new Audio(); + player.src = URL.createObjectURL(event.stream); + player.play(); + }; + return MatrixCall; }]); diff --git a/webclient/components/matrix/matrix-phone-service.js b/webclient/components/matrix/matrix-phone-service.js index 9e296f6939..6f96875103 100644 --- a/webclient/components/matrix/matrix-phone-service.js +++ b/webclient/components/matrix/matrix-phone-service.js @@ -17,19 +17,14 @@ limitations under the License. 'use strict'; angular.module('matrixPhoneService', []) -.factory('matrixPhoneService', ['$rootScope', 'matrixService', 'MatrixCall', 'eventHandlerService', function MatrixCallFactory($rootScope, matrixService, MatrixCall, eventHandlerService) { +.factory('matrixPhoneService', ['$rootScope', '$injector', 'matrixService', 'eventHandlerService', function MatrixPhoneService($rootScope, $injector, matrixService, eventHandlerService) { var matrixPhoneService = function() { - } + }; matrixPhoneService.CALL_EVENT = "CALL_EVENT"; matrixPhoneService.allCalls = {}; - MatrixCall.prototype.placeCall = function() { - self = this; - navigator.getUserMedia({audio: true, video: false}, function(s) { self.gotUserMedia(s); }, function(e) { self.getUserMediaFailed(e); }); - }; - - matrixPhoneService.prototype.callPlaced = function(call) { + matrixPhoneService.callPlaced = function(call) { matrixPhoneService.allCalls[call.call_id] = call; }; @@ -38,17 +33,34 @@ angular.module('matrixPhoneService', []) if (event.user_id == matrixService.config().user_id) return; var msg = event.content; if (msg.msgtype == 'm.call.invite') { + var MatrixCall = $injector.get('MatrixCall'); var call = new MatrixCall(event.room_id); call.call_id = msg.call_id; - $rootScope.$broadcast(matrixPhoneService.CALL_EVENT, call); + call.initWithInvite(msg); matrixPhoneService.allCalls[call.call_id] = call; + $rootScope.$broadcast(matrixPhoneService.CALL_EVENT, call); + } else if (msg.msgtype == 'm.call.answer') { + var call = matrixPhoneService.allCalls[msg.call_id]; + if (!call) { + console.trace("Got answer for unknown call ID "+msg.call_id); + return; + } + call.receivedAnswer(msg); } else if (msg.msgtype == 'm.call.candidate') { - call = matrixPhoneService.allCalls[msg.call_id]; + var call = matrixPhoneService.allCalls[msg.call_id]; if (!call) { console.trace("Got candidate for unknown call ID "+msg.call_id); return; } call.gotRemoteIceCandidate(msg.candidate); + } else if (msg.msgtype == 'm.call.hangup') { + var call = matrixPhoneService.allCalls[msg.call_id]; + if (!call) { + console.trace("Got hangup for unknown call ID "+msg.call_id); + return; + } + call.onHangup(); + matrixPhoneService.allCalls[msg.call_id] = undefined; } }); |