Переглянути джерело

feat(remotecontrol): UI for requesting permissions

j8
hristoterezov 8 роки тому
джерело
коміт
a4d5c41b3a

+ 9
- 4
conference.js Переглянути файл

@@ -488,11 +488,11 @@ export default {
488 488
             }).then(([tracks, con]) => {
489 489
                 logger.log('initialized with %s local tracks', tracks.length);
490 490
                 APP.connection = connection = con;
491
+                this.isDesktopSharingEnabled =
492
+                    JitsiMeetJS.isDesktopSharingEnabled();
491 493
                 APP.remoteControl.init();
492 494
                 this._bindConnectionFailedHandler(con);
493 495
                 this._createRoom(tracks);
494
-                this.isDesktopSharingEnabled =
495
-                    JitsiMeetJS.isDesktopSharingEnabled();
496 496
 
497 497
                 if (UIUtil.isButtonEnabled('contacts')
498 498
                     && !interfaceConfig.filmStripOnly) {
@@ -985,7 +985,7 @@ export default {
985 985
         let externalInstallation = false;
986 986
 
987 987
         if (shareScreen) {
988
-            createLocalTracks({
988
+            this.screenSharingPromise = createLocalTracks({
989 989
                 devices: ['desktop'],
990 990
                 desktopSharingExtensionExternalInstallation: {
991 991
                     interval: 500,
@@ -1075,7 +1075,9 @@ export default {
1075 1075
             });
1076 1076
         } else {
1077 1077
             APP.remoteControl.receiver.stop();
1078
-            createLocalTracks({ devices: ['video'] }).then(
1078
+            this.screenSharingPromise = createLocalTracks(
1079
+                { devices: ['video'] })
1080
+            .then(
1079 1081
                 ([stream]) => this.useVideoStream(stream)
1080 1082
             ).then(() => {
1081 1083
                 this.videoSwitchInProgress = false;
@@ -1107,6 +1109,8 @@ export default {
1107 1109
             }
1108 1110
         );
1109 1111
 
1112
+        room.on(ConferenceEvents.PARTCIPANT_FEATURES_CHANGED,
1113
+            user => APP.UI.onUserFeaturesChanged(user));
1110 1114
         room.on(ConferenceEvents.USER_JOINED, (id, user) => {
1111 1115
             if (user.isHidden())
1112 1116
                 return;
@@ -1780,6 +1784,7 @@ export default {
1780 1784
      */
1781 1785
     hangup (requestFeedback = false) {
1782 1786
         APP.UI.hideRingOverLay();
1787
+        APP.remoteControl.receiver.enable(false);
1783 1788
         let requestFeedbackPromise = requestFeedback
1784 1789
                 ? APP.UI.requestFeedbackOnHangup()
1785 1790
                 // false - because the thank you dialog shouldn't be displayed

+ 3
- 3
css/_filmstrip.scss Переглянути файл

@@ -92,7 +92,7 @@
92 92
                 0 0 3px $videoThumbnailSelected !important;
93 93
             }
94 94
 
95
-            .remotevideomenu {
95
+            .remotevideomenu > .icon-menu {
96 96
                 display: none;
97 97
             }
98 98
 
@@ -105,7 +105,7 @@
105 105
                 box-shadow: inset 0 0 3px $videoThumbnailHovered,
106 106
                 0 0 3px $videoThumbnailHovered;
107 107
 
108
-                .remotevideomenu {
108
+                .remotevideomenu > .icon-menu {
109 109
                     display: inline-block;
110 110
                 }
111 111
             }
@@ -121,4 +121,4 @@
121 121
             }
122 122
         }
123 123
     }
124
-}
124
+}

+ 6
- 2
css/_popup_menu.scss Переглянути файл

@@ -6,7 +6,6 @@
6 6
     padding: 0;
7 7
     margin: 2px 0;
8 8
     bottom: 0;
9
-    width: 100px;
10 9
     height: auto;
11 10
 
12 11
     &:first-child {
@@ -66,4 +65,9 @@
66 65
 
67 66
 span.remotevideomenu:hover ul.popupmenu, ul.popupmenu:hover {
68 67
     display:block !important;
69
-}
68
+}
69
+
70
+.remote-control-spinner {
71
+    top: 6px;
72
+    left: 2px;
73
+}

+ 8
- 3
lang/main.json Переглянути файл

@@ -156,8 +156,8 @@
156 156
         "kick": "Kick out",
157 157
         "muted": "Muted",
158 158
         "domute": "Mute",
159
-        "flip": "Flip"
160
-
159
+        "flip": "Flip",
160
+        "remoteControl": "Remote control"
161 161
     },
162 162
     "connectionindicator":
163 163
     {
@@ -316,7 +316,12 @@
316 316
         "externalInstallationMsg": "You need to install our desktop sharing extension.",
317 317
         "muteParticipantTitle": "Mute this participant?",
318 318
         "muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.",
319
-        "muteParticipantButton": "Mute"
319
+        "muteParticipantButton": "Mute",
320
+        "remoteControlTitle": "Remote Control",
321
+        "remoteControlDeniedMessage": "__user__ rejected your remote control request!",
322
+        "remoteControlAllowedMessage": "__user__ accepted your remote control request!",
323
+        "remoteControlErrorMessage": "An error occurred while trying to request remote control permissions from __user__!",
324
+        "remoteControlStopMessage": "The remote control session ended!"
320 325
     },
321 326
     "email":
322 327
     {

+ 7
- 0
modules/UI/UI.js Переглянути файл

@@ -1441,4 +1441,11 @@ UI.hideUserMediaPermissionsGuidanceOverlay = function () {
1441 1441
     GumPermissionsOverlay.hide();
1442 1442
 };
1443 1443
 
1444
+/**
1445
+ * Handles user's features changes.
1446
+ */
1447
+UI.onUserFeaturesChanged = function (user) {
1448
+    VideoLayout.onUserFeaturesChanged(user);
1449
+};
1450
+
1444 1451
 module.exports = UI;

+ 122
- 38
modules/UI/videolayout/RemoteVideo.js Переглянути файл

@@ -29,6 +29,7 @@ function RemoteVideo(user, VideoLayout, emitter) {
29 29
     this.videoSpanId = `participant_${this.id}`;
30 30
     SmallVideo.call(this, VideoLayout);
31 31
     this.hasRemoteVideoMenu = false;
32
+    this._supportsRemoteControl = false;
32 33
     this.addRemoteVideoContainer();
33 34
     this.connectionIndicator = new ConnectionIndicator(this, this.id);
34 35
     this.setDisplayName();
@@ -64,7 +65,7 @@ RemoteVideo.prototype.addRemoteVideoContainer = function() {
64 65
 
65 66
     this.initBrowserSpecificProperties();
66 67
 
67
-    if (APP.conference.isModerator) {
68
+    if (APP.conference.isModerator || this._supportsRemoteControl) {
68 69
         this.addRemoteVideoMenu();
69 70
     }
70 71
 
@@ -106,14 +107,6 @@ RemoteVideo.prototype._initPopupMenu = function (popupMenuElement) {
106 107
         // call the original show, passing its actual this
107 108
         origShowFunc.call(this.popover);
108 109
     }.bind(this);
109
-
110
-    // override popover hide method so we can cleanup click handlers
111
-    let origHideFunc = this.popover.forceHide;
112
-    this.popover.forceHide = function () {
113
-        $(document).off("click", '#mutelink_' + this.id);
114
-        $(document).off("click", '#ejectlink_' + this.id);
115
-        origHideFunc.call(this.popover);
116
-    }.bind(this);
117 110
 };
118 111
 
119 112
 /**
@@ -139,38 +132,68 @@ RemoteVideo.prototype._generatePopupContent = function () {
139 132
     let popupmenuElement = document.createElement('ul');
140 133
     popupmenuElement.className = 'popupmenu';
141 134
     popupmenuElement.id = `remote_popupmenu_${this.id}`;
135
+    let menuItems = [];
136
+
137
+    if(APP.conference.isModerator) {
138
+        let muteTranslationKey;
139
+        let muteClassName;
140
+        if (this.isAudioMuted) {
141
+            muteTranslationKey = 'videothumbnail.muted';
142
+            muteClassName = 'mutelink disabled';
143
+        } else {
144
+            muteTranslationKey = 'videothumbnail.domute';
145
+            muteClassName = 'mutelink';
146
+        }
142 147
 
143
-    let muteTranslationKey;
144
-    let muteClassName;
145
-    if (this.isAudioMuted) {
146
-        muteTranslationKey = 'videothumbnail.muted';
147
-        muteClassName = 'mutelink disabled';
148
-    } else {
149
-        muteTranslationKey = 'videothumbnail.domute';
150
-        muteClassName = 'mutelink';
148
+        let muteHandler = this._muteHandler.bind(this);
149
+        let kickHandler = this._kickHandler.bind(this);
150
+
151
+        menuItems = [
152
+            {
153
+                id: 'mutelink_' + this.id,
154
+                handler: muteHandler,
155
+                icon: 'icon-mic-disabled',
156
+                className: muteClassName,
157
+                data: {
158
+                    i18n: muteTranslationKey
159
+                }
160
+            }, {
161
+                id: 'ejectlink_' + this.id,
162
+                handler: kickHandler,
163
+                icon: 'icon-kick',
164
+                data: {
165
+                    i18n: 'videothumbnail.kick'
166
+                }
167
+            }
168
+        ];
151 169
     }
152 170
 
153
-    let muteHandler = this._muteHandler.bind(this);
154
-    let kickHandler = this._kickHandler.bind(this);
155
-
156
-    let menuItems = [
157
-        {
158
-            id: 'mutelink_' + this.id,
159
-            handler: muteHandler,
160
-            icon: 'icon-mic-disabled',
161
-            className: muteClassName,
162
-            data: {
163
-                i18n: muteTranslationKey
164
-            }
165
-        }, {
166
-            id: 'ejectlink_' + this.id,
167
-            handler: kickHandler,
168
-            icon: 'icon-kick',
171
+    if(this._supportsRemoteControl) {
172
+        let icon, handler, className;
173
+        if(APP.remoteControl.controller.getRequestedParticipant()
174
+            === this.id) {
175
+            handler = () => {};
176
+            className = "requestRemoteControlLink disabled";
177
+            icon = "remote-control-spinner fa fa-spinner fa-spin";
178
+        } else if(!APP.remoteControl.controller.isStarted()) {
179
+            handler = this._requestRemoteControlPermissions.bind(this);
180
+            icon = "fa fa-play";
181
+            className = "requestRemoteControlLink";
182
+        } else {
183
+            handler = this._stopRemoteControl.bind(this);
184
+            icon = "fa fa-stop";
185
+            className = "requestRemoteControlLink";
186
+        }
187
+        menuItems.push({
188
+            id: 'remoteControl_' + this.id,
189
+            handler,
190
+            icon,
191
+            className,
169 192
             data: {
170
-                i18n: 'videothumbnail.kick'
193
+                i18n: 'videothumbnail.remoteControl'
171 194
             }
172
-        }
173
-    ];
195
+        });
196
+    }
174 197
 
175 198
     menuItems.forEach(el => {
176 199
         let menuItem = this._generatePopupMenuItem(el);
@@ -182,6 +205,68 @@ RemoteVideo.prototype._generatePopupContent = function () {
182 205
     return popupmenuElement;
183 206
 };
184 207
 
208
+/**
209
+ * Sets the remote control supported value and initializes or updates the menu
210
+ * depending on the remote control is supported or not.
211
+ * @param {boolean} isSupported
212
+ */
213
+RemoteVideo.prototype.setRemoteControlSupport = function(isSupported = false) {
214
+    if(this._supportsRemoteControl === isSupported) {
215
+        return;
216
+    }
217
+    this._supportsRemoteControl = isSupported;
218
+    if(!isSupported) {
219
+        return;
220
+    }
221
+
222
+    if(!this.hasRemoteVideoMenu) {
223
+        //create menu
224
+        this.addRemoteVideoMenu();
225
+    } else {
226
+        //update the content
227
+        this.updateRemoteVideoMenu(this.isAudioMuted, true);
228
+    }
229
+
230
+};
231
+
232
+/**
233
+ * Requests permissions for remote control session.
234
+ */
235
+RemoteVideo.prototype._requestRemoteControlPermissions = function () {
236
+    APP.remoteControl.controller.requestPermissions(this.id).then(result => {
237
+        if(result === null) {
238
+            return;
239
+        }
240
+        this.updateRemoteVideoMenu(this.isAudioMuted, true);
241
+        APP.UI.messageHandler.openMessageDialog(
242
+            "dialog.remoteControlTitle",
243
+            (result === false) ? "dialog.remoteControlDeniedMessage"
244
+                : "dialog.remoteControlAllowedMessage",
245
+            {user: this.user.getDisplayName()
246
+                || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME}
247
+        );
248
+    }, error => {
249
+        logger.error(error);
250
+        this.updateRemoteVideoMenu(this.isAudioMuted, true);
251
+        APP.UI.messageHandler.openMessageDialog(
252
+            "dialog.remoteControlTitle",
253
+            "dialog.remoteControlErrorMessage",
254
+            {user: this.user.getDisplayName()
255
+                || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME}
256
+        );
257
+    });
258
+    this.updateRemoteVideoMenu(this.isAudioMuted, true);
259
+};
260
+
261
+/**
262
+ * Stops remote control session.
263
+ */
264
+RemoteVideo.prototype._stopRemoteControl = function () {
265
+    // send message about stopping
266
+    APP.remoteControl.controller.stop();
267
+    this.updateRemoteVideoMenu(this.isAudioMuted, true);
268
+};
269
+
185 270
 RemoteVideo.prototype._muteHandler = function () {
186 271
     if (this.isAudioMuted)
187 272
         return;
@@ -244,8 +329,7 @@ RemoteVideo.prototype._generatePopupMenuItem = function (opts = {}) {
244 329
     linkItem.appendChild(textContent);
245 330
     linkItem.id = id;
246 331
 
247
-    // Delegate event to the document.
248
-    $(document).on("click", `#${id}`, handler);
332
+    linkItem.onclick = handler;
249 333
     menuItem.appendChild(linkItem);
250 334
 
251 335
     return menuItem;

+ 27
- 2
modules/UI/videolayout/VideoLayout.js Переглянути файл

@@ -406,6 +406,7 @@ var VideoLayout = {
406 406
             remoteVideo = smallVideo;
407 407
         else
408 408
             remoteVideo = new RemoteVideo(user, VideoLayout, eventEmitter);
409
+        this._setRemoteControlProperties(user, remoteVideo);
409 410
         this.addRemoteVideoContainer(id, remoteVideo);
410 411
     },
411 412
 
@@ -1158,12 +1159,36 @@ var VideoLayout = {
1158 1159
      * Sets the flipX state of the local video.
1159 1160
      * @param {boolean} true for flipped otherwise false;
1160 1161
      */
1161
-    setLocalFlipX: function (val) {
1162
+    setLocalFlipX (val) {
1162 1163
         this.localFlipX = val;
1164
+    },
1165
+
1166
+    getEventEmitter() {return eventEmitter;},
1167
+
1168
+    /**
1169
+     * Handles user's features changes.
1170
+     */
1171
+    onUserFeaturesChanged (user) {
1172
+        let video = this.getSmallVideo(user.getId());
1163 1173
 
1174
+        if (!video) {
1175
+            return;
1176
+        }
1177
+        this._setRemoteControlProperties(user, video);
1164 1178
     },
1165 1179
 
1166
-    getEventEmitter: () => {return eventEmitter;}
1180
+    /**
1181
+     * Sets the remote control properties (checks whether remote control
1182
+     * is supported and executes remoteVideo.setRemoteControlSupport).
1183
+     * @param {JitsiParticipant} user the user that will be checked for remote
1184
+     * control support.
1185
+     * @param {RemoteVideo} remoteVideo the remoteVideo on which the properties
1186
+     * will be set.
1187
+     */
1188
+    _setRemoteControlProperties (user, remoteVideo) {
1189
+        APP.remoteControl.checkUserRemoteControlSupport(user).then(result =>
1190
+            remoteVideo.setRemoteControlSupport(result));
1191
+    }
1167 1192
 };
1168 1193
 
1169 1194
 export default VideoLayout;

+ 91
- 21
modules/remotecontrol/Controller.js Переглянути файл

@@ -55,20 +55,34 @@ export default class Controller extends RemoteControlParticipant {
55 55
         super();
56 56
         this.controlledParticipant = null;
57 57
         this.requestedParticipant = null;
58
-        this.stopListener = this._handleRemoteControlStoppedEvent.bind(this);
58
+        this._stopListener = this._handleRemoteControlStoppedEvent.bind(this);
59
+        this._userLeftListener = this._onUserLeft.bind(this);
59 60
     }
60 61
 
61 62
     /**
62 63
      * Requests permissions from the remote control receiver side.
63 64
      * @param {string} userId the user id of the participant that will be
64 65
      * requested.
66
+     * @returns {Promise<boolean>} - resolve values:
67
+     * true - accept
68
+     * false - deny
69
+     * null - the participant has left.
65 70
      */
66 71
     requestPermissions(userId) {
67 72
         if(!this.enabled) {
68 73
             return Promise.reject(new Error("Remote control is disabled!"));
69 74
         }
70 75
         return new Promise((resolve, reject) => {
71
-            let permissionsReplyListener = (participant, event) => {
76
+            const clearRequest = () => {
77
+                this.requestedParticipant = null;
78
+                APP.conference.removeConferenceListener(
79
+                    ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
80
+                    permissionsReplyListener);
81
+                APP.conference.removeConferenceListener(
82
+                    ConferenceEvents.USER_LEFT,
83
+                    onUserLeft);
84
+            };
85
+            const permissionsReplyListener = (participant, event) => {
72 86
                 let result = null;
73 87
                 try {
74 88
                     result = this._handleReply(participant, event);
@@ -76,25 +90,27 @@ export default class Controller extends RemoteControlParticipant {
76 90
                     reject(e);
77 91
                 }
78 92
                 if(result !== null) {
79
-                    this.requestedParticipant = null;
80
-                    APP.conference.removeConferenceListener(
81
-                        ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
82
-                        permissionsReplyListener);
93
+                    clearRequest();
83 94
                     resolve(result);
84 95
                 }
85 96
             };
97
+            const onUserLeft = (id) => {
98
+                if(id === this.requestedParticipant) {
99
+                    clearRequest();
100
+                    resolve(null);
101
+                }
102
+            };
86 103
             APP.conference.addConferenceListener(
87 104
                 ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
88 105
                 permissionsReplyListener);
106
+            APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
107
+                onUserLeft);
89 108
             this.requestedParticipant = userId;
90 109
             this._sendRemoteControlEvent(userId, {
91 110
                 type: EVENT_TYPES.permissions,
92 111
                 action: PERMISSIONS_ACTIONS.request
93 112
             }, e => {
94
-                APP.conference.removeConferenceListener(
95
-                    ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
96
-                    permissionsReplyListener);
97
-                this.requestedParticipant = null;
113
+                clearRequest();
98 114
                 reject(e);
99 115
             });
100 116
         });
@@ -112,14 +128,18 @@ export default class Controller extends RemoteControlParticipant {
112 128
         if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE
113 129
             && remoteControlEvent.type === EVENT_TYPES.permissions
114 130
             && userId === this.requestedParticipant) {
115
-            if(remoteControlEvent.action === PERMISSIONS_ACTIONS.grant) {
116
-                this.controlledParticipant = userId;
117
-                this._start();
118
-                return true;
119
-            } else if(remoteControlEvent.action === PERMISSIONS_ACTIONS.deny) {
120
-                return false;
121
-            } else {
122
-                throw new Error("Unknown reply received!");
131
+            switch(remoteControlEvent.action) {
132
+                case PERMISSIONS_ACTIONS.grant: {
133
+                    this.controlledParticipant = userId;
134
+                    this._start();
135
+                    return true;
136
+                }
137
+                case PERMISSIONS_ACTIONS.deny:
138
+                    return false;
139
+                case PERMISSIONS_ACTIONS.error:
140
+                    throw new Error("Error occurred on receiver side");
141
+                default:
142
+                    throw new Error("Unknown reply received!");
123 143
             }
124 144
         } else {
125 145
             //different message type or another user -> ignoring the message
@@ -149,7 +169,9 @@ export default class Controller extends RemoteControlParticipant {
149 169
             return;
150 170
         APP.conference.addConferenceListener(
151 171
             ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
152
-            this.stopListener);
172
+            this._stopListener);
173
+        APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
174
+            this._userLeftListener);
153 175
         this.area = $("#largeVideoWrapper");
154 176
         this.area.mousemove(event => {
155 177
             const position = this.area.position();
@@ -179,12 +201,17 @@ export default class Controller extends RemoteControlParticipant {
179 201
     }
180 202
 
181 203
     /**
182
-     * Stops processing the mouse and keyboard events.
204
+     * Stops processing the mouse and keyboard events. Removes added listeners.
183 205
      */
184 206
     _stop() {
207
+        if(!this.controlledParticipant) {
208
+            return;
209
+        }
185 210
         APP.conference.removeConferenceListener(
186 211
             ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
187
-            this.stopListener);
212
+            this._stopListener);
213
+        APP.conference.removeConferenceListener(ConferenceEvents.USER_LEFT,
214
+            this._userLeftListener);
188 215
         this.controlledParticipant = null;
189 216
         this.area.off( "mousemove" );
190 217
         this.area.off( "mousedown" );
@@ -194,6 +221,23 @@ export default class Controller extends RemoteControlParticipant {
194 221
         $(window).off( "keydown");
195 222
         $(window).off( "keyup");
196 223
         this.area[0].onmousewheel = undefined;
224
+        APP.UI.messageHandler.openMessageDialog(
225
+            "dialog.remoteControlTitle",
226
+            "dialog.remoteControlStopMessage"
227
+        );
228
+    }
229
+
230
+    /**
231
+     * Calls this._stop() and sends stop message to the controlled participant.
232
+     */
233
+    stop() {
234
+        if(!this.controlledParticipant) {
235
+            return;
236
+        }
237
+        this._sendRemoteControlEvent(this.controlledParticipant, {
238
+            type: EVENT_TYPES.stop
239
+        });
240
+        this._stop();
197 241
     }
198 242
 
199 243
     /**
@@ -208,6 +252,22 @@ export default class Controller extends RemoteControlParticipant {
208 252
         });
209 253
     }
210 254
 
255
+    /**
256
+     * Returns true if the remote control session is started.
257
+     * @returns {boolean}
258
+     */
259
+    isStarted() {
260
+        return this.controlledParticipant !== null;
261
+    }
262
+
263
+    /**
264
+     * Returns the id of the requested participant
265
+     * @returns {string} this.requestedParticipant
266
+     */
267
+    getRequestedParticipant() {
268
+        return this.requestedParticipant;
269
+    }
270
+
211 271
     /**
212 272
      * Handler for key press events.
213 273
      * @param {String} type the type of event ("keydown"/"keyup")
@@ -220,4 +280,14 @@ export default class Controller extends RemoteControlParticipant {
220 280
             modifiers: getModifiers(event),
221 281
         });
222 282
     }
283
+
284
+    /**
285
+     * Calls the stop method if the other side have left.
286
+     * @param {string} id - the user id for the participant that have left
287
+     */
288
+    _onUserLeft(id) {
289
+        if(this.controlledParticipant === id) {
290
+            this._stop();
291
+        }
292
+    }
223 293
 }

+ 83
- 12
modules/remotecontrol/Receiver.js Переглянути файл

@@ -1,4 +1,4 @@
1
-/* global APP, JitsiMeetJS */
1
+/* global APP, JitsiMeetJS, interfaceConfig */
2 2
 import {DISCO_REMOTE_CONTROL_FEATURE, REMOTE_CONTROL_EVENT_TYPE, EVENT_TYPES,
3 3
     PERMISSIONS_ACTIONS} from "../../service/remotecontrol/Constants";
4 4
 import RemoteControlParticipant from "./RemoteControlParticipant";
@@ -21,6 +21,7 @@ export default class Receiver extends RemoteControlParticipant {
21 21
         this.controller = null;
22 22
         this._remoteControlEventsListener
23 23
             = this._onRemoteControlEvent.bind(this);
24
+        this._userLeftListener = this._onUserLeft.bind(this);
24 25
     }
25 26
 
26 27
     /**
@@ -28,30 +29,60 @@ export default class Receiver extends RemoteControlParticipant {
28 29
      * @param {boolean} enabled the new state.
29 30
      */
30 31
     enable(enabled) {
31
-        if(this.enabled !== enabled && enabled === true) {
32
+        if(this.enabled !== enabled) {
32 33
             this.enabled = enabled;
34
+        }
35
+        if(enabled === true) {
33 36
             // Announce remote control support.
34 37
             APP.connection.addFeature(DISCO_REMOTE_CONTROL_FEATURE, true);
35 38
             APP.conference.addConferenceListener(
36 39
                 ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
37 40
                 this._remoteControlEventsListener);
41
+        } else {
42
+            this._stop(true);
43
+            APP.connection.removeFeature(DISCO_REMOTE_CONTROL_FEATURE);
44
+            APP.conference.removeConferenceListener(
45
+                ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
46
+                this._remoteControlEventsListener);
38 47
         }
39 48
     }
40 49
 
41 50
     /**
42 51
      * Removes the listener for ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED
43
-     * events.
52
+     * events. Sends stop message to the wrapper application. Optionally
53
+     * displays dialog for informing the user that remote control session
54
+     * ended.
55
+     * @param {boolean} dontShowDialog - if true the dialog won't be displayed.
56
+     */
57
+    _stop(dontShowDialog = false) {
58
+        if(!this.controller) {
59
+            return;
60
+        }
61
+        this.controller = null;
62
+        APP.conference.removeConferenceListener(ConferenceEvents.USER_LEFT,
63
+            this._userLeftListener);
64
+        APP.API.sendRemoteControlEvent({
65
+            type: EVENT_TYPES.stop
66
+        });
67
+        if(!dontShowDialog) {
68
+            APP.UI.messageHandler.openMessageDialog(
69
+                "dialog.remoteControlTitle",
70
+                "dialog.remoteControlStopMessage"
71
+            );
72
+        }
73
+    }
74
+
75
+    /**
76
+     * Calls this._stop() and sends stop message to the controller participant
44 77
      */
45 78
     stop() {
46
-        APP.conference.removeConferenceListener(
47
-            ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
48
-            this._remoteControlEventsListener);
49
-        const event = {
79
+        if(!this.controller) {
80
+            return;
81
+        }
82
+        this._sendRemoteControlEvent(this.controller, {
50 83
             type: EVENT_TYPES.stop
51
-        };
52
-        this._sendRemoteControlEvent(this.controller, event);
53
-        this.controller = null;
54
-        APP.API.sendRemoteControlEvent(event);
84
+        });
85
+        this._stop();
55 86
     }
56 87
 
57 88
     /**
@@ -67,9 +98,15 @@ export default class Receiver extends RemoteControlParticipant {
67 98
                 && remoteControlEvent.action === PERMISSIONS_ACTIONS.request) {
68 99
                 remoteControlEvent.userId = participant.getId();
69 100
                 remoteControlEvent.userJID = participant.getJid();
70
-                remoteControlEvent.displayName = participant.getDisplayName();
101
+                remoteControlEvent.displayName = participant.getDisplayName()
102
+                    || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
103
+                remoteControlEvent.screenSharing
104
+                    = APP.conference.isSharingScreen;
71 105
             } else if(this.controller !== participant.getId()) {
72 106
                 return;
107
+            } else if(remoteControlEvent.type === EVENT_TYPES.stop) {
108
+                this._stop();
109
+                return;
73 110
             }
74 111
             APP.API.sendRemoteControlEvent(remoteControlEvent);
75 112
         }
@@ -83,11 +120,45 @@ export default class Receiver extends RemoteControlParticipant {
83 120
      */
84 121
     _onRemoteControlPermissionsEvent(userId, action) {
85 122
         if(action === PERMISSIONS_ACTIONS.grant) {
123
+            APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
124
+                this._userLeftListener);
86 125
             this.controller = userId;
126
+            if(!APP.conference.isSharingScreen) {
127
+                APP.conference.toggleScreenSharing();
128
+                APP.conference.screenSharingPromise.then(() => {
129
+                    if(APP.conference.isSharingScreen) {
130
+                        this._sendRemoteControlEvent(userId, {
131
+                            type: EVENT_TYPES.permissions,
132
+                            action: action
133
+                        });
134
+                    } else {
135
+                        this._sendRemoteControlEvent(userId, {
136
+                            type: EVENT_TYPES.permissions,
137
+                            action: PERMISSIONS_ACTIONS.error
138
+                        });
139
+                    }
140
+                }).catch(() => {
141
+                    this._sendRemoteControlEvent(userId, {
142
+                        type: EVENT_TYPES.permissions,
143
+                        action: PERMISSIONS_ACTIONS.error
144
+                    });
145
+                });
146
+                return;
147
+            }
87 148
         }
88 149
         this._sendRemoteControlEvent(userId, {
89 150
             type: EVENT_TYPES.permissions,
90 151
             action: action
91 152
         });
92 153
     }
154
+
155
+    /**
156
+     * Calls the stop method if the other side have left.
157
+     * @param {string} id - the user id for the participant that have left
158
+     */
159
+    _onUserLeft(id) {
160
+        if(this.controller === id) {
161
+            this._stop();
162
+        }
163
+    }
93 164
 }

+ 18
- 2
modules/remotecontrol/RemoteControl.js Переглянути файл

@@ -1,7 +1,7 @@
1 1
 /* global APP, config */
2 2
 import Controller from "./Controller";
3 3
 import Receiver from "./Receiver";
4
-import {EVENT_TYPES}
4
+import {EVENT_TYPES, DISCO_REMOTE_CONTROL_FEATURE}
5 5
     from "../../service/remotecontrol/Constants";
6 6
 
7 7
 /**
@@ -24,7 +24,8 @@ class RemoteControl {
24 24
      * enabled or not, initializes the API module.
25 25
      */
26 26
     init() {
27
-        if(config.disableRemoteControl || this.initialized) {
27
+        if(config.disableRemoteControl || this.initialized
28
+            || !APP.conference.isDesktopSharingEnabled) {
28 29
             return;
29 30
         }
30 31
         this.initialized = true;
@@ -32,6 +33,9 @@ class RemoteControl {
32 33
             forceEnable: true,
33 34
         });
34 35
         this.controller.enable(true);
36
+        if(this.enabled) { // supported message came before init.
37
+            this._onRemoteControlSupported();
38
+        }
35 39
     }
36 40
 
37 41
     /**
@@ -62,6 +66,18 @@ class RemoteControl {
62 66
             }
63 67
         }
64 68
     }
69
+
70
+    /**
71
+     * Checks whether the passed user supports remote control or not
72
+     * @param {JitsiParticipant} user the user to be tested
73
+     * @returns {Promise<boolean>} the promise will be resolved with true if
74
+     * the user supports remote control and with false if not.
75
+     */
76
+    checkUserRemoteControlSupport(user) {
77
+        return user.getFeatures().then(features =>
78
+            features.has(DISCO_REMOTE_CONTROL_FEATURE), () => false
79
+        );
80
+    }
65 81
 }
66 82
 
67 83
 export default new RemoteControl();

+ 2
- 1
service/remotecontrol/Constants.js Переглянути файл

@@ -26,7 +26,8 @@ export const EVENT_TYPES = {
26 26
 export const PERMISSIONS_ACTIONS = {
27 27
     request: "request",
28 28
     grant: "grant",
29
-    deny: "deny"
29
+    deny: "deny",
30
+    error: "error"
30 31
 };
31 32
 
32 33
 /**

Завантаження…
Відмінити
Зберегти