Просмотр исходного кода

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 лет назад
Родитель
Сommit
768cff48a4
Аккаунт пользователя с таким Email не найден

+ 24
- 2
conference.js Просмотреть файл

@@ -49,6 +49,7 @@ import {
49 49
     setDesktopSharingEnabled
50 50
 } from './react/features/base/conference';
51 51
 import {
52
+    checkAndNotifyForNewDevice,
52 53
     getAvailableDevices,
53 54
     setAudioOutputDeviceId,
54 55
     updateDeviceList
@@ -2123,8 +2124,8 @@ export default {
2123 2124
 
2124 2125
                     return stream;
2125 2126
                 })
2126
-                .then(stream => {
2127
-                    this.useAudioStream(stream);
2127
+                .then(stream => this.useAudioStream(stream))
2128
+                .then(() => {
2128 2129
                     logger.log('switched local audio device');
2129 2130
 
2130 2131
                     this._updateAudioDeviceId();
@@ -2344,6 +2345,8 @@ export default {
2344 2345
      * @returns {Promise}
2345 2346
      */
2346 2347
     _onDeviceListChanged(devices) {
2348
+        const oldDevices = APP.store.getState()['features/base/devices'].availableDevices;
2349
+
2347 2350
         APP.store.dispatch(updateDeviceList(devices));
2348 2351
 
2349 2352
         const newDevices
@@ -2382,6 +2385,25 @@ export default {
2382 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 2407
         promises.push(
2386 2408
             mediaDeviceHelper.createLocalTracksAfterDeviceListChanged(
2387 2409
                     createLocalTracksF,

+ 5
- 1
lang/main.json Просмотреть файл

@@ -476,7 +476,11 @@
476 476
         "raisedHand": "__name__ would like to speak.",
477 477
         "somebody": "Somebody",
478 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 485
     "passwordSetRemotely": "set by another member",
482 486
     "poweredby": "powered by",

+ 11
- 0
modules/devices/mediaDeviceHelper.js Просмотреть файл

@@ -25,6 +25,17 @@ function getNewAudioOutputDevice(newDevices) {
25 25
             d.deviceId === selectedAudioOutputDeviceId)) {
26 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 Просмотреть файл

@@ -50,3 +50,15 @@ export const ADD_PENDING_DEVICE_REQUEST = 'ADD_PENDING_DEVICE_REQUEST';
50 50
  * }
51 51
  */
52 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 Просмотреть файл

@@ -3,6 +3,7 @@ import { updateSettings } from '../settings';
3 3
 
4 4
 import {
5 5
     ADD_PENDING_DEVICE_REQUEST,
6
+    CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
6 7
     REMOVE_PENDING_DEVICE_REQUESTS,
7 8
     SET_AUDIO_INPUT_DEVICE,
8 9
     SET_VIDEO_INPUT_DEVICE,
@@ -186,3 +187,21 @@ export function updateDeviceList(devices) {
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 Просмотреть файл

@@ -5,11 +5,21 @@ import { processExternalDeviceRequest } from '../../device-selection';
5 5
 import { MiddlewareRegistry } from '../redux';
6 6
 import UIEvents from '../../../../service/UI/UIEvents';
7 7
 
8
-import { removePendingDeviceRequests } from './actions';
9 8
 import {
9
+    removePendingDeviceRequests,
10
+    setAudioInputDevice,
11
+    setVideoInputDevice
12
+} from './actions';
13
+import {
14
+    CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
10 15
     SET_AUDIO_INPUT_DEVICE,
11 16
     SET_VIDEO_INPUT_DEVICE
12 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 25
  * Implements the middleware of the feature base/devices.
@@ -28,6 +38,9 @@ MiddlewareRegistry.register(store => next => action => {
28 38
     case SET_VIDEO_INPUT_DEVICE:
29 39
         APP.UI.emitEvent(UIEvents.VIDEO_DEVICE_CHANGED, action.deviceId);
30 40
         break;
41
+    case CHECK_AND_NOTIFY_FOR_NEW_DEVICE:
42
+        _checkAndNotifyForNewDevice(store, action.newDevices, action.oldDevices);
43
+        break;
31 44
     }
32 45
 
33 46
     return next(action);
@@ -63,3 +76,107 @@ function _conferenceJoined({ dispatch, getState }, next, action) {
63 76
 
64 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 Просмотреть файл

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

+ 10
- 0
react/features/notifications/components/AbstractNotification.js Просмотреть файл

@@ -12,6 +12,16 @@ export type Props = {
12 12
      */
13 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 26
      * The text to display in the body of the notification. If not passed
17 27
      * in, the passed in descriptionKey will be used.

+ 13
- 0
react/features/notifications/components/web/Notification.js Просмотреть файл

@@ -136,6 +136,19 @@ class Notification extends AbstractNotification<Props> {
136 136
             ];
137 137
 
138 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 152
             return [];
140 153
         }
141 154
     }

Загрузка…
Отмена
Сохранить