663 lines
18 KiB
JavaScript
663 lines
18 KiB
JavaScript
import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../../notifications';
|
|
import { set } from '../redux';
|
|
|
|
import {
|
|
DOMINANT_SPEAKER_CHANGED,
|
|
HIDDEN_PARTICIPANT_JOINED,
|
|
HIDDEN_PARTICIPANT_LEFT,
|
|
GRANT_MODERATOR,
|
|
KICK_PARTICIPANT,
|
|
LOCAL_PARTICIPANT_AUDIO_LEVEL_CHANGED,
|
|
LOCAL_PARTICIPANT_RAISE_HAND,
|
|
MUTE_REMOTE_PARTICIPANT,
|
|
PARTICIPANT_ID_CHANGED,
|
|
PARTICIPANT_JOINED,
|
|
PARTICIPANT_KICKED,
|
|
PARTICIPANT_LEFT,
|
|
PARTICIPANT_UPDATED,
|
|
PIN_PARTICIPANT,
|
|
SCREENSHARE_PARTICIPANT_NAME_CHANGED,
|
|
SET_LOADABLE_AVATAR_URL,
|
|
RAISE_HAND_UPDATED
|
|
} from './actionTypes';
|
|
import {
|
|
DISCO_REMOTE_CONTROL_FEATURE
|
|
} from './constants';
|
|
import {
|
|
getLocalParticipant,
|
|
getVirtualScreenshareParticipantOwnerId,
|
|
getNormalizedDisplayName,
|
|
getParticipantDisplayName,
|
|
getParticipantById
|
|
} from './functions';
|
|
import logger from './logger';
|
|
|
|
/**
|
|
* Create an action for when dominant speaker changes.
|
|
*
|
|
* @param {string} dominantSpeaker - Participant ID of the dominant speaker.
|
|
* @param {Array<string>} previousSpeakers - Participant IDs of the previous speakers.
|
|
* @param {JitsiConference} conference - The {@code JitsiConference} associated
|
|
* with the participant identified by the specified {@code id}. Only the local
|
|
* participant is allowed to not specify an associated {@code JitsiConference}
|
|
* instance.
|
|
* @returns {{
|
|
* type: DOMINANT_SPEAKER_CHANGED,
|
|
* participant: {
|
|
* conference: JitsiConference,
|
|
* id: string,
|
|
* previousSpeakers: Array<string>
|
|
* }
|
|
* }}
|
|
*/
|
|
export function dominantSpeakerChanged(dominantSpeaker, previousSpeakers, conference) {
|
|
return {
|
|
type: DOMINANT_SPEAKER_CHANGED,
|
|
participant: {
|
|
conference,
|
|
id: dominantSpeaker,
|
|
previousSpeakers
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create an action for granting moderator to a participant.
|
|
*
|
|
* @param {string} id - Participant's ID.
|
|
* @returns {{
|
|
* type: GRANT_MODERATOR,
|
|
* id: string
|
|
* }}
|
|
*/
|
|
export function grantModerator(id) {
|
|
return {
|
|
type: GRANT_MODERATOR,
|
|
id
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create an action for removing a participant from the conference.
|
|
*
|
|
* @param {string} id - Participant's ID.
|
|
* @returns {{
|
|
* type: KICK_PARTICIPANT,
|
|
* id: string
|
|
* }}
|
|
*/
|
|
export function kickParticipant(id) {
|
|
return {
|
|
type: KICK_PARTICIPANT,
|
|
id
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates an action to signal the connection status of the local participant
|
|
* has changed.
|
|
*
|
|
* @param {string} connectionStatus - The current connection status of the local
|
|
* participant, as enumerated by the library's participantConnectionStatus
|
|
* constants.
|
|
* @returns {Function}
|
|
*/
|
|
export function localParticipantConnectionStatusChanged(connectionStatus) {
|
|
return (dispatch, getState) => {
|
|
const participant = getLocalParticipant(getState);
|
|
|
|
if (participant) {
|
|
return dispatch(participantConnectionStatusChanged(
|
|
participant.id,
|
|
connectionStatus));
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Action to signal that the ID of local participant has changed. It happens
|
|
* when the local participant joins a new conference or leaves an existing
|
|
* conference.
|
|
*
|
|
* @param {string} id - New ID for local participant.
|
|
* @returns {Function}
|
|
*/
|
|
export function localParticipantIdChanged(id) {
|
|
return (dispatch, getState) => {
|
|
const participant = getLocalParticipant(getState);
|
|
|
|
if (participant) {
|
|
return dispatch({
|
|
type: PARTICIPANT_ID_CHANGED,
|
|
|
|
// XXX A participant is identified by an id-conference pair.
|
|
// Only the local participant is with an undefined conference.
|
|
conference: undefined,
|
|
newValue: id,
|
|
oldValue: participant.id
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Action to signal that a local participant has joined.
|
|
*
|
|
* @param {Participant} participant={} - Information about participant.
|
|
* @returns {{
|
|
* type: PARTICIPANT_JOINED,
|
|
* participant: Participant
|
|
* }}
|
|
*/
|
|
export function localParticipantJoined(participant = {}) {
|
|
return participantJoined(set(participant, 'local', true));
|
|
}
|
|
|
|
/**
|
|
* Action to remove a local participant.
|
|
*
|
|
* @returns {Function}
|
|
*/
|
|
export function localParticipantLeft() {
|
|
return (dispatch, getState) => {
|
|
const participant = getLocalParticipant(getState);
|
|
|
|
if (participant) {
|
|
return (
|
|
dispatch(
|
|
participantLeft(
|
|
participant.id,
|
|
|
|
// XXX Only the local participant is allowed to leave
|
|
// without stating the JitsiConference instance because
|
|
// the local participant is uniquely identified by the
|
|
// very fact that there is only one local participant
|
|
// (and the fact that the local participant "joins" at
|
|
// the beginning of the app and "leaves" at the end of
|
|
// the app).
|
|
undefined)));
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Action to signal the role of the local participant has changed. It can happen
|
|
* when the participant has joined a conference, even before a non-default local
|
|
* id has been set, or after a moderator leaves.
|
|
*
|
|
* @param {string} role - The new role of the local participant.
|
|
* @returns {Function}
|
|
*/
|
|
export function localParticipantRoleChanged(role) {
|
|
return (dispatch, getState) => {
|
|
const participant = getLocalParticipant(getState);
|
|
|
|
if (participant) {
|
|
return dispatch(participantRoleChanged(participant.id, role));
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create an action for muting another participant in the conference.
|
|
*
|
|
* @param {string} id - Participant's ID.
|
|
* @param {MEDIA_TYPE} mediaType - The media to mute.
|
|
* @returns {{
|
|
* type: MUTE_REMOTE_PARTICIPANT,
|
|
* id: string,
|
|
* mediaType: MEDIA_TYPE
|
|
* }}
|
|
*/
|
|
export function muteRemoteParticipant(id, mediaType) {
|
|
return {
|
|
type: MUTE_REMOTE_PARTICIPANT,
|
|
id,
|
|
mediaType
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Action to update a participant's connection status.
|
|
*
|
|
* @param {string} id - Participant's ID.
|
|
* @param {string} connectionStatus - The new connection status of the
|
|
* participant.
|
|
* @returns {{
|
|
* type: PARTICIPANT_UPDATED,
|
|
* participant: {
|
|
* connectionStatus: string,
|
|
* id: string
|
|
* }
|
|
* }}
|
|
*/
|
|
export function participantConnectionStatusChanged(id, connectionStatus) {
|
|
return {
|
|
type: PARTICIPANT_UPDATED,
|
|
participant: {
|
|
connectionStatus,
|
|
id
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Action to signal that a participant has joined.
|
|
*
|
|
* @param {Participant} participant - Information about participant.
|
|
* @returns {{
|
|
* type: PARTICIPANT_JOINED,
|
|
* participant: Participant
|
|
* }}
|
|
*/
|
|
export function participantJoined(participant) {
|
|
// Only the local participant is not identified with an id-conference pair.
|
|
if (participant.local) {
|
|
return {
|
|
type: PARTICIPANT_JOINED,
|
|
participant
|
|
};
|
|
}
|
|
|
|
// In other words, a remote participant is identified with an id-conference
|
|
// pair.
|
|
const { conference } = participant;
|
|
|
|
if (!conference) {
|
|
throw Error(
|
|
'A remote participant must be associated with a JitsiConference!');
|
|
}
|
|
|
|
return (dispatch, getState) => {
|
|
// A remote participant is only expected to join in a joined or joining
|
|
// conference. The following check is really necessary because a
|
|
// JitsiConference may have moved into leaving but may still manage to
|
|
// sneak a PARTICIPANT_JOINED in if its leave is delayed for any purpose
|
|
// (which is not outragous given that leaving involves network
|
|
// requests.)
|
|
const stateFeaturesBaseConference
|
|
= getState()['features/base/conference'];
|
|
|
|
if (conference === stateFeaturesBaseConference.conference
|
|
|| conference === stateFeaturesBaseConference.joining) {
|
|
return dispatch({
|
|
type: PARTICIPANT_JOINED,
|
|
participant
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Updates the features of a remote participant.
|
|
*
|
|
* @param {JitsiParticipant} jitsiParticipant - The ID of the participant.
|
|
* @returns {{
|
|
* type: PARTICIPANT_UPDATED,
|
|
* participant: Participant
|
|
* }}
|
|
*/
|
|
export function updateRemoteParticipantFeatures(jitsiParticipant) {
|
|
return (dispatch, getState) => {
|
|
if (!jitsiParticipant) {
|
|
return;
|
|
}
|
|
|
|
const id = jitsiParticipant.getId();
|
|
|
|
jitsiParticipant.getFeatures()
|
|
.then(features => {
|
|
const supportsRemoteControl = features.has(DISCO_REMOTE_CONTROL_FEATURE);
|
|
const participant = getParticipantById(getState(), id);
|
|
|
|
if (!participant || participant.local) {
|
|
return;
|
|
}
|
|
|
|
if (participant?.supportsRemoteControl !== supportsRemoteControl) {
|
|
return dispatch({
|
|
type: PARTICIPANT_UPDATED,
|
|
participant: {
|
|
id,
|
|
supportsRemoteControl
|
|
}
|
|
});
|
|
}
|
|
})
|
|
.catch(error => {
|
|
logger.error(`Failed to get participant features for ${id}!`, error);
|
|
});
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Action to signal that a hidden participant has joined the conference.
|
|
*
|
|
* @param {string} id - The id of the participant.
|
|
* @param {string} displayName - The display name, or undefined when
|
|
* unknown.
|
|
* @returns {{
|
|
* type: HIDDEN_PARTICIPANT_JOINED,
|
|
* displayName: string,
|
|
* id: string
|
|
* }}
|
|
*/
|
|
export function hiddenParticipantJoined(id, displayName) {
|
|
return {
|
|
type: HIDDEN_PARTICIPANT_JOINED,
|
|
id,
|
|
displayName
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Action to signal that a hidden participant has left the conference.
|
|
*
|
|
* @param {string} id - The id of the participant.
|
|
* @returns {{
|
|
* type: HIDDEN_PARTICIPANT_LEFT,
|
|
* id: string
|
|
* }}
|
|
*/
|
|
export function hiddenParticipantLeft(id) {
|
|
return {
|
|
type: HIDDEN_PARTICIPANT_LEFT,
|
|
id
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Action to signal that a participant has left.
|
|
*
|
|
* @param {string} id - Participant's ID.
|
|
* @param {JitsiConference} conference - The {@code JitsiConference} associated
|
|
* with the participant identified by the specified {@code id}. Only the local
|
|
* participant is allowed to not specify an associated {@code JitsiConference}
|
|
* instance.
|
|
* @param {boolean} isReplaced - Whether the participant is to be replaced in the meeting.
|
|
* @returns {{
|
|
* type: PARTICIPANT_LEFT,
|
|
* participant: {
|
|
* conference: JitsiConference,
|
|
* id: string
|
|
* }
|
|
* }}
|
|
*/
|
|
export function participantLeft(id, conference, isReplaced) {
|
|
return {
|
|
type: PARTICIPANT_LEFT,
|
|
participant: {
|
|
conference,
|
|
id,
|
|
isReplaced
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Action to signal that a participant's presence status has changed.
|
|
*
|
|
* @param {string} id - Participant's ID.
|
|
* @param {string} presence - Participant's new presence status.
|
|
* @returns {{
|
|
* type: PARTICIPANT_UPDATED,
|
|
* participant: {
|
|
* id: string,
|
|
* presence: string
|
|
* }
|
|
* }}
|
|
*/
|
|
export function participantPresenceChanged(id, presence) {
|
|
return participantUpdated({
|
|
id,
|
|
presence
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Action to signal that a participant's role has changed.
|
|
*
|
|
* @param {string} id - Participant's ID.
|
|
* @param {PARTICIPANT_ROLE} role - Participant's new role.
|
|
* @returns {{
|
|
* type: PARTICIPANT_UPDATED,
|
|
* participant: {
|
|
* id: string,
|
|
* role: PARTICIPANT_ROLE
|
|
* }
|
|
* }}
|
|
*/
|
|
export function participantRoleChanged(id, role) {
|
|
return participantUpdated({
|
|
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.
|
|
*
|
|
* @param {Participant} participant={} - Information about participant. To
|
|
* identify the participant the object should contain either property id with
|
|
* value the id of the participant or property local with value true (if the
|
|
* local participant hasn't joined the conference yet).
|
|
* @returns {{
|
|
* type: PARTICIPANT_UPDATED,
|
|
* participant: Participant
|
|
* }}
|
|
*/
|
|
export function participantUpdated(participant = {}) {
|
|
const participantToUpdate = {
|
|
...participant
|
|
};
|
|
|
|
if (participant.name) {
|
|
participantToUpdate.name = getNormalizedDisplayName(participant.name);
|
|
}
|
|
|
|
return {
|
|
type: PARTICIPANT_UPDATED,
|
|
participant: participantToUpdate
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Action to signal that a participant has muted us.
|
|
*
|
|
* @param {JitsiParticipant} participant - Information about participant.
|
|
* @param {JitsiLocalTrack} track - Information about the track that has been muted.
|
|
* @returns {Promise}
|
|
*/
|
|
export function participantMutedUs(participant, track) {
|
|
return (dispatch, getState) => {
|
|
if (!participant) {
|
|
return;
|
|
}
|
|
|
|
const isAudio = track.isAudioTrack();
|
|
|
|
dispatch(showNotification({
|
|
titleKey: isAudio ? 'notify.mutedRemotelyTitle' : 'notify.videoMutedRemotelyTitle',
|
|
titleArguments: {
|
|
participantDisplayName: getParticipantDisplayName(getState, participant.getId())
|
|
}
|
|
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Action to create a virtual screenshare participant.
|
|
*
|
|
* @param {(string)} sourceName - JitsiTrack instance.
|
|
* @param {(boolean)} local - JitsiTrack instance.
|
|
* @returns {Function}
|
|
*/
|
|
export function createVirtualScreenshareParticipant(sourceName, local) {
|
|
return (dispatch, getState) => {
|
|
const state = getState();
|
|
const ownerId = getVirtualScreenshareParticipantOwnerId(sourceName);
|
|
const owner = getParticipantById(state, ownerId);
|
|
const ownerName = owner.name;
|
|
|
|
if (!ownerName) {
|
|
logger.error(`Failed to create a screenshare participant for sourceName: ${sourceName}`);
|
|
|
|
return;
|
|
}
|
|
|
|
dispatch(participantJoined({
|
|
conference: state['features/base/conference'].conference,
|
|
id: sourceName,
|
|
isVirtualScreenshareParticipant: true,
|
|
isLocalScreenShare: local,
|
|
name: ownerName
|
|
}));
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Action to signal that a participant had been kicked.
|
|
*
|
|
* @param {JitsiParticipant} kicker - Information about participant performing the kick.
|
|
* @param {JitsiParticipant} kicked - Information about participant that was kicked.
|
|
* @returns {Promise}
|
|
*/
|
|
export function participantKicked(kicker, kicked) {
|
|
return (dispatch, getState) => {
|
|
|
|
dispatch({
|
|
type: PARTICIPANT_KICKED,
|
|
kicked: kicked.getId(),
|
|
kicker: kicker?.getId()
|
|
});
|
|
|
|
if (kicked.isReplaced && kicked.isReplaced()) {
|
|
return;
|
|
}
|
|
|
|
dispatch(showNotification({
|
|
titleArguments: {
|
|
kicked:
|
|
getParticipantDisplayName(getState, kicked.getId()),
|
|
kicker:
|
|
getParticipantDisplayName(getState, kicker.getId())
|
|
},
|
|
titleKey: 'notify.kickParticipant'
|
|
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create an action which pins a conference participant.
|
|
*
|
|
* @param {string|null} id - The ID of the conference participant to pin or null
|
|
* if none of the conference's participants are to be pinned.
|
|
* @returns {{
|
|
* type: PIN_PARTICIPANT,
|
|
* participant: {
|
|
* id: string
|
|
* }
|
|
* }}
|
|
*/
|
|
export function pinParticipant(id) {
|
|
return {
|
|
type: PIN_PARTICIPANT,
|
|
participant: {
|
|
id
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates an action which notifies the app that the loadable URL of the avatar of a participant got updated.
|
|
*
|
|
* @param {string} participantId - The ID of the participant.
|
|
* @param {string} url - The new URL.
|
|
* @param {boolean} useCORS - Indicates whether we need to use CORS for this URL.
|
|
* @returns {{
|
|
* type: SET_LOADABLE_AVATAR_URL,
|
|
* participant: {
|
|
* id: string,
|
|
* loadableAvatarUrl: string,
|
|
* loadableAvatarUrlUseCORS: boolean
|
|
* }
|
|
* }}
|
|
*/
|
|
export function setLoadableAvatarUrl(participantId, url, useCORS) {
|
|
return {
|
|
type: SET_LOADABLE_AVATAR_URL,
|
|
participant: {
|
|
id: participantId,
|
|
loadableAvatarUrl: url,
|
|
loadableAvatarUrlUseCORS: useCORS
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Raise hand for the local participant.
|
|
*
|
|
* @param {boolean} enabled - Raise or lower hand.
|
|
* @returns {{
|
|
* type: LOCAL_PARTICIPANT_RAISE_HAND,
|
|
* raisedHandTimestamp: number
|
|
* }}
|
|
*/
|
|
export function raiseHand(enabled) {
|
|
return {
|
|
type: LOCAL_PARTICIPANT_RAISE_HAND,
|
|
raisedHandTimestamp: enabled ? Date.now() : 0
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Update raise hand queue of participants.
|
|
*
|
|
* @param {Object} participant - Participant that updated raised hand.
|
|
* @returns {{
|
|
* type: RAISE_HAND_UPDATED,
|
|
* participant: Object
|
|
* }}
|
|
*/
|
|
export function raiseHandUpdateQueue(participant) {
|
|
return {
|
|
type: RAISE_HAND_UPDATED,
|
|
participant
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Notifies if the local participant audio level has changed.
|
|
*
|
|
* @param {number} level - The audio level.
|
|
* @returns {{
|
|
* type: LOCAL_PARTICIPANT_AUDIO_LEVEL_CHANGED,
|
|
* level: number
|
|
* }}
|
|
*/
|
|
export function localParticipantAudioLevelChanged(level) {
|
|
return {
|
|
type: LOCAL_PARTICIPANT_AUDIO_LEVEL_CHANGED,
|
|
level
|
|
};
|
|
}
|