in modules/API/API.js [115:674]
function initCommands() {
commands = {
'answer-knocking-participant': (id, approved) => {
APP.store.dispatch(setKnockingParticipantApproval(id, approved));
},
'approve-video': participantId => {
if (!isLocalParticipantModerator(APP.store.getState())) {
return;
}
APP.store.dispatch(approveParticipantVideo(participantId));
},
'ask-to-unmute': participantId => {
if (!isLocalParticipantModerator(APP.store.getState())) {
return;
}
APP.store.dispatch(approveParticipantAudio(participantId));
},
'display-name': displayName => {
sendAnalytics(createApiEvent('display.name.changed'));
APP.conference.changeLocalDisplayName(displayName);
},
'mute-everyone': mediaType => {
const muteMediaType = mediaType ? mediaType : MEDIA_TYPE.AUDIO;
sendAnalytics(createApiEvent('muted-everyone'));
const localParticipant = getLocalParticipant(APP.store.getState());
const exclude = [];
if (localParticipant && isParticipantModerator(localParticipant)) {
exclude.push(localParticipant.id);
}
APP.store.dispatch(muteAllParticipants(exclude, muteMediaType));
},
'toggle-lobby': isLobbyEnabled => {
APP.store.dispatch(toggleLobbyMode(isLobbyEnabled));
},
'password': password => {
const { conference, passwordRequired }
= APP.store.getState()['features/base/conference'];
if (passwordRequired) {
sendAnalytics(createApiEvent('submit.password'));
APP.store.dispatch(setPassword(
passwordRequired,
passwordRequired.join,
password
));
} else {
sendAnalytics(createApiEvent('password.changed'));
APP.store.dispatch(setPassword(
conference,
conference.lock,
password
));
}
},
'pin-participant': id => {
logger.debug('Pin participant command received');
sendAnalytics(createApiEvent('participant.pinned'));
APP.store.dispatch(pinParticipant(id));
},
'proxy-connection-event': event => {
APP.conference.onProxyConnectionEvent(event);
},
'reject-participant': (participantId, mediaType) => {
if (!isLocalParticipantModerator(APP.store.getState())) {
return;
}
const reject = mediaType === MEDIA_TYPE.VIDEO ? rejectParticipantVideo : rejectParticipantAudio;
APP.store.dispatch(reject(participantId));
},
'resize-large-video': (width, height) => {
logger.debug('Resize large video command received');
sendAnalytics(createApiEvent('largevideo.resized'));
APP.store.dispatch(resizeLargeVideo(width, height));
},
'send-tones': (options = {}) => {
const { duration, tones, pause } = options;
APP.store.dispatch(sendTones(tones, duration, pause));
},
'set-follow-me': value => {
logger.debug('Set follow me command received');
if (value) {
sendAnalytics(createApiEvent('follow.me.set'));
} else {
sendAnalytics(createApiEvent('follow.me.unset'));
}
APP.store.dispatch(setFollowMe(value));
},
'set-large-video-participant': participantId => {
logger.debug('Set large video participant command received');
sendAnalytics(createApiEvent('largevideo.participant.set'));
APP.store.dispatch(selectParticipantInLargeVideo(participantId));
},
'set-participant-volume': (participantId, volume) => {
APP.store.dispatch(setVolume(participantId, volume));
},
'subject': subject => {
sendAnalytics(createApiEvent('subject.changed'));
APP.store.dispatch(setSubject(subject));
},
'submit-feedback': feedback => {
sendAnalytics(createApiEvent('submit.feedback'));
APP.conference.submitFeedback(feedback.score, feedback.message);
},
'toggle-audio': () => {
sendAnalytics(createApiEvent('toggle-audio'));
logger.log('Audio toggle: API command received');
APP.conference.toggleAudioMuted(false /* no UI */);
},
'toggle-video': () => {
sendAnalytics(createApiEvent('toggle-video'));
logger.log('Video toggle: API command received');
APP.conference.toggleVideoMuted(false /* no UI */);
},
'toggle-film-strip': () => {
sendAnalytics(createApiEvent('film.strip.toggled'));
APP.UI.toggleFilmstrip();
},
'toggle-camera': () => {
if (!isToggleCameraEnabled(APP.store.getState())) {
return;
}
APP.store.dispatch(toggleCamera());
},
'toggle-camera-mirror': () => {
const state = APP.store.getState();
const { localFlipX: currentFlipX } = state['features/base/settings'];
APP.store.dispatch(updateSettings({ localFlipX: !currentFlipX }));
},
'toggle-chat': () => {
sendAnalytics(createApiEvent('chat.toggled'));
APP.store.dispatch(toggleChat());
},
'toggle-moderation': (enabled, mediaType) => {
const state = APP.store.getState();
if (!isLocalParticipantModerator(state)) {
return;
}
const enable = mediaType === MEDIA_TYPE.VIDEO
? requestEnableVideoModeration : requestEnableAudioModeration;
const disable = mediaType === MEDIA_TYPE.VIDEO
? requestDisableVideoModeration : requestDisableAudioModeration;
if (enabled) {
APP.store.dispatch(enable());
} else {
APP.store.dispatch(disable());
}
},
'toggle-raise-hand': () => {
const localParticipant = getLocalParticipant(APP.store.getState());
if (!localParticipant) {
return;
}
const raisedHand = hasRaisedHand(localParticipant);
sendAnalytics(createApiEvent('raise-hand.toggled'));
APP.store.dispatch(raiseHand(!raisedHand));
},
'toggle-share-audio': () => {
sendAnalytics(createApiEvent('audio.screen.sharing.toggled'));
if (isScreenAudioSupported()) {
APP.store.dispatch(startAudioScreenShareFlow());
return;
}
logger.error('Audio screen sharing is not supported by the current platform!');
},
/**
* Callback to invoke when the "toggle-share-screen" command is received.
*
* @param {Object} options - Additional details of how to perform
* the action. Note this parameter is undocumented and experimental.
* @param {boolean} options.enable - Whether trying to enable screen
* sharing or to turn it off.
* @returns {void}
*/
'toggle-share-screen': (options = {}) => {
sendAnalytics(createApiEvent('screen.sharing.toggled'));
toggleScreenSharing(options.enable);
},
'toggle-tile-view': () => {
sendAnalytics(createApiEvent('tile-view.toggled'));
APP.store.dispatch(toggleTileView());
},
'set-tile-view': enabled => {
APP.store.dispatch(setTileView(enabled));
},
'video-hangup': (showFeedbackDialog = true) => {
sendAnalytics(createApiEvent('video.hangup'));
APP.conference.hangup(showFeedbackDialog);
},
'email': email => {
sendAnalytics(createApiEvent('email.changed'));
APP.conference.changeLocalEmail(email);
},
'avatar-url': avatarUrl => {
sendAnalytics(createApiEvent('avatar.url.changed'));
APP.conference.changeLocalAvatarUrl(avatarUrl);
},
'send-chat-message': (message, to, ignorePrivacy = false) => {
logger.debug('Send chat message command received');
if (to) {
const participant = getParticipantById(APP.store.getState(), to);
if (participant) {
APP.store.dispatch(setPrivateMessageRecipient(participant));
} else {
logger.error(`Participant with id ${to} not found!`);
return;
}
} else {
APP.store.dispatch(setPrivateMessageRecipient());
}
APP.store.dispatch(sendMessage(message, ignorePrivacy));
},
'send-endpoint-text-message': (to, text) => {
logger.debug('Send endpoint message command received');
try {
APP.conference.sendEndpointMessage(to, {
name: ENDPOINT_TEXT_MESSAGE_NAME,
text
});
} catch (err) {
logger.error('Failed sending endpoint text message', err);
}
},
'toggle-e2ee': enabled => {
logger.debug('Toggle E2EE key command received');
APP.store.dispatch(toggleE2EE(enabled));
},
'set-media-encryption-key': keyInfo => {
APP.store.dispatch(setMediaEncryptionKey(JSON.parse(keyInfo)));
},
'set-video-quality': frameHeight => {
logger.debug('Set video quality command received');
sendAnalytics(createApiEvent('set.video.quality'));
APP.store.dispatch(setVideoQuality(frameHeight));
},
'start-share-video': url => {
logger.debug('Share video command received');
sendAnalytics(createApiEvent('share.video.start'));
APP.store.dispatch(playSharedVideo(url));
},
'stop-share-video': () => {
logger.debug('Share video command received');
sendAnalytics(createApiEvent('share.video.stop'));
APP.store.dispatch(stopSharedVideo());
},
/**
* Starts a file recording or streaming session depending on the passed on params.
* For RTMP streams, `rtmpStreamKey` must be passed on. `rtmpBroadcastID` is optional.
* For youtube streams, `youtubeStreamKey` must be passed on. `youtubeBroadcastID` is optional.
* For dropbox recording, recording `mode` should be `file` and a dropbox oauth2 token must be provided.
* For file recording, recording `mode` should be `file` and optionally `shouldShare` could be passed on.
* No other params should be passed.
*
* @param { string } arg.mode - Recording mode, either `file` or `stream`.
* @param { string } arg.dropboxToken - Dropbox oauth2 token.
* @param { string } arg.rtmpStreamKey - The RTMP stream key.
* @param { string } arg.rtmpBroadcastID - The RTMP braodcast ID.
* @param { boolean } arg.shouldShare - Whether the recording should be shared with the participants or not.
* Only applies to certain jitsi meet deploys.
* @param { string } arg.youtubeStreamKey - The youtube stream key.
* @param { string } arg.youtubeBroadcastID - The youtube broacast ID.
* @returns {void}
*/
'start-recording': ({
mode,
dropboxToken,
shouldShare,
rtmpStreamKey,
rtmpBroadcastID,
youtubeStreamKey,
youtubeBroadcastID
}) => {
const state = APP.store.getState();
const conference = getCurrentConference(state);
if (!conference) {
logger.error('Conference is not defined');
return;
}
if (dropboxToken && !isDropboxEnabled(state)) {
logger.error('Failed starting recording: dropbox is not enabled on this deployment');
return;
}
if (mode === JitsiRecordingConstants.mode.STREAM && !(youtubeStreamKey || rtmpStreamKey)) {
logger.error('Failed starting recording: missing youtube or RTMP stream key');
return;
}
let recordingConfig;
if (mode === JitsiRecordingConstants.mode.FILE) {
if (dropboxToken) {
recordingConfig = {
mode: JitsiRecordingConstants.mode.FILE,
appData: JSON.stringify({
'file_recording_metadata': {
'upload_credentials': {
'service_name': RECORDING_TYPES.DROPBOX,
'token': dropboxToken
}
}
})
};
} else {
recordingConfig = {
mode: JitsiRecordingConstants.mode.FILE,
appData: JSON.stringify({
'file_recording_metadata': {
'share': shouldShare
}
})
};
}
} else if (mode === JitsiRecordingConstants.mode.STREAM) {
recordingConfig = {
broadcastId: youtubeBroadcastID || rtmpBroadcastID,
mode: JitsiRecordingConstants.mode.STREAM,
streamId: youtubeStreamKey || rtmpStreamKey
};
} else {
logger.error('Invalid recording mode provided');
return;
}
conference.startRecording(recordingConfig);
},
/**
* Stops a recording or streaming in progress.
*
* @param {string} mode - `file` or `stream`.
* @returns {void}
*/
'stop-recording': mode => {
const state = APP.store.getState();
const conference = getCurrentConference(state);
if (!conference) {
logger.error('Conference is not defined');
return;
}
if (![ JitsiRecordingConstants.mode.FILE, JitsiRecordingConstants.mode.STREAM ].includes(mode)) {
logger.error('Invalid recording mode provided!');
return;
}
const activeSession = getActiveSession(state, mode);
if (activeSession && activeSession.id) {
conference.stopRecording(activeSession.id);
} else {
logger.error('No recording or streaming session found');
}
},
'initiate-private-chat': participantId => {
const state = APP.store.getState();
const participant = getParticipantById(state, participantId);
if (participant) {
const { isOpen: isChatOpen } = state['features/chat'];
if (!isChatOpen) {
APP.store.dispatch(toggleChat());
}
APP.store.dispatch(openChat(participant));
} else {
logger.error('No participant found for the given participantId');
}
},
'cancel-private-chat': () => {
APP.store.dispatch(setPrivateMessageRecipient());
},
'kick-participant': participantId => {
APP.store.dispatch(kickParticipant(participantId));
},
'overwrite-config': config => {
const whitelistedConfig = getWhitelistedJSON('config', config);
APP.store.dispatch(overwriteConfig(whitelistedConfig));
},
'toggle-virtual-background': () => {
APP.store.dispatch(toggleDialog(VirtualBackgroundDialog));
}
};
transport.on('event', ({ data, name }) => {
if (name && commands[name]) {
commands[name](...data);
return true;
}
return false;
});
transport.on('request', (request, callback) => {
const { dispatch, getState } = APP.store;
if (processExternalDeviceRequest(dispatch, getState, request, callback)) {
return true;
}
const { name } = request;
switch (name) {
case 'capture-largevideo-screenshot' :
APP.store.dispatch(captureLargeVideoScreenshot())
.then(dataURL => {
let error;
if (!dataURL) {
error = new Error('No large video found!');
}
callback({
error,
dataURL
});
});
break;
case 'deployment-info':
callback(APP.store.getState()['features/base/config'].deploymentInfo);
break;
case 'invite': {
const { invitees } = request;
if (!Array.isArray(invitees) || invitees.length === 0) {
callback({
error: new Error('Unexpected format of invitees')
});
break;
}
// The store should be already available because API.init is called
// on appWillMount action.
APP.store.dispatch(
invite(invitees, true))
.then(failedInvitees => {
let error;
let result;
if (failedInvitees.length) {
error = new Error('One or more invites failed!');
} else {
result = true;
}
callback({
error,
result
});
});
break;
}
case 'is-audio-muted':
callback(APP.conference.isLocalAudioMuted());
break;
case 'is-moderation-on': {
const { mediaType } = request;
const type = mediaType || MEDIA_TYPE.AUDIO;
callback(isEnabledFromState(type, APP.store.getState()));
break;
}
case 'is-participant-force-muted': {
const state = APP.store.getState();
const { participantId, mediaType } = request;
const type = mediaType || MEDIA_TYPE.AUDIO;
const participant = getParticipantById(state, participantId);
callback(isForceMuted(participant, type, state));
break;
}
case 'is-video-muted':
callback(APP.conference.isLocalVideoMuted());
break;
case 'is-audio-available':
callback(audioAvailable);
break;
case 'is-video-available':
callback(videoAvailable);
break;
case 'is-sharing-screen':
callback(Boolean(APP.conference.isSharingScreen));
break;
case 'get-content-sharing-participants': {
const tracks = getState()['features/base/tracks'];
const sharingParticipantIds = tracks.filter(tr => tr.videoType === 'desktop').map(t => t.participantId);
callback({
sharingParticipantIds
});
break;
}
case 'get-livestream-url': {
const state = APP.store.getState();
const conference = getCurrentConference(state);
let livestreamUrl;
if (conference) {
const activeSession = getActiveSession(state, JitsiRecordingConstants.mode.STREAM);
livestreamUrl = activeSession?.liveStreamViewURL;
} else {
logger.error('Conference is not defined');
}
callback({
livestreamUrl
});
break;
}
case 'get-custom-avatar-backgrounds' : {
callback({
avatarBackgrounds: APP.store.getState()['features/dynamic-branding'].avatarBackgrounds
});
break;
}
default:
return false;
}
return true;
});
}