diff --git a/config.js b/config.js index da1c05d2b..f774216f6 100644 --- a/config.js +++ b/config.js @@ -76,7 +76,13 @@ var config = { // Enabling this will run the lib-jitsi-meet no audio detection module which // will notify the user if the current selected microphone has no audio // input and will suggest another valid device if one is present. - // enableNoAudioDetection: false + enableNoAudioDetection: true, + + // Enabling this will run the lib-jitsi-meet noise detection module which will + // notify the user if there is noise, other than voice, coming from the current + // selected microphone. The purpose it to let the user know that the input could + // be potentially unpleasant for other meeting participants. + enableNoisyMicDetection: true, // Start the conference in audio only mode (no video is being received nor // sent). diff --git a/lang/main.json b/lang/main.json index 987d0193e..43649e622 100644 --- a/lang/main.json +++ b/lang/main.json @@ -634,6 +634,8 @@ "noAudioSignalTitle": "There is no input coming from your mic!", "noAudioSignalDesc": "If you did not purposely mute it from system settings or hardware, consider changing the device.", "noAudioSignalDescSuggestion": "If you did not purposely mute it from system settings or hardware, consider using the following device:", + "noisyAudioInputTitle": "Your microphone appears to be noisy!", + "noisyAudioInputDesc": "Jitsi has detected noise coming from your microphone, please consider muting or changing the device.", "openChat": "Open chat", "pip": "Enter Picture-in-Picture mode", "privateMessage": "Send private message", diff --git a/package-lock.json b/package-lock.json index a6fb81fa6..a55bfc924 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10869,8 +10869,8 @@ } }, "lib-jitsi-meet": { - "version": "github:jitsi/lib-jitsi-meet#8deeaf6bd2b233ab163303e42febfee87201b2d9", - "from": "github:jitsi/lib-jitsi-meet#8deeaf6bd2b233ab163303e42febfee87201b2d9", + "version": "github:jitsi/lib-jitsi-meet#e4b523d0fab83a1cf1408abbf41f5d0ca1169900", + "from": "github:jitsi/lib-jitsi-meet#e4b523d0fab83a1cf1408abbf41f5d0ca1169900", "requires": { "@jitsi/sdp-interop": "0.1.14", "@jitsi/sdp-simulcast": "0.2.2", diff --git a/package.json b/package.json index 8a1b207e6..495baa8c7 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "js-utils": "github:jitsi/js-utils#400ce825d3565019946ee75d86ed773c6f21e117", "jsrsasign": "8.0.12", "jwt-decode": "2.2.0", - "lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#8deeaf6bd2b233ab163303e42febfee87201b2d9", + "lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#e4b523d0fab83a1cf1408abbf41f5d0ca1169900", "libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d", "lodash": "4.17.13", "moment": "2.19.4", diff --git a/react/features/app/components/App.web.js b/react/features/app/components/App.web.js index 670183a95..56b23c0d5 100644 --- a/react/features/app/components/App.web.js +++ b/react/features/app/components/App.web.js @@ -8,6 +8,7 @@ import '../../base/user-interaction'; import '../../chat'; import '../../external-api'; import '../../no-audio-signal'; +import '../../noise-detection'; import '../../power-monitor'; import '../../room-lock'; import '../../talk-while-muted'; diff --git a/react/features/no-audio-signal/middleware.js b/react/features/no-audio-signal/middleware.js index bf30d1ed7..148e9fa7c 100644 --- a/react/features/no-audio-signal/middleware.js +++ b/react/features/no-audio-signal/middleware.js @@ -48,13 +48,10 @@ MiddlewareRegistry.register(store => next => async action => { async function _handleNoAudioSignalNotification({ dispatch, getState }, action) { const { conference } = action; - let confAudioInputState; conference.on(JitsiConferenceEvents.AUDIO_INPUT_STATE_CHANGE, hasAudioInput => { const { noAudioSignalNotificationUid } = getState()['features/no-audio-signal']; - confAudioInputState = hasAudioInput; - // In case the notification is displayed but the conference detected audio input signal we hide it. if (noAudioSignalNotificationUid && hasAudioInput) { dispatch(hideNotification(noAudioSignalNotificationUid)); @@ -71,17 +68,8 @@ async function _handleNoAudioSignalNotification({ dispatch, getState }, action) return; } - // Force the flag to false in case AUDIO_INPUT_STATE_CHANGE is received after the notification is displayed, - // possibly preventing the notification from displaying because of an outdated state. - confAudioInputState = false; - - const activeDevice = await JitsiMeetJS.getActiveAudioDevice(); - if (confAudioInputState) { - return; - } - // In case there is a previous notification displayed just hide it. const { noAudioSignalNotificationUid } = getState()['features/no-audio-signal']; diff --git a/react/features/noise-detection/actionTypes.js b/react/features/noise-detection/actionTypes.js new file mode 100644 index 000000000..a97d99fe0 --- /dev/null +++ b/react/features/noise-detection/actionTypes.js @@ -0,0 +1,11 @@ +/** + * The type of Redux action which sets the pending notification UID + * to use it when hiding the notification is necessary, or unset it when + * undefined (or no param) is passed. + * + * { + * type: SET_NOISY_AUDIO_INPUT_NOTIFICATION_UID + * uid: ?number + * } + */ +export const SET_NOISY_AUDIO_INPUT_NOTIFICATION_UID = 'SET_NOISY_AUDIO_INPUT_NOTIFICATION_UID'; diff --git a/react/features/noise-detection/actions.js b/react/features/noise-detection/actions.js new file mode 100644 index 000000000..a4bdb2266 --- /dev/null +++ b/react/features/noise-detection/actions.js @@ -0,0 +1,21 @@ +// @flow + +import { SET_NOISY_AUDIO_INPUT_NOTIFICATION_UID } from './actionTypes'; + +/** + * Sets UID of the the pending notification to use it when hiding + * the notification is necessary, or unset it when undefined (or no param) is + * passed. + * + * @param {?number} uid - The UID of the notification. + * @returns {{ + * type: SET_NOISY_AUDIO_INPUT_NOTIFICATION_UID, + * uid: number + * }} + */ +export function setNoisyAudioInputNotificationUid(uid: ?number) { + return { + type: SET_NOISY_AUDIO_INPUT_NOTIFICATION_UID, + uid + }; +} diff --git a/react/features/noise-detection/constants.js b/react/features/noise-detection/constants.js new file mode 100644 index 000000000..231300e86 --- /dev/null +++ b/react/features/noise-detection/constants.js @@ -0,0 +1,6 @@ +/** + * The identifier of the sound to be played when we display a you are noisy notification. + * + * @type {string} + */ +export const NOISY_AUDIO_INPUT_SOUND_ID = 'NOISY_AUDIO_INPUT_SOUND_ID'; diff --git a/react/features/noise-detection/index.js b/react/features/noise-detection/index.js new file mode 100644 index 000000000..d040422d0 --- /dev/null +++ b/react/features/noise-detection/index.js @@ -0,0 +1,4 @@ +// @flow + +import './middleware'; +import './reducer'; diff --git a/react/features/noise-detection/middleware.js b/react/features/noise-detection/middleware.js new file mode 100644 index 000000000..2e7df5361 --- /dev/null +++ b/react/features/noise-detection/middleware.js @@ -0,0 +1,57 @@ +// @flow + +import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app'; +import { CONFERENCE_JOINED } from '../base/conference'; +import { JitsiConferenceEvents } from '../base/lib-jitsi-meet'; +import { MiddlewareRegistry } from '../base/redux'; +import { playSound, registerSound, unregisterSound } from '../base/sounds'; +import { hideNotification, showNotification } from '../notifications'; + +import { setNoisyAudioInputNotificationUid } from './actions'; +import { NOISY_AUDIO_INPUT_SOUND_ID } from './constants'; +import { NOISY_AUDIO_INPUT_SOUND_FILE } from './sounds'; + +MiddlewareRegistry.register(store => next => action => { + const result = next(action); + + switch (action.type) { + case APP_WILL_MOUNT: + store.dispatch(registerSound(NOISY_AUDIO_INPUT_SOUND_ID, NOISY_AUDIO_INPUT_SOUND_FILE)); + break; + case APP_WILL_UNMOUNT: + store.dispatch(unregisterSound(NOISY_AUDIO_INPUT_SOUND_ID)); + break; + case CONFERENCE_JOINED: { + const { dispatch, getState } = store; + const { conference } = action; + + conference.on( + JitsiConferenceEvents.TRACK_MUTE_CHANGED, + track => { + const { noisyAudioInputNotificationUid } = getState()['features/noise-detection']; + + // Hide the notification in case the user mutes the microphone + if (noisyAudioInputNotificationUid && track.isAudioTrack() && track.isLocal() && track.isMuted()) { + dispatch(hideNotification(noisyAudioInputNotificationUid)); + dispatch(setNoisyAudioInputNotificationUid()); + } + }); + conference.on( + JitsiConferenceEvents.NOISY_MIC, () => { + const notification = showNotification({ + titleKey: 'toolbar.noisyAudioInputTitle', + descriptionKey: 'toolbar.noisyAudioInputDesc' + }); + + dispatch(notification); + dispatch(playSound(NOISY_AUDIO_INPUT_SOUND_ID)); + + // we store the last notification id so we can hide it if the mic is muted + dispatch(setNoisyAudioInputNotificationUid(notification.uid)); + }); + break; + } + } + + return result; +}); diff --git a/react/features/noise-detection/reducer.js b/react/features/noise-detection/reducer.js new file mode 100644 index 000000000..5fbc2ecc7 --- /dev/null +++ b/react/features/noise-detection/reducer.js @@ -0,0 +1,17 @@ +// @flow + +import { ReducerRegistry, set } from '../base/redux'; + +import { SET_NOISY_AUDIO_INPUT_NOTIFICATION_UID } from './actionTypes'; + +/** + * Reduces the redux actions of noise detection feature + */ +ReducerRegistry.register('features/noise-detection', (state = {}, action) => { + switch (action.type) { + case SET_NOISY_AUDIO_INPUT_NOTIFICATION_UID: + return set(state, 'noisyAudioInputNotificationUid', action.uid); + } + + return state; +}); diff --git a/react/features/noise-detection/sounds.js b/react/features/noise-detection/sounds.js new file mode 100644 index 000000000..c949e249e --- /dev/null +++ b/react/features/noise-detection/sounds.js @@ -0,0 +1,6 @@ +/** + * The file used for the noisy audio input notification. + * + * @type {string} + */ +export const NOISY_AUDIO_INPUT_SOUND_FILE = 'noisyAudioInput.mp3'; diff --git a/sounds/noisyAudioInput.mp3 b/sounds/noisyAudioInput.mp3 new file mode 100644 index 000000000..cf07fb704 Binary files /dev/null and b/sounds/noisyAudioInput.mp3 differ