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 0000000000..71681b99eb --- /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 0000000000..813740fb10 --- /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 11e2be9ca2..fcc1d274a6 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 e00ef2424a..e717ebb981 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 0000000000..8aa688b21e --- /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 23bd63d3ca..e4a16b829a 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 eb91b5442c..26b96baaee 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 fcc1d274a6..11e2be9ca2 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 e717ebb981..ec0f00b6ff 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 8aa688b21e..49f85caa9b 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 813740fb10..be594142ce 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 26b96baaee..c7eb1474ce 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 49f85caa9b..047c378997 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 0000000000..245f8bb9d6 --- /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 717f91e7a4..95776c2a23 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 71681b99eb..7fbee43699 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 be594142ce..954ad3648f 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 c7eb1474ce..fcafb945e2 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 9bb54fa612..7004da1575 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 245f8bb9d6..83b2328ab1 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 0000000000..8fd8282fcf --- /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 fcafb945e2..5ff280925f 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 7004da1575..41beeadf18 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 0000000000..f5e09338d2 --- /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 83b2328ab1..b08f6ab1e1 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 8fd8282fcf..41023dd639 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 f5e09338d2..0bb4685bfc 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 e4a16b829a..95e27a062b 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 41023dd639..cbdcc4c240 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 5ff280925f..debcbfb8f0 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 0000000000..ce6d8906af --- /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 41beeadf18..c2c996e4be 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 047c378997..29ddf70e6a 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 0bb4685bfc..0000000000 --- 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 0000000000..9546f885ff --- /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 95e27a062b..e64aee5387 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 29ddf70e6a..dde8cf4ef8 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 ce6d8906af..5fefa7cf10 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 dde8cf4ef8..b66d1ff90f 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 5fefa7cf10..d6367a6bf2 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 9546f885ff..0a5e3e2f66 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 e64aee5387..1708bd1c39 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 d6367a6bf2..029b89174a 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 b66d1ff90f..766f9b40d0 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 0a5e3e2f66..16cd398d33 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 029b89174a..5285be1825 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 16cd398d33..485782e943 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 5285be1825..c041f64628 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 cbdcc4c240..3642e6b58b 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 766f9b40d0..24f0d47abe 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 b08f6ab1e1..8aa688b21e 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 = { };