From aba103b8e08ba20d3d1dfa39cc6bd546ec619d5a Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 15 Jul 2015 13:09:15 +0100 Subject: [PATCH 01/15] Add VideoFeed atom and VideoView organism. --- skins/base/views/atoms/VideoFeed.js | 34 +++++++++++++++++ skins/base/views/organisms/VideoView.js | 49 +++++++++++++++++++++++++ skins/base/views/pages/MatrixChat.js | 2 + src/ComponentBroker.js | 2 + src/controllers/atoms/VideoFeed.js | 21 +++++++++++ 5 files changed, 108 insertions(+) create mode 100644 skins/base/views/atoms/VideoFeed.js create mode 100644 skins/base/views/organisms/VideoView.js create mode 100644 src/controllers/atoms/VideoFeed.js diff --git a/skins/base/views/atoms/VideoFeed.js b/skins/base/views/atoms/VideoFeed.js new file mode 100644 index 000000000..71681b99e --- /dev/null +++ b/skins/base/views/atoms/VideoFeed.js @@ -0,0 +1,34 @@ +/* +Copyright 2015 OpenMarket Ltd + +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'; + +var React = require('react'); + +var VideoFeedController = require("../../../../src/controllers/atoms/VideoFeed"); + +module.exports = React.createClass({ + displayName: 'VideoFeed', + mixins: [VideoFeedController], + + render: function() { + return ( + + ); + }, +}); + diff --git a/skins/base/views/organisms/VideoView.js b/skins/base/views/organisms/VideoView.js new file mode 100644 index 000000000..813740fb1 --- /dev/null +++ b/skins/base/views/organisms/VideoView.js @@ -0,0 +1,49 @@ +/* +Copyright 2015 OpenMarket Ltd + +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'; + +var React = require('react'); + +var MatrixClientPeg = require("../../../../src/MatrixClientPeg"); +var ComponentBroker = require('../../../../src/ComponentBroker'); + +var VideoFeed = ComponentBroker.get('atoms/VideoFeed'); + +module.exports = React.createClass({ + displayName: 'VideoView', + + getRemoteVideoElement: function() { + return this.refs.remote.getDOMNode(); + }, + + getLocalVideoElement: function() { + return this.refs.local.getDOMNode(); + }, + + render: function() { + return ( +
+
+ +
+
+ +
+
+ ); + }, +}); \ No newline at end of file diff --git a/skins/base/views/pages/MatrixChat.js b/skins/base/views/pages/MatrixChat.js index 11e2be9ca..fcc1d274a 100644 --- a/skins/base/views/pages/MatrixChat.js +++ b/skins/base/views/pages/MatrixChat.js @@ -22,6 +22,7 @@ var ComponentBroker = require('../../../../src/ComponentBroker'); var LeftPanel = ComponentBroker.get('organisms/LeftPanel'); var RoomView = ComponentBroker.get('organisms/RoomView'); var RightPanel = ComponentBroker.get('organisms/RightPanel'); +var VideoView = ComponentBroker.get('organisms/VideoView'); var Login = ComponentBroker.get('templates/Login'); var MatrixChatController = require("../../../../src/controllers/pages/MatrixChat"); @@ -40,6 +41,7 @@ module.exports = React.createClass({
+
); diff --git a/src/ComponentBroker.js b/src/ComponentBroker.js index e00ef2424..e717ebb98 100644 --- a/src/ComponentBroker.js +++ b/src/ComponentBroker.js @@ -61,6 +61,7 @@ if (0) { require('../skins/base/views/atoms/LogoutButton'); require('../skins/base/views/atoms/EnableNotificationsButton'); require('../skins/base/views/atoms/MessageTimestamp'); +require('../skins/base/views/atoms/VideoFeed'); require('../skins/base/views/molecules/MatrixToolbar'); require('../skins/base/views/molecules/RoomTile'); require('../skins/base/views/molecules/MessageTile'); @@ -88,5 +89,6 @@ require('../skins/base/views/organisms/RightPanel'); require('../skins/base/views/molecules/RoomCreate'); require('../skins/base/views/molecules/RoomDropTarget'); require('../skins/base/views/molecules/DirectoryMenu'); +require('../skins/base/views/organisms/VideoView'); } diff --git a/src/controllers/atoms/VideoFeed.js b/src/controllers/atoms/VideoFeed.js new file mode 100644 index 000000000..8aa688b21 --- /dev/null +++ b/src/controllers/atoms/VideoFeed.js @@ -0,0 +1,21 @@ +/* +Copyright 2015 OpenMarket Ltd + +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'; + +module.exports = { +}; + From f94a061fdad7f7b5c55b5c0543d65d240f61fa30 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 15 Jul 2015 13:34:11 +0100 Subject: [PATCH 02/15] Add onClick listeners. Add getters for refs. --- skins/base/views/molecules/RoomHeader.js | 4 ++-- skins/base/views/{organisms => molecules}/VideoView.js | 0 skins/base/views/organisms/RoomView.js | 9 ++++++++- skins/base/views/pages/MatrixChat.js | 2 -- src/ComponentBroker.js | 2 +- src/controllers/molecules/RoomHeader.js | 8 ++++++++ 6 files changed, 19 insertions(+), 6 deletions(-) rename skins/base/views/{organisms => molecules}/VideoView.js (100%) diff --git a/skins/base/views/molecules/RoomHeader.js b/skins/base/views/molecules/RoomHeader.js index 23bd63d3c..e4a16b829 100644 --- a/skins/base/views/molecules/RoomHeader.js +++ b/skins/base/views/molecules/RoomHeader.js @@ -49,10 +49,10 @@ module.exports = React.createClass({
-
+
-
+
diff --git a/skins/base/views/organisms/VideoView.js b/skins/base/views/molecules/VideoView.js similarity index 100% rename from skins/base/views/organisms/VideoView.js rename to skins/base/views/molecules/VideoView.js diff --git a/skins/base/views/organisms/RoomView.js b/skins/base/views/organisms/RoomView.js index eb91b5442..26b96baae 100644 --- a/skins/base/views/organisms/RoomView.js +++ b/skins/base/views/organisms/RoomView.js @@ -26,6 +26,7 @@ var classNames = require("classnames"); var MessageTile = ComponentBroker.get('molecules/MessageTile'); var RoomHeader = ComponentBroker.get('molecules/RoomHeader'); var MessageComposer = ComponentBroker.get('molecules/MessageComposer'); +var VideoView = ComponentBroker.get("molecules/VideoView"); var RoomViewController = require("../../../../src/controllers/organisms/RoomView"); @@ -36,6 +37,10 @@ module.exports = React.createClass({ displayName: 'RoomView', mixins: [RoomViewController], + getVideoView: function() { + return this.refs.video; + }, + render: function() { var myUserId = MatrixClientPeg.get().credentials.userId; if (this.state.room.currentState.members[myUserId].membership == 'invite') { @@ -67,7 +72,9 @@ module.exports = React.createClass({ return (
-
+
+ +
diff --git a/skins/base/views/pages/MatrixChat.js b/skins/base/views/pages/MatrixChat.js index fcc1d274a..11e2be9ca 100644 --- a/skins/base/views/pages/MatrixChat.js +++ b/skins/base/views/pages/MatrixChat.js @@ -22,7 +22,6 @@ var ComponentBroker = require('../../../../src/ComponentBroker'); var LeftPanel = ComponentBroker.get('organisms/LeftPanel'); var RoomView = ComponentBroker.get('organisms/RoomView'); var RightPanel = ComponentBroker.get('organisms/RightPanel'); -var VideoView = ComponentBroker.get('organisms/VideoView'); var Login = ComponentBroker.get('templates/Login'); var MatrixChatController = require("../../../../src/controllers/pages/MatrixChat"); @@ -41,7 +40,6 @@ module.exports = React.createClass({
-
); diff --git a/src/ComponentBroker.js b/src/ComponentBroker.js index e717ebb98..ec0f00b6f 100644 --- a/src/ComponentBroker.js +++ b/src/ComponentBroker.js @@ -89,6 +89,6 @@ require('../skins/base/views/organisms/RightPanel'); require('../skins/base/views/molecules/RoomCreate'); require('../skins/base/views/molecules/RoomDropTarget'); require('../skins/base/views/molecules/DirectoryMenu'); -require('../skins/base/views/organisms/VideoView'); +require('../skins/base/views/molecules/VideoView'); } diff --git a/src/controllers/molecules/RoomHeader.js b/src/controllers/molecules/RoomHeader.js index 8aa688b21..49f85caa9 100644 --- a/src/controllers/molecules/RoomHeader.js +++ b/src/controllers/molecules/RoomHeader.js @@ -16,6 +16,14 @@ limitations under the License. 'use strict'; +var MatrixClientPeg = require("../../MatrixClientPeg"); + module.exports = { + onVideoClick: function() { + console.log("video clicked"); + }, + onVoiceClick: function() { + console.log("voice clicked"); + } }; From 78bea916e11dc9116cbc03070a7c75a54ba8bb91 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 15 Jul 2015 14:06:44 +0100 Subject: [PATCH 03/15] Dispatch events when calls are made/received. --- skins/base/views/molecules/VideoView.js | 5 ++-- skins/base/views/organisms/RoomView.js | 6 +--- src/controllers/molecules/RoomHeader.js | 14 +++++++-- src/controllers/molecules/VideoView.js | 38 +++++++++++++++++++++++++ src/controllers/pages/MatrixChat.js | 6 ++++ 5 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 src/controllers/molecules/VideoView.js diff --git a/skins/base/views/molecules/VideoView.js b/skins/base/views/molecules/VideoView.js index 813740fb1..be594142c 100644 --- a/skins/base/views/molecules/VideoView.js +++ b/skins/base/views/molecules/VideoView.js @@ -20,11 +20,12 @@ var React = require('react'); var MatrixClientPeg = require("../../../../src/MatrixClientPeg"); var ComponentBroker = require('../../../../src/ComponentBroker'); - +var VideoViewController = require("../../../../src/controllers/molecules/VideoView"); var VideoFeed = ComponentBroker.get('atoms/VideoFeed'); module.exports = React.createClass({ displayName: 'VideoView', + mixins: [VideoViewController], getRemoteVideoElement: function() { return this.refs.remote.getDOMNode(); @@ -45,5 +46,5 @@ module.exports = React.createClass({
); - }, + } }); \ No newline at end of file diff --git a/skins/base/views/organisms/RoomView.js b/skins/base/views/organisms/RoomView.js index 26b96baae..c7eb1474c 100644 --- a/skins/base/views/organisms/RoomView.js +++ b/skins/base/views/organisms/RoomView.js @@ -37,10 +37,6 @@ module.exports = React.createClass({ displayName: 'RoomView', mixins: [RoomViewController], - getVideoView: function() { - return this.refs.video; - }, - render: function() { var myUserId = MatrixClientPeg.get().credentials.userId; if (this.state.room.currentState.members[myUserId].membership == 'invite') { @@ -73,7 +69,7 @@ module.exports = React.createClass({
- +
diff --git a/src/controllers/molecules/RoomHeader.js b/src/controllers/molecules/RoomHeader.js index 49f85caa9..047c37899 100644 --- a/src/controllers/molecules/RoomHeader.js +++ b/src/controllers/molecules/RoomHeader.js @@ -16,14 +16,22 @@ limitations under the License. 'use strict'; -var MatrixClientPeg = require("../../MatrixClientPeg"); +var dis = require("../../dispatcher"); module.exports = { onVideoClick: function() { - console.log("video clicked"); + dis.dispatch({ + action: 'place_call', + type: "video", + room_id: this.props.room.roomId + }); }, onVoiceClick: function() { - console.log("voice clicked"); + dis.dispatch({ + action: 'place_call', + type: "voice", + room_id: this.props.room.roomId + }); } }; diff --git a/src/controllers/molecules/VideoView.js b/src/controllers/molecules/VideoView.js new file mode 100644 index 000000000..245f8bb9d --- /dev/null +++ b/src/controllers/molecules/VideoView.js @@ -0,0 +1,38 @@ +/* +Copyright 2015 OpenMarket Ltd + +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'; + +var dis = require("../../dispatcher"); + +module.exports = { + + componentDidMount: function() { + this.dispatcherRef = dis.register(this.onAction); + }, + + onAction: function(payload) { + switch(payload.action) { + case 'place_call': + console.log("Place %s call in %s", payload.type, payload.room_id); + break; + case 'incoming_call': + console.log("Incoming call: %s", payload.call); + break; + } + } +}; + diff --git a/src/controllers/pages/MatrixChat.js b/src/controllers/pages/MatrixChat.js index 717f91e7a..95776c2a2 100644 --- a/src/controllers/pages/MatrixChat.js +++ b/src/controllers/pages/MatrixChat.js @@ -116,6 +116,12 @@ module.exports = { that.setState({ready: true, currentRoom: firstRoom}); dis.dispatch({action: 'focus_composer'}); }); + cli.on('Call.incoming', function(call) { + dis.dispatch({ + action: 'incoming_call', + call: call + }); + }); Notifier.start(); cli.startClient(); }, From 28cebab9a3e428576ff153ac98303dc52b374cc6 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 15 Jul 2015 14:35:04 +0100 Subject: [PATCH 04/15] Add voip subdirectory. --- skins/base/views/atoms/{ => voip}/VideoFeed.js | 2 +- skins/base/views/molecules/{ => voip}/VideoView.js | 8 ++++---- skins/base/views/organisms/RoomView.js | 2 +- src/ComponentBroker.js | 5 +++-- src/controllers/atoms/{ => voip}/VideoFeed.js | 0 src/controllers/molecules/{ => voip}/VideoView.js | 2 +- 6 files changed, 10 insertions(+), 9 deletions(-) rename skins/base/views/atoms/{ => voip}/VideoFeed.js (90%) rename skins/base/views/molecules/{ => voip}/VideoView.js (79%) rename src/controllers/atoms/{ => voip}/VideoFeed.js (100%) rename src/controllers/molecules/{ => voip}/VideoView.js (96%) diff --git a/skins/base/views/atoms/VideoFeed.js b/skins/base/views/atoms/voip/VideoFeed.js similarity index 90% rename from skins/base/views/atoms/VideoFeed.js rename to skins/base/views/atoms/voip/VideoFeed.js index 71681b99e..7fbee4369 100644 --- a/skins/base/views/atoms/VideoFeed.js +++ b/skins/base/views/atoms/voip/VideoFeed.js @@ -18,7 +18,7 @@ limitations under the License. var React = require('react'); -var VideoFeedController = require("../../../../src/controllers/atoms/VideoFeed"); +var VideoFeedController = require("../../../../../src/controllers/atoms/voip/VideoFeed"); module.exports = React.createClass({ displayName: 'VideoFeed', diff --git a/skins/base/views/molecules/VideoView.js b/skins/base/views/molecules/voip/VideoView.js similarity index 79% rename from skins/base/views/molecules/VideoView.js rename to skins/base/views/molecules/voip/VideoView.js index be594142c..954ad3648 100644 --- a/skins/base/views/molecules/VideoView.js +++ b/skins/base/views/molecules/voip/VideoView.js @@ -18,10 +18,10 @@ limitations under the License. var React = require('react'); -var MatrixClientPeg = require("../../../../src/MatrixClientPeg"); -var ComponentBroker = require('../../../../src/ComponentBroker'); -var VideoViewController = require("../../../../src/controllers/molecules/VideoView"); -var VideoFeed = ComponentBroker.get('atoms/VideoFeed'); +var MatrixClientPeg = require("../../../../../src/MatrixClientPeg"); +var ComponentBroker = require('../../../../../src/ComponentBroker'); +var VideoViewController = require("../../../../../src/controllers/molecules/voip/VideoView"); +var VideoFeed = ComponentBroker.get('atoms/voip/VideoFeed'); module.exports = React.createClass({ displayName: 'VideoView', diff --git a/skins/base/views/organisms/RoomView.js b/skins/base/views/organisms/RoomView.js index c7eb1474c..fcafb945e 100644 --- a/skins/base/views/organisms/RoomView.js +++ b/skins/base/views/organisms/RoomView.js @@ -26,7 +26,7 @@ var classNames = require("classnames"); var MessageTile = ComponentBroker.get('molecules/MessageTile'); var RoomHeader = ComponentBroker.get('molecules/RoomHeader'); var MessageComposer = ComponentBroker.get('molecules/MessageComposer'); -var VideoView = ComponentBroker.get("molecules/VideoView"); +var VideoView = ComponentBroker.get("molecules/voip/VideoView"); var RoomViewController = require("../../../../src/controllers/organisms/RoomView"); diff --git a/src/ComponentBroker.js b/src/ComponentBroker.js index 9bb54fa61..7004da157 100644 --- a/src/ComponentBroker.js +++ b/src/ComponentBroker.js @@ -61,7 +61,6 @@ if (0) { require('../skins/base/views/atoms/LogoutButton'); require('../skins/base/views/atoms/EnableNotificationsButton'); require('../skins/base/views/atoms/MessageTimestamp'); -require('../skins/base/views/atoms/VideoFeed'); require('../skins/base/views/atoms/create_room/CreateRoomButton'); require('../skins/base/views/atoms/create_room/RoomNameTextbox'); require('../skins/base/views/atoms/create_room/Presets'); @@ -95,6 +94,8 @@ require('../skins/base/views/organisms/RightPanel'); require('../skins/base/views/molecules/RoomCreate'); require('../skins/base/views/molecules/RoomDropTarget'); require('../skins/base/views/molecules/DirectoryMenu'); -require('../skins/base/views/molecules/VideoView'); +require('../skins/base/views/atoms/voip/VideoFeed'); +require('../skins/base/views/molecules/voip/VideoView'); + } diff --git a/src/controllers/atoms/VideoFeed.js b/src/controllers/atoms/voip/VideoFeed.js similarity index 100% rename from src/controllers/atoms/VideoFeed.js rename to src/controllers/atoms/voip/VideoFeed.js diff --git a/src/controllers/molecules/VideoView.js b/src/controllers/molecules/voip/VideoView.js similarity index 96% rename from src/controllers/molecules/VideoView.js rename to src/controllers/molecules/voip/VideoView.js index 245f8bb9d..83b2328ab 100644 --- a/src/controllers/molecules/VideoView.js +++ b/src/controllers/molecules/voip/VideoView.js @@ -16,7 +16,7 @@ limitations under the License. 'use strict'; -var dis = require("../../dispatcher"); +var dis = require("../../../dispatcher"); module.exports = { From 7e30c0f47b6584c3e0c33a6c83e7b9373e2b80ff Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 15 Jul 2015 14:57:52 +0100 Subject: [PATCH 05/15] Add CallHandler to handle call logic and make VideoViews/WaveformViews. --- .../base/views/molecules/voip/CallHandler.js | 36 ++++++++++++++ skins/base/views/organisms/RoomView.js | 4 +- src/ComponentBroker.js | 1 + src/controllers/molecules/voip/CallHandler.js | 48 +++++++++++++++++++ src/controllers/molecules/voip/VideoView.js | 15 ------ 5 files changed, 87 insertions(+), 17 deletions(-) create mode 100644 skins/base/views/molecules/voip/CallHandler.js create mode 100644 src/controllers/molecules/voip/CallHandler.js diff --git a/skins/base/views/molecules/voip/CallHandler.js b/skins/base/views/molecules/voip/CallHandler.js new file mode 100644 index 000000000..8fd8282fc --- /dev/null +++ b/skins/base/views/molecules/voip/CallHandler.js @@ -0,0 +1,36 @@ +/* +Copyright 2015 OpenMarket Ltd + +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'; + +var React = require('react'); + +var MatrixClientPeg = require("../../../../../src/MatrixClientPeg"); +var ComponentBroker = require('../../../../../src/ComponentBroker'); +var CallHandlerController = require( + "../../../../../src/controllers/molecules/voip/CallHandler" +); +var VideoView = ComponentBroker.get('molecules/voip/VideoView'); + +module.exports = React.createClass({ + displayName: 'CallHandler', + mixins: [CallHandlerController], + render: function(){ + return ( +
+ ); + } +}); \ No newline at end of file diff --git a/skins/base/views/organisms/RoomView.js b/skins/base/views/organisms/RoomView.js index fcafb945e..5ff280925 100644 --- a/skins/base/views/organisms/RoomView.js +++ b/skins/base/views/organisms/RoomView.js @@ -26,7 +26,7 @@ var classNames = require("classnames"); var MessageTile = ComponentBroker.get('molecules/MessageTile'); var RoomHeader = ComponentBroker.get('molecules/RoomHeader'); var MessageComposer = ComponentBroker.get('molecules/MessageComposer'); -var VideoView = ComponentBroker.get("molecules/voip/VideoView"); +var CallHandler = ComponentBroker.get("molecules/voip/CallHandler"); var RoomViewController = require("../../../../src/controllers/organisms/RoomView"); @@ -69,7 +69,7 @@ module.exports = React.createClass({
- +
diff --git a/src/ComponentBroker.js b/src/ComponentBroker.js index 7004da157..41beeadf1 100644 --- a/src/ComponentBroker.js +++ b/src/ComponentBroker.js @@ -96,6 +96,7 @@ require('../skins/base/views/molecules/RoomDropTarget'); require('../skins/base/views/molecules/DirectoryMenu'); require('../skins/base/views/atoms/voip/VideoFeed'); require('../skins/base/views/molecules/voip/VideoView'); +require('../skins/base/views/molecules/voip/CallHandler'); } diff --git a/src/controllers/molecules/voip/CallHandler.js b/src/controllers/molecules/voip/CallHandler.js new file mode 100644 index 000000000..f5e09338d --- /dev/null +++ b/src/controllers/molecules/voip/CallHandler.js @@ -0,0 +1,48 @@ +/* +Copyright 2015 OpenMarket Ltd + +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'; + +var dis = require("../../../dispatcher"); + +module.exports = { + + componentDidMount: function() { + this.dispatcherRef = dis.register(this.onAction); + }, + + componentWillUnmount: function() { + dis.unregister(this.dispatcherRef); + }, + + onAction: function(payload) { + // if we were given a room_id to track, don't handle anything else. + if (payload.room_id && this.props.room && + this.props.room.roomId !== payload.room_id) { + return; + } + + switch (payload.action) { + case 'place_call': + console.log("Place %s call in %s", payload.type, payload.room_id); + break; + case 'incoming_call': + console.log("Incoming call: %s", payload.call); + break; + } + } +}; + diff --git a/src/controllers/molecules/voip/VideoView.js b/src/controllers/molecules/voip/VideoView.js index 83b2328ab..b08f6ab1e 100644 --- a/src/controllers/molecules/voip/VideoView.js +++ b/src/controllers/molecules/voip/VideoView.js @@ -19,20 +19,5 @@ limitations under the License. var dis = require("../../../dispatcher"); module.exports = { - - componentDidMount: function() { - this.dispatcherRef = dis.register(this.onAction); - }, - - onAction: function(payload) { - switch(payload.action) { - case 'place_call': - console.log("Place %s call in %s", payload.type, payload.room_id); - break; - case 'incoming_call': - console.log("Incoming call: %s", payload.call); - break; - } - } }; From 6316f1b195b9178693b09765eb8f70b90d9044a9 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 15 Jul 2015 15:36:45 +0100 Subject: [PATCH 06/15] Add call handling logic. Outbound voice calls work! --- .../base/views/molecules/voip/CallHandler.js | 18 +++++ src/controllers/molecules/voip/CallHandler.js | 77 ++++++++++++++++++- 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/skins/base/views/molecules/voip/CallHandler.js b/skins/base/views/molecules/voip/CallHandler.js index 8fd8282fc..41023dd63 100644 --- a/skins/base/views/molecules/voip/CallHandler.js +++ b/skins/base/views/molecules/voip/CallHandler.js @@ -28,7 +28,25 @@ var VideoView = ComponentBroker.get('molecules/voip/VideoView'); module.exports = React.createClass({ displayName: 'CallHandler', mixins: [CallHandlerController], + + getVideoView: function() { + return this.refs.video; + }, + render: function(){ + if (this.state && this.state.call) { + if (this.state.call.type === "video") { + return ( + + ); + } + else if (this.state.call.type === "voice") { + // in the future. + return ( +
+ ); + } + } return (
); diff --git a/src/controllers/molecules/voip/CallHandler.js b/src/controllers/molecules/voip/CallHandler.js index f5e09338d..0bb4685bf 100644 --- a/src/controllers/molecules/voip/CallHandler.js +++ b/src/controllers/molecules/voip/CallHandler.js @@ -15,13 +15,25 @@ limitations under the License. */ 'use strict'; - +var MatrixClientPeg = require("../../../MatrixClientPeg"); +var Matrix = require("matrix-js-sdk"); var dis = require("../../../dispatcher"); +/* + * State vars: + * this.state.call = MatrixCall|null + * + * Props: + * this.props.room = Room (JS SDK) - can be null (for singleton views) + */ + module.exports = { componentDidMount: function() { this.dispatcherRef = dis.register(this.onAction); + this.setState({ + call: null + }); }, componentWillUnmount: function() { @@ -37,12 +49,75 @@ module.exports = { switch (payload.action) { case 'place_call': + if (this.state.call) { + return; // don't allow >1 call to be placed. + } console.log("Place %s call in %s", payload.type, payload.room_id); + var call = Matrix.createNewMatrixCall( + MatrixClientPeg.get(), payload.room_id + ); + this._setCallListeners(call); + this.setState({ + call: call + }); + if (payload.type === 'voice') { + call.placeVoiceCall(); + } + else if (payload.type === 'video') { + var videoView = this.getVideoView(); + call.placeVideoCall( + videoView.getRemoteVideoElement(), + videoView.getLocalVideoElement() + ); + } + else { + console.error("Unknown call type: %s", payload.type); + } break; case 'incoming_call': + if (this.state.call) { + payload.call.hangup("busy"); + return; // don't allow >1 call to be received. + } + this._setCallListeners(call); + this.setState({ + call: call + }); console.log("Incoming call: %s", payload.call); break; + case 'hangup': + if (!this.state.call) { + return; // no call to hangup + } + this.state.call.hangup(); + this.setState({ + call: null + }); + break; + case 'answer': + if (!this.state.call) { + return; // no call to answer + } + this.state.call.answer(); + break; } + }, + + _setCallListeners: function(call) { + var self = this; + call.on("error", function(err) { + console.error("Call error: %s", err); + console.error(err.stack); + call.hangup(); + self.setState({ + call: null + }); + }); + call.on("hangup", function() { + self.setState({ + call: null + }); + }) } }; From 37c9c8fbb425d9fca76c1f9fcd3f7a5a322baf1b Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 15 Jul 2015 16:52:23 +0100 Subject: [PATCH 07/15] Add CallHandler singleton and add CallView. CallView is the container for either VideoViews or WaveformViews. All UI elements listen for 'call_state' payloads and then call CallHandler.getCall(roomId) to extract the current MatrixCall for that room. We can't do this via stateful dispatches because dispatching does not preserve ordering empirically (probably due to setTimeout). --- skins/base/views/molecules/RoomHeader.js | 7 + .../voip/{CallHandler.js => CallView.js} | 14 +- skins/base/views/organisms/RoomView.js | 4 +- src/CallHandler.js | 139 ++++++++++++++++++ src/ComponentBroker.js | 2 +- src/controllers/molecules/RoomHeader.js | 37 +++++ src/controllers/molecules/voip/CallHandler.js | 123 ---------------- src/controllers/molecules/voip/CallView.js | 56 +++++++ 8 files changed, 251 insertions(+), 131 deletions(-) rename skins/base/views/molecules/voip/{CallHandler.js => CallView.js} (85%) create mode 100644 src/CallHandler.js delete mode 100644 src/controllers/molecules/voip/CallHandler.js create mode 100644 src/controllers/molecules/voip/CallView.js diff --git a/skins/base/views/molecules/RoomHeader.js b/skins/base/views/molecules/RoomHeader.js index e4a16b829..95e27a062 100644 --- a/skins/base/views/molecules/RoomHeader.js +++ b/skins/base/views/molecules/RoomHeader.js @@ -49,6 +49,13 @@ module.exports = React.createClass({
+ { + this.state && this.state.inCall ? +
+ +
+ : null + }
diff --git a/skins/base/views/molecules/voip/CallHandler.js b/skins/base/views/molecules/voip/CallView.js similarity index 85% rename from skins/base/views/molecules/voip/CallHandler.js rename to skins/base/views/molecules/voip/CallView.js index 41023dd63..cbdcc4c24 100644 --- a/skins/base/views/molecules/voip/CallHandler.js +++ b/skins/base/views/molecules/voip/CallView.js @@ -20,20 +20,24 @@ var React = require('react'); var MatrixClientPeg = require("../../../../../src/MatrixClientPeg"); var ComponentBroker = require('../../../../../src/ComponentBroker'); -var CallHandlerController = require( - "../../../../../src/controllers/molecules/voip/CallHandler" +var CallViewController = require( + "../../../../../src/controllers/molecules/voip/CallView" ); var VideoView = ComponentBroker.get('molecules/voip/VideoView'); module.exports = React.createClass({ - displayName: 'CallHandler', - mixins: [CallHandlerController], + displayName: 'CallView', + mixins: [CallViewController], getVideoView: function() { return this.refs.video; }, render: function(){ + return ( + + ); + /* if (this.state && this.state.call) { if (this.state.call.type === "video") { return ( @@ -49,6 +53,6 @@ module.exports = React.createClass({ } return (
- ); + ); */ } }); \ No newline at end of file diff --git a/skins/base/views/organisms/RoomView.js b/skins/base/views/organisms/RoomView.js index 5ff280925..debcbfb8f 100644 --- a/skins/base/views/organisms/RoomView.js +++ b/skins/base/views/organisms/RoomView.js @@ -26,7 +26,7 @@ var classNames = require("classnames"); var MessageTile = ComponentBroker.get('molecules/MessageTile'); var RoomHeader = ComponentBroker.get('molecules/RoomHeader'); var MessageComposer = ComponentBroker.get('molecules/MessageComposer'); -var CallHandler = ComponentBroker.get("molecules/voip/CallHandler"); +var CallView = ComponentBroker.get("molecules/voip/CallView"); var RoomViewController = require("../../../../src/controllers/organisms/RoomView"); @@ -69,7 +69,7 @@ module.exports = React.createClass({
- +
diff --git a/src/CallHandler.js b/src/CallHandler.js new file mode 100644 index 000000000..ce6d8906a --- /dev/null +++ b/src/CallHandler.js @@ -0,0 +1,139 @@ +/* +Copyright 2015 OpenMarket Ltd + +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'; + +/* + * Manages a list of all the currently active calls. + * + * This handler dispatches when voip calls are added/removed from this list: + * { + * action: 'call_state' + * room_id: + * } + * + * To know if the call was added/removed, this handler exposes a getter to + * obtain the call for a room: + * CallHandler.getCall(roomId) + * + * This handler listens for and handles the following actions: + * { + * action: 'place_call', + * type: 'voice|video', + * room_id: + * } + * + * { + * action: 'incoming_call' + * call: MatrixCall + * } + * + * { + * action: 'hangup' + * room_id: + * } + * + * { + * action: 'answer' + * room_id: + * } + */ + +var MatrixClientPeg = require("./MatrixClientPeg"); +var Matrix = require("matrix-js-sdk"); +var dis = require("./dispatcher"); + +var calls = { + //room_id: MatrixCall +}; + +function _setCallListeners(call) { + call.on("error", function(err) { + console.error("Call error: %s", err); + console.error(err.stack); + call.hangup(); + _setCallState(undefined, call.roomId); + }); + call.on("hangup", function() { + _setCallState(undefined, call.roomId); + }); +} + +function _setCallState(call, roomId) { + console.log("_setState >>> %s >>> %s ", call, roomId); + calls[roomId] = call; + dis.dispatch({ + action: 'call_state', + room_id: roomId + }); +} + +dis.register(function(payload) { + switch (payload.action) { + case 'place_call': + if (calls[payload.room_id]) { + return; // don't allow >1 call to be placed. + } + console.log("Place %s call in %s", payload.type, payload.room_id); + var call = Matrix.createNewMatrixCall( + MatrixClientPeg.get(), payload.room_id + ); + _setCallListeners(call); + _setCallState(call, call.roomId); + if (payload.type === 'voice') { + call.placeVoiceCall(); + } + else if (payload.type === 'video') { + call.placeVideoCall( + payload.remote_element, + payload.local_element + ); + } + else { + console.error("Unknown call type: %s", payload.type); + } + + break; + case 'incoming_call': + if (calls[payload.call.roomId]) { + payload.call.hangup("busy"); + return; // don't allow >1 call to be received, hangup newer one. + } + var call = payload.call; + _setCallListeners(call); + _setCallState(call, call.roomId); + break; + case 'hangup': + if (!calls[payload.room_id]) { + return; // no call to hangup + } + calls[payload.room_id].hangup(); + _setCallState(null, payload.room_id); + break; + case 'answer': + if (!calls[payload.room_id]) { + return; // no call to answer + } + calls[payload.room_id].answer(); + break; + } +}); + +module.exports = { + getCall: function(roomId) { + return calls[roomId] || null; + } +}; \ No newline at end of file diff --git a/src/ComponentBroker.js b/src/ComponentBroker.js index 41beeadf1..c2c996e4b 100644 --- a/src/ComponentBroker.js +++ b/src/ComponentBroker.js @@ -96,7 +96,7 @@ require('../skins/base/views/molecules/RoomDropTarget'); require('../skins/base/views/molecules/DirectoryMenu'); require('../skins/base/views/atoms/voip/VideoFeed'); require('../skins/base/views/molecules/voip/VideoView'); -require('../skins/base/views/molecules/voip/CallHandler'); +require('../skins/base/views/molecules/voip/CallView'); } diff --git a/src/controllers/molecules/RoomHeader.js b/src/controllers/molecules/RoomHeader.js index 047c37899..29ddf70e6 100644 --- a/src/controllers/molecules/RoomHeader.js +++ b/src/controllers/molecules/RoomHeader.js @@ -16,9 +16,40 @@ limitations under the License. 'use strict'; +/* + * State vars: + * this.state.inCall = boolean + */ + var dis = require("../../dispatcher"); +var CallHandler = require("../../CallHandler"); module.exports = { + componentDidMount: function() { + this.dispatcherRef = dis.register(this.onAction); + this.setState({ + inCall: false + }); + }, + + componentWillUnmount: function() { + dis.unregister(this.dispatcherRef); + }, + + onAction: function(payload) { + // if we were given a room_id to track, don't handle anything else. + if (payload.room_id && this.props.room && + this.props.room.roomId !== payload.room_id) { + return; + } + if (payload.action !== 'call_state') { + return; + } + this.setState({ + inCall: (CallHandler.getCall(payload.room_id) !== null) + }); + }, + onVideoClick: function() { dis.dispatch({ action: 'place_call', @@ -32,6 +63,12 @@ module.exports = { type: "voice", room_id: this.props.room.roomId }); + }, + onHangupClick: function() { + dis.dispatch({ + action: 'hangup', + room_id: this.props.room.roomId + }); } }; diff --git a/src/controllers/molecules/voip/CallHandler.js b/src/controllers/molecules/voip/CallHandler.js deleted file mode 100644 index 0bb4685bf..000000000 --- a/src/controllers/molecules/voip/CallHandler.js +++ /dev/null @@ -1,123 +0,0 @@ -/* -Copyright 2015 OpenMarket Ltd - -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'; -var MatrixClientPeg = require("../../../MatrixClientPeg"); -var Matrix = require("matrix-js-sdk"); -var dis = require("../../../dispatcher"); - -/* - * State vars: - * this.state.call = MatrixCall|null - * - * Props: - * this.props.room = Room (JS SDK) - can be null (for singleton views) - */ - -module.exports = { - - componentDidMount: function() { - this.dispatcherRef = dis.register(this.onAction); - this.setState({ - call: null - }); - }, - - componentWillUnmount: function() { - dis.unregister(this.dispatcherRef); - }, - - onAction: function(payload) { - // if we were given a room_id to track, don't handle anything else. - if (payload.room_id && this.props.room && - this.props.room.roomId !== payload.room_id) { - return; - } - - switch (payload.action) { - case 'place_call': - if (this.state.call) { - return; // don't allow >1 call to be placed. - } - console.log("Place %s call in %s", payload.type, payload.room_id); - var call = Matrix.createNewMatrixCall( - MatrixClientPeg.get(), payload.room_id - ); - this._setCallListeners(call); - this.setState({ - call: call - }); - if (payload.type === 'voice') { - call.placeVoiceCall(); - } - else if (payload.type === 'video') { - var videoView = this.getVideoView(); - call.placeVideoCall( - videoView.getRemoteVideoElement(), - videoView.getLocalVideoElement() - ); - } - else { - console.error("Unknown call type: %s", payload.type); - } - break; - case 'incoming_call': - if (this.state.call) { - payload.call.hangup("busy"); - return; // don't allow >1 call to be received. - } - this._setCallListeners(call); - this.setState({ - call: call - }); - console.log("Incoming call: %s", payload.call); - break; - case 'hangup': - if (!this.state.call) { - return; // no call to hangup - } - this.state.call.hangup(); - this.setState({ - call: null - }); - break; - case 'answer': - if (!this.state.call) { - return; // no call to answer - } - this.state.call.answer(); - break; - } - }, - - _setCallListeners: function(call) { - var self = this; - call.on("error", function(err) { - console.error("Call error: %s", err); - console.error(err.stack); - call.hangup(); - self.setState({ - call: null - }); - }); - call.on("hangup", function() { - self.setState({ - call: null - }); - }) - } -}; - diff --git a/src/controllers/molecules/voip/CallView.js b/src/controllers/molecules/voip/CallView.js new file mode 100644 index 000000000..9546f885f --- /dev/null +++ b/src/controllers/molecules/voip/CallView.js @@ -0,0 +1,56 @@ +/* +Copyright 2015 OpenMarket Ltd + +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'; +var dis = require("../../../dispatcher"); +var CallHandler = require("../../../CallHandler"); + +/* + * State vars: + * this.state.call = MatrixCall|null + * + * Props: + * this.props.room = Room (JS SDK) + */ + +module.exports = { + + componentDidMount: function() { + this.dispatcherRef = dis.register(this.onAction); + this.setState({ + call: null + }); + }, + + componentWillUnmount: function() { + dis.unregister(this.dispatcherRef); + }, + + onAction: function(payload) { + // if we were given a room_id to track, don't handle anything else. + if (payload.room_id && this.props.room && + this.props.room.roomId !== payload.room_id) { + return; + } + if (payload.action !== 'call_state') { + return; + } + this.setState({ + call: CallHandler.getCall(payload.room_id) + }); + } +}; + From 14a4da54f861f0bb02d4a27cb03edb02a6d609eb Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 15 Jul 2015 17:36:47 +0100 Subject: [PATCH 08/15] Wire up hangup/answer buttons. --- skins/base/views/molecules/RoomHeader.js | 40 +++++++++++++++++++----- src/controllers/molecules/RoomHeader.js | 28 +++++++++++++++-- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/skins/base/views/molecules/RoomHeader.js b/skins/base/views/molecules/RoomHeader.js index 95e27a062..e64aee538 100644 --- a/skins/base/views/molecules/RoomHeader.js +++ b/skins/base/views/molecules/RoomHeader.js @@ -30,6 +30,38 @@ module.exports = React.createClass({ var topic = this.props.room.currentState.getStateEvents('m.room.topic', ''); topic = topic ?
{ topic.getContent().topic }
: null; + var callButtons; + if (this.state) { + switch (this.state.callState) { + case "INBOUND": + callButtons = ( +
+
+ YUP +
+
+ NOPE +
+
+ ); + break; + case "OUTBOUND": + callButtons = ( +
+ BYEBYE +
+ ); + break; + case "IN_CALL": + callButtons = ( +
+ BYEBYE +
+ ); + break; + } + } + return (
@@ -49,13 +81,7 @@ module.exports = React.createClass({
- { - this.state && this.state.inCall ? -
- -
- : null - } + {callButtons}
diff --git a/src/controllers/molecules/RoomHeader.js b/src/controllers/molecules/RoomHeader.js index 29ddf70e6..dde8cf4ef 100644 --- a/src/controllers/molecules/RoomHeader.js +++ b/src/controllers/molecules/RoomHeader.js @@ -18,7 +18,7 @@ limitations under the License. /* * State vars: - * this.state.inCall = boolean + * this.state.callState = OUTBOUND|INBOUND|IN_CALL|NO_CALL */ var dis = require("../../dispatcher"); @@ -28,7 +28,7 @@ module.exports = { componentDidMount: function() { this.dispatcherRef = dis.register(this.onAction); this.setState({ - inCall: false + callState: "NO_CALL" }); }, @@ -45,8 +45,24 @@ module.exports = { if (payload.action !== 'call_state') { return; } + var call = CallHandler.getCall(payload.room_id); + var callState = 'NO_CALL'; + if (call && call.state !== 'ended') { + if (call.state === 'connected') { + callState = "IN_CALL"; + } + else if (call.direction === 'outbound') { + callState = "OUTBOUND"; + } + else if (call.direction === 'inbound') { + callState = "INBOUND"; + } + else { + console.error("Cannot determine call state."); + } + } this.setState({ - inCall: (CallHandler.getCall(payload.room_id) !== null) + callState: callState }); }, @@ -69,6 +85,12 @@ module.exports = { action: 'hangup', room_id: this.props.room.roomId }); + }, + onAnswerClick: function() { + dis.dispatch({ + action: 'answer', + room_id: this.props.room.roomId + }); } }; From 4f132c418f3903a9b434f0a184703513258e7746 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 15 Jul 2015 17:48:26 +0100 Subject: [PATCH 09/15] Fix a couple state bugs. --- src/CallHandler.js | 7 +++- src/controllers/molecules/RoomHeader.js | 52 +++++++++++++++---------- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index ce6d8906a..5fefa7cf1 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -19,7 +19,7 @@ limitations under the License. /* * Manages a list of all the currently active calls. * - * This handler dispatches when voip calls are added/removed from this list: + * This handler dispatches when voip calls are added/updated/removed from this list: * { * action: 'call_state' * room_id: @@ -33,6 +33,8 @@ limitations under the License. * { * action: 'place_call', * type: 'voice|video', + * remote_element: DOMVideoElement, // only if type: video + * local_element: DOMVideoElement, // only if type: video * room_id: * } * @@ -48,6 +50,8 @@ limitations under the License. * * { * action: 'answer' + * remote_element: DOMVideoElement, // only if type: video + * local_element: DOMVideoElement, // only if type: video * room_id: * } */ @@ -128,6 +132,7 @@ dis.register(function(payload) { return; // no call to answer } calls[payload.room_id].answer(); + _setCallState(calls[payload.room_id], payload.room_id); break; } }); diff --git a/src/controllers/molecules/RoomHeader.js b/src/controllers/molecules/RoomHeader.js index dde8cf4ef..b66d1ff90 100644 --- a/src/controllers/molecules/RoomHeader.js +++ b/src/controllers/molecules/RoomHeader.js @@ -25,29 +25,15 @@ var dis = require("../../dispatcher"); var CallHandler = require("../../CallHandler"); module.exports = { - componentDidMount: function() { - this.dispatcherRef = dis.register(this.onAction); - this.setState({ - callState: "NO_CALL" - }); - }, - - componentWillUnmount: function() { - dis.unregister(this.dispatcherRef); - }, - - onAction: function(payload) { - // if we were given a room_id to track, don't handle anything else. - if (payload.room_id && this.props.room && - this.props.room.roomId !== payload.room_id) { + _setCallState: function(call) { + if (!call) { + this.setState({ + callState: "NO_CALL" + }); return; } - if (payload.action !== 'call_state') { - return; - } - var call = CallHandler.getCall(payload.room_id); var callState = 'NO_CALL'; - if (call && call.state !== 'ended') { + if (call.state !== 'ended') { if (call.state === 'connected') { callState = "IN_CALL"; } @@ -66,6 +52,32 @@ module.exports = { }); }, + componentDidMount: function() { + this.dispatcherRef = dis.register(this.onAction); + var call; + if (this.props.room) { + call = CallHandler.getCall(this.props.room.roomId); + } + this._setCallState(call); + }, + + componentWillUnmount: function() { + dis.unregister(this.dispatcherRef); + }, + + onAction: function(payload) { + // if we were given a room_id to track, don't handle anything else. + if (payload.room_id && this.props.room && + this.props.room.roomId !== payload.room_id) { + return; + } + if (payload.action !== 'call_state') { + return; + } + var call = CallHandler.getCall(payload.room_id); + this._setCallState(call); + }, + onVideoClick: function() { dis.dispatch({ action: 'place_call', From ecd1f090951da443e03b70a1f8b215fbe9ff4adb Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 16 Jul 2015 10:26:41 +0100 Subject: [PATCH 10/15] Glue in video elements. --- src/CallHandler.js | 2 -- src/controllers/molecules/voip/CallView.js | 8 +++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index 5fefa7cf1..d6367a6bf 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -50,8 +50,6 @@ limitations under the License. * * { * action: 'answer' - * remote_element: DOMVideoElement, // only if type: video - * local_element: DOMVideoElement, // only if type: video * room_id: * } */ diff --git a/src/controllers/molecules/voip/CallView.js b/src/controllers/molecules/voip/CallView.js index 9546f885f..0a5e3e2f6 100644 --- a/src/controllers/molecules/voip/CallView.js +++ b/src/controllers/molecules/voip/CallView.js @@ -48,9 +48,11 @@ module.exports = { if (payload.action !== 'call_state') { return; } - this.setState({ - call: CallHandler.getCall(payload.room_id) - }); + var call = CallHandler.getCall(payload.room_id); + if (call) { + call.setLocalVideoElement(this.getVideoView().getLocalVideoElement()); + call.setRemoteVideoElement(this.getVideoView().getRemoteVideoElement()); + } } }; From 7ffd97b5dc91212d0824fe5af2591727774e7cc3 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 16 Jul 2015 11:05:09 +0100 Subject: [PATCH 11/15] Implement call FSM. All works. --- skins/base/views/molecules/RoomHeader.js | 14 ++---- src/CallHandler.js | 55 ++++++++++++++++++------ src/controllers/molecules/RoomHeader.js | 39 ++++------------- 3 files changed, 56 insertions(+), 52 deletions(-) diff --git a/skins/base/views/molecules/RoomHeader.js b/skins/base/views/molecules/RoomHeader.js index e64aee538..1708bd1c3 100644 --- a/skins/base/views/molecules/RoomHeader.js +++ b/skins/base/views/molecules/RoomHeader.js @@ -32,8 +32,8 @@ module.exports = React.createClass({ var callButtons; if (this.state) { - switch (this.state.callState) { - case "INBOUND": + switch (this.state.call_state) { + case "ringing": callButtons = (
@@ -45,14 +45,8 @@ module.exports = React.createClass({
); break; - case "OUTBOUND": - callButtons = ( -
- BYEBYE -
- ); - break; - case "IN_CALL": + case "ringback": + case "connected": callButtons = (
BYEBYE diff --git a/src/CallHandler.js b/src/CallHandler.js index d6367a6bf..029b89174 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -22,7 +22,8 @@ limitations under the License. * This handler dispatches when voip calls are added/updated/removed from this list: * { * action: 'call_state' - * room_id: + * room_id: , + * status: ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing * } * * To know if the call was added/removed, this handler exposes a getter to @@ -33,8 +34,6 @@ limitations under the License. * { * action: 'place_call', * type: 'voice|video', - * remote_element: DOMVideoElement, // only if type: video - * local_element: DOMVideoElement, // only if type: video * room_id: * } * @@ -67,19 +66,51 @@ function _setCallListeners(call) { console.error("Call error: %s", err); console.error(err.stack); call.hangup(); - _setCallState(undefined, call.roomId); + _setCallState(undefined, call.roomId, "ended"); }); call.on("hangup", function() { - _setCallState(undefined, call.roomId); + _setCallState(undefined, call.roomId, "ended"); + }); + // map web rtc states to dummy UI state + // ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing + call.on("state", function(newState, oldState) { + if (newState === "ringing") { + _setCallState(call, call.roomId, "ringing"); + } + else if (newState === "invite_sent") { + _setCallState(call, call.roomId, "ringback"); + } + else if (newState === "ended" && oldState === "connected") { + _setCallState(call, call.roomId, "ended"); + } + else if (newState === "ended" && oldState === "invite_sent" && + (call.hangupParty === "remote" || + (call.hangupParty === "local" && call.hangupReason === "invite_timeout") + )) { + _setCallState(call, call.roomId, "busy"); + } + else if (oldState === "invite_sent") { + _setCallState(call, call.roomId, "stop_ringback"); + } + else if (oldState === "ringing") { + _setCallState(call, call.roomId, "stop_ringing"); + } + else if (newState === "connected") { + _setCallState(call, call.roomId, "connected"); + } }); } -function _setCallState(call, roomId) { - console.log("_setState >>> %s >>> %s ", call, roomId); +function _setCallState(call, roomId, status) { + console.log("_setState >>> %s >>> %s >> %s", call, roomId, status); calls[roomId] = call; + if (call) { + call.call_state = status; + } dis.dispatch({ action: 'call_state', - room_id: roomId + room_id: roomId, + status: status }); } @@ -94,7 +125,7 @@ dis.register(function(payload) { MatrixClientPeg.get(), payload.room_id ); _setCallListeners(call); - _setCallState(call, call.roomId); + _setCallState(call, call.roomId, "ringback"); if (payload.type === 'voice') { call.placeVoiceCall(); } @@ -116,21 +147,21 @@ dis.register(function(payload) { } var call = payload.call; _setCallListeners(call); - _setCallState(call, call.roomId); + _setCallState(call, call.roomId, "ringing"); break; case 'hangup': if (!calls[payload.room_id]) { return; // no call to hangup } calls[payload.room_id].hangup(); - _setCallState(null, payload.room_id); + _setCallState(null, payload.room_id, "ended"); break; case 'answer': if (!calls[payload.room_id]) { return; // no call to answer } calls[payload.room_id].answer(); - _setCallState(calls[payload.room_id], payload.room_id); + _setCallState(calls[payload.room_id], payload.room_id, "connected"); break; } }); diff --git a/src/controllers/molecules/RoomHeader.js b/src/controllers/molecules/RoomHeader.js index b66d1ff90..766f9b40d 100644 --- a/src/controllers/molecules/RoomHeader.js +++ b/src/controllers/molecules/RoomHeader.js @@ -25,40 +25,16 @@ var dis = require("../../dispatcher"); var CallHandler = require("../../CallHandler"); module.exports = { - _setCallState: function(call) { - if (!call) { - this.setState({ - callState: "NO_CALL" - }); - return; - } - var callState = 'NO_CALL'; - if (call.state !== 'ended') { - if (call.state === 'connected') { - callState = "IN_CALL"; - } - else if (call.direction === 'outbound') { - callState = "OUTBOUND"; - } - else if (call.direction === 'inbound') { - callState = "INBOUND"; - } - else { - console.error("Cannot determine call state."); - } - } - this.setState({ - callState: callState - }); - }, componentDidMount: function() { this.dispatcherRef = dis.register(this.onAction); - var call; if (this.props.room) { - call = CallHandler.getCall(this.props.room.roomId); + var call = CallHandler.getCall(this.props.room.roomId); + var callState = call ? call.call_state : "ended"; + this.setState({ + call_state: callState + }); } - this._setCallState(call); }, componentWillUnmount: function() { @@ -75,7 +51,10 @@ module.exports = { return; } var call = CallHandler.getCall(payload.room_id); - this._setCallState(call); + var callState = call ? call.call_state : "ended"; + this.setState({ + call_state: callState + }); }, onVideoClick: function() { From eedd437ca771a14b9873b3886efa7b813365ec7d Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 16 Jul 2015 11:21:43 +0100 Subject: [PATCH 12/15] Minimal CSS bodge so the video actually dies when the call ends. --- src/controllers/molecules/voip/CallView.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/controllers/molecules/voip/CallView.js b/src/controllers/molecules/voip/CallView.js index 0a5e3e2f6..16cd398d3 100644 --- a/src/controllers/molecules/voip/CallView.js +++ b/src/controllers/molecules/voip/CallView.js @@ -50,9 +50,15 @@ module.exports = { } var call = CallHandler.getCall(payload.room_id); if (call) { + this.getVideoView().getLocalVideoElement().style.display = "initial"; + this.getVideoView().getRemoteVideoElement().style.display = "initial"; call.setLocalVideoElement(this.getVideoView().getLocalVideoElement()); call.setRemoteVideoElement(this.getVideoView().getRemoteVideoElement()); } + else { + this.getVideoView().getLocalVideoElement().style.display = "none"; + this.getVideoView().getRemoteVideoElement().style.display = "none"; + } } }; From 50f9d34211d74cbcb5b693c4866b9e2866e6b855 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 16 Jul 2015 11:30:34 +0100 Subject: [PATCH 13/15] Only display video elements in video calls. --- src/CallHandler.js | 4 +++- src/controllers/molecules/voip/CallView.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index 029b89174..5285be182 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -102,7 +102,9 @@ function _setCallListeners(call) { } function _setCallState(call, roomId, status) { - console.log("_setState >>> %s >>> %s >> %s", call, roomId, status); + console.log( + "Call state in %s changed to %s (%s)", roomId, status, (call ? call.state : "-") + ); calls[roomId] = call; if (call) { call.call_state = status; diff --git a/src/controllers/molecules/voip/CallView.js b/src/controllers/molecules/voip/CallView.js index 16cd398d3..485782e94 100644 --- a/src/controllers/molecules/voip/CallView.js +++ b/src/controllers/molecules/voip/CallView.js @@ -49,7 +49,7 @@ module.exports = { return; } var call = CallHandler.getCall(payload.room_id); - if (call) { + if (call && call.type === "video") { this.getVideoView().getLocalVideoElement().style.display = "initial"; this.getVideoView().getRemoteVideoElement().style.display = "initial"; call.setLocalVideoElement(this.getVideoView().getLocalVideoElement()); From c056bdf1049051b9b56f646310909554aa24f932 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 16 Jul 2015 11:34:39 +0100 Subject: [PATCH 14/15] Only allow calls to be placed if there are 2 joined members. --- src/CallHandler.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/CallHandler.js b/src/CallHandler.js index 5285be182..c041f6462 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -122,6 +122,18 @@ dis.register(function(payload) { if (calls[payload.room_id]) { return; // don't allow >1 call to be placed. } + var room = MatrixClientPeg.get().getRoom(payload.room_id); + if (!room) { + console.error("Room %s does not exist.", payload.room_id); + return; + } + if (room.getJoinedMembers().length !== 2) { + console.error( + "Fail: There are %s joined members in this room, not 2.", + room.getJoinedMembers().length + ); + return; + } console.log("Place %s call in %s", payload.type, payload.room_id); var call = Matrix.createNewMatrixCall( MatrixClientPeg.get(), payload.room_id From 5f3721f4712dc77684fa2906e54e5189122bc729 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 16 Jul 2015 11:54:53 +0100 Subject: [PATCH 15/15] Tidying up --- skins/base/views/molecules/voip/CallView.js | 17 ----------------- src/controllers/molecules/RoomHeader.js | 2 +- src/controllers/molecules/voip/VideoView.js | 2 -- 3 files changed, 1 insertion(+), 20 deletions(-) diff --git a/skins/base/views/molecules/voip/CallView.js b/skins/base/views/molecules/voip/CallView.js index cbdcc4c24..3642e6b58 100644 --- a/skins/base/views/molecules/voip/CallView.js +++ b/skins/base/views/molecules/voip/CallView.js @@ -37,22 +37,5 @@ module.exports = React.createClass({ return ( ); - /* - if (this.state && this.state.call) { - if (this.state.call.type === "video") { - return ( - - ); - } - else if (this.state.call.type === "voice") { - // in the future. - return ( -
- ); - } - } - return ( -
- ); */ } }); \ No newline at end of file diff --git a/src/controllers/molecules/RoomHeader.js b/src/controllers/molecules/RoomHeader.js index 766f9b40d..24f0d47ab 100644 --- a/src/controllers/molecules/RoomHeader.js +++ b/src/controllers/molecules/RoomHeader.js @@ -18,7 +18,7 @@ limitations under the License. /* * State vars: - * this.state.callState = OUTBOUND|INBOUND|IN_CALL|NO_CALL + * this.state.call_state = the UI state of the call (see CallHandler) */ var dis = require("../../dispatcher"); diff --git a/src/controllers/molecules/voip/VideoView.js b/src/controllers/molecules/voip/VideoView.js index b08f6ab1e..8aa688b21 100644 --- a/src/controllers/molecules/voip/VideoView.js +++ b/src/controllers/molecules/voip/VideoView.js @@ -16,8 +16,6 @@ limitations under the License. 'use strict'; -var dis = require("../../../dispatcher"); - module.exports = { };