diff --git a/package.json b/package.json index 74cfe58b..7199ba1b 100644 --- a/package.json +++ b/package.json @@ -23,14 +23,15 @@ "prepublish": "npm run build:css && npm run build:compile" }, "dependencies": { - "matrix-react-sdk": "^0.0.1", "classnames": "^2.1.2", "filesize": "^3.1.2", "flux": "~2.0.3", + "linkifyjs": "^2.0.0-beta.4", + "matrix-js-sdk": "^0.2.1", + "matrix-react-sdk": "^0.0.1", "q": "^1.4.1", "react": "^0.13.3", - "react-loader": "^1.4.0", - "linkifyjs": "^2.0.0-beta.4" + "react-loader": "^1.4.0" }, "devDependencies": { "babel": "^5.8.23", diff --git a/src/CallHandler.js b/src/CallHandler.js deleted file mode 100644 index 17754d55..00000000 --- a/src/CallHandler.js +++ /dev/null @@ -1,281 +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'; - -/* - * Manages a list of all the currently active calls. - * - * This handler dispatches when voip calls are added/updated/removed from this list: - * { - * action: 'call_state' - * room_id: - * } - * - * To know the state of the call, this handler exposes a getter to - * obtain the call for a room: - * var call = CallHandler.getCall(roomId) - * var state = call.call_state; // ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing - * - * 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("matrix-react-sdk/lib/MatrixClientPeg"); -var Modal = require("matrix-react-sdk/lib/Modal"); -var sdk = require('matrix-react-sdk'); -var ConferenceCall = require("./ConferenceHandler").ConferenceCall; -var ConferenceHandler = require("./ConferenceHandler"); -var Matrix = require("matrix-js-sdk"); -var dis = require("./dispatcher"); - -var calls = { - //room_id: MatrixCall -}; - -function play(audioId) { - // TODO: Attach an invisible element for this instead - // which listens? - var audio = document.getElementById(audioId); - if (audio) { - audio.load(); - audio.play(); - } -} - -function pause(audioId) { - // TODO: Attach an invisible element for this instead - // which listens? - var audio = document.getElementById(audioId); - if (audio) { - audio.pause(); - } -} - -function _setCallListeners(call) { - call.on("error", function(err) { - console.error("Call error: %s", err); - console.error(err.stack); - call.hangup(); - _setCallState(undefined, call.roomId, "ended"); - }); - call.on("hangup", function() { - _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"); - pause("ringbackAudio"); - } - else if (newState === "invite_sent") { - _setCallState(call, call.roomId, "ringback"); - play("ringbackAudio"); - } - else if (newState === "ended" && oldState === "connected") { - _setCallState(undefined, call.roomId, "ended"); - pause("ringbackAudio"); - play("callendAudio"); - } - else if (newState === "ended" && oldState === "invite_sent" && - (call.hangupParty === "remote" || - (call.hangupParty === "local" && call.hangupReason === "invite_timeout") - )) { - _setCallState(call, call.roomId, "busy"); - pause("ringbackAudio"); - play("busyAudio"); - var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); - Modal.createDialog(ErrorDialog, { - title: "Call Timeout", - description: "The remote side failed to pick up." - }); - } - else if (oldState === "invite_sent") { - _setCallState(call, call.roomId, "stop_ringback"); - pause("ringbackAudio"); - } - else if (oldState === "ringing") { - _setCallState(call, call.roomId, "stop_ringing"); - pause("ringbackAudio"); - } - else if (newState === "connected") { - _setCallState(call, call.roomId, "connected"); - pause("ringbackAudio"); - } - }); -} - -function _setCallState(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; - } - dis.dispatch({ - action: 'call_state', - room_id: roomId - }); -} - -dis.register(function(payload) { - switch (payload.action) { - case 'place_call': - if (module.exports.getAnyActiveCall()) { - Modal.createDialog(ErrorDialog, { - title: "Existing Call", - description: "You are already in a call." - }); - 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; - } - - function placeCall(newCall) { - _setCallListeners(newCall); - _setCallState(newCall, newCall.roomId, "ringback"); - if (payload.type === 'voice') { - newCall.placeVoiceCall(); - } - else if (payload.type === 'video') { - newCall.placeVideoCall( - payload.remote_element, - payload.local_element - ); - } - else { - console.error("Unknown conf call type: %s", payload.type); - } - } - - var members = room.getJoinedMembers(); - if (members.length <= 1) { - Modal.createDialog(ErrorDialog, { - description: "You cannot place a call with yourself." - }); - return; - } - else if (members.length === 2) { - console.log("Place %s call in %s", payload.type, payload.room_id); - var call = Matrix.createNewMatrixCall( - MatrixClientPeg.get(), payload.room_id - ); - placeCall(call); - } - else { // > 2 - console.log("Place conference call in %s", payload.room_id); - var confCall = new ConferenceCall( - MatrixClientPeg.get(), payload.room_id - ); - confCall.setup().done(function(call) { - placeCall(call); - }, function(err) { - console.error("Failed to setup conference call: %s", err); - }); - } - break; - case 'incoming_call': - if (module.exports.getAnyActiveCall()) { - 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, "ringing"); - break; - case 'hangup': - if (!calls[payload.room_id]) { - return; // no call to hangup - } - calls[payload.room_id].hangup(); - _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, "connected"); - dis.dispatch({ - action: "view_room", - room_id: payload.room_id - }); - break; - } -}); - -module.exports = { - - getCallForRoom: function(roomId) { - return ( - module.exports.getCall(roomId) || - module.exports.getConferenceCall(roomId) - ); - }, - - getCall: function(roomId) { - return calls[roomId] || null; - }, - - getConferenceCall: function(roomId) { - // search for a conference 1:1 call for this group chat room ID - var activeCall = module.exports.getAnyActiveCall(); - if (activeCall && activeCall.confUserId) { - var thisRoomConfUserId = ConferenceHandler.getConferenceUserIdForRoom( - roomId - ); - if (thisRoomConfUserId === activeCall.confUserId) { - return activeCall; - } - } - return null; - }, - - getAnyActiveCall: function() { - var roomsWithCalls = Object.keys(calls); - for (var i = 0; i < roomsWithCalls.length; i++) { - if (calls[roomsWithCalls[i]] && - calls[roomsWithCalls[i]].call_state !== "ended") { - return calls[roomsWithCalls[i]]; - } - } - return null; - } -}; diff --git a/src/controllers/molecules/voip/CallView.js b/src/controllers/molecules/voip/CallView.js index a20e4463..e782f228 100644 --- a/src/controllers/molecules/voip/CallView.js +++ b/src/controllers/molecules/voip/CallView.js @@ -16,9 +16,11 @@ limitations under the License. 'use strict'; var dis = require("../../../dispatcher"); -var CallHandler = require("../../../CallHandler"); +var CallHandler = require("matrix-react-sdk/lib/CallHandler"); var MatrixClientPeg = require("../../../MatrixClientPeg"); +var VectorConferenceHandler = require('./VectorConferenceHandler'); + /* * State vars: * this.state.call = MatrixCall|null @@ -66,7 +68,10 @@ module.exports = { }, showCall: function(roomId) { - var call = CallHandler.getCallForRoom(roomId); + var call = ( + CallHandler.getCallForRoom(roomId) || + VectorConferenceHandler.getConferenceCallForRoom(roomId) + ); if (call) { call.setLocalVideoElement(this.getVideoView().getLocalVideoElement()); // N.B. the remote video element is used for playback for audio for voice calls diff --git a/src/ConferenceHandler.js b/src/modules/VectorConferenceHandler.js similarity index 81% rename from src/ConferenceHandler.js rename to src/modules/VectorConferenceHandler.js index c617672e..0d42461f 100644 --- a/src/ConferenceHandler.js +++ b/src/modules/VectorConferenceHandler.js @@ -1,4 +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"; + var q = require("q"); var Matrix = require("matrix-js-sdk"); var Room = Matrix.Room; @@ -90,5 +107,12 @@ module.exports.getConferenceUserIdForRoom = function(roomId) { return "@" + USER_PREFIX + base64RoomId + ":" + DOMAIN; }; +module.exports.createNewMatrixCall = function(client, roomId) { + return new ConferenceCall( + client, roomId + ); +}; + module.exports.ConferenceCall = ConferenceCall; +module.exports.slot = 'conference'; diff --git a/src/skins/vector/skindex.js b/src/skins/vector/skindex.js index 9afaddd6..4f76ad62 100644 --- a/src/skins/vector/skindex.js +++ b/src/skins/vector/skindex.js @@ -21,71 +21,62 @@ limitations under the License. * You are not a salmon. */ -var skin = { - atoms: {}, - molecules: {}, - organisms: {}, - templates: {}, - pages: {} -}; +var skin = {}; -skin.atoms.EditableText = require('./views/atoms/EditableText'); -skin.atoms.EnableNotificationsButton = require('./views/atoms/EnableNotificationsButton'); -skin.atoms.ImageView = require('./views/atoms/ImageView'); -skin.atoms.LogoutButton = require('./views/atoms/LogoutButton'); -skin.atoms.MemberAvatar = require('./views/atoms/MemberAvatar'); -skin.atoms.MessageTimestamp = require('./views/atoms/MessageTimestamp'); -skin.atoms.RoomAvatar = require('./views/atoms/RoomAvatar'); -skin.atoms.create_room = {}; -skin.atoms.create_room.CreateRoomButton = require('./views/atoms/create_room/CreateRoomButton'); -skin.atoms.create_room.Presets = require('./views/atoms/create_room/Presets'); -skin.atoms.create_room.RoomAlias = require('./views/atoms/create_room/RoomAlias'); -skin.atoms.voip = {}; -skin.atoms.voip.VideoFeed = require('./views/atoms/voip/VideoFeed'); -skin.molecules.BottomLeftMenu = require('./views/molecules/BottomLeftMenu'); -skin.molecules.ChangeAvatar = require('./views/molecules/ChangeAvatar'); -skin.molecules.ChangePassword = require('./views/molecules/ChangePassword'); -skin.molecules.DateSeparator = require('./views/molecules/DateSeparator'); -skin.molecules.EventAsTextTile = require('./views/molecules/EventAsTextTile'); -skin.molecules.MEmoteTile = require('./views/molecules/MEmoteTile'); -skin.molecules.MFileTile = require('./views/molecules/MFileTile'); -skin.molecules.MImageTile = require('./views/molecules/MImageTile'); -skin.molecules.MNoticeTile = require('./views/molecules/MNoticeTile'); -skin.molecules.MRoomMemberTile = require('./views/molecules/MRoomMemberTile'); -skin.molecules.MTextTile = require('./views/molecules/MTextTile'); -skin.molecules.MatrixToolbar = require('./views/molecules/MatrixToolbar'); -skin.molecules.MemberInfo = require('./views/molecules/MemberInfo'); -skin.molecules.MemberTile = require('./views/molecules/MemberTile'); -skin.molecules.MessageComposer = require('./views/molecules/MessageComposer'); -skin.molecules.MessageTile = require('./views/molecules/MessageTile'); -skin.molecules.ProgressBar = require('./views/molecules/ProgressBar'); -skin.molecules.RoomCreate = require('./views/molecules/RoomCreate'); -skin.molecules.RoomDropTarget = require('./views/molecules/RoomDropTarget'); -skin.molecules.RoomHeader = require('./views/molecules/RoomHeader'); -skin.molecules.RoomSettings = require('./views/molecules/RoomSettings'); -skin.molecules.RoomTile = require('./views/molecules/RoomTile'); -skin.molecules.SenderProfile = require('./views/molecules/SenderProfile'); -skin.molecules.ServerConfig = require('./views/molecules/ServerConfig'); -skin.molecules.UnknownMessageTile = require('./views/molecules/UnknownMessageTile'); -skin.molecules.UserSelector = require('./views/molecules/UserSelector'); -skin.molecules.voip = {}; -skin.molecules.voip.CallView = require('./views/molecules/voip/CallView'); -skin.molecules.voip.IncomingCallBox = require('./views/molecules/voip/IncomingCallBox'); -skin.molecules.voip.VideoView = require('./views/molecules/voip/VideoView'); -skin.organisms.CreateRoom = require('./views/organisms/CreateRoom'); -skin.organisms.ErrorDialog = require('./views/organisms/ErrorDialog'); -skin.organisms.LeftPanel = require('./views/organisms/LeftPanel'); -skin.organisms.LogoutPrompt = require('./views/organisms/LogoutPrompt'); -skin.organisms.MemberList = require('./views/organisms/MemberList'); -skin.organisms.Notifier = require('./views/organisms/Notifier'); -skin.organisms.QuestionDialog = require('./views/organisms/QuestionDialog'); -skin.organisms.RightPanel = require('./views/organisms/RightPanel'); -skin.organisms.RoomDirectory = require('./views/organisms/RoomDirectory'); -skin.organisms.RoomList = require('./views/organisms/RoomList'); -skin.organisms.RoomView = require('./views/organisms/RoomView'); -skin.organisms.UserSettings = require('./views/organisms/UserSettings'); -skin.pages.MatrixChat = require('./views/pages/MatrixChat'); -skin.templates.Login = require('./views/templates/Login'); -skin.templates.Register = require('./views/templates/Register'); +skin['atoms.EditableText'] = require('./views/atoms/EditableText'); +skin['atoms.EnableNotificationsButton'] = require('./views/atoms/EnableNotificationsButton'); +skin['atoms.ImageView'] = require('./views/atoms/ImageView'); +skin['atoms.LogoutButton'] = require('./views/atoms/LogoutButton'); +skin['atoms.MemberAvatar'] = require('./views/atoms/MemberAvatar'); +skin['atoms.MessageTimestamp'] = require('./views/atoms/MessageTimestamp'); +skin['atoms.RoomAvatar'] = require('./views/atoms/RoomAvatar'); +skin['atoms.create_room.CreateRoomButton'] = require('./views/atoms/create_room/CreateRoomButton'); +skin['atoms.create_room.Presets'] = require('./views/atoms/create_room/Presets'); +skin['atoms.create_room.RoomAlias'] = require('./views/atoms/create_room/RoomAlias'); +skin['atoms.voip.VideoFeed'] = require('./views/atoms/voip/VideoFeed'); +skin['molecules.BottomLeftMenu'] = require('./views/molecules/BottomLeftMenu'); +skin['molecules.ChangeAvatar'] = require('./views/molecules/ChangeAvatar'); +skin['molecules.ChangePassword'] = require('./views/molecules/ChangePassword'); +skin['molecules.DateSeparator'] = require('./views/molecules/DateSeparator'); +skin['molecules.EventAsTextTile'] = require('./views/molecules/EventAsTextTile'); +skin['molecules.MEmoteTile'] = require('./views/molecules/MEmoteTile'); +skin['molecules.MFileTile'] = require('./views/molecules/MFileTile'); +skin['molecules.MImageTile'] = require('./views/molecules/MImageTile'); +skin['molecules.MNoticeTile'] = require('./views/molecules/MNoticeTile'); +skin['molecules.MRoomMemberTile'] = require('./views/molecules/MRoomMemberTile'); +skin['molecules.MTextTile'] = require('./views/molecules/MTextTile'); +skin['molecules.MatrixToolbar'] = require('./views/molecules/MatrixToolbar'); +skin['molecules.MemberInfo'] = require('./views/molecules/MemberInfo'); +skin['molecules.MemberTile'] = require('./views/molecules/MemberTile'); +skin['molecules.MessageComposer'] = require('./views/molecules/MessageComposer'); +skin['molecules.MessageTile'] = require('./views/molecules/MessageTile'); +skin['molecules.ProgressBar'] = require('./views/molecules/ProgressBar'); +skin['molecules.RoomCreate'] = require('./views/molecules/RoomCreate'); +skin['molecules.RoomDropTarget'] = require('./views/molecules/RoomDropTarget'); +skin['molecules.RoomHeader'] = require('./views/molecules/RoomHeader'); +skin['molecules.RoomSettings'] = require('./views/molecules/RoomSettings'); +skin['molecules.RoomTile'] = require('./views/molecules/RoomTile'); +skin['molecules.SenderProfile'] = require('./views/molecules/SenderProfile'); +skin['molecules.ServerConfig'] = require('./views/molecules/ServerConfig'); +skin['molecules.UnknownMessageTile'] = require('./views/molecules/UnknownMessageTile'); +skin['molecules.UserSelector'] = require('./views/molecules/UserSelector'); +skin['molecules.voip.CallView'] = require('./views/molecules/voip/CallView'); +skin['molecules.voip.IncomingCallBox'] = require('./views/molecules/voip/IncomingCallBox'); +skin['molecules.voip.VideoView'] = require('./views/molecules/voip/VideoView'); +skin['organisms.CreateRoom'] = require('./views/organisms/CreateRoom'); +skin['organisms.ErrorDialog'] = require('./views/organisms/ErrorDialog'); +skin['organisms.LeftPanel'] = require('./views/organisms/LeftPanel'); +skin['organisms.LogoutPrompt'] = require('./views/organisms/LogoutPrompt'); +skin['organisms.MemberList'] = require('./views/organisms/MemberList'); +skin['organisms.Notifier'] = require('./views/organisms/Notifier'); +skin['organisms.QuestionDialog'] = require('./views/organisms/QuestionDialog'); +skin['organisms.RightPanel'] = require('./views/organisms/RightPanel'); +skin['organisms.RoomDirectory'] = require('./views/organisms/RoomDirectory'); +skin['organisms.RoomList'] = require('./views/organisms/RoomList'); +skin['organisms.RoomView'] = require('./views/organisms/RoomView'); +skin['organisms.UserSettings'] = require('./views/organisms/UserSettings'); +skin['pages.MatrixChat'] = require('./views/pages/MatrixChat'); +skin['templates.Login'] = require('./views/templates/Login'); +skin['templates.Register'] = require('./views/templates/Register'); module.exports = skin; \ No newline at end of file diff --git a/src/vector/index.js b/src/vector/index.js index accba178..597803f7 100644 --- a/src/vector/index.js +++ b/src/vector/index.js @@ -19,6 +19,7 @@ limitations under the License. var React = require("react"); var sdk = require("matrix-react-sdk"); sdk.loadSkin(require('../skins/vector/skindex')); +sdk.loadModule(require('../modules/VectorConferenceHandler')); var lastLocationHashSet = null;