瀏覽代碼

fix(prejoin): Store prejoin tracks in 'features/base/tracks'

master
Vlad Piersec 4 年之前
父節點
當前提交
4f169988a3

+ 1
- 28
conference.js 查看文件

118
 import { suspendDetected } from './react/features/power-monitor';
118
 import { suspendDetected } from './react/features/power-monitor';
119
 import {
119
 import {
120
     initPrejoin,
120
     initPrejoin,
121
-    isPrejoinPageEnabled,
122
-    isPrejoinPageVisible,
123
-    replacePrejoinAudioTrack,
124
-    replacePrejoinVideoTrack
121
+    isPrejoinPageEnabled
125
 } from './react/features/prejoin';
122
 } from './react/features/prejoin';
126
 import { createRnnoiseProcessorPromise } from './react/features/rnnoise';
123
 import { createRnnoiseProcessorPromise } from './react/features/rnnoise';
127
 import { toggleScreenshotCaptureEffect } from './react/features/screenshot-capture';
124
 import { toggleScreenshotCaptureEffect } from './react/features/screenshot-capture';
1409
     useVideoStream(newStream) {
1406
     useVideoStream(newStream) {
1410
         return new Promise((resolve, reject) => {
1407
         return new Promise((resolve, reject) => {
1411
             _replaceLocalVideoTrackQueue.enqueue(onFinish => {
1408
             _replaceLocalVideoTrackQueue.enqueue(onFinish => {
1412
-                /**
1413
-                 * When the prejoin page is visible there is no conference object
1414
-                 * created. The prejoin tracks are managed separately,
1415
-                 * so this updates the prejoin video track.
1416
-                 */
1417
-                if (isPrejoinPageVisible(APP.store.getState())) {
1418
-                    return APP.store.dispatch(replacePrejoinVideoTrack(newStream))
1419
-                        .then(resolve)
1420
-                        .catch(reject)
1421
-                        .then(onFinish);
1422
-                }
1423
-
1424
                 APP.store.dispatch(
1409
                 APP.store.dispatch(
1425
                 replaceLocalTrack(this.localVideo, newStream, room))
1410
                 replaceLocalTrack(this.localVideo, newStream, room))
1426
                     .then(() => {
1411
                     .then(() => {
1474
     useAudioStream(newStream) {
1459
     useAudioStream(newStream) {
1475
         return new Promise((resolve, reject) => {
1460
         return new Promise((resolve, reject) => {
1476
             _replaceLocalAudioTrackQueue.enqueue(onFinish => {
1461
             _replaceLocalAudioTrackQueue.enqueue(onFinish => {
1477
-                /**
1478
-                 * When the prejoin page is visible there is no conference object
1479
-                 * created. The prejoin tracks are managed separately,
1480
-                 * so this updates the prejoin audio stream.
1481
-                 */
1482
-                if (isPrejoinPageVisible(APP.store.getState())) {
1483
-                    return APP.store.dispatch(replacePrejoinAudioTrack(newStream))
1484
-                        .then(resolve)
1485
-                        .catch(reject)
1486
-                        .then(onFinish);
1487
-                }
1488
-
1489
                 APP.store.dispatch(
1462
                 APP.store.dispatch(
1490
                 replaceLocalTrack(this.localAudio, newStream, room))
1463
                 replaceLocalTrack(this.localAudio, newStream, room))
1491
                     .then(() => {
1464
                     .then(() => {

+ 4
- 0
css/_settings-button.scss 查看文件

35
                 cursor: initial;
35
                 cursor: initial;
36
                 color: #fff;
36
                 color: #fff;
37
                 background-color: #a4b8d1;
37
                 background-color: #a4b8d1;
38
+
39
+                &:hover {
40
+                    background-color: #a4b8d1;
41
+                }
38
             }
42
             }
39
 
43
 
40
             svg {
44
             svg {

+ 11
- 0
react/features/base/media/functions.js 查看文件

4
 
4
 
5
 import { VIDEO_MUTISM_AUTHORITY } from './constants';
5
 import { VIDEO_MUTISM_AUTHORITY } from './constants';
6
 
6
 
7
+/**
8
+ * Determines whether audio is currently muted.
9
+ *
10
+ * @param {Function|Object} stateful - The redux store, state, or
11
+ * {@code getState} function.
12
+ * @returns {boolean}
13
+ */
14
+export function isAudioMuted(stateful: Function | Object) {
15
+    return Boolean(toState(stateful)['features/base/media'].audio.muted);
16
+}
17
+
7
 /**
18
 /**
8
  * Determines whether video is currently muted by the audio-only authority.
19
  * Determines whether video is currently muted by the audio-only authority.
9
  *
20
  *

+ 18
- 2
react/features/base/settings/functions.web.js 查看文件

9
  * @returns {void}
9
  * @returns {void}
10
  */
10
  */
11
 export function getCurrentCameraDeviceId(state: Object) {
11
 export function getCurrentCameraDeviceId(state: Object) {
12
-    return state['features/base/settings'].cameraDeviceId;
12
+    return getDeviceIdByType(state, 'isVideoTrack');
13
 }
13
 }
14
 
14
 
15
 /**
15
 /**
19
  * @returns {void}
19
  * @returns {void}
20
  */
20
  */
21
 export function getCurrentMicDeviceId(state: Object) {
21
 export function getCurrentMicDeviceId(state: Object) {
22
-    return state['features/base/settings'].micDeviceId;
22
+    return getDeviceIdByType(state, 'isAudioTrack');
23
 }
23
 }
24
 
24
 
25
 /**
25
 /**
32
     return state['features/base/settings'].audioOutputDeviceId;
32
     return state['features/base/settings'].audioOutputDeviceId;
33
 }
33
 }
34
 
34
 
35
+/**
36
+ * Returns the deviceId for the corresponding local track type.
37
+ *
38
+ * @param {Object} state - The state of the application.
39
+ * @param {string} isType - Can be 'isVideoTrack' | 'isAudioTrack'.
40
+ * @returns {string}
41
+ */
42
+function getDeviceIdByType(state: Object, isType: string) {
43
+    const [ deviceId ] = state['features/base/tracks']
44
+          .map(t => t.jitsiTrack)
45
+          .filter(t => t && t.isLocal() && t[isType]())
46
+          .map(t => t.getDeviceId());
47
+
48
+    return deviceId || '';
49
+}
50
+
35
 /**
51
 /**
36
  * Returns the saved display name.
52
  * Returns the saved display name.
37
  *
53
  *

+ 58
- 44
react/features/base/tracks/actions.js 查看文件

267
  * @returns {Function}
267
  * @returns {Function}
268
  */
268
  */
269
 export function replaceLocalTrack(oldTrack, newTrack, conference) {
269
 export function replaceLocalTrack(oldTrack, newTrack, conference) {
270
-    return (dispatch, getState) => {
270
+    return async (dispatch, getState) => {
271
         conference
271
         conference
272
 
272
 
273
             // eslint-disable-next-line no-param-reassign
273
             // eslint-disable-next-line no-param-reassign
274
             || (conference = getState()['features/base/conference'].conference);
274
             || (conference = getState()['features/base/conference'].conference);
275
 
275
 
276
-        return conference.replaceTrack(oldTrack, newTrack)
276
+        if (conference) {
277
+            await conference.replaceTrack(oldTrack, newTrack);
278
+        }
279
+
280
+        return dispatch(replaceStoredTracks(oldTrack, newTrack));
281
+    };
282
+}
283
+
284
+/**
285
+ * Replaces a stored track with another.
286
+ *
287
+ * @param {JitsiLocalTrack|null} oldTrack - The track to dispose.
288
+ * @param {JitsiLocalTrack|null} newTrack - The track to use instead.
289
+ * @returns {Function}
290
+ */
291
+function replaceStoredTracks(oldTrack, newTrack) {
292
+    return dispatch => {
293
+        // We call dispose after doing the replace because dispose will
294
+        // try and do a new o/a after the track removes itself. Doing it
295
+        // after means the JitsiLocalTrack.conference is already
296
+        // cleared, so it won't try and do the o/a.
297
+        const disposePromise
298
+              = oldTrack
299
+                  ? dispatch(_disposeAndRemoveTracks([ oldTrack ]))
300
+                  : Promise.resolve();
301
+
302
+        return disposePromise
277
             .then(() => {
303
             .then(() => {
278
-                // We call dispose after doing the replace because dispose will
279
-                // try and do a new o/a after the track removes itself. Doing it
280
-                // after means the JitsiLocalTrack.conference is already
281
-                // cleared, so it won't try and do the o/a.
282
-                const disposePromise
283
-                    = oldTrack
284
-                        ? dispatch(_disposeAndRemoveTracks([ oldTrack ]))
285
-                        : Promise.resolve();
286
-
287
-                return disposePromise
288
-                    .then(() => {
289
-                        if (newTrack) {
290
-                            // The mute state of the new track should be
291
-                            // reflected in the app's mute state. For example,
292
-                            // if the app is currently muted and changing to a
293
-                            // new track that is not muted, the app's mute
294
-                            // state should be falsey. As such, emit a mute
295
-                            // event here to set up the app to reflect the
296
-                            // track's mute state. If this is not done, the
297
-                            // current mute state of the app will be reflected
298
-                            // on the track, not vice-versa.
299
-                            const setMuted
300
-                                = newTrack.isVideoTrack()
301
-                                    ? setVideoMuted
302
-                                    : setAudioMuted;
303
-                            const isMuted = newTrack.isMuted();
304
-
305
-                            sendAnalytics(createTrackMutedEvent(
306
-                                newTrack.getType(),
307
-                                'track.replaced',
308
-                                isMuted));
309
-                            logger.log(`Replace ${newTrack.getType()} track - ${
310
-                                isMuted ? 'muted' : 'unmuted'}`);
311
-
312
-                            return dispatch(setMuted(isMuted));
313
-                        }
314
-                    })
315
-                    .then(() => {
316
-                        if (newTrack) {
317
-                            return dispatch(_addTracks([ newTrack ]));
318
-                        }
319
-                    });
304
+                if (newTrack) {
305
+                    // The mute state of the new track should be
306
+                    // reflected in the app's mute state. For example,
307
+                    // if the app is currently muted and changing to a
308
+                    // new track that is not muted, the app's mute
309
+                    // state should be falsey. As such, emit a mute
310
+                    // event here to set up the app to reflect the
311
+                    // track's mute state. If this is not done, the
312
+                    // current mute state of the app will be reflected
313
+                    // on the track, not vice-versa.
314
+                    const setMuted
315
+                          = newTrack.isVideoTrack()
316
+                              ? setVideoMuted
317
+                              : setAudioMuted;
318
+                    const isMuted = newTrack.isMuted();
319
+
320
+                    sendAnalytics(createTrackMutedEvent(
321
+                        newTrack.getType(),
322
+                        'track.replaced',
323
+                        isMuted));
324
+                    logger.log(`Replace ${newTrack.getType()} track - ${
325
+                        isMuted ? 'muted' : 'unmuted'}`);
326
+
327
+                    return dispatch(setMuted(isMuted));
328
+                }
329
+            })
330
+            .then(() => {
331
+                if (newTrack) {
332
+                    return dispatch(_addTracks([ newTrack ]));
333
+                }
320
             });
334
             });
321
     };
335
     };
322
 }
336
 }

+ 12
- 0
react/features/base/tracks/functions.js 查看文件

200
     return presenterTrack ? MEDIA_TYPE.PRESENTER : MEDIA_TYPE.VIDEO;
200
     return presenterTrack ? MEDIA_TYPE.PRESENTER : MEDIA_TYPE.VIDEO;
201
 }
201
 }
202
 
202
 
203
+/**
204
+ * Returns the stored local video track.
205
+ *
206
+ * @param {Object} state - The redux state.
207
+ * @returns {Object}
208
+ */
209
+export function getLocalJitsiVideoTrack(state) {
210
+    const track = getLocalVideoTrack(state['features/base/tracks']);
211
+
212
+    return track?.jitsiTrack;
213
+}
214
+
203
 /**
215
 /**
204
  * Returns track of specified media type for specified participant id.
216
  * Returns track of specified media type for specified participant id.
205
  *
217
  *

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

2
 
2
 
3
 import UIEvents from '../../../../service/UI/UIEvents';
3
 import UIEvents from '../../../../service/UI/UIEvents';
4
 import { hideNotification } from '../../notifications';
4
 import { hideNotification } from '../../notifications';
5
+import { isPrejoinPageVisible } from '../../prejoin/functions';
6
+import { getAvailableDevices } from '../devices/actions';
5
 import {
7
 import {
6
     CAMERA_FACING_MODE,
8
     CAMERA_FACING_MODE,
7
     MEDIA_TYPE,
9
     MEDIA_TYPE,
15
 import { MiddlewareRegistry } from '../redux';
17
 import { MiddlewareRegistry } from '../redux';
16
 
18
 
17
 import {
19
 import {
20
+    TRACK_ADDED,
18
     TOGGLE_SCREENSHARING,
21
     TOGGLE_SCREENSHARING,
19
     TRACK_NO_DATA_FROM_SOURCE,
22
     TRACK_NO_DATA_FROM_SOURCE,
20
     TRACK_REMOVED,
23
     TRACK_REMOVED,
44
  */
47
  */
45
 MiddlewareRegistry.register(store => next => action => {
48
 MiddlewareRegistry.register(store => next => action => {
46
     switch (action.type) {
49
     switch (action.type) {
50
+    case TRACK_ADDED: {
51
+        // The devices list needs to be refreshed when no initial video permissions
52
+        // were granted and a local video track is added by umuting the video.
53
+        if (action.track.local) {
54
+            store.dispatch(getAvailableDevices());
55
+        }
56
+
57
+        break;
58
+    }
47
     case TRACK_NO_DATA_FROM_SOURCE: {
59
     case TRACK_NO_DATA_FROM_SOURCE: {
48
         const result = next(action);
60
         const result = next(action);
49
 
61
 
281
         // anymore, unless it is muted by audioOnly.
293
         // anymore, unless it is muted by audioOnly.
282
         jitsiTrack && (jitsiTrack.videoType !== 'desktop' || isAudioOnly)
294
         jitsiTrack && (jitsiTrack.videoType !== 'desktop' || isAudioOnly)
283
             && setTrackMuted(jitsiTrack, muted);
295
             && setTrackMuted(jitsiTrack, muted);
284
-    } else if (!muted && ensureTrack && typeof APP === 'undefined') {
296
+    } else if (!muted && ensureTrack && (typeof APP === 'undefined' || isPrejoinPageVisible(store.getState()))) {
285
         // FIXME: This only runs on mobile now because web has its own way of
297
         // FIXME: This only runs on mobile now because web has its own way of
286
         // creating local tracks. Adjust the check once they are unified.
298
         // creating local tracks. Adjust the check once they are unified.
287
         store.dispatch(createLocalTracksA({ devices: [ mediaType ] }));
299
         store.dispatch(createLocalTracksA({ devices: [ mediaType ] }));

+ 0
- 25
react/features/prejoin/actionTypes.js 查看文件

1
-/**
2
- * Action type to add a video track to the store.
3
- */
4
-export const ADD_PREJOIN_VIDEO_TRACK = 'ADD_PREJOIN_VIDEO_TRACK';
5
-
6
-/**
7
- * Action type to add an audio track to the store.
8
- */
9
-export const ADD_PREJOIN_AUDIO_TRACK = 'ADD_PREJOIN_AUDIO_TRACK';
10
-
11
-/**
12
- * Action type to add a content sharing track to the store.
13
- */
14
-export const ADD_PREJOIN_CONTENT_SHARING_TRACK
15
-    = 'ADD_PREJOIN_CONTENT_SHARING_TRACK';
16
 
1
 
17
 /**
2
 /**
18
  * Action type to signal the start of the conference.
3
  * Action type to signal the start of the conference.
68
  * Action type to set the visibility of the prejoin page.
53
  * Action type to set the visibility of the prejoin page.
69
  */
54
  */
70
 export const SET_PREJOIN_PAGE_VISIBILITY = 'SET_PREJOIN_PAGE_VISIBILITY';
55
 export const SET_PREJOIN_PAGE_VISIBILITY = 'SET_PREJOIN_PAGE_VISIBILITY';
71
-
72
-/**
73
- * Action type to mute/unmute the video while on prejoin page.
74
- */
75
-export const SET_PREJOIN_VIDEO_DISABLED = 'SET_PREJOIN_VIDEO_DISABLED';
76
-
77
-/**
78
- * Action type to mute/unmute the video while on prejoin page.
79
- */
80
-export const SET_PREJOIN_VIDEO_MUTED = 'SET_PREJOIN_VIDEO_MUTED';

+ 21
- 158
react/features/prejoin/actions.js 查看文件

5
 import { getRoomName } from '../base/conference';
5
 import { getRoomName } from '../base/conference';
6
 import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
6
 import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
7
 import { createLocalTrack } from '../base/lib-jitsi-meet';
7
 import { createLocalTrack } from '../base/lib-jitsi-meet';
8
+import {
9
+    getLocalAudioTrack,
10
+    getLocalVideoTrack,
11
+    trackAdded,
12
+    replaceLocalTrack
13
+} from '../base/tracks';
8
 import { openURLInBrowser } from '../base/util';
14
 import { openURLInBrowser } from '../base/util';
9
 import { executeDialOutRequest, executeDialOutStatusRequest, getDialInfoPageURL } from '../invite/functions';
15
 import { executeDialOutRequest, executeDialOutStatusRequest, getDialInfoPageURL } from '../invite/functions';
10
 import { showErrorNotification } from '../notifications';
16
 import { showErrorNotification } from '../notifications';
11
 
17
 
12
 import {
18
 import {
13
-    ADD_PREJOIN_AUDIO_TRACK,
14
-    ADD_PREJOIN_CONTENT_SHARING_TRACK,
15
-    ADD_PREJOIN_VIDEO_TRACK,
16
     PREJOIN_START_CONFERENCE,
19
     PREJOIN_START_CONFERENCE,
17
     SET_DEVICE_STATUS,
20
     SET_DEVICE_STATUS,
18
     SET_DIALOUT_COUNTRY,
21
     SET_DIALOUT_COUNTRY,
20
     SET_DIALOUT_STATUS,
23
     SET_DIALOUT_STATUS,
21
     SET_SKIP_PREJOIN,
24
     SET_SKIP_PREJOIN,
22
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
25
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
23
-    SET_PREJOIN_AUDIO_DISABLED,
24
-    SET_PREJOIN_AUDIO_MUTED,
25
     SET_PREJOIN_DEVICE_ERRORS,
26
     SET_PREJOIN_DEVICE_ERRORS,
26
-    SET_PREJOIN_PAGE_VISIBILITY,
27
-    SET_PREJOIN_VIDEO_DISABLED,
28
-    SET_PREJOIN_VIDEO_MUTED
27
+    SET_PREJOIN_PAGE_VISIBILITY
29
 } from './actionTypes';
28
 } from './actionTypes';
30
 import {
29
 import {
31
     getFullDialOutNumber,
30
     getFullDialOutNumber,
32
-    getAudioTrack,
33
     getDialOutConferenceUrl,
31
     getDialOutConferenceUrl,
34
     getDialOutCountry,
32
     getDialOutCountry,
35
-    getVideoTrack,
36
     isJoinByPhoneDialogVisible
33
     isJoinByPhoneDialogVisible
37
 } from './functions';
34
 } from './functions';
38
 import logger from './logger';
35
 import logger from './logger';
60
  */
57
  */
61
 const STATUS_REQ_CAP = 45;
58
 const STATUS_REQ_CAP = 45;
62
 
59
 
63
-/**
64
- * Action used to add an audio track to the store.
65
- *
66
- * @param {Object} value - The track to be added.
67
- * @returns {Object}
68
- */
69
-export function addPrejoinAudioTrack(value: Object) {
70
-    return {
71
-        type: ADD_PREJOIN_AUDIO_TRACK,
72
-        value
73
-    };
74
-}
75
-
76
-/**
77
- * Action used to add a video track to the store.
78
- *
79
- * @param {Object} value - The track to be added.
80
- * @returns {Object}
81
- */
82
-export function addPrejoinVideoTrack(value: Object) {
83
-    return {
84
-        type: ADD_PREJOIN_VIDEO_TRACK,
85
-        value
86
-    };
87
-}
88
-
89
-/**
90
- * Action used to add a content sharing track to the store.
91
- *
92
- * @param {Object} value - The track to be added.
93
- * @returns {Object}
94
- */
95
-export function addPrejoinContentSharingTrack(value: Object) {
96
-    return {
97
-        type: ADD_PREJOIN_CONTENT_SHARING_TRACK,
98
-        value
99
-    };
100
-}
101
-
102
 /**
60
 /**
103
  * Polls for status change after dial out.
61
  * Polls for status change after dial out.
104
  * Changes dialog message based on response, closes the dialog if there is an error,
62
  * Changes dialog message based on response, closes the dialog if there is an error,
232
  */
190
  */
233
 export function initPrejoin(tracks: Object[], errors: Object) {
191
 export function initPrejoin(tracks: Object[], errors: Object) {
234
     return async function(dispatch: Function) {
192
     return async function(dispatch: Function) {
235
-        const audioTrack = tracks.find(t => t.isAudioTrack());
236
-        const videoTrack = tracks.find(t => t.isVideoTrack());
237
-
238
         dispatch(setPrejoinDeviceErrors(errors));
193
         dispatch(setPrejoinDeviceErrors(errors));
239
 
194
 
240
-        if (audioTrack) {
241
-            dispatch(addPrejoinAudioTrack(audioTrack));
242
-        } else {
243
-            dispatch(setAudioDisabled());
244
-        }
245
 
195
 
246
-        if (videoTrack) {
247
-            if (videoTrack.videoType === 'desktop') {
248
-                dispatch(addPrejoinContentSharingTrack(videoTrack));
249
-                dispatch(setPrejoinVideoDisabled(true));
250
-            } else {
251
-                dispatch(addPrejoinVideoTrack(videoTrack));
252
-            }
253
-        } else {
254
-            dispatch(setPrejoinVideoDisabled(true));
255
-        }
196
+        tracks.forEach(track => dispatch(trackAdded(track)));
256
     };
197
     };
257
 }
198
 }
258
 
199
 
275
  */
216
  */
276
 export function joinConferenceWithoutAudio() {
217
 export function joinConferenceWithoutAudio() {
277
     return async function(dispatch: Function, getState: Function) {
218
     return async function(dispatch: Function, getState: Function) {
278
-        const audioTrack = getAudioTrack(getState());
219
+        const tracks = getState()['features/base/tracks'];
220
+        const audioTrack = getLocalAudioTrack(tracks)?.jitsiTrack;
279
 
221
 
280
         if (audioTrack) {
222
         if (audioTrack) {
281
-            await dispatch(replacePrejoinAudioTrack(null));
223
+            await dispatch(replaceLocalTrack(audioTrack, null));
282
         }
224
         }
283
-        dispatch(setAudioDisabled());
284
         dispatch(joinConference());
225
         dispatch(joinConference());
285
     };
226
     };
286
 }
227
 }
301
     };
242
     };
302
 }
243
 }
303
 
244
 
304
-/**
305
- * Replaces the existing audio track with a new one.
306
- *
307
- * @param {Object} track - The new track.
308
- * @returns {Function}
309
- */
310
-export function replacePrejoinAudioTrack(track: Object) {
311
-    return async (dispatch: Function, getState: Function) => {
312
-        const oldTrack = getAudioTrack(getState());
313
-
314
-        oldTrack && await oldTrack.dispose();
315
-        dispatch(addPrejoinAudioTrack(track));
316
-    };
317
-}
318
-
319
 /**
245
 /**
320
  * Creates a new audio track based on a device id and replaces the current one.
246
  * Creates a new audio track based on a device id and replaces the current one.
321
  *
247
  *
323
  * @returns {Function}
249
  * @returns {Function}
324
  */
250
  */
325
 export function replaceAudioTrackById(deviceId: string) {
251
 export function replaceAudioTrackById(deviceId: string) {
326
-    return async (dispatch: Function) => {
252
+    return async (dispatch: Function, getState: Function) => {
327
         try {
253
         try {
328
-            const track = await createLocalTrack('audio', deviceId);
254
+            const tracks = getState()['features/base/tracks'];
255
+            const newTrack = await createLocalTrack('audio', deviceId);
256
+            const oldTrack = getLocalAudioTrack(tracks)?.jitsiTrack;
329
 
257
 
330
-            dispatch(replacePrejoinAudioTrack(track));
258
+            dispatch(replaceLocalTrack(oldTrack, newTrack));
331
         } catch (err) {
259
         } catch (err) {
332
             dispatch(setDeviceStatusWarning('prejoin.audioTrackError'));
260
             dispatch(setDeviceStatusWarning('prejoin.audioTrackError'));
333
             logger.log('Error replacing audio track', err);
261
             logger.log('Error replacing audio track', err);
335
     };
263
     };
336
 }
264
 }
337
 
265
 
338
-/**
339
- * Replaces the existing video track with a new one.
340
- *
341
- * @param {Object} track - The new track.
342
- * @returns {Function}
343
- */
344
-export function replacePrejoinVideoTrack(track: Object) {
345
-    return async (dispatch: Function, getState: Function) => {
346
-        const oldTrack = getVideoTrack(getState());
347
-
348
-        oldTrack && await oldTrack.dispose();
349
-        dispatch(addPrejoinVideoTrack(track));
350
-    };
351
-}
352
-
353
 /**
266
 /**
354
  * Creates a new video track based on a device id and replaces the current one.
267
  * Creates a new video track based on a device id and replaces the current one.
355
  *
268
  *
357
  * @returns {Function}
270
  * @returns {Function}
358
  */
271
  */
359
 export function replaceVideoTrackById(deviceId: Object) {
272
 export function replaceVideoTrackById(deviceId: Object) {
360
-    return async (dispatch: Function) => {
273
+    return async (dispatch: Function, getState: Function) => {
361
         try {
274
         try {
362
-            const track = await createLocalTrack('video', deviceId);
275
+            const tracks = getState()['features/base/tracks'];
276
+            const newTrack = await createLocalTrack('video', deviceId);
277
+            const oldTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
363
 
278
 
364
-            dispatch(replacePrejoinVideoTrack(track));
279
+            dispatch(replaceLocalTrack(oldTrack, newTrack));
365
         } catch (err) {
280
         } catch (err) {
366
             dispatch(setDeviceStatusWarning('prejoin.videoTrackError'));
281
             dispatch(setDeviceStatusWarning('prejoin.videoTrackError'));
367
             logger.log('Error replacing video track', err);
282
             logger.log('Error replacing video track', err);
369
     };
284
     };
370
 }
285
 }
371
 
286
 
372
-
373
-/**
374
- * Action used to mark audio muted.
375
- *
376
- * @param {boolean} value - True for muted.
377
- * @returns {Object}
378
- */
379
-export function setPrejoinAudioMuted(value: boolean) {
380
-    return {
381
-        type: SET_PREJOIN_AUDIO_MUTED,
382
-        value
383
-    };
384
-}
385
-
386
-/**
387
- * Action used to mark video disabled.
388
- *
389
- * @param {boolean} value - True for muted.
390
- * @returns {Object}
391
- */
392
-export function setPrejoinVideoDisabled(value: boolean) {
393
-    return {
394
-        type: SET_PREJOIN_VIDEO_DISABLED,
395
-        value
396
-    };
397
-}
398
-
399
-
400
-/**
401
- * Action used to mark video muted.
402
- *
403
- * @param {boolean} value - True for muted.
404
- * @returns {Object}
405
- */
406
-export function setPrejoinVideoMuted(value: boolean) {
407
-    return {
408
-        type: SET_PREJOIN_VIDEO_MUTED,
409
-        value
410
-    };
411
-}
412
-
413
-/**
414
- * Action used to mark audio as disabled.
415
- *
416
- * @returns {Object}
417
- */
418
-export function setAudioDisabled() {
419
-    return {
420
-        type: SET_PREJOIN_AUDIO_DISABLED
421
-    };
422
-}
423
-
424
 /**
287
 /**
425
  * Sets the device status as OK with the corresponding text.
288
  * Sets the device status as OK with the corresponding text.
426
  *
289
  *

+ 5
- 5
react/features/prejoin/components/Prejoin.js 查看文件

6
 import { getRoomName } from '../../base/conference';
6
 import { getRoomName } from '../../base/conference';
7
 import { translate } from '../../base/i18n';
7
 import { translate } from '../../base/i18n';
8
 import { Icon, IconPhone, IconVolumeOff } from '../../base/icons';
8
 import { Icon, IconPhone, IconVolumeOff } from '../../base/icons';
9
+import { isVideoMutedByUser } from '../../base/media';
9
 import { ActionButton, InputField, PreMeetingScreen } from '../../base/premeeting';
10
 import { ActionButton, InputField, PreMeetingScreen } from '../../base/premeeting';
10
 import { connect } from '../../base/redux';
11
 import { connect } from '../../base/redux';
11
 import { getDisplayName, updateSettings } from '../../base/settings';
12
 import { getDisplayName, updateSettings } from '../../base/settings';
13
+import { getLocalJitsiVideoTrack } from '../../base/tracks';
12
 import {
14
 import {
13
     joinConference as joinConferenceAction,
15
     joinConference as joinConferenceAction,
14
     joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
16
     joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
16
     setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction
18
     setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction
17
 } from '../actions';
19
 } from '../actions';
18
 import {
20
 import {
19
-    getActiveVideoTrack,
20
     isJoinByPhoneButtonVisible,
21
     isJoinByPhoneButtonVisible,
21
     isDeviceStatusVisible,
22
     isDeviceStatusVisible,
22
-    isJoinByPhoneDialogVisible,
23
-    isPrejoinVideoMuted
23
+    isJoinByPhoneDialogVisible
24
 } from '../functions';
24
 } from '../functions';
25
 
25
 
26
 import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
26
 import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
315
         roomName: getRoomName(state),
315
         roomName: getRoomName(state),
316
         showDialog: isJoinByPhoneDialogVisible(state),
316
         showDialog: isJoinByPhoneDialogVisible(state),
317
         hasJoinByPhoneButton: isJoinByPhoneButtonVisible(state),
317
         hasJoinByPhoneButton: isJoinByPhoneButtonVisible(state),
318
-        showCameraPreview: !isPrejoinVideoMuted(state),
319
-        videoTrack: getActiveVideoTrack(state)
318
+        showCameraPreview: !isVideoMutedByUser(state),
319
+        videoTrack: getLocalJitsiVideoTrack(state)
320
     };
320
     };
321
 }
321
 }
322
 
322
 

+ 3
- 136
react/features/prejoin/functions.js 查看文件

2
 
2
 
3
 import { getRoomName } from '../base/conference';
3
 import { getRoomName } from '../base/conference';
4
 import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
4
 import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
5
-
6
-/**
7
- * Mutes or unmutes a track.
8
- *
9
- * @param {Object} track - The track to be configured.
10
- * @param {boolean} shouldMute - If it should mute or not.
11
- * @returns {Promise<void>}
12
- */
13
-function applyMuteOptionsToTrack(track, shouldMute) {
14
-    if (track.isMuted() === shouldMute) {
15
-        return;
16
-    }
17
-
18
-    if (shouldMute) {
19
-        return track.mute();
20
-    }
21
-
22
-    return track.unmute();
23
-}
5
+import { isAudioMuted, isVideoMutedByUser } from '../base/media';
24
 
6
 
25
 /**
7
 /**
26
  * Selector for the visibility of the 'join by phone' button.
8
  * Selector for the visibility of the 'join by phone' button.
39
  * @returns {boolean}
21
  * @returns {boolean}
40
  */
22
  */
41
 export function isDeviceStatusVisible(state: Object): boolean {
23
 export function isDeviceStatusVisible(state: Object): boolean {
42
-    return !((isAudioDisabled(state) && isPrejoinVideoDisabled(state))
43
-        || (isPrejoinAudioMuted(state) && isPrejoinVideoMuted(state)));
44
-}
45
-
46
-/**
47
- * Selector for getting the active video/content sharing track.
48
- *
49
- * @param {Object} state - The state of the app.
50
- * @returns {boolean}
51
- */
52
-export function getActiveVideoTrack(state: Object): Object {
53
-    const track = getVideoTrack(state) || getContentSharingTrack(state);
54
-
55
-    if (track && track.isActive()) {
56
-        return track;
57
-    }
58
-
59
-    return null;
60
-}
61
-
62
-/**
63
- * Returns a list with all the prejoin tracks configured according to
64
- * user's preferences.
65
- *
66
- * @param {Object} state - The state of the app.
67
- * @returns {Promise<Object[]>}
68
- */
69
-export async function getAllPrejoinConfiguredTracks(state: Object): Promise<Object[]> {
70
-    const tracks = [];
71
-    const audioTrack = getAudioTrack(state);
72
-    const videoTrack = getVideoTrack(state);
73
-    const csTrack = getContentSharingTrack(state);
74
-
75
-    if (csTrack) {
76
-        tracks.push(csTrack);
77
-    } else if (videoTrack) {
78
-        await applyMuteOptionsToTrack(videoTrack, isPrejoinVideoMuted(state));
79
-        tracks.push(videoTrack);
80
-    }
81
-
82
-    if (audioTrack) {
83
-        await applyMuteOptionsToTrack(audioTrack, isPrejoinAudioMuted(state));
84
-        isPrejoinAudioMuted(state) && audioTrack.mute();
85
-        tracks.push(audioTrack);
86
-    }
87
-
88
-    return tracks;
89
-}
90
-
91
-/**
92
- * Selector for getting the prejoin audio track.
93
- *
94
- * @param {Object} state - The state of the app.
95
- * @returns {Object}
96
- */
97
-export function getAudioTrack(state: Object): Object {
98
-    return state['features/prejoin']?.audioTrack;
99
-}
100
-
101
-/**
102
- * Selector for getting the prejoin content sharing track.
103
- *
104
- * @param {Object} state - The state of the app.
105
- * @returns {Object}
106
- */
107
-export function getContentSharingTrack(state: Object): Object {
108
-    return state['features/prejoin']?.contentSharingTrack;
24
+    return !(isAudioMuted(state) && isVideoMutedByUser(state))
25
+    && !state['features/base/config'].startSilent;
109
 }
26
 }
110
 
27
 
111
 /**
28
 /**
181
     return `+${country.dialCode}${dialOutNumber}`;
98
     return `+${country.dialCode}${dialOutNumber}`;
182
 }
99
 }
183
 
100
 
184
-/**
185
- * Selector for getting the prejoin video track.
186
- *
187
- * @param {Object} state - The state of the app.
188
- * @returns {Object}
189
- */
190
-export function getVideoTrack(state: Object): Object {
191
-    return state['features/prejoin']?.videoTrack;
192
-}
193
-
194
-/**
195
- * Selector for getting the mute status of the prejoin audio.
196
- *
197
- * @param {Object} state - The state of the app.
198
- * @returns {boolean}
199
- */
200
-export function isPrejoinAudioMuted(state: Object): boolean {
201
-    return state['features/prejoin']?.audioMuted;
202
-}
203
-
204
-/**
205
- * Selector for getting the mute status of the prejoin video.
206
- *
207
- * @param {Object} state - The state of the app.
208
- * @returns {boolean}
209
- */
210
-export function isPrejoinVideoMuted(state: Object): boolean {
211
-    return state['features/prejoin']?.videoMuted;
212
-}
213
-
214
 /**
101
 /**
215
  * Selector for getting the error if any while creating streams.
102
  * Selector for getting the error if any while creating streams.
216
  *
103
  *
221
     return state['features/prejoin']?.rawError;
108
     return state['features/prejoin']?.rawError;
222
 }
109
 }
223
 
110
 
224
-/**
225
- * Selector for getting state of the prejoin audio.
226
- *
227
- * @param {Object} state - The state of the app.
228
- * @returns {boolean}
229
- */
230
-export function isAudioDisabled(state: Object): Object {
231
-    return state['features/prejoin']?.audioDisabled;
232
-}
233
-
234
-/**
235
- * Selector for getting state of the prejoin video.
236
- *
237
- * @param {Object} state - The state of the app.
238
- * @returns {boolean}
239
- */
240
-export function isPrejoinVideoDisabled(state: Object): Object {
241
-    return state['features/prejoin']?.videoDisabled;
242
-}
243
-
244
 /**
111
 /**
245
  * Selector for getting the visiblity state for the 'JoinByPhoneDialog'.
112
  * Selector for getting the visiblity state for the 'JoinByPhoneDialog'.
246
  *
113
  *

+ 3
- 50
react/features/prejoin/middleware.js 查看文件

1
 // @flow
1
 // @flow
2
 
2
 
3
-import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../base/media';
4
 import { MiddlewareRegistry } from '../base/redux';
3
 import { MiddlewareRegistry } from '../base/redux';
5
 import { updateSettings } from '../base/settings';
4
 import { updateSettings } from '../base/settings';
6
 
5
 
7
-import {
8
-    ADD_PREJOIN_AUDIO_TRACK,
9
-    ADD_PREJOIN_VIDEO_TRACK,
10
-    PREJOIN_START_CONFERENCE
11
-} from './actionTypes';
12
-import { setPrejoinAudioMuted, setPrejoinVideoMuted } from './actions';
13
-import { getAllPrejoinConfiguredTracks } from './functions';
6
+import { PREJOIN_START_CONFERENCE } from './actionTypes';
14
 
7
 
15
 declare var APP: Object;
8
 declare var APP: Object;
16
 
9
 
22
  */
15
  */
23
 MiddlewareRegistry.register(store => next => async action => {
16
 MiddlewareRegistry.register(store => next => async action => {
24
     switch (action.type) {
17
     switch (action.type) {
25
-    case ADD_PREJOIN_AUDIO_TRACK: {
26
-        const { value: audioTrack } = action;
27
-
28
-        if (audioTrack) {
29
-            store.dispatch(
30
-                    updateSettings({
31
-                        micDeviceId: audioTrack.getDeviceId()
32
-                    }),
33
-            );
34
-        }
35
-
36
-        break;
37
-    }
38
-
39
-    case ADD_PREJOIN_VIDEO_TRACK: {
40
-        const { value: videoTrack } = action;
41
-
42
-        if (videoTrack) {
43
-            store.dispatch(
44
-                    updateSettings({
45
-                        cameraDeviceId: videoTrack.getDeviceId()
46
-                    }),
47
-            );
48
-        }
49
-
50
-        break;
51
-    }
52
-
53
     case PREJOIN_START_CONFERENCE: {
18
     case PREJOIN_START_CONFERENCE: {
54
         const { getState, dispatch } = store;
19
         const { getState, dispatch } = store;
55
         const state = getState();
20
         const state = getState();
56
         const { userSelectedSkipPrejoin } = state['features/prejoin'];
21
         const { userSelectedSkipPrejoin } = state['features/prejoin'];
22
+        const tracks = state['features/base/tracks'];
57
 
23
 
58
         userSelectedSkipPrejoin && dispatch(updateSettings({
24
         userSelectedSkipPrejoin && dispatch(updateSettings({
59
             userSelectedSkipPrejoin
25
             userSelectedSkipPrejoin
60
         }));
26
         }));
61
 
27
 
62
-
63
-        const tracks = await getAllPrejoinConfiguredTracks(state);
64
-
65
-        APP.conference.prejoinStart(tracks);
28
+        APP.conference.prejoinStart(tracks.map(t => t.jitsiTrack));
66
 
29
 
67
         break;
30
         break;
68
     }
31
     }
69
-
70
-    case SET_AUDIO_MUTED: {
71
-        store.dispatch(setPrejoinAudioMuted(Boolean(action.muted)));
72
-        break;
73
     }
32
     }
74
 
33
 
75
-    case SET_VIDEO_MUTED: {
76
-        store.dispatch(setPrejoinVideoMuted(Boolean(action.muted)));
77
-        break;
78
-    }
79
-
80
-    }
81
 
34
 
82
     return next(action);
35
     return next(action);
83
 });
36
 });

+ 2
- 62
react/features/prejoin/reducer.js 查看文件

1
 import { ReducerRegistry } from '../base/redux';
1
 import { ReducerRegistry } from '../base/redux';
2
 
2
 
3
 import {
3
 import {
4
-    ADD_PREJOIN_AUDIO_TRACK,
5
-    ADD_PREJOIN_CONTENT_SHARING_TRACK,
6
-    ADD_PREJOIN_VIDEO_TRACK,
7
     SET_DEVICE_STATUS,
4
     SET_DEVICE_STATUS,
8
     SET_DIALOUT_NUMBER,
5
     SET_DIALOUT_NUMBER,
9
     SET_DIALOUT_COUNTRY,
6
     SET_DIALOUT_COUNTRY,
10
     SET_DIALOUT_STATUS,
7
     SET_DIALOUT_STATUS,
11
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
8
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
12
     SET_SKIP_PREJOIN,
9
     SET_SKIP_PREJOIN,
13
-    SET_PREJOIN_AUDIO_DISABLED,
14
-    SET_PREJOIN_AUDIO_MUTED,
15
     SET_PREJOIN_DEVICE_ERRORS,
10
     SET_PREJOIN_DEVICE_ERRORS,
16
-    SET_PREJOIN_PAGE_VISIBILITY,
17
-    SET_PREJOIN_VIDEO_DISABLED,
18
-    SET_PREJOIN_VIDEO_MUTED
11
+    SET_PREJOIN_PAGE_VISIBILITY
19
 } from './actionTypes';
12
 } from './actionTypes';
20
 
13
 
21
 const DEFAULT_STATE = {
14
 const DEFAULT_STATE = {
22
-    audioDisabled: false,
23
-    audioMuted: false,
24
-    audioTrack: null,
25
-    contentSharingTrack: null,
26
     country: '',
15
     country: '',
27
     deviceStatusText: 'prejoin.configuringDevices',
16
     deviceStatusText: 'prejoin.configuringDevices',
28
     deviceStatusType: 'ok',
17
     deviceStatusType: 'ok',
37
     rawError: '',
26
     rawError: '',
38
     showPrejoin: true,
27
     showPrejoin: true,
39
     showJoinByPhoneDialog: false,
28
     showJoinByPhoneDialog: false,
40
-    userSelectedSkipPrejoin: false,
41
-    videoTrack: null,
42
-    videoDisabled: false,
43
-    videoMuted: false
29
+    userSelectedSkipPrejoin: false
44
 };
30
 };
45
 
31
 
46
 /**
32
 /**
49
 ReducerRegistry.register(
35
 ReducerRegistry.register(
50
     'features/prejoin', (state = DEFAULT_STATE, action) => {
36
     'features/prejoin', (state = DEFAULT_STATE, action) => {
51
         switch (action.type) {
37
         switch (action.type) {
52
-        case ADD_PREJOIN_AUDIO_TRACK: {
53
-            return {
54
-                ...state,
55
-                audioTrack: action.value
56
-            };
57
-        }
58
-
59
-        case ADD_PREJOIN_CONTENT_SHARING_TRACK: {
60
-            return {
61
-                ...state,
62
-                contentSharingTrack: action.value
63
-            };
64
-        }
65
-
66
-        case ADD_PREJOIN_VIDEO_TRACK: {
67
-            return {
68
-                ...state,
69
-                videoTrack: action.value
70
-            };
71
-        }
72
 
38
 
73
         case SET_SKIP_PREJOIN: {
39
         case SET_SKIP_PREJOIN: {
74
             return {
40
             return {
83
                 showPrejoin: action.value
49
                 showPrejoin: action.value
84
             };
50
             };
85
 
51
 
86
-        case SET_PREJOIN_VIDEO_DISABLED: {
87
-            return {
88
-                ...state,
89
-                videoDisabled: action.value
90
-            };
91
-        }
92
-
93
-        case SET_PREJOIN_VIDEO_MUTED:
94
-            return {
95
-                ...state,
96
-                videoMuted: action.value
97
-            };
98
-
99
-        case SET_PREJOIN_AUDIO_MUTED:
100
-            return {
101
-                ...state,
102
-                audioMuted: action.value
103
-            };
104
-
105
         case SET_PREJOIN_DEVICE_ERRORS: {
52
         case SET_PREJOIN_DEVICE_ERRORS: {
106
             const status = getStatusFromErrors(action.value);
53
             const status = getStatusFromErrors(action.value);
107
 
54
 
119
             };
66
             };
120
         }
67
         }
121
 
68
 
122
-        case SET_PREJOIN_AUDIO_DISABLED: {
123
-            return {
124
-                ...state,
125
-                audioDisabled: true
126
-            };
127
-        }
128
-
129
         case SET_DIALOUT_NUMBER: {
69
         case SET_DIALOUT_NUMBER: {
130
             return {
70
             return {
131
                 ...state,
71
                 ...state,

+ 2
- 17
react/features/toolbox/components/AudioMuteButton.js 查看文件

12
 import { AbstractAudioMuteButton } from '../../base/toolbox';
12
 import { AbstractAudioMuteButton } from '../../base/toolbox';
13
 import type { AbstractButtonProps } from '../../base/toolbox';
13
 import type { AbstractButtonProps } from '../../base/toolbox';
14
 import { isLocalTrackMuted } from '../../base/tracks';
14
 import { isLocalTrackMuted } from '../../base/tracks';
15
-import {
16
-    isPrejoinAudioMuted,
17
-    isAudioDisabled,
18
-    isPrejoinPageVisible
19
-} from '../../prejoin/functions';
20
 import { muteLocal } from '../../remote-video-menu/actions';
15
 import { muteLocal } from '../../remote-video-menu/actions';
21
 
16
 
22
 declare var APP: Object;
17
 declare var APP: Object;
154
  * }}
149
  * }}
155
  */
150
  */
156
 function _mapStateToProps(state): Object {
151
 function _mapStateToProps(state): Object {
157
-    let _audioMuted;
158
-    let _disabled;
159
-
160
-    if (isPrejoinPageVisible(state)) {
161
-        _audioMuted = isPrejoinAudioMuted(state);
162
-        _disabled = state['features/base/config'].startSilent;
163
-    } else {
164
-        const tracks = state['features/base/tracks'];
165
-
166
-        _audioMuted = isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO);
167
-        _disabled = state['features/base/config'].startSilent || isAudioDisabled(state);
168
-    }
152
+    const _audioMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.AUDIO);
153
+    const _disabled = state['features/base/config'].startSilent;
169
 
154
 
170
     return {
155
     return {
171
         _audioMuted,
156
         _audioMuted,

+ 3
- 14
react/features/toolbox/components/VideoMuteButton.js 查看文件

9
     sendAnalytics
9
     sendAnalytics
10
 } from '../../analytics';
10
 } from '../../analytics';
11
 import { setAudioOnly } from '../../base/audio-only';
11
 import { setAudioOnly } from '../../base/audio-only';
12
+import { hasAvailableDevices } from '../../base/devices';
12
 import { translate } from '../../base/i18n';
13
 import { translate } from '../../base/i18n';
13
 import {
14
 import {
14
     VIDEO_MUTISM_AUTHORITY,
15
     VIDEO_MUTISM_AUTHORITY,
18
 import { AbstractVideoMuteButton } from '../../base/toolbox';
19
 import { AbstractVideoMuteButton } from '../../base/toolbox';
19
 import type { AbstractButtonProps } from '../../base/toolbox';
20
 import type { AbstractButtonProps } from '../../base/toolbox';
20
 import { getLocalVideoType, isLocalVideoTrackMuted } from '../../base/tracks';
21
 import { getLocalVideoType, isLocalVideoTrackMuted } from '../../base/tracks';
21
-import {
22
-    isPrejoinPageVisible,
23
-    isPrejoinVideoDisabled,
24
-    isPrejoinVideoMuted
25
-} from '../../prejoin/functions';
26
 
22
 
27
 declare var APP: Object;
23
 declare var APP: Object;
28
 
24
 
191
 function _mapStateToProps(state): Object {
187
 function _mapStateToProps(state): Object {
192
     const { enabled: audioOnly } = state['features/base/audio-only'];
188
     const { enabled: audioOnly } = state['features/base/audio-only'];
193
     const tracks = state['features/base/tracks'];
189
     const tracks = state['features/base/tracks'];
194
-    let _videoMuted = isLocalVideoTrackMuted(tracks);
195
-    let _videoDisabled = false;
196
-
197
-    if (isPrejoinPageVisible(state)) {
198
-        _videoMuted = isPrejoinVideoMuted(state);
199
-        _videoDisabled = isPrejoinVideoDisabled(state);
200
-    }
201
 
190
 
202
     return {
191
     return {
203
         _audioOnly: Boolean(audioOnly),
192
         _audioOnly: Boolean(audioOnly),
204
-        _videoDisabled,
193
+        _videoDisabled: !hasAvailableDevices(state, 'videoInput'),
205
         _videoMediaType: getLocalVideoType(tracks),
194
         _videoMediaType: getLocalVideoType(tracks),
206
-        _videoMuted
195
+        _videoMuted: isLocalVideoTrackMuted(tracks)
207
     };
196
     };
208
 }
197
 }
209
 
198
 

+ 20
- 3
react/features/toolbox/components/web/VideoSettingsButton.js 查看文件

6
 import JitsiMeetJS from '../../../base/lib-jitsi-meet/_';
6
 import JitsiMeetJS from '../../../base/lib-jitsi-meet/_';
7
 import { connect } from '../../../base/redux';
7
 import { connect } from '../../../base/redux';
8
 import { ToolboxButtonWithIcon } from '../../../base/toolbox';
8
 import { ToolboxButtonWithIcon } from '../../../base/toolbox';
9
+import { getLocalJitsiVideoTrack } from '../../../base/tracks';
9
 import { getMediaPermissionPromptVisibility } from '../../../overlay';
10
 import { getMediaPermissionPromptVisibility } from '../../../overlay';
10
 import { toggleVideoSettings, VideoSettingsPopup } from '../../../settings';
11
 import { toggleVideoSettings, VideoSettingsPopup } from '../../../settings';
11
 import { isVideoSettingsButtonDisabled } from '../../functions';
12
 import { isVideoSettingsButtonDisabled } from '../../functions';
24
      */
25
      */
25
     permissionPromptVisibility: boolean,
26
     permissionPromptVisibility: boolean,
26
 
27
 
28
+    /**
29
+     * Whether there is a video track or not.
30
+     */
31
+    hasVideoTrack: boolean,
32
+
27
     /**
33
     /**
28
      * If the button should be disabled
34
      * If the button should be disabled
29
      */
35
      */
66
         };
72
         };
67
     }
73
     }
68
 
74
 
75
+    /**
76
+     * Returns true if the settings icon is disabled.
77
+     *
78
+     * @returns {boolean}
79
+     */
80
+    _isIconDisabled() {
81
+        const { hasVideoTrack, isDisabled } = this.props;
82
+
83
+        return (!this.state.hasPermissions || isDisabled) && !hasVideoTrack;
84
+    }
85
+
69
     /**
86
     /**
70
      * Updates device permissions.
87
      * Updates device permissions.
71
      *
88
      *
116
      * @inheritdoc
133
      * @inheritdoc
117
      */
134
      */
118
     render() {
135
     render() {
119
-        const { isDisabled, onVideoOptionsClick, visible } = this.props;
120
-        const iconDisabled = !this.state.hasPermissions || isDisabled;
136
+        const { onVideoOptionsClick, visible } = this.props;
121
 
137
 
122
         return visible ? (
138
         return visible ? (
123
             <VideoSettingsPopup>
139
             <VideoSettingsPopup>
124
                 <ToolboxButtonWithIcon
140
                 <ToolboxButtonWithIcon
125
                     icon = { IconArrowDown }
141
                     icon = { IconArrowDown }
126
-                    iconDisabled = { iconDisabled }
142
+                    iconDisabled = { this._isIconDisabled() }
127
                     onIconClick = { onVideoOptionsClick }>
143
                     onIconClick = { onVideoOptionsClick }>
128
                     <VideoMuteButton />
144
                     <VideoMuteButton />
129
                 </ToolboxButtonWithIcon>
145
                 </ToolboxButtonWithIcon>
140
  */
156
  */
141
 function mapStateToProps(state) {
157
 function mapStateToProps(state) {
142
     return {
158
     return {
159
+        hasVideoTrack: Boolean(getLocalJitsiVideoTrack(state)),
143
         isDisabled: isVideoSettingsButtonDisabled(state),
160
         isDisabled: isVideoSettingsButtonDisabled(state),
144
         permissionPromptVisibility: getMediaPermissionPromptVisibility(state)
161
         permissionPromptVisibility: getMediaPermissionPromptVisibility(state)
145
     };
162
     };

+ 4
- 16
react/features/toolbox/functions.web.js 查看文件

1
 // @flow
1
 // @flow
2
 
2
 
3
 import { hasAvailableDevices } from '../base/devices';
3
 import { hasAvailableDevices } from '../base/devices';
4
-import {
5
-    isAudioDisabled,
6
-    isPrejoinPageVisible,
7
-    isPrejoinVideoDisabled
8
-} from '../prejoin';
9
 
4
 
10
 declare var interfaceConfig: Object;
5
 declare var interfaceConfig: Object;
11
 
6
 
60
  * @returns {boolean}
55
  * @returns {boolean}
61
  */
56
  */
62
 export function isAudioSettingsButtonDisabled(state: Object) {
57
 export function isAudioSettingsButtonDisabled(state: Object) {
63
-    const devicesMissing = !hasAvailableDevices(state, 'audioInput')
64
-          && !hasAvailableDevices(state, 'audioOutput');
65
-
66
-    return isPrejoinPageVisible(state)
67
-        ? devicesMissing || isAudioDisabled(state)
68
-        : devicesMissing;
58
+    return (!hasAvailableDevices(state, 'audioInput')
59
+          && !hasAvailableDevices(state, 'audioOutput'))
60
+          || state['features/base/config'].startSilent;
69
 }
61
 }
70
 
62
 
71
 /**
63
 /**
75
  * @returns {boolean}
67
  * @returns {boolean}
76
  */
68
  */
77
 export function isVideoSettingsButtonDisabled(state: Object) {
69
 export function isVideoSettingsButtonDisabled(state: Object) {
78
-    const devicesMissing = !hasAvailableDevices(state, 'videoInput');
79
-
80
-    return isPrejoinPageVisible(state)
81
-        ? devicesMissing || isPrejoinVideoDisabled(state)
82
-        : devicesMissing;
70
+    return !hasAvailableDevices(state, 'videoInput');
83
 }
71
 }

Loading…
取消
儲存