Compare commits

..

1 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
d490b29b7a deps: replace node-sass with sass
The former is no longer actively maintained.

Fixes: https://github.com/jitsi/jitsi-meet/issues/6427
2020-09-17 16:24:57 +02:00
39 changed files with 274 additions and 610 deletions

View File

@@ -20,5 +20,5 @@
android.useAndroidX=true
android.enableJetifier=true
appVersion=20.5.0
sdkVersion=2.11.0
appVersion=20.4.0
sdkVersion=2.10.0

View File

@@ -14,6 +14,9 @@ var config = {
// Domain for authenticated users. Defaults to <domain>.
// authdomain: 'jitsi-meet.example.com',
// Jirecon recording component domain.
// jirecon: 'jirecon.jitsi-meet.example.com',
// Call control component (Jigasi).
// call_control: 'callcontrol.jitsi-meet.example.com',
@@ -64,11 +67,6 @@ var config = {
// adjusted to 2.5 Mbps. This takes a value between 0 and 1 which determines
// the probability for this to be enabled.
// capScreenshareBitrate: 1 // 0 to disable
// Enable callstats only for a percentage of users.
// This takes a value between 0 and 100 which determines the probability for
// the callstats to be enabled.
// callStatsThreshold: 5 // enable callstats for 5% of the users.
},
// Disables ICE/UDP by filtering out local and remote UDP candidates in
@@ -637,6 +635,8 @@ var config = {
// List of undocumented settings used in jitsi-meet
/**
_immediateReloadThreshold
autoRecord
autoRecordToken
debug
debugAudioLevels
deploymentInfo

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>20.5.0</string>
<string>20.4.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@@ -66,10 +66,10 @@
<string>See your scheduled meetings in the app.</string>
<key>NSCameraUsageDescription</key>
<string>Participate in meetings with video.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>NSMicrophoneUsageDescription</key>
<string>Participate in meetings with voice.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Local network is used for establishing Peer-to-Peer connections.</string>
<key>NSUserActivityTypes</key>
<array>
<string>org.jitsi.JitsiMeet.ios.conference</string>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>20.5.0</string>
<string>20.4.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>UISupportedInterfaceOrientations</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>20.5.0</string>
<string>20.4.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CLKComplicationPrincipalClass</key>

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.11.0</string>
<string>2.10.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -212,7 +212,7 @@
"kickParticipantButton": "Verwijderen",
"kickParticipantDialog": "Weet u zeker dat u deze deelnemer wilt verwijderen?",
"kickParticipantTitle": "Deze deelnemer verwijderen?",
"kickTitle": "Oei! {{participantDisplayName}} heeft u uit de vergadering verwijderd",
"kickTitle": "Oei! {{ParticipantDisplayName}} heeft u uit de vergadering verwijderd",
"liveStreaming": "Livestreamen",
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Niet mogelijk tijdens opnemen",
"liveStreamingDisabledForGuestTooltip": "Gasten kunnen geen livestream starten.",

View File

@@ -15,18 +15,13 @@ import {
} from '../../react/features/base/conference';
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
import { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
import { pinParticipant } from '../../react/features/base/participants';
import {
processExternalDeviceRequest
} from '../../react/features/device-selection/functions';
import { isEnabled as isDropboxEnabled } from '../../react/features/dropbox';
import { toggleE2EE } from '../../react/features/e2ee/actions';
import { invite } from '../../react/features/invite';
import {
captureLargeVideoScreenshot,
resizeLargeVideo,
selectParticipantInLargeVideo
} from '../../react/features/large-video/actions';
import { selectParticipantInLargeVideo } from '../../react/features/large-video/actions';
import { toggleLobbyMode } from '../../react/features/lobby/actions.web';
import { RECORDING_TYPES } from '../../react/features/recording/constants';
import { getActiveSession } from '../../react/features/recording/functions';
@@ -121,19 +116,9 @@ function initCommands() {
));
}
},
'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);
},
'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;
@@ -349,21 +334,6 @@ function initCommands() {
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 'invite': {
const { invitees } = request;

View File

@@ -35,8 +35,6 @@ const commands = {
hangup: 'video-hangup',
muteEveryone: 'mute-everyone',
password: 'password',
pinParticipant: 'pin-participant',
resizeLargeVideo: 'resize-large-video',
sendEndpointTextMessage: 'send-endpoint-text-message',
sendTones: 'send-tones',
setLargeVideoParticipant: 'set-large-video-participant',
@@ -439,12 +437,10 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
const parsedWidth = parseSizeParam(width);
if (parsedHeight !== undefined) {
this._height = height;
this._frame.style.height = parsedHeight;
}
if (parsedWidth !== undefined) {
this._width = width;
this._frame.style.width = parsedWidth;
}
}
@@ -637,18 +633,6 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
}
}
/**
* Captures the screenshot of the large video.
*
* @returns {Promise<string>} - Resolves with a base64 encoded image data of the screenshot
* if large video is detected, an error otherwise.
*/
captureLargeVideoScreenshot() {
return this._transport.sendRequest({
name: 'capture-largevideo-screenshot'
});
}
/**
* Removes the listeners and removes the Jitsi Meet frame.
*
@@ -920,17 +904,6 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
});
}
/**
* Pins a participant's video on to the stage view.
*
* @param {string} participantId - Participant id (JID) of the participant
* that needs to be pinned on the stage view.
* @returns {void}
*/
pinParticipant(participantId) {
this.executeCommand('pinParticipant', participantId);
}
/**
* Removes event listener.
*
@@ -957,19 +930,6 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
eventList.forEach(event => this.removeEventListener(event));
}
/**
* Resizes the large video container as per the dimensions provided.
*
* @param {number} width - Width that needs to be applied on the large video container.
* @param {number} height - Height that needs to be applied on the large video container.
* @returns {void}
*/
resizeLargeVideo(width, height) {
if (width <= this._width && height <= this._height) {
this.executeCommand('resizeLargeVideo', width, height);
}
}
/**
* Passes an event along to the local conference participant to establish
* or update a direct peer connection. This is currently used for developing

View File

@@ -5,6 +5,13 @@
*/
const UIUtil = {
/**
* Returns the available video width.
*/
getAvailableVideoWidth() {
return window.innerWidth;
},
/**
* Escapes the given text.
*/

View File

@@ -15,12 +15,13 @@ import { VIDEO_TYPE } from '../../../react/features/base/media';
import { CHAT_SIZE } from '../../../react/features/chat';
import {
updateKnownLargeVideoResolution
} from '../../../react/features/large-video/actions';
} from '../../../react/features/large-video';
import { PresenceLabel } from '../../../react/features/presence-status';
/* eslint-enable no-unused-vars */
import UIEvents from '../../../service/UI/UIEvents';
import { createDeferred } from '../../util/helpers';
import AudioLevels from '../audio_levels/AudioLevels';
import UIUtil from '../util/UIUtil';
import { VideoContainer, VIDEO_CONTAINER_TYPE } from './VideoContainer';
@@ -67,30 +68,7 @@ export default class LargeVideoManager {
// use the same video container to handle desktop tracks
this.addContainer(DESKTOP_CONTAINER_TYPE, this.videoContainer);
/**
* The preferred width passed as an argument to {@link updateContainerSize}.
*
* @type {number|undefined}
*/
this.preferredWidth = undefined;
/**
* The preferred height passed as an argument to {@link updateContainerSize}.
*
* @type {number|undefined}
*/
this.preferredHeight = undefined;
/**
* The calculated width that will be used for the large video.
* @type {number}
*/
this.width = 0;
/**
* The calculated height that will be used for the large video.
* @type {number}
*/
this.height = 0;
/**
@@ -345,32 +323,20 @@ export default class LargeVideoManager {
/**
* Update container size.
*/
updateContainerSize(width, height) {
if (typeof width === 'number') {
this.preferredWidth = width;
}
if (typeof height === 'number') {
this.preferredHeight = height;
}
let widthToUse = this.preferredWidth || window.innerWidth;
updateContainerSize() {
let widthToUse = UIUtil.getAvailableVideoWidth();
const { isOpen } = APP.store.getState()['features/chat'];
/**
* If chat state is open, we re-compute the container width by subtracting the default width of
* the chat. We re-compute the width again after the chat window is closed. This is needed when
* custom styling is configured on the large video container through the iFrame API.
*/
if (isOpen && !this.resizedForChat) {
if (isOpen) {
/**
* If chat state is open, we re-compute the container width
* by subtracting the default width of the chat.
*/
widthToUse -= CHAT_SIZE;
this.resizedForChat = true;
} else if (this.resizedForChat) {
this.resizedForChat = false;
widthToUse += CHAT_SIZE;
}
this.width = widthToUse;
this.height = this.preferredHeight || window.innerHeight;
this.height = window.innerHeight;
}
/**

View File

@@ -373,6 +373,7 @@ export default class RemoteVideo extends SmallVideo {
if (stream === this.videoStream) {
this.videoStream = null;
this.videoType = undefined;
}
this.updateView();
@@ -483,6 +484,7 @@ export default class RemoteVideo extends SmallVideo {
if (isVideo) {
this.videoStream = stream;
this.videoType = stream.videoType;
} else {
this.audioStream = stream;
}

View File

@@ -243,10 +243,6 @@ export default class SmallVideo {
* or hidden
*/
setScreenSharing(isScreenSharing) {
if (isScreenSharing === this.isScreenSharing) {
return;
}
this.isScreenSharing = isScreenSharing;
this.updateView();
this.updateStatusBar();

View File

@@ -489,7 +489,7 @@ const VideoLayout = {
onVideoTypeChanged(id, newVideoType) {
const remoteVideo = remoteVideos[id];
if (!remoteVideo) {
if (!remoteVideo || remoteVideo.videoType === newVideoType) {
return;
}

View File

@@ -209,7 +209,7 @@ export default {
})
.catch(err => {
audioTrackError = err;
showError && APP.store.dispatch(notifyMicError(err));
showError && APP.store.disptach(notifyMicError(err));
return [];
}));

18
package-lock.json generated
View File

@@ -10500,8 +10500,8 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#412606485511afc9a2495f6fb58aa76f65d509a9",
"from": "github:jitsi/lib-jitsi-meet#412606485511afc9a2495f6fb58aa76f65d509a9",
"version": "github:jitsi/lib-jitsi-meet#43e7c853b834dc7ced0f81ee5f4b130444d85e95",
"from": "github:jitsi/lib-jitsi-meet#43e7c853b834dc7ced0f81ee5f4b130444d85e95",
"requires": {
"@jitsi/js-utils": "1.0.0",
"@jitsi/sdp-interop": "1.0.3",
@@ -13141,11 +13141,6 @@
}
}
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"q": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
@@ -13804,8 +13799,8 @@
"integrity": "sha512-00DphusbzXUuJDP+SSRe7FJczFWzrCErr+2TdRxYfbQjpDaRk3rXw8nhlQXI5U6KthquEQOrCRMq2F2hi/HKLg=="
},
"react-native-calendar-events": {
"version": "github:jitsi/react-native-calendar-events#df48ecdc4e1e90c5352f803ddbab1fa7269b74a7",
"from": "github:jitsi/react-native-calendar-events#df48ecdc4e1e90c5352f803ddbab1fa7269b74a7"
"version": "github:jitsi/react-native-calendar-events#928a80e2ffef0d7e84936d7e7e0acc4f53ee8470",
"from": "github:jitsi/react-native-calendar-events#928a80e2ffef0d7e84936d7e7e0acc4f53ee8470"
},
"react-native-callstats": {
"version": "3.61.0",
@@ -15239,11 +15234,6 @@
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz",
"integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA=="
},
"stackblur-canvas": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.3.0.tgz",
"integrity": "sha512-3ZHJv+43D8YttgumssIxkfs3hBXW7XaMS5Ux65fOBhKDYMjbG5hF8Ey8a90RiiJ58aQnAhWbGilPzZ9rkIlWgQ=="
},
"stacktrace-parser": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.8.tgz",

View File

@@ -56,14 +56,13 @@
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#412606485511afc9a2495f6fb58aa76f65d509a9",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#43e7c853b834dc7ced0f81ee5f4b130444d85e95",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.19",
"moment": "2.19.4",
"moment-duration-format": "2.2.2",
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
"pixelmatch": "5.1.0",
"punycode": "2.1.1",
"react": "16.9",
"react-dom": "16.9",
"react-emoji-render": "1.2.4",
@@ -71,7 +70,7 @@
"react-linkify": "1.0.0-alpha",
"react-native": "github:jitsi/react-native#efd2aff5661d75a230e36406b698cfe0ee545be2",
"react-native-background-timer": "2.4.0",
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#df48ecdc4e1e90c5352f803ddbab1fa7269b74a7",
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#928a80e2ffef0d7e84936d7e7e0acc4f53ee8470",
"react-native-callstats": "3.61.0",
"react-native-collapsible": "1.5.1",
"react-native-default-preference": "1.4.2",
@@ -93,7 +92,6 @@
"redux-thunk": "2.2.0",
"rnnoise-wasm": "github:jitsi/rnnoise-wasm.git#566a16885897704d6e6d67a1d5ac5d39781db2af",
"rtcstats": "github:jitsi/rtcstats#v6.2.0",
"stackblur-canvas": "2.3.0",
"styled-components": "3.4.9",
"util": "0.12.1",
"uuid": "3.1.0",

View File

@@ -57,15 +57,12 @@ export function resetAnalytics() {
* @param {Store} store - The redux store in which the specified {@code action} is being dispatched.
* @returns {Promise} Resolves with the handlers that have been successfully loaded.
*/
export async function createHandlers({ getState }: { getState: Function }) {
export function createHandlers({ getState }: { getState: Function }) {
getJitsiMeetGlobalNS().analyticsHandlers = [];
window.analyticsHandlers = []; // Legacy support.
if (!isAnalyticsEnabled(getState)) {
// Avoid all analytics processing if there are no handlers, since no event would be sent.
analytics.dispose();
return [];
return Promise.resolve([]);
}
const state = getState();
@@ -103,47 +100,43 @@ export async function createHandlers({ getState }: { getState: Function }) {
};
const handlers = [];
if (amplitudeAPPKey) {
try {
const amplitude = new AmplitudeHandler(handlerConstructorOptions);
try {
const amplitude = new AmplitudeHandler(handlerConstructorOptions);
analytics.amplitudeIdentityProps = amplitude.getIdentityProps();
analytics.amplitudeIdentityProps = amplitude.getIdentityProps();
handlers.push(amplitude);
} catch (e) {
logger.error('Failed to initialize Amplitude handler', e);
}
}
handlers.push(amplitude);
// eslint-disable-next-line no-empty
} catch (e) {}
if (matomoEndpoint && matomoSiteID) {
try {
const matomo = new MatomoHandler(handlerConstructorOptions);
try {
const matomo = new MatomoHandler(handlerConstructorOptions);
handlers.push(matomo);
} catch (e) {
logger.error('Failed to initialize Matomo handler', e);
}
}
handlers.push(matomo);
// eslint-disable-next-line no-empty
} catch (e) {}
if (Array.isArray(scriptURLs) && scriptURLs.length > 0) {
let externalHandlers;
return (
_loadHandlers(scriptURLs, handlerConstructorOptions)
.then(externalHandlers => {
handlers.push(...externalHandlers);
if (handlers.length === 0) {
// Throwing an error in order to dispose the analytics in the catch clause due to the lack of any
// analytics handlers.
throw new Error('No analytics handlers created!');
}
try {
externalHandlers = await _loadHandlers(scriptURLs, handlerConstructorOptions);
handlers.push(...externalHandlers);
} catch (e) {
logger.error('Failed to initialize external analytics handlers', e);
}
}
return handlers;
})
.catch(e => {
analytics.dispose();
if (handlers.length !== 0) {
logger.error(e);
}
// Avoid all analytics processing if there are no handlers, since no event would be sent.
if (handlers.length === 0) {
analytics.dispose();
}
return [];
}));
logger.info(`Initialized ${handlers.length} analytics handlers`);
return handlers;
}
/**
@@ -235,7 +228,7 @@ function _inIframe() {
}
/**
* Tries to load the scripts for the external analytics handlers and creates them.
* Tries to load the scripts for the analytics handlers and creates them.
*
* @param {Array} scriptURLs - The array of script urls to load.
* @param {Object} handlerConstructorOptions - The default options to pass when creating handlers.
@@ -286,7 +279,7 @@ function _loadHandlers(scriptURLs = [], handlerConstructorOptions) {
logger.warn(`Error creating analytics handler: ${error}`);
}
}
logger.debug(`Loaded ${handlers.length} external analytics handlers`);
logger.debug(`Loaded ${handlers.length} analytics handlers`);
return handlers;
});

View File

@@ -72,11 +72,6 @@ export default class AmplitudeHandler extends AbstractHandler {
* @returns {Object}
*/
getIdentityProps() {
// TODO: Remove when web and native Aplitude implementations are unified.
if (navigator.product === 'ReactNative') {
return {};
}
return {
sessionId: amplitude.getInstance(this._amplitudeOptions).getSessionId(),
deviceId: amplitude.getInstance(this._amplitudeOptions).options.deviceId,

View File

@@ -294,8 +294,6 @@ export function maybeRedirectToWelcomePage(options: Object = {}) {
if (enableClosePage) {
if (isVpaasMeeting(getState())) {
redirectToStaticPage('/');
return;
}
const { isGuest, jwt } = getState()['features/base/jwt'];

View File

@@ -15,6 +15,8 @@ export default [
'abTesting',
'analytics.disabled',
'audioLevelsInterval',
'autoRecord',
'autoRecordToken',
'apiLogLevels',
'avgRtpStatsN',

View File

@@ -1,6 +1,5 @@
// @flow
import punycode from 'punycode';
import React, { Component } from 'react';
import ReactLinkify from 'react-linkify';
import { Text } from 'react-native';
@@ -69,7 +68,7 @@ export default class Linkify extends Component<Props> {
key = { key }
style = { this.props.linkStyle }
url = { decoratedHref }>
{ punycode.toASCII(decoratedText) }
{decoratedText}
</Link>
);
}

View File

@@ -1,6 +1,5 @@
// @flow
import punycode from 'punycode';
import React, { Component } from 'react';
import ReactLinkify from 'react-linkify';
@@ -45,7 +44,7 @@ export default class Linkify extends Component<Props> {
key = { key }
rel = 'noopener noreferrer'
target = '_blank'>
{ punycode.toASCII(decoratedText) }
{decoratedText}
</a>
);
}

View File

@@ -24,19 +24,35 @@ const _RIGHT_WATERMARK_STYLE = {
type Props = {
/**
* The link used to navigate to on logo click.
* The user selected url used to navigate to on logo click.
*/
_logoLink: string,
_customLogoLink: string,
/**
* The url for the logo.
* The url of the user selected logo.
*/
_logoUrl: string,
_customLogoUrl: string,
/**
* If the Jitsi watermark should be displayed or not.
* Whether or not the current user is logged in through a JWT.
*/
_showJitsiWatermark: boolean,
_isGuest: boolean,
/**
* Whether or not the current meeting is a vpaas one.
*/
_isVpaas: boolean,
/**
* Flag used to signal that the logo can be displayed.
* It becomes true after the user customization options are fetched.
*/
_readyToDisplayJitsiWatermark: boolean,
/**
* Returns true if welcome page is visible at the moment.
*/
_welcomePageIsVisible: boolean,
/**
* The default value for the Jitsi logo URL.
@@ -59,11 +75,27 @@ type State = {
*/
brandWatermarkLink: string,
/**
* The url to open when clicking the Jitsi watermark.
*/
jitsiWatermarkLink: string,
/**
* Whether or not the brand watermark should be displayed.
*/
showBrandWatermark: boolean,
/**
* Whether or not the Jitsi watermark should be displayed.
*/
showJitsiWatermark: boolean,
/**
* Whether or not the Jitsi watermark should be displayed for users not
* logged in through a JWT.
*/
showJitsiWatermarkForGuests: boolean,
/**
* Whether or not the show the "powered by Jitsi.org" link.
*/
@@ -85,17 +117,29 @@ class Watermarks extends Component<Props, State> {
super(props);
let showBrandWatermark;
let showJitsiWatermark;
let showJitsiWatermarkForGuests;
if (interfaceConfig.filmStripOnly) {
showBrandWatermark = false;
showJitsiWatermark = false;
showJitsiWatermarkForGuests = false;
} else {
showBrandWatermark = interfaceConfig.SHOW_BRAND_WATERMARK;
showJitsiWatermark = interfaceConfig.SHOW_JITSI_WATERMARK;
showJitsiWatermarkForGuests
= interfaceConfig.SHOW_WATERMARK_FOR_GUESTS;
}
this.state = {
brandWatermarkLink:
showBrandWatermark ? interfaceConfig.BRAND_WATERMARK_LINK : '',
jitsiWatermarkLink:
showJitsiWatermark || showJitsiWatermarkForGuests
? interfaceConfig.JITSI_WATERMARK_LINK : '',
showBrandWatermark,
showJitsiWatermark,
showJitsiWatermarkForGuests,
showPoweredBy: interfaceConfig.SHOW_POWERED_BY
};
}
@@ -122,6 +166,55 @@ class Watermarks extends Component<Props, State> {
);
}
/**
* Returns true if the watermark is ready to be displayed.
*
* @private
* @returns {boolean}
*/
_canDisplayJitsiWatermark() {
const {
showJitsiWatermark,
showJitsiWatermarkForGuests
} = this.state;
const {
_isGuest,
_readyToDisplayJitsiWatermark,
_welcomePageIsVisible
} = this.props;
return (_readyToDisplayJitsiWatermark
&& (showJitsiWatermark || (_isGuest && showJitsiWatermarkForGuests)))
|| _welcomePageIsVisible;
}
/**
* Returns the background image style.
*
* @private
* @returns {string}
*/
_getBackgroundImageStyle() {
const {
_customLogoUrl,
_isVpaas,
defaultJitsiLogoURL
} = this.props;
let style = 'none';
if (_isVpaas) {
if (_customLogoUrl) {
style = `url(${_customLogoUrl})`;
}
} else {
style = `url(${_customLogoUrl
|| defaultJitsiLogoURL
|| interfaceConfig.DEFAULT_LOGO_URL})`;
}
return style;
}
/**
* Renders a brand watermark if it is enabled.
*
@@ -161,28 +254,33 @@ class Watermarks extends Component<Props, State> {
* @returns {ReactElement|null}
*/
_renderJitsiWatermark() {
const {
_logoLink,
_logoUrl,
_showJitsiWatermark
} = this.props;
let reactElement = null;
if (_showJitsiWatermark) {
if (this._canDisplayJitsiWatermark()) {
const backgroundImage = this._getBackgroundImageStyle();
const link = this.props._customLogoLink || this.state.jitsiWatermarkLink;
const additionalStyles = {};
if (backgroundImage === 'none') {
additionalStyles.height = 0;
additionalStyles.width = 0;
}
const style = {
backgroundImage: `url(${_logoUrl})`,
backgroundImage,
maxWidth: 140,
maxHeight: 70
maxHeight: 70,
...additionalStyles
};
reactElement = (<div
className = 'watermark leftwatermark'
style = { style } />);
if (_logoLink) {
if (link) {
reactElement = (
<a
href = { _logoLink }
href = { link }
target = '_new'>
{ reactElement }
</a>
@@ -221,52 +319,27 @@ class Watermarks extends Component<Props, State> {
* Maps parts of Redux store to component prop types.
*
* @param {Object} state - Snapshot of Redux store.
* @param {Object} ownProps - Component's own props.
* @returns {Props}
*/
function _mapStateToProps(state, ownProps) {
function _mapStateToProps(state) {
const { isGuest } = state['features/base/jwt'];
const {
customizationReady,
customizationFailed,
defaultBranding,
useDynamicBrandingData,
logoClickUrl,
logoImageUrl
} = state['features/dynamic-branding'];
const isValidRoom = state['features/base/conference'].room;
const {
DEFAULT_LOGO_URL,
JITSI_WATERMARK_LINK,
SHOW_JITSI_WATERMARK,
SHOW_JITSI_WATERMARK_FOR_GUESTS,
filmStripOnly
} = interfaceConfig;
let _showJitsiWatermark = (!filmStripOnly
&& (customizationReady && !customizationFailed)
&& (SHOW_JITSI_WATERMARK || (isGuest && SHOW_JITSI_WATERMARK_FOR_GUESTS)))
|| !isValidRoom;
let _logoUrl = logoImageUrl;
let _logoLink = logoClickUrl;
if (useDynamicBrandingData) {
if (isVpaasMeeting(state)) {
// don't show logo if request fails or no logo set for vpaas meetings
_showJitsiWatermark = !customizationFailed && Boolean(logoImageUrl);
} else if (defaultBranding) {
_logoUrl = DEFAULT_LOGO_URL;
_logoLink = JITSI_WATERMARK_LINK;
}
} else {
// When there is no custom branding data use defaults
_logoUrl = ownProps.defaultJitsiLogoURL || DEFAULT_LOGO_URL;
_logoLink = JITSI_WATERMARK_LINK;
}
const { customizationReady, logoClickUrl, logoImageUrl } = state['features/dynamic-branding'];
const { room } = state['features/base/conference'];
return {
_logoLink,
_logoUrl,
_showJitsiWatermark
/**
* The indicator which determines whether the local participant is a
* guest in the conference.
*
* @private
* @type {boolean}
*/
_customLogoLink: logoClickUrl,
_customLogoUrl: logoImageUrl,
_isGuest: isGuest,
_isVpaas: isVpaasMeeting(state),
_readyToDisplayJitsiWatermark: customizationReady,
_welcomePageIsVisible: !room
};
}

View File

@@ -3,11 +3,6 @@
*/
export const SET_DYNAMIC_BRANDING_DATA = 'SET_DYNAMIC_BRANDING_DATA';
/**
* Action used to signal the customization failed.
*/
export const SET_DYNAMIC_BRANDING_FAILED = 'SET_DYNAMIC_BRANDING_FAILED';
/**
* Action used to signal the branding elements are ready to be displayed
*/

View File

@@ -4,11 +4,7 @@ import { getLogger } from 'jitsi-meet-logger';
import { doGetJSON } from '../base/util';
import {
SET_DYNAMIC_BRANDING_DATA,
SET_DYNAMIC_BRANDING_FAILED,
SET_DYNAMIC_BRANDING_READY
} from './actionTypes';
import { SET_DYNAMIC_BRANDING_DATA, SET_DYNAMIC_BRANDING_READY } from './actionTypes';
import { extractFqnFromPath } from './functions';
const logger = getLogger(__filename);
@@ -36,8 +32,6 @@ export function fetchCustomBrandingData() {
return dispatch(setDynamicBrandingData(res));
} catch (err) {
logger.error('Error fetching branding data', err);
return dispatch(setDynamicBrandingFailed());
}
}
@@ -69,14 +63,3 @@ function setDynamicBrandingReady() {
type: SET_DYNAMIC_BRANDING_READY
};
}
/**
* Action used to signal the branding request failed.
*
* @returns {Object}
*/
function setDynamicBrandingFailed() {
return {
type: SET_DYNAMIC_BRANDING_FAILED
};
}

View File

@@ -2,11 +2,7 @@
import { ReducerRegistry } from '../base/redux';
import {
SET_DYNAMIC_BRANDING_DATA,
SET_DYNAMIC_BRANDING_FAILED,
SET_DYNAMIC_BRANDING_READY
} from './actionTypes';
import { SET_DYNAMIC_BRANDING_DATA, SET_DYNAMIC_BRANDING_READY } from './actionTypes';
/**
* The name of the redux store/state property which is the root of the redux
@@ -15,80 +11,12 @@ import {
const STORE_NAME = 'features/dynamic-branding';
const DEFAULT_STATE = {
/**
* The custom background color for the LargeVideo.
*
* @public
* @type {string}
*/
backgroundColor: '',
/**
* The custom background image used on the LargeVideo.
*
* @public
* @type {string}
*/
backgroundImageUrl: '',
/**
* Flag indicating that the logo (JitsiWatermark) can be displayed.
* This is used in order to avoid image flickering.
*
* @public
* @type {boolean}
*/
customizationReady: false,
/**
* Flag indicating that the dynamic branding data request has failed.
* When the request fails there is no logo (JitsiWatermark) displayed.
*
* @public
* @type {boolean}
*/
customizationFailed: false,
/**
* Flag indicating that the dynamic branding has not been modified and should use
* the default options.
*
* @public
* @type {boolean}
*/
defaultBranding: true,
/**
* The custom invite domain.
*
* @public
* @type {string}
*/
inviteDomain: '',
/**
* The custom url used when the user clicks the logo.
*
* @public
* @type {string}
*/
logoClickUrl: '',
/**
* The custom logo (JitisWatermark).
*
* @public
* @type {string}
*/
logoImageUrl: '',
/**
* Flag used to signal if the app should use a custom logo or not
*
* @public
* @type {boolean}
*/
useDynamicBrandingData: false
logoImageUrl: ''
};
/**
@@ -97,33 +25,15 @@ const DEFAULT_STATE = {
ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
switch (action.type) {
case SET_DYNAMIC_BRANDING_DATA: {
const {
backgroundColor,
backgroundImageUrl,
defaultBranding,
inviteDomain,
logoClickUrl,
logoImageUrl
} = action.value;
const { backgroundColor, backgroundImageUrl, inviteDomain, logoClickUrl, logoImageUrl } = action.value;
return {
backgroundColor,
backgroundImageUrl,
defaultBranding,
inviteDomain,
logoClickUrl,
logoImageUrl,
customizationFailed: false,
customizationReady: true,
useDynamicBrandingData: true
};
}
case SET_DYNAMIC_BRANDING_FAILED: {
return {
...state,
customizationReady: true,
customizationFailed: true,
useDynamicBrandingData: true
customizationReady: true
};
}
case SET_DYNAMIC_BRANDING_READY:
@@ -131,6 +41,7 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
...state,
customizationReady: true
};
}
return state;

View File

@@ -10,7 +10,6 @@ import { translate } from '../../../../base/i18n';
import { JitsiRecordingConstants } from '../../../../base/lib-jitsi-meet';
import { getLocalParticipant } from '../../../../base/participants';
import { connect } from '../../../../base/redux';
import { isVpaasMeeting } from '../../../../billing-counter/functions';
import EmbedMeetingTrigger from '../../../../embed-meeting/components/EmbedMeetingTrigger';
import { getActiveSession } from '../../../../recording';
import { updateDialInNumbers } from '../../../actions';
@@ -37,11 +36,6 @@ type Props = {
*/
_dialIn: Object,
/**
* Whether or not embed meeting should be visible.
*/
_embedMeetingVisible: boolean,
/**
* Whether or not invite contacts should be visible.
*/
@@ -86,7 +80,6 @@ type Props = {
function AddPeopleDialog({
_conferenceName,
_dialIn,
_embedMeetingVisible,
_inviteContactsVisible,
_inviteUrl,
_liveStreamViewURL,
@@ -159,7 +152,7 @@ function AddPeopleDialog({
<InviteByEmailSection
inviteSubject = { inviteSubject }
inviteText = { invite } />
{ _embedMeetingVisible && <EmbedMeetingTrigger /> }
<EmbedMeetingTrigger />
<div className = 'invite-more-dialog separator' />
{
_liveStreamViewURL
@@ -198,7 +191,6 @@ function mapStateToProps(state) {
return {
_conferenceName: getRoomName(state),
_dialIn: state['features/invite'],
_embedMeetingVisible: !isVpaasMeeting(state),
_inviteContactsVisible: interfaceConfig.ENABLE_DIAL_OUT && !hideInviteContacts,
_inviteUrl: getInviteURL(state),
_liveStreamViewURL:

View File

@@ -17,6 +17,8 @@ import {
UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
} from './actionTypes';
declare var APP: Object;
/**
* Signals conference to select a participant.
*
@@ -48,7 +50,7 @@ export function selectParticipant() {
/**
* Action to select the participant to be displayed in LargeVideo based on the
* participant id provided. If a participant id is not provided, the LargeVideo
* participant id provided. If a partcipant id is not provided, the LargeVideo
* participant will be selected based on a variety of factors: If there is a
* dominant or pinned speaker, or if there are remote tracks, etc.
*

View File

@@ -1,3 +0,0 @@
// @flow
export * from './actions.any';

View File

@@ -1,75 +0,0 @@
// @flow
import type { Dispatch } from 'redux';
import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
import { MEDIA_TYPE } from '../base/media';
import { getTrackByMediaTypeAndParticipant } from '../base/tracks';
export * from './actions.any';
/**
* Captures a screenshot of the video displayed on the large video.
*
* @returns {Function}
*/
export function captureLargeVideoScreenshot() {
return (dispatch: Dispatch<any>, getState: Function): Promise<string> => {
const state = getState();
const largeVideo = state['features/large-video'];
if (!largeVideo) {
return Promise.resolve();
}
const tracks = state['features/base/tracks'];
const { jitsiTrack } = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, largeVideo.participantId);
const videoStream = jitsiTrack.getOriginalStream();
// Get the video element for the large video, cast HTMLElement to HTMLVideoElement to make flow happy.
/* eslint-disable-next-line no-extra-parens*/
const videoElement = ((document.getElementById('largeVideo'): any): HTMLVideoElement);
if (!videoElement) {
return Promise.resolve();
}
// Create a HTML canvas and draw video on to the canvas.
const [ track ] = videoStream.getVideoTracks();
const { height, width } = track.getSettings() ?? track.getConstraints();
const canvasElement = document.createElement('canvas');
const ctx = canvasElement.getContext('2d');
canvasElement.style.display = 'none';
canvasElement.height = parseInt(height, 10);
canvasElement.width = parseInt(width, 10);
ctx.drawImage(videoElement, 0, 0);
const dataURL = canvasElement.toDataURL('image/png', 1.0);
// Cleanup.
ctx.clearRect(0, 0, canvasElement.width, canvasElement.height);
canvasElement.remove();
return Promise.resolve(dataURL);
};
}
/**
* Resizes the large video container based on the dimensions provided.
*
* @param {number} width - Width that needs to be applied on the large video container.
* @param {number} height - Height that needs to be applied on the large video container.
* @returns {Function}
*/
export function resizeLargeVideo(width: number, height: number) {
return (dispatch: Dispatch<any>, getState: Function) => {
const state = getState();
const largeVideo = state['features/large-video'];
if (largeVideo) {
const largeVideoContainer = VideoLayout.getLargeVideo();
largeVideoContainer.updateContainerSize(width, height);
largeVideoContainer.resize();
}
};
}

View File

@@ -1,21 +0,0 @@
// @flow
import { toState } from '../base/redux';
/**
* Checks whether rtcstats is enabled or not.
*
* @param {Function|Object} stateful - The redux store or {@code getState} function.
* @returns {boolean}
*/
export function isRtcstatsEnabled(stateful: Function | Object) {
// TODO: Remove when rtcstats is fully cimpatible with mobile.
if (navigator.product === 'ReactNative') {
return false;
}
const state = toState(stateful);
const config = state['features/base/config'];
return config?.analytics?.rtcstatsEnabled ?? false;
}

View File

@@ -9,7 +9,6 @@ import { getLocalParticipant } from '../base/participants';
import { MiddlewareRegistry } from '../base/redux';
import RTCStats from './RTCStats';
import { isRtcstatsEnabled } from './functions';
import logger from './logger';
/**
@@ -26,7 +25,7 @@ MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case LIB_WILL_INIT: {
if (isRtcstatsEnabled(state)) {
if (analytics.rtcstatsEnabled) {
// RTCStats "proxies" WebRTC functions such as GUM and RTCPeerConnection by rewriting the global
// window functions. Because lib-jitsi-meet uses references to those functions that are taken on
// init, we need to add these proxies before it initializes, otherwise lib-jitsi-meet will use the
@@ -48,7 +47,7 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
case CONFERENCE_JOINED: {
if (isRtcstatsEnabled(state) && RTCStats.isInitialized()) {
if (analytics.rtcstatsEnabled && RTCStats.isInitialized()) {
// Once the conference started connect to the rtcstats server and send data.
try {
RTCStats.connect();

View File

@@ -1,11 +1,11 @@
// @flow
import * as StackBlur from 'stackblur-canvas';
import * as bodyPix from '@tensorflow-models/body-pix';
import {
CLEAR_TIMEOUT,
TIMEOUT_TICK,
SET_TIMEOUT,
CLEAR_INTERVAL,
INTERVAL_TIMEOUT,
SET_INTERVAL,
timerWorkerScript
} from './TimerWorker';
@@ -17,7 +17,6 @@ import {
export default class JitsiStreamBlurEffect {
_bpModel: Object;
_inputVideoElement: HTMLVideoElement;
_inputVideoCanvasElement: HTMLCanvasElement;
_onMaskFrameTimer: Function;
_maskFrameTimerWorker: Worker;
_maskInProgress: boolean;
@@ -44,7 +43,6 @@ export default class JitsiStreamBlurEffect {
this._outputCanvasElement = document.createElement('canvas');
this._outputCanvasElement.getContext('2d');
this._inputVideoElement = document.createElement('video');
this._inputVideoCanvasElement = document.createElement('canvas');
}
/**
@@ -55,8 +53,10 @@ export default class JitsiStreamBlurEffect {
* @returns {void}
*/
async _onMaskFrameTimer(response: Object) {
if (response.data.id === TIMEOUT_TICK) {
await this._renderMask();
if (response.data.id === INTERVAL_TIMEOUT) {
if (!this._maskInProgress) {
await this._renderMask();
}
}
}
@@ -67,53 +67,20 @@ export default class JitsiStreamBlurEffect {
* @returns {void}
*/
async _renderMask() {
if (!this._maskInProgress) {
this._maskInProgress = true;
this._bpModel.segmentPerson(this._inputVideoElement, {
internalResolution: 'low', // resized to 0.5 times of the original resolution before inference
maxDetections: 1, // max. number of person poses to detect per image
segmentationThreshold: 0.7, // represents probability that a pixel belongs to a person
flipHorizontal: false,
scoreThreshold: 0.2
}).then(data => {
this._segmentationData = data;
this._maskInProgress = false;
});
}
const inputCanvasCtx = this._inputVideoCanvasElement.getContext('2d');
inputCanvasCtx.drawImage(this._inputVideoElement, 0, 0);
const currentFrame = inputCanvasCtx.getImageData(
0,
0,
this._inputVideoCanvasElement.width,
this._inputVideoCanvasElement.height
);
if (this._segmentationData) {
const blurData = new ImageData(currentFrame.data.slice(), currentFrame.width, currentFrame.height);
StackBlur.imageDataRGB(blurData, 0, 0, currentFrame.width, currentFrame.height, 12);
for (let x = 0; x < this._outputCanvasElement.width; x++) {
for (let y = 0; y < this._outputCanvasElement.height; y++) {
const n = (y * this._outputCanvasElement.width) + x;
if (this._segmentationData.data[n] === 0) {
currentFrame.data[n * 4] = blurData.data[n * 4];
currentFrame.data[(n * 4) + 1] = blurData.data[(n * 4) + 1];
currentFrame.data[(n * 4) + 2] = blurData.data[(n * 4) + 2];
currentFrame.data[(n * 4) + 3] = blurData.data[(n * 4) + 3];
}
}
}
}
this._outputCanvasElement.getContext('2d').putImageData(currentFrame, 0, 0);
this._maskFrameTimerWorker.postMessage({
id: SET_TIMEOUT,
timeMs: 1000 / 30
this._maskInProgress = true;
this._segmentationData = await this._bpModel.segmentPerson(this._inputVideoElement, {
internalResolution: 'medium', // resized to 0.5 times of the original resolution before inference
maxDetections: 1, // max. number of person poses to detect per image
segmentationThreshold: 0.7 // represents probability that a pixel belongs to a person
});
this._maskInProgress = false;
bodyPix.drawBokehEffect(
this._outputCanvasElement,
this._inputVideoElement,
this._segmentationData,
12, // Constant for background blur, integer values between 0-20
7 // Constant for edge blur, integer values between 0-20
);
}
/**
@@ -143,16 +110,14 @@ export default class JitsiStreamBlurEffect {
this._outputCanvasElement.width = parseInt(width, 10);
this._outputCanvasElement.height = parseInt(height, 10);
this._inputVideoCanvasElement.width = parseInt(width, 10);
this._inputVideoCanvasElement.height = parseInt(height, 10);
this._inputVideoElement.width = parseInt(width, 10);
this._inputVideoElement.height = parseInt(height, 10);
this._inputVideoElement.autoplay = true;
this._inputVideoElement.srcObject = stream;
this._inputVideoElement.onloadeddata = () => {
this._maskFrameTimerWorker.postMessage({
id: SET_TIMEOUT,
timeMs: 1000 / 30
id: SET_INTERVAL,
timeMs: 1000 / parseInt(frameRate, 10)
});
};
@@ -166,7 +131,7 @@ export default class JitsiStreamBlurEffect {
*/
stopEffect() {
this._maskFrameTimerWorker.postMessage({
id: CLEAR_TIMEOUT
id: CLEAR_INTERVAL
});
this._maskFrameTimerWorker.terminate();

View File

@@ -1,34 +1,34 @@
/**
* SET_TIMEOUT constant is used to set interval and it is set in
* SET_INTERVAL constant is used to set interval and it is set in
* the id property of the request.data property. timeMs property must
* also be set. request.data example:
*
* {
* id: SET_TIMEOUT,
* id: SET_INTERVAL,
* timeMs: 33
* }
*/
export const SET_TIMEOUT = 1;
export const SET_INTERVAL = 1;
/**
* CLEAR_TIMEOUT constant is used to clear the interval and it is set in
* CLEAR_INTERVAL constant is used to clear the interval and it is set in
* the id property of the request.data property.
*
* {
* id: CLEAR_TIMEOUT
* id: CLEAR_INTERVAL
* }
*/
export const CLEAR_TIMEOUT = 2;
export const CLEAR_INTERVAL = 2;
/**
* TIMEOUT_TICK constant is used as response and it is set in the id property.
* INTERVAL_TIMEOUT constant is used as response and it is set in the id property.
*
* {
* id: TIMEOUT_TICK
* id: INTERVAL_TIMEOUT
* }
*/
export const TIMEOUT_TICK = 3;
export const INTERVAL_TIMEOUT = 3;
/**
* The following code is needed as string to create a URL from a Blob.
@@ -40,15 +40,15 @@ const code = `
onmessage = function(request) {
switch (request.data.id) {
case ${SET_TIMEOUT}: {
timer = setTimeout(() => {
postMessage({ id: ${TIMEOUT_TICK} });
case ${SET_INTERVAL}: {
timer = setInterval(() => {
postMessage({ id: ${INTERVAL_TIMEOUT} });
}, request.data.timeMs);
break;
}
case ${CLEAR_TIMEOUT}: {
case ${CLEAR_INTERVAL}: {
if (timer) {
clearTimeout(timer);
clearInterval(timer);
}
break;
}

View File

@@ -33,7 +33,6 @@ import {
import { connect, equals } from '../../../base/redux';
import { OverflowMenuItem } from '../../../base/toolbox/components';
import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
import { isVpaasMeeting } from '../../../billing-counter/functions';
import { VideoBlurButton } from '../../../blur';
import { CHAT_SIZE, ChatCounter, toggleChat } from '../../../chat';
import { EmbedMeetingDialog } from '../../../embed-meeting';
@@ -137,11 +136,6 @@ type Props = {
*/
_isGuest: boolean,
/**
* Whether or not the current meeting belongs to a JaaS user.
*/
_isVpaasMeeting: boolean,
/**
* The ID of the local participant.
*/
@@ -977,15 +971,6 @@ class Toolbox extends Component<Props, State> {
);
}
/**
* Returns true if the profile button is visible and false otherwise.
*
* @returns {boolean}
*/
_isEmbedMeetingVisible() {
return !this.props._isVpaasMeeting && this._shouldShowButton('embedmeeting');
}
/**
* Returns true if the profile button is visible and false otherwise.
*
@@ -1062,7 +1047,7 @@ class Toolbox extends Component<Props, State> {
key = 'stats'
onClick = { this._onToolbarOpenSpeakerStats }
text = { t('toolbar.speakerStats') } />,
this._isEmbedMeetingVisible()
this._shouldShowButton('embedmeeting')
&& <OverflowMenuItem
accessibilityLabel = { t('toolbar.accessibilityLabel.embedMeeting') }
icon = { IconCodeBlock }
@@ -1448,7 +1433,6 @@ function _mapStateToProps(state) {
_dialog: Boolean(state['features/base/dialog'].component),
_feedbackConfigured: Boolean(callStatsID),
_isGuest: state['features/base/jwt'].isGuest,
_isVpaasMeeting: isVpaasMeeting(state),
_fullScreen: fullScreen,
_tileViewEnabled: shouldDisplayTileView(state),
_localParticipantID: localParticipant.id,

View File

@@ -5,7 +5,7 @@ import debounce from 'lodash/debounce';
import { pinParticipant, getPinnedParticipant } from '../base/participants';
import { StateListenerRegistry, equals } from '../base/redux';
import { isFollowMeActive } from '../follow-me';
import { selectParticipant } from '../large-video/actions';
import { selectParticipant } from '../large-video';
import { setParticipantsWithScreenShare } from './actions';

View File

@@ -1,4 +1,3 @@
local new_throttle = require "util.throttle".create;
local st = require "util.stanza";
local token_util = module:require "token/util".new(module);
@@ -11,19 +10,12 @@ if token_util == nil then
return;
end
-- The maximum number of simultaneous calls,
-- and also the maximum number of new calls per minute that a session is allowed to create.
local limit_outgoing_calls;
local function load_config()
limit_outgoing_calls = module:get_option_number("max_number_outgoing_calls", -1);
end
load_config();
-- configuration to limit number of outgoing calls
local LIMIT_OUTGOING_CALLS = module:get_option_number("max_number_outgoing_calls", -1);
-- Header names to use to push extra data extracted from token, if any
local OUT_INITIATOR_USER_ATTR_NAME = "X-outbound-call-initiator-user";
local OUT_INITIATOR_GROUP_ATTR_NAME = "X-outbound-call-initiator-group";
local OUTGOING_CALLS_THROTTLE_INTERVAL = 60; -- if max_number_outgoing_calls is enabled it will be
-- the max number of outgoing calls a user can try for a minute
-- filters rayo iq in case of requested from not jwt authenticated sessions
-- or if the session has features in user context and it doesn't mention
@@ -61,21 +53,15 @@ module:hook("pre-iq/full", function(event)
end
-- now lets check any limits if configured
if limit_outgoing_calls > 0 then
if not session.dial_out_throttle then
module:log("debug", "Enabling dial-out throttle session=%s.", session);
session.dial_out_throttle = new_throttle(limit_outgoing_calls, OUTGOING_CALLS_THROTTLE_INTERVAL);
end
if not session.dial_out_throttle:poll(1) -- we first check the throttle so we can mark one incoming dial for the balance
or get_concurrent_outgoing_count(session.jitsi_meet_context_user["id"], session.jitsi_meet_context_group)
>= limit_outgoing_calls
then
module:log("warn",
"Filtering stanza dial, stanza:%s, outgoing calls limit reached", tostring(stanza));
session.send(st.error_reply(stanza, "cancel", "resource-constraint"));
return true;
end
if LIMIT_OUTGOING_CALLS > 0
and get_concurrent_outgoing_count(
session.jitsi_meet_context_user["id"],
session.jitsi_meet_context_group) >= LIMIT_OUTGOING_CALLS
then
module:log("warn",
"Filtering stanza dial, stanza:%s, outgoing calls limit reached", tostring(stanza));
session.send(st.error_reply(stanza, "cancel", "resource-constraint"));
return true;
end
-- now lets insert token information if any
@@ -177,4 +163,3 @@ function get_concurrent_outgoing_count(context_user, context_group)
return count;
end
module:hook_global('config-reloaded', load_config);

View File

@@ -142,8 +142,7 @@ const config = {
// value that is a mock (/index.js).
__filename: true,
// Provide some empty Node modules (required by olm).
crypto: 'empty',
// Provide an empty 'fs' module.
fs: 'empty'
},
optimization: {
@@ -191,7 +190,7 @@ module.exports = [
entry: {
'app.bundle': './app.js'
},
performance: getPerformanceHints(4 * 1024 * 1024)
performance: getPerformanceHints(4.5 * 1024 * 1024)
}),
Object.assign({}, config, {
entry: {