瀏覽代碼

Make web use the redux settings/profile

j8
zbettenbuk 7 年之前
父節點
當前提交
959db3a665

+ 0
- 2
app.js 查看文件

15
 import API from './modules/API';
15
 import API from './modules/API';
16
 import keyboardshortcut from './modules/keyboardshortcut/keyboardshortcut';
16
 import keyboardshortcut from './modules/keyboardshortcut/keyboardshortcut';
17
 import remoteControl from './modules/remotecontrol/RemoteControl';
17
 import remoteControl from './modules/remotecontrol/RemoteControl';
18
-import settings from './modules/settings/Settings';
19
 import translation from './modules/translation/translation';
18
 import translation from './modules/translation/translation';
20
 import UI from './modules/UI/UI';
19
 import UI from './modules/UI/UI';
21
 
20
 
41
 
40
 
42
     keyboardshortcut,
41
     keyboardshortcut,
43
     remoteControl,
42
     remoteControl,
44
-    settings,
45
     translation,
43
     translation,
46
     UI
44
     UI
47
 };
45
 };

+ 35
- 15
conference.js 查看文件

46
     sendLocalParticipant,
46
     sendLocalParticipant,
47
     setDesktopSharingEnabled
47
     setDesktopSharingEnabled
48
 } from './react/features/base/conference';
48
 } from './react/features/base/conference';
49
-import { updateDeviceList } from './react/features/base/devices';
49
+import {
50
+    setAudioOutputDeviceId,
51
+    updateDeviceList
52
+} from './react/features/base/devices';
50
 import {
53
 import {
51
     isFatalJitsiConnectionError,
54
     isFatalJitsiConnectionError,
52
     JitsiConferenceErrors,
55
     JitsiConferenceErrors,
82
     participantRoleChanged,
85
     participantRoleChanged,
83
     participantUpdated
86
     participantUpdated
84
 } from './react/features/base/participants';
87
 } from './react/features/base/participants';
88
+import { updateSettings } from './react/features/base/settings';
85
 import {
89
 import {
86
     createLocalTracksF,
90
     createLocalTracksF,
87
     isLocalTrackMuted,
91
     isLocalTrackMuted,
1273
                     : 'colibri';
1277
                     : 'colibri';
1274
         }
1278
         }
1275
 
1279
 
1276
-        const nick = APP.settings.getDisplayName();
1280
+        const nick = APP.store.getState()['features/base/settings'].displayName;
1277
 
1281
 
1278
         if (nick) {
1282
         if (nick) {
1279
             options.displayName = nick;
1283
             options.displayName = nick;
2131
                 })
2135
                 })
2132
                 .then(() => {
2136
                 .then(() => {
2133
                     logger.log('switched local video device');
2137
                     logger.log('switched local video device');
2134
-                    APP.settings.setCameraDeviceId(cameraDeviceId, true);
2138
+                    APP.store.dispatch(updateSettings({
2139
+                        cameraDeviceId
2140
+                    }));
2135
                 })
2141
                 })
2136
                 .catch(err => {
2142
                 .catch(err => {
2137
                     APP.UI.showCameraErrorNotification(err);
2143
                     APP.UI.showCameraErrorNotification(err);
2163
                 .then(stream => {
2169
                 .then(stream => {
2164
                     this.useAudioStream(stream);
2170
                     this.useAudioStream(stream);
2165
                     logger.log('switched local audio device');
2171
                     logger.log('switched local audio device');
2166
-                    APP.settings.setMicDeviceId(micDeviceId, true);
2172
+                    APP.store.dispatch(updateSettings({
2173
+                        micDeviceId
2174
+                    }));
2167
                 })
2175
                 })
2168
                 .catch(err => {
2176
                 .catch(err => {
2169
                     APP.UI.showMicErrorNotification(err);
2177
                     APP.UI.showMicErrorNotification(err);
2175
             UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
2183
             UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
2176
             audioOutputDeviceId => {
2184
             audioOutputDeviceId => {
2177
                 sendAnalytics(createDeviceChangedEvent('audio', 'output'));
2185
                 sendAnalytics(createDeviceChangedEvent('audio', 'output'));
2178
-                APP.settings.setAudioOutputDeviceId(audioOutputDeviceId)
2186
+                setAudioOutputDeviceId(audioOutputDeviceId)
2179
                     .then(() => logger.log('changed audio output device'))
2187
                     .then(() => logger.log('changed audio output device'))
2180
                     .catch(err => {
2188
                     .catch(err => {
2181
                         logger.warn('Failed to change audio output device. '
2189
                         logger.warn('Failed to change audio output device. '
2317
         APP.store.dispatch(conferenceJoined(room));
2325
         APP.store.dispatch(conferenceJoined(room));
2318
 
2326
 
2319
         APP.UI.mucJoined();
2327
         APP.UI.mucJoined();
2320
-        const displayName = APP.settings.getDisplayName();
2328
+        const displayName
2329
+            = APP.store.getState()['features/base/settings'].displayName;
2321
 
2330
 
2322
         APP.API.notifyConferenceJoined(
2331
         APP.API.notifyConferenceJoined(
2323
             this.roomName,
2332
             this.roomName,
2367
                         // storage and settings menu. This is a workaround until
2376
                         // storage and settings menu. This is a workaround until
2368
                         // getConstraints() method will be implemented
2377
                         // getConstraints() method will be implemented
2369
                         // in browsers.
2378
                         // in browsers.
2379
+                        const { dispatch } = APP.store;
2380
+
2370
                         if (this.localAudio) {
2381
                         if (this.localAudio) {
2371
-                            APP.settings.setMicDeviceId(
2372
-                                this.localAudio.getDeviceId(), false);
2382
+                            dispatch(updateSettings({
2383
+                                micDeviceId: this.localAudio.getDeviceId()
2384
+                            }));
2373
                         }
2385
                         }
2374
 
2386
 
2375
                         if (this.localVideo) {
2387
                         if (this.localVideo) {
2376
-                            APP.settings.setCameraDeviceId(
2377
-                                this.localVideo.getDeviceId(), false);
2388
+                            dispatch(updateSettings({
2389
+                                cameraDeviceId: this.localVideo.getDeviceId()
2390
+                            }));
2378
                         }
2391
                         }
2379
 
2392
 
2380
                         mediaDeviceHelper.setCurrentMediaDevices(devices);
2393
                         mediaDeviceHelper.setCurrentMediaDevices(devices);
2426
 
2439
 
2427
         if (typeof newDevices.audiooutput !== 'undefined') {
2440
         if (typeof newDevices.audiooutput !== 'undefined') {
2428
             // Just ignore any errors in catch block.
2441
             // Just ignore any errors in catch block.
2429
-            promises.push(APP.settings
2430
-                .setAudioOutputDeviceId(newDevices.audiooutput)
2442
+            promises.push(setAudioOutputDeviceId(newDevices.audiooutput)
2431
                 .catch());
2443
                 .catch());
2432
         }
2444
         }
2433
 
2445
 
2576
             email: formattedEmail
2588
             email: formattedEmail
2577
         }));
2589
         }));
2578
 
2590
 
2579
-        APP.settings.setEmail(formattedEmail);
2591
+        APP.store.dispatch(updateSettings({
2592
+            email: formattedEmail
2593
+        }));
2594
+
2580
         APP.UI.setUserEmail(localId, formattedEmail);
2595
         APP.UI.setUserEmail(localId, formattedEmail);
2581
         sendData(commands.EMAIL, formattedEmail);
2596
         sendData(commands.EMAIL, formattedEmail);
2582
     },
2597
     },
2600
             avatarURL: formattedUrl
2615
             avatarURL: formattedUrl
2601
         }));
2616
         }));
2602
 
2617
 
2603
-        APP.settings.setAvatarUrl(url);
2618
+        APP.store.dispatch(updateSettings({
2619
+            avatarURL: formattedUrl
2620
+        }));
2604
         sendData(commands.AVATAR_URL, url);
2621
         sendData(commands.AVATAR_URL, url);
2605
     },
2622
     },
2606
 
2623
 
2654
             name: formattedNickname
2671
             name: formattedNickname
2655
         }));
2672
         }));
2656
 
2673
 
2657
-        APP.settings.setDisplayName(formattedNickname);
2674
+        APP.store.dispatch(updateSettings({
2675
+            displayName: formattedNickname
2676
+        }));
2677
+
2658
         APP.API.notifyDisplayNameChanged(id, {
2678
         APP.API.notifyDisplayNameChanged(id, {
2659
             displayName: formattedNickname,
2679
             displayName: formattedNickname,
2660
             formattedDisplayName:
2680
             formattedDisplayName:

+ 4
- 3
modules/UI/side_pannels/profile/Profile.js 查看文件

1
 /* global $, APP */
1
 /* global $, APP */
2
 import UIUtil from '../../util/UIUtil';
2
 import UIUtil from '../../util/UIUtil';
3
 import UIEvents from '../../../../service/UI/UIEvents';
3
 import UIEvents from '../../../../service/UI/UIEvents';
4
-import Settings from '../../../settings/Settings';
5
 
4
 
6
 import {
5
 import {
7
     createProfilePanelButtonEvent,
6
     createProfilePanelButtonEvent,
54
     init(emitter) {
53
     init(emitter) {
55
         initHTML();
54
         initHTML();
56
 
55
 
56
+        const settings = APP.store.getState()['features/base/settings'];
57
+
57
         /**
58
         /**
58
          * Updates display name.
59
          * Updates display name.
59
          *
60
          *
64
         }
65
         }
65
 
66
 
66
         $('#setDisplayName')
67
         $('#setDisplayName')
67
-            .val(Settings.getDisplayName())
68
+            .val(settings.displayName)
68
             .keyup(event => {
69
             .keyup(event => {
69
                 if (event.keyCode === 13) { // enter
70
                 if (event.keyCode === 13) { // enter
70
                     updateDisplayName();
71
                     updateDisplayName();
82
         }
83
         }
83
 
84
 
84
         $('#setEmail')
85
         $('#setEmail')
85
-            .val(Settings.getEmail())
86
+            .val(settings.email)
86
             .keyup(event => {
87
             .keyup(event => {
87
                 if (event.keyCode === 13) { // enter
88
                 if (event.keyCode === 13) { // enter
88
                     updateEmail();
89
                     updateEmail();

+ 0
- 11
modules/UI/util/UIUtil.js 查看文件

74
             .html();
74
             .html();
75
     },
75
     },
76
 
76
 
77
-    /**
78
-     * Unescapes the given text.
79
-     *
80
-     * @param {string} safe string which contains escaped html
81
-     * @returns {string} unescaped html string.
82
-     */
83
-    unescapeHtml(safe) {
84
-        return $('<div />').html(safe)
85
-            .text();
86
-    },
87
-
88
     imageToGrayScale(canvas) {
77
     imageToGrayScale(canvas) {
89
         const context = canvas.getContext('2d');
78
         const context = canvas.getContext('2d');
90
         const imgData = context.getImageData(0, 0, canvas.width, canvas.height);
79
         const imgData = context.getImageData(0, 0, canvas.width, canvas.height);

+ 9
- 3
modules/UI/videolayout/LocalVideo.js 查看文件

10
 import {
10
 import {
11
     getAvatarURLByParticipantId
11
     getAvatarURLByParticipantId
12
 } from '../../../react/features/base/participants';
12
 } from '../../../react/features/base/participants';
13
+import { updateSettings } from '../../../react/features/base/settings';
13
 /* eslint-enable no-unused-vars */
14
 /* eslint-enable no-unused-vars */
14
 
15
 
15
 const logger = require('jitsi-meet-logger').getLogger(__filename);
16
 const logger = require('jitsi-meet-logger').getLogger(__filename);
121
 
122
 
122
     // eslint-disable-next-line eqeqeq
123
     // eslint-disable-next-line eqeqeq
123
     const isVideo = stream.videoType != 'desktop';
124
     const isVideo = stream.videoType != 'desktop';
125
+    const settings = APP.store.getState()['features/base/settings'];
124
 
126
 
125
     this._enableDisableContextMenu(isVideo);
127
     this._enableDisableContextMenu(isVideo);
126
-    this.setFlipX(isVideo ? APP.settings.getLocalFlipX() : false);
128
+    this.setFlipX(isVideo ? settings.localFlipX : false);
127
 
129
 
128
     const endedHandler = () => {
130
     const endedHandler = () => {
129
 
131
 
194
             flip: {
196
             flip: {
195
                 name: 'Flip',
197
                 name: 'Flip',
196
                 callback: () => {
198
                 callback: () => {
197
-                    const val = !APP.settings.getLocalFlipX();
199
+                    const { store } = APP;
200
+                    const val = !store.getState()['features/base/settings']
201
+                    .localFlipX;
198
 
202
 
199
                     this.setFlipX(val);
203
                     this.setFlipX(val);
200
-                    APP.settings.setLocalFlipX(val);
204
+                    store.dispatch(updateSettings({
205
+                        localFlipX: val
206
+                    }));
201
                 }
207
                 }
202
             }
208
             }
203
         },
209
         },

+ 7
- 3
modules/devices/mediaDeviceHelper.js 查看文件

1
 /* global APP, JitsiMeetJS */
1
 /* global APP, JitsiMeetJS */
2
 
2
 
3
+import { getAudioOutputDeviceId } from '../../react/features/base/devices';
4
+
3
 let currentAudioInputDevices,
5
 let currentAudioInputDevices,
4
     currentAudioOutputDevices,
6
     currentAudioOutputDevices,
5
     currentVideoInputDevices;
7
     currentVideoInputDevices;
16
         return;
18
         return;
17
     }
19
     }
18
 
20
 
19
-    const selectedAudioOutputDeviceId = APP.settings.getAudioOutputDeviceId();
21
+    const selectedAudioOutputDeviceId = getAudioOutputDeviceId();
20
     const availableAudioOutputDevices = newDevices.filter(
22
     const availableAudioOutputDevices = newDevices.filter(
21
         d => d.kind === 'audiooutput');
23
         d => d.kind === 'audiooutput');
22
 
24
 
40
 function getNewAudioInputDevice(newDevices, localAudio) {
42
 function getNewAudioInputDevice(newDevices, localAudio) {
41
     const availableAudioInputDevices = newDevices.filter(
43
     const availableAudioInputDevices = newDevices.filter(
42
         d => d.kind === 'audioinput');
44
         d => d.kind === 'audioinput');
43
-    const selectedAudioInputDeviceId = APP.settings.getMicDeviceId();
45
+    const settings = APP.store.getState()['features/base/settings'];
46
+    const selectedAudioInputDeviceId = settings.micDeviceId;
44
     const selectedAudioInputDevice = availableAudioInputDevices.find(
47
     const selectedAudioInputDevice = availableAudioInputDevices.find(
45
         d => d.deviceId === selectedAudioInputDeviceId);
48
         d => d.deviceId === selectedAudioInputDeviceId);
46
 
49
 
76
 function getNewVideoInputDevice(newDevices, localVideo) {
79
 function getNewVideoInputDevice(newDevices, localVideo) {
77
     const availableVideoInputDevices = newDevices.filter(
80
     const availableVideoInputDevices = newDevices.filter(
78
         d => d.kind === 'videoinput');
81
         d => d.kind === 'videoinput');
79
-    const selectedVideoInputDeviceId = APP.settings.getCameraDeviceId();
82
+    const settings = APP.store.getState()['features/base/settings'];
83
+    const selectedVideoInputDeviceId = settings.cameraDeviceId;
80
     const selectedVideoInputDevice = availableVideoInputDevices.find(
84
     const selectedVideoInputDevice = availableVideoInputDevices.find(
81
         d => d.deviceId === selectedVideoInputDeviceId);
85
         d => d.deviceId === selectedVideoInputDeviceId);
82
 
86
 

+ 0
- 191
modules/settings/Settings.js 查看文件

1
-/* global JitsiMeetJS */
2
-const logger = require('jitsi-meet-logger').getLogger(__filename);
3
-
4
-import UIUtil from '../UI/util/UIUtil';
5
-import jitsiLocalStorage from '../util/JitsiLocalStorage';
6
-import { randomHexString } from '../../react/features/base/util';
7
-
8
-let avatarUrl = '';
9
-
10
-let email = UIUtil.unescapeHtml(jitsiLocalStorage.getItem('email') || '');
11
-let avatarId = UIUtil.unescapeHtml(jitsiLocalStorage.getItem('avatarId') || '');
12
-
13
-if (!avatarId) {
14
-    // if there is no avatar id, we generate a unique one and use it forever
15
-    avatarId = randomHexString(32);
16
-    jitsiLocalStorage.setItem('avatarId', avatarId);
17
-}
18
-
19
-let localFlipX = JSON.parse(jitsiLocalStorage.getItem('localFlipX') || true);
20
-let displayName = UIUtil.unescapeHtml(
21
-    jitsiLocalStorage.getItem('displayname') || '');
22
-let cameraDeviceId = jitsiLocalStorage.getItem('cameraDeviceId') || '';
23
-let micDeviceId = jitsiLocalStorage.getItem('micDeviceId') || '';
24
-
25
-// Currently audio output device change is supported only in Chrome and
26
-// default output always has 'default' device ID
27
-const audioOutputDeviceId = jitsiLocalStorage.getItem('audioOutputDeviceId')
28
-    || 'default';
29
-
30
-if (audioOutputDeviceId
31
-    !== JitsiMeetJS.mediaDevices.getAudioOutputDevice()) {
32
-    JitsiMeetJS.mediaDevices.setAudioOutputDevice(audioOutputDeviceId)
33
-        .catch(ex => {
34
-            logger.warn('Failed to set audio output device from local '
35
-                + 'storage. Default audio output device will be used'
36
-                + 'instead.', ex);
37
-        });
38
-}
39
-
40
-export default {
41
-
42
-    /**
43
-     * Sets the local user display name and saves it to local storage
44
-     *
45
-     * @param {string} newDisplayName unescaped display name for the local user
46
-     * @param {boolean} disableLocalStore disables local store the display name
47
-     */
48
-    setDisplayName(newDisplayName, disableLocalStore) {
49
-        displayName = newDisplayName;
50
-
51
-        if (!disableLocalStore) {
52
-            jitsiLocalStorage.setItem('displayname',
53
-                UIUtil.escapeHtml(displayName));
54
-        }
55
-    },
56
-
57
-    /**
58
-     * Returns the escaped display name currently used by the user
59
-     * @returns {string} currently valid user display name.
60
-     */
61
-    getDisplayName() {
62
-        return displayName;
63
-    },
64
-
65
-    /**
66
-     * Sets new email for local user and saves it to the local storage.
67
-     * @param {string} newEmail new email for the local user
68
-     * @param {boolean} disableLocalStore disables local store the email
69
-     */
70
-    setEmail(newEmail, disableLocalStore) {
71
-        email = newEmail;
72
-
73
-        if (!disableLocalStore) {
74
-            jitsiLocalStorage.setItem('email', UIUtil.escapeHtml(newEmail));
75
-        }
76
-    },
77
-
78
-    /**
79
-     * Returns email address of the local user.
80
-     * @returns {string} email
81
-     */
82
-    getEmail() {
83
-        return email;
84
-    },
85
-
86
-    /**
87
-     * Returns avatar id of the local user.
88
-     * @returns {string} avatar id
89
-     */
90
-    getAvatarId() {
91
-        return avatarId;
92
-    },
93
-
94
-    /**
95
-     * Sets new avatarUrl for local user and saves it to the local storage.
96
-     * @param {string} newAvatarUrl new avatarUrl for the local user
97
-     */
98
-    setAvatarUrl(newAvatarUrl) {
99
-        avatarUrl = newAvatarUrl;
100
-    },
101
-
102
-    /**
103
-     * Returns avatarUrl address of the local user.
104
-     * @returns {string} avatarUrl
105
-     */
106
-    getAvatarUrl() {
107
-        return avatarUrl;
108
-    },
109
-
110
-    /**
111
-     * Sets new flipX state of local video and saves it to the local storage.
112
-     * @param {string} val flipX state of local video
113
-     */
114
-    setLocalFlipX(val) {
115
-        localFlipX = val;
116
-        jitsiLocalStorage.setItem('localFlipX', val);
117
-    },
118
-
119
-    /**
120
-     * Returns flipX state of local video.
121
-     * @returns {string} flipX
122
-     */
123
-    getLocalFlipX() {
124
-        return localFlipX;
125
-    },
126
-
127
-    /**
128
-     * Get device id of the camera which is currently in use.
129
-     * Empty string stands for default device.
130
-     * @returns {String}
131
-     */
132
-    getCameraDeviceId() {
133
-        return cameraDeviceId;
134
-    },
135
-
136
-    /**
137
-     * Set device id of the camera which is currently in use.
138
-     * Empty string stands for default device.
139
-     * @param {string} newId new camera device id
140
-     * @param {boolean} whether we need to store the value
141
-     */
142
-    setCameraDeviceId(newId, store) {
143
-        cameraDeviceId = newId;
144
-        if (store) {
145
-            jitsiLocalStorage.setItem('cameraDeviceId', newId);
146
-        }
147
-    },
148
-
149
-    /**
150
-     * Get device id of the microphone which is currently in use.
151
-     * Empty string stands for default device.
152
-     * @returns {String}
153
-     */
154
-    getMicDeviceId() {
155
-        return micDeviceId;
156
-    },
157
-
158
-    /**
159
-     * Set device id of the microphone which is currently in use.
160
-     * Empty string stands for default device.
161
-     * @param {string} newId new microphone device id
162
-     * @param {boolean} whether we need to store the value
163
-     */
164
-    setMicDeviceId(newId, store) {
165
-        micDeviceId = newId;
166
-        if (store) {
167
-            jitsiLocalStorage.setItem('micDeviceId', newId);
168
-        }
169
-    },
170
-
171
-    /**
172
-     * Get device id of the audio output device which is currently in use.
173
-     * Empty string stands for default device.
174
-     * @returns {String}
175
-     */
176
-    getAudioOutputDeviceId() {
177
-        return JitsiMeetJS.mediaDevices.getAudioOutputDevice();
178
-    },
179
-
180
-    /**
181
-     * Set device id of the audio output device which is currently in use.
182
-     * Empty string stands for default device.
183
-     * @param {string} newId='default' - new audio output device id
184
-     * @returns {Promise}
185
-     */
186
-    setAudioOutputDeviceId(newId = 'default') {
187
-        return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
188
-            .then(() =>
189
-                jitsiLocalStorage.setItem('audioOutputDeviceId', newId));
190
-    }
191
-};

+ 12
- 22
react/features/app/components/AbstractApp.js 查看文件

12
     localParticipantJoined,
12
     localParticipantJoined,
13
     localParticipantLeft
13
     localParticipantLeft
14
 } from '../../base/participants';
14
 } from '../../base/participants';
15
-import '../../base/profile';
16
 import { Fragment, RouteRegistry } from '../../base/react';
15
 import { Fragment, RouteRegistry } from '../../base/react';
17
 import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
16
 import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
18
 import { SoundCollection } from '../../base/sounds';
17
 import { SoundCollection } from '../../base/sounds';
26
 /**
25
 /**
27
  * The default URL to open if no other was specified to {@code AbstractApp}
26
  * The default URL to open if no other was specified to {@code AbstractApp}
28
  * via props.
27
  * via props.
28
+ *
29
+ * FIXME: This is not at the best place here. This should be either in the
30
+ * base/settings feature or a default in base/config.
29
  */
31
  */
30
 const DEFAULT_URL = 'https://meet.jit.si';
32
 const DEFAULT_URL = 'https://meet.jit.si';
31
 
33
 
133
             // actions is important, not the call site. Moreover, we've got
135
             // actions is important, not the call site. Moreover, we've got
134
             // localParticipant business logic in the React Component
136
             // localParticipant business logic in the React Component
135
             // (i.e. UI) AbstractApp now.
137
             // (i.e. UI) AbstractApp now.
136
-            let localParticipant = {};
137
-
138
-            if (typeof APP === 'object') {
139
-                localParticipant = {
140
-                    avatarID: APP.settings.getAvatarId(),
141
-                    avatarURL: APP.settings.getAvatarUrl(),
142
-                    email: APP.settings.getEmail(),
143
-                    name: APP.settings.getDisplayName()
144
-                };
145
-            }
146
 
138
 
147
-            // Profile is the new React compatible settings.
148
-            const profile = getState()['features/base/profile'];
149
-
150
-            if (profile) {
151
-                localParticipant.email
152
-                    = profile.email || localParticipant.email;
153
-                localParticipant.name
154
-                    = profile.displayName || localParticipant.name;
155
-            }
139
+            const settings = getState()['features/base/settings'];
140
+            const localParticipant = {
141
+                avatarID: settings.avatarID,
142
+                avatarURL: settings.avatarURL,
143
+                email: settings.email,
144
+                name: settings.displayName
145
+            };
156
 
146
 
157
             // We set the initialized state here and not in the contructor to
147
             // We set the initialized state here and not in the contructor to
158
             // make sure that {@code componentWillMount} gets invoked before
148
             // make sure that {@code componentWillMount} gets invoked before
383
 
373
 
384
         return (
374
         return (
385
             this.props.defaultURL
375
             this.props.defaultURL
386
-                || this._getStore().getState()['features/base/profile']
387
-                    .serverURL
376
+                || this._getStore().getState()['features/base/settings']
377
+                .serverURL
388
                 || DEFAULT_URL);
378
                 || DEFAULT_URL);
389
     }
379
     }
390
 
380
 

+ 32
- 0
react/features/base/devices/functions.js 查看文件

1
+// @flow
2
+
3
+import JitsiMeetJS from '../lib-jitsi-meet';
4
+import { updateSettings } from '../settings';
5
+
6
+/**
7
+ * Get device id of the audio output device which is currently in use.
8
+ * Empty string stands for default device.
9
+ *
10
+ * @returns {string}
11
+ */
12
+export function getAudioOutputDeviceId() {
13
+    return JitsiMeetJS.mediaDevices.getAudioOutputDevice();
14
+}
15
+
16
+/**
17
+ * Set device id of the audio output device which is currently in use.
18
+ * Empty string stands for default device.
19
+ *
20
+ * @param {string} newId - New audio output device id.
21
+ * @param {Function} dispatch - The Redux dispatch function.
22
+ * @returns {Promise}
23
+ */
24
+export function setAudioOutputDeviceId(
25
+        newId: string = 'default',
26
+        dispatch: Function): Promise<*> {
27
+    return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
28
+        .then(() =>
29
+            dispatch(updateSettings({
30
+                audioOutputDeviceId: newId
31
+            })));
32
+}

+ 1
- 0
react/features/base/devices/index.js 查看文件

1
 export * from './actions';
1
 export * from './actions';
2
 export * from './actionTypes';
2
 export * from './actionTypes';
3
+export * from './functions';
3
 
4
 
4
 import './middleware';
5
 import './middleware';
5
 import './reducer';
6
 import './reducer';

+ 6
- 6
react/features/base/media/middleware.js 查看文件

8
 } from '../../analytics';
8
 } from '../../analytics';
9
 import { isRoomValid, SET_ROOM, setAudioOnly } from '../conference';
9
 import { isRoomValid, SET_ROOM, setAudioOnly } from '../conference';
10
 import JitsiMeetJS from '../lib-jitsi-meet';
10
 import JitsiMeetJS from '../lib-jitsi-meet';
11
-import { getPropertyValue } from '../profile';
12
 import { MiddlewareRegistry } from '../redux';
11
 import { MiddlewareRegistry } from '../redux';
12
+import { getPropertyValue } from '../settings';
13
 import { setTrackMuted, TRACK_ADDED } from '../tracks';
13
 import { setTrackMuted, TRACK_ADDED } from '../tracks';
14
 
14
 
15
 import { setAudioMuted, setCameraFacingMode, setVideoMuted } from './actions';
15
 import { setAudioMuted, setCameraFacingMode, setVideoMuted } from './actions';
79
     const mutedSources = {
79
     const mutedSources = {
80
         // We have startWithAudioMuted and startWithVideoMuted here:
80
         // We have startWithAudioMuted and startWithVideoMuted here:
81
         config: true,
81
         config: true,
82
-        profile: true,
82
+        settings: true,
83
 
83
 
84
         // XXX We've already overwritten base/config with urlParams. However,
84
         // XXX We've already overwritten base/config with urlParams. However,
85
-        // profile is more important than the server-side config. Consequently,
86
-        // we need to read from urlParams anyway:
85
+        // settings are more important than the server-side config.
86
+        // Consequently, we need to read from urlParams anyway:
87
         urlParams: true,
87
         urlParams: true,
88
 
88
 
89
         // We don't have startWithAudioMuted and startWithVideoMuted here:
89
         // We don't have startWithAudioMuted and startWithVideoMuted here:
141
                         config: roomIsValid,
141
                         config: roomIsValid,
142
 
142
 
143
                         // XXX We've already overwritten base/config with
143
                         // XXX We've already overwritten base/config with
144
-                        // urlParams if roomIsValid. However, profile is more
144
+                        // urlParams if roomIsValid. However, settings are more
145
                         // important than the server-side config. Consequently,
145
                         // important than the server-side config. Consequently,
146
                         // we need to read from urlParams anyway. We also
146
                         // we need to read from urlParams anyway. We also
147
                         // probably want to read from urlParams when
147
                         // probably want to read from urlParams when
151
                         // The following don't have complications around whether
151
                         // The following don't have complications around whether
152
                         // they are defined or not:
152
                         // they are defined or not:
153
                         jwt: false,
153
                         jwt: false,
154
-                        profile: true
154
+                        settings: true
155
                     }));
155
                     }));
156
     } else {
156
     } else {
157
         // Default to audio-only if the (execution) environment does not
157
         // Default to audio-only if the (execution) environment does not

+ 0
- 15
react/features/base/profile/actionTypes.js 查看文件

1
-/**
2
- * Create an action for when the local profile is updated.
3
- *
4
- * {
5
- *     type: PROFILE_UPDATED,
6
- *     profile: {
7
- *         displayName: string,
8
- *         defaultURL: URL,
9
- *         email: string,
10
- *         startWithAudioMuted: boolean,
11
- *         startWithVideoMuted: boolean
12
- *     }
13
- * }
14
- */
15
-export const PROFILE_UPDATED = Symbol('PROFILE_UPDATED');

+ 0
- 23
react/features/base/profile/actions.js 查看文件

1
-import { PROFILE_UPDATED } from './actionTypes';
2
-
3
-/**
4
- * Create an action for when the local profile is updated.
5
- *
6
- * @param {Object} profile - The new profile data.
7
- * @returns {{
8
- *     type: UPDATE_PROFILE,
9
- *     profile: {
10
- *         displayName: string,
11
- *         defaultURL: URL,
12
- *         email: string,
13
- *         startWithAudioMuted: boolean,
14
- *         startWithVideoMuted: boolean
15
- *     }
16
- * }}
17
- */
18
-export function updateProfile(profile) {
19
-    return {
20
-        type: PROFILE_UPDATED,
21
-        profile
22
-    };
23
-}

+ 0
- 53
react/features/base/profile/reducer.js 查看文件

1
-// @flow
2
-
3
-import { APP_WILL_MOUNT } from '../../app';
4
-import { ReducerRegistry } from '../redux';
5
-import { PersistenceRegistry } from '../storage';
6
-
7
-import { PROFILE_UPDATED } from './actionTypes';
8
-
9
-/**
10
- * The default/initial redux state of the feature {@code base/profile}.
11
- *
12
- * @type Object
13
- */
14
-const DEFAULT_STATE = {};
15
-
16
-const STORE_NAME = 'features/base/profile';
17
-
18
-/**
19
- * Sets up the persistence of the feature {@code base/profile}.
20
- */
21
-PersistenceRegistry.register(STORE_NAME);
22
-
23
-ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
24
-    switch (action.type) {
25
-    case APP_WILL_MOUNT:
26
-        // XXX APP_WILL_MOUNT is the earliest redux action of ours dispatched in
27
-        // the store. For the purposes of legacy support, make sure that the
28
-        // deserialized base/profile's state is in the format deemed current by
29
-        // the current app revision.
30
-        if (state && typeof state === 'object') {
31
-            // In an enterprise/internal build of Jitsi Meet for Android and iOS
32
-            // we had base/profile's state as an object with property profile.
33
-            const { profile } = state;
34
-
35
-            if (profile && typeof profile === 'object') {
36
-                return { ...profile };
37
-            }
38
-        } else {
39
-            // In the weird case that we have previously persisted/serialized
40
-            // null.
41
-            return DEFAULT_STATE;
42
-        }
43
-        break;
44
-
45
-    case PROFILE_UPDATED:
46
-        return {
47
-            ...state,
48
-            ...action.profile
49
-        };
50
-    }
51
-
52
-    return state;
53
-});

+ 22
- 0
react/features/base/settings/actionTypes.js 查看文件

1
+/**
2
+ * Create an action for when the settings are updated.
3
+ *
4
+ * {
5
+ *     type: SETTINGS_UPDATED,
6
+ *     settings: {
7
+ *         audioOutputDeviceId: string,
8
+ *         avatarID: string,
9
+ *         avatarURL: string,
10
+ *         cameraDeviceId: string,
11
+ *         displayName: string,
12
+ *         email: string,
13
+ *         localFlipX: boolean,
14
+ *         micDeviceId: string,
15
+ *         serverURL: string,
16
+ *         startAudioOnly: boolean,
17
+ *         startWithAudioMuted: boolean,
18
+ *         startWithVideoMuted: boolean
19
+ *     }
20
+ * }
21
+ */
22
+export const SETTINGS_UPDATED = Symbol('SETTINGS_UPDATED');

+ 30
- 0
react/features/base/settings/actions.js 查看文件

1
+import { SETTINGS_UPDATED } from './actionTypes';
2
+
3
+/**
4
+ * Create an action for when the settings are updated.
5
+ *
6
+ * @param {Object} settings - The new (partial) settings properties.
7
+ * @returns {{
8
+ *     type: SETTINGS_UPDATED,
9
+ *     settings: {
10
+ *         audioOutputDeviceId: string,
11
+ *         avatarID: string,
12
+ *         avatarURL: string,
13
+ *         cameraDeviceId: string,
14
+ *         displayName: string,
15
+ *         email: string,
16
+ *         localFlipX: boolean,
17
+ *         micDeviceId: string,
18
+ *         serverURL: string,
19
+ *         startAudioOnly: boolean,
20
+ *         startWithAudioMuted: boolean,
21
+ *         startWithVideoMuted: boolean
22
+ *     }
23
+ * }}
24
+ */
25
+export function updateSettings(settings) {
26
+    return {
27
+        type: SETTINGS_UPDATED,
28
+        settings
29
+    };
30
+}

react/features/base/profile/functions.js → react/features/base/settings/functions.js 查看文件

3
 import { parseURLParams } from '../config';
3
 import { parseURLParams } from '../config';
4
 import { toState } from '../redux';
4
 import { toState } from '../redux';
5
 
5
 
6
+
6
 /**
7
 /**
7
  * Returns the effective value of a configuration/preference/setting by applying
8
  * Returns the effective value of a configuration/preference/setting by applying
8
- * a precedence among the values specified by JWT, URL, profile, and config.
9
+ * a precedence among the values specified by JWT, URL, settings,
10
+ * and config.
9
  *
11
  *
10
  * @param {Object|Function} stateful - The redux state object or
12
  * @param {Object|Function} stateful - The redux state object or
11
  * {@code getState} function.
13
  * {@code getState} function.
14
  * @param {{
16
  * @param {{
15
  *     config: boolean,
17
  *     config: boolean,
16
  *     jwt: boolean,
18
  *     jwt: boolean,
17
- *     profile: boolean,
19
+ *     settings: boolean,
18
  *     urlParams: boolean
20
  *     urlParams: boolean
19
  * }} [sources] - A set/structure of {@code boolean} flags indicating the
21
  * }} [sources] - A set/structure of {@code boolean} flags indicating the
20
  * configuration/preference/setting sources to consider/retrieve values from.
22
  * configuration/preference/setting sources to consider/retrieve values from.
31
         // Defaults:
33
         // Defaults:
32
         config: true,
34
         config: true,
33
         jwt: true,
35
         jwt: true,
34
-        profile: true,
36
+        settings: true,
35
         urlParams: true,
37
         urlParams: true,
36
 
38
 
37
         ...sources
39
         ...sources
38
     };
40
     };
39
 
41
 
40
-    // Precedence: jwt -> urlParams -> profile -> config.
42
+    // Precedence: jwt -> urlParams -> settings -> config.
41
 
43
 
42
     const state = toState(stateful);
44
     const state = toState(stateful);
43
 
45
 
61
         }
63
         }
62
     }
64
     }
63
 
65
 
64
-    // profile
65
-    if (sources.profile) {
66
-        const value = state['features/base/profile'][propertyName];
66
+    // settings
67
+    if (sources.settings) {
68
+        const value = state['features/base/settings'][propertyName];
67
 
69
 
68
         if (typeof value !== 'undefined') {
70
         if (typeof value !== 'undefined') {
69
             return value;
71
             return value;

react/features/base/profile/index.js → react/features/base/settings/index.js 查看文件


react/features/base/profile/middleware.js → react/features/base/settings/middleware.js 查看文件

4
 import { getLocalParticipant, participantUpdated } from '../participants';
4
 import { getLocalParticipant, participantUpdated } from '../participants';
5
 import { MiddlewareRegistry, toState } from '../redux';
5
 import { MiddlewareRegistry, toState } from '../redux';
6
 
6
 
7
-import { PROFILE_UPDATED } from './actionTypes';
7
+import { SETTINGS_UPDATED } from './actionTypes';
8
+import { getSettings } from './functions';
8
 
9
 
9
 /**
10
 /**
10
- * The middleware of the feature base/profile. Distributes changes to the state
11
- * of base/profile to the states of other features computed from the state of
12
- * base/profile.
11
+ * The middleware of the feature base/settings. Distributes changes to the state
12
+ * of base/settings to the states of other features computed from the state of
13
+ * base/settings.
13
  *
14
  *
14
  * @param {Store} store - The redux store.
15
  * @param {Store} store - The redux store.
15
  * @returns {Function}
16
  * @returns {Function}
18
     const result = next(action);
19
     const result = next(action);
19
 
20
 
20
     switch (action.type) {
21
     switch (action.type) {
21
-    case PROFILE_UPDATED:
22
-        _updateLocalParticipant(store);
22
+    case SETTINGS_UPDATED:
23
         _maybeSetAudioOnly(store, action);
23
         _maybeSetAudioOnly(store, action);
24
+        _updateLocalParticipant(store);
24
     }
25
     }
25
 
26
 
26
     return result;
27
     return result;
27
 });
28
 });
28
 
29
 
29
 /**
30
 /**
30
- * Updates {@code startAudioOnly} flag if it's updated in the profile.
31
+ * Updates {@code startAudioOnly} flag if it's updated in the settings.
31
  *
32
  *
32
  * @param {Store} store - The redux store.
33
  * @param {Store} store - The redux store.
33
  * @param {Object} action - The redux action.
34
  * @param {Object} action - The redux action.
36
  */
37
  */
37
 function _maybeSetAudioOnly(
38
 function _maybeSetAudioOnly(
38
         { dispatch },
39
         { dispatch },
39
-        { profile: { startAudioOnly } }) {
40
+        { settings: { startAudioOnly } }) {
40
     if (typeof startAudioOnly === 'boolean') {
41
     if (typeof startAudioOnly === 'boolean') {
41
         dispatch(setAudioOnly(startAudioOnly));
42
         dispatch(setAudioOnly(startAudioOnly));
42
     }
43
     }
43
 }
44
 }
44
 
45
 
45
 /**
46
 /**
46
- * Updates the local participant according to profile changes.
47
+ * Updates the local participant according to settings changes.
47
  *
48
  *
48
  * @param {Store} store - The redux store.
49
  * @param {Store} store - The redux store.
49
  * @private
50
  * @private
52
 function _updateLocalParticipant(store) {
53
 function _updateLocalParticipant(store) {
53
     const state = toState(store);
54
     const state = toState(store);
54
     const localParticipant = getLocalParticipant(state);
55
     const localParticipant = getLocalParticipant(state);
55
-    const profile = state['features/base/profile'];
56
+    const settings = getSettings(state);
56
 
57
 
57
     store.dispatch(participantUpdated({
58
     store.dispatch(participantUpdated({
58
         // Identify that the participant to update i.e. the local participant:
59
         // Identify that the participant to update i.e. the local participant:
60
         local: true,
61
         local: true,
61
 
62
 
62
         // Specify the updates to be applied to the identified participant:
63
         // Specify the updates to be applied to the identified participant:
63
-        email: profile.email,
64
-        name: profile.displayName
64
+        email: settings.email,
65
+        name: settings.displayName
65
     }));
66
     }));
66
 }
67
 }

+ 157
- 0
react/features/base/settings/reducer.js 查看文件

1
+// @flow
2
+import _ from 'lodash';
3
+
4
+import { APP_WILL_MOUNT } from '../../app';
5
+
6
+import JitsiMeetJS, { browser } from '../lib-jitsi-meet';
7
+import { ReducerRegistry } from '../redux';
8
+import { PersistenceRegistry } from '../storage';
9
+import { assignIfDefined, randomHexString } from '../util';
10
+
11
+import { SETTINGS_UPDATED } from './actionTypes';
12
+
13
+const logger = require('jitsi-meet-logger').getLogger(__filename);
14
+
15
+/**
16
+ * The default/initial redux state of the feature {@code base/settings}.
17
+ *
18
+ * @type Object
19
+ */
20
+const DEFAULT_STATE = {
21
+    audioOutputDeviceId: undefined,
22
+    avatarID: undefined,
23
+    avatarURL: undefined,
24
+    cameraDeviceId: undefined,
25
+    displayName: undefined,
26
+    email: undefined,
27
+    localFlipX: true,
28
+    micDeviceId: undefined,
29
+    serverURL: undefined,
30
+    startAudioOnly: false,
31
+    startWithAudioMuted: false,
32
+    startWithVideoMuted: false
33
+};
34
+
35
+const STORE_NAME = 'features/base/settings';
36
+
37
+/**
38
+ * Sets up the persistence of the feature {@code base/settings}.
39
+ */
40
+PersistenceRegistry.register(STORE_NAME);
41
+
42
+ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
43
+    switch (action.type) {
44
+    case APP_WILL_MOUNT:
45
+        return _initSettings(state);
46
+
47
+    case SETTINGS_UPDATED:
48
+        return {
49
+            ...state,
50
+            ...action.settings
51
+        };
52
+    }
53
+
54
+    return state;
55
+});
56
+
57
+/**
58
+ * Retrieves the legacy profile values regardless of it's being in pre or
59
+ * post-flattening format.
60
+ *
61
+ * FIXME: Let's remove this after a predefined time (e.g. by July 2018) to avoid
62
+ * garbage in the source.
63
+ *
64
+ * @private
65
+ * @returns {Object}
66
+ */
67
+function _getLegacyProfile() {
68
+    let persistedProfile
69
+        = window.localStorage.getItem('features/base/profile');
70
+
71
+    if (persistedProfile) {
72
+        try {
73
+            persistedProfile = JSON.parse(persistedProfile);
74
+
75
+            if (persistedProfile && typeof persistedProfile === 'object') {
76
+                const preFlattenedProfile = persistedProfile.profile;
77
+
78
+                return preFlattenedProfile || persistedProfile;
79
+            }
80
+        } catch (e) {
81
+            logger.warn('Error parsing persisted legacy profile', e);
82
+        }
83
+    }
84
+
85
+    return {};
86
+}
87
+
88
+/**
89
+ * Inits the settings object based on what information we have available.
90
+ * Info taken into consideration:
91
+ *   - Old Settings.js style data
92
+ *   - Things that we stored in profile earlier but belong here.
93
+ *
94
+ * @private
95
+ * @param {Object} featureState - The current state of the feature.
96
+ * @returns {Object}
97
+ */
98
+function _initSettings(featureState) {
99
+    let settings = featureState;
100
+
101
+    // Old Settings.js values
102
+    // FIXME: Let's remove this after a predefined time (e.g. by July 2018) to
103
+    // avoid garbage in the source.
104
+    const displayName = _.escape(window.localStorage.getItem('displayname'));
105
+    const email = _.escape(window.localStorage.getItem('email'));
106
+    let avatarID = _.escape(window.localStorage.getItem('avatarId'));
107
+
108
+    if (!avatarID) {
109
+        // if there is no avatar id, we generate a unique one and use it forever
110
+        avatarID = randomHexString(32);
111
+    }
112
+
113
+    settings = assignIfDefined({
114
+        avatarID,
115
+        displayName,
116
+        email
117
+    }, settings);
118
+
119
+    if (!browser.isReactNative()) {
120
+        // Browser only
121
+        const localFlipX
122
+            = JSON.parse(window.localStorage.getItem('localFlipX') || 'true');
123
+        const cameraDeviceId
124
+            = window.localStorage.getItem('cameraDeviceId') || '';
125
+        const micDeviceId = window.localStorage.getItem('micDeviceId') || '';
126
+
127
+        // Currently audio output device change is supported only in Chrome and
128
+        // default output always has 'default' device ID
129
+        const audioOutputDeviceId
130
+            = window.localStorage.getItem('audioOutputDeviceId') || 'default';
131
+
132
+        if (audioOutputDeviceId
133
+            !== JitsiMeetJS.mediaDevices.getAudioOutputDevice()) {
134
+            JitsiMeetJS.mediaDevices.setAudioOutputDevice(
135
+                audioOutputDeviceId
136
+            ).catch(ex => {
137
+                logger.warn('Failed to set audio output device from local '
138
+                    + 'storage. Default audio output device will be used'
139
+                    + 'instead.', ex);
140
+            });
141
+        }
142
+
143
+        settings = assignIfDefined({
144
+            audioOutputDeviceId,
145
+            cameraDeviceId,
146
+            localFlipX,
147
+            micDeviceId
148
+        }, settings);
149
+    }
150
+
151
+    // Things we stored in profile earlier
152
+    const legacyProfile = _getLegacyProfile();
153
+
154
+    settings = assignIfDefined(legacyProfile, settings);
155
+
156
+    return settings;
157
+}

+ 17
- 6
react/features/base/storage/README.md 查看文件

6
 Usage
6
 Usage
7
 =====
7
 =====
8
 If a subtree of the redux store should be persisted (e.g.
8
 If a subtree of the redux store should be persisted (e.g.
9
-`'features/base/profile'`), then persistence for that subtree should be
9
+`'features/base/settings'`), then persistence for that subtree should be
10
 requested by registering the subtree with `PersistenceRegistry`.
10
 requested by registering the subtree with `PersistenceRegistry`.
11
 
11
 
12
-For example, to register the field `profile` of the redux subtree
13
-`'features/base/profile'` to be persisted, use:
12
+For example, to register the field `displayName` of the redux subtree
13
+`'features/base/settings'` to be persisted, use:
14
 ```javascript
14
 ```javascript
15
-PersistenceRegistry.register('features/base/profile', {
16
-    profile: true
15
+PersistenceRegistry.register('features/base/settings', {
16
+    displayName: true
17
 });
17
 });
18
 ```
18
 ```
19
 
19
 
20
-in the `reducer.js` of the `base/profile` feature.
20
+in the `reducer.js` of the `base/settings` feature.
21
+
22
+If the second parameter is omitted, the entire feature state is persisted.
21
 
23
 
22
 When it's done, Jitsi Meet will automatically persist these subtrees and
24
 When it's done, Jitsi Meet will automatically persist these subtrees and
23
 rehydrate them on startup.
25
 rehydrate them on startup.
31
 ```
33
 ```
32
 react/features/base/storage/middleware.js#PERSIST_STATE_DELAY
34
 react/features/base/storage/middleware.js#PERSIST_STATE_DELAY
33
 ```
35
 ```
36
+
37
+Serialization
38
+=============
39
+The API JSON.stringify() is currently used to serialize feature states,
40
+therefore its limitations affect the persistency feature too. E.g. complex
41
+objects, such as Maps (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)
42
+or Sets (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set)
43
+cannot be automatically persisted at the moment. The same applies to Functions
44
+(which is not a good practice to store in Redux anyhow).

+ 6
- 4
react/features/base/tracks/functions.js 查看文件

35
     if (typeof APP !== 'undefined') {
35
     if (typeof APP !== 'undefined') {
36
         // TODO The app's settings should go in the redux store and then the
36
         // TODO The app's settings should go in the redux store and then the
37
         // reliance on the global variable APP will go away.
37
         // reliance on the global variable APP will go away.
38
+        store || (store = APP.store); // eslint-disable-line no-param-reassign
39
+
40
+        const settings = store.getState()['features/base/settings'];
41
+
38
         if (typeof cameraDeviceId === 'undefined' || cameraDeviceId === null) {
42
         if (typeof cameraDeviceId === 'undefined' || cameraDeviceId === null) {
39
-            cameraDeviceId = APP.settings.getCameraDeviceId();
43
+            cameraDeviceId = settings.cameraDeviceId;
40
         }
44
         }
41
         if (typeof micDeviceId === 'undefined' || micDeviceId === null) {
45
         if (typeof micDeviceId === 'undefined' || micDeviceId === null) {
42
-            micDeviceId = APP.settings.getMicDeviceId();
46
+            micDeviceId = settings.micDeviceId;
43
         }
47
         }
44
-
45
-        store || (store = APP.store); // eslint-disable-line no-param-reassign
46
     }
48
     }
47
 
49
 
48
     const {
50
     const {

+ 24
- 0
react/features/base/util/helpers.js 查看文件

18
 
18
 
19
     return window.JitsiMeetJS.app;
19
     return window.JitsiMeetJS.app;
20
 }
20
 }
21
+
22
+/**
23
+ * A helper function that behaves similar to Object.assign, but only reassigns a
24
+ * property in target if it's defined in source.
25
+ *
26
+ * @param {Object} target - The target object to assign the values into.
27
+ * @param {Object} source - The source object.
28
+ * @returns {Object}
29
+ */
30
+export function assignIfDefined(target: Object, source: Object) {
31
+    const to = Object(target);
32
+
33
+    for (const nextKey in source) {
34
+        if (source.hasOwnProperty(nextKey)) {
35
+            const value = source[nextKey];
36
+
37
+            if (typeof value !== 'undefined') {
38
+                to[nextKey] = value;
39
+            }
40
+        }
41
+    }
42
+
43
+    return to;
44
+}

+ 19
- 12
react/features/device-selection/actions.js 查看文件

1
 /* globals APP, interfaceConfig */
1
 /* globals APP, interfaceConfig */
2
 
2
 
3
-import { openDialog } from '../base/dialog';
4
-import JitsiMeetJS from '../base/lib-jitsi-meet';
5
 import { API_ID } from '../../../modules/API/constants';
3
 import { API_ID } from '../../../modules/API/constants';
6
 import {
4
 import {
5
+    PostMessageTransportBackend,
6
+    Transport
7
+} from '../../../modules/transport';
8
+
9
+import {
10
+    getAudioOutputDeviceId,
7
     setAudioInputDevice,
11
     setAudioInputDevice,
8
     setAudioOutputDevice,
12
     setAudioOutputDevice,
9
     setVideoInputDevice
13
     setVideoInputDevice
10
 } from '../base/devices';
14
 } from '../base/devices';
15
+import { openDialog } from '../base/dialog';
11
 import { i18next } from '../base/i18n';
16
 import { i18next } from '../base/i18n';
12
-import {
13
-    PostMessageTransportBackend,
14
-    Transport
15
-} from '../../../modules/transport';
17
+import JitsiMeetJS from '../base/lib-jitsi-meet';
16
 
18
 
17
 import { SET_DEVICE_SELECTION_POPUP_DATA } from './actionTypes';
19
 import { SET_DEVICE_SELECTION_POPUP_DATA } from './actionTypes';
18
 import { DeviceSelectionDialog } from './components';
20
 import { DeviceSelectionDialog } from './components';
42
     return dispatch =>
44
     return dispatch =>
43
         JitsiMeetJS.mediaDevices.isDeviceListAvailable()
45
         JitsiMeetJS.mediaDevices.isDeviceListAvailable()
44
             .then(isDeviceListAvailable => {
46
             .then(isDeviceListAvailable => {
47
+                const settings = APP.store.getState()['features/base/settings'];
48
+
45
                 dispatch(openDialog(DeviceSelectionDialog, {
49
                 dispatch(openDialog(DeviceSelectionDialog, {
46
-                    currentAudioInputId: APP.settings.getMicDeviceId(),
47
-                    currentAudioOutputId: APP.settings.getAudioOutputDeviceId(),
48
-                    currentVideoInputId: APP.settings.getCameraDeviceId(),
50
+                    currentAudioInputId: settings.micDeviceId,
51
+                    currentAudioOutputId: getAudioOutputDeviceId(),
52
+                    currentVideoInputId: settings.cameraDeviceId,
49
                     disableAudioInputChange:
53
                     disableAudioInputChange:
50
                         !JitsiMeetJS.isMultipleAudioInputSupported(),
54
                         !JitsiMeetJS.isMultipleAudioInputSupported(),
51
                     disableDeviceChange: !isDeviceListAvailable
55
                     disableDeviceChange: !isDeviceListAvailable
135
  */
139
  */
136
 function _processRequest(dispatch, getState, request, responseCallback) { // eslint-disable-line max-len, max-params
140
 function _processRequest(dispatch, getState, request, responseCallback) { // eslint-disable-line max-len, max-params
137
     if (request.type === 'devices') {
141
     if (request.type === 'devices') {
142
+        const state = getState();
143
+        const settings = state['features/base/settings'];
144
+
138
         switch (request.name) {
145
         switch (request.name) {
139
         case 'isDeviceListAvailable':
146
         case 'isDeviceListAvailable':
140
             JitsiMeetJS.mediaDevices.isDeviceListAvailable()
147
             JitsiMeetJS.mediaDevices.isDeviceListAvailable()
152
             break;
159
             break;
153
         case 'getCurrentDevices':
160
         case 'getCurrentDevices':
154
             responseCallback({
161
             responseCallback({
155
-                audioInput: APP.settings.getMicDeviceId(),
156
-                audioOutput: APP.settings.getAudioOutputDeviceId(),
157
-                videoInput: APP.settings.getCameraDeviceId()
162
+                audioInput: settings.micDeviceId,
163
+                audioOutput: getAudioOutputDeviceId(),
164
+                videoInput: settings.cameraDeviceId
158
             });
165
             });
159
             break;
166
             break;
160
         case 'getAvailableDevices':
167
         case 'getAvailableDevices':

+ 18
- 19
react/features/settings/components/AbstractSettingsView.js 查看文件

2
 
2
 
3
 import { Component } from 'react';
3
 import { Component } from 'react';
4
 
4
 
5
-import { updateProfile } from '../../base/profile';
5
+import { updateSettings } from '../../base/settings';
6
 
6
 
7
 /**
7
 /**
8
  * The type of the React {@code Component} props of
8
  * The type of the React {@code Component} props of
11
 type Props = {
11
 type Props = {
12
 
12
 
13
     /**
13
     /**
14
-     * The current profile object.
14
+     * The default URL for when there is no custom URL set in the settings.
15
      *
15
      *
16
      * @protected
16
      * @protected
17
      */
17
      */
18
-    _profile: Object,
18
+    _serverURL: string,
19
 
19
 
20
     /**
20
     /**
21
-     * The default URL for when there is no custom URL set in the profile.
22
-     *
23
-     * @protected
21
+     * The current settings object.
24
      */
22
      */
25
-    _serverURL: string,
23
+    _settings: Object,
26
 
24
 
27
     /**
25
     /**
28
      * Whether {@link AbstractSettingsView} is visible.
26
      * Whether {@link AbstractSettingsView} is visible.
79
      * @returns {void}
77
      * @returns {void}
80
      */
78
      */
81
     _onChangeDisplayName(text) {
79
     _onChangeDisplayName(text) {
82
-        this._updateProfile({
80
+        this._updateSettings({
83
             displayName: text
81
             displayName: text
84
         });
82
         });
85
     }
83
     }
94
      * @returns {void}
92
      * @returns {void}
95
      */
93
      */
96
     _onChangeEmail(text) {
94
     _onChangeEmail(text) {
97
-        this._updateProfile({
95
+        this._updateSettings({
98
             email: text
96
             email: text
99
         });
97
         });
100
     }
98
     }
109
      * @returns {void}
107
      * @returns {void}
110
      */
108
      */
111
     _onChangeServerURL(text) {
109
     _onChangeServerURL(text) {
112
-        this._updateProfile({
110
+        this._updateSettings({
113
             serverURL: text
111
             serverURL: text
114
         });
112
         });
115
     }
113
     }
125
      * @returns {void}
123
      * @returns {void}
126
      */
124
      */
127
     _onStartAudioMutedChange(newValue) {
125
     _onStartAudioMutedChange(newValue) {
128
-        this._updateProfile({
126
+        this._updateSettings({
129
             startWithAudioMuted: newValue
127
             startWithAudioMuted: newValue
130
         });
128
         });
131
     }
129
     }
141
      * @returns {void}
139
      * @returns {void}
142
      */
140
      */
143
     _onStartVideoMutedChange(newValue) {
141
     _onStartVideoMutedChange(newValue) {
144
-        this._updateProfile({
142
+        this._updateSettings({
145
             startWithVideoMuted: newValue
143
             startWithVideoMuted: newValue
146
         });
144
         });
147
     }
145
     }
148
 
146
 
149
-    _updateProfile: (Object) => void;
147
+    _updateSettings: (Object) => void;
150
 
148
 
151
     /**
149
     /**
152
-     * Updates the persisted profile on any change.
150
+     * Updates the persisted settings on any change.
153
      *
151
      *
154
-     * @param {Object} updateObject - The partial update object for the profile.
152
+     * @param {Object} updateObject - The partial update object for the
153
+     * settings.
155
      * @private
154
      * @private
156
      * @returns {void}
155
      * @returns {void}
157
      */
156
      */
158
-    _updateProfile(updateObject: Object) {
159
-        this.props.dispatch(updateProfile(updateObject));
157
+    _updateSettings(updateObject: Object) {
158
+        this.props.dispatch(updateSettings(updateObject));
160
     }
159
     }
161
 }
160
 }
162
 
161
 
167
  * @param {Object} state - The redux state.
166
  * @param {Object} state - The redux state.
168
  * @protected
167
  * @protected
169
  * @returns {{
168
  * @returns {{
170
- *     _profile: Object,
171
  *     _serverURL: string,
169
  *     _serverURL: string,
170
+ *     _settings: Object,
172
  *     _visible: boolean
171
  *     _visible: boolean
173
  * }}
172
  * }}
174
  */
173
  */
175
 export function _mapStateToProps(state: Object) {
174
 export function _mapStateToProps(state: Object) {
176
     return {
175
     return {
177
-        _profile: state['features/base/profile'],
178
         _serverURL: state['features/app'].app._getDefaultURL(),
176
         _serverURL: state['features/app'].app._getDefaultURL(),
177
+        _settings: state['features/base/settings'],
179
         _visible: state['features/settings'].visible
178
         _visible: state['features/settings'].visible
180
     };
179
     };
181
 }
180
 }

+ 7
- 7
react/features/settings/components/native/SettingsView.js 查看文件

120
      * @returns {void}
120
      * @returns {void}
121
      */
121
      */
122
     _processServerURL(hideOnSuccess: boolean) {
122
     _processServerURL(hideOnSuccess: boolean) {
123
-        const { serverURL } = this.props._profile;
123
+        const { serverURL } = this.props._settings;
124
         const normalizedURL = normalizeUserInputURL(serverURL);
124
         const normalizedURL = normalizeUserInputURL(serverURL);
125
 
125
 
126
         if (normalizedURL === null) {
126
         if (normalizedURL === null) {
140
      * @returns {React$Element}
140
      * @returns {React$Element}
141
      */
141
      */
142
     _renderBody() {
142
     _renderBody() {
143
-        const { _profile } = this.props;
143
+        const { _settings } = this.props;
144
 
144
 
145
         return (
145
         return (
146
             <SafeAreaView style = { styles.settingsForm }>
146
             <SafeAreaView style = { styles.settingsForm }>
154
                             autoCorrect = { false }
154
                             autoCorrect = { false }
155
                             onChangeText = { this._onChangeDisplayName }
155
                             onChangeText = { this._onChangeDisplayName }
156
                             placeholder = 'John Doe'
156
                             placeholder = 'John Doe'
157
-                            value = { _profile.displayName } />
157
+                            value = { _settings.displayName } />
158
                     </FormRow>
158
                     </FormRow>
159
                     <FormRow i18nLabel = 'settingsView.email'>
159
                     <FormRow i18nLabel = 'settingsView.email'>
160
                         <TextInput
160
                         <TextInput
163
                             keyboardType = { 'email-address' }
163
                             keyboardType = { 'email-address' }
164
                             onChangeText = { this._onChangeEmail }
164
                             onChangeText = { this._onChangeEmail }
165
                             placeholder = 'email@example.com'
165
                             placeholder = 'email@example.com'
166
-                            value = { _profile.email } />
166
+                            value = { _settings.email } />
167
                     </FormRow>
167
                     </FormRow>
168
                     <FormSectionHeader
168
                     <FormSectionHeader
169
                         i18nLabel = 'settingsView.conferenceSection' />
169
                         i18nLabel = 'settingsView.conferenceSection' />
176
                             onBlur = { this._onBlurServerURL }
176
                             onBlur = { this._onBlurServerURL }
177
                             onChangeText = { this._onChangeServerURL }
177
                             onChangeText = { this._onChangeServerURL }
178
                             placeholder = { this.props._serverURL }
178
                             placeholder = { this.props._serverURL }
179
-                            value = { _profile.serverURL } />
179
+                            value = { _settings.serverURL } />
180
                     </FormRow>
180
                     </FormRow>
181
                     <FormRow
181
                     <FormRow
182
                         fieldSeparator = { true }
182
                         fieldSeparator = { true }
183
                         i18nLabel = 'settingsView.startWithAudioMuted'>
183
                         i18nLabel = 'settingsView.startWithAudioMuted'>
184
                         <Switch
184
                         <Switch
185
                             onValueChange = { this._onStartAudioMutedChange }
185
                             onValueChange = { this._onStartAudioMutedChange }
186
-                            value = { _profile.startWithAudioMuted } />
186
+                            value = { _settings.startWithAudioMuted } />
187
                     </FormRow>
187
                     </FormRow>
188
                     <FormRow i18nLabel = 'settingsView.startWithVideoMuted'>
188
                     <FormRow i18nLabel = 'settingsView.startWithVideoMuted'>
189
                         <Switch
189
                         <Switch
190
                             onValueChange = { this._onStartVideoMutedChange }
190
                             onValueChange = { this._onStartVideoMutedChange }
191
-                            value = { _profile.startWithVideoMuted } />
191
+                            value = { _settings.startWithVideoMuted } />
192
                     </FormRow>
192
                     </FormRow>
193
                 </ScrollView>
193
                 </ScrollView>
194
             </SafeAreaView>
194
             </SafeAreaView>

+ 14
- 6
react/features/welcome/components/AbstractWelcomePage.js 查看文件

14
 type Props = {
14
 type Props = {
15
 
15
 
16
     /**
16
     /**
17
-     * The user's profile.
17
+     * Room name to join to.
18
      */
18
      */
19
-    _profile: Object,
20
     _room: string,
19
     _room: string,
20
+
21
+    /**
22
+     * The current settings.
23
+     */
24
+    _settings: Object,
25
+
26
+    /**
27
+     * The Redux dispatch Function.
28
+     */
21
     dispatch: Dispatch<*>
29
     dispatch: Dispatch<*>
22
 };
30
 };
23
 
31
 
229
  * @param {Object} state - The redux state.
237
  * @param {Object} state - The redux state.
230
  * @protected
238
  * @protected
231
  * @returns {{
239
  * @returns {{
232
- *     _profile: Object,
233
- *     _room: string
240
+ *     _room: string,
241
+ *     _settings: Object
234
  * }}
242
  * }}
235
  */
243
  */
236
 export function _mapStateToProps(state: Object) {
244
 export function _mapStateToProps(state: Object) {
237
     return {
245
     return {
238
-        _profile: state['features/base/profile'],
239
-        _room: state['features/base/conference'].room
246
+        _room: state['features/base/conference'].room,
247
+        _settings: state['features/base/settings']
240
     };
248
     };
241
 }
249
 }

+ 8
- 9
react/features/welcome/components/VideoSwitch.js 查看文件

5
 import { connect } from 'react-redux';
5
 import { connect } from 'react-redux';
6
 
6
 
7
 import { translate } from '../../base/i18n';
7
 import { translate } from '../../base/i18n';
8
-import { updateProfile } from '../../base/profile';
9
 import { Header, Text } from '../../base/react';
8
 import { Header, Text } from '../../base/react';
9
+import { updateSettings } from '../../base/settings';
10
 
10
 
11
 import styles, { SWITCH_THUMB_COLOR, SWITCH_UNDER_COLOR } from './styles';
11
 import styles, { SWITCH_THUMB_COLOR, SWITCH_UNDER_COLOR } from './styles';
12
 
12
 
26
     t: Function,
26
     t: Function,
27
 
27
 
28
     /**
28
     /**
29
-     * The current profile settings from redux.
29
+     * The current settings from redux.
30
      */
30
      */
31
-    _profile: Object
31
+    _settings: Object
32
 };
32
 };
33
 
33
 
34
 /**
34
 /**
55
      * @inheritdoc
55
      * @inheritdoc
56
      */
56
      */
57
     render() {
57
     render() {
58
-        const { t, _profile } = this.props;
58
+        const { t, _settings } = this.props;
59
         const { textStyle } = Header;
59
         const { textStyle } = Header;
60
 
60
 
61
         return (
61
         return (
71
                     onValueChange = { this._onStartAudioOnlyChange }
71
                     onValueChange = { this._onStartAudioOnlyChange }
72
                     style = { styles.audioVideoSwitch }
72
                     style = { styles.audioVideoSwitch }
73
                     thumbTintColor = { SWITCH_THUMB_COLOR }
73
                     thumbTintColor = { SWITCH_THUMB_COLOR }
74
-                    value = { _profile.startAudioOnly } />
74
+                    value = { _settings.startAudioOnly } />
75
                 <TouchableWithoutFeedback
75
                 <TouchableWithoutFeedback
76
                     onPress = { this._onStartAudioOnlyTrue }>
76
                     onPress = { this._onStartAudioOnlyTrue }>
77
                     <Text style = { textStyle }>
77
                     <Text style = { textStyle }>
94
     _onStartAudioOnlyChange(startAudioOnly) {
94
     _onStartAudioOnlyChange(startAudioOnly) {
95
         const { dispatch } = this.props;
95
         const { dispatch } = this.props;
96
 
96
 
97
-        dispatch(updateProfile({
98
-            ...this.props._profile,
97
+        dispatch(updateSettings({
99
             startAudioOnly
98
             startAudioOnly
100
         }));
99
         }));
101
     }
100
     }
124
  * @param {Object} state - The redux state.
123
  * @param {Object} state - The redux state.
125
  * @protected
124
  * @protected
126
  * @returns {{
125
  * @returns {{
127
- *     _profile: Object
126
+ *     _settings: Object
128
  * }}
127
  * }}
129
  */
128
  */
130
 export function _mapStateToProps(state: Object) {
129
 export function _mapStateToProps(state: Object) {
131
     return {
130
     return {
132
-        _profile: state['features/base/profile']
131
+        _settings: state['features/base/settings']
133
     };
132
     };
134
 }
133
 }
135
 
134
 

+ 1
- 1
react/features/welcome/components/WelcomePage.native.js 查看文件

66
 
66
 
67
         const { dispatch } = this.props;
67
         const { dispatch } = this.props;
68
 
68
 
69
-        if (this.props._profile.startAudioOnly) {
69
+        if (this.props._settings.startAudioOnly) {
70
             dispatch(destroyLocalTracks());
70
             dispatch(destroyLocalTracks());
71
         } else {
71
         } else {
72
             dispatch(createDesiredLocalTracks(MEDIA_TYPE.VIDEO));
72
             dispatch(createDesiredLocalTracks(MEDIA_TYPE.VIDEO));

Loading…
取消
儲存