Kaynağa Gözat

feat: show no audio signal notification

j8
Andrei Gavrilescu 5 yıl önce
ebeveyn
işleme
c494d6c48b

+ 9
- 5
conference.js Dosyayı Görüntüle

@@ -479,14 +479,18 @@ export default {
479 479
             audioOnlyError,
480 480
             screenSharingError,
481 481
             videoOnlyError;
482
-        const initialDevices = [];
483
-        let requestedAudio = false;
482
+        const initialDevices = [ 'audio' ];
483
+        const requestedAudio = true;
484 484
         let requestedVideo = false;
485 485
 
486
-        if (!options.startWithAudioMuted) {
487
-            initialDevices.push('audio');
488
-            requestedAudio = true;
486
+        // Always get a handle on the audio input device so that we have statistics even if the user joins the
487
+        // conference muted. Previous implementation would only acquire the handle when the user first unmuted,
488
+        // which would results in statistics ( such as "No audio input" or "Are you trying to speak?") being available
489
+        // only after that point.
490
+        if (options.startWithAudioMuted) {
491
+            this.muteAudio(true, true);
489 492
         }
493
+
490 494
         if (!options.startWithVideoMuted
491 495
                 && !options.startAudioOnly
492 496
                 && !options.startScreenSharing) {

+ 1
- 0
config.js Dosyayı Görüntüle

@@ -470,6 +470,7 @@ var config = {
470 470
      disableNS
471 471
      enableLipSync
472 472
      enableTalkWhileMuted
473
+     enableNoAudioDetection
473 474
      forceJVB121Ratio
474 475
      hiddenDomain
475 476
      ignoreStartMuted

+ 4
- 0
lang/main.json Dosyayı Görüntüle

@@ -630,6 +630,10 @@
630 630
         "lowerYourHand": "Lower your hand",
631 631
         "moreActions": "More actions",
632 632
         "mute": "Mute / Unmute",
633
+
634
+        "noAudioSignalTitle": "There is no input coming from your mic!",
635
+        "noAudioSignalDesc": "If you did not purposely mute it from system settings or hardware, consider changing the device.",
636
+        "noAudioSignalDescSuggestion": "If you did not purposely mute it from system settings or hardware, consider using the following device:",
633 637
         "openChat": "Open chat",
634 638
         "pip": "Enter Picture-in-Picture mode",
635 639
         "privateMessage": "Send private message",

+ 1
- 0
react/features/app/components/App.web.js Dosyayı Görüntüle

@@ -9,6 +9,7 @@ import '../../chat';
9 9
 import '../../external-api';
10 10
 import '../../power-monitor';
11 11
 import '../../room-lock';
12
+import '../../no-audio-signal';
12 13
 import '../../talk-while-muted';
13 14
 import '../../video-layout';
14 15
 

+ 33
- 0
react/features/base/devices/functions.js Dosyayı Görüntüle

@@ -139,6 +139,39 @@ export function groupDevicesByKind(devices: Object[]): Object {
139 139
     };
140 140
 }
141 141
 
142
+/**
143
+ * Filters audio devices from a list of MediaDeviceInfo objects.
144
+ *
145
+ * @param {Array<MediaDeviceInfo>} devices - Unfiltered media devices.
146
+ * @private
147
+ * @returns {Array<MediaDeviceInfo>} Filtered audio devices.
148
+ */
149
+export function filterAudioDevices(devices: Object[]): Object {
150
+    return devices.filter(device => device.kind === 'audioinput');
151
+}
152
+
153
+/**
154
+ * We want to strip any device details that are not very user friendly, like usb ids put in brackets at the end.
155
+ *
156
+ * @param {string} label - Device label to format.
157
+ *
158
+ * @returns {string} - Formatted string.
159
+ */
160
+export function formatDeviceLabel(label: string) {
161
+
162
+    let formattedLabel = label;
163
+
164
+    // Remove braked description at the end as it contains non user friendly strings i.e.
165
+    // Microsoft® LifeCam HD-3000 (045e:0779:31dg:d1231)
166
+    const ix = formattedLabel.lastIndexOf('(');
167
+
168
+    if (ix !== -1) {
169
+        formattedLabel = formattedLabel.substr(0, ix);
170
+    }
171
+
172
+    return formattedLabel;
173
+}
174
+
142 175
 /**
143 176
  * Set device id of the audio output device which is currently in use.
144 177
  * Empty string stands for default device.

+ 1
- 0
react/features/base/devices/index.js Dosyayı Görüntüle

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

+ 2
- 7
react/features/base/devices/middleware.js Dosyayı Görüntüle

@@ -20,7 +20,7 @@ import {
20 20
 } from './actionTypes';
21 21
 import { showNotification, showWarningNotification } from '../../notifications';
22 22
 import { updateSettings } from '../settings';
23
-import { setAudioOutputDeviceId } from './functions';
23
+import { formatDeviceLabel, setAudioOutputDeviceId } from './functions';
24 24
 import logger from './logger';
25 25
 
26 26
 const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
@@ -186,12 +186,7 @@ function _checkAndNotifyForNewDevice(store, newDevices, oldDevices) {
186 186
 
187 187
         // we want to strip any device details that are not very
188 188
         // user friendly, like usb ids put in brackets at the end
189
-        let description = newDevice.label;
190
-        const ix = description.lastIndexOf('(');
191
-
192
-        if (ix !== -1) {
193
-            description = description.substr(0, ix);
194
-        }
189
+        const description = formatDeviceLabel(newDevice.label);
195 190
 
196 191
         let titleKey;
197 192
 

+ 143
- 0
react/features/base/devices/services/device-detect/ActiveDeviceDetector.js Dosyayı Görüntüle

@@ -0,0 +1,143 @@
1
+// @flow
2
+import EventEmitter from 'events';
3
+import { ACTIVE_DEVICE_DETECTED } from './Events';
4
+import logger from '../../logger';
5
+import JitsiMeetJS from '../../../lib-jitsi-meet';
6
+
7
+const JitsiTrackEvents = JitsiMeetJS.events.track;
8
+
9
+// If after 3000 ms the detector did not find any active devices consider that there aren't any usable ones available
10
+// i.e. audioLevel > 0.008
11
+const DETECTION_TIMEOUT = 3000;
12
+
13
+/**
14
+ * Detect active input devices based on their audio levels, currently this is very simplistic. It works by simply
15
+ * checking all monitored devices for TRACK_AUDIO_LEVEL_CHANGED if a device has a audio level > 0.008 ( 0.008 is
16
+ * no input from the perspective of a JitsiLocalTrack ), at which point it triggers a ACTIVE_DEVICE_DETECTED event.
17
+ * If there are no devices that meet that criteria for DETECTION_TIMEOUT an event with empty deviceLabel parameter
18
+ * will be triggered,
19
+ * signaling that no active device was detected.
20
+ * TODO Potentially improve the active device detection using rnnoise VAD scoring.
21
+ */
22
+export class ActiveDeviceDetector extends EventEmitter {
23
+
24
+    /**
25
+     * Currently monitored devices.
26
+     */
27
+    _availableDevices: Array<Object>;
28
+
29
+    /**
30
+     * State flag, check if the instance was destroyed.
31
+     */
32
+    _destroyed: boolean = false;
33
+
34
+    /**
35
+     * Create active device detector.
36
+     *
37
+     * @param {Array<MediaDeviceInfo>} micDeviceList - Device list that is monitored inside the service.
38
+     *
39
+     * @returns {ActiveDeviceDetector}
40
+     */
41
+    static async create(micDeviceList: Array<MediaDeviceInfo>) {
42
+        const availableDevices = [];
43
+
44
+        try {
45
+            for (const micDevice of micDeviceList) {
46
+                const localTrack = await JitsiMeetJS.createLocalTracks({
47
+                    devices: [ 'audio' ],
48
+                    micDeviceId: micDevice.deviceId
49
+                });
50
+
51
+                // We provide a specific deviceId thus we expect a single JitsiLocalTrack to be returned.
52
+                availableDevices.push(localTrack[0]);
53
+            }
54
+
55
+            return new ActiveDeviceDetector(availableDevices);
56
+        } catch (error) {
57
+            logger.error('Cleaning up remaining JitsiLocalTrack, due to ActiveDeviceDetector create fail!');
58
+
59
+            for (const device of availableDevices) {
60
+                device.stopStream();
61
+            }
62
+
63
+            throw error;
64
+        }
65
+    }
66
+
67
+    /**
68
+     * Constructor.
69
+     *
70
+     * @param {Array<Object>} availableDevices - Device list that is monitored inside the service.
71
+     */
72
+    constructor(availableDevices: Array<Object>) {
73
+        super();
74
+
75
+        this._availableDevices = availableDevices;
76
+
77
+        // Setup event handlers for monitored devices.
78
+        for (const device of this._availableDevices) {
79
+            device.on(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, audioLevel => {
80
+                this._handleAudioLevelEvent(device, audioLevel);
81
+            });
82
+        }
83
+
84
+        // Cancel the detection in case no devices was found with audioLevel > 0 in te set timeout.
85
+        setTimeout(this._handleDetectionTimeout.bind(this), DETECTION_TIMEOUT);
86
+    }
87
+
88
+    /**
89
+     * Handle what happens if no device publishes a score in the defined time frame, i.e. Emit an event with empty
90
+     * deviceLabel.
91
+     *
92
+     * @returns {void}
93
+     */
94
+    _handleDetectionTimeout() {
95
+        if (!this._destroyed) {
96
+            this.emit(ACTIVE_DEVICE_DETECTED, { deviceLabel: '',
97
+                audioLevel: 0 });
98
+            this.destroy();
99
+        }
100
+    }
101
+
102
+    /**
103
+     * Handles audio level event generated by JitsiLocalTracks.
104
+     *
105
+     * @param {Object} device - Label of the emitting track.
106
+     * @param {number} audioLevel - Audio level generated by device.
107
+     *
108
+     * @returns {void}
109
+     */
110
+    _handleAudioLevelEvent(device, audioLevel) {
111
+        if (!this._destroyed) {
112
+            // This is a very naive approach but works is most, a more accurate approach would ne to use rnnoise
113
+            // in order to limit the number of false positives.
114
+            // The 0.008 constant is due to how LocalStatsCollector from lib-jitsi-meet publishes audio-levels, in this
115
+            // case 0.008 denotes no input.
116
+            // TODO potentially refactor lib-jitsi-meet to expose this constant as a function. i.e. getSilenceLevel.
117
+            if (audioLevel > 0.008) {
118
+                this.emit(ACTIVE_DEVICE_DETECTED, { deviceId: device.deviceId,
119
+                    deviceLabel: device.track.label,
120
+                    audioLevel });
121
+                this.destroy();
122
+            }
123
+        }
124
+    }
125
+
126
+    /**
127
+     * Destroy the ActiveDeviceDetector, clean up the currently monitored devices associated JitsiLocalTracks.
128
+     *
129
+     * @returns {void}.
130
+     */
131
+    destroy() {
132
+        if (this._destroyed) {
133
+            return;
134
+        }
135
+
136
+        for (const device of this._availableDevices) {
137
+            device.removeAllListeners();
138
+            device.stopStream();
139
+        }
140
+
141
+        this._destroyed = true;
142
+    }
143
+}

+ 8
- 0
react/features/base/devices/services/device-detect/Events.js Dosyayı Görüntüle

@@ -0,0 +1,8 @@
1
+// Event triggered when the ActiveDeviceDetector finds an audio device that has audio input.
2
+// Note it does not check if the input is valid or not it simply checks for intensity > 0.008.
3
+// Event structure:
4
+// { deviceId: string,
5
+//   deviceLabel: string,
6
+//   audioLevel: number }
7
+// TO DO. Potentially use rnnoise service to get a more accurate reading.
8
+export const ACTIVE_DEVICE_DETECTED = 'active_device_detected';

+ 5
- 0
react/features/base/devices/services/index.js Dosyayı Görüntüle

@@ -0,0 +1,5 @@
1
+export * from './device-detect/ActiveDeviceDetector';
2
+export * from './device-detect/Events';
3
+export * from './vad-reporter/Events';
4
+export * from './vad-reporter/TrackVADEmitter';
5
+export * from './vad-reporter/VADReportingService';

react/features/vad-reporter/VADEvents.js → react/features/base/devices/services/vad-reporter/Events.js Dosyayı Görüntüle


react/features/vad-reporter/TrackVADEmitter.js → react/features/base/devices/services/vad-reporter/TrackVADEmitter.js Dosyayı Görüntüle

@@ -1,10 +1,10 @@
1 1
 // @flow
2 2
 
3
-import { createRnnoiseProcessorPromise, getSampleLength } from '../rnnoise/';
3
+import { createRnnoiseProcessorPromise, getSampleLength } from '../../../../rnnoise';
4 4
 import EventEmitter from 'events';
5
-import JitsiMeetJS from '../base/lib-jitsi-meet';
6
-import logger from './logger';
7
-import { VAD_SCORE_PUBLISHED } from './VADEvents';
5
+import JitsiMeetJS from '../../../lib-jitsi-meet';
6
+import logger from '../../logger';
7
+import { VAD_SCORE_PUBLISHED } from './Events';
8 8
 
9 9
 /**
10 10
  * The structure used by TrackVADEmitter to relay a score

react/features/vad-reporter/VADReportingService.js → react/features/base/devices/services/vad-reporter/VADReportingService.js Dosyayı Görüntüle

@@ -1,9 +1,9 @@
1 1
 // @flow
2 2
 
3 3
 import EventEmitter from 'events';
4
-import logger from './logger';
4
+import logger from '../../logger';
5 5
 import TrackVADEmitter from './TrackVADEmitter';
6
-import { VAD_SCORE_PUBLISHED, VAD_REPORT_PUBLISHED } from './VADEvents';
6
+import { VAD_SCORE_PUBLISHED, VAD_REPORT_PUBLISHED } from './Events';
7 7
 import type { VADScore } from './TrackVADEmitter';
8 8
 export type { VADScore };
9 9
 

+ 11
- 0
react/features/base/tracks/actionTypes.js Dosyayı Görüntüle

@@ -1,3 +1,14 @@
1
+/**
2
+ * The type of Redux action which sets the noSrcDataNotificationUid state representing the UID of the previous
3
+ * no data from source notification. Used to check if such a notification was previously displayed.
4
+ *
5
+ * {
6
+ *     type: SET_NO_SRC_DATA_NOTI_UID,
7
+ *     uid: ?number
8
+ * }
9
+ */
10
+export const SET_NO_SRC_DATA_NOTI_UID = 'SET_NO_SRC_DATA_NOTI_UID';
11
+
1 12
 /**
2 13
  * The type of redux action dispatched to disable screensharing or to start the
3 14
  * flow for enabling screenshare.

+ 27
- 0
react/features/base/tracks/actions.js Dosyayı Görüntüle

@@ -14,6 +14,7 @@ import {
14 14
 import { getLocalParticipant } from '../participants';
15 15
 
16 16
 import {
17
+    SET_NO_SRC_DATA_NOTI_UID,
17 18
     TOGGLE_SCREENSHARING,
18 19
     TRACK_ADDED,
19 20
     TRACK_CREATE_CANCELED,
@@ -342,6 +343,9 @@ export function trackAdded(track) {
342 343
         let isReceivingData, noDataFromSourceNotificationInfo, participantId;
343 344
 
344 345
         if (local) {
346
+            // Reset the no data from src notification state when we change the track, as it's context is set
347
+            // on a per device basis.
348
+            dispatch(setNoSrcDataNotificationUid());
345 349
             const participant = getLocalParticipant(getState);
346 350
 
347 351
             if (participant) {
@@ -358,6 +362,12 @@ export function trackAdded(track) {
358 362
                     });
359 363
 
360 364
                     dispatch(notificationAction);
365
+
366
+                    // Set the notification ID so that other parts of the application know that this was
367
+                    // displayed in the context of the current device.
368
+                    // I.E. The no-audio-signal notification shouldn't be displayed if this was already shown.
369
+                    dispatch(setNoSrcDataNotificationUid(notificationAction.uid));
370
+
361 371
                     noDataFromSourceNotificationInfo = { uid: notificationAction.uid };
362 372
                 } else {
363 373
                     const timeout = setTimeout(() => dispatch(showNoDataFromSourceVideoError(track)), 5000);
@@ -638,3 +648,20 @@ function _trackCreateCanceled(mediaType) {
638 648
         trackType: mediaType
639 649
     };
640 650
 }
651
+
652
+/**
653
+ * Sets UID of the displayed no data from source notification. Used to track
654
+ * if the notification was previously displayed in this context.
655
+ *
656
+ * @param {number} uid - Notification UID.
657
+ * @returns {{
658
+    *     type: SET_NO_AUDIO_SIGNAL_UID,
659
+    *     uid: number
660
+    * }}
661
+    */
662
+export function setNoSrcDataNotificationUid(uid) {
663
+    return {
664
+        type: SET_NO_SRC_DATA_NOTI_UID,
665
+        uid
666
+    };
667
+}

+ 16
- 1
react/features/base/tracks/reducer.js Dosyayı Görüntüle

@@ -1,7 +1,8 @@
1 1
 import { PARTICIPANT_ID_CHANGED } from '../participants';
2
-import { ReducerRegistry } from '../redux';
2
+import { ReducerRegistry, set } from '../redux';
3 3
 
4 4
 import {
5
+    SET_NO_SRC_DATA_NOTI_UID,
5 6
     TRACK_ADDED,
6 7
     TRACK_CREATE_CANCELED,
7 8
     TRACK_CREATE_ERROR,
@@ -133,3 +134,17 @@ ReducerRegistry.register('features/base/tracks', (state = [], action) => {
133 134
         return state;
134 135
     }
135 136
 });
137
+
138
+/**
139
+ * Listen for actions that mutate the no-src-data state, like the current notification id
140
+ */
141
+ReducerRegistry.register('features/base/no-src-data', (state = {}, action) => {
142
+    switch (action.type) {
143
+    case SET_NO_SRC_DATA_NOTI_UID:
144
+        return set(state, 'noSrcDataNotiUid', action.uid);
145
+
146
+    default:
147
+        return state;
148
+    }
149
+});
150
+

+ 11
- 0
react/features/no-audio-signal/actionTypes.js Dosyayı Görüntüle

@@ -0,0 +1,11 @@
1
+/**
2
+ * The type of Redux action which sets the pending notification UID
3
+ * to use it when hiding the notification is necessary, or unset it when
4
+ * undefined (or no param) is passed.
5
+ *
6
+ * {
7
+ *     type: SET_CURRENT_NOTIFICATION_UID,
8
+ *     uid: ?number
9
+ * }
10
+ */
11
+export const SET_NO_AUDIO_SIGNAL_NOTI_UID = 'SET_NO_AUDIO_SIGNAL_NOTI_UID';

+ 21
- 0
react/features/no-audio-signal/actions.js Dosyayı Görüntüle

@@ -0,0 +1,21 @@
1
+// @flow
2
+
3
+import { SET_NO_AUDIO_SIGNAL_NOTI_UID } from './actionTypes';
4
+
5
+/**
6
+ * Sets UID of the the pending notification to use it when hiding
7
+ * the notification is necessary, or unset it when undefined (or no param) is
8
+ * passed.
9
+ *
10
+ * @param {?number} uid - The UID of the notification.
11
+ * @returns {{
12
+ *     type: SET_NO_AUDIO_SIGNAL_NOTI_UID,
13
+ *     uid: number
14
+ * }}
15
+ */
16
+export function setNoAudioSignalNotificationUid(uid: ?number) {
17
+    return {
18
+        type: SET_NO_AUDIO_SIGNAL_NOTI_UID,
19
+        uid
20
+    };
21
+}

+ 6
- 0
react/features/no-audio-signal/constants.js Dosyayı Görüntüle

@@ -0,0 +1,6 @@
1
+/**
2
+ * The identifier of the sound to be played when we got an event for no audio signal.
3
+ *
4
+ * @type {string}
5
+ */
6
+export const NO_AUDIO_SIGNAL_SOUND_ID = 'NO_AUDIO_SIGNAL_SOUND_ID';

+ 4
- 0
react/features/no-audio-signal/index.js Dosyayı Görüntüle

@@ -0,0 +1,4 @@
1
+// @flow
2
+
3
+import './middleware';
4
+import './reducer';

+ 120
- 0
react/features/no-audio-signal/middleware.js Dosyayı Görüntüle

@@ -0,0 +1,120 @@
1
+// @flow
2
+import { setNoAudioSignalNotificationUid } from './actions';
3
+import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
4
+import { CONFERENCE_JOINED } from '../base/conference';
5
+import {
6
+    ACTIVE_DEVICE_DETECTED,
7
+    ActiveDeviceDetector,
8
+    filterAudioDevices,
9
+    formatDeviceLabel,
10
+    getAvailableDevices,
11
+    setAudioInputDevice
12
+} from '../base/devices';
13
+import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
14
+import { MiddlewareRegistry } from '../base/redux';
15
+import { updateSettings } from '../base/settings';
16
+import { playSound, registerSound, unregisterSound } from '../base/sounds';
17
+import { NO_AUDIO_SIGNAL_SOUND_ID } from './constants';
18
+import { hideNotification, showNotification } from '../notifications';
19
+import { NO_AUDIO_SIGNAL_SOUND_FILE } from './sounds';
20
+
21
+MiddlewareRegistry.register(store => next => async action => {
22
+    const result = next(action);
23
+    const { dispatch, getState } = store;
24
+    const { conference } = action;
25
+    let audioDetectService = null;
26
+
27
+    switch (action.type) {
28
+    case APP_WILL_MOUNT:
29
+        dispatch(registerSound(NO_AUDIO_SIGNAL_SOUND_ID, NO_AUDIO_SIGNAL_SOUND_FILE));
30
+        break;
31
+    case APP_WILL_UNMOUNT:
32
+        dispatch(unregisterSound(NO_AUDIO_SIGNAL_SOUND_ID));
33
+        break;
34
+
35
+    case CONFERENCE_JOINED: {
36
+        conference.on(JitsiConferenceEvents.TRACK_ADDED, track => {
37
+            const { noAudioSignalNotificationUid } = getState()['features/no-audio-signal'];
38
+
39
+            if (track.isAudioTrack() && track.isLocal()) {
40
+                // In case the device is switched attempt to destroy, this should prevent the notification firing
41
+                // when the device was switched, however it is possible that a user switches the device and the
42
+                // notification from the previous devices pops up, but this will probably happen very rarely and even
43
+                // if it does it's not that disruptive to the ux.
44
+                if (audioDetectService) {
45
+                    audioDetectService.destroy();
46
+                    audioDetectService = null;
47
+                }
48
+
49
+                // When a new track is added hide the current notification is one is displayed, and reset the redux
50
+                // state so that we begin monitoring on the new device as well.
51
+                if (noAudioSignalNotificationUid) {
52
+                    dispatch(hideNotification(noAudioSignalNotificationUid));
53
+                    dispatch(setNoAudioSignalNotificationUid());
54
+                }
55
+            }
56
+        });
57
+        conference.on(JitsiConferenceEvents.NO_AUDIO_INPUT, async () => {
58
+            const { noSrcDataNotiUid } = getState()['features/base/no-src-data'];
59
+
60
+            // In case the 'no data detected from source' notification was already shown, we prevent the
61
+            // no audio signal notification as it's redundant i.e. it's clear that the users microphone is
62
+            // muted from system settings.
63
+            if (noSrcDataNotiUid) {
64
+                return;
65
+            }
66
+
67
+            const devices = await dispatch(getAvailableDevices());
68
+            const audioDevices = filterAudioDevices(devices);
69
+
70
+            audioDetectService = await ActiveDeviceDetector.create(audioDevices);
71
+
72
+            audioDetectService.on(ACTIVE_DEVICE_DETECTED, detectEvent => {
73
+                let descriptionKey = 'toolbar.noAudioSignalDesc';
74
+                let customActionNameKey = null;
75
+                let customActionHandler = null;
76
+
77
+                // In case the detector picked up a device show a notification with a device suggestion
78
+                if (detectEvent.deviceLabel !== '') {
79
+                    descriptionKey = 'toolbar.noAudioSignalDescSuggestion';
80
+
81
+                    // Preferably the label should be passed as an argument paired with a i18next string, however
82
+                    // at the point of the implementation the showNotification function only supports doing that for
83
+                    // the description.
84
+                    // TODO Add support for arguments to showNotification title and customAction strings.
85
+                    customActionNameKey = `Use ${formatDeviceLabel(detectEvent.deviceLabel)}`;
86
+                    customActionHandler = () => {
87
+                        // Select device callback
88
+                        dispatch(
89
+                                updateSettings({
90
+                                    userSelectedMicDeviceId: detectEvent.deviceId,
91
+                                    userSelectedMicDeviceLabel: detectEvent.deviceLabel
92
+                                })
93
+                        );
94
+
95
+                        dispatch(setAudioInputDevice(detectEvent.deviceId));
96
+                    };
97
+                }
98
+
99
+                const notification = showNotification({
100
+                    titleKey: 'toolbar.noAudioSignalTitle',
101
+                    descriptionKey,
102
+                    customActionNameKey,
103
+                    customActionHandler
104
+                });
105
+
106
+                dispatch(notification);
107
+
108
+                dispatch(playSound(NO_AUDIO_SIGNAL_SOUND_ID));
109
+
110
+                // Store the current notification uid so we can check for this state and hide it in case
111
+                // a new track was added, thus changing the context of the notification
112
+                dispatch(setNoAudioSignalNotificationUid(notification.uid));
113
+            });
114
+        });
115
+        break;
116
+    }
117
+    }
118
+
119
+    return result;
120
+});

+ 17
- 0
react/features/no-audio-signal/reducer.js Dosyayı Görüntüle

@@ -0,0 +1,17 @@
1
+// @flow
2
+
3
+import { ReducerRegistry, set } from '../base/redux';
4
+
5
+import { SET_NO_AUDIO_SIGNAL_NOTI_UID } from './actionTypes';
6
+
7
+/**
8
+ * Reduces the redux actions of the feature no audio signal
9
+ */
10
+ReducerRegistry.register('features/no-audio-signal', (state = {}, action) => {
11
+    switch (action.type) {
12
+    case SET_NO_AUDIO_SIGNAL_NOTI_UID:
13
+        return set(state, 'noAudioSignalNotificationUid', action.uid);
14
+    }
15
+
16
+    return state;
17
+});

+ 6
- 0
react/features/no-audio-signal/sounds.js Dosyayı Görüntüle

@@ -0,0 +1,6 @@
1
+/**
2
+ * The file used for the no audio signal sound notification.
3
+ *
4
+ * @type {string}
5
+ */
6
+export const NO_AUDIO_SIGNAL_SOUND_FILE = 'noAudioSignal.mp3';

+ 0
- 5
react/features/vad-reporter/logger.js Dosyayı Görüntüle

@@ -1,5 +0,0 @@
1
-// @flow
2
-
3
-import { getLogger } from '../base/logging/functions';
4
-
5
-export default getLogger('features/vad-reporter');

BIN
sounds/noAudioSignal.mp3 Dosyayı Görüntüle


Loading…
İptal
Kaydet