Browse Source

feat(reaction-sounds) Added sounds for reactions (#9775)

* Added sounds for reactions

* Updated reactions list

* Added reactions to sound settings

* Added support for multiple sounds

* Added feature flag for sounds

* Updated sound settings

Moved reactions toggle at the top of the list

* Added disable reaction sounds notification

* Added reaction button zoom for burst intensity

* Fixed raise hand sound

* Fixed register sounds for reactions

* Changed boo emoji

* Updated sounds

* Fixed lint errors

* Fixed reaction sounds file names

* Fix raise hand sound

Play sound only on raise hand not on lower hand

* Fixed types for sound constants

* Fixed type for raise hand sound constant
master
robertpin 4 years ago
parent
commit
c7a91e1974
No account linked to committer's email address

+ 7
- 0
css/_reactions-menu.scss View File

48
 			display: flex;
48
 			display: flex;
49
 			align-items: center;
49
 			align-items: center;
50
 			justify-content: center;
50
 			justify-content: center;
51
+			transition: font-size ease .1s;
52
+
53
+			@for $i from 1 through 12 {
54
+				&.increase-#{$i}{
55
+					font-size: calc(20px + #{$i}px);
56
+				}
57
+			}
51
 		}
58
 		}
52
 	}
59
 	}
53
 
60
 

+ 6
- 3
lang/main.json View File

591
         "moderationStoppedTitle": "Moderation stopped",
591
         "moderationStoppedTitle": "Moderation stopped",
592
         "moderationToggleDescription": "by {{participantDisplayName}}",
592
         "moderationToggleDescription": "by {{participantDisplayName}}",
593
         "raiseHandAction": "Raise hand",
593
         "raiseHandAction": "Raise hand",
594
+        "reactionSounds": "Disable sounds",
594
         "groupTitle": "Notifications"
595
         "groupTitle": "Notifications"
595
     },
596
     },
596
     "participantsPane": {
597
     "participantsPane": {
794
         "participantJoined": "Participant Joined",
795
         "participantJoined": "Participant Joined",
795
         "participantLeft": "Participant Left",
796
         "participantLeft": "Participant Left",
796
         "playSounds": "Play sound on",
797
         "playSounds": "Play sound on",
798
+        "reactions": "Meeting reactions",
797
         "sameAsSystem": "Same as system ({{label}})",
799
         "sameAsSystem": "Same as system ({{label}})",
798
         "selectAudioOutput": "Audio output",
800
         "selectAudioOutput": "Audio output",
799
         "selectCamera": "Camera",
801
         "selectCamera": "Camera",
884
             "muteEveryonesVideo": "Disable everyone's camera",
886
             "muteEveryonesVideo": "Disable everyone's camera",
885
             "muteEveryoneElsesVideo": "Disable everyone else's camera",
887
             "muteEveryoneElsesVideo": "Disable everyone else's camera",
886
             "participants": "Participants",
888
             "participants": "Participants",
887
-            "party": "Party Popper",
888
             "pip": "Toggle Picture-in-Picture mode",
889
             "pip": "Toggle Picture-in-Picture mode",
889
             "privateMessage": "Send private message",
890
             "privateMessage": "Send private message",
890
             "profile": "Edit your profile",
891
             "profile": "Edit your profile",
901
             "shareYourScreen": "Start / Stop sharing your screen",
902
             "shareYourScreen": "Start / Stop sharing your screen",
902
             "shortcuts": "Toggle shortcuts",
903
             "shortcuts": "Toggle shortcuts",
903
             "show": "Show on stage",
904
             "show": "Show on stage",
905
+            "silence": "Silence",
904
             "speakerStats": "Toggle speaker statistics",
906
             "speakerStats": "Toggle speaker statistics",
905
             "surprised": "Surprised",
907
             "surprised": "Surprised",
906
             "tileView": "Toggle tile view",
908
             "tileView": "Toggle tile view",
925
         "clap": "Clap",
927
         "clap": "Clap",
926
         "closeChat": "Close chat",
928
         "closeChat": "Close chat",
927
         "closeReactionsMenu": "Close reactions menu",
929
         "closeReactionsMenu": "Close reactions menu",
930
+        "disableReactionSounds": "You can disable reaction sounds for this meeting",
928
         "documentClose": "Close shared document",
931
         "documentClose": "Close shared document",
929
         "documentOpen": "Open shared document",
932
         "documentOpen": "Open shared document",
930
         "download": "Download our apps",
933
         "download": "Download our apps",
960
         "openChat": "Open chat",
963
         "openChat": "Open chat",
961
         "openReactionsMenu": "Open reactions menu",
964
         "openReactionsMenu": "Open reactions menu",
962
         "participants": "Participants",
965
         "participants": "Participants",
963
-        "party": "Celebration",
964
         "pip": "Enter Picture-in-Picture mode",
966
         "pip": "Enter Picture-in-Picture mode",
965
         "privateMessage": "Send private message",
967
         "privateMessage": "Send private message",
966
         "profile": "Edit your profile",
968
         "profile": "Edit your profile",
970
         "reactionClap": "Send clap reaction",
972
         "reactionClap": "Send clap reaction",
971
         "reactionLaugh": "Send laugh reaction",
973
         "reactionLaugh": "Send laugh reaction",
972
         "reactionLike": "Send thumbs up reaction",
974
         "reactionLike": "Send thumbs up reaction",
973
-        "reactionParty": "Send party popper reaction",
975
+        "reactionSilence": "Send silence reaction",
974
         "reactionSurprised": "Send surprised reaction",
976
         "reactionSurprised": "Send surprised reaction",
975
         "security": "Security options",
977
         "security": "Security options",
976
         "Settings": "Settings",
978
         "Settings": "Settings",
978
         "sharedvideo": "Share video",
980
         "sharedvideo": "Share video",
979
         "shareRoom": "Invite someone",
981
         "shareRoom": "Invite someone",
980
         "shortcuts": "View shortcuts",
982
         "shortcuts": "View shortcuts",
983
+        "silence": "Silence",
981
         "speakerStats": "Speaker stats",
984
         "speakerStats": "Speaker stats",
982
         "startScreenSharing": "Start screen sharing",
985
         "startScreenSharing": "Start screen sharing",
983
         "startSubtitles": "Start subtitles",
986
         "startSubtitles": "Start subtitles",

+ 1
- 0
react/features/base/settings/reducer.js View File

31
     soundsParticipantJoined: true,
31
     soundsParticipantJoined: true,
32
     soundsParticipantLeft: true,
32
     soundsParticipantLeft: true,
33
     soundsTalkWhileMuted: true,
33
     soundsTalkWhileMuted: true,
34
+    soundsReactions: true,
34
     startAudioOnly: false,
35
     startAudioOnly: false,
35
     startWithAudioMuted: false,
36
     startWithAudioMuted: false,
36
     startWithVideoMuted: false,
37
     startWithVideoMuted: false,

+ 5
- 0
react/features/reactions/actionTypes.js View File

58
  * The type of action to adds reactions to the queue.
58
  * The type of action to adds reactions to the queue.
59
  */
59
  */
60
 export const PUSH_REACTIONS = 'PUSH_REACTIONS';
60
 export const PUSH_REACTIONS = 'PUSH_REACTIONS';
61
+
62
+/**
63
+ * The type of action to display disable notification sounds.
64
+ */
65
+export const SHOW_SOUNDS_NOTIFICATION = 'SHOW_SOUNDS_NOTIFICATION';

+ 13
- 1
react/features/reactions/actions.web.js View File

1
 // @flow
1
 // @flow
2
 
2
 
3
 import {
3
 import {
4
+    SHOW_SOUNDS_NOTIFICATION,
4
     TOGGLE_REACTIONS_VISIBLE
5
     TOGGLE_REACTIONS_VISIBLE
5
 } from './actionTypes';
6
 } from './actionTypes';
6
 
7
 
7
 /**
8
 /**
8
  * Toggles the visibility of the reactions menu.
9
  * Toggles the visibility of the reactions menu.
9
  *
10
  *
10
- * @returns {Function}
11
+ * @returns {Object}
11
  */
12
  */
12
 export function toggleReactionsMenuVisibility() {
13
 export function toggleReactionsMenuVisibility() {
13
     return {
14
     return {
14
         type: TOGGLE_REACTIONS_VISIBLE
15
         type: TOGGLE_REACTIONS_VISIBLE
15
     };
16
     };
16
 }
17
 }
18
+
19
+/**
20
+ * Displays the disable sounds notification.
21
+ *
22
+ * @returns {Object}
23
+ */
24
+export function displayReactionSoundsNotification() {
25
+    return {
26
+        type: SHOW_SOUNDS_NOTIFICATION
27
+    };
28
+}

+ 54
- 5
react/features/reactions/components/web/ReactionButton.js View File

28
     label?: string
28
     label?: string
29
 };
29
 };
30
 
30
 
31
+/**
32
+ * The type of the React {@code Component} state of {@link ReactionButton}.
33
+ */
34
+type State = {
35
+
36
+    /**
37
+     * Used to determine zoom level on reaction burst.
38
+     */
39
+    increaseLevel: number,
40
+
41
+    /**
42
+     * Timeout ID to reset reaction burst.
43
+     */
44
+    increaseTimeout: TimeoutID | null
45
+}
46
+
31
 /**
47
 /**
32
  * Represents a button in the reactions menu.
48
  * Represents a button in the reactions menu.
33
  *
49
  *
34
  * @extends AbstractToolbarButton
50
  * @extends AbstractToolbarButton
35
  */
51
  */
36
-class ReactionButton extends AbstractToolbarButton<Props> {
52
+class ReactionButton extends AbstractToolbarButton<Props, State> {
37
     /**
53
     /**
38
      * Default values for {@code ReactionButton} component's properties.
54
      * Default values for {@code ReactionButton} component's properties.
39
      *
55
      *
52
         super(props);
68
         super(props);
53
 
69
 
54
         this._onKeyDown = this._onKeyDown.bind(this);
70
         this._onKeyDown = this._onKeyDown.bind(this);
71
+        this._onClickHandler = this._onClickHandler.bind(this);
72
+
73
+        this.state = {
74
+            increaseLevel: 0,
75
+            increaseTimeout: null
76
+        };
55
     }
77
     }
56
 
78
 
57
     _onKeyDown: (Object) => void;
79
     _onKeyDown: (Object) => void;
58
 
80
 
81
+    _onClickHandler: () => void;
82
+
59
     /**
83
     /**
60
      * Handles 'Enter' key on the button to trigger onClick for accessibility.
84
      * Handles 'Enter' key on the button to trigger onClick for accessibility.
61
      * We should be handling Space onKeyUp but it conflicts with PTT.
85
      * We should be handling Space onKeyUp but it conflicts with PTT.
78
         }
102
         }
79
     }
103
     }
80
 
104
 
105
+    /**
106
+     * Handles reaction button click.
107
+     *
108
+     * @returns {void}
109
+     */
110
+    _onClickHandler() {
111
+        this.props.onClick();
112
+        clearTimeout(this.state.increaseTimeout);
113
+        const timeout = setTimeout(() => {
114
+            this.setState({
115
+                increaseLevel: 0
116
+            });
117
+        }, 500);
118
+
119
+        this.setState(state => {
120
+            return {
121
+                increaseLevel: state.increaseLevel + 1,
122
+                increaseTimeout: timeout
123
+            };
124
+        });
125
+    }
126
+
81
     /**
127
     /**
82
      * Renders the button of this {@code ReactionButton}.
128
      * Renders the button of this {@code ReactionButton}.
83
      *
129
      *
92
                 aria-label = { this.props.accessibilityLabel }
138
                 aria-label = { this.props.accessibilityLabel }
93
                 aria-pressed = { this.props.toggled }
139
                 aria-pressed = { this.props.toggled }
94
                 className = 'toolbox-button'
140
                 className = 'toolbox-button'
95
-                onClick = { this.props.onClick }
141
+                onClick = { this._onClickHandler }
96
                 onKeyDown = { this._onKeyDown }
142
                 onKeyDown = { this._onKeyDown }
97
                 role = 'button'
143
                 role = 'button'
98
                 tabIndex = { 0 }>
144
                 tabIndex = { 0 }>
113
      * @inheritdoc
159
      * @inheritdoc
114
      */
160
      */
115
     _renderIcon() {
161
     _renderIcon() {
162
+        const { toggled, icon, label } = this.props;
163
+        const { increaseLevel } = this.state;
164
+
116
         return (
165
         return (
117
-            <div className = { `toolbox-icon ${this.props.toggled ? 'toggled' : ''}` }>
118
-                <span className = 'emoji'>{this.props.icon}</span>
119
-                {this.props.label && <span className = 'text'>{this.props.label}</span>}
166
+            <div className = { `toolbox-icon ${toggled ? 'toggled' : ''}` }>
167
+                <span className = { `emoji increase-${increaseLevel > 12 ? 12 : increaseLevel}` }>{icon}</span>
168
+                {label && <span className = 'text'>{label}</span>}
120
             </div>
169
             </div>
121
         );
170
         );
122
     }
171
     }

+ 18
- 5
react/features/reactions/components/web/ReactionsMenu.js View File

11
 import { translate } from '../../../base/i18n';
11
 import { translate } from '../../../base/i18n';
12
 import { getLocalParticipant, getParticipantCount, participantUpdated } from '../../../base/participants';
12
 import { getLocalParticipant, getParticipantCount, participantUpdated } from '../../../base/participants';
13
 import { connect } from '../../../base/redux';
13
 import { connect } from '../../../base/redux';
14
+import { playSound } from '../../../base/sounds';
14
 import { dockToolbox } from '../../../toolbox/actions.web';
15
 import { dockToolbox } from '../../../toolbox/actions.web';
15
 import { addReactionToBuffer } from '../../actions.any';
16
 import { addReactionToBuffer } from '../../actions.any';
16
 import { toggleReactionsMenuVisibility } from '../../actions.web';
17
 import { toggleReactionsMenuVisibility } from '../../actions.web';
17
-import { REACTIONS } from '../../constants';
18
+import { RAISE_HAND_SOUND_ID, REACTIONS } from '../../constants';
18
 
19
 
19
 import ReactionButton from './ReactionButton';
20
 import ReactionButton from './ReactionButton';
20
 
21
 
53
     /**
54
     /**
54
      * Whether or not it's displayed in the overflow menu.
55
      * Whether or not it's displayed in the overflow menu.
55
      */
56
      */
56
-    overflowMenu: boolean
57
+    overflowMenu: boolean,
58
+
59
+    /**
60
+     * Whether or not reaction sounds are enabled.
61
+     */
62
+    _reactionSounds: boolean
57
 };
63
 };
58
 
64
 
59
 declare var APP: Object;
65
 declare var APP: Object;
106
      * @returns {void}
112
      * @returns {void}
107
      */
113
      */
108
     _onToolbarToggleRaiseHand() {
114
     _onToolbarToggleRaiseHand() {
115
+        const { dispatch, _raisedHand, _reactionSounds } = this.props;
116
+
109
         sendAnalytics(createToolbarEvent(
117
         sendAnalytics(createToolbarEvent(
110
             'raise.hand',
118
             'raise.hand',
111
-            { enable: !this.props._raisedHand }));
119
+            { enable: !_raisedHand }));
112
         this._doToggleRaiseHand();
120
         this._doToggleRaiseHand();
113
-        this.props.dispatch(toggleReactionsMenuVisibility());
121
+        dispatch(toggleReactionsMenuVisibility());
122
+        if (_reactionSounds && _raisedHand) {
123
+            dispatch(playSound(RAISE_HAND_SOUND_ID));
124
+        }
114
     }
125
     }
115
 
126
 
116
     /**
127
     /**
212
  */
223
  */
213
 function mapStateToProps(state) {
224
 function mapStateToProps(state) {
214
     const localParticipant = getLocalParticipant(state);
225
     const localParticipant = getLocalParticipant(state);
226
+    const { soundsReactions } = state['features/base/settings'];
215
 
227
 
216
     return {
228
     return {
217
         _localParticipantID: localParticipant.id,
229
         _localParticipantID: localParticipant.id,
218
         _raisedHand: localParticipant.raisedHand,
230
         _raisedHand: localParticipant.raisedHand,
219
-        _participantCount: getParticipantCount(state)
231
+        _participantCount: getParticipantCount(state),
232
+        _reactionSounds: soundsReactions
220
     };
233
     };
221
 }
234
 }
222
 
235
 

+ 102
- 22
react/features/reactions/constants.js View File

1
 // @flow
1
 // @flow
2
 
2
 
3
+import {
4
+    CLAP_SOUND_FILES,
5
+    LAUGH_SOUND_FILES,
6
+    LIKE_SOUND_FILES,
7
+    BOO_SOUND_FILES,
8
+    SURPRISE_SOUND_FILES,
9
+    SILENCE_SOUND_FILES
10
+} from './sounds';
11
+
12
+/**
13
+ * The audio ID prefix of the audio element for which the {@link playAudio} action is
14
+ * triggered when a new laugh reaction is received.
15
+ *
16
+ * @type { string }
17
+ */
18
+export const LAUGH_SOUND_ID = 'LAUGH_SOUND_';
19
+
20
+/**
21
+ * The audio ID prefix of the audio element for which the {@link playAudio} action is
22
+ * triggered when a new clap reaction is received.
23
+ *
24
+ * @type {string}
25
+ */
26
+export const CLAP_SOUND_ID = 'CLAP_SOUND_';
27
+
28
+/**
29
+ * The audio ID prefix of the audio element for which the {@link playAudio} action is
30
+ * triggered when a new like reaction is received.
31
+ *
32
+ * @type {string}
33
+ */
34
+export const LIKE_SOUND_ID = 'LIKE_SOUND_';
35
+
36
+/**
37
+ * The audio ID prefix of the audio element for which the {@link playAudio} action is
38
+ * triggered when a new boo reaction is received.
39
+ *
40
+ * @type {string}
41
+ */
42
+export const BOO_SOUND_ID = 'BOO_SOUND_';
43
+
44
+/**
45
+ * The audio ID prefix of the audio element for which the {@link playAudio} action is
46
+ * triggered when a new surprised reaction is received.
47
+ *
48
+ * @type {string}
49
+ */
50
+export const SURPRISE_SOUND_ID = 'SURPRISE_SOUND_';
51
+
52
+/**
53
+ * The audio ID prefix of the audio element for which the {@link playAudio} action is
54
+ * triggered when a new silence reaction is received.
55
+ *
56
+ * @type {string}
57
+ */
58
+export const SILENCE_SOUND_ID = 'SILENCE_SOUND_';
59
+
60
+/**
61
+ * The audio ID of the audio element for which the {@link playAudio} action is
62
+ * triggered when a new raise hand event is received.
63
+ *
64
+ * @type {string}
65
+ */
66
+export const RAISE_HAND_SOUND_ID = 'RAISE_HAND_SOUND_ID';
67
+
68
+export type ReactionEmojiProps = {
69
+
70
+    /**
71
+     * Reaction to be displayed.
72
+     */
73
+    reaction: string,
74
+
75
+    /**
76
+     * Id of the reaction.
77
+     */
78
+    uid: number
79
+}
80
+
81
+export const SOUNDS_THRESHOLDS = [ 1, 4, 10 ];
82
+
83
+
3
 export const REACTIONS = {
84
 export const REACTIONS = {
4
     like: {
85
     like: {
5
         message: ':thumbs_up:',
86
         message: ':thumbs_up:',
6
         emoji: '👍',
87
         emoji: '👍',
7
-        shortcutChar: 'T'
88
+        shortcutChar: 'T',
89
+        soundId: LIKE_SOUND_ID,
90
+        soundFiles: LIKE_SOUND_FILES
8
     },
91
     },
9
     clap: {
92
     clap: {
10
         message: ':clap:',
93
         message: ':clap:',
11
         emoji: '👏',
94
         emoji: '👏',
12
-        shortcutChar: 'C'
95
+        shortcutChar: 'C',
96
+        soundId: CLAP_SOUND_ID,
97
+        soundFiles: CLAP_SOUND_FILES
13
     },
98
     },
14
     laugh: {
99
     laugh: {
15
         message: ':grinning_face:',
100
         message: ':grinning_face:',
16
         emoji: '😀',
101
         emoji: '😀',
17
-        shortcutChar: 'L'
102
+        shortcutChar: 'L',
103
+        soundId: LAUGH_SOUND_ID,
104
+        soundFiles: LAUGH_SOUND_FILES
18
     },
105
     },
19
     surprised: {
106
     surprised: {
20
         message: ':face_with_open_mouth:',
107
         message: ':face_with_open_mouth:',
21
         emoji: '😮',
108
         emoji: '😮',
22
-        shortcutChar: 'O'
109
+        shortcutChar: 'O',
110
+        soundId: SURPRISE_SOUND_ID,
111
+        soundFiles: SURPRISE_SOUND_FILES
23
     },
112
     },
24
     boo: {
113
     boo: {
25
         message: ':slightly_frowning_face:',
114
         message: ':slightly_frowning_face:',
26
         emoji: '🙁',
115
         emoji: '🙁',
27
-        shortcutChar: 'B'
116
+        shortcutChar: 'B',
117
+        soundId: BOO_SOUND_ID,
118
+        soundFiles: BOO_SOUND_FILES
28
     },
119
     },
29
-    party: {
30
-        message: ':party_popper:',
31
-        emoji: '🎉',
32
-        shortcutChar: 'P'
120
+    silence: {
121
+        message: ':face_without_mouth:',
122
+        emoji: '😶',
123
+        shortcutChar: 'S',
124
+        soundId: SILENCE_SOUND_ID,
125
+        soundFiles: SILENCE_SOUND_FILES
33
     }
126
     }
34
 };
127
 };
35
-
36
-export type ReactionEmojiProps = {
37
-
38
-    /**
39
-     * Reaction to be displayed.
40
-     */
41
-    reaction: string,
42
-
43
-    /**
44
-     * Id of the reaction.
45
-     */
46
-    uid: number
47
-}

+ 55
- 1
react/features/reactions/functions.any.js View File

5
 import { getLocalParticipant } from '../base/participants';
5
 import { getLocalParticipant } from '../base/participants';
6
 import { extractFqnFromPath } from '../dynamic-branding/functions';
6
 import { extractFqnFromPath } from '../dynamic-branding/functions';
7
 
7
 
8
-import { REACTIONS } from './constants';
8
+import { REACTIONS, SOUNDS_THRESHOLDS } from './constants';
9
 import logger from './logger';
9
 import logger from './logger';
10
 
10
 
11
 /**
11
 /**
88
         }
88
         }
89
     }
89
     }
90
 }
90
 }
91
+
92
+/**
93
+ * Returns unique reactions from the reactions buffer.
94
+ *
95
+ * @param {Array} reactions - The reactions buffer.
96
+ * @returns {Array}
97
+ */
98
+function getUniqueReactions(reactions: Array<string>) {
99
+    return [ ...new Set(reactions) ];
100
+}
101
+
102
+/**
103
+ * Returns frequency of given reaction in array.
104
+ *
105
+ * @param {Array} reactions - Array of reactions.
106
+ * @param {string} reaction - Reaction to get frequency for.
107
+ * @returns {number}
108
+ */
109
+function getReactionFrequency(reactions: Array<string>, reaction: string) {
110
+    return reactions.filter(r => r === reaction).length;
111
+}
112
+
113
+/**
114
+ * Returns the threshold number for a given frequency.
115
+ *
116
+ * @param {number} frequency - Frequency of reaction.
117
+ * @returns {number}
118
+ */
119
+function getSoundThresholdByFrequency(frequency) {
120
+    for (const i of SOUNDS_THRESHOLDS) {
121
+        if (frequency <= i) {
122
+            return i;
123
+        }
124
+    }
125
+
126
+    return SOUNDS_THRESHOLDS[SOUNDS_THRESHOLDS.length - 1];
127
+}
128
+
129
+/**
130
+ * Returns unique reactions with threshold.
131
+ *
132
+ * @param {Array} reactions - The reactions buffer.
133
+ * @returns {Array}
134
+ */
135
+export function getReactionsSoundsThresholds(reactions: Array<string>) {
136
+    const unique = getUniqueReactions(reactions);
137
+
138
+    return unique.map<Object>(reaction => {
139
+        return {
140
+            reaction,
141
+            threshold: getSoundThresholdByFrequency(getReactionFrequency(reactions, reaction))
142
+        };
143
+    });
144
+}

+ 70
- 4
react/features/reactions/middleware.js View File

3
 import { batch } from 'react-redux';
3
 import { batch } from 'react-redux';
4
 
4
 
5
 import { ENDPOINT_REACTION_NAME } from '../../../modules/API/constants';
5
 import { ENDPOINT_REACTION_NAME } from '../../../modules/API/constants';
6
+import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
6
 import { MiddlewareRegistry } from '../base/redux';
7
 import { MiddlewareRegistry } from '../base/redux';
8
+import { updateSettings } from '../base/settings';
9
+import { playSound, registerSound, unregisterSound } from '../base/sounds';
7
 import { isVpaasMeeting } from '../jaas/functions';
10
 import { isVpaasMeeting } from '../jaas/functions';
11
+import { NOTIFICATION_TIMEOUT, showNotification } from '../notifications';
8
 
12
 
9
 import {
13
 import {
10
     ADD_REACTION_BUFFER,
14
     ADD_REACTION_BUFFER,
11
     FLUSH_REACTION_BUFFER,
15
     FLUSH_REACTION_BUFFER,
12
     SEND_REACTIONS,
16
     SEND_REACTIONS,
13
-    PUSH_REACTIONS
17
+    PUSH_REACTIONS,
18
+    SHOW_SOUNDS_NOTIFICATION
14
 } from './actionTypes';
19
 } from './actionTypes';
15
 import {
20
 import {
16
     addReactionsToChat,
21
     addReactionsToChat,
19
     sendReactions,
24
     sendReactions,
20
     setReactionQueue
25
     setReactionQueue
21
 } from './actions.any';
26
 } from './actions.any';
22
-import { getReactionMessageFromBuffer, getReactionsWithId, sendReactionsWebhook } from './functions.any';
27
+import { displayReactionSoundsNotification } from './actions.web';
28
+import { RAISE_HAND_SOUND_ID, REACTIONS, SOUNDS_THRESHOLDS } from './constants';
29
+import {
30
+    getReactionMessageFromBuffer,
31
+    getReactionsSoundsThresholds,
32
+    getReactionsWithId,
33
+    sendReactionsWebhook
34
+} from './functions.any';
35
+import { RAISE_HAND_SOUND_FILE } from './sounds';
23
 
36
 
24
 
37
 
25
 declare var APP: Object;
38
 declare var APP: Object;
35
     const { dispatch, getState } = store;
48
     const { dispatch, getState } = store;
36
 
49
 
37
     switch (action.type) {
50
     switch (action.type) {
51
+    case APP_WILL_MOUNT:
52
+        batch(() => {
53
+            Object.keys(REACTIONS).forEach(key => {
54
+                for (let i = 0; i < SOUNDS_THRESHOLDS.length; i++) {
55
+                    dispatch(registerSound(
56
+                        `${REACTIONS[key].soundId}${SOUNDS_THRESHOLDS[i]}`,
57
+                        REACTIONS[key].soundFiles[i]
58
+                    )
59
+                    );
60
+                }
61
+            }
62
+            );
63
+            dispatch(registerSound(RAISE_HAND_SOUND_ID, RAISE_HAND_SOUND_FILE));
64
+        });
65
+        break;
66
+
67
+    case APP_WILL_UNMOUNT:
68
+        batch(() => {
69
+            Object.keys(REACTIONS).forEach(key => {
70
+                for (let i = 0; i < SOUNDS_THRESHOLDS.length; i++) {
71
+                    dispatch(unregisterSound(`${REACTIONS[key].soundId}${SOUNDS_THRESHOLDS[i]}`));
72
+                }
73
+            });
74
+            dispatch(unregisterSound(RAISE_HAND_SOUND_ID));
75
+        });
76
+        break;
77
+
38
     case ADD_REACTION_BUFFER: {
78
     case ADD_REACTION_BUFFER: {
39
         const { timeoutID, buffer } = getState()['features/reactions'];
79
         const { timeoutID, buffer } = getState()['features/reactions'];
40
         const { reaction } = action;
80
         const { reaction } = action;
82
     }
122
     }
83
 
123
 
84
     case PUSH_REACTIONS: {
124
     case PUSH_REACTIONS: {
85
-        const queue = store.getState()['features/reactions'].queue;
125
+        const state = getState();
126
+        const { queue, notificationDisplayed } = state['features/reactions'];
127
+        const { soundsReactions } = state['features/base/settings'];
86
         const reactions = action.reactions;
128
         const reactions = action.reactions;
87
 
129
 
88
-        dispatch(setReactionQueue([ ...queue, ...getReactionsWithId(reactions) ]));
130
+        batch(() => {
131
+            if (!notificationDisplayed && soundsReactions) {
132
+                dispatch(displayReactionSoundsNotification());
133
+            }
134
+            if (soundsReactions) {
135
+                const reactionSoundsThresholds = getReactionsSoundsThresholds(reactions);
136
+
137
+                reactionSoundsThresholds.forEach(reaction =>
138
+                    dispatch(playSound(`${REACTIONS[reaction.reaction].soundId}${reaction.threshold}`))
139
+                );
140
+            }
141
+            dispatch(setReactionQueue([ ...queue, ...getReactionsWithId(reactions) ]));
142
+        });
143
+        break;
144
+    }
145
+
146
+    case SHOW_SOUNDS_NOTIFICATION: {
147
+        dispatch(showNotification({
148
+            titleKey: 'toolbar.disableReactionSounds',
149
+            customActionNameKey: 'notify.reactionSounds',
150
+            customActionHandler: () => dispatch(updateSettings({
151
+                soundsReactions: false
152
+            }))
153
+        }, NOTIFICATION_TIMEOUT));
154
+        break;
89
     }
155
     }
90
     }
156
     }
91
 
157
 

+ 17
- 3
react/features/reactions/reducer.js View File

6
     TOGGLE_REACTIONS_VISIBLE,
6
     TOGGLE_REACTIONS_VISIBLE,
7
     SET_REACTION_QUEUE,
7
     SET_REACTION_QUEUE,
8
     ADD_REACTION_BUFFER,
8
     ADD_REACTION_BUFFER,
9
-    FLUSH_REACTION_BUFFER
9
+    FLUSH_REACTION_BUFFER,
10
+    SHOW_SOUNDS_NOTIFICATION
10
 } from './actionTypes';
11
 } from './actionTypes';
11
 
12
 
12
 /**
13
 /**
17
  *     visible: boolean,
18
  *     visible: boolean,
18
  *     message: string,
19
  *     message: string,
19
  *     timeoutID: number,
20
  *     timeoutID: number,
20
- *     queue: Array
21
+ *     queue: Array,
22
+ *     notificationDisplayed: boolean
21
  * }}
23
  * }}
22
  */
24
  */
23
 function _getInitialState() {
25
 function _getInitialState() {
49
          *
51
          *
50
          * @type {Array}
52
          * @type {Array}
51
          */
53
          */
52
-        queue: []
54
+        queue: [],
55
+
56
+        /**
57
+         * Whether or not the disable reaction sounds notification was shown
58
+         */
59
+        notificationDisplayed: false
53
     };
60
     };
54
 }
61
 }
55
 
62
 
84
                 queue: action.value
91
                 queue: action.value
85
             };
92
             };
86
         }
93
         }
94
+
95
+        case SHOW_SOUNDS_NOTIFICATION: {
96
+            return {
97
+                ...state,
98
+                notificationDisplayed: true
99
+            };
100
+        }
87
         }
101
         }
88
 
102
 
89
         return state;
103
         return state;

+ 48
- 0
react/features/reactions/sounds.js View File

1
+/**
2
+ * The name of the bundled audio files which will be played for the laugh reaction sound.
3
+ *
4
+ * @type {Array<string>}
5
+ */
6
+export const LAUGH_SOUND_FILES = [ 'reactions-laughter.mp3', 'reactions-laughter.mp3', 'reactions-laughter.mp3' ];
7
+
8
+/**
9
+ * The name of the bundled audio file which will be played for the clap reaction sound.
10
+ *
11
+ * @type {Array<string>}
12
+ */
13
+export const CLAP_SOUND_FILES = [ 'reactions–applause.mp3', 'reactions–applause.mp3', 'reactions–applause.mp3' ];
14
+
15
+/**
16
+ * The name of the bundled audio file which will be played for the like reaction sound.
17
+ *
18
+ * @type {Array<string>}
19
+ */
20
+export const LIKE_SOUND_FILES = [ 'reactions–thumbs-up.mp3', 'reactions–thumbs-up.mp3', 'reactions–thumbs-up.mp3' ];
21
+
22
+/**
23
+ * The name of the bundled audio file which will be played for the boo reaction sound.
24
+ *
25
+ * @type {Array<string>}
26
+ */
27
+export const BOO_SOUND_FILES = [ 'reactions–boo.mp3', 'reactions–boo.mp3', 'reactions–boo.mp3' ];
28
+
29
+/**
30
+ * The name of the bundled audio file which will be played for the surprised reaction sound.
31
+ *
32
+ * @type {Array<string>}
33
+ */
34
+export const SURPRISE_SOUND_FILES = [ 'reactions–surprise.mp3', 'reactions–surprise.mp3', 'reactions–surprise.mp3' ];
35
+
36
+/**
37
+ * The name of the bundled audio file which will be played for the silence reaction sound.
38
+ *
39
+ * @type {Array<string>}
40
+ */
41
+export const SILENCE_SOUND_FILES = [ 'reactions–crickets.mp3', 'reactions–crickets.mp3', 'reactions–crickets.mp3' ];
42
+
43
+/**
44
+ * The name of the bundled audio file which will be played for the raise hand sound.
45
+ *
46
+ * @type {string}
47
+ */
48
+export const RAISE_HAND_SOUND_FILE = 'reactions–raised-hand.mp3';

+ 4
- 2
react/features/settings/actions.js View File

142
         const shouldUpdate = (newState.soundsIncomingMessage !== currentState.soundsIncomingMessage)
142
         const shouldUpdate = (newState.soundsIncomingMessage !== currentState.soundsIncomingMessage)
143
             || (newState.soundsParticipantJoined !== currentState.soundsParticipantJoined)
143
             || (newState.soundsParticipantJoined !== currentState.soundsParticipantJoined)
144
             || (newState.soundsParticipantLeft !== currentState.soundsParticipantLeft)
144
             || (newState.soundsParticipantLeft !== currentState.soundsParticipantLeft)
145
-            || (newState.soundsTalkWhileMuted !== currentState.soundsTalkWhileMuted);
145
+            || (newState.soundsTalkWhileMuted !== currentState.soundsTalkWhileMuted)
146
+            || (newState.soundsReactions !== currentState.soundsReactions);
146
 
147
 
147
         if (shouldUpdate) {
148
         if (shouldUpdate) {
148
             dispatch(updateSettings({
149
             dispatch(updateSettings({
149
                 soundsIncomingMessage: newState.soundsIncomingMessage,
150
                 soundsIncomingMessage: newState.soundsIncomingMessage,
150
                 soundsParticipantJoined: newState.soundsParticipantJoined,
151
                 soundsParticipantJoined: newState.soundsParticipantJoined,
151
                 soundsParticipantLeft: newState.soundsParticipantLeft,
152
                 soundsParticipantLeft: newState.soundsParticipantLeft,
152
-                soundsTalkWhileMuted: newState.soundsTalkWhileMuted
153
+                soundsTalkWhileMuted: newState.soundsTalkWhileMuted,
154
+                soundsReactions: newState.soundsReactions
153
             }));
155
             }));
154
         }
156
         }
155
     };
157
     };

+ 18
- 0
react/features/settings/components/web/SoundsTab.js View File

35
      */
35
      */
36
     soundsTalkWhileMuted: Boolean,
36
     soundsTalkWhileMuted: Boolean,
37
 
37
 
38
+    /**
39
+    * Whether or not the sound for reactions should play.
40
+    */
41
+    soundsReactions: Boolean,
42
+
43
+    /**
44
+     * Whether or not the reactions feature is enabled.
45
+     */
46
+    enableReactions: Boolean,
47
+
38
     /**
48
     /**
39
      * Invoked to obtain translated strings.
49
      * Invoked to obtain translated strings.
40
      */
50
      */
85
             soundsParticipantJoined,
95
             soundsParticipantJoined,
86
             soundsParticipantLeft,
96
             soundsParticipantLeft,
87
             soundsTalkWhileMuted,
97
             soundsTalkWhileMuted,
98
+            soundsReactions,
99
+            enableReactions,
88
             t
100
             t
89
         } = this.props;
101
         } = this.props;
90
 
102
 
95
                 <h2 className = 'mock-atlaskit-label'>
107
                 <h2 className = 'mock-atlaskit-label'>
96
                     {t('settings.playSounds')}
108
                     {t('settings.playSounds')}
97
                 </h2>
109
                 </h2>
110
+                {enableReactions && <Checkbox
111
+                    isChecked = { soundsReactions }
112
+                    label = { t('settings.reactions') }
113
+                    name = 'soundsReactions'
114
+                    onChange = { this._onChange } />
115
+                }
98
                 <Checkbox
116
                 <Checkbox
99
                     isChecked = { soundsIncomingMessage }
117
                     isChecked = { soundsIncomingMessage }
100
                     label = { t('settings.incomingMessage') }
118
                     label = { t('settings.incomingMessage') }

+ 6
- 2
react/features/settings/functions.js View File

171
         soundsIncomingMessage,
171
         soundsIncomingMessage,
172
         soundsParticipantJoined,
172
         soundsParticipantJoined,
173
         soundsParticipantLeft,
173
         soundsParticipantLeft,
174
-        soundsTalkWhileMuted
174
+        soundsTalkWhileMuted,
175
+        soundsReactions
175
     } = state['features/base/settings'];
176
     } = state['features/base/settings'];
177
+    const { enableReactions } = state['features/base/config'];
176
 
178
 
177
     return {
179
     return {
178
         soundsIncomingMessage,
180
         soundsIncomingMessage,
179
         soundsParticipantJoined,
181
         soundsParticipantJoined,
180
         soundsParticipantLeft,
182
         soundsParticipantLeft,
181
-        soundsTalkWhileMuted
183
+        soundsTalkWhileMuted,
184
+        soundsReactions,
185
+        enableReactions
182
     };
186
     };
183
 }
187
 }
184
 
188
 

+ 1
- 1
react/features/toolbox/components/AbstractToolbarButton.js View File

50
  *
50
  *
51
  * @abstract
51
  * @abstract
52
  */
52
  */
53
-export default class AbstractToolbarButton<P: Props> extends Component<P> {
53
+export default class AbstractToolbarButton<P: Props, State=void> extends Component<P, State> {
54
     /**
54
     /**
55
      * Initializes a new {@code AbstractToolbarButton} instance.
55
      * Initializes a new {@code AbstractToolbarButton} instance.
56
      *
56
      *

BIN
sounds/reactions-laughter.mp3 View File


BIN
sounds/reactions–applause.mp3 View File


BIN
sounds/reactions–boo.mp3 View File


BIN
sounds/reactions–crickets.mp3 View File


BIN
sounds/reactions–raised-hand.mp3 View File


BIN
sounds/reactions–surprise.mp3 View File


BIN
sounds/reactions–thumbs-up.mp3 View File


Loading…
Cancel
Save