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) + }); + } +}; +