From 37c9c8fbb425d9fca76c1f9fcd3f7a5a322baf1b Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 15 Jul 2015 16:52:23 +0100 Subject: [PATCH] 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) + }); + } +}; +