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

pull/14955/head jitsi-meet_9647
Mengyuan Liu 10 months ago committed by GitHub
parent 23be14697c
commit c04000ea20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      lang/main.json
  2. 26
      react/features/base/config/functions.any.ts
  3. 9
      react/features/base/participants/actionTypes.ts
  4. 20
      react/features/base/participants/reducer.ts
  5. 36
      react/features/base/participants/subscriber.ts
  6. 1
      react/features/video-menu/actions.any.ts

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

@ -6,6 +6,7 @@ import { safeJsonParse } from '@jitsi/js-utils/json';
import _ from 'lodash';
import { IReduxState } from '../../app/types';
import { getLocalParticipant } from '../participants/functions';
import { parseURLParams } from '../util/parseURLParams';
import { IConfig } from './configType';
@ -184,6 +185,31 @@ export function isNameReadOnly(state: IReduxState): boolean {
|| state['features/base/config'].readOnlyName);
}
/**
* Selector for determining if the participant is the next one in the queue to speak.
*
* @param {Object} state - The state of the app.
* @returns {boolean}
*/
export function isNextToSpeak(state: IReduxState): boolean {
const raisedHandsQueue = state['features/base/participants'].raisedHandsQueue || [];
const participantId = getLocalParticipant(state)?.id;
return participantId === raisedHandsQueue[0]?.id;
}
/**
* Selector for determining if the next to speak participant in the queue has been notified.
*
* @param {Object} state - The state of the app.
* @returns {boolean}
*/
export function hasBeenNotified(state: IReduxState): boolean {
const raisedHandsQueue = state['features/base/participants'].raisedHandsQueue;
return Boolean(raisedHandsQueue[0]?.hasBeenNotified);
}
/**
* Selector for determining if the display name is visible.
*

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

@ -6,6 +6,7 @@ import { set } from '../redux/functions';
import {
DOMINANT_SPEAKER_CHANGED,
NOTIFIED_TO_SPEAK,
OVERWRITE_PARTICIPANT_NAME,
PARTICIPANT_ID_CHANGED,
PARTICIPANT_JOINED,
@ -92,7 +93,7 @@ export interface IParticipantsState {
numberOfParticipantsNotSupportingE2EE: number;
overwrittenNameList: { [id: string]: string; };
pinnedParticipant?: string;
raisedHandsQueue: Array<{ id: string; raisedHandTimestamp: number; }>;
raisedHandsQueue: Array<{ hasBeenNotified?: boolean; id: string; raisedHandTimestamp: number; }>;
remote: Map<string, IParticipant>;
remoteVideoSources: Set<string>;
sortedRemoteParticipants: Map<string, string>;
@ -114,6 +115,23 @@ export interface IParticipantsState {
ReducerRegistry.register<IParticipantsState>('features/base/participants',
(state = DEFAULT_STATE, action): IParticipantsState => {
switch (action.type) {
case NOTIFIED_TO_SPEAK: {
return {
...state,
raisedHandsQueue: state.raisedHandsQueue.map((item, index) => {
if (index === 0) {
return {
...item,
hasBeenNotified: true
};
}
return item;
})
};
}
case PARTICIPANT_ID_CHANGED: {
const { local } = state;

@ -1,11 +1,16 @@
import _ from 'lodash';
import { batch } from 'react-redux';
import { IStore } from '../../app/types';
import { showNotification } from '../../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
import { getCurrentConference } from '../conference/functions';
import { getSsrcRewritingFeatureFlag } from '../config/functions.any';
import { getSsrcRewritingFeatureFlag, hasBeenNotified, isNextToSpeak } from '../config/functions.any';
import { VIDEO_TYPE } from '../media/constants';
import StateListenerRegistry from '../redux/StateListenerRegistry';
import { NOTIFIED_TO_SPEAK } from './actionTypes';
import { createVirtualScreenshareParticipant, participantLeft } from './actions';
import {
getParticipantById,
@ -25,6 +30,15 @@ StateListenerRegistry.register(
&& _updateScreenshareParticipantsBasedOnPresence(store)
);
StateListenerRegistry.register(
/* selector */ state => state['features/base/participants'].raisedHandsQueue,
/* listener */ (raisedHandsQueue, store) => {
if (isNextToSpeak(store.getState()) && !hasBeenNotified(store.getState())) {
_notifyNextSpeakerInRaisedHandQueue(store);
}
}
);
/**
* Compares the old and new screenshare lists provided and creates/removes the virtual screenshare participant
* tiles accodingly.
@ -121,3 +135,23 @@ function _updateScreenshareParticipantsBasedOnPresence(store: IStore): void {
_createOrRemoveVirtualParticipants(previousScreenshareSourceNames, currentScreenshareSourceNames, store);
}
/**
* Handles notifying the next speaker in the raised hand queue.
*
* @param {*} store - The redux store.
* @returns {void}
*/
function _notifyNextSpeakerInRaisedHandQueue(store: IStore): void {
const { dispatch } = store;
batch(() => {
dispatch(showNotification({
titleKey: 'notify.nextToSpeak',
maxLines: 2
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
dispatch({
type: NOTIFIED_TO_SPEAK
});
});
}

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

Loading…
Cancel
Save