Compare commits
24 Commits
jitihouse/
...
release-59
Author | SHA1 | Date |
---|---|---|
Horatiu Muresan | 9be0ee3bda | |
Hristo Terezov | e367621e91 | |
Saúl Ibarra Corretgé | ea57ab1c2a | |
Calin Chitu | a274e3a3de | |
Mihaela Dumitru | 41e790771f | |
Saúl Ibarra Corretgé | 331bd2014b | |
Horatiu Muresan | a27948b3af | |
Avram Tudor | 43beb627a2 | |
Mihaela Dumitru | bd994f2103 | |
Robert Pintilii | 6a3755f5d6 | |
Avram Tudor | 97f355ec22 | |
Avram Tudor | 79fe929eb7 | |
Avram Tudor | ef2a490961 | |
Robert Pintilii | 3bbba6f305 | |
Robert Pintilii | 7a573fd580 | |
Jaya Allamsetty | 1484e0fd0b | |
Saúl Ibarra Corretgé | 2909176a73 | |
Avram Tudor | 64d3faa52a | |
Avram Tudor | 842755674a | |
Robert Pintilii | c007a6194e | |
Robert Pintilii | 71a8d7937c | |
Hristo Terezov | 10785cb1c7 | |
Avram Tudor | 5cdee7d989 | |
Robert Pintilii | b2d8a6115a |
|
@ -1637,29 +1637,32 @@ export default {
|
||||||
|
|
||||||
APP.store.dispatch(setScreenAudioShareState(false));
|
APP.store.dispatch(setScreenAudioShareState(false));
|
||||||
|
|
||||||
promise = promise.then(() => createLocalTracksF({ devices: [ 'video' ] }))
|
if (didHaveVideo && !ignoreDidHaveVideo) {
|
||||||
.then(([ stream ]) => {
|
promise = promise.then(() => createLocalTracksF({ devices: [ 'video' ] }))
|
||||||
logger.debug(`_turnScreenSharingOff using ${stream} for useVideoStream`);
|
.then(([ stream ]) => {
|
||||||
|
logger.debug(`_turnScreenSharingOff using ${stream} for useVideoStream`);
|
||||||
|
|
||||||
return this.useVideoStream(stream);
|
return this.useVideoStream(stream);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
logger.error('failed to switch back to local video', error);
|
logger.error('failed to switch back to local video', error);
|
||||||
|
|
||||||
return this.useVideoStream(null).then(() =>
|
return this.useVideoStream(null).then(() =>
|
||||||
|
|
||||||
// Still fail with the original err
|
// Still fail with the original err
|
||||||
Promise.reject(error)
|
Promise.reject(error)
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
promise = promise.then(() => {
|
||||||
|
logger.debug('_turnScreenSharingOff using null for useVideoStream');
|
||||||
|
|
||||||
|
return this.useVideoStream(null);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return promise.then(
|
return promise.then(
|
||||||
() => {
|
() => {
|
||||||
// Mute the video if camera video needs to be ignored or if video was muted before switching to screen
|
|
||||||
// share.
|
|
||||||
if (ignoreDidHaveVideo || !didHaveVideo) {
|
|
||||||
APP.store.dispatch(setVideoMuted(true, MEDIA_TYPE.VIDEO));
|
|
||||||
}
|
|
||||||
this.videoSwitchInProgress = false;
|
this.videoSwitchInProgress = false;
|
||||||
sendAnalytics(createScreenSharingEvent('stopped',
|
sendAnalytics(createScreenSharingEvent('stopped',
|
||||||
duration === 0 ? null : duration));
|
duration === 0 ? null : duration));
|
||||||
|
|
|
@ -841,7 +841,7 @@
|
||||||
"raisedHandsLabel": "Number of raised hands",
|
"raisedHandsLabel": "Number of raised hands",
|
||||||
"record": {
|
"record": {
|
||||||
"already": {
|
"already": {
|
||||||
"linked": "Record is already linked to this session."
|
"linked": "The meeting is already linked to this Salesforce object."
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
|
|
|
@ -39,7 +39,8 @@ import {
|
||||||
raiseHand,
|
raiseHand,
|
||||||
isParticipantModerator,
|
isParticipantModerator,
|
||||||
isLocalParticipantModerator,
|
isLocalParticipantModerator,
|
||||||
hasRaisedHand
|
hasRaisedHand,
|
||||||
|
grantModerator
|
||||||
} from '../../react/features/base/participants';
|
} from '../../react/features/base/participants';
|
||||||
import { updateSettings } from '../../react/features/base/settings';
|
import { updateSettings } from '../../react/features/base/settings';
|
||||||
import { isToggleCameraEnabled, toggleCamera } from '../../react/features/base/tracks';
|
import { isToggleCameraEnabled, toggleCamera } from '../../react/features/base/tracks';
|
||||||
|
@ -164,6 +165,14 @@ function initCommands() {
|
||||||
}
|
}
|
||||||
APP.store.dispatch(autoAssignToBreakoutRooms());
|
APP.store.dispatch(autoAssignToBreakoutRooms());
|
||||||
},
|
},
|
||||||
|
'grant-moderator': participantId => {
|
||||||
|
if (!isLocalParticipantModerator(APP.store.getState())) {
|
||||||
|
logger.error('Missing moderator rights to grant moderator right to another participant');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
APP.store.dispatch(grantModerator(participantId));
|
||||||
|
},
|
||||||
'display-name': displayName => {
|
'display-name': displayName => {
|
||||||
sendAnalytics(createApiEvent('display.name.changed'));
|
sendAnalytics(createApiEvent('display.name.changed'));
|
||||||
APP.conference.changeLocalDisplayName(displayName);
|
APP.conference.changeLocalDisplayName(displayName);
|
||||||
|
|
|
@ -38,7 +38,7 @@ const commands = {
|
||||||
displayName: 'display-name',
|
displayName: 'display-name',
|
||||||
e2eeKey: 'e2ee-key',
|
e2eeKey: 'e2ee-key',
|
||||||
email: 'email',
|
email: 'email',
|
||||||
toggleLobby: 'toggle-lobby',
|
grantModerator: 'grant-moderator',
|
||||||
hangup: 'video-hangup',
|
hangup: 'video-hangup',
|
||||||
initiatePrivateChat: 'initiate-private-chat',
|
initiatePrivateChat: 'initiate-private-chat',
|
||||||
joinBreakoutRoom: 'join-breakout-room',
|
joinBreakoutRoom: 'join-breakout-room',
|
||||||
|
@ -73,6 +73,7 @@ const commands = {
|
||||||
toggleChat: 'toggle-chat',
|
toggleChat: 'toggle-chat',
|
||||||
toggleE2EE: 'toggle-e2ee',
|
toggleE2EE: 'toggle-e2ee',
|
||||||
toggleFilmStrip: 'toggle-film-strip',
|
toggleFilmStrip: 'toggle-film-strip',
|
||||||
|
toggleLobby: 'toggle-lobby',
|
||||||
toggleModeration: 'toggle-moderation',
|
toggleModeration: 'toggle-moderation',
|
||||||
toggleParticipantsPane: 'toggle-participants-pane',
|
toggleParticipantsPane: 'toggle-participants-pane',
|
||||||
toggleRaiseHand: 'toggle-raise-hand',
|
toggleRaiseHand: 'toggle-raise-hand',
|
||||||
|
|
|
@ -108,13 +108,13 @@ UI.start = function() {
|
||||||
$('body').addClass('mobile-browser');
|
$('body').addClass('mobile-browser');
|
||||||
} else {
|
} else {
|
||||||
$('body').addClass('desktop-browser');
|
$('body').addClass('desktop-browser');
|
||||||
|
}
|
||||||
|
|
||||||
if (config.backgroundAlpha !== undefined) {
|
if (config.backgroundAlpha !== undefined) {
|
||||||
const backgroundColor = $('body').css('background-color');
|
const backgroundColor = $('body').css('background-color');
|
||||||
const alphaColor = setColorAlpha(backgroundColor, config.backgroundAlpha);
|
const alphaColor = setColorAlpha(backgroundColor, config.backgroundAlpha);
|
||||||
|
|
||||||
$('body').css('background-color', alphaColor);
|
$('body').css('background-color', alphaColor);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.iAmRecorder) {
|
if (config.iAmRecorder) {
|
||||||
|
|
|
@ -73,7 +73,7 @@
|
||||||
"jquery-i18next": "1.2.1",
|
"jquery-i18next": "1.2.1",
|
||||||
"js-md5": "0.6.1",
|
"js-md5": "0.6.1",
|
||||||
"jwt-decode": "2.2.0",
|
"jwt-decode": "2.2.0",
|
||||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1399.0.0+1a98d919/lib-jitsi-meet.tgz",
|
"lib-jitsi-meet": "https://git@github.com/jitsi/lib-jitsi-meet#c286b17f732d350b945553e973753b6701e42127",
|
||||||
"libflacjs": "https://git@github.com/mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
"libflacjs": "https://git@github.com/mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"moment": "2.29.1",
|
"moment": "2.29.1",
|
||||||
|
@ -11789,8 +11789,8 @@
|
||||||
},
|
},
|
||||||
"node_modules/lib-jitsi-meet": {
|
"node_modules/lib-jitsi-meet": {
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1399.0.0+1a98d919/lib-jitsi-meet.tgz",
|
"resolved": "git+https://git@github.com/jitsi/lib-jitsi-meet.git#c286b17f732d350b945553e973753b6701e42127",
|
||||||
"integrity": "sha512-aIWaPY62nEZ9x13JDvv92UFhAvCSdC0ogCv4KpR1+Bwb6YmOPks+GHWpirZGk2RzgOL3cry43SDTVL4Tc0VyNA==",
|
"integrity": "sha512-hj81bry+CtmoJAtSZKubKcGsA7SLqZCRSJskICd/fsvGnutbJrkBfJ6s6SPG1lFmHUkwccWyOwqfu9pK0Qp7WQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jitsi/js-utils": "2.0.0",
|
"@jitsi/js-utils": "2.0.0",
|
||||||
|
@ -28784,8 +28784,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lib-jitsi-meet": {
|
"lib-jitsi-meet": {
|
||||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1399.0.0+1a98d919/lib-jitsi-meet.tgz",
|
"version": "git+https://git@github.com/jitsi/lib-jitsi-meet.git#c286b17f732d350b945553e973753b6701e42127",
|
||||||
"integrity": "sha512-aIWaPY62nEZ9x13JDvv92UFhAvCSdC0ogCv4KpR1+Bwb6YmOPks+GHWpirZGk2RzgOL3cry43SDTVL4Tc0VyNA==",
|
"integrity": "sha512-hj81bry+CtmoJAtSZKubKcGsA7SLqZCRSJskICd/fsvGnutbJrkBfJ6s6SPG1lFmHUkwccWyOwqfu9pK0Qp7WQ==",
|
||||||
|
"from": "lib-jitsi-meet@https://git@github.com/jitsi/lib-jitsi-meet#c286b17f732d350b945553e973753b6701e42127",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@jitsi/js-utils": "2.0.0",
|
"@jitsi/js-utils": "2.0.0",
|
||||||
"@jitsi/logger": "2.0.0",
|
"@jitsi/logger": "2.0.0",
|
||||||
|
|
|
@ -78,7 +78,7 @@
|
||||||
"jquery-i18next": "1.2.1",
|
"jquery-i18next": "1.2.1",
|
||||||
"js-md5": "0.6.1",
|
"js-md5": "0.6.1",
|
||||||
"jwt-decode": "2.2.0",
|
"jwt-decode": "2.2.0",
|
||||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1399.0.0+1a98d919/lib-jitsi-meet.tgz",
|
"lib-jitsi-meet": "https://git@github.com/jitsi/lib-jitsi-meet#c286b17f732d350b945553e973753b6701e42127",
|
||||||
"libflacjs": "https://git@github.com/mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
"libflacjs": "https://git@github.com/mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"moment": "2.29.1",
|
"moment": "2.29.1",
|
||||||
|
|
|
@ -148,7 +148,7 @@ const ContextMenu = ({
|
||||||
|
|
||||||
setIsHidden(false);
|
setIsHidden(false);
|
||||||
} else {
|
} else {
|
||||||
setIsHidden(true);
|
hidden === undefined && setIsHidden(true);
|
||||||
}
|
}
|
||||||
}, [ entity, offsetTarget, _overflowDrawer ]);
|
}, [ entity, offsetTarget, _overflowDrawer ]);
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ import {
|
||||||
NOTIFICATION_TIMEOUT_TYPE,
|
NOTIFICATION_TIMEOUT_TYPE,
|
||||||
showErrorNotification
|
showErrorNotification
|
||||||
} from '../../notifications';
|
} from '../../notifications';
|
||||||
import { showSalesforceNotification } from '../../salesforce';
|
|
||||||
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED, connectionDisconnected } from '../connection';
|
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED, connectionDisconnected } from '../connection';
|
||||||
import { validateJwt } from '../jwt';
|
import { validateJwt } from '../jwt';
|
||||||
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
||||||
|
@ -240,9 +239,6 @@ function _conferenceJoined({ dispatch, getState }, next, action) {
|
||||||
dispatch(openDisplayNamePrompt(undefined));
|
dispatch(openDisplayNamePrompt(undefined));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
dispatch(showSalesforceNotification());
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -199,7 +199,6 @@ class Chat extends AbstractChat<Props> {
|
||||||
<ChatInput
|
<ChatInput
|
||||||
onResize = { this._onChatInputResize }
|
onResize = { this._onChatInputResize }
|
||||||
onSend = { this._onSendMessage } />
|
onSend = { this._onSendMessage } />
|
||||||
<KeyboardAvoider />
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -222,12 +222,12 @@ class Conference extends AbstractConference<Props, *> {
|
||||||
id = 'layout_wrapper'
|
id = 'layout_wrapper'
|
||||||
onMouseEnter = { this._onMouseEnter }
|
onMouseEnter = { this._onMouseEnter }
|
||||||
onMouseLeave = { this._onMouseLeave }
|
onMouseLeave = { this._onMouseLeave }
|
||||||
onMouseMove = { this._onMouseMove } >
|
onMouseMove = { this._onMouseMove }
|
||||||
|
ref = { this._setBackground }>
|
||||||
<div
|
<div
|
||||||
className = { _layoutClassName }
|
className = { _layoutClassName }
|
||||||
id = 'videoconference_page'
|
id = 'videoconference_page'
|
||||||
onMouseMove = { isMobileBrowser() ? undefined : this._onShowToolbar }
|
onMouseMove = { isMobileBrowser() ? undefined : this._onShowToolbar }>
|
||||||
ref = { this._setBackground }>
|
|
||||||
<ConferenceInfo />
|
<ConferenceInfo />
|
||||||
|
|
||||||
<Notice />
|
<Notice />
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
|
||||||
import { SET_REDUCED_UI } from '../base/responsive-ui';
|
import { SET_REDUCED_UI } from '../base/responsive-ui';
|
||||||
import { FeedbackDialog } from '../feedback';
|
import { FeedbackDialog } from '../feedback';
|
||||||
import { setFilmstripEnabled } from '../filmstrip';
|
import { setFilmstripEnabled } from '../filmstrip';
|
||||||
|
import { showSalesforceNotification } from '../salesforce/actions';
|
||||||
import { setToolboxEnabled } from '../toolbox/actions';
|
import { setToolboxEnabled } from '../toolbox/actions';
|
||||||
|
|
||||||
import { notifyKickedOut } from './actions';
|
import { notifyKickedOut } from './actions';
|
||||||
|
@ -21,13 +22,12 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case CONFERENCE_JOINED:
|
case CONFERENCE_JOINED:
|
||||||
case SET_REDUCED_UI: {
|
_conferenceJoined(store);
|
||||||
const { dispatch, getState } = store;
|
|
||||||
const state = getState();
|
|
||||||
const { reducedUI } = state['features/base/responsive-ui'];
|
|
||||||
|
|
||||||
dispatch(setToolboxEnabled(!reducedUI));
|
break;
|
||||||
dispatch(setFilmstripEnabled(!reducedUI));
|
|
||||||
|
case SET_REDUCED_UI: {
|
||||||
|
_setReducedUI(store);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -80,3 +80,37 @@ StateListenerRegistry.register(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the UI. In reduced UI mode some components will
|
||||||
|
* be hidden if there is no space to render them.
|
||||||
|
*
|
||||||
|
* @param {Store} store - The redux store in which the specified {@code action}
|
||||||
|
* is being dispatched.
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function _setReducedUI({ dispatch, getState }) {
|
||||||
|
const { reducedUI } = getState()['features/base/responsive-ui'];
|
||||||
|
|
||||||
|
dispatch(setToolboxEnabled(!reducedUI));
|
||||||
|
dispatch(setFilmstripEnabled(!reducedUI));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does extra sync up on properties that may need to be updated after the
|
||||||
|
* conference was joined.
|
||||||
|
*
|
||||||
|
* @param {Store} store - The redux store in which the specified {@code action}
|
||||||
|
* is being dispatched.
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function _conferenceJoined({ dispatch, getState }) {
|
||||||
|
_setReducedUI({
|
||||||
|
dispatch,
|
||||||
|
getState
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(showSalesforceNotification());
|
||||||
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ export function getDeviceSelectionDialogProps(stateful: Object | Function) {
|
||||||
// conference and this is not supported, when we open device selection on
|
// conference and this is not supported, when we open device selection on
|
||||||
// welcome page changing input devices will not be a problem
|
// welcome page changing input devices will not be a problem
|
||||||
// on welcome page we also show only what we have saved as user selected devices
|
// on welcome page we also show only what we have saved as user selected devices
|
||||||
if (!conference) {
|
if (!conference && !isMobileSafari) {
|
||||||
disableAudioInputChange = false;
|
disableAudioInputChange = false;
|
||||||
disableVideoInputSelect = false;
|
disableVideoInputSelect = false;
|
||||||
selectedAudioInputId = userSelectedMic;
|
selectedAudioInputId = userSelectedMic;
|
||||||
|
|
|
@ -22,6 +22,7 @@ const useStyles = makeStyles(theme => {
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
marginBottom: theme.spacing(7),
|
marginBottom: theme.spacing(7),
|
||||||
transition: 'margin-bottom 0.3s',
|
transition: 'margin-bottom 0.3s',
|
||||||
|
pointerEvents: 'none',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
|
|
|
@ -35,7 +35,7 @@ const queue = [];
|
||||||
let lastValidFaceBox;
|
let lastValidFaceBox;
|
||||||
|
|
||||||
const detect = async message => {
|
const detect = async message => {
|
||||||
const { baseUrl, imageBitmap, isHorizontallyFlipped, threshold } = message.data;
|
const { baseUrl, image, isHorizontallyFlipped, threshold } = message.data;
|
||||||
|
|
||||||
if (initInProgress || initError) {
|
if (initInProgress || initError) {
|
||||||
return;
|
return;
|
||||||
|
@ -70,8 +70,8 @@ const detect = async message => {
|
||||||
|
|
||||||
tf.engine().startScope();
|
tf.engine().startScope();
|
||||||
|
|
||||||
const image = tf.browser.fromPixels(imageBitmap);
|
const imageTensor = tf.browser.fromPixels(image);
|
||||||
const detections = await model.estimateFaces(image, false, isHorizontallyFlipped, false);
|
const detections = await model.estimateFaces(imageTensor, false, isHorizontallyFlipped, false);
|
||||||
|
|
||||||
tf.engine().endScope();
|
tf.engine().endScope();
|
||||||
|
|
||||||
|
@ -80,10 +80,10 @@ const detect = async message => {
|
||||||
if (detections.length) {
|
if (detections.length) {
|
||||||
faceBox = {
|
faceBox = {
|
||||||
// normalize to percentage based
|
// normalize to percentage based
|
||||||
left: Math.round(Math.min(...detections.map(d => d.topLeft[0])) * 100 / imageBitmap.width),
|
left: Math.round(Math.min(...detections.map(d => d.topLeft[0])) * 100 / image.width),
|
||||||
right: Math.round(Math.max(...detections.map(d => d.bottomRight[0])) * 100 / imageBitmap.width),
|
right: Math.round(Math.max(...detections.map(d => d.bottomRight[0])) * 100 / image.width),
|
||||||
top: Math.round(Math.min(...detections.map(d => d.topLeft[1])) * 100 / imageBitmap.height),
|
top: Math.round(Math.min(...detections.map(d => d.topLeft[1])) * 100 / image.height),
|
||||||
bottom: Math.round(Math.max(...detections.map(d => d.bottomRight[1])) * 100 / imageBitmap.height)
|
bottom: Math.round(Math.max(...detections.map(d => d.bottomRight[1])) * 100 / image.height)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (lastValidFaceBox && Math.abs(lastValidFaceBox.left - faceBox.left) < threshold) {
|
if (lastValidFaceBox && Math.abs(lastValidFaceBox.left - faceBox.left) < threshold) {
|
||||||
|
|
|
@ -44,6 +44,7 @@ export async function sendDataToWorker(
|
||||||
}
|
}
|
||||||
|
|
||||||
let imageBitmap;
|
let imageBitmap;
|
||||||
|
let image;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
imageBitmap = await imageCapture.grabFrame();
|
imageBitmap = await imageCapture.grabFrame();
|
||||||
|
@ -53,13 +54,28 @@ export async function sendDataToWorker(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof OffscreenCanvas === 'undefined') {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
|
||||||
|
canvas.width = imageBitmap.width;
|
||||||
|
canvas.height = imageBitmap.height;
|
||||||
|
context.drawImage(imageBitmap, 0, 0);
|
||||||
|
|
||||||
|
image = context.getImageData(0, 0, imageBitmap.width, imageBitmap.height);
|
||||||
|
} else {
|
||||||
|
image = imageBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
worker.postMessage({
|
worker.postMessage({
|
||||||
id: DETECT_FACE_BOX,
|
id: DETECT_FACE_BOX,
|
||||||
baseUrl: getBaseUrl(),
|
baseUrl: getBaseUrl(),
|
||||||
imageBitmap,
|
image,
|
||||||
threshold,
|
threshold,
|
||||||
isHorizontallyFlipped
|
isHorizontallyFlipped
|
||||||
});
|
});
|
||||||
|
|
||||||
|
imageBitmap.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,17 +5,10 @@ import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../base/participants';
|
||||||
import { MiddlewareRegistry } from '../base/redux';
|
import { MiddlewareRegistry } from '../base/redux';
|
||||||
import { CLIENT_RESIZED } from '../base/responsive-ui';
|
import { CLIENT_RESIZED } from '../base/responsive-ui';
|
||||||
import { SETTINGS_UPDATED } from '../base/settings';
|
import { SETTINGS_UPDATED } from '../base/settings';
|
||||||
import {
|
|
||||||
getCurrentLayout,
|
|
||||||
LAYOUTS
|
|
||||||
} from '../video-layout';
|
|
||||||
|
|
||||||
import { SET_USER_FILMSTRIP_WIDTH } from './actionTypes';
|
import { SET_USER_FILMSTRIP_WIDTH } from './actionTypes';
|
||||||
import {
|
import {
|
||||||
setFilmstripWidth,
|
setFilmstripWidth
|
||||||
setHorizontalViewDimensions,
|
|
||||||
setTileViewDimensions,
|
|
||||||
setVerticalViewDimensions
|
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import { DEFAULT_FILMSTRIP_WIDTH, MIN_STAGE_VIEW_WIDTH } from './constants';
|
import { DEFAULT_FILMSTRIP_WIDTH, MIN_STAGE_VIEW_WIDTH } from './constants';
|
||||||
import { updateRemoteParticipants, updateRemoteParticipantsOnLeave } from './functions';
|
import { updateRemoteParticipants, updateRemoteParticipantsOnLeave } from './functions';
|
||||||
|
@ -40,21 +33,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case CLIENT_RESIZED: {
|
case CLIENT_RESIZED: {
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
const layout = getCurrentLayout(state);
|
|
||||||
|
|
||||||
switch (layout) {
|
|
||||||
case LAYOUTS.TILE_VIEW: {
|
|
||||||
store.dispatch(setTileViewDimensions());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:
|
|
||||||
store.dispatch(setHorizontalViewDimensions());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
|
|
||||||
store.dispatch(setVerticalViewDimensions());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFilmstripResizable(state)) {
|
if (isFilmstripResizable(state)) {
|
||||||
const { width: filmstripWidth } = state['features/filmstrip'];
|
const { width: filmstripWidth } = state['features/filmstrip'];
|
||||||
|
|
|
@ -51,8 +51,11 @@ StateListenerRegistry.register(
|
||||||
* Listens for changes in the selected layout to calculate the dimensions of the tile view grid and horizontal view.
|
* Listens for changes in the selected layout to calculate the dimensions of the tile view grid and horizontal view.
|
||||||
*/
|
*/
|
||||||
StateListenerRegistry.register(
|
StateListenerRegistry.register(
|
||||||
/* selector */ state => getCurrentLayout(state),
|
/* selector */ state => {
|
||||||
/* listener */ (layout, store) => {
|
return { layout: getCurrentLayout(state),
|
||||||
|
width: state['features/base/responsive-ui'].clientWidth };
|
||||||
|
},
|
||||||
|
/* listener */ ({ layout }, store) => {
|
||||||
switch (layout) {
|
switch (layout) {
|
||||||
case LAYOUTS.TILE_VIEW:
|
case LAYOUTS.TILE_VIEW:
|
||||||
store.dispatch(setTileViewDimensions());
|
store.dispatch(setTileViewDimensions());
|
||||||
|
@ -64,6 +67,8 @@ StateListenerRegistry.register(
|
||||||
store.dispatch(setVerticalViewDimensions());
|
store.dispatch(setVerticalViewDimensions());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
deepEquals: true
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -100,7 +100,7 @@ function mapStateToProps(state): Object {
|
||||||
const { premeetingBackground } = state['features/dynamic-branding'];
|
const { premeetingBackground } = state['features/dynamic-branding'];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...abstractMapStateToProps,
|
...abstractMapStateToProps(state),
|
||||||
_premeetingBackground: premeetingBackground
|
_premeetingBackground: premeetingBackground
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,18 @@ const PollCreate = (props: AbstractProps) => {
|
||||||
}, [ lastFocus ]);
|
}, [ lastFocus ]);
|
||||||
|
|
||||||
const checkModifiers = useCallback(ev => {
|
const checkModifiers = useCallback(ev => {
|
||||||
|
// Composition events used to add accents to characters
|
||||||
|
// despite their absence from standard US keyboards,
|
||||||
|
// to build up logograms of many Asian languages
|
||||||
|
// from their base components or categories and so on.
|
||||||
|
if (ev.isComposing || ev.keyCode === 229) {
|
||||||
|
// keyCode 229 means that user pressed some button,
|
||||||
|
// but input method is still processing that.
|
||||||
|
// This is a standard behavior for some input methods
|
||||||
|
// like entering japanese or сhinese hieroglyphs.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Because this isn't done automatically on MacOS
|
// Because this isn't done automatically on MacOS
|
||||||
if (ev.key === 'Enter' && ev.metaKey) {
|
if (ev.key === 'Enter' && ev.metaKey) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
@ -90,7 +102,11 @@ const PollCreate = (props: AbstractProps) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ev.key === 'Enter') {
|
if (ev.key === 'Enter') {
|
||||||
addAnswer(i + 1);
|
// We add a new option input
|
||||||
|
// only if we are on the last option input
|
||||||
|
if (i === answers.length - 1) {
|
||||||
|
addAnswer(i + 1);
|
||||||
|
}
|
||||||
requestFocus(i + 1);
|
requestFocus(i + 1);
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
} else if (ev.key === 'Backspace' && ev.target.value === '' && answers.length > 1) {
|
} else if (ev.key === 'Backspace' && ev.target.value === '' && answers.length > 1) {
|
||||||
|
|
|
@ -132,14 +132,13 @@ export function highlightMeetingMoment() {
|
||||||
return async (dispatch: Function, getState: Function) => {
|
return async (dispatch: Function, getState: Function) => {
|
||||||
dispatch(setHighlightMomentButtonState(true));
|
dispatch(setHighlightMomentButtonState(true));
|
||||||
|
|
||||||
try {
|
const success = await sendMeetingHighlight(getState());
|
||||||
await sendMeetingHighlight(getState());
|
|
||||||
|
if (success) {
|
||||||
dispatch(showNotification({
|
dispatch(showNotification({
|
||||||
descriptionKey: 'recording.highlightMomentSucessDescription',
|
descriptionKey: 'recording.highlightMomentSucessDescription',
|
||||||
titleKey: 'recording.highlightMomentSuccess'
|
titleKey: 'recording.highlightMomentSuccess'
|
||||||
}));
|
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
|
||||||
} catch (err) {
|
|
||||||
logger.error('Could not highlight meeting moment', err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(setHighlightMomentButtonState(false));
|
dispatch(setHighlightMomentButtonState(false));
|
||||||
|
|
|
@ -1,17 +1,38 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
|
import { batch } from 'react-redux';
|
||||||
|
|
||||||
import { getActiveSession, isHighlightMeetingMomentDisabled } from '../..';
|
import { getActiveSession, isHighlightMeetingMomentDisabled } from '../..';
|
||||||
|
import { openDialog } from '../../../base/dialog';
|
||||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||||
|
import {
|
||||||
|
hideNotification,
|
||||||
|
NOTIFICATION_TIMEOUT_TYPE,
|
||||||
|
NOTIFICATION_TYPE,
|
||||||
|
showNotification
|
||||||
|
} from '../../../notifications';
|
||||||
import { highlightMeetingMoment } from '../../actions.any';
|
import { highlightMeetingMoment } from '../../actions.any';
|
||||||
|
import { StartRecordingDialog } from '../../components';
|
||||||
|
import { PROMPT_RECORDING_NOTIFICATION_ID } from '../../constants';
|
||||||
|
import { getRecordButtonProps } from '../../functions';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the conference is in audio only mode.
|
* Indicates whether or not the button is disabled.
|
||||||
*/
|
*/
|
||||||
_audioOnly: boolean,
|
_disabled: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not a highlight request is in progress.
|
||||||
|
*/
|
||||||
|
_isHighlightInProgress: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not the button should be visible.
|
||||||
|
*/
|
||||||
|
_visible: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked to obtain translated strings.
|
* Invoked to obtain translated strings.
|
||||||
|
@ -24,7 +45,7 @@ export type Props = {
|
||||||
*/
|
*/
|
||||||
export default class AbstractHighlightButton<P: Props> extends Component<P> {
|
export default class AbstractHighlightButton<P: Props> extends Component<P> {
|
||||||
/**
|
/**
|
||||||
* Initializes a new AbstractVideoTrack instance.
|
* Initializes a new AbstractHighlightButton instance.
|
||||||
*
|
*
|
||||||
* @param {Object} props - The read-only properties with which the new
|
* @param {Object} props - The read-only properties with which the new
|
||||||
* instance is to be initialized.
|
* instance is to be initialized.
|
||||||
|
@ -43,9 +64,27 @@ export default class AbstractHighlightButton<P: Props> extends Component<P> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onClick() {
|
_onClick() {
|
||||||
const { _disabled, dispatch } = this.props;
|
const { _disabled, _isHighlightInProgress, dispatch } = this.props;
|
||||||
|
|
||||||
if (!_disabled) {
|
if (_isHighlightInProgress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_disabled) {
|
||||||
|
dispatch(showNotification({
|
||||||
|
descriptionKey: 'recording.highlightMomentDisabled',
|
||||||
|
titleKey: 'recording.highlightMoment',
|
||||||
|
uid: PROMPT_RECORDING_NOTIFICATION_ID,
|
||||||
|
customActionNameKey: [ 'localRecording.start' ],
|
||||||
|
customActionHandler: [ () => {
|
||||||
|
batch(() => {
|
||||||
|
dispatch(hideNotification(PROMPT_RECORDING_NOTIFICATION_ID));
|
||||||
|
dispatch(openDialog(StartRecordingDialog));
|
||||||
|
});
|
||||||
|
} ],
|
||||||
|
appearance: NOTIFICATION_TYPE.NORMAL
|
||||||
|
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||||
|
} else {
|
||||||
dispatch(highlightMeetingMoment());
|
dispatch(highlightMeetingMoment());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,12 +92,14 @@ export default class AbstractHighlightButton<P: Props> extends Component<P> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps (parts of) the Redux state to the associated
|
* Maps (parts of) the Redux state to the associated
|
||||||
* {@code AbstractVideoQualityLabel}'s props.
|
* {@code AbstractHighlightButton}'s props.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The Redux state.
|
* @param {Object} state - The Redux state.
|
||||||
* @private
|
* @private
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* _audioOnly: boolean
|
* _disabled: boolean,
|
||||||
|
* _isHighlightInProgress: boolean,
|
||||||
|
* _visible: boolean
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function _abstractMapStateToProps(state: Object) {
|
export function _abstractMapStateToProps(state: Object) {
|
||||||
|
@ -66,8 +107,17 @@ export function _abstractMapStateToProps(state: Object) {
|
||||||
const isButtonDisabled = isHighlightMeetingMomentDisabled(state);
|
const isButtonDisabled = isHighlightMeetingMomentDisabled(state);
|
||||||
const { webhookProxyUrl } = state['features/base/config'];
|
const { webhookProxyUrl } = state['features/base/config'];
|
||||||
|
|
||||||
|
const {
|
||||||
|
disabled: isRecordButtonDisabled,
|
||||||
|
visible: isRecordButtonVisible
|
||||||
|
} = getRecordButtonProps(state);
|
||||||
|
|
||||||
|
const canStartRecording = isRecordButtonVisible && !isRecordButtonDisabled;
|
||||||
|
const _visible = (canStartRecording || isRecordingRunning) && Boolean(webhookProxyUrl);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_disabled: !isRecordingRunning || isButtonDisabled,
|
_disabled: !isRecordingRunning,
|
||||||
_visible: Boolean(webhookProxyUrl)
|
_isHighlightInProgress: isButtonDisabled,
|
||||||
|
_visible
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,16 +6,10 @@ import {
|
||||||
} from '../../../analytics';
|
} from '../../../analytics';
|
||||||
import { IconToggleRecording } from '../../../base/icons';
|
import { IconToggleRecording } from '../../../base/icons';
|
||||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||||
import {
|
|
||||||
getLocalParticipant,
|
|
||||||
isLocalParticipantModerator
|
|
||||||
} from '../../../base/participants';
|
|
||||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||||
import { isInBreakoutRoom } from '../../../breakout-rooms/functions';
|
|
||||||
import { maybeShowPremiumFeatureDialog } from '../../../jaas/actions';
|
import { maybeShowPremiumFeatureDialog } from '../../../jaas/actions';
|
||||||
import { FEATURES } from '../../../jaas/constants';
|
import { FEATURES } from '../../../jaas/constants';
|
||||||
import { getActiveSession } from '../../functions';
|
import { getActiveSession, getRecordButtonProps } from '../../functions';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the React {@code Component} props of
|
* The type of the React {@code Component} props of
|
||||||
|
@ -131,57 +125,20 @@ export default class AbstractRecordButton<P: Props> extends AbstractButton<P, *>
|
||||||
* {@code RecordButton} component.
|
* {@code RecordButton} component.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The Redux state.
|
* @param {Object} state - The Redux state.
|
||||||
* @param {Props} ownProps - The own props of the Component.
|
|
||||||
* @private
|
* @private
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* _disabled: boolean,
|
* _disabled: boolean,
|
||||||
* _isRecordingRunning: boolean,
|
* _isRecordingRunning: boolean,
|
||||||
|
* _tooltip: string,
|
||||||
* visible: boolean
|
* visible: boolean
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function _mapStateToProps(state: Object, ownProps: Props): Object {
|
export function _mapStateToProps(state: Object): Object {
|
||||||
let { visible } = ownProps;
|
const {
|
||||||
|
disabled: _disabled,
|
||||||
// a button can be disabled/enabled if enableFeaturesBasedOnToken
|
tooltip: _tooltip,
|
||||||
// is on or if the livestreaming is running.
|
visible
|
||||||
let _disabled;
|
} = getRecordButtonProps(state);
|
||||||
let _tooltip = '';
|
|
||||||
|
|
||||||
if (typeof visible === 'undefined') {
|
|
||||||
// If the containing component provides the visible prop, that is one
|
|
||||||
// above all, but if not, the button should be autonomus and decide on
|
|
||||||
// its own to be visible or not.
|
|
||||||
const isModerator = isLocalParticipantModerator(state);
|
|
||||||
const {
|
|
||||||
enableFeaturesBasedOnToken,
|
|
||||||
fileRecordingsEnabled
|
|
||||||
} = state['features/base/config'];
|
|
||||||
const { features = {} } = getLocalParticipant(state);
|
|
||||||
|
|
||||||
visible = isModerator && fileRecordingsEnabled;
|
|
||||||
|
|
||||||
if (enableFeaturesBasedOnToken) {
|
|
||||||
visible = visible && String(features.recording) === 'true';
|
|
||||||
_disabled = String(features.recording) === 'disabled';
|
|
||||||
if (!visible && !_disabled) {
|
|
||||||
_disabled = true;
|
|
||||||
visible = true;
|
|
||||||
_tooltip = 'dialog.recordingDisabledTooltip';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// disable the button if the livestreaming is running.
|
|
||||||
if (getActiveSession(state, JitsiRecordingConstants.mode.STREAM)) {
|
|
||||||
_disabled = true;
|
|
||||||
_tooltip = 'dialog.recordingDisabledBecauseOfActiveLiveStreamingTooltip';
|
|
||||||
}
|
|
||||||
|
|
||||||
// disable the button if we are in a breakout room.
|
|
||||||
if (isInBreakoutRoom(state)) {
|
|
||||||
_disabled = true;
|
|
||||||
visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_disabled,
|
_disabled,
|
||||||
|
|
|
@ -231,8 +231,7 @@ class StartRecordingDialogContent extends Component<Props> {
|
||||||
t
|
t
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (isVpaas
|
if (!(isVpaas && selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE)) {
|
||||||
|| selectedRecordingService !== RECORDING_TYPES.JITSI_REC_SERVICE) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,14 @@
|
||||||
import { withStyles } from '@material-ui/core';
|
import { withStyles } from '@material-ui/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { openDialog } from '../../../../base/dialog';
|
||||||
import { translate } from '../../../../base/i18n';
|
import { translate } from '../../../../base/i18n';
|
||||||
import { IconHighlight } from '../../../../base/icons';
|
import { IconHighlight } from '../../../../base/icons';
|
||||||
import { Label } from '../../../../base/label';
|
import { Label } from '../../../../base/label';
|
||||||
import { connect } from '../../../../base/redux';
|
import { connect } from '../../../../base/redux';
|
||||||
import { Tooltip } from '../../../../base/tooltip';
|
import { Tooltip } from '../../../../base/tooltip';
|
||||||
import BaseTheme from '../../../../base/ui/components/BaseTheme';
|
import BaseTheme from '../../../../base/ui/components/BaseTheme';
|
||||||
|
import { StartRecordingDialog } from '../../../components';
|
||||||
import AbstractHighlightButton, {
|
import AbstractHighlightButton, {
|
||||||
_abstractMapStateToProps,
|
_abstractMapStateToProps,
|
||||||
type Props as AbstractProps
|
type Props as AbstractProps
|
||||||
|
@ -28,6 +30,17 @@ type Props = AbstractProps & {
|
||||||
_visible: boolean,
|
_visible: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the React {@code Component} state of {@link HighlightButton}.
|
||||||
|
*/
|
||||||
|
type State = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the notification which prompts for starting recording is open is not.
|
||||||
|
*/
|
||||||
|
isNotificationOpen: boolean
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the styles for the component.
|
* Creates the styles for the component.
|
||||||
*
|
*
|
||||||
|
@ -37,13 +50,36 @@ type Props = AbstractProps & {
|
||||||
*/
|
*/
|
||||||
const styles = theme => {
|
const styles = theme => {
|
||||||
return {
|
return {
|
||||||
regular: {
|
container: {
|
||||||
background: theme.palette.field02,
|
position: 'relative'
|
||||||
margin: '0 4px 4px 4px'
|
|
||||||
},
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
background: theme.palette.text02,
|
background: theme.palette.text02,
|
||||||
margin: '0 4px 4px 4px'
|
margin: '0 4px 4px 4px'
|
||||||
|
},
|
||||||
|
regular: {
|
||||||
|
background: theme.palette.field02,
|
||||||
|
margin: '0 4px 4px 4px'
|
||||||
|
},
|
||||||
|
highlightNotification: {
|
||||||
|
backgroundColor: theme.palette.field02,
|
||||||
|
borderRadius: '6px',
|
||||||
|
boxShadow: '0px 6px 20px rgba(0, 0, 0, 0.25)',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
color: theme.palette.uiBackground,
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: '400',
|
||||||
|
left: '4px',
|
||||||
|
padding: '16px',
|
||||||
|
position: 'absolute',
|
||||||
|
top: '32px',
|
||||||
|
width: 320
|
||||||
|
},
|
||||||
|
highlightNotificationButton: {
|
||||||
|
color: theme.palette.field01Focus,
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontWeight: '600',
|
||||||
|
marginTop: '8px'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -52,7 +88,82 @@ const styles = theme => {
|
||||||
* React {@code Component} responsible for displaying an action that
|
* React {@code Component} responsible for displaying an action that
|
||||||
* allows users to highlight a meeting moment.
|
* allows users to highlight a meeting moment.
|
||||||
*/
|
*/
|
||||||
export class HighlightButton extends AbstractHighlightButton<Props> {
|
export class HighlightButton extends AbstractHighlightButton<Props, State> {
|
||||||
|
/**
|
||||||
|
* Initializes a new HighlightButton instance.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The read-only properties with which the new
|
||||||
|
* instance is to be initialized.
|
||||||
|
*/
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isNotificationOpen: false
|
||||||
|
};
|
||||||
|
|
||||||
|
this._onOpenDialog = this._onOpenDialog.bind(this);
|
||||||
|
this._onWindowClickListener = this._onWindowClickListener.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#componentDidMount()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
componentDidMount() {
|
||||||
|
window.addEventListener('click', this._onWindowClickListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#componentWillUnmount()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
componentWillUnmount() {
|
||||||
|
window.removeEventListener('click', this._onWindowClickListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles clicking / pressing the start recording button.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onOpenDialog() {
|
||||||
|
this.props.dispatch(openDialog(StartRecordingDialog));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles clicking / pressing the highlight button.
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
* @param {Event} e - The click event.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onClick(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const { _disabled } = this.props;
|
||||||
|
|
||||||
|
if (_disabled) {
|
||||||
|
this.setState({
|
||||||
|
isNotificationOpen: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
super._onClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Window click event listener.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onWindowClickListener() {
|
||||||
|
this.setState({
|
||||||
|
isNotificationOpen: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements React's {@link Component#render()}.
|
* Implements React's {@link Component#render()}.
|
||||||
|
@ -77,16 +188,28 @@ export class HighlightButton extends AbstractHighlightButton<Props> {
|
||||||
const tooltipKey = _disabled ? 'recording.highlightMomentDisabled' : 'recording.highlightMoment';
|
const tooltipKey = _disabled ? 'recording.highlightMomentDisabled' : 'recording.highlightMoment';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<div className = { classes.container }>
|
||||||
content = { t(tooltipKey) }
|
<Tooltip
|
||||||
position = { 'bottom' }>
|
content = { t(tooltipKey) }
|
||||||
<Label
|
position = { 'bottom' }>
|
||||||
className = { className }
|
<Label
|
||||||
icon = { IconHighlight }
|
className = { className }
|
||||||
iconColor = { _disabled ? BaseTheme.palette.text03 : BaseTheme.palette.field01 }
|
icon = { IconHighlight }
|
||||||
id = 'highlightMeetingLabel'
|
iconColor = { _disabled ? BaseTheme.palette.text03 : BaseTheme.palette.field01 }
|
||||||
onClick = { this._onClick } />
|
id = 'highlightMeetingLabel'
|
||||||
</Tooltip>
|
onClick = { this._onClick } />
|
||||||
|
</Tooltip>
|
||||||
|
{this.state.isNotificationOpen && (
|
||||||
|
<div className = { classes.highlightNotification }>
|
||||||
|
{t('recording.highlightMomentDisabled')}
|
||||||
|
<div
|
||||||
|
className = { classes.highlightNotificationButton }
|
||||||
|
onClick = { this._onOpenDialog }>
|
||||||
|
{t('localRecording.start')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,11 +50,7 @@ class RecordingButton extends AbstractRecordButton<Props> {
|
||||||
export function _mapStateToProps(state: Object, ownProps: Props): Object {
|
export function _mapStateToProps(state: Object, ownProps: Props): Object {
|
||||||
const abstractProps = _abstractMapStateToProps(state, ownProps);
|
const abstractProps = _abstractMapStateToProps(state, ownProps);
|
||||||
const toolbarButtons = getToolbarButtons(state);
|
const toolbarButtons = getToolbarButtons(state);
|
||||||
let { visible } = ownProps;
|
const visible = toolbarButtons.includes('recording') && abstractProps.visible;
|
||||||
|
|
||||||
if (typeof visible === 'undefined') {
|
|
||||||
visible = toolbarButtons.includes('recording') && abstractProps.visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...abstractProps,
|
...abstractProps,
|
||||||
|
|
|
@ -17,6 +17,13 @@ export const LIVE_STREAMING_OFF_SOUND_ID = 'LIVE_STREAMING_OFF_SOUND';
|
||||||
*/
|
*/
|
||||||
export const LIVE_STREAMING_ON_SOUND_ID = 'LIVE_STREAMING_ON_SOUND';
|
export const LIVE_STREAMING_ON_SOUND_ID = 'LIVE_STREAMING_ON_SOUND';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The identifier of the prompt to start recording notification.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
export const PROMPT_RECORDING_NOTIFICATION_ID = 'PROMPT_RECORDING_NOTIFICATION_ID';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The identifier of the sound to be played when a recording session is stopped.
|
* The identifier of the sound to be played when a recording session is stopped.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
|
import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
|
||||||
import { getLocalParticipant } from '../base/participants';
|
import { getLocalParticipant, isLocalParticipantModerator } from '../base/participants';
|
||||||
|
import { isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||||
import { isEnabled as isDropboxEnabled } from '../dropbox';
|
import { isEnabled as isDropboxEnabled } from '../dropbox';
|
||||||
import { extractFqnFromPath } from '../dynamic-branding';
|
import { extractFqnFromPath } from '../dynamic-branding';
|
||||||
|
|
||||||
|
@ -119,6 +120,65 @@ export function getSessionStatusToShow(state: Object, mode: string): ?string {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the recording button props.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The redux state to search in.
|
||||||
|
*
|
||||||
|
* @returns {{
|
||||||
|
* disabled: boolean,
|
||||||
|
* tooltip: string,
|
||||||
|
* visible: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function getRecordButtonProps(state: Object): ?string {
|
||||||
|
let visible;
|
||||||
|
|
||||||
|
// a button can be disabled/enabled if enableFeaturesBasedOnToken
|
||||||
|
// is on or if the livestreaming is running.
|
||||||
|
let disabled;
|
||||||
|
let tooltip = '';
|
||||||
|
|
||||||
|
// If the containing component provides the visible prop, that is one
|
||||||
|
// above all, but if not, the button should be autonomus and decide on
|
||||||
|
// its own to be visible or not.
|
||||||
|
const isModerator = isLocalParticipantModerator(state);
|
||||||
|
const {
|
||||||
|
enableFeaturesBasedOnToken,
|
||||||
|
fileRecordingsEnabled
|
||||||
|
} = state['features/base/config'];
|
||||||
|
const { features = {} } = getLocalParticipant(state);
|
||||||
|
|
||||||
|
visible = isModerator && fileRecordingsEnabled;
|
||||||
|
|
||||||
|
if (enableFeaturesBasedOnToken) {
|
||||||
|
visible = visible && String(features.recording) === 'true';
|
||||||
|
disabled = String(features.recording) === 'disabled';
|
||||||
|
if (!visible && !disabled) {
|
||||||
|
disabled = true;
|
||||||
|
visible = true;
|
||||||
|
tooltip = 'dialog.recordingDisabledTooltip';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable the button if the livestreaming is running.
|
||||||
|
if (getActiveSession(state, JitsiRecordingConstants.mode.STREAM)) {
|
||||||
|
disabled = true;
|
||||||
|
tooltip = 'dialog.recordingDisabledBecauseOfActiveLiveStreamingTooltip';
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable the button if we are in a breakout room.
|
||||||
|
if (isInBreakoutRoom(state)) {
|
||||||
|
disabled = true;
|
||||||
|
visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
disabled,
|
||||||
|
tooltip,
|
||||||
|
visible
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the resource id.
|
* Returns the resource id.
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
} from '../notifications';
|
} from '../notifications';
|
||||||
|
|
||||||
import { SalesforceLinkDialog } from './components';
|
import { SalesforceLinkDialog } from './components';
|
||||||
|
import { isSalesforceEnabled } from './functions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the notification for linking the meeting to Salesforce.
|
* Displays the notification for linking the meeting to Salesforce.
|
||||||
|
@ -18,9 +19,7 @@ import { SalesforceLinkDialog } from './components';
|
||||||
*/
|
*/
|
||||||
export function showSalesforceNotification() {
|
export function showSalesforceNotification() {
|
||||||
return (dispatch: Object, getState: Function) => {
|
return (dispatch: Object, getState: Function) => {
|
||||||
const { salesforceUrl } = getState()['features/base/config'];
|
if (!isSalesforceEnabled(getState())) {
|
||||||
|
|
||||||
if (!salesforceUrl) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,21 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { doGetJSON } from '../base/util';
|
import { doGetJSON } from '../base/util';
|
||||||
|
import { isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether Salesforce is enabled for the current conference.
|
||||||
|
*
|
||||||
|
* @param {Function|Object} state - The redux store, the redux
|
||||||
|
* {@code getState} function, or the redux state itself.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export const isSalesforceEnabled = (state: Function | Object) => {
|
||||||
|
const { salesforceUrl } = state['features/base/config'];
|
||||||
|
const isBreakoutRoom = isInBreakoutRoom(state);
|
||||||
|
|
||||||
|
return Boolean(salesforceUrl) && !isBreakoutRoom;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the Salesforce records that were most recently interacted with.
|
* Fetches the Salesforce records that were most recently interacted with.
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
export * from './components';
|
|
||||||
export * from './actions';
|
|
|
@ -200,10 +200,10 @@ function _mapStateToProps(state) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...newProps,
|
...newProps,
|
||||||
followMeEnabled: tabState.followMeEnabled,
|
followMeEnabled: tabState?.followMeEnabled,
|
||||||
startAudioMuted: tabState.startAudioMuted,
|
startAudioMuted: tabState?.startAudioMuted,
|
||||||
startVideoMuted: tabState.startVideoMuted,
|
startVideoMuted: tabState?.startVideoMuted,
|
||||||
startReactionsMuted: tabState.startReactionsMuted
|
startReactionsMuted: tabState?.startReactionsMuted
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
styles: 'settings-pane moderator-pane',
|
styles: 'settings-pane moderator-pane',
|
||||||
|
@ -242,11 +242,11 @@ function _mapStateToProps(state) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...newProps,
|
...newProps,
|
||||||
currentFramerate: tabState.currentFramerate,
|
currentFramerate: tabState?.currentFramerate,
|
||||||
currentLanguage: tabState.currentLanguage,
|
currentLanguage: tabState?.currentLanguage,
|
||||||
hideSelfView: tabState.hideSelfView,
|
hideSelfView: tabState?.hideSelfView,
|
||||||
showPrejoinPage: tabState.showPrejoinPage,
|
showPrejoinPage: tabState?.showPrejoinPage,
|
||||||
enabledNotifications: tabState.enabledNotifications
|
enabledNotifications: tabState?.enabledNotifications
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
styles: 'settings-pane more-pane',
|
styles: 'settings-pane more-pane',
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/
|
||||||
import { navigate }
|
import { navigate }
|
||||||
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||||
import { screen } from '../../../mobile/navigation/routes';
|
import { screen } from '../../../mobile/navigation/routes';
|
||||||
|
import { isSalesforceEnabled } from '../../../salesforce/functions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of a button for opening the Salesforce link dialog.
|
* Implementation of a button for opening the Salesforce link dialog.
|
||||||
|
@ -39,7 +40,7 @@ class LinkToSalesforceButton extends AbstractButton<AbstractButtonProps, *> {
|
||||||
*/
|
*/
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
visible: Boolean(state['features/base/config'].salesforceUrl)
|
visible: isSalesforceEnabled(state)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { translate } from '../../../base/i18n';
|
||||||
import { IconSalesforce } from '../../../base/icons';
|
import { IconSalesforce } from '../../../base/icons';
|
||||||
import { connect } from '../../../base/redux';
|
import { connect } from '../../../base/redux';
|
||||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||||
import { SalesforceLinkDialog } from '../../../salesforce';
|
import { SalesforceLinkDialog } from '../../../salesforce/components';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the React {@code Component} props of {@link LinkToSalesforce}.
|
* The type of the React {@code Component} props of {@link LinkToSalesforce}.
|
||||||
|
|
|
@ -52,6 +52,7 @@ import {
|
||||||
LiveStreamButton,
|
LiveStreamButton,
|
||||||
RecordButton
|
RecordButton
|
||||||
} from '../../../recording';
|
} from '../../../recording';
|
||||||
|
import { isSalesforceEnabled } from '../../../salesforce/functions';
|
||||||
import {
|
import {
|
||||||
isScreenAudioSupported,
|
isScreenAudioSupported,
|
||||||
isScreenVideoShared,
|
isScreenVideoShared,
|
||||||
|
@ -867,6 +868,27 @@ class Toolbox extends Component<Props> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the notify mode of the given toolbox button.
|
||||||
|
*
|
||||||
|
* @param {string} btnName - The toolbar button's name.
|
||||||
|
* @returns {string|undefined} - The button's notify mode.
|
||||||
|
*/
|
||||||
|
_getButtonNotifyMode(btnName) {
|
||||||
|
const notify = this.props._buttonsWithNotifyClick?.find(
|
||||||
|
(btn: string | Object) =>
|
||||||
|
(typeof btn === 'string' && btn === btnName)
|
||||||
|
|| (typeof btn === 'object' && btn.key === btnName)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (notify) {
|
||||||
|
return typeof notify === 'string' || notify.preventExecution
|
||||||
|
? NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
||||||
|
: NOTIFY_CLICK_MODE.ONLY_NOTIFY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the notify click mode for the buttons.
|
* Sets the notify click mode for the buttons.
|
||||||
*
|
*
|
||||||
|
@ -880,19 +902,7 @@ class Toolbox extends Component<Props> {
|
||||||
|
|
||||||
Object.values(buttons).forEach((button: any) => {
|
Object.values(buttons).forEach((button: any) => {
|
||||||
if (typeof button === 'object') {
|
if (typeof button === 'object') {
|
||||||
const notify = this.props._buttonsWithNotifyClick.find(
|
button.notifyMode = this._getButtonNotifyMode(button.key);
|
||||||
(btn: string | Object) =>
|
|
||||||
(typeof btn === 'string' && btn === button.key)
|
|
||||||
|| (typeof btn === 'object' && btn.key === button.key)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (notify) {
|
|
||||||
const notifyMode = typeof notify === 'string' || notify.preventExecution
|
|
||||||
? NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
|
||||||
: NOTIFY_CLICK_MODE.ONLY_NOTIFY;
|
|
||||||
|
|
||||||
button.notifyMode = notifyMode;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1363,8 +1373,10 @@ class Toolbox extends Component<Props> {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<HangupButton
|
<HangupButton
|
||||||
|
buttonKey = 'hangup'
|
||||||
customClass = 'hangup-button'
|
customClass = 'hangup-button'
|
||||||
key = 'hangup-button'
|
key = 'hangup-button'
|
||||||
|
notifyMode = { this._getButtonNotifyMode('hangup') }
|
||||||
visible = { isToolbarButtonEnabled('hangup', _toolbarButtons) } />
|
visible = { isToolbarButtonEnabled('hangup', _toolbarButtons) } />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1391,8 +1403,7 @@ function _mapStateToProps(state, ownProps) {
|
||||||
disableProfile,
|
disableProfile,
|
||||||
enableFeaturesBasedOnToken,
|
enableFeaturesBasedOnToken,
|
||||||
iAmRecorder,
|
iAmRecorder,
|
||||||
iAmSipGateway,
|
iAmSipGateway
|
||||||
salesforceUrl
|
|
||||||
} = state['features/base/config'];
|
} = state['features/base/config'];
|
||||||
const {
|
const {
|
||||||
fullScreen,
|
fullScreen,
|
||||||
|
@ -1441,7 +1452,7 @@ function _mapStateToProps(state, ownProps) {
|
||||||
_isIosMobile: isIosMobileBrowser(),
|
_isIosMobile: isIosMobileBrowser(),
|
||||||
_isMobile: isMobileBrowser(),
|
_isMobile: isMobileBrowser(),
|
||||||
_isVpaasMeeting: isVpaasMeeting(state),
|
_isVpaasMeeting: isVpaasMeeting(state),
|
||||||
_hasSalesforce: Boolean(salesforceUrl),
|
_hasSalesforce: isSalesforceEnabled(state),
|
||||||
_localParticipantID: localParticipant?.id,
|
_localParticipantID: localParticipant?.id,
|
||||||
_localVideo: localVideo,
|
_localVideo: localVideo,
|
||||||
_overflowMenuVisible: overflowMenuVisible,
|
_overflowMenuVisible: overflowMenuVisible,
|
||||||
|
|
|
@ -279,14 +279,16 @@ function _updateReceiverVideoConstraints({ getState }) {
|
||||||
const qualityLevel = getVideoQualityForResizableFilmstripThumbnails(state);
|
const qualityLevel = getVideoQualityForResizableFilmstripThumbnails(state);
|
||||||
|
|
||||||
visibleRemoteTrackSourceNames.forEach(sourceName => {
|
visibleRemoteTrackSourceNames.forEach(sourceName => {
|
||||||
receiverConstraints.constraints[sourceName] = { 'maxHeight': qualityLevel };
|
receiverConstraints.constraints[sourceName] = { 'maxHeight': Math.min(qualityLevel,
|
||||||
|
maxFrameHeight) };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (largeVideoSourceName) {
|
if (largeVideoSourceName) {
|
||||||
let quality = maxFrameHeight;
|
let quality = maxFrameHeight;
|
||||||
|
|
||||||
if (!remoteScreenShares.find(id => id === largeVideoParticipantId)) {
|
if (navigator.product !== 'ReactNative'
|
||||||
|
&& !remoteScreenShares.find(id => id === largeVideoParticipantId)) {
|
||||||
quality = getVideoQualityForLargeVideo();
|
quality = getVideoQualityForLargeVideo();
|
||||||
}
|
}
|
||||||
receiverConstraints.constraints[largeVideoSourceName] = { 'maxHeight': quality };
|
receiverConstraints.constraints[largeVideoSourceName] = { 'maxHeight': quality };
|
||||||
|
@ -326,14 +328,16 @@ function _updateReceiverVideoConstraints({ getState }) {
|
||||||
const qualityLevel = getVideoQualityForResizableFilmstripThumbnails(state);
|
const qualityLevel = getVideoQualityForResizableFilmstripThumbnails(state);
|
||||||
|
|
||||||
visibleRemoteParticipants.forEach(participantId => {
|
visibleRemoteParticipants.forEach(participantId => {
|
||||||
receiverConstraints.constraints[participantId] = { 'maxHeight': qualityLevel };
|
receiverConstraints.constraints[participantId] = { 'maxHeight': Math.min(qualityLevel,
|
||||||
|
maxFrameHeight) };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (largeVideoParticipantId) {
|
if (largeVideoParticipantId) {
|
||||||
let quality = maxFrameHeight;
|
let quality = maxFrameHeight;
|
||||||
|
|
||||||
if (!remoteScreenShares.find(id => id === largeVideoParticipantId)) {
|
if (navigator.product !== 'ReactNative'
|
||||||
|
&& !remoteScreenShares.find(id => id === largeVideoParticipantId)) {
|
||||||
quality = getVideoQualityForLargeVideo();
|
quality = getVideoQualityForLargeVideo();
|
||||||
}
|
}
|
||||||
receiverConstraints.constraints[largeVideoParticipantId] = { 'maxHeight': quality };
|
receiverConstraints.constraints[largeVideoParticipantId] = { 'maxHeight': quality };
|
||||||
|
|
Loading…
Reference in New Issue