浏览代码

feat(alwaysontop): Toolbar.

j8
hristoterezov 8 年前
父节点
当前提交
1782030936

+ 38
- 11
conference.js 查看文件

727
                 // so that the user can try unmute later on and add audio/video
727
                 // so that the user can try unmute later on and add audio/video
728
                 // to the conference
728
                 // to the conference
729
                 if (!tracks.find((t) => t.isAudioTrack())) {
729
                 if (!tracks.find((t) => t.isAudioTrack())) {
730
-                    this.audioMuted = true;
730
+                    this.setAudioMuteStatus(true);
731
                     APP.UI.setAudioMuted(this.getMyUserId(), this.audioMuted);
731
                     APP.UI.setAudioMuted(this.getMyUserId(), this.audioMuted);
732
                 }
732
                 }
733
 
733
 
734
                 if (!tracks.find((t) => t.isVideoTrack())) {
734
                 if (!tracks.find((t) => t.isVideoTrack())) {
735
-                    this.videoMuted = true;
735
+                    this.setVideoMuteStatus(true);
736
                     APP.UI.setVideoMuted(this.getMyUserId(), this.videoMuted);
736
                     APP.UI.setVideoMuted(this.getMyUserId(), this.videoMuted);
737
                 }
737
                 }
738
 
738
 
765
     muteAudio(mute, showUI = true) {
765
     muteAudio(mute, showUI = true) {
766
         // Not ready to modify track's state yet
766
         // Not ready to modify track's state yet
767
         if (!this._localTracksInitialized) {
767
         if (!this._localTracksInitialized) {
768
-            this.audioMuted = mute;
768
+            this.setAudioMuteStatus(mute);
769
             return;
769
             return;
770
         } else if (localAudio && localAudio.isMuted() === mute) {
770
         } else if (localAudio && localAudio.isMuted() === mute) {
771
             // NO-OP
771
             // NO-OP
794
             muteLocalAudio(mute)
794
             muteLocalAudio(mute)
795
                 .catch(error => {
795
                 .catch(error => {
796
                     maybeShowErrorDialog(error);
796
                     maybeShowErrorDialog(error);
797
-                    this.audioMuted = oldMutedStatus;
797
+                    this.setAudioMuteStatus(oldMutedStatus);
798
                     APP.UI.setAudioMuted(this.getMyUserId(), this.audioMuted);
798
                     APP.UI.setAudioMuted(this.getMyUserId(), this.audioMuted);
799
                 });
799
                 });
800
         }
800
         }
824
     muteVideo(mute, showUI = true) {
824
     muteVideo(mute, showUI = true) {
825
         // Not ready to modify track's state yet
825
         // Not ready to modify track's state yet
826
         if (!this._localTracksInitialized) {
826
         if (!this._localTracksInitialized) {
827
-            this.videoMuted = mute;
827
+            this.setVideoMuteStatus(mute);
828
 
828
 
829
             return;
829
             return;
830
         } else if (localVideo && localVideo.isMuted() === mute) {
830
         } else if (localVideo && localVideo.isMuted() === mute) {
863
             muteLocalVideo(mute)
863
             muteLocalVideo(mute)
864
                 .catch(error => {
864
                 .catch(error => {
865
                     maybeShowErrorDialog(error);
865
                     maybeShowErrorDialog(error);
866
-                    this.videoMuted = oldMutedStatus;
866
+                    this.setVideoMuteStatus(oldMutedStatus);
867
                     APP.UI.setVideoMuted(this.getMyUserId(), this.videoMuted);
867
                     APP.UI.setVideoMuted(this.getMyUserId(), this.videoMuted);
868
                 });
868
                 });
869
         }
869
         }
1220
             .then(() => {
1220
             .then(() => {
1221
                 localVideo = newStream;
1221
                 localVideo = newStream;
1222
                 if (newStream) {
1222
                 if (newStream) {
1223
-                    this.videoMuted = newStream.isMuted();
1223
+                    this.setVideoMuteStatus(newStream.isMuted());
1224
                     this.isSharingScreen = newStream.videoType === 'desktop';
1224
                     this.isSharingScreen = newStream.videoType === 'desktop';
1225
 
1225
 
1226
                     APP.UI.addLocalStream(newStream);
1226
                     APP.UI.addLocalStream(newStream);
1227
                 } else {
1227
                 } else {
1228
                     // No video is treated the same way as being video muted
1228
                     // No video is treated the same way as being video muted
1229
-                    this.videoMuted = true;
1229
+                    this.setVideoMuteStatus(true);
1230
                     this.isSharingScreen = false;
1230
                     this.isSharingScreen = false;
1231
                 }
1231
                 }
1232
                 APP.UI.setVideoMuted(this.getMyUserId(), this.videoMuted);
1232
                 APP.UI.setVideoMuted(this.getMyUserId(), this.videoMuted);
1245
             replaceLocalTrack(localAudio, newStream, room))
1245
             replaceLocalTrack(localAudio, newStream, room))
1246
             .then(() => {
1246
             .then(() => {
1247
                 localAudio = newStream;
1247
                 localAudio = newStream;
1248
+
1248
                 if (newStream) {
1249
                 if (newStream) {
1249
-                    this.audioMuted = newStream.isMuted();
1250
+                    this.setAudioMuteStatus(newStream.isMuted());
1250
                     APP.UI.addLocalStream(newStream);
1251
                     APP.UI.addLocalStream(newStream);
1251
                 } else {
1252
                 } else {
1252
                     // No audio is treated the same way as being audio muted
1253
                     // No audio is treated the same way as being audio muted
1253
-                    this.audioMuted = true;
1254
+                    this.setAudioMuteStatus(true);
1254
                 }
1255
                 }
1255
                 APP.UI.setAudioMuted(this.getMyUserId(), this.audioMuted);
1256
                 APP.UI.setAudioMuted(this.getMyUserId(), this.audioMuted);
1256
             });
1257
             });
2310
             'device count: ' + audioDeviceCount);
2311
             'device count: ' + audioDeviceCount);
2311
 
2312
 
2312
         APP.store.dispatch(setAudioAvailable(available));
2313
         APP.store.dispatch(setAudioAvailable(available));
2314
+        APP.API.notifyAudioAvailabilityChanged(available);
2313
     },
2315
     },
2314
 
2316
 
2315
     /**
2317
     /**
2334
             'device count: ' + videoDeviceCount);
2336
             'device count: ' + videoDeviceCount);
2335
 
2337
 
2336
         APP.store.dispatch(setVideoAvailable(available));
2338
         APP.store.dispatch(setVideoAvailable(available));
2339
+        APP.API.notifyVideoAvailabilityChanged(available);
2337
     },
2340
     },
2338
 
2341
 
2339
     /**
2342
     /**
2541
      */
2544
      */
2542
     getDesktopSharingSourceType() {
2545
     getDesktopSharingSourceType() {
2543
         return localVideo.sourceType;
2546
         return localVideo.sourceType;
2544
-    }
2547
+    },
2548
+
2549
+    /**
2550
+     * Sets the video muted status.
2551
+     *
2552
+     * @param {boolean} muted - New muted status.
2553
+     */
2554
+    setVideoMuteStatus(muted) {
2555
+        if (this.videoMuted !== muted) {
2556
+            this.videoMuted = muted;
2557
+            APP.API.notifyVideoMutedStatusChanged(muted);
2558
+        }
2559
+    },
2560
+
2561
+    /**
2562
+     * Sets the audio muted status.
2563
+     *
2564
+     * @param {boolean} muted - New muted status.
2565
+     */
2566
+    setAudioMuteStatus(muted) {
2567
+        if (this.audioMuted !== muted) {
2568
+            this.audioMuted = muted;
2569
+            APP.API.notifyAudioMutedStatusChanged(muted);
2570
+        }
2571
+    },
2545
 };
2572
 };

+ 57
- 1
doc/api.md 查看文件

25
     * **parentNode**: (optional) HTML DOM Element where the iframe will be added as a child.
25
     * **parentNode**: (optional) HTML DOM Element where the iframe will be added as a child.
26
     * **configOverwrite**: (optional) JS object with overrides for options defined in [config.js].
26
     * **configOverwrite**: (optional) JS object with overrides for options defined in [config.js].
27
     * **interfaceConfigOverwrite**: (optional) JS object with overrides for options defined in [interface_config.js].
27
     * **interfaceConfigOverwrite**: (optional) JS object with overrides for options defined in [interface_config.js].
28
-    * **noSsl**: (optional, defaults to true) Boolean indicating if the server should be contacted using HTTP or HTTPS.
28
+    * **noSSL**: (optional, defaults to true) Boolean indicating if the server should be contacted using HTTP or HTTPS.
29
     * **jwt**: (optional) [JWT](https://jwt.io/) token.
29
     * **jwt**: (optional) [JWT](https://jwt.io/) token.
30
 
30
 
31
 Example:
31
 Example:
141
 
141
 
142
 The following events are currently supported:
142
 The following events are currently supported:
143
 
143
 
144
+* **audioAvailabilityChanged** - event notifications about audio availability status changes. The listener will receive an object with the following structure:
145
+```javascript
146
+{
147
+"available": available   // new available status - boolean
148
+}
149
+```
150
+
151
+* **audioMuteStatusChanged** - event notifications about audio mute status changes. The listener will receive an object with the following structure:
152
+```javascript
153
+{
154
+"muted": muted   // new muted status - boolean
155
+}
156
+```
157
+
144
 * **incomingMessage** - Event notifications about incoming
158
 * **incomingMessage** - Event notifications about incoming
145
 messages. The listener will receive an object with the following structure:
159
 messages. The listener will receive an object with the following structure:
146
 ```javascript
160
 ```javascript
196
 }
210
 }
197
 ```
211
 ```
198
 
212
 
213
+* **videoAvailabilityChanged** - event notifications about video availability status changes. The listener will receive an object with the following structure:
214
+```javascript
215
+{
216
+"available": available   // new available status - boolean
217
+}
218
+```
219
+
220
+* **videoMuteStatusChanged** - event notifications about video mute status changes. The listener will receive an object with the following structure:
221
+```javascript
222
+{
223
+"muted": muted   // new muted status - boolean
224
+}
225
+```
226
+
199
 * **readyToClose** - event notification fired when Jitsi Meet is ready to be closed (hangup operations are completed).
227
 * **readyToClose** - event notification fired when Jitsi Meet is ready to be closed (hangup operations are completed).
200
 
228
 
201
 You can also add multiple event listeners by using `addEventListeners`.
229
 You can also add multiple event listeners by using `addEventListeners`.
241
 var iframe = api.getIFrame();
269
 var iframe = api.getIFrame();
242
 ```
270
 ```
243
 
271
 
272
+You can check whether the audio is muted with the following API function:
273
+```javascript
274
+isAudioMuted().then(function(muted) {
275
+    ...
276
+});
277
+```
278
+
279
+You can check whether the video is muted with the following API function:
280
+```javascript
281
+isVideoMuted().then(function(muted) {
282
+    ...
283
+});
284
+```
285
+
286
+You can check whether the audio is available with the following API function:
287
+```javascript
288
+isAudioAvailable().then(function(available) {
289
+    ...
290
+});
291
+```
292
+
293
+You can check whether the video is available with the following API function:
294
+```javascript
295
+isVideoAvailable().then(function(available) {
296
+    ...
297
+});
298
+```
299
+
244
 You can remove the embedded Jitsi Meet Conference with the following API function:
300
 You can remove the embedded Jitsi Meet Conference with the following API function:
245
 ```javascript
301
 ```javascript
246
 api.dispose()
302
 api.dispose()

+ 93
- 0
modules/API/API.js 查看文件

26
  */
26
  */
27
 const transport = getJitsiMeetTransport();
27
 const transport = getJitsiMeetTransport();
28
 
28
 
29
+/**
30
+ * The current audio availability.
31
+ *
32
+ * @type {boolean}
33
+ */
34
+let audioAvailable = true;
35
+
36
+/**
37
+ * The current video availability.
38
+ *
39
+ * @type {boolean}
40
+ */
41
+let videoAvailable = true;
42
+
29
 /**
43
 /**
30
  * Initializes supported commands.
44
  * Initializes supported commands.
31
  *
45
  *
58
 
72
 
59
         return false;
73
         return false;
60
     });
74
     });
75
+    transport.on('request', ({ data, name }, callback) => {
76
+        switch (name) {
77
+        case 'is-audio-muted':
78
+            callback(APP.conference.audioMuted);
79
+            break;
80
+        case 'is-video-muted':
81
+            callback(APP.conference.videoMuted);
82
+            break;
83
+        case 'is-audio-available':
84
+            callback(audioAvailable);
85
+            break;
86
+        case 'is-video-available':
87
+            callback(videoAvailable);
88
+            break;
89
+        default:
90
+            return false;
91
+        }
92
+
93
+        return true;
94
+    });
61
 }
95
 }
62
 
96
 
63
 /**
97
 /**
265
         this._sendEvent({ name: 'video-ready-to-close' });
299
         this._sendEvent({ name: 'video-ready-to-close' });
266
     }
300
     }
267
 
301
 
302
+    /**
303
+     * Notify external application (if API is enabled) for audio muted status
304
+     * changed.
305
+     *
306
+     * @param {boolean} muted - The new muted status.
307
+     * @returns {void}
308
+     */
309
+    notifyAudioMutedStatusChanged(muted) {
310
+        this._sendEvent({
311
+            name: 'audio-mute-status-changed',
312
+            muted
313
+        });
314
+    }
315
+
316
+    /**
317
+     * Notify external application (if API is enabled) for video muted status
318
+     * changed.
319
+     *
320
+     * @param {boolean} muted - The new muted status.
321
+     * @returns {void}
322
+     */
323
+    notifyVideoMutedStatusChanged(muted) {
324
+        this._sendEvent({
325
+            name: 'video-mute-status-changed',
326
+            muted
327
+        });
328
+    }
329
+
330
+    /**
331
+     * Notify external application (if API is enabled) for audio availability
332
+     * changed.
333
+     *
334
+     * @param {boolean} available - True if available and false otherwise.
335
+     * @returns {void}
336
+     */
337
+    notifyAudioAvailabilityChanged(available) {
338
+        audioAvailable = available;
339
+        this._sendEvent({
340
+            name: 'audio-availability-changed',
341
+            available
342
+        });
343
+    }
344
+
345
+    /**
346
+     * Notify external application (if API is enabled) for video available
347
+     * status changed.
348
+     *
349
+     * @param {boolean} available - True if available and false otherwise.
350
+     * @returns {void}
351
+     */
352
+    notifyVideoAvailabilityChanged(available) {
353
+        videoAvailable = available;
354
+        this._sendEvent({
355
+            name: 'video-availability-changed',
356
+            available
357
+        });
358
+    }
359
+
360
+
268
     /**
361
     /**
269
      * Disposes the allocated resources.
362
      * Disposes the allocated resources.
270
      *
363
      *

+ 55
- 5
modules/API/external/external_api.js 查看文件

9
 const logger = require('jitsi-meet-logger').getLogger(__filename);
9
 const logger = require('jitsi-meet-logger').getLogger(__filename);
10
 
10
 
11
 const ALWAYS_ON_TOP_FILENAMES = [
11
 const ALWAYS_ON_TOP_FILENAMES = [
12
-    'css/alwaysontop.css', 'libs/alwaysontop.bundle.min.js'
12
+    'css/all.css', 'libs/alwaysontop.min.js'
13
 ];
13
 ];
14
 
14
 
15
 /**
15
 /**
34
  * events expected by jitsi-meet
34
  * events expected by jitsi-meet
35
  */
35
  */
36
 const events = {
36
 const events = {
37
+    'audio-availability-changed': 'audioAvailabilityChanged',
38
+    'audio-mute-status-changed': 'audioMuteStatusChanged',
37
     'display-name-change': 'displayNameChange',
39
     'display-name-change': 'displayNameChange',
38
     'incoming-message': 'incomingMessage',
40
     'incoming-message': 'incomingMessage',
39
     'outgoing-message': 'outgoingMessage',
41
     'outgoing-message': 'outgoingMessage',
41
     'participant-left': 'participantLeft',
43
     'participant-left': 'participantLeft',
42
     'video-ready-to-close': 'readyToClose',
44
     'video-ready-to-close': 'readyToClose',
43
     'video-conference-joined': 'videoConferenceJoined',
45
     'video-conference-joined': 'videoConferenceJoined',
44
-    'video-conference-left': 'videoConferenceLeft'
46
+    'video-conference-left': 'videoConferenceLeft',
47
+    'video-availability-changed': 'videoAvailabilityChanged',
48
+    'video-mute-status-changed': 'videoMuteStatusChanged'
45
 };
49
 };
46
 
50
 
47
 /**
51
 /**
211
             noSSL,
215
             noSSL,
212
             roomName
216
             roomName
213
         });
217
         });
214
-        this._baseUrl = generateURL(domain, {
215
-            noSSL
216
-        });
218
+        this._baseUrl = `${noSSL ? 'http' : 'https'}://${domain}/`;
217
         this._createIFrame(height, width);
219
         this._createIFrame(height, width);
218
         this._transport = new Transport({
220
         this._transport = new Transport({
219
             backend: new PostMessageTransportBackend({
221
             backend: new PostMessageTransportBackend({
448
         }
450
         }
449
     }
451
     }
450
 
452
 
453
+    /**
454
+     * Check if the audio is available.
455
+     *
456
+     * @returns {Promise} - Resolves with true if the audio available, with
457
+     * false if not and rejects on failure.
458
+     */
459
+    isAudioAvailable() {
460
+        return this._transport.sendRequest({
461
+            name: 'is-audio-available'
462
+        });
463
+    }
464
+
465
+    /**
466
+     * Returns the audio mute status.
467
+     *
468
+     * @returns {Promise} - Resolves with the audio mute status and rejects on
469
+     * failure.
470
+     */
471
+    isAudioMuted() {
472
+        return this._transport.sendRequest({
473
+            name: 'is-audio-muted'
474
+        });
475
+    }
476
+
451
     /**
477
     /**
452
      * Returns the iframe that loads Jitsi Meet.
478
      * Returns the iframe that loads Jitsi Meet.
453
      *
479
      *
467
         return this._numberOfParticipants;
493
         return this._numberOfParticipants;
468
     }
494
     }
469
 
495
 
496
+    /**
497
+     * Check if the video is available.
498
+     *
499
+     * @returns {Promise} - Resolves with true if the video available, with
500
+     * false if not and rejects on failure.
501
+     */
502
+    isVideoAvailable() {
503
+        return this._transport.sendRequest({
504
+            name: 'is-video-available'
505
+        });
506
+    }
507
+
508
+    /**
509
+     * Returns the audio mute status.
510
+     *
511
+     * @returns {Promise} - Resolves with the audio mute status and rejects on
512
+     * failure.
513
+     */
514
+    isVideoMuted() {
515
+        return this._transport.sendRequest({
516
+            name: 'is-video-muted'
517
+        });
518
+    }
519
+
470
     /**
520
     /**
471
      * Removes event listener.
521
      * Removes event listener.
472
      *
522
      *

+ 297
- 0
react/features/always-on-top/AlwaysOnTop.js 查看文件

1
+import React, { Component } from 'react';
2
+
3
+import StatelessToolbar from '../toolbox/components/StatelessToolbar';
4
+import StatelessToolbarButton
5
+    from '../toolbox/components/StatelessToolbarButton';
6
+
7
+const { api } = window.alwaysOnTop;
8
+
9
+/**
10
+ * The timeout in ms for hidding the toolbar.
11
+ */
12
+const TOOLBAR_TIMEOUT = 4000;
13
+
14
+/**
15
+ * Map with toolbar button descriptors.
16
+ */
17
+const toolbarButtons = {
18
+    /**
19
+     * The descriptor of the camera toolbar button.
20
+     */
21
+    camera: {
22
+        classNames: [ 'button', 'icon-camera' ],
23
+        enabled: true,
24
+        id: 'toolbar_button_camera',
25
+        onClick() {
26
+            api.executeCommand('toggleVideo');
27
+        }
28
+    },
29
+
30
+    /**
31
+     * The descriptor of the toolbar button which hangs up the call/conference.
32
+     */
33
+    hangup: {
34
+        classNames: [ 'button', 'icon-hangup', 'button_hangup' ],
35
+        enabled: true,
36
+        id: 'toolbar_button_hangup',
37
+        onClick() {
38
+            api.executeCommand('hangup');
39
+            window.close();
40
+        }
41
+    },
42
+
43
+    /**
44
+     * The descriptor of the microphone toolbar button.
45
+     */
46
+    microphone: {
47
+        classNames: [ 'button', 'icon-microphone' ],
48
+        enabled: true,
49
+        id: 'toolbar_button_mute',
50
+        onClick() {
51
+            api.executeCommand('toggleAudio');
52
+        }
53
+    }
54
+};
55
+
56
+/**
57
+ * Represents the always on top page.
58
+ *
59
+ * @class AlwaysOnTop
60
+ * @extends Component
61
+ */
62
+export default class AlwaysOnTop extends Component {
63
+    /**
64
+     * Initializes new AlwaysOnTop instance.
65
+     *
66
+     * @param {Object} props - The read-only properties with which the new
67
+     * instance is to be initialized.
68
+     */
69
+    constructor(props) {
70
+        super(props);
71
+
72
+        this.state = {
73
+            visible: true,
74
+            audioMuted: false,
75
+            videoMuted: false,
76
+            audioAvailable: false,
77
+            videoAvailable: false
78
+        };
79
+
80
+        this._hovered = false;
81
+
82
+        this._audioAvailabilityListener
83
+            = this._audioAvailabilityListener.bind(this);
84
+        this._audioMutedListener = this._audioMutedListener.bind(this);
85
+        this._mouseMove = this._mouseMove.bind(this);
86
+        this._onMouseOver = this._onMouseOver.bind(this);
87
+        this._onMouseOut = this._onMouseOut.bind(this);
88
+        this._videoAvailabilityListener
89
+            = this._videoAvailabilityListener.bind(this);
90
+        this._videoMutedListener = this._videoMutedListener.bind(this);
91
+    }
92
+
93
+    /**
94
+     * Handles audio available api events.
95
+     *
96
+     * @param {{ available: boolean }} status - The new available status.
97
+     * @returns {void}
98
+     */
99
+    _audioAvailabilityListener({ available }) {
100
+        this.setState({ audioAvailable: available });
101
+    }
102
+
103
+    /**
104
+     * Handles audio muted api events.
105
+     *
106
+     * @param {{ muted: boolean }} status - The new muted status.
107
+     * @returns {void}
108
+     */
109
+    _audioMutedListener({ muted }) {
110
+        this.setState({ audioMuted: muted });
111
+    }
112
+
113
+    /**
114
+     * Hides the toolbar after a timeout.
115
+     *
116
+     * @returns {void}
117
+     */
118
+    _hideToolbarAfterTimeout() {
119
+        setTimeout(() => {
120
+            if (this._hovered) {
121
+                this._hideToolbarAfterTimeout();
122
+
123
+                return;
124
+            }
125
+            this.setState({ visible: false });
126
+        }, TOOLBAR_TIMEOUT);
127
+    }
128
+
129
+    /**
130
+     * Handles mouse move events.
131
+     *
132
+     * @returns {void}
133
+     */
134
+    _mouseMove() {
135
+        if (!this.state.visible) {
136
+            this.setState({ visible: true });
137
+        }
138
+    }
139
+
140
+    /**
141
+     * Toolbar mouse over handler.
142
+     *
143
+     * @returns {void}
144
+     */
145
+    _onMouseOver() {
146
+        this._hovered = true;
147
+    }
148
+
149
+    /**
150
+     * Toolbar mouse out handler.
151
+     *
152
+     * @returns {void}
153
+     */
154
+    _onMouseOut() {
155
+        this._hovered = false;
156
+    }
157
+
158
+    /**
159
+     * Handles audio available api events.
160
+     *
161
+     * @param {{ available: boolean }} status - The new available status.
162
+     * @returns {void}
163
+     */
164
+    _videoAvailabilityListener({ available }) {
165
+        this.setState({ videoAvailable: available });
166
+    }
167
+
168
+    /**
169
+     * Handles video muted api events.
170
+     *
171
+     * @param {{ muted: boolean }} status - The new muted status.
172
+     * @returns {void}
173
+     */
174
+    _videoMutedListener({ muted }) {
175
+        this.setState({ videoMuted: muted });
176
+    }
177
+
178
+    /**
179
+     * Sets mouse move listener and initial toolbar timeout.
180
+     *
181
+     * @inheritdoc
182
+     * @returns {void}
183
+     */
184
+    componentDidMount() {
185
+        api.on('audioMuteStatusChanged', this._audioMutedListener);
186
+        api.on('videoMuteStatusChanged', this._videoMutedListener);
187
+        api.on('audioAvailabilityChanged', this._audioAvailabilityListener);
188
+        api.on('videoAvailabilityChanged', this._videoAvailabilityListener);
189
+
190
+        Promise.all([
191
+            api.isAudioMuted(),
192
+            api.isVideoMuted(),
193
+            api.isAudioAvailable(),
194
+            api.isVideoAvailable()
195
+        ])
196
+        .then(([
197
+            audioMuted = false,
198
+            videoMuted = false,
199
+            audioAvailable = false,
200
+            videoAvailable = false
201
+        ]) =>
202
+            this.setState({
203
+                audioMuted,
204
+                videoMuted,
205
+                audioAvailable,
206
+                videoAvailable
207
+            })
208
+        )
209
+        .catch(console.error);
210
+
211
+        window.addEventListener('mousemove', this._mouseMove);
212
+
213
+        this._hideToolbarAfterTimeout();
214
+    }
215
+
216
+    /**
217
+     * Removes all listeners.
218
+     *
219
+     * @inheritdoc
220
+     * @returns {void}
221
+     */
222
+    componentWillUnmount() {
223
+        api.removeListener('audioMuteStatusChanged',
224
+            this._audioMutedListener);
225
+        api.removeListener('videoMuteStatusChanged',
226
+            this._videoMutedListener);
227
+        api.removeListener('audioAvailabilityChanged',
228
+            this._audioAvailabilityListener);
229
+        api.removeListener('videoAvailabilityChanged',
230
+            this._videoAvailabilityListener);
231
+        window.removeEventListener('mousemove', this._mouseMove);
232
+    }
233
+
234
+    /**
235
+     * Sets a timeout to hide the toolbar when the toolbar is shown.
236
+     *
237
+     * @inheritdoc
238
+     * @returns {void}
239
+     */
240
+    componentWillUpdate(nextProps, nextState) {
241
+        if (!this.state.visible && nextState.visible) {
242
+            this._hideToolbarAfterTimeout();
243
+        }
244
+    }
245
+
246
+    /**
247
+     * Implements React's {@link Component#render()}.
248
+     *
249
+     * @inheritdoc
250
+     * @returns {ReactElement}
251
+     */
252
+    render() {
253
+        const className
254
+            = `toolbar_primary ${this.state.visible ? 'fadeIn' : 'fadeOut'}`;
255
+
256
+        return (
257
+            <StatelessToolbar
258
+                className = { className }
259
+                onMouseOut = { this._onMouseOut }
260
+                onMouseOver = { this._onMouseOver }>
261
+                {
262
+                    Object.entries(toolbarButtons).map(([ key, button ]) => {
263
+                        const { onClick } = button;
264
+                        let enabled = false, toggled = false;
265
+
266
+                        switch (key) {
267
+                        case 'microphone':
268
+                            enabled = this.state.audioAvailable;
269
+                            toggled = enabled ? this.state.audioMuted : true;
270
+                            break;
271
+                        case 'camera':
272
+                            enabled = this.state.videoAvailable;
273
+                            toggled = enabled ? this.state.videoMuted : true;
274
+                            break;
275
+                        default: // hangup button
276
+                            toggled = false;
277
+                            enabled = true;
278
+                        }
279
+
280
+                        const updatedButton = {
281
+                            ...button,
282
+                            enabled,
283
+                            toggled
284
+                        };
285
+
286
+                        return (
287
+                            <StatelessToolbarButton
288
+                                button = { updatedButton }
289
+                                key = { key }
290
+                                onClick = { onClick } />
291
+                        );
292
+                    })
293
+                }
294
+            </StatelessToolbar>
295
+        );
296
+    }
297
+}

+ 10
- 0
react/features/always-on-top/index.js 查看文件

1
+import React from 'react';
2
+import ReactDOM from 'react-dom';
3
+
4
+import AlwaysOnTop from './AlwaysOnTop';
5
+
6
+// Render the main/root Component.
7
+ReactDOM.render(
8
+    <AlwaysOnTop />,
9
+    document.getElementById('react')
10
+);

+ 2
- 2
react/features/base/tracks/middleware.js 查看文件

110
 
110
 
111
             if (jitsiTrack.isLocal()) {
111
             if (jitsiTrack.isLocal()) {
112
                 if (isVideoTrack) {
112
                 if (isVideoTrack) {
113
-                    APP.conference.videoMuted = muted;
113
+                    APP.conference.setVideoMuteStatus(muted);
114
                 } else {
114
                 } else {
115
-                    APP.conference.audioMuted = muted;
115
+                    APP.conference.setAudioMuteStatus(muted);
116
                 }
116
                 }
117
             }
117
             }
118
 
118
 

+ 2
- 3
react/features/toolbox/components/StatelessToolbar.web.js 查看文件

4
 
4
 
5
 /**
5
 /**
6
  * Implements a toolbar in React/Web. It is a strip that contains a set of
6
  * Implements a toolbar in React/Web. It is a strip that contains a set of
7
- * toolbar items such as buttons. Toolbar is commonly placed inside of a
8
- * Toolbox.
7
+ * toolbar items such as buttons.
9
  *
8
  *
10
- * @class Toolbar
9
+ * @class StatelessToolbar
11
  * @extends Component
10
  * @extends Component
12
  */
11
  */
13
 export default class StatelessToolbar extends Component {
12
 export default class StatelessToolbar extends Component {

+ 5
- 18
react/features/toolbox/components/Toolbar.web.js 查看文件

76
      * @returns {ReactElement}
76
      * @returns {ReactElement}
77
      */
77
      */
78
     render(): ReactElement<*> {
78
     render(): ReactElement<*> {
79
-        const toolbarButtons = new Map();
80
-
81
-        this.props.toolbarButtons
82
-            .forEach((button, key) => {
83
-                const { onClick } = button;
84
-
85
-                toolbarButtons.set(key, {
86
-                    ...button,
87
-                    onClick: (...args) =>
88
-                        onClick && onClick(this.props.dispatch, ...args)
89
-                });
90
-            });
91
-
92
         const props = {
79
         const props = {
93
-            ...this.props,
80
+            className: this.props.className,
94
             onMouseOut: this._onMouseOut,
81
             onMouseOut: this._onMouseOut,
95
-            onMouseOver: this._onMouseOver,
96
-            toolbarButtons
82
+            onMouseOver: this._onMouseOver
97
         };
83
         };
98
 
84
 
99
         return (
85
         return (
150
         }
136
         }
151
 
137
 
152
         const { tooltipPosition } = this.props;
138
         const { tooltipPosition } = this.props;
153
-
154
         const { onClick, onMount, onUnmount } = button;
139
         const { onClick, onMount, onUnmount } = button;
140
+        const onClickWithDispatch = (...args) =>
141
+            onClick && onClick(this.props.dispatch, ...args);
155
 
142
 
156
         return (
143
         return (
157
             <ToolbarButton
144
             <ToolbarButton
158
                 button = { button }
145
                 button = { button }
159
                 key = { key }
146
                 key = { key }
160
-                onClick = { onClick }
147
+                onClick = { onClickWithDispatch }
161
                 onMount = { onMount }
148
                 onMount = { onMount }
162
                 onUnmount = { onUnmount }
149
                 onUnmount = { onUnmount }
163
                 tooltipPosition = { tooltipPosition } />
150
                 tooltipPosition = { tooltipPosition } />

+ 3
- 0
webpack.config.js 查看文件

176
             'device_selection_popup_bundle':
176
             'device_selection_popup_bundle':
177
                 './react/features/device-selection/popup.js',
177
                 './react/features/device-selection/popup.js',
178
 
178
 
179
+            'alwaysontop':
180
+                './react/features/always-on-top/index.js',
181
+
179
             'do_external_connect':
182
             'do_external_connect':
180
                 './connection_optimization/do_external_connect.js'
183
                 './connection_optimization/do_external_connect.js'
181
         }
184
         }

正在加载...
取消
保存