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 00000000..71681b99 --- /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 00000000..813740fb --- /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 11e2be9c..fcc1d274 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 e00ef242..e717ebb9 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 00000000..8aa688b2 --- /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 23bd63d3..e4a16b82 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 eb91b544..26b96baa 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 fcc1d274..11e2be9c 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 e717ebb9..ec0f00b6 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 8aa688b2..49f85caa 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 813740fb..be594142 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 26b96baa..c7eb1474 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 49f85caa..047c3789 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 00000000..245f8bb9 --- /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 717f91e7..95776c2a 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 71681b99..7fbee436 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 be594142..954ad364 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 c7eb1474..fcafb945 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 9bb54fa6..7004da15 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 245f8bb9..83b2328a 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 00000000..8fd8282f --- /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 fcafb945..5ff28092 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 7004da15..41beeadf 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 00000000..f5e09338 --- /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 83b2328a..b08f6ab1 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 8fd8282f..41023dd6 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 f5e09338..0bb4685b 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 e4a16b82..95e27a06 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 41023dd6..cbdcc4c2 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 5ff28092..debcbfb8 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 00000000..ce6d8906 --- /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 41beeadf..c2c996e4 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 047c3789..29ddf70e 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 0bb4685b..00000000 --- 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 00000000..9546f885 --- /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 95e27a06..e64aee53 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 29ddf70e..dde8cf4e 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 ce6d8906..5fefa7cf 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 dde8cf4e..b66d1ff9 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 5fefa7cf..d6367a6b 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 9546f885..0a5e3e2f 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 e64aee53..1708bd1c 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 d6367a6b..029b8917 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 b66d1ff9..766f9b40 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 0a5e3e2f..16cd398d 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 029b8917..5285be18 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 16cd398d..485782e9 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 5285be18..c041f646 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 cbdcc4c2..3642e6b5 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 766f9b40..24f0d47a 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 b08f6ab1..8aa688b2 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 = { };