diff --git a/conference.js b/conference.js index b1c4626a86..1345100f9c 100644 --- a/conference.js +++ b/conference.js @@ -54,7 +54,11 @@ import { sendLocalParticipant, nonParticipantMessageReceived } from './react/features/base/conference'; -import { getReplaceParticipant, getMultipleVideoSupportFeatureFlag } from './react/features/base/config/functions'; +import { + getReplaceParticipant, + getMultipleVideoSupportFeatureFlag, + getSourceNameSignalingFeatureFlag +} from './react/features/base/config/functions'; import { checkAndNotifyForNewDevice, getAvailableDevices, @@ -93,6 +97,7 @@ import { dominantSpeakerChanged, getLocalParticipant, getNormalizedDisplayName, + getScreenshareParticipantByOwnerId, localParticipantAudioLevelChanged, localParticipantConnectionStatusChanged, localParticipantRoleChanged, @@ -102,6 +107,7 @@ import { participantPresenceChanged, participantRoleChanged, participantUpdated, + screenshareParticipantDisplayNameChanged, updateRemoteParticipantFeatures } from './react/features/base/participants'; import { @@ -2258,6 +2264,17 @@ export default { id, name: formattedDisplayName })); + + if (getSourceNameSignalingFeatureFlag(state)) { + const screenshareParticipantId = getScreenshareParticipantByOwnerId(state, id)?.id; + + if (screenshareParticipantId) { + APP.store.dispatch( + screenshareParticipantDisplayNameChanged(screenshareParticipantId, formattedDisplayName) + ); + } + } + APP.API.notifyDisplayNameChanged(id, { displayName: formattedDisplayName, formattedDisplayName: diff --git a/react/features/base/participants/actionTypes.ts b/react/features/base/participants/actionTypes.ts index 6319bfad78..b31534dfd0 100644 --- a/react/features/base/participants/actionTypes.ts +++ b/react/features/base/participants/actionTypes.ts @@ -173,6 +173,17 @@ export const HIDDEN_PARTICIPANT_LEFT = 'HIDDEN_PARTICIPANT_LEFT'; */ export const SET_LOADABLE_AVATAR_URL = 'SET_LOADABLE_AVATAR_URL'; +/** + * The type of Redux action which notifies that the screenshare participant's display name has changed. + * + * { + * type: SCREENSHARE_PARTICIPANT_NAME_CHANGED, + * id: string, + * name: string + * } + */ + export const SCREENSHARE_PARTICIPANT_NAME_CHANGED = 'SCREENSHARE_PARTICIPANT_NAME_CHANGED'; + /** * Raises hand for the local participant. * { diff --git a/react/features/base/participants/actions.js b/react/features/base/participants/actions.js index a6386fa8d4..142849521d 100644 --- a/react/features/base/participants/actions.js +++ b/react/features/base/participants/actions.js @@ -16,6 +16,7 @@ import { PARTICIPANT_LEFT, PARTICIPANT_UPDATED, PIN_PARTICIPANT, + SCREENSHARE_PARTICIPANT_NAME_CHANGED, SET_LOADABLE_AVATAR_URL, RAISE_HAND_UPDATED } from './actionTypes'; @@ -432,6 +433,25 @@ export function participantRoleChanged(id, role) { }); } +/** + * Action to signal that a participant's display name has changed. + * + * @param {string} id - Screenshare participant's ID. + * @param {name} name - The new display name of the screenshare participant's owner. + * @returns {{ + * type: SCREENSHARE_PARTICIPANT_NAME_CHANGED, + * id: string, + * name: string + * }} + */ +export function screenshareParticipantDisplayNameChanged(id, name) { + return { + type: SCREENSHARE_PARTICIPANT_NAME_CHANGED, + id, + name + }; +} + /** * Action to signal that some of participant properties has been changed. * diff --git a/react/features/base/participants/functions.js b/react/features/base/participants/functions.js index c71cc4f3ee..bd4d93eb96 100644 --- a/react/features/base/participants/functions.js +++ b/react/features/base/participants/functions.js @@ -105,6 +105,22 @@ export function getLocalScreenShareParticipant(stateful: Object | Function) { return state.localScreenShare; } +/** + * Returns screenshare participant. + * + * @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to + * retrieve the state features/base/participants. + * @param {string} id - The owner ID of the screenshare participant to retrieve. + * @returns {(Participant|undefined)} + */ +export function getScreenshareParticipantByOwnerId(stateful: Object | Function, id: string) { + const track = getTrackByMediaTypeAndParticipant( + toState(stateful)['features/base/tracks'], MEDIA_TYPE.SCREENSHARE, id + ); + + return getParticipantById(stateful, track?.jitsiTrack.getSourceName()); +} + /** * Normalizes a display name so then no invalid values (padding, length...etc) * can be set. @@ -244,14 +260,12 @@ export function getParticipantCountWithFake(stateful: Object | Function) { /** * Returns participant's display name. * - * @param {(Function|Object)} stateful - The (whole) redux state, or redux's - * {@code getState} function to be used to retrieve the state. + * @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to + * retrieve the state. * @param {string} id - The ID of the participant's display name to retrieve. * @returns {string} */ -export function getParticipantDisplayName( - stateful: Object | Function, - id: string) { +export function getParticipantDisplayName(stateful: Object | Function, id: string) { const participant = getParticipantById(stateful, id); const { defaultLocalDisplayName, @@ -259,6 +273,10 @@ export function getParticipantDisplayName( } = toState(stateful)['features/base/config']; if (participant) { + if (participant.isFakeScreenShareParticipant) { + return getScreenshareParticipantDisplayName(stateful, id); + } + if (participant.name) { return participant.name; } @@ -271,6 +289,20 @@ export function getParticipantDisplayName( return defaultRemoteDisplayName; } +/** + * Returns screenshare participant's display name. + * + * @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to + * retrieve the state. + * @param {string} id - The ID of the screenshare participant's display name to retrieve. + * @returns {string} + */ +export function getScreenshareParticipantDisplayName(stateful: Object | Function, id: string) { + const owner = getParticipantById(stateful, getFakeScreenShareParticipantOwnerId(id)); + + return `${owner.name}'s screen`; +} + /** * Returns the presence status of a participant associated with the passed id. * diff --git a/react/features/base/participants/reducer.js b/react/features/base/participants/reducer.js index 683a65df1f..87ed55e4dc 100644 --- a/react/features/base/participants/reducer.js +++ b/react/features/base/participants/reducer.js @@ -13,6 +13,7 @@ import { PARTICIPANT_UPDATED, PIN_PARTICIPANT, RAISE_HAND_UPDATED, + SCREENSHARE_PARTICIPANT_NAME_CHANGED, SET_LOADABLE_AVATAR_URL } from './actionTypes'; import { LOCAL_PARTICIPANT_DEFAULT_ID, PARTICIPANT_ROLE } from './constants'; @@ -209,6 +210,23 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a ...state }; } + case SCREENSHARE_PARTICIPANT_NAME_CHANGED: { + const { id, name } = action; + + if (state.sortedRemoteFakeScreenShareParticipants.has(id)) { + state.sortedRemoteFakeScreenShareParticipants.delete(id); + + const sortedRemoteFakeScreenShareParticipants = [ ...state.sortedRemoteFakeScreenShareParticipants ]; + + sortedRemoteFakeScreenShareParticipants.push([ id, name ]); + sortedRemoteFakeScreenShareParticipants.sort((a, b) => a[1].localeCompare(b[1])); + + state.sortedRemoteFakeScreenShareParticipants = new Map(sortedRemoteFakeScreenShareParticipants); + } + + return { ...state }; + } + case PARTICIPANT_JOINED: { const participant = _participantJoined(action); const { id, isFakeParticipant, isFakeScreenShareParticipant, isLocalScreenShare, name, pinned } = participant; diff --git a/react/features/base/tracks/middleware.js b/react/features/base/tracks/middleware.js index 1e2eb9c126..266cf8d355 100644 --- a/react/features/base/tracks/middleware.js +++ b/react/features/base/tracks/middleware.js @@ -400,7 +400,7 @@ function createFakeScreenShareParticipant({ dispatch, getState }, { track }) { id: track.jitsiTrack.getSourceName(), isFakeScreenShareParticipant: true, isLocalScreenShare: track?.jitsiTrack.isLocal(), - name: `${participant.name}'s screen` + name: participant.name })); } else { logger.error(`Failed to create a screenshare participant for participantId: ${participantId}`); diff --git a/react/features/display-name/components/web/StageParticipantNameLabel.js b/react/features/display-name/components/web/StageParticipantNameLabel.js index 3638ea5ce6..3f38875085 100644 --- a/react/features/display-name/components/web/StageParticipantNameLabel.js +++ b/react/features/display-name/components/web/StageParticipantNameLabel.js @@ -6,7 +6,7 @@ import React from 'react'; import { useSelector } from 'react-redux'; import { isDisplayNameVisible } from '../../../base/config/functions.any'; -import { getLocalParticipant } from '../../../base/participants'; +import { getLocalParticipant, getParticipantDisplayName } from '../../../base/participants'; import { withPixelLineHeight } from '../../../base/styles/functions.web'; import { getLargeVideoParticipant } from '../../../large-video/functions'; import { isToolboxVisible } from '../../../toolbox/functions.web'; @@ -44,8 +44,8 @@ const useStyles = makeStyles(theme => { const StageParticipantNameLabel = () => { const classes = useStyles(); const largeVideoParticipant = useSelector(getLargeVideoParticipant); - const nameToDisplay = largeVideoParticipant?.name; const selectedId = largeVideoParticipant?.id; + const nameToDisplay = useSelector(state => getParticipantDisplayName(state, selectedId)); const localParticipant = useSelector(getLocalParticipant); const localId = localParticipant?.id; diff --git a/react/features/filmstrip/subscriber.any.js b/react/features/filmstrip/subscriber.any.js index ef31f6ebf4..09f814728c 100644 --- a/react/features/filmstrip/subscriber.any.js +++ b/react/features/filmstrip/subscriber.any.js @@ -12,6 +12,13 @@ StateListenerRegistry.register( /* selector */ state => state['features/video-layout'].remoteScreenShares, /* listener */ (remoteScreenShares, store) => updateRemoteParticipants(store)); +/** + * Listens for changes to the remote screenshare participants to recompute the reordered list of the remote endpoints. + */ +StateListenerRegistry.register( + /* selector */ state => state['features/base/participants'].sortedRemoteFakeScreenShareParticipants, + /* listener */ (sortedRemoteFakeScreenShareParticipants, store) => updateRemoteParticipants(store)); + /** * Listens for changes to the dominant speaker to recompute the reordered list of the remote endpoints. */