Przeglądaj źródła

Notify for new device (#4165)

* Fix detecting preferred audio output.

Fixes detecting when a new output device is found and we have stored user preference of using that device.

* Does not store which is the currently open device on save.

Does not save the currently opened device when saving settings dialog, this will be done once we successfully replace the tracks to use the new devices.

* Saves opened audio device after successfully changing it.

If we do it earlier _updateAudioDeviceId is using localAudio and can store wrong value.

* Adds notification for new non preferred devices.

A notification is shown which gives an option to the user to select and use the newly plugged devices.
Adding custom button and handler for the action to the notifications.

* Changes logic to search and handle all newly added devices from array.

* Moves some utility methods to features/base/devices.
master
Дамян Минков 6 lat temu
rodzic
commit
768cff48a4
No account linked to committer's email address

+ 24
- 2
conference.js Wyświetl plik

49
     setDesktopSharingEnabled
49
     setDesktopSharingEnabled
50
 } from './react/features/base/conference';
50
 } from './react/features/base/conference';
51
 import {
51
 import {
52
+    checkAndNotifyForNewDevice,
52
     getAvailableDevices,
53
     getAvailableDevices,
53
     setAudioOutputDeviceId,
54
     setAudioOutputDeviceId,
54
     updateDeviceList
55
     updateDeviceList
2123
 
2124
 
2124
                     return stream;
2125
                     return stream;
2125
                 })
2126
                 })
2126
-                .then(stream => {
2127
-                    this.useAudioStream(stream);
2127
+                .then(stream => this.useAudioStream(stream))
2128
+                .then(() => {
2128
                     logger.log('switched local audio device');
2129
                     logger.log('switched local audio device');
2129
 
2130
 
2130
                     this._updateAudioDeviceId();
2131
                     this._updateAudioDeviceId();
2344
      * @returns {Promise}
2345
      * @returns {Promise}
2345
      */
2346
      */
2346
     _onDeviceListChanged(devices) {
2347
     _onDeviceListChanged(devices) {
2348
+        const oldDevices = APP.store.getState()['features/base/devices'].availableDevices;
2349
+
2347
         APP.store.dispatch(updateDeviceList(devices));
2350
         APP.store.dispatch(updateDeviceList(devices));
2348
 
2351
 
2349
         const newDevices
2352
         const newDevices
2382
             this.localVideo.stopStream();
2385
             this.localVideo.stopStream();
2383
         }
2386
         }
2384
 
2387
 
2388
+        // Let's handle unknown/non-preferred devices
2389
+        const newAvailDevices
2390
+            = APP.store.getState()['features/base/devices'].availableDevices;
2391
+
2392
+        if (typeof newDevices.audiooutput === 'undefined') {
2393
+            APP.store.dispatch(
2394
+                checkAndNotifyForNewDevice(newAvailDevices.audioOutput, oldDevices.audioOutput));
2395
+        }
2396
+
2397
+        if (!requestedInput.audio) {
2398
+            APP.store.dispatch(
2399
+                checkAndNotifyForNewDevice(newAvailDevices.audioInput, oldDevices.audioInput));
2400
+        }
2401
+
2402
+        if (!requestedInput.video) {
2403
+            APP.store.dispatch(
2404
+                checkAndNotifyForNewDevice(newAvailDevices.videoInput, oldDevices.videoInput));
2405
+        }
2406
+
2385
         promises.push(
2407
         promises.push(
2386
             mediaDeviceHelper.createLocalTracksAfterDeviceListChanged(
2408
             mediaDeviceHelper.createLocalTracksAfterDeviceListChanged(
2387
                     createLocalTracksF,
2409
                     createLocalTracksF,

+ 5
- 1
lang/main.json Wyświetl plik

476
         "raisedHand": "__name__ would like to speak.",
476
         "raisedHand": "__name__ would like to speak.",
477
         "somebody": "Somebody",
477
         "somebody": "Somebody",
478
         "suboptimalExperienceDescription": "Eer... we are afraid your experience with __appName__ isn't going to be that great here. We are looking for ways to improve this but, until then, please try using one of the <a href='static/recommendedBrowsers.html' target='_blank'>fully supported browsers</a>.",
478
         "suboptimalExperienceDescription": "Eer... we are afraid your experience with __appName__ isn't going to be that great here. We are looking for ways to improve this but, until then, please try using one of the <a href='static/recommendedBrowsers.html' target='_blank'>fully supported browsers</a>.",
479
-        "suboptimalExperienceTitle": "Browser Warning"
479
+        "suboptimalExperienceTitle": "Browser Warning",
480
+        "newDeviceCameraTitle": "New camera detected",
481
+        "newDeviceMicTitle": "New microphone detected",
482
+        "newDeviceCameraTitle": "New audio output detected",
483
+        "newDeviceAction": "Use"
480
     },
484
     },
481
     "passwordSetRemotely": "set by another member",
485
     "passwordSetRemotely": "set by another member",
482
     "poweredby": "powered by",
486
     "poweredby": "powered by",

+ 11
- 0
modules/devices/mediaDeviceHelper.js Wyświetl plik

25
             d.deviceId === selectedAudioOutputDeviceId)) {
25
             d.deviceId === selectedAudioOutputDeviceId)) {
26
         return 'default';
26
         return 'default';
27
     }
27
     }
28
+
29
+    const settings = APP.store.getState()['features/base/settings'];
30
+    const preferredAudioOutputDeviceId = settings.userSelectedAudioOutputDeviceId;
31
+
32
+    // if the preferred one is not the selected and is available in the new devices
33
+    // we want to use it as it was just added
34
+    if (preferredAudioOutputDeviceId
35
+        && preferredAudioOutputDeviceId !== selectedAudioOutputDeviceId
36
+        && availableAudioOutputDevices.find(d => d.deviceId === preferredAudioOutputDeviceId)) {
37
+        return preferredAudioOutputDeviceId;
38
+    }
28
 }
39
 }
29
 
40
 
30
 /**
41
 /**

+ 12
- 0
react/features/base/devices/actionTypes.js Wyświetl plik

50
  * }
50
  * }
51
  */
51
  */
52
 export const REMOVE_PENDING_DEVICE_REQUESTS = 'REMOVE_PENDING_DEVICE_REQUESTS';
52
 export const REMOVE_PENDING_DEVICE_REQUESTS = 'REMOVE_PENDING_DEVICE_REQUESTS';
53
+
54
+/**
55
+ * The type of Redux action which will check passed old and passed new devices
56
+ * and if needed will show notifications asking the user whether to use those.
57
+ *
58
+ * {
59
+ *     type: CHECK_AND_NOTIFY_FOR_NEW_DEVICE
60
+ *     newDevices: Array<MediaDeviceInfo>
61
+ *     oldDevices: Array<MediaDeviceInfo>
62
+ * }
63
+ */
64
+export const CHECK_AND_NOTIFY_FOR_NEW_DEVICE = 'CHECK_AND_NOTIFY_FOR_NEW_DEVICE';

+ 19
- 0
react/features/base/devices/actions.js Wyświetl plik

3
 
3
 
4
 import {
4
 import {
5
     ADD_PENDING_DEVICE_REQUEST,
5
     ADD_PENDING_DEVICE_REQUEST,
6
+    CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
6
     REMOVE_PENDING_DEVICE_REQUESTS,
7
     REMOVE_PENDING_DEVICE_REQUESTS,
7
     SET_AUDIO_INPUT_DEVICE,
8
     SET_AUDIO_INPUT_DEVICE,
8
     SET_VIDEO_INPUT_DEVICE,
9
     SET_VIDEO_INPUT_DEVICE,
186
     };
187
     };
187
 }
188
 }
188
 
189
 
190
+/**
191
+ * Signals to check new and old devices for newly added devices and notify.
192
+ *
193
+ * @param {Array<MediaDeviceInfo>} newDevices - Array of the new devices.
194
+ * @param {Array<MediaDeviceInfo>} oldDevices - Array of the old devices.
195
+ * @returns {{
196
+ *      type: CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
197
+ *      newDevices: Array<MediaDeviceInfo>,
198
+ *      oldDevices: Array<MediaDeviceInfo>
199
+ * }}
200
+ */
201
+export function checkAndNotifyForNewDevice(newDevices, oldDevices) {
202
+    return {
203
+        type: CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
204
+        newDevices,
205
+        oldDevices
206
+    };
207
+}

+ 118
- 1
react/features/base/devices/middleware.js Wyświetl plik

5
 import { MiddlewareRegistry } from '../redux';
5
 import { MiddlewareRegistry } from '../redux';
6
 import UIEvents from '../../../../service/UI/UIEvents';
6
 import UIEvents from '../../../../service/UI/UIEvents';
7
 
7
 
8
-import { removePendingDeviceRequests } from './actions';
9
 import {
8
 import {
9
+    removePendingDeviceRequests,
10
+    setAudioInputDevice,
11
+    setVideoInputDevice
12
+} from './actions';
13
+import {
14
+    CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
10
     SET_AUDIO_INPUT_DEVICE,
15
     SET_AUDIO_INPUT_DEVICE,
11
     SET_VIDEO_INPUT_DEVICE
16
     SET_VIDEO_INPUT_DEVICE
12
 } from './actionTypes';
17
 } from './actionTypes';
18
+import { showNotification } from '../../notifications';
19
+import { updateSettings } from '../settings';
20
+import { setAudioOutputDeviceId } from './functions';
21
+
22
+const logger = require('jitsi-meet-logger').getLogger(__filename);
13
 
23
 
14
 /**
24
 /**
15
  * Implements the middleware of the feature base/devices.
25
  * Implements the middleware of the feature base/devices.
28
     case SET_VIDEO_INPUT_DEVICE:
38
     case SET_VIDEO_INPUT_DEVICE:
29
         APP.UI.emitEvent(UIEvents.VIDEO_DEVICE_CHANGED, action.deviceId);
39
         APP.UI.emitEvent(UIEvents.VIDEO_DEVICE_CHANGED, action.deviceId);
30
         break;
40
         break;
41
+    case CHECK_AND_NOTIFY_FOR_NEW_DEVICE:
42
+        _checkAndNotifyForNewDevice(store, action.newDevices, action.oldDevices);
43
+        break;
31
     }
44
     }
32
 
45
 
33
     return next(action);
46
     return next(action);
63
 
76
 
64
     return result;
77
     return result;
65
 }
78
 }
79
+
80
+/**
81
+ * Finds a new device by comparing new and old array of devices and dispatches
82
+ * notification with the new device.
83
+ *
84
+ * @param {Store} store - The redux store in which the specified {@code action}
85
+ * is being dispatched.
86
+ * @param {MediaDeviceInfo[]} newDevices - The array of new devices we received.
87
+ * @param {MediaDeviceInfo[]} oldDevices - The array of the old devices we have.
88
+ * @private
89
+ * @returns {void}
90
+ */
91
+function _checkAndNotifyForNewDevice(store, newDevices, oldDevices) {
92
+    const { dispatch } = store;
93
+
94
+    // let's intersect both newDevices and oldDevices and handle thew newly
95
+    // added devices
96
+    const onlyNewDevices = newDevices.filter(
97
+        nDevice => !oldDevices.find(
98
+            device => device.deviceId === nDevice.deviceId));
99
+
100
+    onlyNewDevices.forEach(newDevice => {
101
+
102
+        // we want to strip any device details that are not very
103
+        // user friendly, like usb ids put in brackets at the end
104
+        let description = newDevice.label;
105
+        const ix = description.lastIndexOf('(');
106
+
107
+        if (ix !== -1) {
108
+            description = description.substr(0, ix);
109
+        }
110
+
111
+        let titleKey;
112
+
113
+        switch (newDevice.kind) {
114
+        case 'videoinput': {
115
+            titleKey = 'notify.newDeviceCameraTitle';
116
+            break;
117
+        }
118
+        case 'audioinput': {
119
+            titleKey = 'notify.newDeviceMicTitle';
120
+            break;
121
+        }
122
+        case 'audiooutput': {
123
+            titleKey = 'notify.newDeviceCameraTitle';
124
+            break;
125
+        }
126
+        }
127
+
128
+        dispatch(showNotification({
129
+            description,
130
+            titleKey,
131
+            customActionNameKey: 'notify.newDeviceAction',
132
+            customActionHandler: _useDevice.bind(undefined, store, newDevice)
133
+        }));
134
+    });
135
+}
136
+
137
+/**
138
+ * Set a device to be currently used, selected by the user.
139
+ *
140
+ * @param {Store} store - The redux store in which the specified {@code action}
141
+ * is being dispatched.
142
+ * @param {MediaDeviceInfo} device - The device to save.
143
+ * @returns {boolean} - Returns true in order notifications to be dismissed.
144
+ * @private
145
+ */
146
+function _useDevice({ dispatch }, device) {
147
+    switch (device.kind) {
148
+    case 'videoinput': {
149
+        dispatch(updateSettings({
150
+            userSelectedCameraDeviceId: device.deviceId
151
+        }));
152
+
153
+        dispatch(setVideoInputDevice(device.deviceId));
154
+        break;
155
+    }
156
+    case 'audioinput': {
157
+        dispatch(updateSettings({
158
+            userSelectedMicDeviceId: device.deviceId
159
+        }));
160
+
161
+        dispatch(setAudioInputDevice(device.deviceId));
162
+        break;
163
+    }
164
+    case 'audiooutput': {
165
+        setAudioOutputDeviceId(
166
+            device.deviceId,
167
+            dispatch,
168
+            true)
169
+            .then(() => logger.log('changed audio output device'))
170
+            .catch(err => {
171
+                logger.warn(
172
+                    'Failed to change audio output device.',
173
+                    'Default or previously set audio output device will',
174
+                    ' be used instead.',
175
+                    err);
176
+            });
177
+        break;
178
+    }
179
+    }
180
+
181
+    return true;
182
+}

+ 0
- 2
react/features/device-selection/actions.js Wyświetl plik

112
             && newState.selectedVideoInputId
112
             && newState.selectedVideoInputId
113
                 !== currentState.selectedVideoInputId) {
113
                 !== currentState.selectedVideoInputId) {
114
             dispatch(updateSettings({
114
             dispatch(updateSettings({
115
-                cameraDeviceId: newState.selectedVideoInputId,
116
                 userSelectedCameraDeviceId: newState.selectedVideoInputId
115
                 userSelectedCameraDeviceId: newState.selectedVideoInputId
117
             }));
116
             }));
118
 
117
 
124
                 && newState.selectedAudioInputId
123
                 && newState.selectedAudioInputId
125
                   !== currentState.selectedAudioInputId) {
124
                   !== currentState.selectedAudioInputId) {
126
             dispatch(updateSettings({
125
             dispatch(updateSettings({
127
-                micDeviceId: newState.selectedAudioInputId,
128
                 userSelectedMicDeviceId: newState.selectedAudioInputId
126
                 userSelectedMicDeviceId: newState.selectedAudioInputId
129
             }));
127
             }));
130
 
128
 

+ 10
- 0
react/features/notifications/components/AbstractNotification.js Wyświetl plik

12
      */
12
      */
13
     appearance: string,
13
     appearance: string,
14
 
14
 
15
+    /**
16
+     * Callback invoked when the custom button is clicked.
17
+     */
18
+    customActionHandler: Function,
19
+
20
+    /**
21
+     * The text to display as button in the notification for the custom action.
22
+     */
23
+    customActionNameKey: string,
24
+
15
     /**
25
     /**
16
      * The text to display in the body of the notification. If not passed
26
      * The text to display in the body of the notification. If not passed
17
      * in, the passed in descriptionKey will be used.
27
      * in, the passed in descriptionKey will be used.

+ 13
- 0
react/features/notifications/components/web/Notification.js Wyświetl plik

136
             ];
136
             ];
137
 
137
 
138
         default:
138
         default:
139
+            if (this.props.customActionNameKey && this.props.customActionHandler) {
140
+                return [
141
+                    {
142
+                        content: this.props.t(this.props.customActionNameKey),
143
+                        onClick: () => {
144
+                            if (this.props.customActionHandler()) {
145
+                                this._onDismissed();
146
+                            }
147
+                        }
148
+                    }
149
+                ];
150
+            }
151
+
139
             return [];
152
             return [];
140
         }
153
         }
141
     }
154
     }

Ładowanie…
Anuluj
Zapisz