Pārlūkot izejas kodu

feat(remotecontrol): UI for requesting permissions

master
hristoterezov 8 gadus atpakaļ
vecāks
revīzija
a4d5c41b3a

+ 9
- 4
conference.js Parādīt failu

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

+ 3
- 3
css/_filmstrip.scss Parādīt failu

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

+ 6
- 2
css/_popup_menu.scss Parādīt failu

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

+ 8
- 3
lang/main.json Parādīt failu

156
         "kick": "Kick out",
156
         "kick": "Kick out",
157
         "muted": "Muted",
157
         "muted": "Muted",
158
         "domute": "Mute",
158
         "domute": "Mute",
159
-        "flip": "Flip"
160
-
159
+        "flip": "Flip",
160
+        "remoteControl": "Remote control"
161
     },
161
     },
162
     "connectionindicator":
162
     "connectionindicator":
163
     {
163
     {
316
         "externalInstallationMsg": "You need to install our desktop sharing extension.",
316
         "externalInstallationMsg": "You need to install our desktop sharing extension.",
317
         "muteParticipantTitle": "Mute this participant?",
317
         "muteParticipantTitle": "Mute this participant?",
318
         "muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.",
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
     "email":
326
     "email":
322
     {
327
     {

+ 7
- 0
modules/UI/UI.js Parādīt failu

1441
     GumPermissionsOverlay.hide();
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
 module.exports = UI;
1451
 module.exports = UI;

+ 122
- 38
modules/UI/videolayout/RemoteVideo.js Parādīt failu

29
     this.videoSpanId = `participant_${this.id}`;
29
     this.videoSpanId = `participant_${this.id}`;
30
     SmallVideo.call(this, VideoLayout);
30
     SmallVideo.call(this, VideoLayout);
31
     this.hasRemoteVideoMenu = false;
31
     this.hasRemoteVideoMenu = false;
32
+    this._supportsRemoteControl = false;
32
     this.addRemoteVideoContainer();
33
     this.addRemoteVideoContainer();
33
     this.connectionIndicator = new ConnectionIndicator(this, this.id);
34
     this.connectionIndicator = new ConnectionIndicator(this, this.id);
34
     this.setDisplayName();
35
     this.setDisplayName();
64
 
65
 
65
     this.initBrowserSpecificProperties();
66
     this.initBrowserSpecificProperties();
66
 
67
 
67
-    if (APP.conference.isModerator) {
68
+    if (APP.conference.isModerator || this._supportsRemoteControl) {
68
         this.addRemoteVideoMenu();
69
         this.addRemoteVideoMenu();
69
     }
70
     }
70
 
71
 
106
         // call the original show, passing its actual this
107
         // call the original show, passing its actual this
107
         origShowFunc.call(this.popover);
108
         origShowFunc.call(this.popover);
108
     }.bind(this);
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
     let popupmenuElement = document.createElement('ul');
132
     let popupmenuElement = document.createElement('ul');
140
     popupmenuElement.className = 'popupmenu';
133
     popupmenuElement.className = 'popupmenu';
141
     popupmenuElement.id = `remote_popupmenu_${this.id}`;
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
             data: {
192
             data: {
170
-                i18n: 'videothumbnail.kick'
193
+                i18n: 'videothumbnail.remoteControl'
171
             }
194
             }
172
-        }
173
-    ];
195
+        });
196
+    }
174
 
197
 
175
     menuItems.forEach(el => {
198
     menuItems.forEach(el => {
176
         let menuItem = this._generatePopupMenuItem(el);
199
         let menuItem = this._generatePopupMenuItem(el);
182
     return popupmenuElement;
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
 RemoteVideo.prototype._muteHandler = function () {
270
 RemoteVideo.prototype._muteHandler = function () {
186
     if (this.isAudioMuted)
271
     if (this.isAudioMuted)
187
         return;
272
         return;
244
     linkItem.appendChild(textContent);
329
     linkItem.appendChild(textContent);
245
     linkItem.id = id;
330
     linkItem.id = id;
246
 
331
 
247
-    // Delegate event to the document.
248
-    $(document).on("click", `#${id}`, handler);
332
+    linkItem.onclick = handler;
249
     menuItem.appendChild(linkItem);
333
     menuItem.appendChild(linkItem);
250
 
334
 
251
     return menuItem;
335
     return menuItem;

+ 27
- 2
modules/UI/videolayout/VideoLayout.js Parādīt failu

406
             remoteVideo = smallVideo;
406
             remoteVideo = smallVideo;
407
         else
407
         else
408
             remoteVideo = new RemoteVideo(user, VideoLayout, eventEmitter);
408
             remoteVideo = new RemoteVideo(user, VideoLayout, eventEmitter);
409
+        this._setRemoteControlProperties(user, remoteVideo);
409
         this.addRemoteVideoContainer(id, remoteVideo);
410
         this.addRemoteVideoContainer(id, remoteVideo);
410
     },
411
     },
411
 
412
 
1158
      * Sets the flipX state of the local video.
1159
      * Sets the flipX state of the local video.
1159
      * @param {boolean} true for flipped otherwise false;
1160
      * @param {boolean} true for flipped otherwise false;
1160
      */
1161
      */
1161
-    setLocalFlipX: function (val) {
1162
+    setLocalFlipX (val) {
1162
         this.localFlipX = val;
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
 export default VideoLayout;
1194
 export default VideoLayout;

+ 91
- 21
modules/remotecontrol/Controller.js Parādīt failu

55
         super();
55
         super();
56
         this.controlledParticipant = null;
56
         this.controlledParticipant = null;
57
         this.requestedParticipant = null;
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
      * Requests permissions from the remote control receiver side.
63
      * Requests permissions from the remote control receiver side.
63
      * @param {string} userId the user id of the participant that will be
64
      * @param {string} userId the user id of the participant that will be
64
      * requested.
65
      * requested.
66
+     * @returns {Promise<boolean>} - resolve values:
67
+     * true - accept
68
+     * false - deny
69
+     * null - the participant has left.
65
      */
70
      */
66
     requestPermissions(userId) {
71
     requestPermissions(userId) {
67
         if(!this.enabled) {
72
         if(!this.enabled) {
68
             return Promise.reject(new Error("Remote control is disabled!"));
73
             return Promise.reject(new Error("Remote control is disabled!"));
69
         }
74
         }
70
         return new Promise((resolve, reject) => {
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
                 let result = null;
86
                 let result = null;
73
                 try {
87
                 try {
74
                     result = this._handleReply(participant, event);
88
                     result = this._handleReply(participant, event);
76
                     reject(e);
90
                     reject(e);
77
                 }
91
                 }
78
                 if(result !== null) {
92
                 if(result !== null) {
79
-                    this.requestedParticipant = null;
80
-                    APP.conference.removeConferenceListener(
81
-                        ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
82
-                        permissionsReplyListener);
93
+                    clearRequest();
83
                     resolve(result);
94
                     resolve(result);
84
                 }
95
                 }
85
             };
96
             };
97
+            const onUserLeft = (id) => {
98
+                if(id === this.requestedParticipant) {
99
+                    clearRequest();
100
+                    resolve(null);
101
+                }
102
+            };
86
             APP.conference.addConferenceListener(
103
             APP.conference.addConferenceListener(
87
                 ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
104
                 ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
88
                 permissionsReplyListener);
105
                 permissionsReplyListener);
106
+            APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
107
+                onUserLeft);
89
             this.requestedParticipant = userId;
108
             this.requestedParticipant = userId;
90
             this._sendRemoteControlEvent(userId, {
109
             this._sendRemoteControlEvent(userId, {
91
                 type: EVENT_TYPES.permissions,
110
                 type: EVENT_TYPES.permissions,
92
                 action: PERMISSIONS_ACTIONS.request
111
                 action: PERMISSIONS_ACTIONS.request
93
             }, e => {
112
             }, e => {
94
-                APP.conference.removeConferenceListener(
95
-                    ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
96
-                    permissionsReplyListener);
97
-                this.requestedParticipant = null;
113
+                clearRequest();
98
                 reject(e);
114
                 reject(e);
99
             });
115
             });
100
         });
116
         });
112
         if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE
128
         if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE
113
             && remoteControlEvent.type === EVENT_TYPES.permissions
129
             && remoteControlEvent.type === EVENT_TYPES.permissions
114
             && userId === this.requestedParticipant) {
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
         } else {
144
         } else {
125
             //different message type or another user -> ignoring the message
145
             //different message type or another user -> ignoring the message
149
             return;
169
             return;
150
         APP.conference.addConferenceListener(
170
         APP.conference.addConferenceListener(
151
             ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
171
             ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
152
-            this.stopListener);
172
+            this._stopListener);
173
+        APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
174
+            this._userLeftListener);
153
         this.area = $("#largeVideoWrapper");
175
         this.area = $("#largeVideoWrapper");
154
         this.area.mousemove(event => {
176
         this.area.mousemove(event => {
155
             const position = this.area.position();
177
             const position = this.area.position();
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
     _stop() {
206
     _stop() {
207
+        if(!this.controlledParticipant) {
208
+            return;
209
+        }
185
         APP.conference.removeConferenceListener(
210
         APP.conference.removeConferenceListener(
186
             ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
211
             ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
187
-            this.stopListener);
212
+            this._stopListener);
213
+        APP.conference.removeConferenceListener(ConferenceEvents.USER_LEFT,
214
+            this._userLeftListener);
188
         this.controlledParticipant = null;
215
         this.controlledParticipant = null;
189
         this.area.off( "mousemove" );
216
         this.area.off( "mousemove" );
190
         this.area.off( "mousedown" );
217
         this.area.off( "mousedown" );
194
         $(window).off( "keydown");
221
         $(window).off( "keydown");
195
         $(window).off( "keyup");
222
         $(window).off( "keyup");
196
         this.area[0].onmousewheel = undefined;
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
         });
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
      * Handler for key press events.
272
      * Handler for key press events.
213
      * @param {String} type the type of event ("keydown"/"keyup")
273
      * @param {String} type the type of event ("keydown"/"keyup")
220
             modifiers: getModifiers(event),
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 Parādīt failu

1
-/* global APP, JitsiMeetJS */
1
+/* global APP, JitsiMeetJS, interfaceConfig */
2
 import {DISCO_REMOTE_CONTROL_FEATURE, REMOTE_CONTROL_EVENT_TYPE, EVENT_TYPES,
2
 import {DISCO_REMOTE_CONTROL_FEATURE, REMOTE_CONTROL_EVENT_TYPE, EVENT_TYPES,
3
     PERMISSIONS_ACTIONS} from "../../service/remotecontrol/Constants";
3
     PERMISSIONS_ACTIONS} from "../../service/remotecontrol/Constants";
4
 import RemoteControlParticipant from "./RemoteControlParticipant";
4
 import RemoteControlParticipant from "./RemoteControlParticipant";
21
         this.controller = null;
21
         this.controller = null;
22
         this._remoteControlEventsListener
22
         this._remoteControlEventsListener
23
             = this._onRemoteControlEvent.bind(this);
23
             = this._onRemoteControlEvent.bind(this);
24
+        this._userLeftListener = this._onUserLeft.bind(this);
24
     }
25
     }
25
 
26
 
26
     /**
27
     /**
28
      * @param {boolean} enabled the new state.
29
      * @param {boolean} enabled the new state.
29
      */
30
      */
30
     enable(enabled) {
31
     enable(enabled) {
31
-        if(this.enabled !== enabled && enabled === true) {
32
+        if(this.enabled !== enabled) {
32
             this.enabled = enabled;
33
             this.enabled = enabled;
34
+        }
35
+        if(enabled === true) {
33
             // Announce remote control support.
36
             // Announce remote control support.
34
             APP.connection.addFeature(DISCO_REMOTE_CONTROL_FEATURE, true);
37
             APP.connection.addFeature(DISCO_REMOTE_CONTROL_FEATURE, true);
35
             APP.conference.addConferenceListener(
38
             APP.conference.addConferenceListener(
36
                 ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
39
                 ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
37
                 this._remoteControlEventsListener);
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
      * Removes the listener for ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED
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
     stop() {
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
             type: EVENT_TYPES.stop
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
                 && remoteControlEvent.action === PERMISSIONS_ACTIONS.request) {
98
                 && remoteControlEvent.action === PERMISSIONS_ACTIONS.request) {
68
                 remoteControlEvent.userId = participant.getId();
99
                 remoteControlEvent.userId = participant.getId();
69
                 remoteControlEvent.userJID = participant.getJid();
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
             } else if(this.controller !== participant.getId()) {
105
             } else if(this.controller !== participant.getId()) {
72
                 return;
106
                 return;
107
+            } else if(remoteControlEvent.type === EVENT_TYPES.stop) {
108
+                this._stop();
109
+                return;
73
             }
110
             }
74
             APP.API.sendRemoteControlEvent(remoteControlEvent);
111
             APP.API.sendRemoteControlEvent(remoteControlEvent);
75
         }
112
         }
83
      */
120
      */
84
     _onRemoteControlPermissionsEvent(userId, action) {
121
     _onRemoteControlPermissionsEvent(userId, action) {
85
         if(action === PERMISSIONS_ACTIONS.grant) {
122
         if(action === PERMISSIONS_ACTIONS.grant) {
123
+            APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
124
+                this._userLeftListener);
86
             this.controller = userId;
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
         this._sendRemoteControlEvent(userId, {
149
         this._sendRemoteControlEvent(userId, {
89
             type: EVENT_TYPES.permissions,
150
             type: EVENT_TYPES.permissions,
90
             action: action
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 Parādīt failu

1
 /* global APP, config */
1
 /* global APP, config */
2
 import Controller from "./Controller";
2
 import Controller from "./Controller";
3
 import Receiver from "./Receiver";
3
 import Receiver from "./Receiver";
4
-import {EVENT_TYPES}
4
+import {EVENT_TYPES, DISCO_REMOTE_CONTROL_FEATURE}
5
     from "../../service/remotecontrol/Constants";
5
     from "../../service/remotecontrol/Constants";
6
 
6
 
7
 /**
7
 /**
24
      * enabled or not, initializes the API module.
24
      * enabled or not, initializes the API module.
25
      */
25
      */
26
     init() {
26
     init() {
27
-        if(config.disableRemoteControl || this.initialized) {
27
+        if(config.disableRemoteControl || this.initialized
28
+            || !APP.conference.isDesktopSharingEnabled) {
28
             return;
29
             return;
29
         }
30
         }
30
         this.initialized = true;
31
         this.initialized = true;
32
             forceEnable: true,
33
             forceEnable: true,
33
         });
34
         });
34
         this.controller.enable(true);
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
             }
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
 export default new RemoteControl();
83
 export default new RemoteControl();

+ 2
- 1
service/remotecontrol/Constants.js Parādīt failu

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

Notiek ielāde…
Atcelt
Saglabāt