Преглед на файлове

feat(remotecontrol): Prevent multiple remote control sessions (#1875)

master
hristoterezov преди 7 години
родител
ревизия
378a8d014e

+ 21
- 1
conference.js Целия файл

@@ -11,6 +11,8 @@ import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
11 11
 
12 12
 import { reload, reportError } from './modules/util/helpers';
13 13
 
14
+import * as RemoteControlEvents
15
+    from './service/remotecontrol/RemoteControlEvents';
14 16
 import UIEvents from './service/UI/UIEvents';
15 17
 import UIUtil from './modules/UI/util/UIUtil';
16 18
 import * as JitsiMeetConferenceEvents from './ConferenceEvents';
@@ -1813,10 +1815,27 @@ export default {
1813 1815
             ConferenceEvents.LOCK_STATE_CHANGED,
1814 1816
             (...args) => APP.store.dispatch(lockStateChanged(room, ...args)));
1815 1817
 
1818
+        APP.remoteControl.on(RemoteControlEvents.ACTIVE_CHANGED, isActive => {
1819
+            room.setLocalParticipantProperty(
1820
+                "remoteControlSessionStatus",
1821
+                isActive
1822
+            );
1823
+            APP.UI.setLocalRemoteControlActiveChanged();
1824
+        });
1825
+
1816 1826
         room.on(ConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
1817 1827
                 (participant, name, oldValue, newValue) => {
1818
-            if (name === "raisedHand") {
1828
+            switch (name) {
1829
+            case 'raisedHand':
1819 1830
                 APP.UI.setRaisedHandStatus(participant, newValue);
1831
+                break;
1832
+            case 'remoteControlSessionStatus':
1833
+                APP.UI.setRemoteControlActiveStatus(
1834
+                    participant.getId(),
1835
+                    newValue);
1836
+                break;
1837
+            default:
1838
+            // ignore
1820 1839
             }
1821 1840
         });
1822 1841
 
@@ -2361,6 +2380,7 @@ export default {
2361 2380
             APP.UI.setLocalRaisedHandStatus(raisedHand);
2362 2381
         }
2363 2382
     },
2383
+
2364 2384
     /**
2365 2385
      * Log event to callstats and analytics.
2366 2386
      * @param {string} name the event name

+ 20
- 0
modules/UI/UI.js Целия файл

@@ -1230,6 +1230,26 @@ UI.getRemoteVideosCount = () => VideoLayout.getRemoteVideosCount();
1230 1230
 UI.setRemoteThumbnailsVisibility
1231 1231
     = shouldHide => Filmstrip.setRemoteVideoVisibility(shouldHide);
1232 1232
 
1233
+/**
1234
+ * Sets the remote control active status for a remote participant.
1235
+ *
1236
+ * @param {string} participantID - The id of the remote participant.
1237
+ * @param {boolean} isActive - The new remote control active status.
1238
+ * @returns {void}
1239
+ */
1240
+UI.setRemoteControlActiveStatus = function(participantID, isActive) {
1241
+    VideoLayout.setRemoteControlActiveStatus(participantID, isActive);
1242
+};
1243
+
1244
+/**
1245
+ * Sets the remote control active status for the local participant.
1246
+ *
1247
+ * @returns {void}
1248
+ */
1249
+UI.setLocalRemoteControlActiveChanged = function() {
1250
+    VideoLayout.setLocalRemoteControlActiveChanged();
1251
+};
1252
+
1233 1253
 const UIListeners = new Map([
1234 1254
     [
1235 1255
         UIEvents.ETHERPAD_CLICKED,

+ 30
- 25
modules/UI/videolayout/RemoteVideo.js Целия файл

@@ -51,6 +51,7 @@ function RemoteVideo(user, VideoLayout, emitter) {
51 51
     this.flipX = false;
52 52
     this.isLocal = false;
53 53
     this.popupMenuIsHovered = false;
54
+    this._isRemoteControlSessionActive = false;
54 55
     /**
55 56
      * The flag is set to <tt>true</tt> after the 'onplay' event has been
56 57
      * triggered on the current video element. It goes back to <tt>false</tt>
@@ -87,9 +88,7 @@ RemoteVideo.prototype.addRemoteVideoContainer = function() {
87 88
 
88 89
     this.initBrowserSpecificProperties();
89 90
 
90
-    if (APP.conference.isModerator || this._supportsRemoteControl) {
91
-        this.addRemoteVideoMenu();
92
-    }
91
+    this.addRemoteVideoMenu();
93 92
 
94 93
     this.VideoLayout.resizeThumbnails(false, true);
95 94
 
@@ -135,7 +134,9 @@ RemoteVideo.prototype._generatePopupContent = function () {
135 134
     let remoteControlState = null;
136 135
     let onRemoteControlToggle;
137 136
 
138
-    if (this._supportsRemoteControl) {
137
+    if (this._supportsRemoteControl
138
+        && ((!APP.remoteControl.active && !this._isRemoteControlSessionActive)
139
+            || APP.remoteControl.controller.activeParticipant === this.id)) {
139 140
         if (controller.getRequestedParticipant() === this.id) {
140 141
             onRemoteControlToggle = () => {};
141 142
             remoteControlState = REMOTE_CONTROL_MENU_STATES.REQUESTING;
@@ -179,7 +180,18 @@ RemoteVideo.prototype._generatePopupContent = function () {
179 180
 };
180 181
 
181 182
 RemoteVideo.prototype._onRemoteVideoMenuDisplay = function () {
182
-    this.updateRemoteVideoMenu(this.isAudioMuted, true);
183
+    this.updateRemoteVideoMenu();
184
+};
185
+
186
+/**
187
+ * Sets the remote control active status for the remote video.
188
+ *
189
+ * @param {boolean} isActive - The new remote control active status.
190
+ * @returns {void}
191
+ */
192
+RemoteVideo.prototype.setRemoteControlActiveStatus = function(isActive) {
193
+    this._isRemoteControlSessionActive = isActive;
194
+    this.updateRemoteVideoMenu();
183 195
 };
184 196
 
185 197
 /**
@@ -192,18 +204,7 @@ RemoteVideo.prototype.setRemoteControlSupport = function(isSupported = false) {
192 204
         return;
193 205
     }
194 206
     this._supportsRemoteControl = isSupported;
195
-    if(!isSupported) {
196
-        return;
197
-    }
198
-
199
-    if(!this.hasRemoteVideoMenu) {
200
-        //create menu
201
-        this.addRemoteVideoMenu();
202
-    } else {
203
-        //update the content
204
-        this.updateRemoteVideoMenu(this.isAudioMuted, true);
205
-    }
206
-
207
+    this.updateRemoteVideoMenu();
207 208
 };
208 209
 
209 210
 /**
@@ -215,7 +216,7 @@ RemoteVideo.prototype._requestRemoteControlPermissions = function () {
215 216
         if(result === null) {
216 217
             return;
217 218
         }
218
-        this.updateRemoteVideoMenu(this.isAudioMuted, true);
219
+        this.updateRemoteVideoMenu();
219 220
         APP.UI.messageHandler.notify(
220 221
             "dialog.remoteControlTitle",
221 222
             (result === false) ? "dialog.remoteControlDeniedMessage"
@@ -232,7 +233,7 @@ RemoteVideo.prototype._requestRemoteControlPermissions = function () {
232 233
         }
233 234
     }, error => {
234 235
         logger.error(error);
235
-        this.updateRemoteVideoMenu(this.isAudioMuted, true);
236
+        this.updateRemoteVideoMenu();
236 237
         APP.UI.messageHandler.notify(
237 238
             "dialog.remoteControlTitle",
238 239
             "dialog.remoteControlErrorMessage",
@@ -240,7 +241,7 @@ RemoteVideo.prototype._requestRemoteControlPermissions = function () {
240 241
                 || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME}
241 242
         );
242 243
     });
243
-    this.updateRemoteVideoMenu(this.isAudioMuted, true);
244
+    this.updateRemoteVideoMenu();
244 245
 };
245 246
 
246 247
 /**
@@ -249,7 +250,7 @@ RemoteVideo.prototype._requestRemoteControlPermissions = function () {
249 250
 RemoteVideo.prototype._stopRemoteControl = function () {
250 251
     // send message about stopping
251 252
     APP.remoteControl.controller.stop();
252
-    this.updateRemoteVideoMenu(this.isAudioMuted, true);
253
+    this.updateRemoteVideoMenu();
253 254
 };
254 255
 
255 256
 /**
@@ -286,9 +287,10 @@ RemoteVideo.prototype._setAudioVolume = function (newVal) {
286 287
  * Updates the remote video menu.
287 288
  *
288 289
  * @param isMuted the new muted state to update to
289
- * @param force to work even if popover is not visible
290 290
  */
291
-RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted) {
291
+RemoteVideo.prototype.updateRemoteVideoMenu = function (
292
+    isMuted = this.isAudioMuted
293
+) {
292 294
     this.isAudioMuted = isMuted;
293 295
 
294 296
     this._generatePopupContent();
@@ -323,8 +325,6 @@ RemoteVideo.prototype._figureOutMutedWhileDisconnected = function() {
323 325
  * Adds the remote video menu element for the given <tt>id</tt> in the
324 326
  * given <tt>parentElement</tt>.
325 327
  *
326
- * @param id the id indicating the video for which we're adding a menu.
327
- * @param parentElement the parent element where this menu will be added
328 328
  */
329 329
 RemoteVideo.prototype.addRemoteVideoMenu = function () {
330 330
     if (interfaceConfig.filmStripOnly) {
@@ -576,6 +576,11 @@ RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
576 576
 
577 577
     if (!isVideo) {
578 578
         this._audioStreamElement = streamElement;
579
+
580
+        // If the remote video menu was created before the audio stream was
581
+        // attached we need to update the menu in order to show the volume
582
+        // slider.
583
+        this.updateRemoteVideoMenu();
579 584
     }
580 585
 };
581 586
 

+ 22
- 3
modules/UI/videolayout/VideoLayout.js Целия файл

@@ -642,9 +642,7 @@ var VideoLayout = {
642 642
                 return;
643 643
 
644 644
             remoteVideo.showAudioIndicator(isMuted);
645
-            if (APP.conference.isModerator) {
646
-                remoteVideo.updateRemoteVideoMenu(isMuted);
647
-            }
645
+            remoteVideo.updateRemoteVideoMenu(isMuted);
648 646
         }
649 647
     },
650 648
 
@@ -1165,6 +1163,27 @@ var VideoLayout = {
1165 1163
      */
1166 1164
     getRemoteVideosCount() {
1167 1165
         return Object.keys(remoteVideos).length;
1166
+    },
1167
+    /**
1168
+     * Sets the remote control active status for a remote participant.
1169
+     *
1170
+     * @param {string} participantID - The id of the remote participant.
1171
+     * @param {boolean} isActive - The new remote control active status.
1172
+     * @returns {void}
1173
+     */
1174
+    setRemoteControlActiveStatus(participantID, isActive) {
1175
+        remoteVideos[participantID].setRemoteControlActiveStatus(isActive);
1176
+    },
1177
+
1178
+    /**
1179
+     * Sets the remote control active status for the local participant.
1180
+     *
1181
+     * @returns {void}
1182
+     */
1183
+    setLocalRemoteControlActiveChanged() {
1184
+        Object.values(remoteVideos).forEach(
1185
+            remoteVideo => remoteVideo.updateRemoteVideoMenu()
1186
+        );
1168 1187
     }
1169 1188
 };
1170 1189
 

+ 18
- 1
modules/remotecontrol/Controller.js Целия файл

@@ -8,6 +8,8 @@ import {
8 8
     PERMISSIONS_ACTIONS,
9 9
     REMOTE_CONTROL_MESSAGE_NAME
10 10
 } from '../../service/remotecontrol/Constants';
11
+import * as RemoteControlEvents
12
+    from '../../service/remotecontrol/RemoteControlEvents';
11 13
 import UIEvents from '../../service/UI/UIEvents';
12 14
 
13 15
 import RemoteControlParticipant from './RemoteControlParticipant';
@@ -86,6 +88,15 @@ export default class Controller extends RemoteControlParticipant {
86 88
             = this._onLargeVideoIdChanged.bind(this);
87 89
     }
88 90
 
91
+    /**
92
+     * Returns the current active participant's id.
93
+     *
94
+     * @returns {string|null} - The id of the current active participant.
95
+     */
96
+    get activeParticipant(): string | null {
97
+        return this._requestedParticipant || this._controlledParticipant;
98
+    }
99
+
89 100
     /**
90 101
      * Requests permissions from the remote control receiver side.
91 102
      *
@@ -100,7 +111,7 @@ export default class Controller extends RemoteControlParticipant {
100 111
         if (!this._enabled) {
101 112
             return Promise.reject(new Error('Remote control is disabled!'));
102 113
         }
103
-
114
+        this.emit(RemoteControlEvents.ACTIVE_CHANGED, true);
104 115
         this._area = eventCaptureArea;// $("#largeVideoWrapper")
105 116
         logger.log(`Requsting remote control permissions from: ${userId}`);
106 117
 
@@ -125,16 +136,21 @@ export default class Controller extends RemoteControlParticipant {
125 136
                     result = this._handleReply(participant, event);
126 137
                 } catch (e) {
127 138
                     clearRequest();
139
+                    this.emit(RemoteControlEvents.ACTIVE_CHANGED, false);
128 140
                     reject(e);
129 141
                 }
130 142
                 if (result !== null) {
131 143
                     clearRequest();
144
+                    if (result === false) {
145
+                        this.emit(RemoteControlEvents.ACTIVE_CHANGED, false);
146
+                    }
132 147
                     resolve(result);
133 148
                 }
134 149
             };
135 150
             onUserLeft = id => {
136 151
                 if (id === this._requestedParticipant) {
137 152
                     clearRequest();
153
+                    this.emit(RemoteControlEvents.ACTIVE_CHANGED, false);
138 154
                     resolve(null);
139 155
                 }
140 156
             };
@@ -316,6 +332,7 @@ export default class Controller extends RemoteControlParticipant {
316 332
         this.pause();
317 333
         this._controlledParticipant = null;
318 334
         this._area = undefined;
335
+        this.emit(RemoteControlEvents.ACTIVE_CHANGED, false);
319 336
         APP.UI.messageHandler.notify(
320 337
             'dialog.remoteControlTitle',
321 338
             'dialog.remoteControlStopMessage'

+ 5
- 0
modules/remotecontrol/Receiver.js Целия файл

@@ -13,6 +13,8 @@ import {
13 13
     REMOTE_CONTROL_MESSAGE_NAME,
14 14
     REQUESTS
15 15
 } from '../../service/remotecontrol/Constants';
16
+import * as RemoteControlEvents
17
+    from '../../service/remotecontrol/RemoteControlEvents';
16 18
 import {
17 19
     Transport,
18 20
     PostMessageTransportBackend
@@ -132,6 +134,7 @@ export default class Receiver extends RemoteControlParticipant {
132 134
             name: REMOTE_CONTROL_MESSAGE_NAME,
133 135
             type: EVENTS.stop
134 136
         });
137
+        this.emit(RemoteControlEvents.ACTIVE_CHANGED, false);
135 138
         if (!dontNotify) {
136 139
             APP.UI.messageHandler.notify(
137 140
                 'dialog.remoteControlTitle',
@@ -177,6 +180,7 @@ export default class Receiver extends RemoteControlParticipant {
177 180
                     && message.action === PERMISSIONS_ACTIONS.request) {
178 181
                 const userId = participant.getId();
179 182
 
183
+                this.emit(RemoteControlEvents.ACTIVE_CHANGED, true);
180 184
                 APP.store.dispatch(
181 185
                     openRemoteControlAuthorizationDialog(userId));
182 186
             } else if (this._controller === participant.getId()) {
@@ -200,6 +204,7 @@ export default class Receiver extends RemoteControlParticipant {
200 204
      * @returns {void}
201 205
      */
202 206
     deny(userId: string) {
207
+        this.emit(RemoteControlEvents.ACTIVE_CHANGED, false);
203 208
         this.sendRemoteControlEndpointMessage(userId, {
204 209
             type: EVENTS.permissions,
205 210
             action: PERMISSIONS_ACTIONS.deny

+ 37
- 1
modules/remotecontrol/RemoteControl.js Целия файл

@@ -1,9 +1,12 @@
1 1
 /* @flow */
2 2
 
3
+import EventEmitter from 'events';
3 4
 import { getLogger } from 'jitsi-meet-logger';
4 5
 
5 6
 import { DISCO_REMOTE_CONTROL_FEATURE }
6 7
     from '../../service/remotecontrol/Constants';
8
+import * as RemoteControlEvents
9
+    from '../../service/remotecontrol/RemoteControlEvents';
7 10
 
8 11
 import Controller from './Controller';
9 12
 import Receiver from './Receiver';
@@ -16,7 +19,8 @@ declare var config: Object;
16 19
 /**
17 20
  * Implements the remote control functionality.
18 21
  */
19
-class RemoteControl {
22
+class RemoteControl extends EventEmitter {
23
+    _active: boolean;
20 24
     _initialized: boolean;
21 25
     controller: Controller;
22 26
     receiver: Receiver;
@@ -25,8 +29,36 @@ class RemoteControl {
25 29
      * Constructs new instance. Creates controller and receiver properties.
26 30
      */
27 31
     constructor() {
32
+        super();
28 33
         this.controller = new Controller();
34
+        this._active = false;
29 35
         this._initialized = false;
36
+
37
+        this.controller.on(RemoteControlEvents.ACTIVE_CHANGED, active => {
38
+            this.active = active;
39
+        });
40
+    }
41
+
42
+    /**
43
+     * Sets the remote control session active status.
44
+     *
45
+     * @param {boolean} isActive - True - if the controller or the receiver is
46
+     * currently in remote control session and false otherwise.
47
+     * @returns {void}
48
+     */
49
+    set active(isActive: boolean) {
50
+        this._active = isActive;
51
+        this.emit(RemoteControlEvents.ACTIVE_CHANGED, isActive);
52
+    }
53
+
54
+    /**
55
+     * Returns the remote control session active status.
56
+     *
57
+     * @returns {boolean} - True - if the controller or the receiver is
58
+     * currently in remote control session and false otherwise.
59
+     */
60
+    get active(): boolean {
61
+        return this._active;
30 62
     }
31 63
 
32 64
     /**
@@ -45,6 +77,10 @@ class RemoteControl {
45 77
         this._initialized = true;
46 78
         this.controller.enable(true);
47 79
         this.receiver = new Receiver();
80
+
81
+        this.receiver.on(RemoteControlEvents.ACTIVE_CHANGED, active => {
82
+            this.active = active;
83
+        });
48 84
     }
49 85
 
50 86
     /**

+ 3
- 1
modules/remotecontrol/RemoteControlParticipant.js Целия файл

@@ -1,5 +1,6 @@
1 1
 /* @flow */
2 2
 
3
+import EventEmitter from 'events';
3 4
 import { getLogger } from 'jitsi-meet-logger';
4 5
 
5 6
 import {
@@ -13,13 +14,14 @@ declare var APP: Object;
13 14
 /**
14 15
  * Implements common logic for Receiver class and Controller class.
15 16
  */
16
-export default class RemoteControlParticipant {
17
+export default class RemoteControlParticipant extends EventEmitter {
17 18
     _enabled: boolean;
18 19
 
19 20
     /**
20 21
      * Creates new instance.
21 22
      */
22 23
     constructor() {
24
+        super();
23 25
         this._enabled = false;
24 26
     }
25 27
 

+ 53
- 27
react/features/remote-video-menu/components/RemoteVideoMenuTriggerButton.js Целия файл

@@ -99,9 +99,15 @@ class RemoteVideoMenuTriggerButton extends Component {
99 99
      * @returns {ReactElement}
100 100
      */
101 101
     render() {
102
+        const content = this._renderRemoteVideoMenu();
103
+
104
+        if (!content) {
105
+            return null;
106
+        }
107
+
102 108
         return (
103 109
             <AKInlineDialog
104
-                content = { this._renderRemoteVideoMenu() }
110
+                content = { content }
105 111
                 isOpen = { this.state.showRemoteMenu }
106 112
                 onClose = { this._onRemoteMenuClose }
107 113
                 position = { interfaceConfig.VERTICAL_FILMSTRIP
@@ -162,32 +168,52 @@ class RemoteVideoMenuTriggerButton extends Component {
162 168
             participantID
163 169
         } = this.props;
164 170
 
165
-        return (
166
-            <RemoteVideoMenu id = { participantID }>
167
-                { isModerator
168
-                    ? <MuteButton
169
-                        isAudioMuted = { isAudioMuted }
170
-                        onClick = { this._onRemoteMenuClose }
171
-                        participantID = { participantID } />
172
-                    : null }
173
-                { isModerator
174
-                    ? <KickButton
175
-                        onClick = { this._onRemoteMenuClose }
176
-                        participantID = { participantID } />
177
-                    : null }
178
-                { remoteControlState
179
-                    ? <RemoteControlButton
180
-                        onClick = { onRemoteControlToggle }
181
-                        participantID = { participantID }
182
-                        remoteControlState = { remoteControlState } />
183
-                    : null }
184
-                { onVolumeChange
185
-                    ? <VolumeSlider
186
-                        initialValue = { initialVolumeValue }
187
-                        onChange = { onVolumeChange } />
188
-                    : null }
189
-            </RemoteVideoMenu>
190
-        );
171
+        const buttons = [];
172
+
173
+        if (isModerator) {
174
+            buttons.push(
175
+                <MuteButton
176
+                    isAudioMuted = { isAudioMuted }
177
+                    key = 'mute'
178
+                    onClick = { this._onRemoteMenuClose }
179
+                    participantID = { participantID } />
180
+            );
181
+            buttons.push(
182
+                <KickButton
183
+                    key = 'kick'
184
+                    onClick = { this._onRemoteMenuClose }
185
+                    participantID = { participantID } />
186
+            );
187
+        }
188
+
189
+        if (remoteControlState) {
190
+            buttons.push(
191
+                <RemoteControlButton
192
+                    key = 'remote-control'
193
+                    onClick = { onRemoteControlToggle }
194
+                    participantID = { participantID }
195
+                    remoteControlState = { remoteControlState } />
196
+            );
197
+        }
198
+
199
+        if (onVolumeChange && isModerator) {
200
+            buttons.push(
201
+                <VolumeSlider
202
+                    initialValue = { initialVolumeValue }
203
+                    key = 'volume-slider'
204
+                    onChange = { onVolumeChange } />
205
+            );
206
+        }
207
+
208
+        if (buttons.length > 0) {
209
+            return (
210
+                <RemoteVideoMenu id = { participantID }>
211
+                    { buttons }
212
+                </RemoteVideoMenu>
213
+            );
214
+        }
215
+
216
+        return null;
191 217
     }
192 218
 }
193 219
 

+ 8
- 0
service/remotecontrol/RemoteControlEvents.js Целия файл

@@ -0,0 +1,8 @@
1
+/**
2
+ * Events fired from the remote control module through the EventEmitter.
3
+ */
4
+
5
+/**
6
+ * Notifies about remote control active session status changes.
7
+ */
8
+export const ACTIVE_CHANGED = 'active-changed';

Loading…
Отказ
Запис