From 7d34a1c108967ad8e5f24f979aecad97595622c8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 27 Aug 2014 18:57:54 +0100 Subject: [PATCH 1/3] WIP voip support on web client --- webclient/app.js | 2 + .../matrix/event-handler-service.js | 1 - webclient/components/matrix/matrix-call.js | 93 +++++++++++++++++++ .../components/matrix/matrix-phone-service.js | 56 +++++++++++ webclient/index.html | 2 + webclient/room/room-controller.js | 18 +++- webclient/room/room.html | 1 + 7 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 webclient/components/matrix/matrix-call.js create mode 100644 webclient/components/matrix/matrix-phone-service.js diff --git a/webclient/app.js b/webclient/app.js index 1d5503ebc0..b52479babe 100644 --- a/webclient/app.js +++ b/webclient/app.js @@ -24,6 +24,8 @@ var matrixWebClient = angular.module('matrixWebClient', [ 'SettingsController', 'UserController', 'matrixService', + 'matrixPhoneService', + 'MatrixCall', 'eventStreamService', 'eventHandlerService', 'infinite-scroll' diff --git a/webclient/components/matrix/event-handler-service.js b/webclient/components/matrix/event-handler-service.js index 6ea0f58bc5..7514770583 100644 --- a/webclient/components/matrix/event-handler-service.js +++ b/webclient/components/matrix/event-handler-service.js @@ -93,7 +93,6 @@ angular.module('eventHandlerService', []) $rootScope.$broadcast(PRESENCE_EVENT, event, isLiveEvent); }; - return { MSG_EVENT: MSG_EVENT, MEMBER_EVENT: MEMBER_EVENT, diff --git a/webclient/components/matrix/matrix-call.js b/webclient/components/matrix/matrix-call.js new file mode 100644 index 0000000000..1bed843c44 --- /dev/null +++ b/webclient/components/matrix/matrix-call.js @@ -0,0 +1,93 @@ +/* +Copyright 2014 matrix.org + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +'use strict'; + +angular.module('MatrixCall', []) +.factory('MatrixCall', ['matrixService', 'matrixPhoneService', function MatrixCallFactory(matrixService, matrixPhoneService) { + var MatrixCall = function(room_id) { + this.room_id = room_id; + this.call_id = "c" + new Date().getTime(); + } + + navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; + + window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection; + + 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); }); + }; + + MatrixCall.prototype.gotUserMedia = function(stream) { + this.peerConn = new window.RTCPeerConnection({"iceServers":[{"urls":"stun:stun.l.google.com:19302"}]}) + this.peerConn.addStream(stream); + self = this; + this.peerConn.onicecandidate = function(c) { self.gotLocalIceCandidate(c); }; + this.peerConn.createOffer(function(d) { + self.gotLocalOffer(d); + }, function(e) { + self.getLocalOfferFailed(e); + }); + }; + + MatrixCall.prototype.gotLocalIceCandidate = function(event) { + console.trace(event); + if (event.candidate) { + var content = { + msgtype: "m.call.candidate", + version: 0, + call_id: this.call_id, + candidate: event.candidate + }; + matrixService.sendMessage(this.room_id, undefined, content).then(this.messageSent, this.messageSendFailed); + } + } + + MatrixCall.prototype.gotRemoteIceCandidate = function(cand) { + this.peerConn.addIceCandidate(cand); + }; + + MatrixCall.prototype.gotLocalOffer = function(description) { + console.trace(description); + this.peerConn.setLocalDescription(description); + + var content = { + msgtype: "m.call.invite", + version: 0, + call_id: this.call_id, + offer: description + }; + matrixService.sendMessage(this.room_id, undefined, content).then(this.messageSent, this.messageSendFailed); + }; + + MatrixCall.prototype.messageSent = function() { + }; + + MatrixCall.prototype.messageSendFailed = function(error) { + }; + + MatrixCall.prototype.getLocalOfferFailed = function(error) { + this.onError("Failed to start audio for call!"); + }; + + MatrixCall.prototype.getUserMediaFailed = function() { + this.onError("Couldn't start capturing audio! Is your microphone set up?"); + }; + + return MatrixCall; +}]); diff --git a/webclient/components/matrix/matrix-phone-service.js b/webclient/components/matrix/matrix-phone-service.js new file mode 100644 index 0000000000..9e296f6939 --- /dev/null +++ b/webclient/components/matrix/matrix-phone-service.js @@ -0,0 +1,56 @@ +/* +Copyright 2014 matrix.org + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +'use strict'; + +angular.module('matrixPhoneService', []) +.factory('matrixPhoneService', ['$rootScope', 'matrixService', 'MatrixCall', 'eventHandlerService', function MatrixCallFactory($rootScope, matrixService, MatrixCall, 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.allCalls[call.call_id] = call; + }; + + $rootScope.$on(eventHandlerService.MSG_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 (msg.msgtype == 'm.call.invite') { + var call = new MatrixCall(event.room_id); + call.call_id = msg.call_id; + $rootScope.$broadcast(matrixPhoneService.CALL_EVENT, call); + matrixPhoneService.allCalls[call.call_id] = call; + } else if (msg.msgtype == 'm.call.candidate') { + call = matrixPhoneService.allCalls[msg.call_id]; + if (!call) { + console.trace("Got candidate for unknown call ID "+msg.call_id); + return; + } + call.gotRemoteIceCandidate(msg.candidate); + } + }); + + return matrixPhoneService; +}]); diff --git a/webclient/index.html b/webclient/index.html index 16f0e8ac5f..5faf165626 100644 --- a/webclient/index.html +++ b/webclient/index.html @@ -26,6 +26,8 @@ + + diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index 6c98db269e..de3738ca0e 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -15,8 +15,8 @@ limitations under the License. */ angular.module('RoomController', ['ngSanitize', 'mFileInput', 'mUtilities']) -.controller('RoomController', ['$scope', '$http', '$timeout', '$routeParams', '$location', 'matrixService', 'eventStreamService', 'eventHandlerService', 'mFileUpload', 'mUtilities', '$rootScope', - function($scope, $http, $timeout, $routeParams, $location, matrixService, eventStreamService, eventHandlerService, mFileUpload, mUtilities, $rootScope) { +.controller('RoomController', ['$scope', '$http', '$timeout', '$routeParams', '$location', 'matrixService', 'eventStreamService', 'eventHandlerService', 'matrixPhoneService', 'mFileUpload', 'MatrixCall', 'mUtilities', '$rootScope', + function($scope, $http, $timeout, $routeParams, $location, matrixService, eventStreamService, eventHandlerService, matrixPhoneService, mFileUpload, MatrixCall, mUtilities, $rootScope) { 'use strict'; var MESSAGES_PER_PAGINATION = 30; var THUMBNAIL_SIZE = 320; @@ -82,6 +82,10 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput', 'mUtilities']) $scope.$on(eventHandlerService.PRESENCE_EVENT, function(ngEvent, event, isLive) { updatePresence(event); }); + + $rootScope.$on(matrixPhoneService.CALL_EVENT, function(ngEvent, call) { + console.trace("incoming call"); + }); $scope.paginateMore = function() { if ($scope.state.can_paginate) { @@ -430,4 +434,14 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput', 'mUtilities']) $scope.loadMoreHistory = function() { paginate(MESSAGES_PER_PAGINATION); }; + + $scope.startVoiceCall = function() { + var call = new MatrixCall($scope.room_id); + call.onError = $scope.onCallError; + call.placeCall(); + } + + $scope.onCallError = function(errStr) { + $scope.feedback = errStr; + } }]); diff --git a/webclient/room/room.html b/webclient/room/room.html index 236ca0a89b..4f5584b568 100644 --- a/webclient/room/room.html +++ b/webclient/room/room.html @@ -98,6 +98,7 @@ + {{ feedback }} From ca7426eee0f1d421815ff1921bfd2a5cd03c960f Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 28 Aug 2014 19:03:34 +0100 Subject: [PATCH 2/3] First basic working VoIP call support --- webclient/components/matrix/matrix-call.js | 119 +++++++++++++++++- .../components/matrix/matrix-phone-service.js | 32 +++-- webclient/room/room-controller.js | 19 +++ webclient/room/room.html | 9 +- 4 files changed, 162 insertions(+), 17 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; + 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; } }); diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index de3738ca0e..c596af820c 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -85,6 +85,9 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput', 'mUtilities']) $rootScope.$on(matrixPhoneService.CALL_EVENT, function(ngEvent, call) { console.trace("incoming call"); + call.onError = $scope.onCallError; + call.onHangup = $scope.onCallHangup; + $scope.currentCall = call; }); $scope.paginateMore = function() { @@ -93,6 +96,15 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput', 'mUtilities']) paginate(MESSAGES_PER_PAGINATION); } }; + + $scope.answerCall = function() { + $scope.currentCall.answer(); + }; + + $scope.hangupCall = function() { + $scope.currentCall.hangup(); + $scope.currentCall = undefined; + }; var paginate = function(numItems) { // console.log("paginate " + numItems); @@ -438,10 +450,17 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput', 'mUtilities']) $scope.startVoiceCall = function() { var call = new MatrixCall($scope.room_id); call.onError = $scope.onCallError; + call.onHangup = $scope.onCallHangup; call.placeCall(); + $scope.currentCall = call; } $scope.onCallError = function(errStr) { $scope.feedback = errStr; } + + $scope.onCallHangup = function() { + $scope.feedback = "Call ended"; + $scope.currentCall = undefined; + } }]); diff --git a/webclient/room/room.html b/webclient/room/room.html index 4f5584b568..dceb7322f5 100644 --- a/webclient/room/room.html +++ b/webclient/room/room.html @@ -98,13 +98,20 @@ - + +
+ Incoming call from {{ currentCall.user_id }} + + +
+ {{ currentCall.state }} {{ feedback }}
{{ state.stream_failure.data.error || "Connection failure" }}
+ From 41d02ab6742643c755f37665c31afa94c0cc8af5 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 29 Aug 2014 11:29:36 +0100 Subject: [PATCH 3/3] More basic functionality for voip calls (like hanging up) --- webclient/components/matrix/matrix-call.js | 70 ++++++++++++++++++- .../components/matrix/matrix-phone-service.js | 2 +- webclient/room/room.html | 2 +- 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/webclient/components/matrix/matrix-call.js b/webclient/components/matrix/matrix-call.js index a5f2529b87..3aab6413fc 100644 --- a/webclient/components/matrix/matrix-call.js +++ b/webclient/components/matrix/matrix-call.js @@ -16,6 +16,25 @@ limitations under the License. 'use strict'; +var forAllVideoTracksOnStream = function(s, f) { + var tracks = s.getVideoTracks(); + for (var i = 0; i < tracks.length; i++) { + f(tracks[i]); + } +} + +var forAllAudioTracksOnStream = function(s, f) { + var tracks = s.getAudioTracks(); + for (var i = 0; i < tracks.length; i++) { + f(tracks[i]); + } +} + +var forAllTracksOnStream = function(s, f) { + forAllVideoTracksOnStream(s, f); + forAllAudioTracksOnStream(s, f); +} + angular.module('MatrixCall', []) .factory('MatrixCall', ['matrixService', 'matrixPhoneService', function MatrixCallFactory(matrixService, matrixPhoneService) { var MatrixCall = function(room_id) { @@ -55,7 +74,15 @@ angular.module('MatrixCall', []) }; MatrixCall.prototype.hangup = function() { - console.trace("Rejecting call "+this.call_id); + console.trace("Ending call "+this.call_id); + + forAllTracksOnStream(this.localAVStream, function(t) { + t.stop(); + }); + forAllTracksOnStream(this.remoteAVStream, function(t) { + t.stop(); + }); + var content = { msgtype: "m.call.hangup", version: 0, @@ -66,6 +93,7 @@ angular.module('MatrixCall', []) }; MatrixCall.prototype.gotUserMediaForInvite = function(stream) { + this.localAVStream = stream; var audioTracks = stream.getAudioTracks(); for (var i = 0; i < audioTracks.length; i++) { audioTracks[i].enabled = true; @@ -86,6 +114,7 @@ angular.module('MatrixCall', []) }; MatrixCall.prototype.gotUserMediaForAnswer = function(stream) { + this.localAVStream = stream; var audioTracks = stream.getAudioTracks(); for (var i = 0; i < audioTracks.length; i++) { audioTracks[i].enabled = true; @@ -172,7 +201,8 @@ angular.module('MatrixCall', []) MatrixCall.prototype.onIceConnectionStateChanged = function() { console.trace("Ice connection state changed to: "+this.peerConn.iceConnectionState); - if (this.peerConn.iceConnectionState == 'completed') { + // ideally we'd consider the call to be connected when we get media but chrome doesn't implement nay of the 'onstarted' events yet + if (this.peerConn.iceConnectionState == 'completed' || this.peerConn.iceConnectionState == 'connected') { this.state = 'connected'; } }; @@ -191,10 +221,44 @@ angular.module('MatrixCall', []) MatrixCall.prototype.onAddStream = function(event) { console.trace("Stream added"+event); + + var s = event.stream; + + this.remoteAVStream = s; + + var self = this; + forAllTracksOnStream(s, function(t) { + // not currently implemented in chrome + t.onstarted = self.onRemoteStreamTrackStarted; + }); + + // not currently implemented in chrome + event.stream.onstarted = this.onRemoteStreamStarted; var player = new Audio(); - player.src = URL.createObjectURL(event.stream); + player.src = URL.createObjectURL(s); player.play(); }; + MatrixCall.prototype.onRemoteStreamStarted = function(event) { + this.state = 'connected'; + }; + + MatrixCall.prototype.onRemoteStreamTrackStarted = function(event) { + this.state = 'connected'; + }; + + MatrixCall.prototype.onHangupReceived = function() { + this.state = 'ended'; + + forAllTracksOnStream(this.localAVStream, function(t) { + t.stop(); + }); + forAllTracksOnStream(this.remoteAVStream, function(t) { + t.stop(); + }); + + this.onHangup(); + }; + return MatrixCall; }]); diff --git a/webclient/components/matrix/matrix-phone-service.js b/webclient/components/matrix/matrix-phone-service.js index 6f96875103..7f1ff531c4 100644 --- a/webclient/components/matrix/matrix-phone-service.js +++ b/webclient/components/matrix/matrix-phone-service.js @@ -59,7 +59,7 @@ angular.module('matrixPhoneService', []) console.trace("Got hangup for unknown call ID "+msg.call_id); return; } - call.onHangup(); + call.onHangupReceived(); matrixPhoneService.allCalls[msg.call_id] = undefined; } }); diff --git a/webclient/room/room.html b/webclient/room/room.html index dceb7322f5..bc3eefaf34 100644 --- a/webclient/room/room.html +++ b/webclient/room/room.html @@ -104,6 +104,7 @@ + {{ currentCall.state }} @@ -111,7 +112,6 @@
{{ state.stream_failure.data.error || "Connection failure" }}
-