Ver código fonte

feat(raise-hand) notify next speaker (#14904)

factor2
Mengyuan Liu 10 meses atrás
pai
commit
c04000ea20
Nenhuma conta vinculada ao e-mail do autor do commit

+ 1
- 0
lang/main.json Ver arquivo

@@ -786,6 +786,7 @@
786 786
         "newDeviceAction": "Use",
787 787
         "newDeviceAudioTitle": "New audio device detected",
788 788
         "newDeviceCameraTitle": "New camera detected",
789
+        "nextToSpeak": "You are the next in line to speak",
789 790
         "noiseSuppressionDesktopAudioDescription": "Noise suppression can't be enabled while sharing desktop audio, please disable it and try again.",
790 791
         "noiseSuppressionFailedTitle": "Failed to start noise suppression",
791 792
         "noiseSuppressionStereoDescription": "Stereo audio noise suppression is not currently supported.",

+ 26
- 0
react/features/base/config/functions.any.ts Ver arquivo

@@ -6,6 +6,7 @@ import { safeJsonParse } from '@jitsi/js-utils/json';
6 6
 import _ from 'lodash';
7 7
 
8 8
 import { IReduxState } from '../../app/types';
9
+import { getLocalParticipant } from '../participants/functions';
9 10
 import { parseURLParams } from '../util/parseURLParams';
10 11
 
11 12
 import { IConfig } from './configType';
@@ -184,6 +185,31 @@ export function isNameReadOnly(state: IReduxState): boolean {
184 185
         || state['features/base/config'].readOnlyName);
185 186
 }
186 187
 
188
+/**
189
+ * Selector for determining if the participant is the next one in the queue to speak.
190
+ *
191
+ * @param {Object} state - The state of the app.
192
+ * @returns {boolean}
193
+ */
194
+export function isNextToSpeak(state: IReduxState): boolean {
195
+    const raisedHandsQueue = state['features/base/participants'].raisedHandsQueue || [];
196
+    const participantId = getLocalParticipant(state)?.id;
197
+
198
+    return participantId === raisedHandsQueue[0]?.id;
199
+}
200
+
201
+/**
202
+ * Selector for determining if the next to speak participant in the queue has been notified.
203
+ *
204
+ * @param {Object} state - The state of the app.
205
+ * @returns {boolean}
206
+ */
207
+export function hasBeenNotified(state: IReduxState): boolean {
208
+    const raisedHandsQueue = state['features/base/participants'].raisedHandsQueue;
209
+
210
+    return Boolean(raisedHandsQueue[0]?.hasBeenNotified);
211
+}
212
+
187 213
 /**
188 214
  * Selector for determining if the display name is visible.
189 215
  *

+ 9
- 0
react/features/base/participants/actionTypes.ts Ver arquivo

@@ -1,3 +1,12 @@
1
+/**
2
+ * Create an action to mark the participant as notified to speak next.
3
+ *
4
+ * {
5
+ *     type: NOTIFIED_TO_SPEAK
6
+ * }
7
+ */
8
+export const NOTIFIED_TO_SPEAK = 'NOTIFIED_TO_SPEAK';
9
+
1 10
 /**
2 11
  * Create an action for when dominant speaker changes.
3 12
  *

+ 19
- 1
react/features/base/participants/reducer.ts Ver arquivo

@@ -6,6 +6,7 @@ import { set } from '../redux/functions';
6 6
 
7 7
 import {
8 8
     DOMINANT_SPEAKER_CHANGED,
9
+    NOTIFIED_TO_SPEAK,
9 10
     OVERWRITE_PARTICIPANT_NAME,
10 11
     PARTICIPANT_ID_CHANGED,
11 12
     PARTICIPANT_JOINED,
@@ -92,7 +93,7 @@ export interface IParticipantsState {
92 93
     numberOfParticipantsNotSupportingE2EE: number;
93 94
     overwrittenNameList: { [id: string]: string; };
94 95
     pinnedParticipant?: string;
95
-    raisedHandsQueue: Array<{ id: string; raisedHandTimestamp: number; }>;
96
+    raisedHandsQueue: Array<{ hasBeenNotified?: boolean; id: string; raisedHandTimestamp: number; }>;
96 97
     remote: Map<string, IParticipant>;
97 98
     remoteVideoSources: Set<string>;
98 99
     sortedRemoteParticipants: Map<string, string>;
@@ -114,6 +115,23 @@ export interface IParticipantsState {
114 115
 ReducerRegistry.register<IParticipantsState>('features/base/participants',
115 116
 (state = DEFAULT_STATE, action): IParticipantsState => {
116 117
     switch (action.type) {
118
+    case NOTIFIED_TO_SPEAK: {
119
+        return {
120
+            ...state,
121
+            raisedHandsQueue: state.raisedHandsQueue.map((item, index) => {
122
+                if (index === 0) {
123
+
124
+                    return {
125
+                        ...item,
126
+                        hasBeenNotified: true
127
+                    };
128
+                }
129
+
130
+                return item;
131
+            })
132
+        };
133
+    }
134
+
117 135
     case PARTICIPANT_ID_CHANGED: {
118 136
         const { local } = state;
119 137
 

+ 35
- 1
react/features/base/participants/subscriber.ts Ver arquivo

@@ -1,11 +1,16 @@
1
+
1 2
 import _ from 'lodash';
3
+import { batch } from 'react-redux';
2 4
 
3 5
 import { IStore } from '../../app/types';
6
+import { showNotification } from '../../notifications/actions';
7
+import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
4 8
 import { getCurrentConference } from '../conference/functions';
5
-import { getSsrcRewritingFeatureFlag } from '../config/functions.any';
9
+import { getSsrcRewritingFeatureFlag, hasBeenNotified, isNextToSpeak } from '../config/functions.any';
6 10
 import { VIDEO_TYPE } from '../media/constants';
7 11
 import StateListenerRegistry from '../redux/StateListenerRegistry';
8 12
 
13
+import { NOTIFIED_TO_SPEAK } from './actionTypes';
9 14
 import { createVirtualScreenshareParticipant, participantLeft } from './actions';
10 15
 import {
11 16
     getParticipantById,
@@ -25,6 +30,15 @@ StateListenerRegistry.register(
25 30
         && _updateScreenshareParticipantsBasedOnPresence(store)
26 31
 );
27 32
 
33
+StateListenerRegistry.register(
34
+    /* selector */ state => state['features/base/participants'].raisedHandsQueue,
35
+    /* listener */ (raisedHandsQueue, store) => {
36
+        if (isNextToSpeak(store.getState()) && !hasBeenNotified(store.getState())) {
37
+            _notifyNextSpeakerInRaisedHandQueue(store);
38
+        }
39
+    }
40
+);
41
+
28 42
 /**
29 43
  * Compares the old and new screenshare lists provided and creates/removes the virtual screenshare participant
30 44
  * tiles accodingly.
@@ -121,3 +135,23 @@ function _updateScreenshareParticipantsBasedOnPresence(store: IStore): void {
121 135
 
122 136
     _createOrRemoveVirtualParticipants(previousScreenshareSourceNames, currentScreenshareSourceNames, store);
123 137
 }
138
+
139
+/**
140
+ * Handles notifying the next speaker in the raised hand queue.
141
+ *
142
+ * @param {*} store - The redux store.
143
+ * @returns {void}
144
+ */
145
+function _notifyNextSpeakerInRaisedHandQueue(store: IStore): void {
146
+    const { dispatch } = store;
147
+
148
+    batch(() => {
149
+        dispatch(showNotification({
150
+            titleKey: 'notify.nextToSpeak',
151
+            maxLines: 2
152
+        }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
153
+        dispatch({
154
+            type: NOTIFIED_TO_SPEAK
155
+        });
156
+    });
157
+}

+ 0
- 1
react/features/video-menu/actions.any.ts Ver arquivo

@@ -108,4 +108,3 @@ export function muteAllParticipants(exclude: Array<string>, mediaType: MediaType
108 108
         });
109 109
     };
110 110
 }
111
-

Carregando…
Cancelar
Salvar