Bladeren bron

Fix Recording regression caused by 'React Toolbar'

Saúl Ibarra Corretgé reported that Recording shows an error dialog
stating "There was an error connecting to your camera". Hristo Terezov
and Yana Stamcheva traced that the problem originates in
da4425b5c0
and, more specifically, is caused by a different order of execution due
to the move of the invocation of the function Recording.init.

The solution is to bring back the execution location of Recording.init.
j8
Lyubo Marinov 8 jaren geleden
bovenliggende
commit
ae06a6ce41

+ 4
- 0
modules/UI/UI.js Bestand weergeven

@@ -322,6 +322,10 @@ UI.start = function () {
322 322
 
323 323
         $("#videoconference_page").mousemove(debouncedShowToolbar);
324 324
 
325
+        // Initialise the recording module.
326
+        if (config.enableRecording) {
327
+            Recording.init(eventEmitter, config.recordingType);
328
+        }
325 329
         // Initialize side panels
326 330
         SidePanels.init(eventEmitter);
327 331
     } else {

+ 128
- 128
modules/UI/recording/Recording.js Bestand weergeven

@@ -21,7 +21,7 @@ import UIUtil from '../util/UIUtil';
21 21
 import VideoLayout from '../videolayout/VideoLayout';
22 22
 import Feedback from '../feedback/Feedback.js';
23 23
 
24
-import { hideToolbox } from '../../../react/features/toolbox';
24
+import { setToolboxEnabled } from '../../../react/features/toolbox';
25 25
 
26 26
 /**
27 27
  * The dialog for user input.
@@ -35,8 +35,10 @@ let dialog = null;
35 35
  * @private
36 36
  */
37 37
 function _isRecordingButtonEnabled() {
38
-    return interfaceConfig.TOOLBAR_BUTTONS.indexOf("recording") !== -1
39
-            && config.enableRecording && APP.conference.isRecordingSupported();
38
+    return (
39
+        interfaceConfig.TOOLBAR_BUTTONS.indexOf("recording") !== -1
40
+            && config.enableRecording
41
+            && APP.conference.isRecordingSupported());
40 42
 }
41 43
 
42 44
 /**
@@ -129,7 +131,7 @@ function _requestLiveStreamId() {
129 131
  * Request recording token from the user.
130 132
  * @returns {Promise}
131 133
  */
132
-function _requestRecordingToken () {
134
+function _requestRecordingToken() {
133 135
     let titleKey = "dialog.recordingToken";
134 136
     let messageString = (
135 137
         `<input name="recordingToken" type="text"
@@ -164,7 +166,7 @@ function _requestRecordingToken () {
164 166
  * @returns {Promise}
165 167
  * @private
166 168
  */
167
-function _showStopRecordingPrompt (recordingType) {
169
+function _showStopRecordingPrompt(recordingType) {
168 170
     var title;
169 171
     var message;
170 172
     var buttonKey;
@@ -179,19 +181,13 @@ function _showStopRecordingPrompt (recordingType) {
179 181
         buttonKey = "dialog.stopRecording";
180 182
     }
181 183
 
182
-    return new Promise(function (resolve, reject) {
184
+    return new Promise((resolve, reject) => {
183 185
         dialog = APP.UI.messageHandler.openTwoButtonDialog({
184 186
             titleKey: title,
185 187
             msgKey: message,
186 188
             leftButtonKey: buttonKey,
187
-            submitFunction: function(e,v) {
188
-                if (v) {
189
-                    resolve();
190
-                } else {
191
-                    reject();
192
-                }
193
-            },
194
-            closeFunction: function () {
189
+            submitFunction: (e, v) => (v ? resolve : reject)(),
190
+            closeFunction: () => {
195 191
                 dialog = null;
196 192
             }
197 193
         });
@@ -250,35 +246,12 @@ var Recording = {
250 246
     /**
251 247
      * Initializes the recording UI.
252 248
      */
253
-    init (emitter, recordingType) {
254
-        this.eventEmitter = emitter;
249
+    init(eventEmitter, recordingType) {
250
+        this.eventEmitter = eventEmitter;
251
+        this.recordingType = recordingType;
255 252
 
256 253
         this.updateRecordingState(APP.conference.getRecordingState());
257 254
 
258
-        this.initRecordingButton(recordingType);
259
-
260
-        // If I am a recorder then I publish my recorder custom role to notify
261
-        // everyone.
262
-        if (config.iAmRecorder) {
263
-            VideoLayout.enableDeviceAvailabilityIcons(
264
-                APP.conference.getMyUserId(), false);
265
-            VideoLayout.setLocalVideoVisible(false);
266
-            Feedback.enableFeedback(false);
267
-            APP.store.dispatch(hideToolbox());
268
-            APP.UI.messageHandler.enableNotifications(false);
269
-            APP.UI.messageHandler.enablePopups(false);
270
-        }
271
-    },
272
-
273
-    /**
274
-     * Initialise the recording button.
275
-     */
276
-    initRecordingButton(recordingType) {
277
-        let selector = $('#toolbar_button_record');
278
-
279
-        let button = selector.get(0);
280
-        UIUtil.setTooltip(button, 'liveStreaming.buttonTooltip', 'right');
281
-
282 255
         if (recordingType === 'jibri') {
283 256
             this.baseClass = "fa fa-play-circle";
284 257
             this.recordingTitle = "dialog.liveStreaming";
@@ -304,101 +277,44 @@ var Recording = {
304 277
             this.recordingBusy = "liveStreaming.busy";
305 278
         }
306 279
 
280
+        // XXX Due to the React-ification of Toolbox, the HTMLElement with id
281
+        // toolbar_button_record may not exist yet.
282
+        $(document).on(
283
+            'click',
284
+            '#toolbar_button_record',
285
+            ev => this._onToolbarButtonClick(ev));
286
+
287
+        // If I am a recorder then I publish my recorder custom role to notify
288
+        // everyone.
289
+        if (config.iAmRecorder) {
290
+            VideoLayout.enableDeviceAvailabilityIcons(
291
+                APP.conference.getMyUserId(), false);
292
+            VideoLayout.setLocalVideoVisible(false);
293
+            Feedback.enableFeedback(false);
294
+            APP.store.dispatch(setToolboxEnabled(false));
295
+            APP.UI.messageHandler.enableNotifications(false);
296
+            APP.UI.messageHandler.enablePopups(false);
297
+        }
298
+    },
299
+
300
+    /**
301
+     * Initialise the recording button.
302
+     */
303
+    initRecordingButton() {
304
+        const selector = $('#toolbar_button_record');
305
+
306
+        UIUtil.setTooltip(selector, 'liveStreaming.buttonTooltip', 'right');
307
+
307 308
         selector.addClass(this.baseClass);
308 309
         selector.attr("data-i18n", "[content]" + this.recordingButtonTooltip);
309 310
         APP.translation.translateElement(selector);
310
-
311
-        var self = this;
312
-        selector.click(function () {
313
-            if (dialog)
314
-                return;
315
-            JitsiMeetJS.analytics.sendEvent('recording.clicked');
316
-            switch (self.currentState) {
317
-                case Status.ON:
318
-                case Status.RETRYING:
319
-                case Status.PENDING: {
320
-                    _showStopRecordingPrompt(recordingType).then(
321
-                        () => {
322
-                            self.eventEmitter.emit(UIEvents.RECORDING_TOGGLED);
323
-                            JitsiMeetJS.analytics.sendEvent(
324
-                                'recording.stopped');
325
-                        },
326
-                        () => {});
327
-                    break;
328
-                }
329
-                case Status.AVAILABLE:
330
-                case Status.OFF: {
331
-                    if (recordingType === 'jibri')
332
-                        _requestLiveStreamId().then((streamId) => {
333
-                            self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
334
-                                {streamId: streamId});
335
-                            JitsiMeetJS.analytics.sendEvent(
336
-                                'recording.started');
337
-                        }).catch(
338
-                            reason => {
339
-                                if (reason !== APP.UI.messageHandler.CANCEL)
340
-                                    logger.error(reason);
341
-                                else
342
-                                    JitsiMeetJS.analytics.sendEvent(
343
-                                        'recording.canceled');
344
-                            }
345
-                        );
346
-                    else {
347
-                        if (self.predefinedToken) {
348
-                            self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
349
-                                {token: self.predefinedToken});
350
-                            JitsiMeetJS.analytics.sendEvent(
351
-                                'recording.started');
352
-                            return;
353
-                        }
354
-
355
-                        _requestRecordingToken().then((token) => {
356
-                            self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
357
-                                {token: token});
358
-                            JitsiMeetJS.analytics.sendEvent(
359
-                                'recording.started');
360
-                        }).catch(
361
-                            reason => {
362
-                                if (reason !== APP.UI.messageHandler.CANCEL)
363
-                                    logger.error(reason);
364
-                                else
365
-                                    JitsiMeetJS.analytics.sendEvent(
366
-                                        'recording.canceled');
367
-                            }
368
-                        );
369
-                    }
370
-                    break;
371
-                }
372
-                case Status.BUSY: {
373
-                    dialog = APP.UI.messageHandler.openMessageDialog(
374
-                        self.recordingTitle,
375
-                        self.recordingBusy,
376
-                        null,
377
-                        function () {
378
-                            dialog = null;
379
-                        }
380
-                    );
381
-                    break;
382
-                }
383
-                default: {
384
-                    dialog = APP.UI.messageHandler.openMessageDialog(
385
-                        self.recordingTitle,
386
-                        self.recordingUnavailable,
387
-                        null,
388
-                        function () {
389
-                            dialog = null;
390
-                        }
391
-                    );
392
-                }
393
-            }
394
-        });
395 311
     },
396 312
 
397 313
     /**
398 314
      * Shows or hides the 'recording' button.
399 315
      * @param show {true} to show the recording button, {false} to hide it
400 316
      */
401
-    showRecordingButton (show) {
317
+    showRecordingButton(show) {
402 318
         let shouldShow = show && _isRecordingButtonEnabled();
403 319
         let id = 'toolbar_button_record';
404 320
 
@@ -425,7 +341,7 @@ var Recording = {
425 341
      * Sets the state of the recording button.
426 342
      * @param recordingState gives us the current recording state
427 343
      */
428
-    updateRecordingUI (recordingState) {
344
+    updateRecordingUI(recordingState) {
429 345
 
430 346
         let oldState = this.currentState;
431 347
         this.currentState = recordingState;
@@ -491,7 +407,7 @@ var Recording = {
491 407
     },
492 408
     // checks whether recording is enabled and whether we have params
493 409
     // to start automatically recording
494
-    checkAutoRecord () {
410
+    checkAutoRecord() {
495 411
         if (_isRecordingButtonEnabled && config.autoRecord) {
496 412
             this.predefinedToken = UIUtil.escapeHtml(config.autoRecordToken);
497 413
             this.eventEmitter.emit(UIEvents.RECORDING_TOGGLED,
@@ -514,6 +430,90 @@ var Recording = {
514 430
         APP.translation.translateElement(labelSelector);
515 431
     },
516 432
 
433
+    /**
434
+     * Handles {@code click} on {@code toolbar_button_record}.
435
+     *
436
+     * @returns {void}
437
+     */
438
+    _onToolbarButtonClick() {
439
+        if (dialog) {
440
+            return;
441
+        }
442
+
443
+        JitsiMeetJS.analytics.sendEvent('recording.clicked');
444
+        switch (this.currentState) {
445
+        case Status.ON:
446
+        case Status.RETRYING:
447
+        case Status.PENDING: {
448
+            _showStopRecordingPrompt(this.recordingType).then(
449
+                () => {
450
+                    this.eventEmitter.emit(UIEvents.RECORDING_TOGGLED);
451
+                    JitsiMeetJS.analytics.sendEvent('recording.stopped');
452
+                },
453
+                () => {});
454
+            break;
455
+        }
456
+        case Status.AVAILABLE:
457
+        case Status.OFF: {
458
+            if (this.recordingType === 'jibri')
459
+                _requestLiveStreamId().then(streamId => {
460
+                    this.eventEmitter.emit(
461
+                        UIEvents.RECORDING_TOGGLED,
462
+                        { streamId });
463
+                    JitsiMeetJS.analytics.sendEvent('recording.started');
464
+                }).catch(reason => {
465
+                    if (reason !== APP.UI.messageHandler.CANCEL)
466
+                        logger.error(reason);
467
+                    else
468
+                        JitsiMeetJS.analytics.sendEvent('recording.canceled');
469
+                });
470
+            else {
471
+                if (this.predefinedToken) {
472
+                    this.eventEmitter.emit(
473
+                        UIEvents.RECORDING_TOGGLED,
474
+                        { token: this.predefinedToken });
475
+                    JitsiMeetJS.analytics.sendEvent('recording.started');
476
+                    return;
477
+                }
478
+
479
+                _requestRecordingToken().then((token) => {
480
+                    this.eventEmitter.emit(
481
+                        UIEvents.RECORDING_TOGGLED,
482
+                        { token });
483
+                    JitsiMeetJS.analytics.sendEvent('recording.started');
484
+                }).catch(reason => {
485
+                    if (reason !== APP.UI.messageHandler.CANCEL)
486
+                        logger.error(reason);
487
+                    else
488
+                        JitsiMeetJS.analytics.sendEvent('recording.canceled');
489
+                });
490
+            }
491
+            break;
492
+        }
493
+        case Status.BUSY: {
494
+            dialog = APP.UI.messageHandler.openMessageDialog(
495
+                this.recordingTitle,
496
+                this.recordingBusy,
497
+                null,
498
+                () => {
499
+                    dialog = null;
500
+                }
501
+            );
502
+            break;
503
+        }
504
+        default: {
505
+            dialog = APP.UI.messageHandler.openMessageDialog(
506
+                this.recordingTitle,
507
+                this.recordingUnavailable,
508
+                null,
509
+                () => {
510
+                    dialog = null;
511
+                }
512
+            );
513
+        }
514
+        }
515
+    },
516
+
517 517
     /**
518 518
      * Sets the toggled state of the recording toolbar button.
519 519
      *

+ 6
- 4
modules/UI/util/UIUtil.js Bestand weergeven

@@ -157,11 +157,13 @@ const IndicatorFontSizes = {
157 157
      * @param position the position of the tooltip in relation to the element
158 158
      */
159 159
     setTooltip(element, key, position) {
160
-        if (element !== null) {
161
-            element.setAttribute('data-tooltip', TOOLTIP_POSITIONS[position]);
162
-            element.setAttribute('data-i18n', '[content]' + key);
160
+        if (element) {
161
+            const selector = element.jquery ? element : $(element);
163 162
 
164
-            APP.translation.translateElement($(element));
163
+            selector.attr('data-tooltip', TOOLTIP_POSITIONS[position]);
164
+            selector.attr('data-i18n', `[content]${key}`);
165
+
166
+            APP.translation.translateElement(selector);
165 167
         }
166 168
     },
167 169
 

+ 20
- 10
react/features/toolbox/actionTypes.js Bestand weergeven

@@ -21,16 +21,6 @@ export const CLEAR_TOOLBOX_TIMEOUT = Symbol('CLEAR_TOOLBOX_TIMEOUT');
21 21
 export const SET_DEFAULT_TOOLBOX_BUTTONS
22 22
     = Symbol('SET_DEFAULT_TOOLBOX_BUTTONS');
23 23
 
24
-/**
25
- * The type of the action which sets the permanent visibility of the Toolbox.
26
- *
27
- * {
28
- *     type: SET_TOOLBOX_ALWAYS_VISIBLE,
29
- *     alwaysVisible: boolean
30
- * }
31
- */
32
-export const SET_TOOLBOX_ALWAYS_VISIBLE = Symbol('SET_TOOLBOX_ALWAYS_VISIBLE');
33
-
34 24
 /**
35 25
  * The type of the action which sets the conference subject.
36 26
  *
@@ -73,6 +63,26 @@ export const SET_TOOLBAR_BUTTON = Symbol('SET_TOOLBAR_BUTTON');
73 63
  */
74 64
 export const SET_TOOLBAR_HOVERED = Symbol('SET_TOOLBAR_HOVERED');
75 65
 
66
+/**
67
+ * The type of the action which sets the permanent visibility of the Toolbox.
68
+ *
69
+ * {
70
+ *     type: SET_TOOLBOX_ALWAYS_VISIBLE,
71
+ *     alwaysVisible: boolean
72
+ * }
73
+ */
74
+export const SET_TOOLBOX_ALWAYS_VISIBLE = Symbol('SET_TOOLBOX_ALWAYS_VISIBLE');
75
+
76
+/**
77
+ * The type of the (redux) action which enables/disables the Toolbox.
78
+ *
79
+ * {
80
+ *     type: SET_TOOLBOX_ENABLED,
81
+ *     enabled: boolean
82
+ * }
83
+ */
84
+export const SET_TOOLBOX_ENABLED = Symbol('SET_TOOLBOX_ENABLED');
85
+
76 86
 /**
77 87
  * The type of the action which sets a new Toolbox visibility timeout and its
78 88
  * delay.

+ 18
- 1
react/features/toolbox/actions.native.js Bestand weergeven

@@ -5,11 +5,12 @@ import type { Dispatch } from 'redux-thunk';
5 5
 import {
6 6
     CLEAR_TOOLBOX_TIMEOUT,
7 7
     SET_DEFAULT_TOOLBOX_BUTTONS,
8
-    SET_TOOLBOX_ALWAYS_VISIBLE,
9 8
     SET_SUBJECT,
10 9
     SET_SUBJECT_SLIDE_IN,
11 10
     SET_TOOLBAR_BUTTON,
12 11
     SET_TOOLBAR_HOVERED,
12
+    SET_TOOLBOX_ALWAYS_VISIBLE,
13
+    SET_TOOLBOX_ENABLED,
13 14
     SET_TOOLBOX_TIMEOUT,
14 15
     SET_TOOLBOX_TIMEOUT_MS,
15 16
     SET_TOOLBOX_VISIBLE
@@ -168,6 +169,22 @@ export function setToolboxAlwaysVisible(alwaysVisible: boolean): Object {
168 169
 
169 170
 /* eslint-disable flowtype/space-before-type-colon */
170 171
 
172
+/**
173
+ * Enables/disables the toolbox.
174
+ *
175
+ * @param {boolean} enabled - True to enable the toolbox or false to disable it.
176
+ * @returns {{
177
+ *     type: SET_TOOLBOX_ENABLED,
178
+ *     enabled: boolean
179
+ * }}
180
+ */
181
+export function setToolboxEnabled(enabled: boolean): Object {
182
+    return {
183
+        type: SET_TOOLBOX_ENABLED,
184
+        enabled
185
+    };
186
+}
187
+
171 188
 /**
172 189
  * Dispatches an action which sets new timeout and clears the previous one.
173 190
  *

+ 10
- 8
react/features/toolbox/actions.web.js Bestand weergeven

@@ -3,8 +3,8 @@
3 3
 import Recording from '../../../modules/UI/recording/Recording';
4 4
 import SideContainerToggler
5 5
     from '../../../modules/UI/side_pannels/SideContainerToggler';
6
-import UIEvents from '../../../service/UI/UIEvents';
7 6
 import UIUtil from '../../../modules/UI/util/UIUtil';
7
+import UIEvents from '../../../service/UI/UIEvents';
8 8
 
9 9
 import {
10 10
     clearToolboxTimeout,
@@ -171,14 +171,11 @@ export function showDialPadButton(show: boolean): Function {
171 171
  */
172 172
 export function showRecordingButton(): Function {
173 173
     return (dispatch: Dispatch<*>) => {
174
-        const eventEmitter = APP.UI.eventEmitter;
175
-        const buttonName = 'recording';
176
-
177
-        dispatch(setToolbarButton(buttonName, {
174
+        dispatch(setToolbarButton('recording', {
178 175
             hidden: false
179 176
         }));
180 177
 
181
-        Recording.init(eventEmitter, config.recordingType);
178
+        Recording.initRecordingButton();
182 179
     };
183 180
 }
184 181
 
@@ -234,9 +231,14 @@ export function showSIPCallButton(show: boolean): Function {
234 231
 export function showToolbox(timeout: number = 0): Object {
235 232
     return (dispatch: Dispatch<*>, getState: Function) => {
236 233
         const state = getState();
237
-        const { alwaysVisible, timeoutMS, visible } = state['features/toolbox'];
234
+        const {
235
+            alwaysVisible,
236
+            enabled,
237
+            timeoutMS,
238
+            visible
239
+        } = state['features/toolbox'];
238 240
 
239
-        if (!visible) {
241
+        if (enabled && !visible) {
240 242
             dispatch(setToolboxVisible(true));
241 243
             dispatch(setSubjectSlideIn(true));
242 244
 

+ 22
- 7
react/features/toolbox/reducer.js Bestand weergeven

@@ -5,11 +5,12 @@ import { ReducerRegistry } from '../base/redux';
5 5
 import {
6 6
     CLEAR_TOOLBOX_TIMEOUT,
7 7
     SET_DEFAULT_TOOLBOX_BUTTONS,
8
-    SET_TOOLBOX_ALWAYS_VISIBLE,
9 8
     SET_SUBJECT,
10 9
     SET_SUBJECT_SLIDE_IN,
11 10
     SET_TOOLBAR_BUTTON,
12 11
     SET_TOOLBAR_HOVERED,
12
+    SET_TOOLBOX_ALWAYS_VISIBLE,
13
+    SET_TOOLBOX_ENABLED,
13 14
     SET_TOOLBOX_TIMEOUT,
14 15
     SET_TOOLBOX_TIMEOUT_MS,
15 16
     SET_TOOLBOX_VISIBLE
@@ -51,6 +52,14 @@ function _getInitialState() {
51 52
          */
52 53
         alwaysVisible: false,
53 54
 
55
+        /**
56
+         * The indicator which determines whether the Toolbox is enabled. For
57
+         * example, modules/UI/recording/Recording.js disables the Toolbox.
58
+         *
59
+         * @type {boolean}
60
+         */
61
+        enabled: true,
62
+
54 63
         /**
55 64
          * The indicator which determines whether a Toolbar in the Toolbox is
56 65
          * hovered.
@@ -132,12 +141,6 @@ ReducerRegistry.register(
132 141
             };
133 142
         }
134 143
 
135
-        case SET_TOOLBOX_ALWAYS_VISIBLE:
136
-            return {
137
-                ...state,
138
-                alwaysVisible: action.alwaysVisible
139
-            };
140
-
141 144
         case SET_SUBJECT:
142 145
             return {
143 146
                 ...state,
@@ -159,6 +162,18 @@ ReducerRegistry.register(
159 162
                 hovered: action.hovered
160 163
             };
161 164
 
165
+        case SET_TOOLBOX_ALWAYS_VISIBLE:
166
+            return {
167
+                ...state,
168
+                alwaysVisible: action.alwaysVisible
169
+            };
170
+
171
+        case SET_TOOLBOX_ENABLED:
172
+            return {
173
+                ...state,
174
+                enabled: action.enabled
175
+            };
176
+
162 177
         case SET_TOOLBOX_TIMEOUT:
163 178
             return {
164 179
                 ...state,

Laden…
Annuleren
Opslaan