浏览代码

feat(alwaysontop): Toolbar.

master
hristoterezov 7 年前
父节点
当前提交
1782030936

+ 38
- 11
conference.js 查看文件

@@ -727,12 +727,12 @@ export default {
727 727
                 // so that the user can try unmute later on and add audio/video
728 728
                 // to the conference
729 729
                 if (!tracks.find((t) => t.isAudioTrack())) {
730
-                    this.audioMuted = true;
730
+                    this.setAudioMuteStatus(true);
731 731
                     APP.UI.setAudioMuted(this.getMyUserId(), this.audioMuted);
732 732
                 }
733 733
 
734 734
                 if (!tracks.find((t) => t.isVideoTrack())) {
735
-                    this.videoMuted = true;
735
+                    this.setVideoMuteStatus(true);
736 736
                     APP.UI.setVideoMuted(this.getMyUserId(), this.videoMuted);
737 737
                 }
738 738
 
@@ -765,7 +765,7 @@ export default {
765 765
     muteAudio(mute, showUI = true) {
766 766
         // Not ready to modify track's state yet
767 767
         if (!this._localTracksInitialized) {
768
-            this.audioMuted = mute;
768
+            this.setAudioMuteStatus(mute);
769 769
             return;
770 770
         } else if (localAudio && localAudio.isMuted() === mute) {
771 771
             // NO-OP
@@ -794,7 +794,7 @@ export default {
794 794
             muteLocalAudio(mute)
795 795
                 .catch(error => {
796 796
                     maybeShowErrorDialog(error);
797
-                    this.audioMuted = oldMutedStatus;
797
+                    this.setAudioMuteStatus(oldMutedStatus);
798 798
                     APP.UI.setAudioMuted(this.getMyUserId(), this.audioMuted);
799 799
                 });
800 800
         }
@@ -824,7 +824,7 @@ export default {
824 824
     muteVideo(mute, showUI = true) {
825 825
         // Not ready to modify track's state yet
826 826
         if (!this._localTracksInitialized) {
827
-            this.videoMuted = mute;
827
+            this.setVideoMuteStatus(mute);
828 828
 
829 829
             return;
830 830
         } else if (localVideo && localVideo.isMuted() === mute) {
@@ -863,7 +863,7 @@ export default {
863 863
             muteLocalVideo(mute)
864 864
                 .catch(error => {
865 865
                     maybeShowErrorDialog(error);
866
-                    this.videoMuted = oldMutedStatus;
866
+                    this.setVideoMuteStatus(oldMutedStatus);
867 867
                     APP.UI.setVideoMuted(this.getMyUserId(), this.videoMuted);
868 868
                 });
869 869
         }
@@ -1220,13 +1220,13 @@ export default {
1220 1220
             .then(() => {
1221 1221
                 localVideo = newStream;
1222 1222
                 if (newStream) {
1223
-                    this.videoMuted = newStream.isMuted();
1223
+                    this.setVideoMuteStatus(newStream.isMuted());
1224 1224
                     this.isSharingScreen = newStream.videoType === 'desktop';
1225 1225
 
1226 1226
                     APP.UI.addLocalStream(newStream);
1227 1227
                 } else {
1228 1228
                     // No video is treated the same way as being video muted
1229
-                    this.videoMuted = true;
1229
+                    this.setVideoMuteStatus(true);
1230 1230
                     this.isSharingScreen = false;
1231 1231
                 }
1232 1232
                 APP.UI.setVideoMuted(this.getMyUserId(), this.videoMuted);
@@ -1245,12 +1245,13 @@ export default {
1245 1245
             replaceLocalTrack(localAudio, newStream, room))
1246 1246
             .then(() => {
1247 1247
                 localAudio = newStream;
1248
+
1248 1249
                 if (newStream) {
1249
-                    this.audioMuted = newStream.isMuted();
1250
+                    this.setAudioMuteStatus(newStream.isMuted());
1250 1251
                     APP.UI.addLocalStream(newStream);
1251 1252
                 } else {
1252 1253
                     // No audio is treated the same way as being audio muted
1253
-                    this.audioMuted = true;
1254
+                    this.setAudioMuteStatus(true);
1254 1255
                 }
1255 1256
                 APP.UI.setAudioMuted(this.getMyUserId(), this.audioMuted);
1256 1257
             });
@@ -2310,6 +2311,7 @@ export default {
2310 2311
             'device count: ' + audioDeviceCount);
2311 2312
 
2312 2313
         APP.store.dispatch(setAudioAvailable(available));
2314
+        APP.API.notifyAudioAvailabilityChanged(available);
2313 2315
     },
2314 2316
 
2315 2317
     /**
@@ -2334,6 +2336,7 @@ export default {
2334 2336
             'device count: ' + videoDeviceCount);
2335 2337
 
2336 2338
         APP.store.dispatch(setVideoAvailable(available));
2339
+        APP.API.notifyVideoAvailabilityChanged(available);
2337 2340
     },
2338 2341
 
2339 2342
     /**
@@ -2541,5 +2544,29 @@ export default {
2541 2544
      */
2542 2545
     getDesktopSharingSourceType() {
2543 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,7 +25,7 @@ Its constructor gets a number of options:
25 25
     * **parentNode**: (optional) HTML DOM Element where the iframe will be added as a child.
26 26
     * **configOverwrite**: (optional) JS object with overrides for options defined in [config.js].
27 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 29
     * **jwt**: (optional) [JWT](https://jwt.io/) token.
30 30
 
31 31
 Example:
@@ -141,6 +141,20 @@ The `listener` parameter is a Function object with one argument that will be not
141 141
 
142 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 158
 * **incomingMessage** - Event notifications about incoming
145 159
 messages. The listener will receive an object with the following structure:
146 160
 ```javascript
@@ -196,6 +210,20 @@ changes. The listener will receive an object with the following structure:
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 227
 * **readyToClose** - event notification fired when Jitsi Meet is ready to be closed (hangup operations are completed).
200 228
 
201 229
 You can also add multiple event listeners by using `addEventListeners`.
@@ -241,6 +269,34 @@ You can get the iframe HTML element where Jitsi Meet is loaded with the followin
241 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 300
 You can remove the embedded Jitsi Meet Conference with the following API function:
245 301
 ```javascript
246 302
 api.dispose()

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

@@ -26,6 +26,20 @@ let initialScreenSharingState = false;
26 26
  */
27 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 44
  * Initializes supported commands.
31 45
  *
@@ -58,6 +72,26 @@ function initCommands() {
58 72
 
59 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,6 +299,65 @@ class API {
265 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 362
      * Disposes the allocated resources.
270 363
      *

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

@@ -9,7 +9,7 @@ import {
9 9
 const logger = require('jitsi-meet-logger').getLogger(__filename);
10 10
 
11 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,6 +34,8 @@ const commands = {
34 34
  * events expected by jitsi-meet
35 35
  */
36 36
 const events = {
37
+    'audio-availability-changed': 'audioAvailabilityChanged',
38
+    'audio-mute-status-changed': 'audioMuteStatusChanged',
37 39
     'display-name-change': 'displayNameChange',
38 40
     'incoming-message': 'incomingMessage',
39 41
     'outgoing-message': 'outgoingMessage',
@@ -41,7 +43,9 @@ const events = {
41 43
     'participant-left': 'participantLeft',
42 44
     'video-ready-to-close': 'readyToClose',
43 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,9 +215,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
211 215
             noSSL,
212 216
             roomName
213 217
         });
214
-        this._baseUrl = generateURL(domain, {
215
-            noSSL
216
-        });
218
+        this._baseUrl = `${noSSL ? 'http' : 'https'}://${domain}/`;
217 219
         this._createIFrame(height, width);
218 220
         this._transport = new Transport({
219 221
             backend: new PostMessageTransportBackend({
@@ -448,6 +450,30 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
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 478
      * Returns the iframe that loads Jitsi Meet.
453 479
      *
@@ -467,6 +493,30 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
467 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 521
      * Removes event listener.
472 522
      *

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

@@ -0,0 +1,297 @@
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 查看文件

@@ -0,0 +1,10 @@
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,9 +110,9 @@ MiddlewareRegistry.register(store => next => action => {
110 110
 
111 111
             if (jitsiTrack.isLocal()) {
112 112
                 if (isVideoTrack) {
113
-                    APP.conference.videoMuted = muted;
113
+                    APP.conference.setVideoMuteStatus(muted);
114 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,10 +4,9 @@ import React, { Component } from 'react';
4 4
 
5 5
 /**
6 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 10
  * @extends Component
12 11
  */
13 12
 export default class StatelessToolbar extends Component {

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

@@ -76,24 +76,10 @@ class Toolbar extends Component {
76 76
      * @returns {ReactElement}
77 77
      */
78 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 79
         const props = {
93
-            ...this.props,
80
+            className: this.props.className,
94 81
             onMouseOut: this._onMouseOut,
95
-            onMouseOver: this._onMouseOver,
96
-            toolbarButtons
82
+            onMouseOver: this._onMouseOver
97 83
         };
98 84
 
99 85
         return (
@@ -150,14 +136,15 @@ class Toolbar extends Component {
150 136
         }
151 137
 
152 138
         const { tooltipPosition } = this.props;
153
-
154 139
         const { onClick, onMount, onUnmount } = button;
140
+        const onClickWithDispatch = (...args) =>
141
+            onClick && onClick(this.props.dispatch, ...args);
155 142
 
156 143
         return (
157 144
             <ToolbarButton
158 145
                 button = { button }
159 146
                 key = { key }
160
-                onClick = { onClick }
147
+                onClick = { onClickWithDispatch }
161 148
                 onMount = { onMount }
162 149
                 onUnmount = { onUnmount }
163 150
                 tooltipPosition = { tooltipPosition } />

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

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

正在加载...
取消
保存