From c10805f81b156e95a66c67f2346bde27616bdea5 Mon Sep 17 00:00:00 2001 From: robertpin Date: Tue, 20 Jul 2021 14:56:57 +0300 Subject: [PATCH] feat(sound-settings) Added ability to control sounds --- config.js | 3 + interface_config.js | 2 +- lang/main.json | 6 + react/features/base/config/configWhitelist.js | 1 + .../features/base/participants/middleware.js | 6 +- react/features/base/settings/reducer.js | 4 + react/features/chat/middleware.js | 4 +- react/features/settings/actions.js | 27 +++- .../settings/components/web/SettingsDialog.js | 17 ++- .../settings/components/web/SoundsTab.js | 123 ++++++++++++++++++ react/features/settings/constants.js | 3 +- react/features/settings/functions.js | 26 ++++ react/features/talk-while-muted/middleware.js | 7 +- 13 files changed, 221 insertions(+), 8 deletions(-) create mode 100644 react/features/settings/components/web/SoundsTab.js diff --git a/config.js b/config.js index 34d261882..e3345da99 100644 --- a/config.js +++ b/config.js @@ -603,6 +603,9 @@ var config = { // conference (if set to true, these sounds will not be played). // disableJoinLeaveSounds: false, + // Disables the sounds that play when a chat message is received. + // disableIncomingMessageSound: false, + // Information for the chrome extension banner // chromeExtensionBanner: { // // The chrome extension to be installed address diff --git a/interface_config.js b/interface_config.js index 425b7844b..39b4743fb 100644 --- a/interface_config.js +++ b/interface_config.js @@ -174,7 +174,7 @@ var interfaceConfig = { RECENT_LIST_ENABLED: true, REMOTE_THUMBNAIL_RATIO: 1, // 1:1 - SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar' ], + SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar', 'sounds' ], /** * Specify which sharing features should be displayed. If the value is not set diff --git a/lang/main.json b/lang/main.json index 57c0d91ff..87c9177a1 100644 --- a/lang/main.json +++ b/lang/main.json @@ -744,6 +744,7 @@ "devices": "Devices", "followMe": "Everyone follows me", "framesPerSecond": "frames-per-second", + "incomingMessage": "Incoming message", "language": "Language", "loggedIn": "Logged in as {{name}}", "microphones": "Microphones", @@ -751,13 +752,18 @@ "more": "More", "name": "Name", "noDevice": "None", + "participantJoined": "Participant Joined", + "participantLeft": "Participant Left", + "playSounds": "Play sound on", "sameAsSystem": "Same as system ({{label}})", "selectAudioOutput": "Audio output", "selectCamera": "Camera", "selectMic": "Microphone", + "sounds": "Sounds", "speakers": "Speakers", "startAudioMuted": "Everyone starts muted", "startVideoMuted": "Everyone starts hidden", + "talkWhileMuted": "Talk while muted", "title": "Settings" }, "settingsView": { diff --git a/react/features/base/config/configWhitelist.js b/react/features/base/config/configWhitelist.js index d8811dd00..af2130597 100644 --- a/react/features/base/config/configWhitelist.js +++ b/react/features/base/config/configWhitelist.js @@ -87,6 +87,7 @@ export default [ 'disableH264', 'disableHPF', 'disableInviteFunctions', + 'disableIncomingMessageSound', 'disableJoinLeaveSounds', 'disableLocalVideoFlip', 'disableNS', diff --git a/react/features/base/participants/middleware.js b/react/features/base/participants/middleware.js index 5c2b986aa..0a465772f 100644 --- a/react/features/base/participants/middleware.js +++ b/react/features/base/participants/middleware.js @@ -371,6 +371,7 @@ function _localParticipantLeft({ dispatch }, next, action) { function _maybePlaySounds({ getState, dispatch }, action) { const state = getState(); const { startAudioMuted, disableJoinLeaveSounds } = state['features/base/config']; + const { soundsParticipantJoined: joinSound, soundsParticipantLeft: leftSound } = state['features/base/settings']; // If we have join/leave sounds disabled, don't play anything. if (disableJoinLeaveSounds) { @@ -387,13 +388,16 @@ function _maybePlaySounds({ getState, dispatch }, action) { const { isReplacing, isReplaced } = action.participant; if (action.type === PARTICIPANT_JOINED) { + if (!joinSound) { + return; + } const { presence } = action.participant; // The sounds for the poltergeist are handled by features/invite. if (presence !== INVITED && presence !== CALLING && !isReplacing) { dispatch(playSound(PARTICIPANT_JOINED_SOUND_ID)); } - } else if (action.type === PARTICIPANT_LEFT && !isReplaced) { + } else if (action.type === PARTICIPANT_LEFT && !isReplaced && leftSound) { dispatch(playSound(PARTICIPANT_LEFT_SOUND_ID)); } } diff --git a/react/features/base/settings/reducer.js b/react/features/base/settings/reducer.js index 4536c4731..c21869795 100644 --- a/react/features/base/settings/reducer.js +++ b/react/features/base/settings/reducer.js @@ -27,6 +27,10 @@ const DEFAULT_STATE = { micDeviceId: undefined, serverURL: undefined, hideShareAudioHelper: false, + soundsIncomingMessage: true, + soundsParticipantJoined: true, + soundsParticipantLeft: true, + soundsTalkWhileMuted: true, startAudioOnly: false, startWithAudioMuted: false, startWithVideoMuted: false, diff --git a/react/features/chat/middleware.js b/react/features/chat/middleware.js index 3f1a3be0e..e803f1203 100644 --- a/react/features/chat/middleware.js +++ b/react/features/chat/middleware.js @@ -305,8 +305,10 @@ function _handleReceivedMessage({ dispatch, getState }, // Logic for all platforms: const state = getState(); const { isOpen: isChatOpen } = state['features/chat']; + const { disableIncomingMessageSound } = state['features/base/config']; + const { soundsIncomingMessage: soundEnabled } = state['features/base/settings']; - if (shouldPlaySound && !isChatOpen) { + if (!disableIncomingMessageSound && soundEnabled && shouldPlaySound && !isChatOpen) { dispatch(playSound(INCOMING_MSG_SOUND_ID)); } diff --git a/react/features/settings/actions.js b/react/features/settings/actions.js index 1c4a77d86..c793cf25b 100644 --- a/react/features/settings/actions.js +++ b/react/features/settings/actions.js @@ -12,7 +12,7 @@ import { SET_VIDEO_SETTINGS_VISIBILITY } from './actionTypes'; import { LogoutDialog, SettingsDialog } from './components'; -import { getMoreTabProps, getProfileTabProps } from './functions'; +import { getMoreTabProps, getProfileTabProps, getSoundsTabProps } from './functions'; declare var APP: Object; @@ -129,6 +129,31 @@ export function submitProfileTab(newState: Object): Function { }; } +/** + * Submits the settings from the "Sounds" tab of the settings dialog. + * + * @param {Object} newState - The new settings. + * @returns {Function} + */ +export function submitSoundsTab(newState: Object): Function { + return (dispatch, getState) => { + const currentState = getSoundsTabProps(getState()); + const shouldUpdate = (newState.soundsIncomingMessage !== currentState.soundsIncomingMessage) + || (newState.soundsParticipantJoined !== currentState.soundsParticipantJoined) + || (newState.soundsParticipantLeft !== currentState.soundsParticipantLeft) + || (newState.soundsTalkWhileMuted !== currentState.soundsTalkWhileMuted); + + if (shouldUpdate) { + dispatch(updateSettings({ + soundsIncomingMessage: newState.soundsIncomingMessage, + soundsParticipantJoined: newState.soundsParticipantJoined, + soundsParticipantLeft: newState.soundsParticipantLeft, + soundsTalkWhileMuted: newState.soundsTalkWhileMuted + })); + } + }; +} + /** * Toggles the visibility of the audio settings. * diff --git a/react/features/settings/components/web/SettingsDialog.js b/react/features/settings/components/web/SettingsDialog.js index ce1f54a53..1126da3e3 100644 --- a/react/features/settings/components/web/SettingsDialog.js +++ b/react/features/settings/components/web/SettingsDialog.js @@ -11,13 +11,14 @@ import { getDeviceSelectionDialogProps, submitDeviceSelectionTab } from '../../../device-selection'; -import { submitMoreTab, submitProfileTab } from '../../actions'; +import { submitMoreTab, submitProfileTab, submitSoundsTab } from '../../actions'; import { SETTINGS_TABS } from '../../constants'; -import { getMoreTabProps, getProfileTabProps } from '../../functions'; +import { getMoreTabProps, getProfileTabProps, getSoundsTabProps } from '../../functions'; import CalendarTab from './CalendarTab'; import MoreTab from './MoreTab'; import ProfileTab from './ProfileTab'; +import SoundsTab from './SoundsTab'; declare var APP: Object; declare var interfaceConfig: Object; @@ -135,6 +136,7 @@ function _mapStateToProps(state) { = configuredTabs.includes('profile') && !state['features/base/config'].disableProfile; const showCalendarSettings = configuredTabs.includes('calendar') && isCalendarEnabled(state); + const showSoundsSettings = configuredTabs.includes('sounds'); const tabs = []; if (showDeviceSettings) { @@ -183,6 +185,17 @@ function _mapStateToProps(state) { }); } + if (showSoundsSettings) { + tabs.push({ + name: SETTINGS_TABS.SOUNDS, + component: SoundsTab, + label: 'settings.sounds', + props: getSoundsTabProps(state), + styles: 'settings-pane profile-pane', + submit: submitSoundsTab + }); + } + if (showModeratorSettings || showLanguageSettings || showPrejoinSettings) { tabs.push({ name: SETTINGS_TABS.MORE, diff --git a/react/features/settings/components/web/SoundsTab.js b/react/features/settings/components/web/SoundsTab.js new file mode 100644 index 000000000..20cb7fb5a --- /dev/null +++ b/react/features/settings/components/web/SoundsTab.js @@ -0,0 +1,123 @@ +// @flow + +import Checkbox from '@atlaskit/checkbox'; +import React from 'react'; + +import { AbstractDialogTab } from '../../../base/dialog'; +import type { Props as AbstractDialogTabProps } from '../../../base/dialog'; +import { translate } from '../../../base/i18n'; + +declare var APP: Object; + +/** + * The type of the React {@code Component} props of {@link SoundsTab}. + */ +export type Props = { + ...$Exact, + + /** + * Whether or not the sound for the incoming message should play. + */ + soundsIncomingMessage: Boolean, + + /** + * Whether or not the sound for the participant joined should play. + */ + soundsParticipantJoined: Boolean, + + /** + * Whether or not the sound for the participant left should play. + */ + soundsParticipantLeft: Boolean, + + /** + * Whether or not the sound for the talk while muted notification should play. + */ + soundsTalkWhileMuted: Boolean, + + /** + * Invoked to obtain translated strings. + */ + t: Function +} + +/** + * React {@code Component} for modifying the local user's sound settings. + * + * @extends Component + */ +class SoundsTab extends AbstractDialogTab { + /** + * Initializes a new {@code SoundsTab} instance. + * + * @param {Props} props - The React {@code Component} props to initialize + * the new {@code SoundsTab} instance with. + */ + constructor(props: Props) { + super(props); + + // Bind event handlers so they are only bound once for every instance. + this._onChange = this._onChange.bind(this); + } + + _onChange: (Object) => void; + + /** + * Changes a sound setting state. + * + * @param {Object} e - The key event to handle. + * + * @returns {void} + */ + _onChange({ target }) { + super._onChange({ [target.name]: target.checked }); + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + const { + soundsIncomingMessage, + soundsParticipantJoined, + soundsParticipantLeft, + soundsTalkWhileMuted, + t + } = this.props; + + return ( +
+

+ {t('settings.playSounds')} +

+ + + + +
+ ); + } +} + +export default translate(SoundsTab); diff --git a/react/features/settings/constants.js b/react/features/settings/constants.js index 7d1c3e1ba..140f9a1e2 100644 --- a/react/features/settings/constants.js +++ b/react/features/settings/constants.js @@ -2,7 +2,8 @@ export const SETTINGS_TABS = { CALENDAR: 'calendar_tab', DEVICES: 'devices_tab', MORE: 'more_tab', - PROFILE: 'profile_tab' + PROFILE: 'profile_tab', + SOUNDS: 'sounds_tab' }; /** diff --git a/react/features/settings/functions.js b/react/features/settings/functions.js index f30be6d22..31b99e72a 100644 --- a/react/features/settings/functions.js +++ b/react/features/settings/functions.js @@ -156,6 +156,32 @@ export function getProfileTabProps(stateful: Object | Function) { }; } +/** + * Returns the properties for the "Sounds" tab from settings dialog from Redux + * state. + * + * @param {(Function|Object)} stateful -The (whole) redux state, or redux's + * {@code getState} function to be used to retrieve the state. + * @returns {Object} - The properties for the "Sounds" tab from settings + * dialog. + */ +export function getSoundsTabProps(stateful: Object | Function) { + const state = toState(stateful); + const { + soundsIncomingMessage, + soundsParticipantJoined, + soundsParticipantLeft, + soundsTalkWhileMuted + } = state['features/base/settings']; + + return { + soundsIncomingMessage, + soundsParticipantJoined, + soundsParticipantLeft, + soundsTalkWhileMuted + }; +} + /** * Returns a promise which resolves with a list of objects containing * all the video jitsiTracks and appropriate errors for the given device ids. diff --git a/react/features/talk-while-muted/middleware.js b/react/features/talk-while-muted/middleware.js index 5103e4045..c203c5057 100644 --- a/react/features/talk-while-muted/middleware.js +++ b/react/features/talk-while-muted/middleware.js @@ -47,7 +47,12 @@ MiddlewareRegistry.register(store => next => action => { customActionHandler: () => dispatch(setAudioMuted(false)) })); - dispatch(playSound(TALK_WHILE_MUTED_SOUND_ID)); + const { soundsTalkWhileMuted } = getState()['features/base/settings']; + + if (soundsTalkWhileMuted) { + dispatch(playSound(TALK_WHILE_MUTED_SOUND_ID)); + } + if (notification) { // we store the last start muted notification id that we showed,