diff --git a/appinfo/info.xml b/appinfo/info.xml index 07590aebf0b..60837b27292 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -38,7 +38,7 @@ And in the works for the [coming versions](https://github.com/nextcloud/spreed/m https://github.com/nextcloud/spreed/issues https://github.com/nextcloud/spreed.git - 2.1.9 + 2.1.10 diff --git a/appinfo/routes.php b/appinfo/routes.php index c0aed8a5c51..11f867dd63f 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -221,6 +221,24 @@ 'token' => '^[a-z0-9]{4,30}$', ], ], + [ + 'name' => 'Room#joinRoom', + 'url' => '/api/{apiVersion}/room/{token}/participants/active', + 'verb' => 'POST', + 'requirements' => [ + 'apiVersion' => 'v1', + 'token' => '^[a-z0-9]{4,30}$', + ], + ], + [ + 'name' => 'Room#exitRoom', + 'url' => '/api/{apiVersion}/room/{token}/participants/active', + 'verb' => 'DELETE', + 'requirements' => [ + 'apiVersion' => 'v1', + 'token' => '^[a-z0-9]{4,30}$', + ], + ], [ 'name' => 'Room#promoteModerator', 'url' => '/api/{apiVersion}/room/{token}/moderators', diff --git a/docs/api-v1.md b/docs/api-v1.md index 43f3ff295c5..d60355d08d1 100644 --- a/docs/api-v1.md +++ b/docs/api-v1.md @@ -278,6 +278,38 @@ Base endpoint is: `/ocs/v2.php/apps/spreed/api/v1` * Method: `DELETE` * Endpoint: `/room/{token}/participants/self` +* Response: + - Header: + + `200 OK` + + `404 Not Found` When the room could not be found for the participant + +### Join a room (available for call and chat) + +* Method: `POST` +* Endpoint: `/room/{token}/participants/active` +* Data: + + field | type | Description + ------|------|------------ + `password` | string | Optional: Password is only required for users which are of type `4` or `5` and only when the room has `hasPassword` set to true. + +* Response: + - Header: + + `200 OK` + + `403 Forbidden` When the password is required and didn't match + + `404 Not Found` When the room could not be found for the participant + + - Data: + + field | type | Description + ------|------|------------ + `sessionId` | string | 512 character long string + +### Leave a room (not available for call and chat anymore) + +* Method: `DELETE` +* Endpoint: `/room/{token}/participants/active` + * Response: - Header: + `200 OK` @@ -347,24 +379,12 @@ Base endpoint is: `/ocs/v2.php/apps/spreed/api/v1` * Method: `POST` * Endpoint: `/call/{token}` -* Data: - - field | type | Description - ------|------|------------ - `password` | string | Optional: Password is only required for users which are of type `4` or `5` and only when the room has `hasPassword` set to true. * Response: - Header: + `200 OK` - + `403 Forbidden` When the password is required and didn't match + `404 Not Found` When the room could not be found for the participant - - Data: - - field | type | Description - ------|------|------------ - `sessionId` | string | 512 character long string - ### Send ping to keep the call alive * Method: `POST` @@ -375,7 +395,7 @@ Base endpoint is: `/ocs/v2.php/apps/spreed/api/v1` + `200 OK` + `404 Not Found` When the room could not be found for the participant -### Leave a call (but staying in the room for future calls) +### Leave a call (but staying in the room for future calls and chat) * Method: `DELETE` * Endpoint: `/call/{token}` diff --git a/js/app.js b/js/app.js index c06f74adec2..d8f475680e8 100644 --- a/js/app.js +++ b/js/app.js @@ -372,7 +372,6 @@ self.setRoomMessageForGuest(self.activeRoom.get('participants')); } - // Disable video when entering a room with more than 5 participants. if (Object.keys(self.activeRoom.get('participants')).length > 5) { self.disableVideo(); @@ -525,18 +524,10 @@ this._registerPageEvents(); this.initShareRoomClipboard(); - var token = $('#app').attr('data-token'); - if (token) { - if (OCA.SpreedMe.webrtc.sessionReady) { - OCA.SpreedMe.Calls.join(token); - } else { - OCA.SpreedMe.webrtc.once('connectionReady', function() { - OCA.SpreedMe.Calls.join(token); - }); - } - } OCA.SpreedMe.Calls.showCamera(); + var token = $('#app').attr('data-token'); + if (oc_current_user) { this._showRoomList(); this.signaling.setRoomCollection(this._rooms) @@ -557,10 +548,20 @@ } this.initAudioVideoSettings(configuration); + + if (token) { + if (OCA.SpreedMe.webrtc.sessionReady) { + OCA.SpreedMe.Calls.joinRoom(token); + } else { + OCA.SpreedMe.webrtc.once('connectionReady', function() { + OCA.SpreedMe.Calls.joinRoom(token); + }); + } + } }, _onPopState: function(params) { if (!_.isUndefined(params.token)) { - OCA.SpreedMe.Calls.join(params.token); + OCA.SpreedMe.Calls.joinRoom(params.token); } }, onDocumentClick: function(event) { diff --git a/js/calls.js b/js/calls.js index adad25d55cc..edc7b13b324 100644 --- a/js/calls.js +++ b/js/calls.js @@ -35,7 +35,7 @@ OC.Util.History.pushState({ token: token }, OC.generateUrl('/call/' + token)); - this.join(token); + this.joinRoom(token); }, createOneToOneVideoCall: function(recipientUserId) { console.log("Creating one-to-one video call", recipientUserId); @@ -81,7 +81,15 @@ success: _.bind(this._createCallSuccessHandle, this) }); }, - join: function(token) { + joinRoom: function(token) { + if (signaling.currentRoomToken === token) { + return; + } + + OCA.SpreedMe.webrtc.leaveRoom(); + OCA.SpreedMe.webrtc.joinRoom(token); + }, + joinCall: function(token) { if (signaling.currentCallToken === token) { return; } @@ -90,8 +98,12 @@ $('.videoView').addClass('hidden'); $('#app-content').addClass('icon-loading'); - OCA.SpreedMe.webrtc.leaveRoom(); - OCA.SpreedMe.webrtc.joinRoom(token); + OCA.SpreedMe.webrtc.leaveCall(); + OCA.SpreedMe.webrtc.joinCall(token); + }, + leaveCall: function(token) { + $('#app-content').removeClass('incall'); + OCA.SpreedMe.webrtc.leaveCall(); }, leaveCurrentCall: function(deleter) { OCA.SpreedMe.webrtc.leaveRoom(); diff --git a/js/signaling.js b/js/signaling.js index 43782cc1171..fa84ed1ddb7 100644 --- a/js/signaling.js +++ b/js/signaling.js @@ -6,6 +6,7 @@ function SignalingBase(settings) { this.settings = settings; this.sessionId = ''; + this.currentRoomToken = null; this.currentCallToken = null; this.handlers = {}; this.features = {}; @@ -61,12 +62,16 @@ SignalingBase.prototype.emit = function(ev, data) { switch (ev) { - case 'join': - var callback = arguments[2]; - var token = data; - this.joinCall(token, callback); + case 'joinRoom': + this.joinRoom(data); break; - case 'leave': + case 'joinCall': + this.joinCall(data, arguments[2]); + break; + case 'leaveRoom': + this.leaveCurrentRoom(); + break; + case 'leaveCall': this.leaveCurrentCall(); break; case 'message': @@ -75,6 +80,13 @@ } }; + SignalingBase.prototype.leaveCurrentRoom = function() { + if (this.currentCallToken) { + this.leaveRoom(this.currentCallToken); + this.currentCallToken = null; + } + }; + SignalingBase.prototype.leaveCurrentCall = function() { if (this.currentCallToken) { this.leaveCall(this.currentCallToken); @@ -207,9 +219,9 @@ return defer; }; - InternalSignaling.prototype.joinCall = function(token, callback, password) { + InternalSignaling.prototype.joinRoom = function(token, password) { $.ajax({ - url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token, + url: OC.linkToOCS('apps/spreed/api/v1/room', 2) + token + '/participants/active', type: 'POST', beforeSend: function (request) { request.setRequestHeader('Accept', 'application/json'); @@ -220,14 +232,9 @@ success: function (result) { console.log("Joined", result); this.sessionId = result.ocs.data.sessionId; - this.currentCallToken = token; + this.currentRoomToken = token; this._startPingCall(); this._startPullingMessages(); - // We send an empty call description to simplewebrtc since - // usersChanged (webrtc.js) will create/remove peer connections - // with call participants - var callDescription = {'clients': {}}; - callback('', callDescription); }.bind(this), error: function (result) { if (result.status === 404 || result.status === 503) { @@ -243,7 +250,7 @@ t('spreed','Password required'), function (result, password) { if (result && password !== '') { - this.joinCall(token, callback, password); + this.joinRoom(token, password); } }.bind(this), true, @@ -262,11 +269,46 @@ }); }; - InternalSignaling.prototype.leaveCall = function(token) { - if (token === this.currentCallToken) { + InternalSignaling.prototype.leaveRoom = function(token) { + if (this.currentCallToken) { + this.leaveCall(); + } + + if (token === this.currentRoomToken) { this._stopPingCall(); this._closeEventSource(); } + + $.ajax({ + url: OC.linkToOCS('apps/spreed/api/v1/room', 2) + token + '/participants/active', + method: 'DELETE', + async: false + }); + }; + + InternalSignaling.prototype.joinCall = function(token, callback) { + $.ajax({ + url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token, + type: 'POST', + beforeSend: function (request) { + request.setRequestHeader('Accept', 'application/json'); + }, + success: function () { + this.currentCallToken = token; + // We send an empty call description to simplewebrtc since + // usersChanged (webrtc.js) will create/remove peer connections + // with call participants + var callDescription = {'clients': {}}; + callback('', callDescription); + }.bind(this), + error: function () { + // Room not found or maintenance mode + OC.redirect(OC.generateUrl('apps/spreed')); + }.bind(this) + }); + }; + + InternalSignaling.prototype.leaveCall = function(token) { $.ajax({ url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token, method: 'DELETE', diff --git a/js/simplewebrtc.js b/js/simplewebrtc.js index 62b4d9e7ac6..89b277a6bba 100644 --- a/js/simplewebrtc.js +++ b/js/simplewebrtc.js @@ -18185,14 +18185,22 @@ SimpleWebRTC.prototype.leaveRoom = function () { if (this.roomName) { - this.connection.emit('leave'); + this.connection.emit('leaveRoom'); + this.emit('leftRoom', this.roomName); + this.roomName = undefined; + } + }; + + SimpleWebRTC.prototype.leaveCall = function () { + if (this.roomName) { + this.connection.emit('leaveCall'); while (this.webrtc.peers.length) { this.webrtc.peers[0].end(); } if (this.getLocalScreen()) { this.stopScreenShare(); } - this.emit('leftRoom', this.roomName); + this.emit('leftCall', this.roomName); this.roomName = undefined; } }; @@ -18249,10 +18257,16 @@ }); }; - SimpleWebRTC.prototype.joinRoom = function (name, cb) { + SimpleWebRTC.prototype.joinRoom = function (name) { + this.connection.emit('joinRoom', name); + this.roomName = name; + this.emit('joinedRoom', name); + }; + + SimpleWebRTC.prototype.joinCall = function (name, cb) { var self = this; this.roomName = name; - this.connection.emit('join', name, function (err, roomDescription) { + this.connection.emit('joinCall', name, function (err, roomDescription) { console.log('join CB', err, roomDescription); if (err) { self.emit('error', err); @@ -18282,7 +18296,7 @@ } if (cb) cb(err, roomDescription); - self.emit('joinedRoom', name); + self.emit('joinedCall', name); }); }; diff --git a/js/views/callinfoview.js b/js/views/callinfoview.js index 157f571963e..2db9becfa73 100644 --- a/js/views/callinfoview.js +++ b/js/views/callinfoview.js @@ -36,6 +36,15 @@ '{{#if isGuest}}' + '
' + '{{/if}}' + + '{{#if participantInCall}}' + + '
' + + ' ' + + '
' + + '{{else}}' + + '
' + + ' ' + + '
' + + '{{/if}}' + '{{#if canModerate}}' + '
' + ' ' + @@ -76,6 +85,8 @@ 'linkCheckbox': '.link-checkbox', 'guestName': 'div.guest-name', + 'joinCallButton': 'button.join-call', + 'leaveCallButton': 'button.leave-call', 'passwordOption': '.password-option', 'passwordInput': '.password-input', @@ -91,13 +102,18 @@ 'change @ui.linkCheckbox': 'toggleLinkCheckbox', 'keyup @ui.passwordInput': 'keyUpPassword', - 'click @ui.passwordConfirm': 'confirmPassword' + 'click @ui.passwordConfirm': 'confirmPassword', + 'click @ui.joinCallButton': 'joinCall', + 'click @ui.leaveCallButton': 'leaveCall' }, modelEvents: { 'change:hasPassword': function() { this.renderWhenInactive(); }, + 'change:participantInCall': function() { + this.renderWhenInactive(); + }, 'change:participantType': function() { this._updateNameEditability(); @@ -222,6 +238,14 @@ }); }, + joinCall: function() { + OCA.SpreedMe.Calls.joinCall(this.model.get('token')); + }, + + leaveCall: function() { + OCA.SpreedMe.Calls.leaveCall(this.model.get('token')); + }, + /** * Password */ diff --git a/js/views/roomlistview.js b/js/views/roomlistview.js index 06889cab61f..a3f5679b25b 100644 --- a/js/views/roomlistview.js +++ b/js/views/roomlistview.js @@ -200,7 +200,7 @@ joinRoom: function(e) { e.preventDefault(); var token = this.ui.room.attr('data-token'); - OCA.SpreedMe.Calls.join(token); + OCA.SpreedMe.Calls.joinRoom(token); OC.Util.History.pushState({ token: token diff --git a/js/webrtc.js b/js/webrtc.js index b8de9df1ab6..4037f7adef8 100644 --- a/js/webrtc.js +++ b/js/webrtc.js @@ -40,6 +40,10 @@ var spreedPeerConnectionTable = []; var currentSessionId = webrtc.connection.getSessionid(); newUsers.forEach(function(user) { + if (!user.inCall) { + return; + } + // TODO(fancycode): Adjust property name of internal PHP backend to be all lowercase. var sessionId = user.sessionId || user.sessionid; if (!sessionId || sessionId === currentSessionId || previousUsersInRoom.indexOf(sessionId) !== -1) { @@ -125,9 +129,15 @@ var spreedPeerConnectionTable = []; var currentSessionId = webrtc.connection.getSessionid(); var currentUsersInRoom = []; var userMapping = {}; + var selfInCall = false; users.forEach(function(user) { + if (!user['inCall']) { + return; + } + var sessionId = user['sessionId'] || user.sessionid; if (sessionId === currentSessionId) { + selfInCall = true; return; } @@ -135,6 +145,10 @@ var spreedPeerConnectionTable = []; userMapping[sessionId] = user; }); + if (!selfInCall) { + return; + } + var newSessionIds = currentUsersInRoom.diff(previousUsersInRoom); var disconnectedSessionIds = previousUsersInRoom.diff(currentUsersInRoom); var newUsers = []; @@ -643,9 +657,12 @@ var spreedPeerConnectionTable = []; } OCA.SpreedMe.webrtc.on('joinedRoom', function(name) { + OCA.SpreedMe.app.syncAndSetActiveRoom(name); + }); + + OCA.SpreedMe.webrtc.on('joinedCall', function() { $('#app-content').removeClass('icon-loading'); $('.videoView').removeClass('hidden'); - OCA.SpreedMe.app.syncAndSetActiveRoom(name); }); OCA.SpreedMe.webrtc.on('channelOpen', function(channel) { diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 62d9c823fee..3aa1e3ec76b 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -87,6 +87,8 @@ protected function registerSignalingHooks(EventDispatcherInterface $dispatcher) $dispatcher->addListener(Room::class . '::postRemoveUser', $listener); $dispatcher->addListener(Room::class . '::postRemoveBySession', $listener); $dispatcher->addListener(Room::class . '::postUserDisconnectRoom', $listener); + $dispatcher->addListener(Room::class . '::postSessionJoinCall', $listener); + $dispatcher->addListener(Room::class . '::postSessionLeaveCall', $listener); $dispatcher->addListener(Room::class . '::postAddUsers', function(GenericEvent $event) { /** @var BackendNotifier $notifier */ diff --git a/lib/Controller/CallController.php b/lib/Controller/CallController.php index 00f5b3630c8..009f2fdd9e4 100644 --- a/lib/Controller/CallController.php +++ b/lib/Controller/CallController.php @@ -106,8 +106,8 @@ public function getPeersForCall($token) { $participants = $room->getParticipants(time() - 30); $result = []; foreach ($participants['users'] as $participant => $data) { - if ($data['sessionId'] === '0') { - // User left the room + if ($data['sessionId'] === '0' || !$data['inCall']) { + // User is not active in call continue; } @@ -136,38 +136,36 @@ public function getPeersForCall($token) { * @UseSession * * @param string $token - * @param string $password * @return DataResponse */ - public function joinCall($token, $password) { + public function joinCall($token) { try { $room = $this->manager->getRoomForParticipantByToken($token, $this->userId); } catch (RoomNotFoundException $e) { return new DataResponse([], Http::STATUS_NOT_FOUND); } - try { - if ($this->userId !== null) { - $sessionIds = $this->manager->getSessionIdsForUser($this->userId); - $newSessionId = $room->enterRoomAsUser($this->userId, $password, $this->session->get('spreed-password') === $room->getToken()); - - if (!empty($sessionIds)) { - $this->messages->deleteMessages($sessionIds); - } - } else { - $newSessionId = $room->enterRoomAsGuest($password, $this->session->get('spreed-password') === $room->getToken()); + if ($this->userId !== null) { + if (!$this->session->exists('spreed-session')) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + $sessionId = $this->session->get('spreed-session'); + } else { + try { + $participant = $room->getParticipant($this->userId); + } catch (ParticipantNotFoundException $e) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + + $sessionId = $participant->getSessionId(); + if ($sessionId === '0') { + return new DataResponse([], Http::STATUS_NOT_FOUND); } - } catch (InvalidPasswordException $e) { - return new DataResponse([], Http::STATUS_FORBIDDEN); } - $this->session->remove('spreed-password'); - $this->session->set('spreed-session', $newSessionId); - $room->ping($this->userId, $newSessionId, time()); + $room->changeInCall($sessionId, true); - return new DataResponse([ - 'sessionId' => $newSessionId, - ]); + return new DataResponse(); } /** @@ -201,23 +199,32 @@ public function pingCall($token) { * @return DataResponse */ public function leaveCall($token) { - $sessionId = $this->session->get('spreed-session'); - $this->session->remove('spreed-session'); - try { $room = $this->manager->getRoomForParticipantByToken($token, $this->userId); + } catch (RoomNotFoundException $e) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } - if ($this->userId === null) { - $participant = $room->getParticipantBySession($sessionId); - $room->removeParticipantBySession($participant); - } else { + if ($this->userId !== null) { + if (!$this->session->exists('spreed-session')) { + return new DataResponse(); + } + $sessionId = $this->session->get('spreed-session'); + } else { + try { $participant = $room->getParticipant($this->userId); - $room->disconnectUserFromAllRooms($participant->getUser()); + } catch (ParticipantNotFoundException $e) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + + $sessionId = $participant->getSessionId(); + if ($sessionId === '0') { + return new DataResponse([], Http::STATUS_NOT_FOUND); } - } catch (RoomNotFoundException $e) { - } catch (ParticipantNotFoundException $e) { } + $room->changeInCall($sessionId, false); + return new DataResponse(); } diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index 27cd045d842..69a42f61c09 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -25,11 +25,13 @@ namespace OCA\Spreed\Controller; +use OCA\Spreed\Exceptions\InvalidPasswordException; use OCA\Spreed\Exceptions\ParticipantNotFoundException; use OCA\Spreed\Exceptions\RoomNotFoundException; use OCA\Spreed\Manager; use OCA\Spreed\Participant; use OCA\Spreed\Room; +use OCA\Spreed\Signaling\Messages; use OCP\Activity\IManager as IActivityManager; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; @@ -57,6 +59,8 @@ class RoomController extends OCSController { private $logger; /** @var Manager */ private $manager; + /** @var Messages */ + private $messages; /** @var INotificationManager */ private $notificationManager; /** @var IActivityManager */ @@ -73,6 +77,7 @@ class RoomController extends OCSController { * @param IGroupManager $groupManager * @param ILogger $logger * @param Manager $manager + * @param Messages $messages * @param INotificationManager $notificationManager * @param IActivityManager $activityManager * @param IL10N $l10n @@ -85,6 +90,7 @@ public function __construct($appName, IGroupManager $groupManager, ILogger $logger, Manager $manager, + Messages $messages, INotificationManager $notificationManager, IActivityManager $activityManager, IL10N $l10n) { @@ -95,6 +101,7 @@ public function __construct($appName, $this->groupManager = $groupManager; $this->logger = $logger; $this->manager = $manager; + $this->messages = $messages; $this->notificationManager = $notificationManager; $this->activityManager = $activityManager; $this->l10n = $l10n; @@ -158,8 +165,10 @@ protected function formatRoom(Room $room, Participant $participant = null) { if ($participant instanceof Participant) { $participantType = $participant->getParticipantType(); + $participantInCall = $participant->isInCall(); } else { $participantType = Participant::GUEST; + $participantInCall = false; } $roomData = [ @@ -169,6 +178,7 @@ protected function formatRoom(Room $room, Participant $participant = null) { 'name' => $room->getName(), 'displayName' => $room->getName(), 'participantType' => $participantType, + 'participantInCall' => $participantInCall, 'count' => $room->getNumberOfParticipants(time() - 30), 'hasPassword' => $room->hasPassword(), ]; @@ -766,6 +776,73 @@ public function setPassword($token, $password) { return new DataResponse(); } + /** + * @PublicPage + * @UseSession + * + * @param string $token + * @param string $password + * @return DataResponse + */ + public function joinRoom($token, $password) { + try { + $room = $this->manager->getRoomForParticipantByToken($token, $this->userId); + } catch (RoomNotFoundException $e) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + + try { + if ($this->userId !== null) { + $sessionIds = $this->manager->getSessionIdsForUser($this->userId); + $newSessionId = $room->enterRoomAsUser($this->userId, $password, $this->session->get('spreed-password') === $room->getToken()); + + if (!empty($sessionIds)) { + $this->messages->deleteMessages($sessionIds); + } + } else { + $newSessionId = $room->enterRoomAsGuest($password, $this->session->get('spreed-password') === $room->getToken()); + } + } catch (InvalidPasswordException $e) { + return new DataResponse([], Http::STATUS_FORBIDDEN); + } + + $this->session->remove('spreed-password'); + $this->session->set('spreed-session', $newSessionId); + $room->ping($this->userId, $newSessionId, time()); + + return new DataResponse([ + 'sessionId' => $newSessionId, + ]); + } + + /** + * @PublicPage + * @UseSession + * + * @param string $token + * @return DataResponse + */ + public function exitRoom($token) { + $sessionId = $this->session->get('spreed-session'); + $this->session->remove('spreed-session'); + + try { + $room = $this->manager->getRoomForParticipantByToken($token, $this->userId); + + if ($this->userId === null) { + $participant = $room->getParticipantBySession($sessionId); + $room->removeParticipantBySession($participant); + } else { + $participant = $room->getParticipant($this->userId); + $room->disconnectUserFromAllRooms($participant->getUser()); + } + } catch (RoomNotFoundException $e) { + } catch (ParticipantNotFoundException $e) { + } + + return new DataResponse(); + } + /** * @NoAdminRequired * diff --git a/lib/Controller/SignalingController.php b/lib/Controller/SignalingController.php index 0065495a8df..0752aebd2a8 100644 --- a/lib/Controller/SignalingController.php +++ b/lib/Controller/SignalingController.php @@ -191,7 +191,7 @@ protected function getUsersInRoom(Room $room) { foreach ($participants['users'] as $participant => $data) { if ($data['sessionId'] === '0') { - // Use left the room + // User is not active continue; } @@ -200,6 +200,7 @@ protected function getUsersInRoom(Room $room) { 'roomId' => $room->getId(), 'lastPing' => $data['lastPing'], 'sessionId' => $data['sessionId'], + 'inCall' => $data['inCall'], ]; } @@ -209,6 +210,7 @@ protected function getUsersInRoom(Room $room) { 'roomId' => $room->getId(), 'lastPing' => $data['lastPing'], 'sessionId' => $data['sessionId'], + 'inCall' => $data['inCall'], ]; } diff --git a/lib/Manager.php b/lib/Manager.php index 042911d05f5..99cc14ce7f5 100644 --- a/lib/Manager.php +++ b/lib/Manager.php @@ -79,7 +79,7 @@ protected function createRoomObject(array $row) { * @return Participant */ protected function createParticipantObject(Room $room, array $row) { - return new Participant($this->db, $room, $row['userId'], (int) $row['participantType'], (int) $row['lastPing'], $row['sessionId']); + return new Participant($this->db, $room, $row['userId'], (int) $row['participantType'], (int) $row['lastPing'], $row['sessionId'], (bool) $row['inCall']); } /** diff --git a/lib/Migration/Version2001Date20171031102049.php b/lib/Migration/Version2001Date20171031102049.php new file mode 100644 index 00000000000..8cf456474f4 --- /dev/null +++ b/lib/Migration/Version2001Date20171031102049.php @@ -0,0 +1,51 @@ +getTable('talk_participants'); + $table->addColumn('inCall', Type::BOOLEAN, [ + 'default' => 0, + ]); + + return $schema; + } + + /** + * @param IOutput $output + * @param \Closure $schemaClosure The `\Closure` returns a `Schema` + * @param array $options + * @since 13.0.0 + */ + public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) { + } +} diff --git a/lib/Participant.php b/lib/Participant.php index 5b994d1486e..27a2f32a64f 100644 --- a/lib/Participant.php +++ b/lib/Participant.php @@ -44,6 +44,8 @@ class Participant { protected $lastPing; /** @var string */ protected $sessionId; + /** @var bool */ + protected $inCall; /** * @param IDBConnection $db @@ -52,14 +54,16 @@ class Participant { * @param int $participantType * @param int $lastPing * @param string $sessionId + * @param bool $inCall */ - public function __construct(IDBConnection $db, Room $room, $user, $participantType, $lastPing, $sessionId) { + public function __construct(IDBConnection $db, Room $room, $user, $participantType, $lastPing, $sessionId, $inCall) { $this->db = $db; $this->room = $room; $this->user = $user; $this->participantType = $participantType; $this->lastPing = $lastPing; $this->sessionId = $sessionId; + $this->inCall = $inCall; } public function getUser() { @@ -77,4 +81,8 @@ public function getLastPing() { public function getSessionId() { return $this->sessionId; } + + public function isInCall() { + return $this->inCall; + } } diff --git a/lib/Room.php b/lib/Room.php index e4f7e257c8f..c308c946f96 100644 --- a/lib/Room.php +++ b/lib/Room.php @@ -547,6 +547,7 @@ public function disconnectUserFromAllRooms($userId) { $query = $this->db->getQueryBuilder(); $query->update('talk_participants') ->set('sessionId', $query->createNamedParameter('0')) + ->set('inCall', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)) ->where($query->expr()->eq('userId', $query->createNamedParameter($userId))) ->andWhere($query->expr()->neq('participantType', $query->createNamedParameter(Participant::USER_SELF_JOINED, IQueryBuilder::PARAM_INT))); $query->execute(); @@ -590,6 +591,39 @@ public function enterRoomAsGuest($password, $passedPasswordProtection = false) { return $sessionId; } + /** + * @param string $sessionId + * @param bool $active + */ + public function changeInCall($sessionId, $active) { + if ($active) { + $this->dispatcher->dispatch(self::class . '::preSessionJoinCall', new GenericEvent($this, [ + 'sessionId' => $sessionId, + ])); + } else { + $this->dispatcher->dispatch(self::class . '::preSessionLeaveCall', new GenericEvent($this, [ + 'sessionId' => $sessionId, + ])); + } + + $query = $this->db->getQueryBuilder(); + $query->update('talk_participants') + ->set('inCall', $query->createNamedParameter((int) $active, IQueryBuilder::PARAM_INT)) + ->where($query->expr()->eq('sessionId', $query->createNamedParameter($sessionId))) + ->andWhere($query->expr()->eq('roomId', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT))); + $query->execute(); + + if ($active) { + $this->dispatcher->dispatch(self::class . '::postSessionJoinCall', new GenericEvent($this, [ + 'sessionId' => $sessionId, + ])); + } else { + $this->dispatcher->dispatch(self::class . '::postSessionLeaveCall', new GenericEvent($this, [ + 'sessionId' => $sessionId, + ])); + } + } + /** * @param string $password * @return bool @@ -647,12 +681,14 @@ public function getParticipants($lastPing = 0) { while ($row = $result->fetch()) { if ($row['userId'] !== '' && $row['userId'] !== null) { $users[$row['userId']] = [ + 'inCall' => (bool) $row['inCall'], 'lastPing' => (int) $row['lastPing'], 'sessionId' => $row['sessionId'], 'participantType' => (int) $row['participantType'], ]; } else { $guests[] = [ + 'inCall' => (bool) $row['inCall'], 'lastPing' => (int) $row['lastPing'], 'sessionId' => $row['sessionId'], ];