Compare commits

...

24 Commits

Author SHA1 Message Date
Jaya Allamsetty
d46f1bcd6e chore(deps) Update LJM, apply Firefox 117 fixes. 2023-09-22 11:50:31 -04:00
Horatiu Muresan
1d6d63906a fix(toolbar-buttons) Show tileview in toolbar when separate reactions button (#13810) 2023-09-08 14:51:54 +03:00
Gabriel Borlea
b3782ba4ef fix(video-select): remove video preview from device selection and fix video switch on mobile browsers (#13780)
* fix(video-select): remove video preview from device selection and fix video switch on android browsers

* simplify if statement

* add for all mobile devices the stop stream

* move mobile check to middleware

* code review
2023-09-06 17:14:34 +03:00
Horatiu Muresan
08f0a095f3 fix(disable-filmstrip) Fix disabling filmstrim through config
- there was a problem with pinning the participants from the Participants pane with the previous approach
2023-09-06 14:56:23 +03:00
Mihaela Dumitru
0a7446e1e7 fix(virtual-background) standardize options object (#13760) 2023-08-29 16:26:42 -05:00
Mihaela Dumitru
a07031cce7 fix(breakout-rooms) allow spaces when renaming (#13761) 2023-08-29 16:26:26 -05:00
Hristo Terezov
c908814381 chore(LJM): from release branch 2023-08-29 14:35:06 -05:00
Hristo Terezov
9f02bdd890 fix: Attempt to fix setSinkId failures. 2023-08-29 14:28:46 -05:00
Hristo Terezov
8a16fd5c1f fix(logger): Prevent JSON stringify errors 2023-08-29 14:24:40 -05:00
Horatiu Muresan
9493ebdd1d feat(filmstrip) Add config for disabling vertical filmstrip (#13752) 2023-08-28 14:47:54 +03:00
Mihaela Dumitru
e5bfe1c89e feat(dial-in) add sip audio only (#13714) (#13733) 2023-08-21 11:51:27 +03:00
Hristo Terezov
114c017f1d fix(webpack):Increase size limit for external_api 2023-08-18 13:22:48 -05:00
Horatiu Muresan
7287172bd3 fix(localFlipX) Fix localFlipX for large video (#13728)
- fixed case when localFlipX was taken from store on it`s value update, before the new value was set into store - so always taking the previous value instead of updated one
2023-08-18 17:51:32 +03:00
Horatiu Muresan
584bdd3137 feat(external-api) add command for setting camera facing mode (#13541)
- added command for setting the camera facing mode remotely
- enhanced toggleVideo command to optionally accept the facing mode
- fix(startSilent) do not create audio track when start silent
2023-08-18 17:51:32 +03:00
Horatiu Muresan
48a1724b19 chore(tileview) Add config for disabling tileview (#13692)
- show fixed number of toolbar buttons in toolbar (including custom buttons) instead of sending to overflow menu
2023-08-18 17:51:32 +03:00
Horatiu Muresan
2af41724bd feat(toolbar-buttons) Add optional background color (#13691) 2023-08-18 17:51:32 +03:00
Hristo Terezov
4d63807a31 fix(jitsi-local-storage):Is empty after reload
When using useHostPageLocalStorage on the iframe api and local storage
is not throwing error we were writting the passed data to the original
local storage and then switching to the dummy local storage from
jitsiLocalStorage.
2023-08-16 21:51:59 -05:00
Robert Pintilii
10b10b5f40 fix(chat) Only display private message dialog for active participants (#13708) 2023-08-14 13:39:54 -05:00
Mihaela Dumitru
7921722039 fix(toolbar) disable the profile button based on the toolbar logic (#13696) (#13706) 2023-08-14 11:06:16 +03:00
Jaya Allamsetty
7c6decb9a0 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1675.0.0+0cc323d9...v1676.0.0+486efad8
2023-08-10 16:41:48 -04:00
Mihaela Dumitru
6e7452239f fix(shortcuts) toggle value based on current state (#13685) (#13690) 2023-08-10 10:44:26 +03:00
Jaya Allamsetty
34180a7997 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1674.0.0+648d0ddc...v1675.0.0+0cc323d9
2023-08-09 11:12:43 -04:00
Saúl Ibarra Corretgé
3298435e7d fix(misc) use safeJsonParse from js-utils 2023-08-09 10:42:39 -04:00
Jaya Allamsetty
8b28b39c03 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1672.0.0+cce452f8...v1674.0.0+648d0ddc
2023-08-09 10:41:04 -04:00
57 changed files with 557 additions and 237 deletions

View File

@@ -441,7 +441,7 @@ export default {
// Always get a handle on the audio input device so that we have statistics (such as "No audio input" or
// "Are you trying to speak?" ) even if the user joins the conference muted.
const initialDevices = config.disableInitialGUM ? [] : [ MEDIA_TYPE.AUDIO ];
const initialDevices = config.startSilent || config.disableInitialGUM ? [] : [ MEDIA_TYPE.AUDIO ];
const requestedAudio = !config.disableInitialGUM;
let requestedVideo = false;

View File

@@ -871,7 +871,7 @@ var config = {
// customParticipantMenuButtons: [],
// An array with custom option buttons for the toolbar
// type: Array<{ icon: string; id: string; text: string; }>
// type: Array<{ icon: string; id: string; text: string; backgroundColor?: string; }>
// customToolbarButtons: [],
// Stats
@@ -1571,6 +1571,8 @@ var config = {
// disableFilmstripAutohiding: false,
// filmstrip: {
// // Disable the vertical/horizonal filmstrip.
// disabled: false,
// // Disables user resizable filmstrip. Also, allows configuration of the filmstrip
// // (width, tiles aspect ratios) through the interfaceConfig options.
// disableResizable: false,
@@ -1593,6 +1595,8 @@ var config = {
// Tile view related config options.
// tileView: {
// // Whether tileview should be disabled.
// disabled: false,
// // The optimal number of tiles that are going to be shown in tile view. Depending on the screen size it may
// // not be possible to show the exact number of participants specified here.
// numberOfVisibleTiles: 25,

View File

@@ -269,6 +269,8 @@
"addMeetingNote": "Add a note about this meeting",
"addOptionalNote": "Add a note (optional):",
"allow": "Allow",
"allowToggleCameraDialog": "Do you allow {{initiatorName}} to toggle your camera facing mode?",
"allowToggleCameraTitle": "Allow toggle camera?",
"alreadySharedVideoMsg": "Another participant is already sharing a video. This conference allows only one shared video at a time.",
"alreadySharedVideoTitle": "Only one shared video is allowed at a time",
"applicationWindow": "Application window",
@@ -530,6 +532,7 @@
"password": "$t(lockRoomPasswordUppercase): ",
"reachedLimit": "You have reached the limit of your plan.",
"sip": "SIP address",
"sipAudioOnly": "SIP audio only address",
"title": "Share",
"tooltip": "Share link and dial-in info for this meeting",
"upgradeOptions": "Please check the upgrade options on"

View File

@@ -50,8 +50,8 @@ import {
} from '../../react/features/base/participants/functions';
import { updateSettings } from '../../react/features/base/settings/actions';
import { getDisplayName } from '../../react/features/base/settings/functions.web';
import { toggleCamera } from '../../react/features/base/tracks/actions.any';
import { isToggleCameraEnabled } from '../../react/features/base/tracks/functions';
import { setCameraFacingMode } from '../../react/features/base/tracks/actions.web';
import { CAMERA_FACING_MODE_MESSAGE } from '../../react/features/base/tracks/constants';
import {
autoAssignToBreakoutRooms,
closeBreakoutRoom,
@@ -395,12 +395,8 @@ function initCommands() {
sendAnalytics(createApiEvent('film.strip.resize'));
APP.store.dispatch(resizeFilmStrip(options.width));
},
'toggle-camera': () => {
if (!isToggleCameraEnabled(APP.store.getState())) {
return;
}
APP.store.dispatch(toggleCamera());
'toggle-camera': facingMode => {
APP.store.dispatch(setCameraFacingMode(facingMode));
},
'toggle-camera-mirror': () => {
const state = APP.store.getState();
@@ -529,6 +525,18 @@ function initCommands() {
logger.error('Failed sending endpoint text message', err);
}
},
'send-camera-facing-mode-message': (to, facingMode) => {
if (!to) {
logger.warn('Participant id not set');
return;
}
APP.conference.sendEndpointMessage(to, {
name: CAMERA_FACING_MODE_MESSAGE,
facingMode
});
},
'overwrite-names': participantList => {
logger.debug('Overwrite names command received');
@@ -1133,7 +1141,11 @@ class API {
*/
_sendEvent(event = {}) {
if (this._enabled) {
transport.sendEvent(event);
try {
transport.sendEvent(event);
} catch (error) {
logger.error('Failed to send and IFrame API event', error);
}
}
}
@@ -1467,11 +1479,43 @@ class API {
* @param {Array<string>} args - Array of strings composing the log message.
* @returns {void}
*/
notifyLog(logLevel, args) {
notifyLog(logLevel, args = []) {
if (!Array.isArray(args)) {
logger.error('notifyLog received wrong argument types!');
return;
}
// Trying to convert arguments to strings. Otherwise in order to send the event the arguments will be formatted
// with JSON.stringify which can throw an error because of circular objects and we will lose the whole log.
const formattedArguments = [];
args.forEach(arg => {
let formattedArgument = '';
if (arg instanceof Error) {
formattedArgument += `${arg.toString()}: ${arg.stack}`;
} else if (typeof arg === 'object') {
// NOTE: The non-enumerable properties of the objects wouldn't be included in the string after
// JSON.strigify. For example Map instance will be translated to '{}'. So I think we have to eventually
// do something better for parsing the arguments. But since this option for strigify is part of the
// public interface and I think it could be useful in some cases I will it for now.
try {
formattedArgument += JSON.stringify(arg);
} catch (error) {
formattedArgument += arg;
}
} else {
formattedArgument += arg;
}
formattedArguments.push(formattedArgument);
});
this._sendEvent({
name: 'log',
logLevel,
args
args: formattedArguments
});
}

View File

@@ -54,6 +54,7 @@ const commands = {
removeBreakoutRoom: 'remove-breakout-room',
resizeFilmStrip: 'resize-film-strip',
resizeLargeVideo: 'resize-large-video',
sendCameraFacingMode: 'send-camera-facing-mode-message',
sendChatMessage: 'send-chat-message',
sendEndpointTextMessage: 'send-endpoint-text-message',
sendParticipantToRoom: 'send-participant-to-room',
@@ -111,6 +112,7 @@ const events = {
'data-channel-opened': 'dataChannelOpened',
'device-list-changed': 'deviceListChanged',
'display-name-change': 'displayNameChange',
'dominant-speaker-changed': 'dominantSpeakerChanged',
'email-change': 'emailChange',
'error-occurred': 'errorOccurred',
'endpoint-text-message-received': 'endpointTextMessageReceived',
@@ -152,7 +154,6 @@ const events = {
'video-mute-status-changed': 'videoMuteStatusChanged',
'video-quality-changed': 'videoQualityChanged',
'screen-sharing-status-changed': 'screenSharingStatusChanged',
'dominant-speaker-changed': 'dominantSpeakerChanged',
'subject-change': 'subjectChange',
'suspend-detected': 'suspendDetected',
'tile-view-changed': 'tileViewChanged',

View File

@@ -1,6 +1,7 @@
/* global APP, interfaceConfig */
/* eslint-disable no-unused-vars */
import Logger from '@jitsi/logger';
import $ from 'jquery';
import React from 'react';
import ReactDOM from 'react-dom';
@@ -23,6 +24,8 @@ export const VIDEO_CONTAINER_TYPE = 'camera';
// Corresponds to animation duration from the animatedFadeIn and animatedFadeOut CSS classes.
const FADE_DURATION_MS = 300;
const logger = Logger.getLogger(__filename);
/**
* Returns an array of the video dimensions, so that it keeps it's aspect
* ratio and fits available area with it's larger dimension. This method
@@ -489,7 +492,9 @@ export class VideoContainer extends LargeContainer {
}
if (this.video) {
stream.attach(this.video);
stream.attach(this.video).catch(error => {
logger.error(`Attaching the remote track ${stream} has failed with `, error);
});
// Ensure large video gets play() called on it when a new stream is attached to it. This is necessary in the
// case of Safari as autoplay doesn't kick-in automatically on Safari 15 and newer versions.

View File

@@ -23,11 +23,8 @@ const VideoLayout = {
/**
* Handler for local flip X changed event.
*/
onLocalFlipXChanged() {
onLocalFlipXChanged(localFlipX) {
if (largeVideo) {
const { store } = APP;
const { localFlipX } = store.getState()['features/base/settings'];
largeVideo.onLocalFlipXChange(localFlipX);
}
},

90
package-lock.json generated
View File

@@ -16,10 +16,9 @@
"@giphy/js-fetch-api": "4.7.1",
"@giphy/react-components": "6.8.1",
"@giphy/react-native-sdk": "2.3.0",
"@hapi/bourne": "2.0.0",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.14/jitsi-excalidraw-0.0.14.tgz",
"@jitsi/js-utils": "2.0.6",
"@jitsi/logger": "2.0.0",
"@jitsi/js-utils": "2.1.3",
"@jitsi/logger": "2.0.2",
"@jitsi/rnnoise-wasm": "0.1.0",
"@jitsi/rtcstats": "9.5.1",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
@@ -60,7 +59,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1672.0.0+cce452f8/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://git@github.com/jitsi/lib-jitsi-meet#886cb6bb0cab43b8b23378ace4a63128ffcf90d2",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -2949,9 +2948,9 @@
}
},
"node_modules/@hapi/bourne": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz",
"integrity": "sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg=="
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz",
"integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w=="
},
"node_modules/@hapi/hoek": {
"version": "9.3.0",
@@ -3110,10 +3109,11 @@
}
},
"node_modules/@jitsi/js-utils": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.0.6.tgz",
"integrity": "sha512-Z5cy1d8JmymAK5O0AOtNQFDqbKictcnafFzDTRxYK7Dcf62GSVlxoHizAhoUowilb+HcZC0W3YA3+NqLtMM//w==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.1.3.tgz",
"integrity": "sha512-fXoNLu2JHQGPgzjCDG5qWPPStiXI+AxSzu1JIVY4dMULIFsw4a+doMgcjTIlSfB393J2xccKQYwZSejZUn9MEw==",
"dependencies": {
"@hapi/bourne": "^3.0.0",
"bowser": "2.7.0",
"js-md5": "0.7.3"
}
@@ -3124,9 +3124,9 @@
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
},
"node_modules/@jitsi/logger": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.0.tgz",
"integrity": "sha512-QZE0NpI/GKRdZK0vhuyFYWr4XkCz4slihkSfy6RTszjj/YEHZKIV7yGJo6Hbs3kYI2h5v7apoy+h2WCOMumPJw=="
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.2.tgz",
"integrity": "sha512-qwbpRwuwkBFgh0F5jivq/5fAm46yVoXURc5LCklEs8lAShYVangFEXKW7RLpZuZ5nQnrHrlvU8MswQNREmvahg=="
},
"node_modules/@jitsi/rnnoise-wasm": {
"version": "0.1.0",
@@ -12770,13 +12770,13 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1672.0.0+cce452f8/lib-jitsi-meet.tgz",
"integrity": "sha512-5c67mHgImndnt6ZgpHWXXZOEat94xpl9d9Rf9gSppMXFAMDAcYQF0M5SEfkjfZpKbtjG8Ydf+xpVrjF3Cqd4/w==",
"resolved": "git+https://git@github.com/jitsi/lib-jitsi-meet.git#886cb6bb0cab43b8b23378ace4a63128ffcf90d2",
"integrity": "sha512-w5H6uBJ6P32J82rIOve0Vj1qhsqOELJGdH0Garl/tsLzs1IwDhATcVBd1i3IMQNOHDEwt98SGm+hlm7BDnF2Cg==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.0.0",
"@jitsi/logger": "2.0.0",
"@jitsi/js-utils": "2.1.3",
"@jitsi/logger": "2.0.2",
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
"@jitsi/sdp-simulcast": "0.4.0",
"@testrtc/watchrtc-sdk": "1.36.3",
@@ -12797,15 +12797,6 @@
"webrtc-adapter": "8.1.1"
}
},
"node_modules/lib-jitsi-meet/node_modules/@jitsi/js-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.0.0.tgz",
"integrity": "sha512-Rk1JFGdXEJ5+eALVRTMohfn3pdMDQqlCJQEkCMLXKlCpEo+JhsOrB4KzlPo1rV9U8PnRfrf0j5N9uf/0C2a8Gw==",
"dependencies": {
"bowser": "2.7.0",
"js-md5": "0.7.3"
}
},
"node_modules/lib-jitsi-meet/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -12888,11 +12879,6 @@
"node": ">=8"
}
},
"node_modules/lib-jitsi-meet/node_modules/js-md5": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
},
"node_modules/lib-jitsi-meet/node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
@@ -21915,9 +21901,9 @@
}
},
"@hapi/bourne": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz",
"integrity": "sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg=="
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz",
"integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w=="
},
"@hapi/hoek": {
"version": "9.3.0",
@@ -22031,10 +22017,11 @@
"integrity": "sha512-iK7p7i6qJFOkjTVZhWDvurDW1u+eMoOhAVgpab9CZEqCTX+W4Ih4AOPrUpf+mjaAHK5XqmQZSc5nVEpMg+xIGQ=="
},
"@jitsi/js-utils": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.0.6.tgz",
"integrity": "sha512-Z5cy1d8JmymAK5O0AOtNQFDqbKictcnafFzDTRxYK7Dcf62GSVlxoHizAhoUowilb+HcZC0W3YA3+NqLtMM//w==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.1.3.tgz",
"integrity": "sha512-fXoNLu2JHQGPgzjCDG5qWPPStiXI+AxSzu1JIVY4dMULIFsw4a+doMgcjTIlSfB393J2xccKQYwZSejZUn9MEw==",
"requires": {
"@hapi/bourne": "^3.0.0",
"bowser": "2.7.0",
"js-md5": "0.7.3"
},
@@ -22047,9 +22034,9 @@
}
},
"@jitsi/logger": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.0.tgz",
"integrity": "sha512-QZE0NpI/GKRdZK0vhuyFYWr4XkCz4slihkSfy6RTszjj/YEHZKIV7yGJo6Hbs3kYI2h5v7apoy+h2WCOMumPJw=="
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.2.tgz",
"integrity": "sha512-qwbpRwuwkBFgh0F5jivq/5fAm46yVoXURc5LCklEs8lAShYVangFEXKW7RLpZuZ5nQnrHrlvU8MswQNREmvahg=="
},
"@jitsi/rnnoise-wasm": {
"version": "0.1.0",
@@ -29289,11 +29276,12 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1672.0.0+cce452f8/lib-jitsi-meet.tgz",
"integrity": "sha512-5c67mHgImndnt6ZgpHWXXZOEat94xpl9d9Rf9gSppMXFAMDAcYQF0M5SEfkjfZpKbtjG8Ydf+xpVrjF3Cqd4/w==",
"version": "git+https://git@github.com/jitsi/lib-jitsi-meet.git#886cb6bb0cab43b8b23378ace4a63128ffcf90d2",
"integrity": "sha512-w5H6uBJ6P32J82rIOve0Vj1qhsqOELJGdH0Garl/tsLzs1IwDhATcVBd1i3IMQNOHDEwt98SGm+hlm7BDnF2Cg==",
"from": "lib-jitsi-meet@https://git@github.com/jitsi/lib-jitsi-meet#886cb6bb0cab43b8b23378ace4a63128ffcf90d2",
"requires": {
"@jitsi/js-utils": "2.0.0",
"@jitsi/logger": "2.0.0",
"@jitsi/js-utils": "2.1.3",
"@jitsi/logger": "2.0.2",
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
"@jitsi/sdp-simulcast": "0.4.0",
"@testrtc/watchrtc-sdk": "1.36.3",
@@ -29314,15 +29302,6 @@
"webrtc-adapter": "8.1.1"
},
"dependencies": {
"@jitsi/js-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.0.0.tgz",
"integrity": "sha512-Rk1JFGdXEJ5+eALVRTMohfn3pdMDQqlCJQEkCMLXKlCpEo+JhsOrB4KzlPo1rV9U8PnRfrf0j5N9uf/0C2a8Gw==",
"requires": {
"bowser": "2.7.0",
"js-md5": "0.7.3"
}
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -29381,11 +29360,6 @@
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"js-md5": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
},
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",

View File

@@ -22,10 +22,9 @@
"@giphy/js-fetch-api": "4.7.1",
"@giphy/react-components": "6.8.1",
"@giphy/react-native-sdk": "2.3.0",
"@hapi/bourne": "2.0.0",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.14/jitsi-excalidraw-0.0.14.tgz",
"@jitsi/js-utils": "2.0.6",
"@jitsi/logger": "2.0.0",
"@jitsi/js-utils": "2.1.3",
"@jitsi/logger": "2.0.2",
"@jitsi/rnnoise-wasm": "0.1.0",
"@jitsi/rtcstats": "9.5.1",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
@@ -66,7 +65,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1672.0.0+cce452f8/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://git@github.com/jitsi/lib-jitsi-meet#886cb6bb0cab43b8b23378ace4a63128ffcf90d2",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",

View File

@@ -1,4 +1,4 @@
type ToolbarButtons = 'camera' |
export type ToolbarButton = 'camera' |
'chat' |
'closedcaptions' |
'desktop' |
@@ -15,6 +15,9 @@ type ToolbarButtons = 'camera' |
'linktosalesforce' |
'livestreaming' |
'microphone' |
'mute-everyone' |
'mute-video-everyone' |
'noisesuppression' |
'participants-pane' |
'profile' |
'raisehand' |
@@ -30,6 +33,7 @@ type ToolbarButtons = 'camera' |
'tileview' |
'toggle-camera' |
'videoquality' |
'whiteboard' |
'__end';
type ButtonsWithNotifyClick = 'camera' |
@@ -270,7 +274,7 @@ export interface IConfig {
};
corsAvatarURLs?: Array<string>;
customParticipantMenuButtons?: Array<{ icon: string; id: string; text: string; }>;
customToolbarButtons?: Array<{ icon: string; id: string; text: string; }>;
customToolbarButtons?: Array<{ backgroundColor?: string; icon: string; id: string; text: string; }>;
deeplinking?: IDeeplinkingConfig;
defaultLanguage?: string;
defaultLocalDisplayName?: string;
@@ -397,6 +401,7 @@ export interface IConfig {
disableResizable?: boolean;
disableStageFilmstrip?: boolean;
disableTopPanel?: boolean;
disabled?: boolean;
minParticipantCountForTopPanel?: number;
};
firefox_fake_device?: string;
@@ -573,12 +578,13 @@ export interface IConfig {
testMode?: boolean;
};
tileView?: {
disabled?: boolean;
numberOfVisibleTiles?: number;
};
tokenAuthUrl?: string;
tokenAuthUrlAutoRedirect?: string;
tokenLogoutUrl?: string;
toolbarButtons?: Array<ToolbarButtons>;
toolbarButtons?: Array<ToolbarButton>;
toolbarConfig?: {
alwaysVisible?: boolean;
autoHideWhileChatIsOpen?: boolean;

View File

@@ -1,3 +1,5 @@
import { ToolbarButton } from './configType';
/**
* The prefix of the {@code localStorage} key into which {@link storeConfig}
* stores and from which {@link restoreConfig} restores.
@@ -13,7 +15,7 @@ export const _CONFIG_STORE_PREFIX = 'config.js';
* @protected
* @type Array<string>
*/
export const TOOLBAR_BUTTONS = [
export const TOOLBAR_BUTTONS: ToolbarButton[] = [
'camera',
'chat',
'closedcaptions',

View File

@@ -1,8 +1,8 @@
// @ts-expect-error
import Bourne from '@hapi/bourne';
// eslint-disable-next-line lines-around-comment
// @ts-expect-error
// @ts-ignore
import { jitsiLocalStorage } from '@jitsi/js-utils';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import { safeJsonParse } from '@jitsi/js-utils/json';
import _ from 'lodash';
import { IReduxState } from '../../app/types';
@@ -223,7 +223,7 @@ export function restoreConfig(baseURL: string) {
if (config) {
try {
return Bourne.parse(config) || undefined;
return safeJsonParse(config) || undefined;
} catch (e) {
// Somehow incorrect data ended up in the storage. Clean it up.
jitsiLocalStorage.removeItem(key);

View File

@@ -7,7 +7,8 @@ import {
IDeeplinkingConfig,
IDeeplinkingMobileConfig,
IDeeplinkingPlatformConfig,
NotifyClickButton
NotifyClickButton,
ToolbarButton
} from './configType';
import { TOOLBAR_BUTTONS } from './constants';
@@ -46,7 +47,7 @@ export function getToolbarButtons(state: IReduxState): Array<string> {
const buttons = Array.isArray(toolbarButtons) ? toolbarButtons : TOOLBAR_BUTTONS;
if (customButtons) {
buttons.push(...customButtons);
buttons.push(...customButtons as ToolbarButton[]);
}
return buttons;

View File

@@ -16,8 +16,10 @@ import {
IDeeplinkingConfig,
IDeeplinkingMobileConfig,
IDeeplinkingPlatformConfig,
IMobileDynamicLink
IMobileDynamicLink,
ToolbarButton
} from './configType';
import { TOOLBAR_BUTTONS } from './constants';
import { _cleanupConfig, _setDeeplinkingDefaults } from './functions';
/**
@@ -546,6 +548,11 @@ function _translateLegacyConfig(oldValue: IConfig) {
};
}
if (oldValue.disableProfile) {
newValue.toolbarButtons = (newValue.toolbarButtons || TOOLBAR_BUTTONS)
.filter((button: ToolbarButton) => button !== 'profile');
}
_setDeeplinkingDefaults(newValue.deeplinking as IDeeplinkingConfig);
return newValue;

View File

@@ -9,10 +9,12 @@ import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
import { replaceAudioTrackById, replaceVideoTrackById, setDeviceStatusWarning } from '../../prejoin/actions';
import { isPrejoinPageVisible } from '../../prejoin/functions';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app/actionTypes';
import { isMobileBrowser } from '../environment/utils';
import JitsiMeetJS, { JitsiMediaDevicesEvents, JitsiTrackErrors } from '../lib-jitsi-meet';
import { MEDIA_TYPE } from '../media/constants';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import { updateSettings } from '../settings/actions';
import { getLocalTrack } from '../tracks/functions';
import {
CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
@@ -182,13 +184,20 @@ MiddlewareRegistry.register(store => next => action => {
APP.UI.emitEvent(UIEvents.AUDIO_DEVICE_CHANGED, action.deviceId);
}
break;
case SET_VIDEO_INPUT_DEVICE:
case SET_VIDEO_INPUT_DEVICE: {
const localTrack = getLocalTrack(store.getState()['features/base/tracks'], MEDIA_TYPE.VIDEO);
// on mobile devices the video stream has to be stopped before replacing it
if (isMobileBrowser() && localTrack && !localTrack.muted) {
localTrack.jitsiTrack.stopStream();
}
if (isPrejoinPageVisible(store.getState())) {
store.dispatch(replaceVideoTrackById(action.deviceId));
} else {
APP.UI.emitEvent(UIEvents.VIDEO_DEVICE_CHANGED, action.deviceId);
}
break;
}
case UPDATE_DEVICE_LIST:
logDeviceList(groupDevicesByKind(action.devices));
if (areDeviceLabelsInitialized(store.getState())) {

View File

@@ -18,3 +18,4 @@ export function isMobileBrowser() {
export function isIosMobileBrowser() {
return Platform.OS === 'ios';
}

View File

@@ -1,8 +1,8 @@
// @ts-expect-error
import Bourne from '@hapi/bourne';
// eslint-disable-next-line lines-around-comment
// @ts-expect-error
// @ts-ignore
import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import { safeJsonParse } from '@jitsi/js-utils/json';
import { browser } from '../lib-jitsi-meet';
import { inIframe } from '../util/iframeUtils';
@@ -17,6 +17,8 @@ import logger from './logger';
* @returns {void}
*/
function onFakeLocalStorageChanged() {
console.error(jitsiLocalStorage.serialize([ 'jitsiLocalStorage' ]));
APP.API.notifyLocalStorageChanged(jitsiLocalStorage.serialize([ 'jitsiLocalStorage' ]));
}
@@ -61,7 +63,14 @@ function setupJitsiLocalStorage() {
if (shouldUseHostPageLocalStorage(urlParams)) {
try {
const localStorageContent = Bourne.parse(urlParams['appData.localStorageContent']);
const localStorageContent = safeJsonParse(urlParams['appData.localStorageContent']);
// We need to disable the local storage before setting the data in case the browser local storage doesn't
// throw exception (in some cases when this happens the local storage may be cleared for every session.
// Example: when loading meet from cross-domain with the IFrame API with Brave with the default
// configuration). Otherwise we will set the data in the browser local storage and then switch to the dummy
// local storage from jitsiLocalStorage and we will loose the data.
jitsiLocalStorage.setLocalStorageDisabled(true);
if (typeof localStorageContent === 'object') {
Object.keys(localStorageContent).forEach(key => {
@@ -72,7 +81,6 @@ function setupJitsiLocalStorage() {
logger.error('Can\'t parse localStorageContent.', error);
}
jitsiLocalStorage.setLocalStorageDisabled(true);
jitsiLocalStorage.on('changed', onFakeLocalStorageChanged);
}
}

View File

@@ -1,5 +1,5 @@
// @ts-expect-error
import Bourne from '@hapi/bourne';
// @ts-ignore
import { safeJsonParse } from '@jitsi/js-utils/json';
import { NativeModules } from 'react-native';
import { loadScript } from '../util/loadScript.native';
@@ -20,7 +20,7 @@ export async function loadConfig(url: string): Promise<Object> {
try {
const configTxt = await loadScript(url, 10 * 1000 /* Timeout in ms */, true /* skipeval */);
const configJson = await JavaScriptSandbox.evaluate(`${configTxt}\nJSON.stringify(config);`);
const config = Bourne.parse(configJson);
const config = safeJsonParse(configJson);
if (typeof config !== 'object') {
throw new Error('config is not an object');

View File

@@ -120,9 +120,7 @@ export default class JitsiMeetLogStorage {
conference.sendApplicationLog(logMessage);
} catch (error) {
// NOTE console is intentional here
console.error(
`Failed to store the logs, msg length: ${logMessage.length}`
+ `error: ${JSON.stringify(error)}`);
console.error(`Failed to store the logs, msg length: ${logMessage.length} error:`, error);
}
}
}

View File

@@ -51,7 +51,7 @@ class AudioTrack extends Component<IProps> {
/**
* Reference to the HTML audio element, stored until the file is ready.
*/
_ref: HTMLAudioElement | null;
_ref: React.RefObject<HTMLAudioElement>;
/**
* The current timeout ID for play() retries.
@@ -80,7 +80,7 @@ class AudioTrack extends Component<IProps> {
// Bind event handlers so they are only bound once for every instance.
this._errorHandler = this._errorHandler.bind(this);
this._setRef = this._setRef.bind(this);
this._ref = React.createRef();
this._play = this._play.bind(this);
}
@@ -94,19 +94,22 @@ class AudioTrack extends Component<IProps> {
componentDidMount() {
this._attachTrack(this.props.audioTrack);
if (this._ref) {
if (this._ref?.current) {
const audio = this._ref?.current;
const { _muted, _volume } = this.props;
if (typeof _volume === 'number') {
this._ref.volume = _volume;
audio.volume = _volume;
}
if (typeof _muted === 'boolean') {
this._ref.muted = _muted;
audio.muted = _muted;
}
// @ts-ignore
this._ref.addEventListener('error', this._errorHandler);
audio.addEventListener('error', this._errorHandler);
} else { // This should never happen
logger.error(`The react reference is null for AudioTrack ${this.props?.id}`);
}
}
@@ -121,7 +124,7 @@ class AudioTrack extends Component<IProps> {
this._detachTrack(this.props.audioTrack);
// @ts-ignore
this._ref?.removeEventListener('error', this._errorHandler);
this._ref?.current?.removeEventListener('error', this._errorHandler);
}
/**
@@ -141,19 +144,25 @@ class AudioTrack extends Component<IProps> {
this._attachTrack(nextProps.audioTrack);
}
if (this._ref) {
const currentVolume = this._ref.volume;
if (this._ref?.current) {
const audio = this._ref?.current;
const currentVolume = audio.volume;
const nextVolume = nextProps._volume;
if (typeof nextVolume === 'number' && !isNaN(nextVolume) && currentVolume !== nextVolume) {
this._ref.volume = nextVolume;
if (nextVolume === 0) {
logger.debug(`Setting audio element ${nextProps?.id} volume to 0`);
}
audio.volume = nextVolume;
}
const currentMuted = this._ref.muted;
const currentMuted = audio.muted;
const nextMuted = nextProps._muted;
if (typeof nextMuted === 'boolean' && currentMuted !== nextMuted) {
this._ref.muted = nextMuted;
logger.debug(`Setting audio element ${nextProps?.id} muted to true`);
audio.muted = nextMuted;
}
}
@@ -173,7 +182,7 @@ class AudioTrack extends Component<IProps> {
<audio
autoPlay = { autoPlay }
id = { id }
ref = { this._setRef } />
ref = { this._ref } />
);
}
@@ -185,12 +194,29 @@ class AudioTrack extends Component<IProps> {
* @returns {void}
*/
_attachTrack(track?: ITrack) {
const { id } = this.props;
if (!track?.jitsiTrack) {
logger.warn(`Attach is called on audio element ${id} without tracks passed!`);
return;
}
track.jitsiTrack.attach(this._ref);
this._play();
if (!this._ref?.current) {
logger.warn(`Attempting to attach track ${track?.jitsiTrack} on AudioTrack ${id} without reference!`);
return;
}
track.jitsiTrack.attach(this._ref.current)
.catch((error: Error) => {
logger.error(
`Attaching the remote track ${track.jitsiTrack} to video with id ${id} has failed with `,
error);
})
.finally(() => {
this._play();
});
}
/**
@@ -202,10 +228,10 @@ class AudioTrack extends Component<IProps> {
* @returns {void}
*/
_detachTrack(track?: ITrack) {
if (this._ref && track && track.jitsiTrack) {
if (this._ref?.current && track && track.jitsiTrack) {
clearTimeout(this._playTimeout);
this._playTimeout = undefined;
track.jitsiTrack.detach(this._ref);
track.jitsiTrack.detach(this._ref.current);
}
}
@@ -229,18 +255,20 @@ class AudioTrack extends Component<IProps> {
* @returns {void}
*/
_play(retries = 0) {
if (!this._ref) {
const { autoPlay, id } = this.props;
if (!this._ref?.current) {
// nothing to play.
logger.warn(`Attempting to call play on AudioTrack ${id} without reference!`);
return;
}
const { autoPlay, id } = this.props;
if (autoPlay) {
// Ensure the audio gets play() called on it. This may be necessary in the
// case where the local video container was moved and re-attached, in which
// case the audio may not autoplay.
this._ref.play()
this._ref.current.play()
.then(() => {
if (retries !== 0) {
// success after some failures
@@ -249,7 +277,7 @@ class AudioTrack extends Component<IProps> {
logger.info(`Successfully played audio track! retries: ${retries}`);
}
}, e => {
logger.error(`Failed to play audio track! retry: ${retries} ; Error: ${e}`);
logger.error(`Failed to play audio track on audio element ${id}! retry: ${retries} ; Error:`, e);
if (retries < 3) {
this._playTimeout = window.setTimeout(() => this._play(retries + 1), 1000);
@@ -264,17 +292,6 @@ class AudioTrack extends Component<IProps> {
});
}
}
/**
* Sets the reference to the HTML audio element.
*
* @param {HTMLAudioElement} audioElement - The HTML audio element instance.
* @private
* @returns {void}
*/
_setRef(audioElement: HTMLAudioElement | null) {
this._ref = audioElement;
}
}
/**

View File

@@ -1,6 +1,7 @@
import React, { Component, ReactEventHandler } from 'react';
import { ITrack } from '../../../tracks/types';
import logger from '../../logger';
/**
* The type of the React {@code Component} props of {@link Video}.
@@ -227,13 +228,13 @@ class Video extends Component<IProps> {
this._videoElement.onplaying = this._onVideoPlaying;
}
this._attachTrack(this.props.videoTrack);
this._attachTrack(this.props.videoTrack).finally(() => {
if (this._videoElement && this.props.autoPlay) {
// Ensure the video gets play() called on it. This may be necessary in the
// case where the local video container was moved and re-attached, in which
// case video does not autoplay.
if (this._videoElement && this.props.autoPlay) {
// Ensure the video gets play() called on it. This may be necessary in the
// case where the local video container was moved and re-attached, in which
// case video does not autoplay.
this._videoElement.play()
this._videoElement.play()
.catch(error => {
// Prevent uncaught "DOMException: The play() request was interrupted by a new load request"
// when video playback takes long to start and it starts after the component was unmounted.
@@ -241,7 +242,8 @@ class Video extends Component<IProps> {
throw error;
}
});
}
}
});
}
/**
@@ -271,7 +273,9 @@ class Video extends Component<IProps> {
if (currentJitsiTrack !== nextJitsiTrack) {
this._detachTrack(this.props.videoTrack);
this._attachTrack(nextProps.videoTrack);
this._attachTrack(nextProps.videoTrack).catch((_error: Error) => {
// Ignore the error. We are already logging it.
});
}
if (this.props.style !== nextProps.style || this.props.className !== nextProps.className) {
@@ -321,11 +325,22 @@ class Video extends Component<IProps> {
* @returns {void}
*/
_attachTrack(videoTrack?: Partial<ITrack>) {
const { id } = this.props;
if (!videoTrack?.jitsiTrack) {
return;
logger.warn(`Attach is called on video element ${id} without tracks passed!`);
// returning Promise.resolve just keep the previous logic.
// TODO: Check if it make sense to call play on this element or we can just return promise.reject().
return Promise.resolve();
}
videoTrack.jitsiTrack.attach(this._videoElement);
return videoTrack.jitsiTrack.attach(this._videoElement)
.catch((error: Error) => {
logger.error(
`Attaching the remote track ${videoTrack.jitsiTrack} to video with id ${id} has failed with `,
error);
});
}
/**

View File

@@ -1,8 +1,8 @@
// @ts-expect-error
import Bourne from '@hapi/bourne';
// eslint-disable-next-line lines-around-comment
// @ts-expect-error
// @ts-ignore
import { jitsiLocalStorage } from '@jitsi/js-utils';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import { safeJsonParse } from '@jitsi/js-utils/json';
import md5 from 'js-md5';
import logger from './logger';
@@ -193,7 +193,7 @@ class PersistenceRegistry {
if (persistedSubtree) {
try {
persistedSubtree = Bourne.parse(persistedSubtree);
persistedSubtree = safeJsonParse(persistedSubtree);
const filteredSubtree
= this._getFilteredSubtree(persistedSubtree, subtreeConfig);

View File

@@ -16,6 +16,11 @@ export interface IProps extends WithTranslation {
*/
afterClick?: Function;
/**
* The button's background color.
*/
backgroundColor?: string;
/**
* The button's key.
*/
@@ -108,6 +113,13 @@ export default class AbstractButton<P extends IProps, S=any> extends Component<P
visible: true
};
/**
* The button's background color.
*
* @abstract
*/
backgroundColor?: string;
/**
* A succinct description of what the button does. Used by accessibility
* tools and torture tests.

View File

@@ -9,6 +9,11 @@ import type { IProps as AbstractToolboxItemProps } from './AbstractToolboxItem';
interface IProps extends AbstractToolboxItemProps {
/**
* The button's background color.
*/
backgroundColor?: string;
/**
* Whether or not the item is displayed in a context menu.
*/
@@ -60,6 +65,7 @@ export default class ToolboxItem extends AbstractToolboxItem<IProps> {
*/
_renderItem() {
const {
backgroundColor,
contextMenu,
disabled,
elementAfter,
@@ -90,6 +96,7 @@ export default class ToolboxItem extends AbstractToolboxItem<IProps> {
return (
<ContextMenuItem
accessibilityLabel = { this.accessibilityLabel }
backgroundColor = { backgroundColor }
disabled = { disabled }
icon = { icon }
onClick = { onClick }
@@ -128,14 +135,18 @@ export default class ToolboxItem extends AbstractToolboxItem<IProps> {
* @returns {ReactElement}
*/
_renderIcon() {
const { customClass, disabled, icon, showLabel, toggled } = this.props;
const { backgroundColor, customClass, disabled, icon, showLabel, toggled } = this.props;
const iconComponent = (<Icon
size = { showLabel ? undefined : 24 }
src = { icon } />);
const elementType = showLabel ? 'span' : 'div';
const className = `${showLabel ? 'overflow-menu-item-icon' : 'toolbox-icon'} ${
toggled ? 'toggled' : ''} ${disabled ? 'disabled' : ''} ${customClass ?? ''}`;
const style = backgroundColor && !showLabel ? { backgroundColor } : {};
return React.createElement(elementType, { className }, iconComponent);
return React.createElement(elementType, {
className,
style
}, iconComponent);
}
}

View File

@@ -13,18 +13,23 @@ import { toggleScreenshotCaptureSummary } from '../../screenshot-capture/actions
import { isScreenshotCaptureEnabled } from '../../screenshot-capture/functions';
import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect';
import { getCurrentConference } from '../conference/functions';
import { openDialog } from '../dialog/actions';
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
import { setScreenshareMuted } from '../media/actions';
import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
import {
addLocalTrack,
replaceLocalTrack
replaceLocalTrack,
toggleCamera
} from './actions.any';
import AllowToggleCameraDialog from './components/web/AllowToggleCameraDialog';
import {
createLocalTracksF,
getLocalDesktopTrack,
getLocalJitsiAudioTrack
getLocalJitsiAudioTrack,
getLocalVideoTrack,
isToggleCameraEnabled
} from './functions';
import { IShareOptions, IToggleScreenSharingOptions } from './types';
@@ -263,3 +268,52 @@ async function _toggleScreenSharing(
APP.API.notifyScreenSharingStatusChanged(enable, screensharingDetails);
}
}
/**
* Sets the camera facing mode(environment/user). If facing mode not provided, it will do a toggle.
*
* @param {string | undefined} facingMode - The selected facing mode.
* @returns {void}
*/
export function setCameraFacingMode(facingMode: string | undefined) {
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
if (!isToggleCameraEnabled(state)) {
return;
}
if (!facingMode) {
dispatch(toggleCamera());
return;
}
const tracks = state['features/base/tracks'];
const localVideoTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
if (!tracks || !localVideoTrack) {
return;
}
const currentFacingMode = localVideoTrack.getCameraFacingMode();
if (currentFacingMode !== facingMode) {
dispatch(toggleCamera());
}
};
}
/**
* Signals to open the permission dialog for toggling camera remotely.
*
* @param {Function} onAllow - Callback to be executed if permission to toggle camera was granted.
* @param {string} initiatorId - The participant id of the requester.
* @returns {Object} - The open dialog action.
*/
export function openAllowToggleCameraDialog(onAllow: Function, initiatorId: string) {
return openDialog(AllowToggleCameraDialog, {
onAllow,
initiatorId
});
}

View File

@@ -0,0 +1,44 @@
import React from 'react';
import { WithTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { IReduxState } from '../../../../app/types';
import { translate } from '../../../i18n/functions';
import { getParticipantDisplayName } from '../../../participants/functions';
import Dialog from '../../../ui/components/web/Dialog';
interface IProps extends WithTranslation {
/**
* The participant id of the toggle camera requester.
*/
initiatorId: string;
/**
* Function to be invoked after permission to toggle camera granted.
*/
onAllow: () => void;
}
/**
* Dialog to allow toggling camera remotely.
*
* @returns {JSX.Element} - The allow toggle camera dialog.
*/
const AllowToggleCameraDialog = ({ onAllow, t, initiatorId }: IProps): JSX.Element => {
const initiatorName = useSelector((state: IReduxState) => getParticipantDisplayName(state, initiatorId));
return (
<Dialog
ok = {{ translationKey: 'dialog.allow' }}
onSubmit = { onAllow }
titleKey = 'dialog.allowToggleCameraTitle'>
<div>
{ t('dialog.allowToggleCameraDialog', { initiatorName }) }
</div>
</Dialog>
);
};
export default translate(AllowToggleCameraDialog);

View File

@@ -0,0 +1,4 @@
/**
* The payload name for remotely setting the camera facing mode message.
*/
export const CAMERA_FACING_MODE_MESSAGE = 'camera-facing-mode-message';

View File

@@ -16,6 +16,11 @@ export interface IProps {
*/
accessibilityLabel: string;
/**
* The context menu item background color.
*/
backgroundColor?: string;
/**
* Component children.
*/
@@ -163,6 +168,7 @@ const useStyles = makeStyles()(theme => {
const ContextMenuItem = ({
accessibilityLabel,
backgroundColor,
children,
className,
controls,
@@ -181,7 +187,7 @@ const ContextMenuItem = ({
textClassName }: IProps) => {
const { classes: styles, cx } = useStyles();
const _overflowDrawer: boolean = useSelector(showOverflowDrawer);
const style = backgroundColor ? { backgroundColor } : {};
const onKeyPressHandler = useCallback(e => {
// only trigger the fallback behavior (onClick) if we dont have any explicit keyboard event handler
if (onClick && !onKeyPress && !onKeyDown && (e.key === 'Enter' || e.key === ' ')) {
@@ -223,6 +229,7 @@ const ContextMenuItem = ({
onKeyDown = { disabled ? undefined : onKeyDown }
onKeyPress = { disabled ? undefined : onKeyPressHandler }
role = { onClick ? role : undefined }
style = { style }
tabIndex = { onClick ? tabIndex : undefined }>
{customIcon ? customIcon
: icon && <Icon

View File

@@ -260,6 +260,7 @@ export const commonStyles = (theme: Theme) => {
padding: 6,
textAlign: 'center' as const,
pointerEvents: 'all' as const,
display: 'flex',
boxShadow: '0px 2px 8px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.15)',
'& > div': {

View File

@@ -1,5 +1,5 @@
// @ts-expect-error
import Bourne from '@hapi/bourne';
// @ts-ignore
import { safeJsonParse } from '@jitsi/js-utils/json';
import { reportError } from './helpers';
@@ -62,7 +62,7 @@ export function parseURLParams(
if (!dontParse) {
const decoded = decodeURIComponent(value).replace(/\\&/, '&');
value = decoded === 'undefined' ? undefined : Bourne.parse(decoded);
value = decoded === 'undefined' ? undefined : safeJsonParse(decoded);
}
} catch (e: any) {
reportError(

View File

@@ -130,7 +130,10 @@ MiddlewareRegistry.register(store => next => action => {
// recipient. This logic tries to mitigate this risk.
const shouldSendPrivateMessageTo = _shouldSendPrivateMessageTo(state, action);
if (shouldSendPrivateMessageTo) {
const participantExists = shouldSendPrivateMessageTo
&& getParticipantById(state, shouldSendPrivateMessageTo);
if (shouldSendPrivateMessageTo && participantExists) {
dispatch(openDialog(ChatPrivacyDialog, {
message: action.message,
participantID: shouldSendPrivateMessageTo

View File

@@ -0,0 +1 @@
import './middleware.any';

View File

@@ -0,0 +1,45 @@
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
import { IJitsiConference } from '../base/conference/reducer';
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { openAllowToggleCameraDialog, setCameraFacingMode } from '../base/tracks/actions.web';
import { CAMERA_FACING_MODE_MESSAGE } from '../base/tracks/constants';
import './middleware.any';
MiddlewareRegistry.register(_store => next => action => {
switch (action.type) {
case CONFERENCE_JOINED: {
_addSetCameraFacingModeListener(action.conference);
break;
}
}
return next(action);
});
/**
* Registers listener for {@link JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED} that
* will perform various chat related activities.
*
* @param {IJitsiConference} conference - The conference.
* @returns {void}
*/
function _addSetCameraFacingModeListener(conference: IJitsiConference) {
conference.on(
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(...args: any) => {
if (args && args.length >= 2) {
const [ sender, eventData ] = args;
if (eventData.name === CAMERA_FACING_MODE_MESSAGE) {
APP.store.dispatch(openAllowToggleCameraDialog(
/* onAllow */ () => APP.store.dispatch(setCameraFacingMode(eventData.facingMode)),
/* initiatorId */ sender._id
));
}
}
}
);
}

View File

@@ -79,10 +79,8 @@ export function submitVideoDeviceSelectionTab(newState: any, isDisplayedOnWelcom
userSelectedCameraDeviceLabel:
getDeviceLabelById(getState(), newState.selectedVideoInputId, 'videoInput')
}));
dispatch(setVideoInputDevice(newState.selectedVideoInputId));
}
if (newState.localFlipX !== currentState.localFlipX) {
dispatch(updateSettings({
localFlipX: newState.localFlipX

View File

@@ -14,7 +14,7 @@ import {
getDeviceIdByLabel,
groupDevicesByKind
} from '../base/devices/functions.web';
import { isIosMobileBrowser } from '../base/environment/utils';
import { isIosMobileBrowser, isMobileBrowser } from '../base/environment/utils';
import JitsiMeetJS from '../base/lib-jitsi-meet';
import { toState } from '../base/redux/functions';
import {
@@ -97,7 +97,7 @@ export function getAudioDeviceSelectionDialogProps(stateful: IStateful, isDispla
export function getVideoDeviceSelectionDialogProps(stateful: IStateful, isDisplayedOnWelcomePage: boolean) {
// On mobile Safari because of https://bugs.webkit.org/show_bug.cgi?id=179363#c30, the old track is stopped
// by the browser when a new track is created for preview. That's why we are disabling all previews.
const disablePreviews = isIosMobileBrowser();
const disablePreviews = isMobileBrowser();
const state = toState(stateful);
const settings = state['features/base/settings'];

View File

@@ -43,6 +43,7 @@ import {
isStageFilmstripTopPanel,
shouldRemoteVideosBeVisible
} from '../../functions';
import { isFilmstripDisabled } from '../../functions.web';
import AudioTracksContainer from './AudioTracksContainer';
import Thumbnail from './Thumbnail';
@@ -74,6 +75,11 @@ interface IProps extends WithTranslation {
*/
_disableSelfView: boolean;
/**
* Whether vertical/horizontal filmstrip is disabled through config.
*/
_filmstripDisabled: boolean;
/**
* The height of the filmstrip.
*/
@@ -333,6 +339,7 @@ class Filmstrip extends PureComponent <IProps, IState> {
const {
_currentLayout,
_disableSelfView,
_filmstripDisabled,
_localScreenShareId,
_mainFilmstripVisible,
_resizableFilmstrip,
@@ -381,7 +388,8 @@ class Filmstrip extends PureComponent <IProps, IState> {
let toolbar = null;
if (!this.props._iAmRecorder && this.props._isFilmstripButtonEnabled
&& _currentLayout !== LAYOUTS.TILE_VIEW && (filmstripType === FILMSTRIP_TYPE.MAIN
&& _currentLayout !== LAYOUTS.TILE_VIEW
&& ((filmstripType === FILMSTRIP_TYPE.MAIN && !_filmstripDisabled)
|| (filmstripType === FILMSTRIP_TYPE.STAGE && _topPanelFilmstrip))) {
toolbar = this._renderToggleButton();
}
@@ -885,6 +893,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
const { isOpen: shiftRight } = state['features/chat'];
const disableSelfView = getHideSelfView(state);
const { clientWidth, clientHeight } = state['features/base/responsive-ui'];
const filmstripDisabled = isFilmstripDisabled(state);
const collapseTileView = reduceHeight
&& isMobileBrowser()
@@ -893,7 +902,8 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
const shouldReduceHeight = reduceHeight && isMobileBrowser();
const _topPanelVisible = isStageFilmstripTopPanel(state) && topPanelVisible;
let isVisible = visible || filmstripType !== FILMSTRIP_TYPE.MAIN;
const notDisabled = visible && !filmstripDisabled;
let isVisible = notDisabled || filmstripType !== FILMSTRIP_TYPE.MAIN;
if (_topPanelFilmstrip) {
isVisible = _topPanelVisible;
@@ -912,13 +922,14 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
_chatOpen: state['features/chat'].isOpen,
_currentLayout,
_disableSelfView: disableSelfView,
_filmstripDisabled: filmstripDisabled,
_hasScroll,
_iAmRecorder: Boolean(iAmRecorder),
_isFilmstripButtonEnabled: isButtonEnabled('filmstrip', state),
_isToolboxVisible: isToolboxVisible(state),
_isVerticalFilmstrip,
_localScreenShareId: localScreenShare?.id,
_mainFilmstripVisible: visible,
_mainFilmstripVisible: notDisabled,
_maxFilmstripWidth: clientWidth - MIN_STAGE_VIEW_WIDTH,
_maxTopPanelHeight: clientHeight - MIN_STAGE_VIEW_HEIGHT,
_remoteParticipantsLength: _remoteParticipants?.length ?? 0,

View File

@@ -1,4 +1,4 @@
import { IStore } from '../app/types';
import { IReduxState, IStore } from '../app/types';
import {
getActiveSpeakersToBeDisplayed,
getVirtualScreenshareParticipantOwnerId
@@ -95,3 +95,15 @@ export function updateRemoteParticipantsOnLeave(store: IStore, participantId: st
reorderedParticipants.delete(participantId)
&& store.dispatch(setRemoteParticipants(Array.from(reorderedParticipants)));
}
/**
* Returns whether tileview is completely disabled.
*
* @param {IReduxState} state - Redux state.
* @returns {boolean} - Whether tileview is completely disabled.
*/
export function isTileViewModeDisabled(state: IReduxState) {
const { tileView = {} } = state['features/base/config'];
return tileView.disabled;
}

View File

@@ -763,6 +763,18 @@ export function isStageFilmstripEnabled(state: IReduxState) {
return Boolean(!filmstrip?.disableStageFilmstrip && interfaceConfig.VERTICAL_FILMSTRIP);
}
/**
* Whether the vertical/horizontal filmstrip is disabled.
*
* @param {Object} state - Redux state.
* @returns {boolean}
*/
export function isFilmstripDisabled(state: IReduxState) {
const { filmstrip } = state['features/base/config'];
return Boolean(filmstrip?.disabled);
}
/**
* Gets the thumbnail type by filmstrip type.
*

View File

@@ -120,7 +120,7 @@ MiddlewareRegistry.register(store => next => action => {
case SETTINGS_UPDATED: {
if (typeof action.settings?.localFlipX === 'boolean') {
// TODO: This needs to be removed once the large video is Reactified.
VideoLayout.onLocalFlipXChanged();
VideoLayout.onLocalFlipXChanged(action.settings.localFlipX);
}
if (action.settings?.disableSelfView) {
const state = store.getState();

View File

@@ -56,7 +56,7 @@ const NumbersList: React.FC<IProps> = ({ t, conferenceID, clickableNumbers, numb
if (countryCode) {
return (
<td className = 'flag-cell'>
{countryCode === 'SIP'
{countryCode === 'SIP' || countryCode === 'SIP_AUDIO_ONLY'
? <Icon src = { IconSip } />
: <i className = { `flag iti-flag ${countryCode}` } />
}
@@ -129,6 +129,8 @@ const NumbersList: React.FC<IProps> = ({ t, conferenceID, clickableNumbers, numb
if (countryCode === 'SIP') {
countryName = t('info.sip');
} else if (countryCode === 'SIP_AUDIO_ONLY') {
countryName = t('info.sipAudioOnly');
} else {
countryName = t(`countries:countries.${countryCode}`);

View File

@@ -20,10 +20,10 @@ export default function BreakoutRoomNamePrompt({ breakoutRoomJid, initialRoomNam
const okDisabled = !roomName;
const dispatch = useDispatch();
const onBreakoutRoomNameChange = useCallback((newRoomName: string) => {
setRoomName(newRoomName?.trim());
setRoomName(newRoomName);
}, [ setRoomName ]);
const onSubmit = useCallback(() => {
dispatch(renameBreakoutRoom(breakoutRoomJid, roomName));
dispatch(renameBreakoutRoom(breakoutRoomJid, roomName?.trim()));
}, [ breakoutRoomJid, dispatch, roomName ]);
return (<Dialog

View File

@@ -476,7 +476,11 @@ export function endpointMessageReceived(participantId: string, message: {
if (type === EVENTS.stop) {
dispatch(stopReceiver(false, true));
} else { // forward the message
transport?.sendEvent(message);
try {
transport?.sendEvent(message);
} catch (error) {
logger.error('Error while trying to execute remote control message', error);
}
}
} // else ignore
} else {

View File

@@ -212,15 +212,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
cancel: () => {
const { options } = getVirtualBackgroundTabProps(state, isDisplayedOnWelcomePage);
return submitVirtualBackgroundTab({
options: {
backgroundType: options.backgroundType,
enabled: options.backgroundEffectEnabled,
url: options.virtualSource,
selectedThumbnail: options.selectedThumbnail,
blurValue: options.blurValue
}
}, true);
return submitVirtualBackgroundTab({ options }, true);
},
icon: IconImage
});

View File

@@ -4,6 +4,7 @@ import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/too
interface IProps extends AbstractButtonProps {
backgroundColor?: string;
icon: string;
id?: string;
text: string;
@@ -18,6 +19,7 @@ class CustomOptionButton extends AbstractButton<IProps> {
iconSrc = this.props.icon;
id = this.props.id;
text = this.props.text;
backgroundColor = this.props.backgroundColor;
accessibilityLabel = this.text;

View File

@@ -103,14 +103,13 @@ class ProfileButton extends AbstractButton<IProps> {
* @returns {Object}
*/
const mapStateToProps = (state: IReduxState) => {
const { defaultLocalDisplayName, disableProfile } = state['features/base/config'];
const { defaultLocalDisplayName } = state['features/base/config'];
return {
_defaultLocalDisplayName: defaultLocalDisplayName ?? '',
_localParticipant: getLocalParticipant(state),
_unclickable: !interfaceConfig.SETTINGS_SECTIONS.includes('profile'),
customClass: 'profile-button-avatar',
visible: !disableProfile
customClass: 'profile-button-avatar'
};
};

View File

@@ -63,7 +63,7 @@ interface IProps extends WithTranslation {
/**
* Custom Toolbar buttons.
*/
_customToolbarButtons?: Array<{ icon: string; id: string; text: string; }>;
_customToolbarButtons?: Array<{ backgroundColor?: string; icon: string; id: string; text: string; }>;
/**
* Whether or not a dialog is displayed.
@@ -290,7 +290,7 @@ const Toolbox = ({
setButtonsNotifyClickMode(buttons);
const isHangupVisible = isToolbarButtonEnabled('hangup', _toolbarButtons);
let { order } = THRESHOLDS.find(({ width }) => _clientWidth > width)
const { order } = THRESHOLDS.find(({ width }) => _clientWidth > width)
|| THRESHOLDS[THRESHOLDS.length - 1];
const keys = Object.keys(buttons);
@@ -302,11 +302,8 @@ const Toolbox = ({
!_jwtDisabledButtons.includes(key)
&& (isToolbarButtonEnabled(key, _toolbarButtons) || isToolbarButtonEnabled(alias, _toolbarButtons))
);
const filteredKeys = filtered.map(button => button.key);
order = order.filter(key => filteredKeys.includes(buttons[key as keyof typeof buttons].key));
let sliceIndex = order.length + 2;
let sliceIndex = _overflowDrawer || _reactionsButtonEnabled ? order.length + 2 : order.length + 1;
if (isHangupVisible) {
sliceIndex -= 1;

View File

@@ -199,6 +199,7 @@ export function getToolbarTimeout(state: IReduxState) {
* @returns {Object} The button maps mainMenuButtons and overflowMenuButtons.
*/
export function getAllToolboxButtons(_customToolbarButtons?: {
backgroundColor?: string;
icon: string;
id: string;
text: string;
@@ -395,10 +396,11 @@ export function getAllToolboxButtons(_customToolbarButtons?: {
group: 4
};
const customButtons = _customToolbarButtons?.reduce((prev, { icon, id, text }) => {
const customButtons = _customToolbarButtons?.reduce((prev, { backgroundColor, icon, id, text }) => {
return {
...prev,
[id]: {
backgroundColor,
key: id,
Content: CustomOptionButton,
group: 4,

View File

@@ -42,9 +42,8 @@ export const useKeyboardShortcuts = (toolbarButtons: Array<string>) => {
const desktopSharingEnabled = JitsiMeetJS.isDesktopSharingEnabled();
const fullScreen = useSelector((state: IReduxState) => state['features/toolbox'].fullScreen);
const gifsEnabled = useSelector(isGifEnabled);
const localParticipant = useSelector(getLocalParticipant);
const participantsPaneOpen = useSelector(getParticipantsPaneOpen);
const raisedHand = hasRaisedHand(localParticipant);
const raisedHand = useSelector((state: IReduxState) => hasRaisedHand(getLocalParticipant(state)));
const reactionsEnabled = useSelector(isReactionsEnabled);
const screenSharing = useSelector(isScreenVideoShared);
const tileViewEnabled = useSelector(shouldDisplayTileView);
@@ -306,5 +305,15 @@ export const useKeyboardShortcuts = (toolbarButtons: Array<string>) => {
dispatch(unregisterShortcut(letter, true)));
}
};
}, []);
}, [
chatOpen,
desktopSharingButtonDisabled,
desktopSharingEnabled,
fullScreen,
gifsEnabled,
participantsPaneOpen,
raisedHand,
screenSharing,
tileViewEnabled
]);
};

View File

@@ -1,4 +1,5 @@
import { IStore } from '../app/types';
import { isTileViewModeDisabled } from '../filmstrip/functions.any';
import {
SET_TILE_VIEW,
@@ -34,9 +35,13 @@ export function virtualScreenshareParticipantsUpdated(participantIds: Array<stri
* }}
*/
export function setTileView(enabled?: boolean) {
return {
type: SET_TILE_VIEW,
enabled
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const tileViewDisabled = isTileViewModeDisabled(getState());
!tileViewDisabled && dispatch({
type: SET_TILE_VIEW,
enabled
});
};
}

View File

@@ -4,7 +4,7 @@ import { getFeatureFlag } from '../base/flags/functions';
import { pinParticipant } from '../base/participants/actions';
import { getParticipantCount, getPinnedParticipant } from '../base/participants/functions';
import { FakeParticipant } from '../base/participants/types';
import { isStageFilmstripAvailable } from '../filmstrip/functions';
import { isStageFilmstripAvailable, isTileViewModeDisabled } from '../filmstrip/functions';
import { isVideoPlaying } from '../shared-video/functions';
import { VIDEO_QUALITY_LEVELS } from '../video-quality/constants';
import { getReceiverVideoQualityLevel } from '../video-quality/functions';
@@ -60,6 +60,12 @@ export function getCurrentLayout(state: IReduxState) {
* @returns {boolean} True if tile view should be displayed.
*/
export function shouldDisplayTileView(state: IReduxState) {
const tileViewDisabled = isTileViewModeDisabled(state);
if (tileViewDisabled) {
return false;
}
const { tileViewEnabled } = state['features/video-layout'] ?? {};
if (tileViewEnabled !== undefined) {

View File

@@ -3,7 +3,7 @@ import { createVirtualBackgroundEffect } from '../stream-effects/virtual-backgro
import { BACKGROUND_ENABLED, SET_VIRTUAL_BACKGROUND } from './actionTypes';
import logger from './logger';
import { IVirtualBackgroundOptions } from './types';
import { IVirtualBackground } from './reducer';
/**
* Signals the local participant activate the virtual background video or not.
@@ -12,16 +12,16 @@ import { IVirtualBackgroundOptions } from './types';
* @param {Object} jitsiTrack - Represents the jitsi track that will have backgraund effect applied.
* @returns {Promise}
*/
export function toggleBackgroundEffect(options: IVirtualBackgroundOptions, jitsiTrack: any) {
export function toggleBackgroundEffect(options: IVirtualBackground, jitsiTrack: any) {
return async function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
await dispatch(backgroundEnabled(options.enabled));
await dispatch(backgroundEnabled(options.backgroundEffectEnabled));
await dispatch(setVirtualBackground(options));
const state = getState();
const virtualBackground = state['features/virtual-background'];
if (jitsiTrack) {
try {
if (options.enabled) {
if (options.backgroundEffectEnabled) {
await jitsiTrack.setEffect(await createVirtualBackgroundEffect(virtualBackground, dispatch));
} else {
await jitsiTrack.setEffect(undefined);
@@ -46,10 +46,10 @@ export function toggleBackgroundEffect(options: IVirtualBackgroundOptions, jitsi
* type: string,
* }}
*/
export function setVirtualBackground(options?: IVirtualBackgroundOptions) {
export function setVirtualBackground(options?: IVirtualBackground) {
return {
type: SET_VIRTUAL_BACKGROUND,
virtualSource: options?.url,
virtualSource: options?.virtualSource,
blurValue: options?.blurValue,
backgroundType: options?.backgroundType,
selectedThumbnail: options?.selectedThumbnail
@@ -65,7 +65,7 @@ export function setVirtualBackground(options?: IVirtualBackgroundOptions) {
* backgroundEffectEnabled: boolean
* }}
*/
export function backgroundEnabled(backgroundEffectEnabled: boolean) {
export function backgroundEnabled(backgroundEffectEnabled?: boolean) {
return {
type: BACKGROUND_ENABLED,
backgroundEffectEnabled

View File

@@ -105,10 +105,10 @@ function UploadImageButton({
}
]);
setOptions({
backgroundEffectEnabled: true,
backgroundType: VIRTUAL_BACKGROUND_TYPE.IMAGE,
enabled: true,
url,
selectedThumbnail: uuId
selectedThumbnail: uuId,
virtualSource: url
});
};
logger.info('New virtual background image uploaded!');

View File

@@ -15,6 +15,7 @@ import { showWarningNotification } from '../../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
import { toggleBackgroundEffect } from '../actions';
import logger from '../logger';
import { IVirtualBackground } from '../reducer';
/**
* The type of the React {@code PureComponent} props of {@link VirtualBackgroundPreview}.
@@ -39,7 +40,7 @@ export interface IProps extends WithTranslation {
/**
* Represents the virtual background set options.
*/
options: any;
options: IVirtualBackground;
/**
* The id of the selected video device.

View File

@@ -1,8 +1,8 @@
// @ts-expect-error
import Bourne from '@hapi/bourne';
// eslint-disable-next-line lines-around-comment
// @ts-expect-error
// @ts-ignore
import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import { safeJsonParse } from '@jitsi/js-utils/json';
import React, { useCallback, useEffect, useState } from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
@@ -19,6 +19,7 @@ import Spinner from '../../base/ui/components/web/Spinner';
import { BACKGROUNDS_LIMIT, IMAGES, type Image, VIRTUAL_BACKGROUND_TYPE } from '../constants';
import { toDataURL } from '../functions';
import logger from '../logger';
import { IVirtualBackground } from '../reducer';
import UploadImageButton from './UploadImageButton';
import VirtualBackgroundPreview from './VirtualBackgroundPreview';
@@ -72,7 +73,7 @@ interface IProps extends WithTranslation {
/**
* Virtual background options.
*/
options: any;
options: IVirtualBackground;
/**
* Returns the selected thumbnail identifier.
@@ -217,7 +218,7 @@ function VirtualBackgrounds({
const { classes, cx } = useStyles();
const [ previewIsLoaded, setPreviewIsLoaded ] = useState(false);
const localImages = jitsiLocalStorage.getItem('virtualBackgrounds');
const [ storedImages, setStoredImages ] = useState<Array<Image>>((localImages && Bourne.parse(localImages)) || []);
const [ storedImages, setStoredImages ] = useState<Array<Image>>((localImages && safeJsonParse(localImages)) || []);
const [ loading, setLoading ] = useState(false);
useEffect(() => {
@@ -254,8 +255,8 @@ function VirtualBackgrounds({
const enableBlur = useCallback(async () => {
onOptionsChange({
backgroundEffectEnabled: true,
backgroundType: VIRTUAL_BACKGROUND_TYPE.BLUR,
enabled: true,
blurValue: 25,
selectedThumbnail: 'blur'
});
@@ -272,8 +273,8 @@ function VirtualBackgrounds({
const enableSlideBlur = useCallback(async () => {
onOptionsChange({
backgroundEffectEnabled: true,
backgroundType: VIRTUAL_BACKGROUND_TYPE.BLUR,
enabled: true,
blurValue: 8,
selectedThumbnail: 'slight-blur'
});
@@ -290,7 +291,7 @@ function VirtualBackgrounds({
const removeBackground = useCallback(async () => {
onOptionsChange({
enabled: false,
backgroundEffectEnabled: false,
selectedThumbnail: 'none'
});
logger.info('"None" option set for virtual background preview!');
@@ -310,10 +311,10 @@ function VirtualBackgrounds({
if (image) {
onOptionsChange({
backgroundType: 'image',
enabled: true,
url: image.src,
selectedThumbnail: image.id
backgroundEffectEnabled: true,
backgroundType: VIRTUAL_BACKGROUND_TYPE.IMAGE,
selectedThumbnail: image.id,
virtualSource: image.src
});
logger.info('Uploaded image set for virtual background preview!');
}
@@ -328,10 +329,10 @@ function VirtualBackgrounds({
const url = await toDataURL(image.src);
onOptionsChange({
backgroundType: 'image',
enabled: true,
url,
selectedThumbnail: image.id
backgroundEffectEnabled: true,
backgroundType: VIRTUAL_BACKGROUND_TYPE.IMAGE,
selectedThumbnail: image.id,
virtualSource: url
});
logger.info('Image set for virtual background preview!');
} catch (err) {

View File

@@ -1,6 +0,0 @@
import { IVirtualBackground } from './reducer';
export interface IVirtualBackgroundOptions extends IVirtualBackground {
enabled: boolean;
url?: string;
}

View File

@@ -339,7 +339,7 @@ module.exports = (_env, argv) => {
...config.plugins,
...getBundleAnalyzerPlugin(analyzeBundle, 'external_api')
],
performance: getPerformanceHints(perfHintOptions, 35 * 1024)
performance: getPerformanceHints(perfHintOptions, 40 * 1024)
}),
Object.assign({}, config, {
entry: {