From 6c5f54f3c49094892ebcc8548d1d77794bf88106 Mon Sep 17 00:00:00 2001 From: Horatiu Muresan Date: Fri, 11 Aug 2023 12:08:03 +0300 Subject: [PATCH 1/3] feat(external-api) add commands/event for setting camera facing mode - added command for setting the camera facing mode remotely - enhanced toggleVideo command to optionally accept the facing mode --- lang/main.json | 2 + modules/API/API.js | 24 +++++--- modules/API/external/external_api.js | 3 +- react/features/base/tracks/actions.web.ts | 58 ++++++++++++++++++- .../web/AllowToggleCameraDialog.tsx | 44 ++++++++++++++ react/features/base/tracks/constants.ts | 4 ++ react/features/base/tracks/middleware.web.ts | 35 +++++++++++ 7 files changed, 159 insertions(+), 11 deletions(-) create mode 100644 react/features/base/tracks/components/web/AllowToggleCameraDialog.tsx create mode 100644 react/features/base/tracks/constants.ts diff --git a/lang/main.json b/lang/main.json index 6c900fca7b63e..d74e04c06d8d4 100644 --- a/lang/main.json +++ b/lang/main.json @@ -269,6 +269,8 @@ "addMeetingNote": "Add a note about this meeting", "addOptionalNote": "Add a note (optional):", "allow": "Allow", + "allowToggleCameraDialog": "Do you allow {{initiatorName}} to toggle your camera facing mode?", + "allowToggleCameraTitle": "Allow toggle camera?", "alreadySharedVideoMsg": "Another participant is already sharing a video. This conference allows only one shared video at a time.", "alreadySharedVideoTitle": "Only one shared video is allowed at a time", "applicationWindow": "Application window", diff --git a/modules/API/API.js b/modules/API/API.js index 01575dcfda852..2cc6a3d1f4d6a 100644 --- a/modules/API/API.js +++ b/modules/API/API.js @@ -50,8 +50,8 @@ import { } from '../../react/features/base/participants/functions'; import { updateSettings } from '../../react/features/base/settings/actions'; import { getDisplayName } from '../../react/features/base/settings/functions.web'; -import { toggleCamera } from '../../react/features/base/tracks/actions.any'; -import { isToggleCameraEnabled } from '../../react/features/base/tracks/functions'; +import { setCameraFacingMode } from '../../react/features/base/tracks/actions.web'; +import { CAMERA_FACING_MODE_MESSAGE } from '../../react/features/base/tracks/constants'; import { autoAssignToBreakoutRooms, closeBreakoutRoom, @@ -395,12 +395,8 @@ function initCommands() { sendAnalytics(createApiEvent('film.strip.resize')); APP.store.dispatch(resizeFilmStrip(options.width)); }, - 'toggle-camera': () => { - if (!isToggleCameraEnabled(APP.store.getState())) { - return; - } - - APP.store.dispatch(toggleCamera()); + 'toggle-camera': facingMode => { + APP.store.dispatch(setCameraFacingMode(facingMode)); }, 'toggle-camera-mirror': () => { const state = APP.store.getState(); @@ -529,6 +525,18 @@ function initCommands() { logger.error('Failed sending endpoint text message', err); } }, + 'send-camera-facing-mode-message': (to, facingMode) => { + if (!to) { + logger.warn('Participant id not set'); + + return; + } + + APP.conference.sendEndpointMessage(to, { + name: CAMERA_FACING_MODE_MESSAGE, + facingMode + }); + }, 'overwrite-names': participantList => { logger.debug('Overwrite names command received'); diff --git a/modules/API/external/external_api.js b/modules/API/external/external_api.js index a06bce3f11ad9..d8ec00a965857 100644 --- a/modules/API/external/external_api.js +++ b/modules/API/external/external_api.js @@ -54,6 +54,7 @@ const commands = { removeBreakoutRoom: 'remove-breakout-room', resizeFilmStrip: 'resize-film-strip', resizeLargeVideo: 'resize-large-video', + sendCameraFacingMode: 'send-camera-facing-mode-message', sendChatMessage: 'send-chat-message', sendEndpointTextMessage: 'send-endpoint-text-message', sendParticipantToRoom: 'send-participant-to-room', @@ -112,6 +113,7 @@ const events = { 'data-channel-opened': 'dataChannelOpened', 'device-list-changed': 'deviceListChanged', 'display-name-change': 'displayNameChange', + 'dominant-speaker-changed': 'dominantSpeakerChanged', 'email-change': 'emailChange', 'error-occurred': 'errorOccurred', 'endpoint-text-message-received': 'endpointTextMessageReceived', @@ -153,7 +155,6 @@ const events = { 'video-mute-status-changed': 'videoMuteStatusChanged', 'video-quality-changed': 'videoQualityChanged', 'screen-sharing-status-changed': 'screenSharingStatusChanged', - 'dominant-speaker-changed': 'dominantSpeakerChanged', 'subject-change': 'subjectChange', 'suspend-detected': 'suspendDetected', 'tile-view-changed': 'tileViewChanged', diff --git a/react/features/base/tracks/actions.web.ts b/react/features/base/tracks/actions.web.ts index ffe6915d1be5d..1dc378c40f28c 100644 --- a/react/features/base/tracks/actions.web.ts +++ b/react/features/base/tracks/actions.web.ts @@ -13,18 +13,23 @@ import { toggleScreenshotCaptureSummary } from '../../screenshot-capture/actions import { isScreenshotCaptureEnabled } from '../../screenshot-capture/functions'; import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect'; import { getCurrentConference } from '../conference/functions'; +import { openDialog } from '../dialog/actions'; import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet'; import { setScreenshareMuted } from '../media/actions'; import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants'; import { addLocalTrack, - replaceLocalTrack + replaceLocalTrack, + toggleCamera } from './actions.any'; +import AllowToggleCameraDialog from './components/web/AllowToggleCameraDialog'; import { createLocalTracksF, getLocalDesktopTrack, - getLocalJitsiAudioTrack + getLocalJitsiAudioTrack, + getLocalVideoTrack, + isToggleCameraEnabled } from './functions'; import { IShareOptions, IToggleScreenSharingOptions } from './types'; @@ -263,3 +268,52 @@ async function _toggleScreenSharing( APP.API.notifyScreenSharingStatusChanged(enable, screensharingDetails); } } + +/** + * Sets the camera facing mode(environment/user). If facing mode not provided, it will do a toggle. + * + * @param {string | undefined} facingMode - The selected facing mode. + * @returns {void} + */ +export function setCameraFacingMode(facingMode: string | undefined) { + return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => { + const state = getState(); + + if (!isToggleCameraEnabled(state)) { + return; + } + + if (!facingMode) { + dispatch(toggleCamera()); + + return; + } + + const tracks = state['features/base/tracks']; + const localVideoTrack = getLocalVideoTrack(tracks)?.jitsiTrack; + + if (!tracks || !localVideoTrack) { + return; + } + + const currentFacingMode = localVideoTrack.getCameraFacingMode(); + + if (currentFacingMode !== facingMode) { + dispatch(toggleCamera()); + } + }; +} + +/** + * Signals to open the permission dialog for toggling camera remotely. + * + * @param {Function} onAllow - Callback to be executed if permission to toggle camera was granted. + * @param {string} initiatorId - The participant id of the requester. + * @returns {Object} - The open dialog action. + */ +export function openAllowToggleCameraDialog(onAllow: Function, initiatorId: string) { + return openDialog(AllowToggleCameraDialog, { + onAllow, + initiatorId + }); +} diff --git a/react/features/base/tracks/components/web/AllowToggleCameraDialog.tsx b/react/features/base/tracks/components/web/AllowToggleCameraDialog.tsx new file mode 100644 index 0000000000000..cb80e7b757e4f --- /dev/null +++ b/react/features/base/tracks/components/web/AllowToggleCameraDialog.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { WithTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; + +import { IReduxState } from '../../../../app/types'; +import { translate } from '../../../i18n/functions'; +import { getParticipantDisplayName } from '../../../participants/functions'; +import Dialog from '../../../ui/components/web/Dialog'; + + +interface IProps extends WithTranslation { + + /** + * The participant id of the toggle camera requester. + */ + initiatorId: string; + + /** + * Function to be invoked after permission to toggle camera granted. + */ + onAllow: () => void; +} + +/** + * Dialog to allow toggling camera remotely. + * + * @returns {JSX.Element} - The allow toggle camera dialog. + */ +const AllowToggleCameraDialog = ({ onAllow, t, initiatorId }: IProps): JSX.Element => { + const initiatorName = useSelector((state: IReduxState) => getParticipantDisplayName(state, initiatorId)); + + return ( + +
+ { t('dialog.allowToggleCameraDialog', { initiatorName }) } +
+
+ ); +}; + +export default translate(AllowToggleCameraDialog); diff --git a/react/features/base/tracks/constants.ts b/react/features/base/tracks/constants.ts new file mode 100644 index 0000000000000..abbe4abc390a8 --- /dev/null +++ b/react/features/base/tracks/constants.ts @@ -0,0 +1,4 @@ +/** + * The payload name for remotely setting the camera facing mode message. + */ +export const CAMERA_FACING_MODE_MESSAGE = 'camera-facing-mode-message'; diff --git a/react/features/base/tracks/middleware.web.ts b/react/features/base/tracks/middleware.web.ts index cd6b2884eec03..5f32c95cb5305 100644 --- a/react/features/base/tracks/middleware.web.ts +++ b/react/features/base/tracks/middleware.web.ts @@ -3,7 +3,10 @@ import { AnyAction } from 'redux'; import { IStore } from '../../app/types'; import { hideNotification } from '../../notifications/actions'; import { isPrejoinPageVisible } from '../../prejoin/functions'; +import { CONFERENCE_JOINED } from '../conference/actionTypes'; +import { IJitsiConference } from '../conference/reducer'; import { getAvailableDevices } from '../devices/actions.web'; +import { JitsiConferenceEvents } from '../lib-jitsi-meet'; import { setScreenshareMuted } from '../media/actions'; import { MEDIA_TYPE, @@ -20,10 +23,13 @@ import { TRACK_UPDATED } from './actionTypes'; import { + openAllowToggleCameraDialog, + setCameraFacingMode, showNoDataFromSourceVideoError, toggleScreensharing, trackNoDataFromSourceNotificationInfoChanged } from './actions.web'; +import { CAMERA_FACING_MODE_MESSAGE } from './constants'; import { getTrackByJitsiTrack } from './functions.web'; @@ -121,11 +127,40 @@ MiddlewareRegistry.register(store => next => action => { return result; } + case CONFERENCE_JOINED: { + _addSetCameraFacingModeListener(action.conference); + break; + } } return next(action); }); +/** + * Registers listener for {@link JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED} that + * will perform various chat related activities. + * + * @param {IJitsiConference} conference - The conference. + * @returns {void} + */ +function _addSetCameraFacingModeListener(conference: IJitsiConference) { + conference.on( + JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, + (...args: any) => { + if (args && args.length >= 2) { + const [ sender, eventData ] = args; + + if (eventData.name === CAMERA_FACING_MODE_MESSAGE) { + APP.store.dispatch(openAllowToggleCameraDialog( + /* onAllow */ () => APP.store.dispatch(setCameraFacingMode(eventData.facingMode)), + /* initiatorId */ sender._id + )); + } + } + } + ); +} + /** * Handles no data from source errors. * From 8198ef1b32844352eb8b63bb8759dc6557419364 Mon Sep 17 00:00:00 2001 From: Horatiu Muresan Date: Fri, 11 Aug 2023 12:32:26 +0300 Subject: [PATCH 2/3] fix(startSilent) Don`t create audio track when start silent --- conference.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conference.js b/conference.js index a1cd055239af8..5c9239f085268 100644 --- a/conference.js +++ b/conference.js @@ -441,7 +441,7 @@ export default { // Always get a handle on the audio input device so that we have statistics (such as "No audio input" or // "Are you trying to speak?" ) even if the user joins the conference muted. - const initialDevices = config.disableInitialGUM ? [] : [ MEDIA_TYPE.AUDIO ]; + const initialDevices = config.startSilent || config.disableInitialGUM ? [] : [ MEDIA_TYPE.AUDIO ]; const requestedAudio = !config.disableInitialGUM; let requestedVideo = false; From 35ea879faa233402cd7845ea9deabc6867dab146 Mon Sep 17 00:00:00 2001 From: Horatiu Muresan Date: Wed, 16 Aug 2023 14:32:19 +0300 Subject: [PATCH 3/3] address review --- react/features/base/tracks/middleware.web.ts | 35 --------------- .../{middleware.ts => middleware.any.ts} | 0 .../features/conference/middleware.native.ts | 1 + react/features/conference/middleware.web.ts | 45 +++++++++++++++++++ 4 files changed, 46 insertions(+), 35 deletions(-) rename react/features/conference/{middleware.ts => middleware.any.ts} (100%) create mode 100644 react/features/conference/middleware.native.ts create mode 100644 react/features/conference/middleware.web.ts diff --git a/react/features/base/tracks/middleware.web.ts b/react/features/base/tracks/middleware.web.ts index 5f32c95cb5305..cd6b2884eec03 100644 --- a/react/features/base/tracks/middleware.web.ts +++ b/react/features/base/tracks/middleware.web.ts @@ -3,10 +3,7 @@ import { AnyAction } from 'redux'; import { IStore } from '../../app/types'; import { hideNotification } from '../../notifications/actions'; import { isPrejoinPageVisible } from '../../prejoin/functions'; -import { CONFERENCE_JOINED } from '../conference/actionTypes'; -import { IJitsiConference } from '../conference/reducer'; import { getAvailableDevices } from '../devices/actions.web'; -import { JitsiConferenceEvents } from '../lib-jitsi-meet'; import { setScreenshareMuted } from '../media/actions'; import { MEDIA_TYPE, @@ -23,13 +20,10 @@ import { TRACK_UPDATED } from './actionTypes'; import { - openAllowToggleCameraDialog, - setCameraFacingMode, showNoDataFromSourceVideoError, toggleScreensharing, trackNoDataFromSourceNotificationInfoChanged } from './actions.web'; -import { CAMERA_FACING_MODE_MESSAGE } from './constants'; import { getTrackByJitsiTrack } from './functions.web'; @@ -127,40 +121,11 @@ MiddlewareRegistry.register(store => next => action => { return result; } - case CONFERENCE_JOINED: { - _addSetCameraFacingModeListener(action.conference); - break; - } } return next(action); }); -/** - * Registers listener for {@link JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED} that - * will perform various chat related activities. - * - * @param {IJitsiConference} conference - The conference. - * @returns {void} - */ -function _addSetCameraFacingModeListener(conference: IJitsiConference) { - conference.on( - JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, - (...args: any) => { - if (args && args.length >= 2) { - const [ sender, eventData ] = args; - - if (eventData.name === CAMERA_FACING_MODE_MESSAGE) { - APP.store.dispatch(openAllowToggleCameraDialog( - /* onAllow */ () => APP.store.dispatch(setCameraFacingMode(eventData.facingMode)), - /* initiatorId */ sender._id - )); - } - } - } - ); -} - /** * Handles no data from source errors. * diff --git a/react/features/conference/middleware.ts b/react/features/conference/middleware.any.ts similarity index 100% rename from react/features/conference/middleware.ts rename to react/features/conference/middleware.any.ts diff --git a/react/features/conference/middleware.native.ts b/react/features/conference/middleware.native.ts new file mode 100644 index 0000000000000..fefd329e8ae2f --- /dev/null +++ b/react/features/conference/middleware.native.ts @@ -0,0 +1 @@ +import './middleware.any'; diff --git a/react/features/conference/middleware.web.ts b/react/features/conference/middleware.web.ts new file mode 100644 index 0000000000000..d5f630be6eefe --- /dev/null +++ b/react/features/conference/middleware.web.ts @@ -0,0 +1,45 @@ + +import { CONFERENCE_JOINED } from '../base/conference/actionTypes'; +import { IJitsiConference } from '../base/conference/reducer'; +import { JitsiConferenceEvents } from '../base/lib-jitsi-meet'; +import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; +import { openAllowToggleCameraDialog, setCameraFacingMode } from '../base/tracks/actions.web'; +import { CAMERA_FACING_MODE_MESSAGE } from '../base/tracks/constants'; + +import './middleware.any'; + +MiddlewareRegistry.register(_store => next => action => { + switch (action.type) { + case CONFERENCE_JOINED: { + _addSetCameraFacingModeListener(action.conference); + break; + } + } + + return next(action); +}); + +/** + * Registers listener for {@link JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED} that + * will perform various chat related activities. + * + * @param {IJitsiConference} conference - The conference. + * @returns {void} + */ +function _addSetCameraFacingModeListener(conference: IJitsiConference) { + conference.on( + JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, + (...args: any) => { + if (args && args.length >= 2) { + const [ sender, eventData ] = args; + + if (eventData.name === CAMERA_FACING_MODE_MESSAGE) { + APP.store.dispatch(openAllowToggleCameraDialog( + /* onAllow */ () => APP.store.dispatch(setCameraFacingMode(eventData.facingMode)), + /* initiatorId */ sender._id + )); + } + } + } + ); +}